config: make ServerArgs read-only with a single audited mutation entry (#31811)

This commit is contained in:
Cheng Wan
2026-07-22 01:16:55 -07:00
committed by GitHub
parent 09688d58bc
commit 97e2c0c4ee
7 changed files with 171 additions and 14 deletions
@@ -3331,8 +3331,9 @@ async def run_dp_worker(
# 0 when CVD is pinned to one GPU, else the absolute id. rank=0, so
# MMEncoder runs set_device(base_gpu_id).
args = copy.deepcopy(server_args)
args.base_gpu_id = gpu_id
args.tp_size = 1
# The copy is already resolved (read-only); route the per-worker
# specialization through the audited mutation entry.
args.override("encode_server.dp_worker", base_gpu_id=gpu_id, tp_size=1)
enc = MMEncoder(args, dist_init_method=f"tcp://127.0.0.1:{get_free_port()}", rank=0)
global encoder_metrics_collector
@@ -93,7 +93,12 @@ def maybe_set_default_flashinfer_gdn_prefill(model_runner: ModelRunner) -> None:
)
if is_flashinfer_gdn_prefill_available():
args.linear_attn_prefill_backend = "flashinfer"
# server_args is resolved (read-only) by the time backends initialize;
# route this load-time default through the audited mutation entry.
args.override(
"gdn_backend.sm100_flashinfer_default",
linear_attn_prefill_backend="flashinfer",
)
rank0_log("Defaulting SM100 GDN prefill backend to FlashInfer.")
+54
View File
@@ -655,6 +655,7 @@ class RuntimeContext:
"parallel",
"_server_args",
"_config_bags",
"_overrides_log",
"flags",
"resources",
"forward",
@@ -664,6 +665,7 @@ class RuntimeContext:
self.parallel = parallel
self._server_args: ServerArgs | None = None
self._config_bags: dict | None = None
self._overrides_log: list = []
self.flags = Flags()
self.resources = Resources()
self.forward = ForwardFlags()
@@ -725,6 +727,8 @@ class RuntimeContext:
# truth for config reads). Driven by NS(...) metadata; a mock/partial
# config with no NS markers yields an empty tree (no bags projected).
self._config_bags = _build_config_bags(server_args)
# Fresh config lifecycle: prior override provenance no longer applies.
self._overrides_log = []
def config_bag(self, name: str) -> _ConfigBag:
"""Return the top-level config namespace bag (``device`` / ``model`` /
@@ -736,6 +740,55 @@ class RuntimeContext:
raise ValueError(f"config namespace {name!r} not published")
return bags[name]
def override(self, source: str, **fields) -> None:
"""The business mutation entry: write resolved config
leaves onto the namespace bags — the single source of truth. It does
**not** touch ``server_args`` (the pristine startup record) and there is
no write-through, so the old "wrote one store, read another" desync class
cannot occur.
Each flat field name is routed to its bag by the ``NS`` metadata (flat
names are unique across namespaces). Validation is all-or-nothing: an
unknown / unprojected field aborts before any write. ``source`` is
recorded for provenance / reproduction.
"""
if not fields:
return
bags = self._config_bags
if bags is None:
raise ValueError("config not published; cannot override")
from sglang.srt.arg_groups.arg_utils import namespace_of
nsmap = namespace_of(type(self._server_args))
targets = [] # (bag, leaf, value) — resolved before any write
for name, value in fields.items():
path = nsmap.get(name)
if path is None:
raise ValueError(
f"override: unknown config field {name!r} (no NS namespace) — "
"not a resolved config leaf"
)
parts = path.split(".")
bag = bags.get(parts[0])
if bag is None:
raise ValueError(f"override: namespace {parts[0]!r} not published")
for seg in parts[1:]:
bag = object.__getattribute__(bag, "_subs").get(seg)
if bag is None:
raise ValueError(
f"override: subgroup {seg!r} missing under {path!r}"
)
if name not in bag:
raise ValueError(f"override: field {name!r} not projected on {path!r}")
targets.append((bag, name, value))
for bag, name, value in targets:
bag._set(name, value)
self._overrides_log.append((source, dict(fields)))
def overrides_log(self) -> list:
"""Provenance of post-publish ``override`` calls: ``[(source, {field: value})]``."""
return list(self._overrides_log)
def override_server_args(self, **fields) -> _ServerArgsOverride:
"""Test-only scoped override for the config tier — the sibling of
``get_parallel().override()`` and the flag groups' ``override()``:
@@ -927,6 +980,7 @@ def reset_context() -> None:
"""
_CONTEXT._server_args = None
_CONTEXT._config_bags = None
_CONTEXT._overrides_log = []
_CONTEXT.flags = Flags()
_CONTEXT.resources = Resources()
_CONTEXT.forward = ForwardFlags()
+11 -10
View File
@@ -7877,21 +7877,22 @@ class ServerArgs:
object.__setattr__(self, "_in_override", False)
def __setattr__(self, name, value):
# After materialization the fields are the resolved configuration:
# under the strict test harness, a bare assignment outside
# ServerArgs.override() (and the resolution pipeline itself) raises.
# after materialization the fields are the resolved startup
# configuration -- the pristine, READ-ONLY record. A bare assignment
# outside ServerArgs.override() (and the resolution pipeline, which runs
# before materialization) always raises; resolved config is mutated on
# the context bags via get_context().override(...), not here. (Formerly
# gated on SGLANG_STRICT_CONFIG_MUTATION; now unconditional.)
if (
not name.startswith("_")
and getattr(self, "_declarations_materialized", False)
and not getattr(self, "_in_override", False)
):
from sglang.srt.environ import envs
if envs.SGLANG_STRICT_CONFIG_MUTATION.get():
raise AttributeError(
f"server_args.{name} assigned after resolution; use "
"server_args.override(source, ...) instead."
)
raise AttributeError(
f"server_args.{name} assigned after resolution; server_args is "
"read-only -- use get_context().override(source, ...) to change "
"resolved config."
)
object.__setattr__(self, name, value)
def _resolved_attention_backends(self):