Skip to content

Commit 8a7fc3c

Browse files
committed
fix(presets): enforce that the served repo is a variant of the requested base
A preset declaring `base: X` was verified against two things that a substitution preserves: that the service advertises the requested name, and that the report echoes the requested base. Neither looks at the repo the agent actually served. So `vllm serve Qwen/Qwen3.5-27B-GPTQ-Int4 --served-model-name Qwen/Qwen3.8-27B` answered a request for Qwen3.8 with a different model generation, verified clean, and was reported successful. `base` is documented as "the base model for which the agent may select a compatible variant", so verification now checks compatibility: the served repo must be the base, or the base plus a suffix at a separator boundary. A different generation is not a variant however similar the name. The comparison is on the model name alone, ignoring the owner. A quantisation is routinely published by someone other than the model's author -- this repository's own fixtures pair a Qwen/Qwen3.5-27B base with a community/Qwen3.5-27B-GPTQ-Int4 repo -- so comparing owners would reject the ordinary case and cost `base` the freedom it exists to grant. It is also case-insensitive, since repo references are. Eight tests: four substitutions that must be rejected, including the reported one and a name that merely starts the same, and four genuine variants that must still pass, including the third-party quantisation and a lowercased reference.
1 parent e828c9e commit 8a7fc3c

2 files changed

Lines changed: 119 additions & 0 deletions

File tree

‎src/dstack/_internal/cli/services/presets/verify.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,46 @@ def _verified_run_service(run: Run, report: PresetAgentSuccess) -> ServiceConfig
135135
return service
136136

137137

138+
#: Characters that may follow the base name in a variant repo. A variant adds a
139+
#: suffix — a quantisation, a format, a precision — and these are what separate
140+
#: it, so requiring one stops ``Qwen3.5-27B`` from accepting ``Qwen3.5-27Bx``.
141+
_VARIANT_SUFFIX_SEPARATORS = ("-", "_", ".")
142+
143+
144+
def _model_name(repo: str) -> str:
145+
"""The model half of a repo reference, without its owner.
146+
147+
Deliberately owner-blind. A quantisation of a model is routinely published
148+
by someone other than the original author — this repository's own fixtures
149+
pair a ``Qwen/Qwen3.5-27B`` base with a ``community/Qwen3.5-27B-GPTQ-Int4``
150+
repo — so requiring the owner to match would reject the ordinary case.
151+
"""
152+
return repo.rsplit("/", 1)[-1].strip()
153+
154+
155+
def _is_variant_of(repo: str, base: str) -> bool:
156+
"""Is ``repo`` a variant of ``base``, rather than a different model?
157+
158+
A variant is the base plus a suffix: ``Qwen3.5-27B`` also answers to
159+
``Qwen3.5-27B-GPTQ-Int4`` and ``Qwen3.5-27B-AWQ``. A different generation
160+
is not a variant, however similar the name — ``Qwen3.8-27B`` does not
161+
answer a request for ``Qwen3.5-27B``, which is exactly the substitution
162+
that verified clean before.
163+
164+
Compared case-insensitively, and on the model name alone, so the check
165+
stays about which model was served rather than about who published it.
166+
"""
167+
served = _model_name(repo).lower()
168+
wanted = _model_name(base).lower()
169+
if not served or not wanted:
170+
return False
171+
if served == wanted:
172+
return True
173+
if not served.startswith(wanted):
174+
return False
175+
return served[len(wanted)] in _VARIANT_SUFFIX_SEPARATORS
176+
177+
138178
def _check_report_answers_request(
139179
report: PresetAgentSuccess, configuration: PresetConfiguration
140180
) -> None:
@@ -145,6 +185,17 @@ def _check_report_answers_request(
145185
if configuration.model.allows_variant_selection:
146186
if report.base != configuration.model.api_model_name:
147187
raise CLIError("Claude final report base does not match the requested model")
188+
# The base must constrain which repos are acceptable, and echoing it
189+
# back does not: the served repo is what the agent chose, and it was
190+
# only ever checked against the *advertised* name — which a
191+
# substitution preserves. `vllm serve Qwen/Qwen3.5-27B-GPTQ-Int4
192+
# --served-model-name Qwen/Qwen3.8-27B` answered a request for
193+
# Qwen3.8 with a different model generation and verified clean.
194+
if not _is_variant_of(report.model, configuration.model.api_model_name):
195+
raise CLIError(
196+
f"Claude served {report.model!r}, which is not a variant of the requested"
197+
f" base {configuration.model.api_model_name!r}"
198+
)
148199
elif report.model != configuration.model.exact_repo:
149200
raise CLIError("Claude changed an exact model request")
150201

‎src/tests/_internal/cli/services/presets/test_verify.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,74 @@ def test_rejects_benchmark_on_a_different_dataset(self, tmp_path, reported):
304304
created_at=datetime(2026, 1, 2, 3, 4, tzinfo=timezone.utc),
305305
)
306306

