[Config Refactor] sentinel default precedence (#3078)
Signed-off-by: lishunyang <lishunyang12@163.com> Signed-off-by: xiaohajiayou <923390377@qq.com> Co-authored-by: lishunyang <lishunyang12@163.com>
This commit is contained in:
1 parent
b33d536f63
commit
22ffb85063
29 files changed
+458
-188
No files matched your search
@@ -4,6 +4,7 @@
|
||||
Unit tests for StageConfigFactory and related classes.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -1196,99 +1197,89 @@ class TestCLIOverrideFlow:
|
||||
assert s.runtime_overrides["enforce_eager"] is True
|
||||
|
||||
|
||||
class TestCLIExplicitPrecedence:
|
||||
"""Verify YAML > argparse defaults; explicit CLI args > YAML."""
|
||||
class TestSentinelDefaultPrecedence:
|
||||
"""Caller-typed (non-None) values win over YAML; None values fall through
|
||||
to YAML / dataclass defaults (#3035)."""
|
||||
|
||||
def _stages(self, cli_overrides, cli_explicit_keys):
|
||||
def _stages(self, cli_overrides):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
return StageConfigFactory._create_from_registry(
|
||||
"qwen3_omni_moe",
|
||||
cli_overrides=cli_overrides,
|
||||
cli_explicit_keys=cli_explicit_keys,
|
||||
)
|
||||
|
||||
def test_explicit_cli_overrides_yaml(self):
|
||||
"""User-typed --max-num-seqs wins over the deploy YAML value."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 999},
|
||||
cli_explicit_keys={"max_num_seqs"},
|
||||
)
|
||||
# Stage 2 yaml has max_num_seqs=1; explicit CLI must beat it.
|
||||
def test_typed_kwarg_overrides_yaml(self):
|
||||
stages = self._stages({"max_num_seqs": 999})
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") == 999
|
||||
|
||||
def test_default_cli_does_not_override_yaml(self):
|
||||
"""Argparse defaults must NOT clobber values that are present in YAML."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 256},
|
||||
cli_explicit_keys=set(), # user typed nothing
|
||||
)
|
||||
# Stage 2's YAML value (1) should win because the user didn't type --max-num-seqs.
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") != 256
|
||||
def test_none_value_skipped_yaml_wins(self):
|
||||
stages = self._stages({"max_num_seqs": None})
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") is None
|
||||
assert stages[2].yaml_engine_args.get("max_num_seqs") == 1
|
||||
|
||||
def test_default_cli_fills_missing_yaml_field(self):
|
||||
"""Argparse defaults still fill fields the YAML doesn't set."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"some_unrelated_knob": "fallback"},
|
||||
cli_explicit_keys=set(),
|
||||
)
|
||||
# Field absent from YAML → CLI default flows through as a fallback.
|
||||
assert stages[0].runtime_overrides.get("some_unrelated_knob") == "fallback"
|
||||
def test_empty_kwargs_yaml_only(self):
|
||||
stages = self._stages({})
|
||||
for stage in stages:
|
||||
assert stage.runtime_overrides == {}
|
||||
|
||||
def test_per_stage_overrides_always_explicit(self):
|
||||
"""``stage_<id>_*`` keys are always treated as explicit."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"stage_0_gpu_memory_utilization": 0.42},
|
||||
cli_explicit_keys=set(), # not in the explicit set, but per-stage
|
||||
)
|
||||
def test_typed_kwarg_equal_to_dataclass_default_still_overrides(self):
|
||||
# Caller intent honored regardless of value coincidence (no heuristic).
|
||||
stages = self._stages({"gpu_memory_utilization": 0.9})
|
||||
assert stages[2].runtime_overrides.get("gpu_memory_utilization") == 0.9
|
||||
|
||||
def test_per_stage_kwarg_routed_to_correct_stage(self):
|
||||
stages = self._stages({"stage_0_gpu_memory_utilization": 0.42})
|
||||
assert stages[0].runtime_overrides.get("gpu_memory_utilization") == 0.42
|
||||
assert stages[2].runtime_overrides.get("gpu_memory_utilization") is None
|
||||
|
||||
def test_none_explicit_set_treats_all_as_explicit(self):
|
||||
"""Programmatic Omni() callers (cli_explicit_keys=None) keep current behavior."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"max_num_seqs": 999},
|
||||
cli_explicit_keys=None,
|
||||
)
|
||||
assert stages[2].runtime_overrides.get("max_num_seqs") == 999
|
||||
|
||||
def test_explicit_async_chunk_false_overrides_yaml(self):
|
||||
"""``--no-async-chunk`` flips the deploy-level async_chunk to False even
|
||||
when the YAML sets it to True. Verifies that the per-stage
|
||||
``async_chunk: True`` injection in ``merge_pipeline_deploy`` is skipped
|
||||
and that ``async_chunk`` does not leak through ``_merge_cli_overrides``.
|
||||
"""
|
||||
stages = self._stages(
|
||||
cli_overrides={"async_chunk": False},
|
||||
cli_explicit_keys={"async_chunk"},
|
||||
)
|
||||
# qwen3_omni_moe.yaml has `async_chunk: true`, so by default every
|
||||
# stage's engine_args would carry it. With the explicit override, it
|
||||
# must NOT show up.
|
||||
def test_async_chunk_false_overrides_yaml_true(self):
|
||||
stages = self._stages({"async_chunk": False})
|
||||
for stage in stages:
|
||||
assert stage.yaml_engine_args.get("async_chunk") is not True
|
||||
assert stage.runtime_overrides.get("async_chunk") is None
|
||||
|
||||
def test_default_async_chunk_leaves_yaml_alone(self):
|
||||
"""An unset ``--async-chunk`` (default None) must leave the YAML's True
|
||||
in force on every stage."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"async_chunk": None},
|
||||
cli_explicit_keys=set(),
|
||||
)
|
||||
# qwen3_omni_moe.yaml: `async_chunk: true` → injected on every stage.
|
||||
def test_async_chunk_none_keeps_yaml_true(self):
|
||||
stages = self._stages({"async_chunk": None})
|
||||
for stage in stages:
|
||||
assert stage.yaml_engine_args.get("async_chunk") is True
|
||||
|
||||
def test_explicit_enable_prefix_caching_overrides_yaml(self):
|
||||
"""``--enable-prefix-caching`` (global) flips every stage's
|
||||
``enable_prefix_caching`` to True regardless of the YAML default."""
|
||||
stages = self._stages(
|
||||
cli_overrides={"enable_prefix_caching": True},
|
||||
cli_explicit_keys={"enable_prefix_caching"},
|
||||
)
|
||||
def test_enable_prefix_caching_typed_overrides_yaml(self):
|
||||
stages = self._stages({"enable_prefix_caching": True})
|
||||
for stage in stages:
|
||||
assert stage.runtime_overrides.get("enable_prefix_caching") is True
|
||||
|
||||
def test_omni_with_vars_args_anti_pattern_is_safe(self):
|
||||
# Omni(**vars(args)) with mostly-None namespace must not clobber YAML.
|
||||
simulated_vars_args = {
|
||||
"gpu_memory_utilization": None,
|
||||
"max_num_seqs": None,
|
||||
"async_chunk": None,
|
||||
"enable_prefix_caching": None,
|
||||
"dtype": None,
|
||||
}
|
||||
stages = self._stages(simulated_vars_args)
|
||||
for stage in stages:
|
||||
assert stage.runtime_overrides == {}
|
||||
|
||||
def test_create_from_registry_no_cli_explicit_keys_param(self):
|
||||
import inspect
|
||||
|
||||
sig = inspect.signature(StageConfigFactory._create_from_registry)
|
||||
named = [p for p in sig.parameters.values() if p.kind != p.VAR_KEYWORD]
|
||||
assert "cli_explicit_keys" not in {p.name for p in named}
|
||||
|
||||
def test_cli_explicit_keys_kwarg_emits_deprecation(self):
|
||||
import vllm_omni.model_executor.models.qwen3_omni.pipeline # noqa: F401
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
StageConfigFactory._create_from_registry(
|
||||
"qwen3_omni_moe",
|
||||
cli_overrides={},
|
||||
cli_explicit_keys={"max_num_seqs"},
|
||||
)
|
||||
assert any(issubclass(x.category, DeprecationWarning) for x in w)
|
||||
|
||||
def test_async_chunk_dispatches_processors(self):
|
||||
"""A single ``qwen3_tts`` pipeline picks per-chunk vs end-to-end
|
||||
processors based on ``deploy.async_chunk``, without needing a
|
||||
|
||||
Reference in new issue
Block a user