Signed-off-by: Lianmin Zheng <lianminzheng@gmail.com>
This commit is contained in:
@@ -305,8 +305,7 @@ class TestGenerateReqInputNormalization(CustomTestCase):
|
||||
# Modalities should be set for all 3 examples
|
||||
self.assertEqual(req.modalities, ["image", "image", "image"])
|
||||
|
||||
def test_parallel_sampling_keeps_one_logical_rid_per_prompt(self):
|
||||
"""Test logical RID and reasoning control preservation across parallel samples."""
|
||||
def test_parallel_sampling_preserves_reasoning_controls(self):
|
||||
single = GenerateReqInput(
|
||||
text="Hello",
|
||||
rid="single",
|
||||
@@ -316,8 +315,11 @@ class TestGenerateReqInputNormalization(CustomTestCase):
|
||||
)
|
||||
single.normalize_batch_and_arguments()
|
||||
|
||||
self.assertEqual(single.rid, ["single"])
|
||||
self.assertEqual([single[i].rid for i in range(3)], ["single"] * 3)
|
||||
self.assertEqual(single.rid, ["single_0", "single_1", "single_2"])
|
||||
self.assertEqual(
|
||||
[single[i].rid for i in range(3)],
|
||||
["single_0", "single_1", "single_2"],
|
||||
)
|
||||
self.assertTrue(all(single[i].require_reasoning for i in range(3)))
|
||||
self.assertEqual(
|
||||
[single[i].max_thinking_tokens for i in range(3)],
|
||||
@@ -331,10 +333,10 @@ class TestGenerateReqInputNormalization(CustomTestCase):
|
||||
)
|
||||
batch.normalize_batch_and_arguments()
|
||||
|
||||
self.assertEqual(batch.rid, ["batch_0", "batch_1"])
|
||||
self.assertEqual(batch.rid, ["batch_0", "batch_1", "batch_2", "batch_3"])
|
||||
self.assertEqual(
|
||||
[batch[i].rid for i in range(4)],
|
||||
["batch_0", "batch_1", "batch_0", "batch_1"],
|
||||
["batch_0", "batch_1", "batch_2", "batch_3"],
|
||||
)
|
||||
|
||||
def test_audio_data_handling(self):
|
||||
@@ -680,15 +682,6 @@ class TestGenerateReqInputNormalization(CustomTestCase):
|
||||
self.assertNotEqual(original_rid, new_rid)
|
||||
self.assertEqual(req.rid, new_rid)
|
||||
|
||||
def test_regenerate_rid_with_parent_prefix(self):
|
||||
"""Test RID regeneration with a logical parent prefix."""
|
||||
req = GenerateReqInput(text="Hello", rid="logical")
|
||||
req.normalize_batch_and_arguments()
|
||||
|
||||
new_rid = req.regenerate_rid(prefix="logical")
|
||||
|
||||
self.assertTrue(new_rid.startswith("logical_"))
|
||||
|
||||
def test_error_cases(self):
|
||||
"""Test various error cases."""
|
||||
# Test when neither text, input_ids, nor input_embeds is provided
|
||||
|
||||
@@ -30,7 +30,6 @@ from sglang.srt.managers.io_struct import ( # noqa: E402
|
||||
)
|
||||
from sglang.srt.managers.tokenizer_manager import ( # noqa: E402
|
||||
ReqState,
|
||||
RequestAbortedError,
|
||||
TokenizerManager,
|
||||
)
|
||||
from sglang.srt.observability.req_time_stats import ( # noqa: E402
|
||||
@@ -121,8 +120,6 @@ def _make_tokenizer_manager() -> TokenizerManager:
|
||||
tm.server_args.dp_size = 1
|
||||
tm.disaggregation_mode = "none"
|
||||
tm.rid_to_state = {}
|
||||
tm.logical_rid_to_child_rids = {}
|
||||
tm.child_rid_to_logical_rid = {}
|
||||
tm.enable_metrics = False
|
||||
tm.enable_trace = False
|
||||
tm.enable_lora = False
|
||||
@@ -132,11 +129,10 @@ def _make_tokenizer_manager() -> TokenizerManager:
|
||||
tm.dump_requests_folder = ""
|
||||
tm.crash_dump_folder = ""
|
||||
tm.send_to_scheduler = MagicMock()
|
||||
tm._dispatch_to_scheduler = Mock()
|
||||
return tm
|
||||
|
||||
|
||||
def _make_req_state(rid: str = "test_rid", *, dispatched: bool = False) -> ReqState:
|
||||
def _make_req_state(rid: str = "test_rid") -> ReqState:
|
||||
"""Create a minimal ReqState for testing."""
|
||||
obj = Mock(spec=GenerateReqInput)
|
||||
obj.rid = rid
|
||||
@@ -150,7 +146,6 @@ def _make_req_state(rid: str = "test_rid", *, dispatched: bool = False) -> ReqSt
|
||||
event=asyncio.Event(),
|
||||
obj=obj,
|
||||
time_stats=APIServerReqTimeStats(),
|
||||
dispatched=dispatched,
|
||||
)
|
||||
|
||||
|
||||
@@ -352,19 +347,6 @@ class TestInitReqStateDuplicateDetection(CustomTestCase):
|
||||
tm._init_req_state(obj)
|
||||
self.assertIn(rid, tm.rid_to_state)
|
||||
|
||||
def test_batch_duplicate_preflight_does_not_insert_partial_state(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
existing_rid = "existing"
|
||||
existing_state = _make_req_state(existing_rid)
|
||||
tm.rid_to_state[existing_rid] = existing_state
|
||||
obj = _make_generate_obj(["new", existing_rid], is_single=False)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "Duplicate request ID"):
|
||||
tm._init_req_state(obj)
|
||||
|
||||
self.assertNotIn("new", tm.rid_to_state)
|
||||
self.assertIs(tm.rid_to_state[existing_rid], existing_state)
|
||||
|
||||
|
||||
class TestResubmitAfterCompletion(CustomTestCase):
|
||||
"""End-to-end test: complete a request, then resubmit with the same rid."""
|
||||
@@ -467,20 +449,17 @@ def _make_generate_obj(rid, is_single):
|
||||
class TestDiscardPendingReqStates(CustomTestCase):
|
||||
"""Direct tests for _discard_pending_req_states."""
|
||||
|
||||
def test_discard_single_aborts_scheduler_before_cleanup(self):
|
||||
def test_discard_single(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
rid = "d_single"
|
||||
tm.rid_to_state[rid] = _make_req_state(rid, dispatched=True)
|
||||
tm.rid_to_state[rid] = _make_req_state(rid)
|
||||
obj = Mock(spec=GenerateReqInput)
|
||||
obj.is_single = True
|
||||
obj.rid = rid
|
||||
tm._discard_pending_req_states(obj)
|
||||
self.assertNotIn(rid, tm.rid_to_state)
|
||||
abort_req = tm._dispatch_to_scheduler.call_args.args[0]
|
||||
self.assertEqual(abort_req.rid, rid)
|
||||
self.assertFalse(abort_req.abort_all)
|
||||
|
||||
def test_discard_unsent_batch_without_scheduler_abort(self):
|
||||
def test_discard_batch_removes_all(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
rids = ["d0", "d1", "d2"]
|
||||
for r in rids:
|
||||
@@ -491,7 +470,6 @@ class TestDiscardPendingReqStates(CustomTestCase):
|
||||
tm._discard_pending_req_states(obj)
|
||||
for r in rids:
|
||||
self.assertNotIn(r, tm.rid_to_state)
|
||||
tm._dispatch_to_scheduler.assert_not_called()
|
||||
|
||||
def test_discard_ignores_already_removed(self):
|
||||
"""Popping a rid that is no longer present must not raise."""
|
||||
@@ -503,62 +481,6 @@ class TestDiscardPendingReqStates(CustomTestCase):
|
||||
tm._discard_pending_req_states(obj) # must not raise
|
||||
self.assertNotIn("p1", tm.rid_to_state)
|
||||
|
||||
def test_parallel_cleanup_aborts_children_and_allows_parent_reuse(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
parent = _make_generate_obj("parent", is_single=True)
|
||||
lifecycle_ids = tm._init_req_state(parent)
|
||||
|
||||
child_rids = {"prefix", "choice_0", "choice_1"}
|
||||
for child_rid in child_rids:
|
||||
child = _make_generate_obj(child_rid, is_single=True)
|
||||
tm._init_child_req_state("parent", child)
|
||||
tm.rid_to_state[child_rid].dispatched = True
|
||||
tm._remove_req_state("parent")
|
||||
|
||||
tm._discard_pending_req_states(parent, lifecycle_ids)
|
||||
|
||||
aborted_rids = {
|
||||
call.args[0].rid for call in tm._dispatch_to_scheduler.call_args_list
|
||||
}
|
||||
self.assertEqual(aborted_rids, child_rids)
|
||||
self.assertFalse(tm.rid_to_state)
|
||||
self.assertFalse(tm.logical_rid_to_child_rids)
|
||||
self.assertFalse(tm.child_rid_to_logical_rid)
|
||||
|
||||
tm._init_req_state(_make_generate_obj("parent", is_single=True))
|
||||
self.assertIn("parent", tm.rid_to_state)
|
||||
|
||||
def test_stale_cleanup_does_not_remove_reused_rid(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
old_obj = _make_generate_obj("reused", is_single=True)
|
||||
old_lifecycle_ids = tm._init_req_state(old_obj)
|
||||
tm._remove_req_state("reused")
|
||||
|
||||
replacement = _make_generate_obj("reused", is_single=True)
|
||||
tm._init_req_state(replacement)
|
||||
replacement_state = tm.rid_to_state["reused"]
|
||||
|
||||
tm._discard_pending_req_states(old_obj, old_lifecycle_ids)
|
||||
|
||||
self.assertIs(tm.rid_to_state["reused"], replacement_state)
|
||||
tm._dispatch_to_scheduler.assert_not_called()
|
||||
|
||||
|
||||
class TestParallelAbortRouting(CustomTestCase):
|
||||
def test_parent_abort_fans_out_to_children(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
tm.server_args.tokenizer_worker_num = 1
|
||||
tm._register_child_rid("parent", "choice_0")
|
||||
tm._register_child_rid("parent", "choice_1")
|
||||
|
||||
tm.abort_request("parent")
|
||||
|
||||
requests = [call.args[0] for call in tm._dispatch_to_scheduler.call_args_list]
|
||||
self.assertEqual(
|
||||
{request.rid for request in requests}, {"choice_0", "choice_1"}
|
||||
)
|
||||
self.assertTrue(all(not request.abort_all for request in requests))
|
||||
|
||||
|
||||
class TestParallelStreamTaskCleanup(CustomTestCase):
|
||||
def test_failing_choice_cancels_and_closes_sibling_waiters(self):
|
||||
@@ -614,40 +536,6 @@ class TestParallelStreamTaskCleanup(CustomTestCase):
|
||||
asyncio.run(drive())
|
||||
|
||||
|
||||
class TestParallelRidReuse(CustomTestCase):
|
||||
def test_completed_n2_request_can_repeat_the_same_logical_rid(self):
|
||||
tm = _make_tokenizer_manager()
|
||||
|
||||
async def complete_child(rid):
|
||||
await tm._handle_batch_output(_make_batch_str_output(rid))
|
||||
|
||||
for _ in range(2):
|
||||
logical = GenerateReqInput(
|
||||
text="hello",
|
||||
rid="repeat-n2",
|
||||
sampling_params={"n": 2},
|
||||
)
|
||||
logical.normalize_batch_and_arguments()
|
||||
tm._init_req_state(logical)
|
||||
|
||||
prefix = GenerateReqInput(text="hello", rid="prefix")
|
||||
prefix.normalize_batch_and_arguments()
|
||||
tm._init_child_req_state("repeat-n2", prefix)
|
||||
asyncio.run(complete_child("prefix"))
|
||||
|
||||
for child_rid in ("choice-0", "choice-1"):
|
||||
child = GenerateReqInput(text="hello", rid=child_rid)
|
||||
child.normalize_batch_and_arguments()
|
||||
tm._init_child_req_state("repeat-n2", child)
|
||||
tm._remove_req_state("repeat-n2")
|
||||
asyncio.run(complete_child("choice-0"))
|
||||
asyncio.run(complete_child("choice-1"))
|
||||
|
||||
self.assertFalse(tm.rid_to_state)
|
||||
self.assertFalse(tm.logical_rid_to_child_rids)
|
||||
self.assertFalse(tm.child_rid_to_logical_rid)
|
||||
|
||||
|
||||
class TestGenerateRequestCleanupOnDispatchFailure(CustomTestCase):
|
||||
"""generate_request must not leak rid_to_state when dispatch fails.
|
||||
|
||||
@@ -674,7 +562,6 @@ class TestGenerateRequestCleanupOnDispatchFailure(CustomTestCase):
|
||||
# Got past _init_req_state (which created the entry) ...
|
||||
tm._tokenize_one_request.assert_awaited_once()
|
||||
tm._send_one_request.assert_not_called()
|
||||
tm._dispatch_to_scheduler.assert_not_called()
|
||||
# ... and the entry was cleaned up rather than leaked.
|
||||
self.assertNotIn(rid, tm.rid_to_state)
|
||||
|
||||
@@ -699,49 +586,6 @@ class TestGenerateRequestCleanupOnDispatchFailure(CustomTestCase):
|
||||
# All sub-request entries created by _init_req_state are cleaned up.
|
||||
for r in rids:
|
||||
self.assertNotIn(r, tm.rid_to_state)
|
||||
tm._dispatch_to_scheduler.assert_not_called()
|
||||
|
||||
def test_interrupted_parallel_tokenization_prevents_child_dispatch(self):
|
||||
for remove_state in (False, True):
|
||||
with self.subTest(remove_state=remove_state):
|
||||
tm = _make_tm_for_generate()
|
||||
tm._send_one_request = Mock()
|
||||
obj = GenerateReqInput(
|
||||
text="hello",
|
||||
rid="interrupted-during-tokenization",
|
||||
sampling_params={"n": 2},
|
||||
)
|
||||
|
||||
async def drive():
|
||||
tokenization_started = asyncio.Event()
|
||||
allow_tokenization = asyncio.Event()
|
||||
|
||||
async def blocked_tokenization(_obj):
|
||||
tokenization_started.set()
|
||||
await allow_tokenization.wait()
|
||||
return MagicMock()
|
||||
|
||||
tm._tokenize_one_request = blocked_tokenization
|
||||
response = tm.generate_request(obj)
|
||||
task = asyncio.create_task(response.__anext__())
|
||||
await tokenization_started.wait()
|
||||
if remove_state:
|
||||
tm._remove_req_state("interrupted-during-tokenization")
|
||||
else:
|
||||
tm.abort_request("interrupted-during-tokenization")
|
||||
allow_tokenization.set()
|
||||
with self.assertRaisesRegex(
|
||||
RequestAbortedError, "interrupted-during-tokenization"
|
||||
):
|
||||
await task
|
||||
|
||||
asyncio.run(drive())
|
||||
|
||||
tm._send_one_request.assert_not_called()
|
||||
tm._dispatch_to_scheduler.assert_not_called()
|
||||
self.assertFalse(tm.rid_to_state)
|
||||
self.assertFalse(tm.logical_rid_to_child_rids)
|
||||
self.assertFalse(tm.child_rid_to_logical_rid)
|
||||
|
||||
def test_thinking_budget_rejects_runtime_without_strict_thinking(self):
|
||||
tm = _make_tm_for_generate()
|
||||
|
||||
@@ -278,8 +278,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
transport.prepare_for_dispatch.return_value = []
|
||||
manager.cuda_vmm_feature_transport = transport
|
||||
manager._dispatch_to_scheduler = MagicMock()
|
||||
state = SimpleNamespace(dispatched=False)
|
||||
manager.rid_to_state = {"test-request": state}
|
||||
tokenized_obj = SimpleNamespace(
|
||||
rid="test-request",
|
||||
mm_inputs=None,
|
||||
@@ -293,7 +291,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
manager._dispatch_to_scheduler.assert_called_once_with(tokenized_obj)
|
||||
transport.prepare_for_dispatch.assert_called_once_with((None,))
|
||||
transport.cancel_for_dispatch.assert_not_called()
|
||||
self.assertTrue(state.dispatched)
|
||||
|
||||
def test_failed_dispatch_cancels_published_items(self):
|
||||
from sglang.srt.managers import tokenizer_manager
|
||||
@@ -308,8 +305,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
manager._dispatch_to_scheduler = MagicMock(
|
||||
side_effect=RuntimeError("send failed")
|
||||
)
|
||||
state = SimpleNamespace(dispatched=False)
|
||||
manager.rid_to_state = {"test-request": state}
|
||||
items = [MultimodalDataItem(modality=Modality.IMAGE, feature=torch.arange(2))]
|
||||
tokenized_obj = SimpleNamespace(
|
||||
rid="test-request",
|
||||
@@ -330,7 +325,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
(tokenized_obj.mm_inputs,)
|
||||
)
|
||||
transport.cancel_for_dispatch.assert_called_once_with(items)
|
||||
self.assertFalse(state.dispatched)
|
||||
|
||||
def test_post_dispatch_failure_does_not_cancel_published_items(self):
|
||||
from sglang.srt.managers import tokenizer_manager
|
||||
@@ -343,8 +337,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
manager = object.__new__(tokenizer_manager.TokenizerManager)
|
||||
transport = MagicMock()
|
||||
manager._dispatch_to_scheduler = MagicMock()
|
||||
state = SimpleNamespace(dispatched=False)
|
||||
manager.rid_to_state = {"test-request": state}
|
||||
time_stats = MagicMock()
|
||||
time_stats.set_api_server_dispatch_finish_time.side_effect = RuntimeError(
|
||||
"bookkeeping failed"
|
||||
@@ -367,7 +359,6 @@ class TestCudaVmmFeatureTransport(unittest.TestCase):
|
||||
|
||||
manager._dispatch_to_scheduler.assert_called_once_with(tokenized_obj)
|
||||
transport.cancel_for_dispatch.assert_not_called()
|
||||
self.assertTrue(state.dispatched)
|
||||
|
||||
def test_prepare_batch_cancels_prior_groups_on_failure(self):
|
||||
from sglang.srt.utils.cuda_vmm_transport_utils import (
|
||||
|
||||
Reference in New Issue
Block a user