307+
@pytest.mark.parametrize(
308+
"served",
309+
[
310+
"Qwen/Qwen3.8-27B-GPTQ-Int4", # the reported substitution: another generation
311+
"Qwen/Qwen3.8-27B",
312+
"meta-llama/Llama-3-8B", # another family entirely
313+
"Qwen/Qwen3.5-27Bx", # a longer name that merely starts the same
314+
],
315+
)
316+
def test_rejects_a_served_repo_that_is_not_a_variant_of_the_base(self, tmp_path, served):
317+
"""`base` has to constrain which repos are acceptable.
318+
319+
Verification checked that the service advertised the requested name and
320+
that the report echoed the requested base — both of which a substitution
321+
preserves. `vllm serve Qwen/Qwen3.5-27B-GPTQ-Int4 --served-model-name
322+
Qwen/Qwen3.8-27B` answered a request for Qwen3.8 with a different model
323+
generation and verified clean.
324+
"""
325+
run = get_running_service_run()
326+
report = get_successful_preset_report(run).model_copy(update={"model": served})
327+
328+
# Both values named, as elsewhere here: "not a variant" alone is not actionable.
329+
with pytest.raises(CLIError, match="is not a variant of the requested base"):
330+
build_verified_preset(
331+
run=run,
332+
preset_configuration=PresetConfiguration(
333+
name="qwen-build", base="Qwen/Qwen3.5-27B"
334+
),
335+
report=report,
336+
workspace_path=tmp_path,
337+
session_path=tmp_path,
338+
preset_id="ab12cd34",
339+
name=None,
340+
created_at=datetime(2026, 1, 2, 3, 4, tzinfo=timezone.utc),
341+
)
342+
343+
@pytest.mark.parametrize(
344+
"served",
345+
[
346+
"community/Qwen3.5-27B-GPTQ-Int4", # a quantisation by another publisher
347+
"Qwen/Qwen3.5-27B-AWQ",
348+
"Qwen/Qwen3.5-27B", # the base itself
349+
"qwen/qwen3.5-27b-gptq-int4", # repo references are not case-sensitive
350+
],
351+
)
352+
def test_accepts_a_genuine_variant_of_the_base(self, tmp_path, served):
353+
"""The check must not cost the freedom `base` exists to grant.
354+
355+
A quantisation is routinely published by someone other than the model's
356+
author, so the owner is deliberately not compared — only the model name.
357+
"""
358+
run = get_running_service_run()
359+
report = get_successful_preset_report(run).model_copy(update={"model": served})
360+
361+
preset = build_verified_preset(
362+
run=run,
363+
preset_configuration=PresetConfiguration(name="qwen-build", base="Qwen/Qwen3.5-27B"),
364+
report=report,
365+
workspace_path=tmp_path,
366+
session_path=tmp_path,
367+
preset_id="ab12cd34",
368+
name=None,
369+
created_at=datetime(2026, 1, 2, 3, 4, tzinfo=timezone.utc),
370+
)
371+
372+
assert preset.repo == served
373+
assert preset.base == "Qwen/Qwen3.5-27B"
374+
307375
def test_rejects_variant_for_exact_model_request(self, tmp_path):
308376
run = get_running_service_run()
309377
report = get_successful_preset_report(run).model_copy(update={"model": "other/model"})

0 commit comments

Comments
 (0)