[Bugfix] Fix colocate-async sleep admission race and deliver AR abort tokens (#6367)

Signed-off-by: knlnguyen1802 <knlnguyen1802@gmail.com>
Signed-off-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Samit <285365963@qq.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Samit <285365963@qq.com>
This commit is contained in:
authored and GitHub committed 2026-08-29 09:11:39 +00:00
1 parent 5c47fd0bc8
commit bb5bf8fd0a
23 files changed
+1229 -157

No files matched your search

+80 -4
View File
@@ -256,8 +256,32 @@ class FakeOutputProcessor:
)
def abort_requests(self, request_ids, internal: bool = False):
self.abort_calls.append(request_ids)
return request_ids
aborted_ids, _outputs = self.abort_requests_collecting_outputs(request_ids, internal=internal)
return aborted_ids
def abort_requests_collecting_outputs(self, request_ids, *, internal: bool = False, commit_state: bool = True):
"""Mirror MultimodalOutputProcessor collecting for AR abort-prefix tests."""
del internal, commit_state
ids = list(request_ids)
self.abort_calls.append(ids)
outputs: list[RequestOutput] = []
for rid in ids:
seeded = next(
(ro for ro in self.request_outputs if getattr(ro, "request_id", None) == rid),
None,
)
token_ids = list(seeded.outputs[0].token_ids) if seeded is not None and seeded.outputs else [1, 2]
# Intentionally stamp an internal-looking id on the RequestOutput so
# StagePool must re-key by the orchestrator id it passed in.
outputs.append(
_build_request_output(
f"engine-internal-{rid}",
token_ids=token_ids,
finished=True,
finish_reason="abort",
)
)
return ids, outputs
def update_scheduler_stats(self, _scheduler_stats) -> None:
return None
@@ -320,6 +344,7 @@ def _build_request_output(
prompt_token_ids: list[int] | None = None,
finished: bool = True,
text: str = "test",
finish_reason: str | None = None,
) -> RequestOutput:
completion = CompletionOutput(
index=0,
@@ -327,7 +352,7 @@ def _build_request_output(
token_ids=list(token_ids or [1, 2]),
cumulative_logprob=0.0,
logprobs=None,
finish_reason="stop" if finished else None,
finish_reason=(finish_reason if finish_reason is not None else ("stop" if finished else None)),
stop_reason=None,
)
return RequestOutput(
@@ -983,7 +1008,11 @@ async def test_run_abort(orchestrator_factory) -> None:
async def test_run_abort_emits_result_when_rpc_id_set(orchestrator_factory) -> None:
stages = [
FakeStageClient(stage_type="llm", final_output=False),
FakeStageClient(stage_type="llm", final_output=True),
FakeStageClient(
stage_type="llm",
final_output=True,
next_inputs=[{"prompt_token_ids": [7, 8, 9]}],
),
]
processors = [
FakeOutputProcessor(request_outputs=[_build_request_output("req-ack", token_ids=[1], finished=True)]),
@@ -1002,6 +1031,11 @@ async def test_run_abort_emits_result_when_rpc_id_set(orchestrator_factory) -> N
final_stage_id=1,
)
await _wait_for(lambda: len(stages[0].add_request_calls) == 1)
# Abort outputs come from the final AR stage's output processor, and
# StagePool.abort_requests only collects for requests with a live
# replica binding. Forward off stage-0 first so stage-1 is bound.
stages[0].push_engine_core_outputs(_engine_core_outputs("stage0-raw", 1.0))
await _wait_for(lambda: len(stages[1].add_request_calls) == 1)
orchestrator_fixture.request_sync_q.put_nowait(AbortRequestMessage(request_ids=["req-ack"], rpc_id="abort-1"))
result = await _get_rpc_message(orchestrator_fixture)
@@ -1011,11 +1045,53 @@ async def test_run_abort_emits_result_when_rpc_id_set(orchestrator_factory) -> N
assert result.error is None
assert result.rpc_correlation_key == ("abort", "abort-1")
assert stages[0].abort_calls == [["req-ack"]]
assert stages[1].abort_calls == [["req-ack"]]
assert "req-ack" not in orchestrator_fixture.orchestrator.request_states
assert result.abort_outputs is not None
assert len(result.abort_outputs) == 1
abort_msg = result.abort_outputs[0]
assert abort_msg.request_id == "req-ack"
assert abort_msg.finished is True
assert abort_msg.stage_id == 1
assert list(abort_msg.engine_outputs.outputs[0].token_ids) == [2]
assert abort_msg.engine_outputs.outputs[0].finish_reason == "abort"
finally:
await _shutdown_orchestrator(orchestrator_fixture)
@pytest.mark.asyncio
async def test_abort_marks_only_last_final_stage_output_finished() -> None:
"""A request with two final AR stages must keep earlier abort outputs unfinished."""
stages = [
FakeStageClient(stage_type="llm", final_output=True),
FakeStageClient(stage_type="llm", final_output=True),
]
processors = [
FakeOutputProcessor(request_outputs=[_build_request_output("req-two", token_ids=[1], finished=True)]),
FakeOutputProcessor(request_outputs=[_build_request_output("req-two", token_ids=[2], finished=True)]),
]
pools = _build_stage_pools([[stages[0]], [stages[1]]], output_processors=processors)
pools[0]._request_bindings["req-two"] = 0
pools[1]._request_bindings["req-two"] = 0
orchestrator = Orchestrator(
request_async_queue=asyncio.Queue(),
output_async_queue=asyncio.Queue(),
rpc_async_queue=asyncio.Queue(),
stage_pools=pools,
)
orchestrator.request_states["req-two"] = OrchestratorRequestState(
request_id="req-two",
final_stage_id=1,
final_output_stage_ids={0, 1},
)
outputs = await orchestrator._abort_request_ids(["req-two"])
assert [(msg.stage_id, msg.finished) for msg in outputs] == [(0, False), (1, True)]
assert list(outputs[0].engine_outputs.outputs[0].token_ids) == [1]
assert list(outputs[1].engine_outputs.outputs[0].token_ids) == [2]
@pytest.mark.asyncio
async def test_handle_abort_emits_error_result_on_failure() -> None:
rpc_queue: asyncio.Queue = asyncio.Queue()