[refactor] Wire the config resolution pipeline (dispatch, stash, dual-apply, publish) (stack 6/15) (#30068)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cheng Wan
2026-07-04 02:21:21 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent c3d751b231
commit df6491d80c
5 changed files with 138 additions and 2 deletions
+6 -1
View File
@@ -85,7 +85,12 @@ class Arg:
@functools.lru_cache(maxsize=None)
def model_overridable_fields(cls) -> frozenset:
"""Names of ``cls`` dataclass fields whose ``Arg`` metadata declares
``model_overridable=True`` — the whitelist for model-override resolution."""
``model_overridable=True`` — the whitelist for model-override resolution.
Non-dataclass types (e.g. mock config objects in tests) have no Arg
metadata and yield an empty whitelist."""
if not dataclasses.is_dataclass(cls):
return frozenset()
hints = get_type_hints(cls, include_extras=True)
names = set()
for field in dataclasses.fields(cls):
+16
View File
@@ -172,7 +172,23 @@ def apply_declarations_to_server_args(
Retired per field once that field's readers have all flipped to the flags
tier (at which point the server_args field returns to pristine).
Validates against the same whitelist as the publish gate BEFORE any write:
a registry typo or a not-yet-resolvable field must fail fast here, not
mutate ``server_args`` and only be rejected at publish time.
"""
# Non-dataclass fixtures carry no Arg metadata (mirrors the
# model_overridable_fields escape); only real ServerArgs is validated.
if dataclasses.is_dataclass(type(server_args)):
whitelist = model_overridable_fields(type(server_args))
for source, decl in list(declarations) + list(terminal):
unknown = set(decl) - whitelist
if unknown:
raise ValueError(
f"{source}: {sorted(unknown)} not model-overridable; the "
"transition dual-apply refuses fields the publish gate "
"would reject."
)
for _source, decl in list(declarations) + list(terminal):
for field, value in decl.items():
setattr(server_args, field, value)
+31
View File
@@ -359,9 +359,40 @@ class RuntimeContext:
Overwrite-allowed: a re-publish replaces the slot (test kits re-publish
per test; production ordering discipline lives at the call-sites, e.g.
the draft-worker guard in ``ModelRunner.__init__``).
Publishing also resolves the stashed model-override declarations
into the flags tier (skipped for objects without the stash — dummy /
"none" fixture ServerArgs and test-kit mocks never compute it).
Resolution runs first: if it fails, the previous publish stays intact.
"""
self._resolve_flags(server_args)
self._server_args = server_args
def _resolve_flags(self, server_args: ServerArgs) -> None:
declarations = getattr(server_args, "_resolved_overrides", None)
if declarations is None:
return
from sglang.srt.arg_groups.overrides import (
apply_model_overrides,
assert_flag_parity,
)
# Resolve into a fresh container and only install it once everything
# passed: a failed resolution (gate validation or the parity assert)
# must not leave the process-global flags half-written for callers
# that catch the error or republish (same install-fresh semantics as
# reset_context()).
flags = Flags()
apply_model_overrides(flags, server_args, declarations)
# Transition-period drift guard: dual-apply keeps the declared fields
# on server_args byte-identical to the resolved flag leaves.
assert_flag_parity(
flags,
server_args,
{field for _source, decl in declarations for field in decl},
)
self.flags = flags
_PARALLEL = ParallelContext()
_CONTEXT = RuntimeContext(parallel=_PARALLEL)
+23
View File
@@ -2569,6 +2569,12 @@ class ServerArgs:
Orchestrates the handling of various server arguments, ensuring proper configuration and validation.
"""
# Declaration stash for the override/post-process passes. Set before any
# short-circuit (none/dummy model paths) so run_post_process_pass and
# direct handler invocations can rely on it even when
# _handle_model_specific_adjustments never runs.
self._resolved_overrides = []
self._maybe_download_model_for_runai()
# Normalize load balancing defaults early (before dummy-model short-circuit).
@@ -3709,6 +3715,7 @@ class ServerArgs:
self.uses_mamba_radix_cache = False
if parse_connector_type(self.model_path) == ConnectorType.INSTANCE:
self._resolved_overrides = []
return
hf_config = self.get_model_config().hf_config
@@ -3718,6 +3725,22 @@ class ServerArgs:
if _hybrid_spec is not None and _hybrid_spec.uses_mamba_radix_cache:
self._handle_mamba_radix_cache(model_arch=model_arch)
# Collect the declarative model overrides (registry) on the
# pristine config and stash them for publish-time flags resolution.
# Transition dual-apply: the same declarations are applied to
# server_args right here, byte-identical to the imperative arch
# branches this dispatch gradually replaces (dual-apply is retired
# per field once that field's readers migrate to the flags tier).
from sglang.srt.arg_groups.overrides import (
apply_declarations_to_server_args,
collect_model_override_declarations,
)
self._resolved_overrides = collect_model_override_declarations(
model_arch, self, hf_config
)
apply_declarations_to_server_args(self, self._resolved_overrides)
if model_arch in [
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
+62 -1
View File
@@ -21,7 +21,12 @@ from sglang.srt.arg_groups.overrides import (
collect_model_override_declarations,
register_model_override,
)
from sglang.srt.runtime_context import _StaticFlags
from sglang.srt.runtime_context import (
_StaticFlags,
get_context,
get_server_args,
reset_context,
)
from sglang.test.test_utils import CustomTestCase
@@ -50,6 +55,9 @@ class TestModelOverridableWhitelist(CustomTestCase):
self.assertEqual(model_overridable_fields(ServerArgs), frozenset())
def test_non_dataclass_yields_empty_whitelist(self):
self.assertEqual(model_overridable_fields(SimpleNamespace), frozenset())
class _IsolatedRegistry(CustomTestCase):
"""Run each test against empty registries (they are process-global)."""
@@ -199,6 +207,59 @@ class TestApplyModelOverridesGate(CustomTestCase):
self.assertEqual(flags.resolved_by_model, "unset") # flat leaf untouched
class _IsolatedPublish(CustomTestCase):
"""Publishing writes the process-global context; save/restore around it."""
def setUp(self):
super().setUp()
self._saved_server_args = get_context()._server_args
def tearDown(self):
reset_context()
if self._saved_server_args is not None:
get_context()._server_args = self._saved_server_args
super().tearDown()
@dataclasses.dataclass
class _NoOverridableArgs:
x: int = 1
class TestPublishResolvesFlags(_IsolatedPublish):
"""Publish wiring: stash-carrying publishes resolve into flags via the
gate; publishes without the stash skip resolution."""
def test_dummy_fixture_has_empty_stash_and_publishes_cleanly(self):
from sglang.srt.server_args import (
ServerArgs,
set_global_server_args_for_scheduler,
)
sa = ServerArgs(model_path="dummy") # __post_init__ early-returns
# The stash is created before the dummy short-circuit and stays empty.
self.assertEqual(sa._resolved_overrides, [])
set_global_server_args_for_scheduler(sa)
self.assertIs(get_server_args(), sa)
def test_empty_stash_publish_runs_gate_as_noop(self):
sa = _NoOverridableArgs()
sa._resolved_overrides = []
get_context().set_server_args(sa)
self.assertIs(get_server_args(), sa)
def test_non_whitelisted_declaration_fails_at_publish(self):
from sglang.srt.runtime_context import get_flags
flags_before = get_flags()
sa = _NoOverridableArgs()
sa._resolved_overrides = [("rogue", {"x": 2})]
with self.assertRaises(ValueError):
get_context().set_server_args(sa)
# a failed publish must leave BOTH the slot and the flags untouched
self.assertIs(get_flags(), flags_before)
class TestDualApplyParity(CustomTestCase):
def test_dual_apply_replays_and_parity_holds(self):
flags, args = _FakeFlags(), _FakeArgs()