[refactor] Move model-capability adjustments into the resolution pipeline (#30299)
This commit is contained in:
@@ -273,6 +273,9 @@ class TestCreateGrammarBackend(unittest.TestCase):
|
||||
self, backend="none", reasoning_parser=None, enable_strict_thinking=False
|
||||
):
|
||||
args = MagicMock()
|
||||
args.override = lambda source, **updates: [
|
||||
setattr(args, key, value) for key, value in updates.items()
|
||||
]
|
||||
args.grammar_backend = backend
|
||||
args.reasoning_parser = reasoning_parser
|
||||
args.enable_strict_thinking = enable_strict_thinking
|
||||
|
||||
@@ -85,9 +85,21 @@ class _DummyPublisherThread:
|
||||
pass
|
||||
|
||||
|
||||
def _fake_server_args(**fields):
|
||||
"""server_args stand-in: carries fields and the override() entry point."""
|
||||
ns = types.SimpleNamespace(**fields)
|
||||
|
||||
def _override(source, **updates):
|
||||
for key, value in updates.items():
|
||||
setattr(ns, key, value)
|
||||
|
||||
ns.override = _override
|
||||
return ns
|
||||
|
||||
|
||||
def _make_reporter(scheduler) -> SchedulerMetricsReporter:
|
||||
if not hasattr(scheduler, "server_args"):
|
||||
scheduler.server_args = types.SimpleNamespace(
|
||||
scheduler.server_args = _fake_server_args(
|
||||
enable_metrics=False,
|
||||
enable_metrics_for_all_schedulers=False,
|
||||
kv_events_config=None,
|
||||
@@ -275,7 +287,7 @@ class TestForwardPassMetrics(unittest.TestCase):
|
||||
|
||||
def test_init_metrics_uses_server_worker_id(self):
|
||||
scheduler = types.SimpleNamespace()
|
||||
scheduler.server_args = types.SimpleNamespace(
|
||||
scheduler.server_args = _fake_server_args(
|
||||
enable_metrics=False,
|
||||
enable_metrics_for_all_schedulers=False,
|
||||
extra_metric_labels=None,
|
||||
@@ -303,7 +315,7 @@ class TestForwardPassMetrics(unittest.TestCase):
|
||||
|
||||
def test_init_fpm_disabled_on_non_last_pp_rank(self):
|
||||
scheduler = types.SimpleNamespace()
|
||||
scheduler.server_args = types.SimpleNamespace(
|
||||
scheduler.server_args = _fake_server_args(
|
||||
enable_metrics=False,
|
||||
enable_metrics_for_all_schedulers=False,
|
||||
extra_metric_labels=None,
|
||||
|
||||
@@ -24,6 +24,18 @@ register_cuda_ci(est_time=20, stage="base-b", runner_config="1-gpu-small")
|
||||
DEVICE = get_device()
|
||||
|
||||
|
||||
def _fake_server_args(**fields):
|
||||
"""server_args stand-in: carries fields and the override() entry point."""
|
||||
ns = SimpleNamespace(**fields)
|
||||
|
||||
def _override(source, **updates):
|
||||
for key, value in updates.items():
|
||||
setattr(ns, key, value)
|
||||
|
||||
ns.override = _override
|
||||
return ns
|
||||
|
||||
|
||||
def _make_chain_lists(num_steps: int, bs: int):
|
||||
"""Build the (score, token, parents) lists a topk=1 chain produces.
|
||||
|
||||
@@ -52,7 +64,7 @@ def _make_worker(num_steps: int, num_draft_tokens: int):
|
||||
worker.device = DEVICE
|
||||
worker.speculative_num_steps = num_steps
|
||||
worker.speculative_num_draft_tokens = num_draft_tokens
|
||||
worker.server_args = SimpleNamespace(
|
||||
worker.server_args = _fake_server_args(
|
||||
cuda_graph_config=SimpleNamespace(decode=SimpleNamespace(max_bs=8)),
|
||||
max_running_requests=8,
|
||||
)
|
||||
@@ -114,7 +126,7 @@ class TestEagleWorkerV2BackendFallback(CustomTestCase):
|
||||
worker = object.__new__(EagleDraftWorker)
|
||||
existing_backend = object()
|
||||
decode_backend = object()
|
||||
worker.server_args = SimpleNamespace()
|
||||
worker.server_args = _fake_server_args()
|
||||
worker.draft_runner = SimpleNamespace(attn_backend=existing_backend)
|
||||
worker.topk = 1
|
||||
worker.speculative_num_steps = 2
|
||||
@@ -135,7 +147,7 @@ class TestEagleWorkerV2BackendFallback(CustomTestCase):
|
||||
existing_backend = object()
|
||||
decode_backend = object()
|
||||
draft_extend_backend = object()
|
||||
worker.server_args = SimpleNamespace()
|
||||
worker.server_args = _fake_server_args()
|
||||
worker.draft_runner = SimpleNamespace(attn_backend=existing_backend)
|
||||
worker.topk = 1
|
||||
worker.speculative_num_steps = 2
|
||||
@@ -180,7 +192,7 @@ class TestEagleWorkerV2BackendFallback(CustomTestCase):
|
||||
)
|
||||
worker.speculative_num_steps = 2
|
||||
worker.speculative_num_draft_tokens = 3
|
||||
worker.server_args = SimpleNamespace(
|
||||
worker.server_args = _fake_server_args(
|
||||
speculative_num_steps=2,
|
||||
speculative_num_draft_tokens=3,
|
||||
cuda_graph_bs_decode=None,
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Ratchet guard: server_args mutations outside the resolution pipeline may
|
||||
only decrease.
|
||||
|
||||
After ``ServerArgs.__post_init__`` returns, the instance carries the resolved
|
||||
configuration; the resolution pipeline (``server_args.py`` and
|
||||
``arg_groups/``) is the only place that computes it. Every assignment to a
|
||||
``server_args`` field elsewhere weakens that contract, so the count below is
|
||||
an exact pin: new mutations must not appear, and removals must lower the
|
||||
baseline to lock in the progress.
|
||||
|
||||
Every audited runtime adjustment goes through ``ServerArgs.override(source,
|
||||
**fields)`` — the single mutation entry point, which records provenance and
|
||||
keeps whitelisted fields consistent with the declaration stash. The baseline
|
||||
is therefore zero. The registered test harness additionally runs with
|
||||
``SGLANG_STRICT_CONFIG_MUTATION=1``, under which a bare assignment after
|
||||
resolution raises at runtime; this ratchet catches sites the tests never
|
||||
execute.
|
||||
"""
|
||||
|
||||
from sglang.test.ci.ci_register import register_cpu_ci
|
||||
|
||||
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import sglang
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
|
||||
_SGLANG_ROOT = Path(next(iter(sglang.__path__)))
|
||||
|
||||
# Assignments to a server_args attribute (``server_args.x = ...``,
|
||||
# ``self.server_args.x = ...``, and the ``sa`` alias used by a few helpers).
|
||||
# ``==`` comparisons are excluded by the negative lookahead.
|
||||
_MUTATION_PATTERNS = [
|
||||
# (?![=}]) skips ``==`` comparisons and f-string ``{x=}`` debug specs.
|
||||
re.compile(r"\bserver_args\.[a-z0-9_]+\s*=(?![=}])"),
|
||||
re.compile(r"\bsa\.[a-z0-9_]+\s*=(?![=}])"),
|
||||
re.compile(r"get_(?:global_)?server_args\(\)\.[a-z0-9_]+\s*=(?![=}])"),
|
||||
]
|
||||
|
||||
# The resolution pipeline itself (mutation is its job); multimodal_gen, whose
|
||||
# ServerArgs is a different class outside this contract; and the sanctioned
|
||||
# mock-fixture factory (bare object.__new__ instances never materialize, so
|
||||
# the strict guard does not apply to their construction).
|
||||
_EXCLUDED = (
|
||||
"srt/server_args.py",
|
||||
"srt/arg_groups",
|
||||
"multimodal_gen",
|
||||
"test/kits/attention_unittest/mock_server_args.py",
|
||||
)
|
||||
|
||||
_BASELINE = 0
|
||||
|
||||
|
||||
class TestServerArgsMutationRatchet(CustomTestCase):
|
||||
def test_out_of_pipeline_mutations_match_the_baseline(self):
|
||||
count = 0
|
||||
for path in sorted(_SGLANG_ROOT.rglob("*.py")):
|
||||
rel = path.relative_to(_SGLANG_ROOT).as_posix()
|
||||
if rel.startswith(_EXCLUDED):
|
||||
continue
|
||||
source = path.read_text()
|
||||
count += sum(len(p.findall(source)) for p in _MUTATION_PATTERNS)
|
||||
if count > _BASELINE:
|
||||
self.fail(
|
||||
f"server_args mutations outside the resolution pipeline grew: "
|
||||
f"{count} > baseline {_BASELINE}. Configuration is resolved in "
|
||||
"ServerArgs.__post_init__; declare through the pipeline "
|
||||
"(passes / declare_load_time_override / "
|
||||
"record_runtime_overrides) instead of assigning fields."
|
||||
)
|
||||
if count < _BASELINE:
|
||||
self.fail(
|
||||
f"server_args mutations outside the resolution pipeline "
|
||||
f"shrank: {count} < baseline {_BASELINE}. Lower the baseline "
|
||||
"in this file to lock in the progress."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user