config: make ServerArgs read-only with a single audited mutation entry (#31811)
This commit is contained in:
@@ -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.")
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user