The skill still described the retired resolve-at-end contract (a single
resolved ServerArgs as the source of truth, ServerArgs.override as the
mutation entry). Rewrite the config sections for the landed model:
- ServerArgs is a pristine published seed; resolved config lives in the
namespace bags (get_exec()/get_memory()/.../get_device()), projected at
publish(server_args, role=...) per process entry.
- Post-publish mutation goes through get_context().override (bag-only);
ServerArgs.override is being retired behind the writer ratchet, and
rerouting a writer co-flips all its readers in the same commit.
- Documented the reads that stay on an instance (per-runner fork fields,
per-instance tokenizer/entrypoints boundaries, whole-object passes),
get_parallel()'s config/live dual semantics with the live-shadowed
sizes rule, preserve_config for nested publishes, and the bag-only
declare_load_time_override route.
- Testing idioms: publish-seeding (override_server_args + scoped bag
overrides) instead of faking accessors or SimpleNamespace stand-ins;
per-file test runs to avoid leaked-publish masking.
- Guardrails: added the writer ratchet, namespace-coverage lint, and the
migration-deferral ratchet.
publish(role=...) has recorded provenance since the namespace split; this
wires the enforcement the role was reserved for. SGLANG_ROLE_NAMESPACES
selects the mode:
- off (default): no bookkeeping; the mode gate in config_bag stays a
dead-branch-prunable check under dynamo (bag reads run inside compiled
forwards — pinned by a fullgraph test).
- record: audit mode — collect (role, namespace) pairs per process and
persist each new pair immediately to SGLANG_ROLE_NAMESPACES_OUT (worker
teardown skips atexit), plus a per-process stderr summary at exit.
- enforce: a bag read outside the role's ROLE_NAMESPACE_SETS entry fails
closed with an actionable error; None entries mean full tree.
Sets are filled only where audits back them: dp_controller reads only
exec (record-mode plain + DP-attention smokes agree with the module's
static read set — the elastic-EP gate). tokenizer observed zero bag
reads (per-instance managers read self.server_args by design) but keeps
the full tree until the multi-tokenizer disagg shape (TokenizerWorker's
get_disagg read) is audited; encoder / expert_backup /
weight_cache_daemon likewise await their deployment shapes.
Verified end-to-end: DP-attention smoke under enforce boots and serves
with zero violations.
The module-skipped tests injected config by faking get_server_args (a
SimpleNamespace stand-in patched onto the module) or by writing fields
onto a ServerArgs instance post-publish — both invisible to the namespace
accessors the production code now reads. Re-enable them by publishing the
config they need (get_context().override_server_args seeding, scoped per
test), asserting bag state where the old assertions checked instance
write-through (declare_load_time_override is bag-only), and extending the
per-runner stubs the code genuinely reads (kv_cache_dtype_str,
max_total_tokens, context_len).
The unified-radix-cache file (which grew a large hicache/insert-walk suite
while skipped) is recovered in the same change:
- test_cache_finished_req_strips_thinking (19 parametrized classes) wrote
strip_thinking_cache onto the ServerArgs instance; the cache reads
get_serving().strip_thinking_cache — use the serving bag's scoped
override.
- test_shallower_crossing_backs_up_above_backuped_middle staged its
broken-backup-continuity setup through insert_host, which now
deliberately drops refills below an un-backed-up node under
write-through (host_insert_dropped). Build the same tree state through
an explicit backup + device eviction.
Every config-namespace-migration deferral is recovered, so the deferral
ratchet (test_migration_deferral_ratchet.py) has done its job and is
retired.
The parallel namespace joins the accessor migration: 106 config-leaf reads
(enable_dp_lm_head, enable_dp_attention, pp_async_batch_depth, dp_size,
ep_join_rank_offset, dwdp_size, ...) flip from get_server_args()/
self.server_args to get_parallel(), which serves config leaves from the
published parallel bag via __getattr__.
- ParallelContext.__getattr__ is restructured to stay dynamo-traceable
(object.__getattribute__ graph-breaks): gate helpers such as
enable_moe_dense_fully_dp() run inside compiled model forwards. A
fullgraph regression test pins the pattern.
- The five live-shadowed topology sizes (tp/pp/dcp/attn_cp/moe_dp_size)
keep their server_args reads: the live @property wins on the accessor,
and conditionally-initialized groups would fail loud at unconditional
call sites.
- Elastic-EP scale writers (ep_size/dp_size x4 in model_runner) reroute
to get_context().override together with their remaining instance
readers (expert_location gpus-per-node paths); the ServerArgs.override
ratchet drops 39 -> 35.
- The expert placement helpers (compute_logical_to_rank_dispatch_
physical_map, _compute_logical_to_all_physical_map,
_prefer_same_node_experts) now read everything from the bags and drop
their server_args parameter; their unit tests publish the config they
need instead of stubbing it.
The load-time gate (maybe_disable_chunked_prefix_cache) wrote its
ServerArgs instance while every reader has moved to the published
config: the attention backends assert / branch on
get_schedule().disable_chunked_prefix_cache when they initialize, so the
flip never reached them and a backend outside
CHUNKED_PREFIX_CACHE_SUPPORTED_ATTENTION_BACKENDS kept chunked prefix
enabled.
Reroute the writer through get_context().override (which writes the
published bags) and flip the two remaining instance reads — the gate's
own log check and the prefill cuda-graph runner's capture flag — to the
bag. A regression test pins the three contracts: the gate lands on the
bag, the pristine ServerArgs instance stays untouched, and the
draft-worker guard never writes.
The ServerArgs.override call-site ratchet drops 39 -> 38.