config: the alias form of the runner-side instance read

The previous batch counted `self.server_args.X` and called the runner surface
done. It was not: the same read spelled through a local alias --
`server_args = model_runner.server_args` (or `sa = kvc.server_args`, `args = ...`)
followed by `server_args.leaf` -- is the same process-global read wearing a
local name, and the AST census counts **57 of them** across eleven files that
the grep never saw. Census per function, following the alias.

52 were leaves and go to their bag (`spec` 11, `schedule` 9, `memory` 7,
`exec.graph` 5, `exec.moe` 5, `parallel` 4, `disagg` 4, `model` 3,
`exec.mamba` 2, `exec.overlap` 2). Five were not leaves:
three derived members on the eager runner --
`max_speculative_num_draft_tokens` and `enable_mamba_extra_buffer` already had
accessors, and `max_prefill_buffer_tokens` gets one (all its inputs are `schedule`
leaves plus the configured PP size, so it derives from the bags and follows a
post-publish override; `TestDerivedPredicatesAgreeAcrossTiers` pins it against
the member over a 48-case matrix) -- plus `get_attention_backends()`, which the
same commit routes through `attention_backends()`, and a dict that merely shares
the name (`server_args_dict.items`). That dict is the one read left behind.

`build_attention_backends` also stops resolving the pair from the record: it
runs after publish, so it asks `attention_backends()` like every other consumer.
The draft override on the runner still wins first.

`dispatch_event_loop`'s three PP checks read the *configured* PP size, not the
live topology: the MLX runner stub never initializes torch.distributed, so the
live property asserts before the MLX event loop can start (a Codex catch). The
configured leaf answers the same value wherever the live groups exist.

`flashinfer_gdn_prefill_default`'s guard is the one read here that asks what the
*operator* named rather than what the config resolved to, and the bag leaf now
answers exactly that: the per-runner auto-default is stamped on the runner and
deliberately never recorded process-wide, so nothing writes that leaf after
launch and reading it back cannot mistake another runner's default for a flag.

Three test doubles injected a `SimpleNamespace`/`MagicMock` record for exactly
these reads and now publish instead (pool configurator, cache registry, GDN
prefill policy) -- the fixture publishes what the case configures and hands the
published instance to the whole-object contracts that still take one.

