Skip to content

Commit e5e2a0a

Browse files
committed
fix(vllm): reject same launch flag in both args and extraArgs
`buildVLLMArgs` renders `spec.engine.args` first, then appends `spec.engine.extraArgs` verbatim. When the same flag key was set in both (e.g. `tensor-parallel-size: "4"` in args and `--tensor-parallel-size=2` in extraArgs), the rendered Deployment carried two conflicting copies. vLLM's argparse is last-wins, so it silently honored the extraArgs value and defeated the `engine.args` one. - Add `validateDuplicateVLLMArgKeys`, called at the top of `buildVLLMArgs` (covers both aggregated and disaggregated paths), which fails with a clear error naming the flag and both fields instead of guessing a winner. Flags that legitimately repeat live only in `extraArgs` and are untouched. - Add regression tests: reject the cross-source conflict in both inline (`--flag=v`) and two-token (`--flag v`) forms, and confirm an `extraArgs`-only flag is still allowed. Signed-off-by: Suraj Deshmukh <suraj.deshmukh@microsoft.com>
1 parent 096c4e2 commit e5e2a0a

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

providers/vllm/transformer.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ func (t *Transformer) buildVLLMArgs(md *airunwayv1alpha1.ModelDeployment, kvTran
397397
return nil, err
398398
}
399399

400+
if err := validateDuplicateVLLMArgKeys(md.Spec.Engine.Args, md.Spec.Engine.ExtraArgs); err != nil {
401+
return nil, err
402+
}
403+
400404
// A flag we derive from structured spec fields (e.g. --tensor-parallel-size
401405
// from the GPU count) must yield to an explicit override of the same key,
402406
// whether it is provided via spec.engine.args (map) or spec.engine.extraArgs
@@ -509,6 +513,33 @@ func validateReservedVLLMServerArgs(engineArgs map[string]string, extraArgs []st
509513
return nil
510514
}
511515

516+
// validateDuplicateVLLMArgKeys rejects a launch flag that is set in BOTH
517+
// spec.engine.args (the structured map) and spec.engine.extraArgs (raw tokens).
518+
// engine.args is a map, so a key can appear there at most once; finding the same
519+
// key again in extraArgs is an unambiguous contradiction. We render engine.args
520+
// first and then append extraArgs verbatim, so without this guard the rendered
521+
// command would carry two conflicting copies of the flag (e.g.
522+
// "--tensor-parallel-size 4 … --tensor-parallel-size=2"). vLLM's argparse is
523+
// last-wins, so it would silently honor the extraArgs value and defeat the
524+
// engine.args one. Surface the conflict as a clear error instead of guessing a
525+
// winner; the user removes one of the two settings. Flags that legitimately
526+
// repeat live only in extraArgs and are untouched by this check.
527+
func validateDuplicateVLLMArgKeys(engineArgs map[string]string, extraArgs []string) error {
528+
if len(engineArgs) == 0 {
529+
return nil
530+
}
531+
for _, arg := range extraArgs {
532+
key, ok := extraArgKey(arg)
533+
if !ok {
534+
continue
535+
}
536+
if _, dup := engineArgs[key]; dup {
537+
return fmt.Errorf("launch flag %q is set in both spec.engine.args and spec.engine.extraArgs (%q); set it in exactly one place so vLLM does not receive conflicting values", key, arg)
538+
}
539+
}
540+
return nil
541+
}
542+
512543
func isReservedVLLMServerArg(key string) bool {
513544
// Normalize the key the same way for both the engineArgs map form ("port")
514545
// and a user that writes the flag form as a map key ("--port") or with an

providers/vllm/transformer_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,54 @@ func TestBuildVLLMArgsTensorParallelDedupViaExtraArgs(t *testing.T) {
12511251
}
12521252
}
12531253

1254+
// Setting the SAME launch flag in both spec.engine.args (the structured map) and
1255+
// spec.engine.extraArgs (raw tokens) is a contradiction: engine.args renders
1256+
// first and extraArgs is appended verbatim, so both would reach vLLM and its
1257+
// last-wins argparse would silently honor the extraArgs value — defeating the
1258+
// engine.args one. The transformer must reject this instead of emitting two
1259+
// conflicting copies. Regression for the --tensor-parallel-size 4-vs-2 bug.
1260+
func TestBuildVLLMArgsRejectsArgsExtraArgsConflict(t *testing.T) {
1261+
for _, tc := range []struct {
1262+
name string
1263+
extraArg string
1264+
}{
1265+
{name: "inline value form", extraArg: "--tensor-parallel-size=2"},
1266+
{name: "two-token form", extraArg: "--tensor-parallel-size"},
1267+
} {
1268+
t.Run(tc.name, func(t *testing.T) {
1269+
tr := NewTransformer()
1270+
md := newTestMD("test-model", "default")
1271+
md.Spec.Engine.Args = map[string]string{
1272+
"tensor-parallel-size": "4",
1273+
}
1274+
md.Spec.Engine.ExtraArgs = []string{tc.extraArg}
1275+
1276+
_, err := tr.buildVLLMArgs(md, "", 0)
1277+
if err == nil {
1278+
t.Fatalf("expected an error for a flag set in both engine.args and extraArgs")
1279+
}
1280+
for _, want := range []string{"tensor-parallel-size", "spec.engine.args", "spec.engine.extraArgs"} {
1281+
if !strings.Contains(err.Error(), want) {
1282+
t.Errorf("error %q should mention %q", err.Error(), want)
1283+
}
1284+
}
1285+
})
1286+
}
1287+
}
1288+
1289+
// A flag that is present ONLY in extraArgs (not also in engine.args) is a valid,
1290+
// legitimately-repeatable override and must NOT be rejected by the duplicate
1291+
// guard — only the cross-source contradiction is an error.
1292+
func TestBuildVLLMArgsAllowsExtraArgsOnlyFlag(t *testing.T) {
1293+
tr := NewTransformer()
1294+
md := newTestMD("test-model", "default")
1295+
md.Spec.Engine.ExtraArgs = []string{"--tensor-parallel-size=2"}
1296+
1297+
if _, err := tr.buildVLLMArgs(md, "", 0); err != nil {
1298+
t.Fatalf("unexpected error for an extraArgs-only flag: %v", err)
1299+
}
1300+
}
1301+
12541302
// The same dedup must hold for the other derived flags so an explicit engine.args
12551303
// entry is never duplicated by the structured-field renderer.
12561304
func TestBuildVLLMArgsDerivedFlagDedup(t *testing.T) {

0 commit comments

Comments
 (0)