Avoid implicit field-based side channel in Scheduler planning (#29408)
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user