[refactor] Move model-capability adjustments into the resolution pipeline (#30299)

This commit is contained in:
Cheng Wan
2026-07-07 21:26:55 -07:00
committed by GitHub
parent d4963f5c55
commit b14f7b4f75
29 changed files with 478 additions and 209 deletions
@@ -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()