The functions this sweep partially converted stop mixing sources (review
catches): the flash-attention constructor's remaining seed reads
(`speculative_eagle_topk`, `speculative_algorithm`, both deterministic gates)
read their bags next to the leaves already converted;
`_should_disable_scheduler_metadata_precompute` reads the parallel config
leaves itself instead of taking the record (its alias binding was the last
use); and the autotune gates (`disable_flashinfer_autotune`, deterministic,
`flashinfer_autotune_skip_ops`) join the moe leaves the same function already
reads from the bags. The pool-configurator fixture drops a parameter nothing
published or read.
This commit is contained in:
Cheng Wan
2026-08-15 00:39:03 -07:00
committed by GitHub
parent 61908870f6
commit f2ab6e306b
18 changed files with 319 additions and 147 deletions
@@ -11,7 +11,7 @@ from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from sglang.srt.distributed.parallel_state_wrapper import ParallelState
from sglang.srt.runtime_context import get_parallel
from sglang.srt.runtime_context import get_parallel, get_server_args
from sglang.test.ci.ci_register import register_cpu_ci
register_cpu_ci(est_time=10, suite="base-a-test-cpu")
@@ -35,7 +35,21 @@ def mock_cpu_env(kv_size=2, tp_size=1, swa_eviction_interval=4):
yield
def _publish_config(testcase, **fields):
"""Publish the configuration a runner double describes.
Installed per call and restored on the calling case's cleanup, so a failed
case cannot leave a partial publish for a later file in a monolithic run.
"""
from sglang.srt.runtime_context import get_context
override = get_context().override_server_args(**fields)
override.install()
testcase.addCleanup(override.restore)
def _make_model_runner(
testcase,
*,
num_kv_heads=4,
head_dim=64,
@@ -56,7 +70,6 @@ def _make_model_runner(
disable_overlap_schedule=False,
sliding_window_size=None,
speculative_num_draft_tokens=None,
max_speculative_num_draft_tokens=None,
speculative_algorithm=None,
speculative_num_steps=None,
speculative_eagle_topk=None,
@@ -105,27 +118,29 @@ def _make_model_runner(
mr.model_config = mc
mr.kv_cache_dtype = "fake_bf16"
sa = SimpleNamespace()
sa.max_total_tokens = None
sa.swa_full_tokens_ratio = swa_full_tokens_ratio
sa.page_size = page_size
sa.disable_radix_cache = disable_radix_cache
sa.chunked_prefill_size = chunked_prefill_size
sa.disable_overlap_schedule = disable_overlap_schedule
sa.speculative_num_draft_tokens = speculative_num_draft_tokens
sa.max_speculative_num_draft_tokens = (
max_speculative_num_draft_tokens or speculative_num_draft_tokens
# The configurator reads the published bags, so the fixture publishes the
# configuration it describes. The instance stays for the whole-object
# hand-offs the configurator still does.
_publish_config(
testcase,
max_total_tokens=None,
swa_full_tokens_ratio=swa_full_tokens_ratio,
page_size=page_size,
disable_radix_cache=disable_radix_cache,
chunked_prefill_size=chunked_prefill_size,
disable_overlap_schedule=disable_overlap_schedule,
speculative_num_draft_tokens=speculative_num_draft_tokens,
speculative_algorithm=speculative_algorithm,
speculative_num_steps=speculative_num_steps,
speculative_eagle_topk=speculative_eagle_topk,
disaggregation_mode=disaggregation_mode,
max_running_requests=max_running_requests,
disaggregation_decode_extra_slots=disaggregation_decode_extra_slots,
enable_hisparse=False,
enable_dsa_cache_layer_split=False,
kv_cache_dtype="auto",
)
sa.speculative_algorithm = speculative_algorithm
sa.speculative_num_steps = speculative_num_steps
sa.speculative_eagle_topk = speculative_eagle_topk
sa.disaggregation_mode = disaggregation_mode
sa.max_running_requests = max_running_requests
sa.disaggregation_decode_extra_slots = disaggregation_decode_extra_slots
sa.enable_hisparse = False
sa.enable_dsa_cache_layer_split = False
sa.kv_cache_dtype = "auto"
mr.server_args = sa
mr.server_args = get_server_args()
spec = MagicMock()
spec.is_eagle.return_value = False
@@ -180,7 +195,7 @@ class TestDefaultConfigurator(unittest.TestCase):
"""Default (MHA): available_bytes -> tokens, memory invariant holds."""
def _run(self, available_bytes, page_size=1, **kwargs):
mr = _make_model_runner(page_size=page_size, **kwargs)
mr = _make_model_runner(self, page_size=page_size, **kwargs)
with mock_cpu_env():
from sglang.srt.model_executor.pool_configurator import (
create_memory_pool_configurator,
@@ -238,10 +253,12 @@ class TestDefaultConfigurator(unittest.TestCase):
):
num_layers = 2
raw = _make_model_runner(
self,
num_layers=num_layers,
use_mla_backend=True,
)
packed = _make_model_runner(
self,
num_layers=num_layers,
use_mla_backend=True,
)
@@ -265,6 +282,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
def _make_swa_runner(self, full_layers=16, swa_layers=16, ratio=0.5, page_size=1):
return _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=list(range(full_layers)),
swa_attention_layer_ids=list(range(full_layers, full_layers + swa_layers)),
@@ -353,6 +371,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
def test_chunk_cache_cap_accounts_for_spec_topk_page_rounding(self):
available = 1_000_000
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -392,6 +411,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
# 2*chunk(4) + page(1) = 9; cap = 41 * 2 + 9 = 91.
available = 1_000_000
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -422,6 +442,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
def test_chunk_cache_cap_drops_prefill_for_disagg_decode(self):
available = 1_000_000
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -452,6 +473,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
# under overlap.
available = 1_000_000
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -484,6 +506,7 @@ class TestHybridSWAConfigurator(unittest.TestCase):
# request count (num_reserved_decode_tokens is a full-pool concern, not SWA).
available = 2_000_000
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -517,6 +540,7 @@ class TestAllSWAConfigurator(unittest.TestCase):
def _run(self, available_bytes, ratio=0.5, page_size=1, **kwargs):
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[],
swa_attention_layer_ids=list(range(32)),
@@ -569,7 +593,7 @@ class TestEagleConfigurator(unittest.TestCase):
num_layers = 32
eagle_draft_num_layers = 4
mr = _make_model_runner(num_layers=num_layers)
mr = _make_model_runner(self, num_layers=num_layers)
mr.spec_algorithm.is_eagle.return_value = True
mr.spec_algorithm.is_standalone.return_value = False
mr.spec_algorithm.is_none.return_value = False
@@ -591,7 +615,7 @@ class TestEagleConfigurator(unittest.TestCase):
class TestFactory(unittest.TestCase):
def test_default_for_non_swa(self):
mr = _make_model_runner(is_hybrid_swa=False)
mr = _make_model_runner(self, is_hybrid_swa=False)
with mock_cpu_env():
from sglang.srt.model_executor.pool_configurator import (
DefaultPoolConfigurator,
@@ -603,6 +627,7 @@ class TestFactory(unittest.TestCase):
def test_swa_for_hybrid(self):
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=list(range(16)),
swa_attention_layer_ids=list(range(16, 32)),
@@ -621,6 +646,7 @@ class TestFactory(unittest.TestCase):
# SWAChunkCapPoolConfigurator is selected only when max_running_requests is set.
def _cfg(max_running_requests):
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=[0],
swa_attention_layer_ids=[1],
@@ -681,7 +707,7 @@ class TestDflashDraftKvBudget(unittest.TestCase):
def test_dcp_replication_scales_draft_budget(self):
"""The replicated draft pool spans every DCP virtual location."""
draft_kv_per_token = 10_240
mr = _make_model_runner()
mr = _make_model_runner(self)
mr.spec_algorithm.is_dflash_family.return_value = True
mr.spec_aux_config = SimpleNamespace(
eagle_draft_num_layers=None,
@@ -715,6 +741,7 @@ class TestDflashDraftKvBudget(unittest.TestCase):
def _tokens(draft_kv_per_token):
mr = _make_model_runner(
self,
is_hybrid_swa=True,
full_attention_layer_ids=list(range(16)),
swa_attention_layer_ids=list(range(16, 32)),