Avoid implicit field-based side channel in Scheduler planning (#29408)

This commit is contained in:
fzyzcjy
2026-07-10 08:55:51 +08:00
committed by GitHub
parent 32c8973ce8
commit 1e75ba236e
13 changed files with 275 additions and 134 deletions
@@ -24,6 +24,9 @@ register_amd_ci(est_time=900, suite="stage-b-test-1-gpu-large-amd")
class TestBenchServing1GPUPart2(CustomTestCase):
@unittest.skip(
"Qwen2.5-VL server crashes with SIGBUS (exit code -7) on main; disable until fixed"
)
def test_vlm_offline_throughput(self):
res = run_bench_serving(
model=DEFAULT_SMALL_VLM_MODEL_NAME_FOR_TEST,
@@ -1137,6 +1137,7 @@ class TestMlxOverlapScheduler(unittest.TestCase):
scheduler.future_map = SimpleNamespace()
scheduler.cur_batch_for_debug = None
scheduler.last_batch = None
scheduler.running_batch = None
scheduler.tp_worker = SimpleNamespace(
async_forward_batch_generation_mlx=fake_forward
)
@@ -1149,7 +1150,11 @@ class TestMlxOverlapScheduler(unittest.TestCase):
spec_algorithm=SpeculativeAlgorithm.NONE,
device="cpu",
)
scheduler.get_next_batch_to_run = lambda: batch
scheduler.get_next_batch_to_run = (
lambda running_batch, last_batch: SimpleNamespace(
batch_to_run=batch, running_batch=running_batch
)
)
with self.assertRaises(_StopLoop):
scheduler.event_loop_overlap_mlx()
@@ -440,7 +440,9 @@ class TestDecodePrebuiltPriority(unittest.TestCase):
"sglang.srt.disaggregation.decode.ScheduleBatch.init_new",
return_value=new_batch,
) as init_new:
ret = SchedulerDisaggregationDecodeMixin.get_new_prebuilt_batch(scheduler)
ret = SchedulerDisaggregationDecodeMixin.get_new_prebuilt_batch(
scheduler, scheduler.running_batch
)
self.assertIs(ret, new_batch)
scheduler.policy.calc_priority.assert_called_once_with(
@@ -12,7 +12,7 @@ from sglang.test.test_utils import CustomTestCase, maybe_stub_sgl_kernel
maybe_stub_sgl_kernel()
from sglang.srt.managers.schedule_batch import Req
from sglang.srt.managers.schedule_batch import NextBatchPlan, Req
from sglang.srt.managers.scheduler import Scheduler
from sglang.srt.mem_cache.chunk_cache import ChunkCache
from sglang.srt.utils.common import Range
@@ -90,7 +90,9 @@ def _scheduler_for_get_next_batch(*, tree_cache, chunked_req) -> Scheduler:
s.running_batch.is_prefill_only = False
s.running_batch.batch_is_full = False
s.running_batch.reqs = []
s.get_new_batch_prefill = MagicMock(return_value=None)
s.get_new_batch_prefill = MagicMock(
return_value=NextBatchPlan(batch_to_run=None, running_batch=s.running_batch)
)
s.dp_attn_adapter = MagicMock()
s.dp_attn_adapter.maybe_prepare_mlp_sync_batch = MagicMock(
side_effect=lambda batch, **_: batch
@@ -137,7 +139,9 @@ class TestStashGatePreservesPrefixIndices(CustomTestCase):
# computed, so the gate must skip stash and leave prefix_indices intact.
s, req, initial_prefix, _ = self._build(fill_len=self.INITIAL_PREFIX_LEN)
Scheduler.get_next_batch_to_run(s)
Scheduler.get_next_batch_to_run(
s, running_batch=s.running_batch, last_batch=s.last_batch
)
self.assertEqual(req.prefix_indices.shape[0], self.INITIAL_PREFIX_LEN)
self.assertTrue(torch.equal(req.prefix_indices, initial_prefix))
@@ -147,7 +151,9 @@ class TestStashGatePreservesPrefixIndices(CustomTestCase):
# the cached prefix, stash must run and advance prefix_indices.
s, req, _, pool = self._build(fill_len=self.POST_RESET_FILL_LEN)
Scheduler.get_next_batch_to_run(s)
Scheduler.get_next_batch_to_run(
s, running_batch=s.running_batch, last_batch=s.last_batch
)
expected = pool.req_to_token[self.POOL_IDX, : self.POST_RESET_FILL_LEN].to(
dtype=torch.int64
@@ -162,7 +168,9 @@ class TestStashGatePreservesPrefixIndices(CustomTestCase):
cache = _make_chunk_cache(pool)
s = _scheduler_for_get_next_batch(tree_cache=cache, chunked_req=None)
Scheduler.get_next_batch_to_run(s)
Scheduler.get_next_batch_to_run(
s, running_batch=s.running_batch, last_batch=s.last_batch
)
self.assertIsNone(s.chunked_req)
@@ -0,0 +1,52 @@
import inspect
import unittest
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import maybe_stub_sgl_kernel
maybe_stub_sgl_kernel()
from sglang.srt.disaggregation.decode import SchedulerDisaggregationDecodeMixin
from sglang.srt.disaggregation.prefill import SchedulerDisaggregationPrefillMixin
from sglang.srt.managers.scheduler import Scheduler
register_cpu_ci(est_time=3, suite="base-a-test-cpu")
FORBIDDEN_TOKENS = ("self.running_batch", "self.last_batch", "self.cur_batch")
DECISION_METHODS = (
Scheduler.get_next_batch_to_run,
Scheduler.get_new_batch_prefill,
Scheduler._get_new_batch_prefill_raw,
Scheduler._abort_on_running_timeout,
Scheduler.is_disable_overlap_for_batch,
SchedulerDisaggregationPrefillMixin.get_next_disagg_prefill_batch_to_run,
SchedulerDisaggregationPrefillMixin.process_prefill_chunk,
SchedulerDisaggregationDecodeMixin.get_new_prebuilt_batch,
SchedulerDisaggregationDecodeMixin.get_next_disagg_decode_batch_to_run,
)
class TestDecisionMethodsHaveNoHiddenBatchChannel(unittest.TestCase):
def test_decision_methods_take_batches_as_params_not_self(self):
"""The batch decision tree must receive running/last batch as params, never via self.*."""
for method in DECISION_METHODS:
source = inspect.getsource(inspect.unwrap(method))
self.assertIn(
f"def {method.__name__}",
source,
msg=f"failed to read the real source of {method.__qualname__}",
)
for token in FORBIDDEN_TOKENS:
self.assertNotIn(
token,
source,
msg=(
f"{method.__qualname__} references {token}; pass the batch "
"explicitly and return it via NextBatchPlan instead."
),
)
if __name__ == "__main__":
unittest.main()