[Bugfix] Restore MRV2 full-payload transport for offline Qwen3-TTS (#8010)

Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
Sy03 authored and GitHub committed 2026-09-23 01:51:20 +00:00
1 parent 3f473a358b
commit 5634f03eb1
13 files changed
+284 -26

No files matched your search

@@ -133,7 +133,9 @@ def test_chunk_lifecycle_no_resubmit_and_state_survives_requeue(monkeypatch) ->
monkeypatch.setattr("vllm_omni.core.sched.omni_generation_scheduler.create_request_queue", create_request_queue)
scheduler._native_data_plane = True
scheduler.chunk_transfer_adapter = None
scheduler.input_coordinator = SimpleNamespace(finished_requests=set(), restore_queues=lambda *args, **kwargs: None)
scheduler.input_coordinator = SimpleNamespace(
_async_chunk=True, finished_requests=set(), restore_queues=lambda *args, **kwargs: None
)
scheduler.running = []
scheduler.waiting = create_request_queue(scheduler.policy)
scheduler.skipped_waiting = create_request_queue(scheduler.policy)
@@ -1527,7 +1527,9 @@ def test_async_chunk_reserves_parked_slots_during_ar_admission(monkeypatch, nati
sched.use_v2_model_runner = native
sched.max_num_running_reqs = 8
sched.input_coordinator = (
SimpleNamespace(_waiting_for_chunk_running=[parked], restore_queues=lambda _w, _r: None) if native else None
SimpleNamespace(_async_chunk=True, _waiting_for_chunk_running=[parked], restore_queues=lambda _w, _r: None)
if native
else None
)
sched.chunk_transfer_adapter = (
None
@@ -6,8 +6,10 @@ from types import SimpleNamespace
from typing import Any
import pytest
from vllm.config import SchedulerConfig, VllmConfig
from vllm.v1.engine import FinishReason
from vllm_omni.config.model import OmniModelConfig
from vllm_omni.core.sched import omni_scheduler_mixin
from vllm_omni.core.sched.omni_scheduler_mixin import OmniSchedulerMixin
from vllm_omni.core.sched.output import OmniChunkRecvHandle
@@ -90,6 +92,31 @@ def test_full_payload_coordinator_matches_legacy_gate(monkeypatch, stage_id, asy
assert (scheduler.input_coordinator is not None) is enabled
@pytest.mark.parametrize("async_chunk", [False, True])
@pytest.mark.parametrize(("stage_id", "required"), [(0, False), (1, False), (1, True)])
def test_native_data_plane_uses_the_selected_input_protocol(async_chunk, stage_id, required):
scheduler = _Scheduler()
model_config = object.__new__(OmniModelConfig)
model_config.stage_id = stage_id
model_config.async_chunk = async_chunk
model_config.requires_full_payload_input = required
model_config.supports_native_mrv2_data_plane = True
model_config.use_v2_model_runner = True
scheduler.vllm_config = object.__new__(VllmConfig)
scheduler.vllm_config.model_config = model_config
scheduler.vllm_config.scheduler_config = object.__new__(SchedulerConfig)
scheduler.vllm_config.scheduler_config.max_num_seqs = 1
scheduler._init_omni_io_scheduling_state()
assert scheduler._native_data_plane
if async_chunk or (stage_id > 0 and required):
assert scheduler.input_coordinator._async_chunk is async_chunk
else:
assert scheduler.input_coordinator is None
assert scheduler._async_chunk_transport_enabled() is async_chunk
assert scheduler.chunk_transfer_adapter is None
def test_schedule_lifecycle_helpers_process_and_restore_both_input_paths():
calls: list[tuple[Any, ...]] = []
scheduler = _Scheduler()
@@ -6,12 +6,8 @@
``_process_pending_chunk_timeouts`` covers async-chunk. Both share
``VLLM_OMNI_INPUT_WAIT_TIMEOUT_S``.
Verifies that the mixin correctly *delegates* timed-out requests to the
base scheduler's ``finish_requests`` API with ``RequestStatus.FINISHED_ERROR``.
The end-to-end effect (queue removal + status set + per-request cleanup +
client-facing FINISHED_ERROR emission) is the responsibility of upstream
vLLM's ``finish_requests`` implementation and is covered by upstream tests;
this file only asserts the wiring from the mixin to that API.
Verifies scheduler cleanup and explicit client-facing ERROR outputs. Upstream
``finish_requests`` frees requests but does not emit their terminal output.
"""
from __future__ import annotations
@@ -23,6 +19,9 @@ import os
from types import SimpleNamespace
import pytest
from vllm import SamplingParams
from vllm.v1.engine import EngineCoreOutputs
from vllm.v1.request import Request
from vllm_omni.core.sched.omni_scheduler_mixin import OmniSchedulerMixin
@@ -52,7 +51,7 @@ class _FakeScheduler(OmniSchedulerMixin):
def test_process_pending_input_timeouts_delegates_to_finish_requests():
"""Timed-out request present in self.requests is forwarded to finish_requests."""
req_id = "stuck-req"
requests = {req_id: SimpleNamespace(request_id=req_id)}
requests = {req_id: SimpleNamespace(request_id=req_id, client_index=0)}
coord = _FakeCoordinator(timed_out_ids={req_id})
scheduler = _FakeScheduler(requests, coord)
@@ -142,7 +141,7 @@ def test_process_pending_chunk_timeouts_delegates_to_finish_requests():
"""
req_id = "stalled-stream"
adapter = _FakeChunkAdapter(timed_out_ids={req_id})
scheduler = _FakeChunkScheduler({req_id: SimpleNamespace(request_id=req_id)}, adapter)
scheduler = _FakeChunkScheduler({req_id: SimpleNamespace(request_id=req_id, client_index=0)}, adapter)
scheduler._process_pending_chunk_timeouts()
@@ -154,6 +153,41 @@ def test_process_pending_chunk_timeouts_delegates_to_finish_requests():
assert getattr(status, "name", str(status)).endswith("FINISHED_ERROR")
@pytest.mark.parametrize("chunked", [False, True])
@pytest.mark.parametrize("synthesize_abort_outputs", [False, True])
def test_timeout_emits_error_once_even_without_model_outputs(chunked, synthesize_abort_outputs):
from vllm.v1.engine import FinishReason
request = Request(
request_id="stuck",
prompt_token_ids=[1],
sampling_params=SamplingParams(max_tokens=1),
pooling_params=None,
client_index=3,
)
if chunked:
scheduler = _FakeChunkScheduler({"stuck": request}, _FakeChunkAdapter({"stuck"}))
scheduler._process_pending_chunk_timeouts()
else:
scheduler = _FakeScheduler({"stuck": request}, _FakeCoordinator({"stuck"}))
scheduler._process_pending_input_timeouts()
# Model upstream's finish_requests cleanup. Generation must still emit
# ERROR, and AR's synthetic ABORT must not replace or duplicate it.
scheduler.requests.clear()
scheduler.finished_req_ids_dict = {3: {"stuck"}}
outputs: dict[int, EngineCoreOutputs] = {}
scheduler._attach_finished_request_sets(outputs, synthesize_abort_outputs=synthesize_abort_outputs)
[error] = outputs[3].outputs
assert error.request_id == "stuck"
assert error.finish_reason == FinishReason.ERROR
assert "connector input" in error.stop_reason
assert outputs[3].finished_requests == {"stuck"}
outputs = {}
scheduler._attach_finished_request_sets(outputs, synthesize_abort_outputs=synthesize_abort_outputs)
assert outputs == {}
def test_process_pending_chunk_timeouts_skips_already_freed_request():
adapter = _FakeChunkAdapter(timed_out_ids={"already-freed"})
scheduler = _FakeChunkScheduler(requests={}, adapter=adapter)
+41
View File
@@ -2000,6 +2000,47 @@ async def test_abort_retry_does_not_repeat_successful_stage_abort():
assert second.bound == set()
@pytest.mark.asyncio
@pytest.mark.parametrize("stage_id", [0, 1])
async def test_raw_stage_error_reaches_caller_before_normal_terminal_routing(mocker, stage_id):
pools = _build_stage_pools([[FakeStageClient()], [FakeStageClient()]])
for pool in pools:
mocker.patch.object(pool, "process_llm_raw_outputs", new_callable=mocker.AsyncMock, return_value=[])
output_queue: asyncio.Queue[ErrorMessage] = asyncio.Queue()
orchestrator = Orchestrator(
request_async_queue=asyncio.Queue(),
output_async_queue=output_queue,
rpc_async_queue=asyncio.Queue(),
stage_pools=[],
)
orchestrator.stage_pools = pools
orchestrator.request_states["stuck"] = OrchestratorRequestState(request_id="stuck", final_stage_id=1)
mocker.patch.object(orchestrator, "_handle_kv_ready_raw_outputs", new_callable=mocker.AsyncMock)
cleanup = mocker.patch.object(orchestrator, "_cleanup_request_ids", new_callable=mocker.AsyncMock)
normal_terminal = mocker.patch.object(
orchestrator, "_apply_raw_terminal_stage_finish", new_callable=mocker.AsyncMock
)
reason = "Timed out waiting for connector input after 5s"
raw = EngineCoreOutputs(
outputs=[
OmniEngineCoreOutput(
request_id="stuck", new_token_ids=[], finish_reason=FinishReason.ERROR, stop_reason=reason
)
]
)
terminal_ids: set[str] = set()
await orchestrator._process_llm_stage_outputs(stage_id, 0, raw, terminal_ids)
error = output_queue.get_nowait()
assert isinstance(error, ErrorMessage)
assert (error.request_id, error.stage_id, error.error) == ("stuck", stage_id, reason)
cleanup.assert_awaited_once_with(["stuck"], abort=True, release_owners=True)
normal_terminal.assert_not_awaited()
assert not terminal_ids
assert output_queue.empty()
@pytest.mark.asyncio
async def test_duplex_session_request_error_finish_is_delivered_as_request_error() -> None:
"""A session-owned request the scheduler finished with FinishReason.ERROR
+72
View File
@@ -48,6 +48,8 @@ def _bare_plane(*, delivery_timeout_s=1.0, shutdown_timeout_s=1.0):
_native_output_closed=False,
_native_outputs_in_flight=defaultdict(int),
_native_terminal_pending=set(),
_async_chunk=True,
_pending_full_payload_send={},
_put_req_chunk=defaultdict(int),
_ramp_chunk_count=defaultdict(int),
_delivery_manager=OmniDeliveryManager(
@@ -82,6 +84,76 @@ def plane():
p._stop_output_worker()
def test_full_payload_waits_for_terminal_and_last_deferred_frame(plane):
plane._async_chunk = False
plane._full_payload_replace_keys_cached = frozenset({"codes.ref"})
request = _new_request()
request.resumable = False
plane.register_request(request)
first = torch.tensor([[1, 2]])
last = torch.tensor([[3, 4]])
ref = torch.tensor([[5, 6]])
plane.reserve_outputs(["internal"])
assert _complete(plane, [{"codes.audio": first, "codes.ref": ref}], token=21) == 0
assert not plane.record.batches
plane.reserve_outputs(["internal"])
assert plane.request_terminal({"internal"}) == 0
assert not plane.record.cleaned
assert _complete(plane, [{"codes.audio": last, "codes.ref": ref}], token=2150) == 1
[(snapshot, payload)] = plane.record.batches[0]
assert snapshot.is_finished()
assert snapshot.output_token_ids == [21, 2150]
torch.testing.assert_close(payload["codes.audio"], torch.cat([first, last]))
torch.testing.assert_close(payload["codes.ref"], ref)
assert plane.record.cleaned == ["internal"]
assert not plane._pending_full_payload_send
assert plane.request_terminal({"internal"}) == 0
def test_full_payload_abort_discards_partial_and_late_outputs(plane):
plane._async_chunk = False
plane._full_payload_replace_keys_cached = frozenset()
plane.register_request(_new_request())
assert _complete(plane, [{"codes.audio": torch.tensor([[1, 2]])}]) == 0
plane.reserve_outputs(["internal"])
assert plane.abort_requests({"internal"}) == 1
[(snapshot, payload)] = plane.record.batches[0]
assert snapshot.is_finished() and payload is None
assert _complete(plane, [{"codes.audio": torch.tensor([[3, 4]])}]) == 0
assert len(plane.record.batches) == 1
assert not plane._pending_full_payload_send
def test_full_payload_qwen_builder_receives_complete_codec(raw_plane, monkeypatch):
from vllm_omni.model_executor.stage_input_processors.qwen3_tts import talker2code2wav_full_payload
raw_plane._async_chunk = False
raw_plane._omni_connector = object()
raw_plane._request_ids_mapping = {}
raw_plane._custom_process_func = talker2code2wav_full_payload
raw_plane._custom_process_batch_func = None
monkeypatch.setattr(raw_plane, "is_data_transfer_rank", lambda: True)
sent = []
def publish(entries, **kwargs):
sent.extend(entries)
return len(entries)
monkeypatch.setattr(raw_plane, "_publish_chunk_cohort", publish)
raw_plane.register_request(_new_request())
for token in (21, 22, 2150):
_complete(raw_plane, [{"codes.audio": torch.full((1, 16), token)}], token=token)
assert not sent
assert raw_plane.request_terminal({"internal"}) == 1
[(request, payload)] = sent
assert request.output_token_ids == [21, 22, 2150]
assert payload["meta"]["finished"].item()
# EOS is filtered, and the two remaining frames are codebook-major.
assert payload["codes"]["audio"].tolist() == [21, 22] * 16
def _gated_connector():
# put() blocks its first call until released (or forever without release).
conn = SimpleNamespace(
@@ -15,6 +15,7 @@ from vllm.v1.core.sched.output import SchedulerOutput
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
from vllm.v1.worker.gpu.sample.sampler import Sampler
from vllm_omni.config.model import OmniModelConfig
from vllm_omni.model_executor.models.output_templates import OmniOutput
from vllm_omni.worker_v2.model_states import init_omni_model_state
from vllm_omni.worker_v2.model_states.omni_model_state import OmniModelState
@@ -53,6 +54,8 @@ def test_add_requests_empty_admission_and_stop_id_sanitization():
def test_prepare_native_data_plane_terminal_abort_split_and_warmup_skip():
runner = _make_runner()
runner.model_config = object.__new__(OmniModelConfig)
runner.model_config.async_chunk = True
plane = SimpleNamespace(
register_request=MagicMock(),
register_receivers=MagicMock(),
@@ -78,6 +81,19 @@ def test_prepare_native_data_plane_terminal_abort_split_and_warmup_skip():
plane.abort_requests.assert_called_once_with({"aborted"})
def test_full_payload_receive_is_polled_without_scheduled_tokens(mocker):
runner = object.__new__(OmniGPUModelRunner)
runner.model_config = object.__new__(OmniModelConfig)
runner.model_config.async_chunk = False
plane = mocker.Mock()
runner._omni_data_plane = plane
scheduler_output = SchedulerOutput.make_empty()
runner._prepare_native_data_plane(scheduler_output)
plane.recv_full_payload_inputs.assert_called_once_with(scheduler_output)
@pytest.mark.parametrize("output_form", ["tuple", "omni"])
def test_capture_model_unwraps_exclude_full_and_capture_mtp(output_form):
runner = object.__new__(OmniGPUModelRunner)
@@ -189,7 +189,11 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
Stateful codecs retain lifetime admission until their decoder state is
released, even when no chunk is currently executing.
"""
if not getattr(self, "_native_data_plane", False) or self._retains_state_across_chunks:
if (
not getattr(self, "_native_data_plane", False)
or not self._async_chunk_transport_enabled()
or self._retains_state_across_chunks
):
return
in_flight: list[Request] = []
for request in self.running:
@@ -236,7 +240,7 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
self._drop_aborted_queued_requests()
self._resync_streaming_input_counter()
async_chunk_transport = self._async_chunk_transport_enabled()
native_chunks = bool(getattr(self, "_native_data_plane", False))
native_chunks = bool(getattr(self, "_native_data_plane", False) and async_chunk_transport)
# Parking releases an execution slot, but stateful codecs retain their
# request-owned state until completion or abort, in either runner.
reserved_running_slots = (
+34 -3
View File
@@ -112,6 +112,7 @@ class OmniSchedulerMixin:
requests: dict[str, Request]
waiting: RequestQueue
running: list[Request]
_pending_input_timeout_outputs: dict[str, tuple[int, OmniEngineCoreOutput]]
def _init_omni_connector_output_inbox(self) -> None:
self._omni_connector_output_inbox: queue.SimpleQueue[OmniConnectorOutput] = queue.SimpleQueue()
@@ -149,7 +150,7 @@ class OmniSchedulerMixin:
else None
)
self.input_coordinator: OmniSchedulingCoordinator | None = None
if self._native_data_plane:
if self._native_data_plane and getattr(model_config, "async_chunk", False):
self.input_coordinator = OmniSchedulingCoordinator(
scheduler_max_num_seqs=self.vllm_config.scheduler_config.max_num_seqs,
stage_id=getattr(model_config, "stage_id", 0),
@@ -251,6 +252,7 @@ class OmniSchedulerMixin:
def _async_chunk_transport_enabled(self) -> bool:
return getattr(self, "chunk_transfer_adapter", None) is not None or bool(
getattr(self, "_native_data_plane", False)
and getattr(getattr(self, "input_coordinator", None), "_async_chunk", False)
)
def _get_async_chunk_reserved_running_slots(self) -> int:
@@ -453,7 +455,29 @@ class OmniSchedulerMixin:
DEFAULT_INPUT_WAIT_TIMEOUT_S,
sorted(present_ids),
)
self.finish_requests(present_ids, RequestStatus.FINISHED_ERROR)
self._finish_input_timeout_requests(present_ids)
def _finish_input_timeout_requests(self, request_ids: set[str]) -> None:
# Upstream finish_requests frees scheduler state, but does not emit
# EngineCoreOutput. Keep an explicit ERROR for both AR and generation
# callers, including a tick with no scheduled model work.
pending = getattr(self, "_pending_input_timeout_outputs", None)
if pending is None:
pending = self._pending_input_timeout_outputs = {}
for request_id in request_ids:
request = self.requests[request_id]
reason = f"Timed out waiting for connector input after {DEFAULT_INPUT_WAIT_TIMEOUT_S:g}s"
request.stop_reason = reason
pending[request_id] = (
request.client_index,
OmniEngineCoreOutput(
request_id=request_id,
new_token_ids=[],
finish_reason=FinishReason.ERROR,
stop_reason=reason,
),
)
self.finish_requests(request_ids, RequestStatus.FINISHED_ERROR)
def _log_failed_chunk_sends(self) -> None:
"""Surface chunks the sender gave up on (R1.2 of #4855).
@@ -531,7 +555,7 @@ class OmniSchedulerMixin:
DEFAULT_INPUT_WAIT_TIMEOUT_S,
sorted(present_ids),
)
self.finish_requests(present_ids, RequestStatus.FINISHED_ERROR)
self._finish_input_timeout_requests(present_ids)
def _capture_omni_connector_output(self, model_runner_output: Any) -> None:
"""Stash the model runner's omni_connector_output for next schedule().
@@ -712,6 +736,13 @@ class OmniSchedulerMixin:
synthesize_abort_outputs: bool,
) -> None:
"""Attach finished IDs while keeping AR's synthetic-abort policy explicit."""
pending = getattr(self, "_pending_input_timeout_outputs", None)
if pending:
for client_index, error_output in pending.values():
output = engine_core_outputs.setdefault(client_index, EngineCoreOutputs())
if not any(item.request_id == error_output.request_id for item in output.outputs):
output.outputs.append(error_output)
pending.clear()
finished_req_ids = self.finished_req_ids_dict
if not finished_req_ids:
return
@@ -25,11 +25,7 @@ def uses_native_mrv2_data_plane(
*,
use_v2_model_runner: bool,
) -> bool:
return bool(
use_v2_model_runner
and getattr(model_config, "async_chunk", False)
and getattr(model_config, "supports_native_mrv2_data_plane", False)
)
return bool(use_v2_model_runner and getattr(model_config, "supports_native_mrv2_data_plane", False))
def uses_full_payload_input_coordinator(model_config: Any) -> bool:
+14 -2
View File
@@ -789,6 +789,18 @@ class OrchestratorBase:
req_state = self.request_states.get(getattr(eco, "request_id", None))
if req_state is None:
continue
if (
getattr(eco, "finish_reason", None) == FinishReason.ERROR
and not getattr(eco, "is_segment_finished", False)
and not req_state.session_owned
):
reason = getattr(eco, "stop_reason", None)
await self._handle_stage_error(
stage_id,
eco,
error=reason if isinstance(reason, str) and reason else "Stage request failed",
)
continue
if req_state.streaming.enabled:
segment_finished = bool(getattr(eco, "is_segment_finished", False))
raw_mm = self._completion_multimodal_output(eco, None)
@@ -1200,7 +1212,7 @@ class OrchestratorBase:
await self._route_output(stage_id, replica_id, output, req_state, stage_metrics)
async def _handle_stage_error(self, stage_id: int, output: Any) -> None:
async def _handle_stage_error(self, stage_id: int, output: Any, *, error: str | None = None) -> None:
"""Emit a frontend-visible error and clean up request state."""
if self._cfg_tracker.is_companion(output.request_id):
parent_id = self._cfg_tracker.get_parent_id(output.request_id) or output.request_id
@@ -1210,7 +1222,7 @@ class OrchestratorBase:
ErrorMessage(
request_id=parent_id,
stage_id=stage_id,
error=output.error,
error=error if error is not None else output.error,
status_code=getattr(output, "error_status_code", None),
error_type=getattr(output, "error_type", None),
)
+23 -4
View File
@@ -15,9 +15,10 @@ from typing import Any, cast
import torch
from vllm.logger import init_logger
from vllm_omni.data_entry_keys import OmniPayload, unflatten_payload
from vllm_omni.data_entry_keys import OmniPayload, flatten_payload, unflatten_payload
from vllm_omni.worker.omni_connector_model_runner_mixin import (
OmniConnectorModelRunnerMixin,
should_accumulate_full_payload_output,
)
from vllm_omni.worker.payload_span import get_tensor_span, merge_tensor_spans
from vllm_omni.worker_v2.delivery import (
@@ -87,7 +88,11 @@ class OmniRunnerDataPlane(OmniConnectorModelRunnerMixin):
delivery_timeout_s=self._connector_delivery_timeout(),
shutdown_timeout_s=_DEFAULT_SHUTDOWN_TIMEOUT_S,
)
self._can_send = self._custom_process_func is not None
self._can_send = (
self._custom_process_func is not None
if self._async_chunk
else should_accumulate_full_payload_output(model_config, self._custom_process_func)
)
self._start_output_worker(max_pending_batches=_NATIVE_OUTPUT_QUEUE_DEPTH)
def _connector_delivery_timeout(self) -> float:
@@ -294,6 +299,8 @@ class OmniRunnerDataPlane(OmniConnectorModelRunnerMixin):
for req_id in active_req_ids:
self._native_outputs_in_flight.pop(req_id, None)
self._native_terminal_pending.discard(req_id)
# Cancellation must not flush a partially accumulated utterance.
self._pending_full_payload_send.pop(req_id, None)
return self.emit_chunks(
req_ids=[],
inter_stage_outputs=None,
@@ -494,11 +501,23 @@ class OmniRunnerDataPlane(OmniConnectorModelRunnerMixin):
state.output_token_ids.extend(int(token_id) for token_id in sampled_by_req.get(req_id, []))
state.finished = req_id in terminal_req_ids
payload = payload_by_req.get(req_id)
if isinstance(payload, dict):
if not self._async_chunk:
# Full-payload consumers execute once, after the producer has
# finished. Reuse V1's concat/replace contract for flat outputs
# and retain the final token history for codec-row cropping.
if isinstance(payload, dict):
self.accumulate_full_payload_output(req_id, flatten_payload(payload), state)
if not state.finished:
continue
accumulated = self._pending_full_payload_send.pop(req_id, None)
payload = self._materialize_full_payload_entry(accumulated)[0] if accumulated is not None else None
if self._async_chunk and isinstance(payload, dict):
payload = unflatten_payload(payload)
if payload is None and not state.finished:
continue
include_token_history = bool(state.resumable or self._put_req_chunk.get(state.external_req_id, 0) == 0)
include_token_history = bool(
not self._async_chunk or state.resumable or self._put_req_chunk.get(state.external_req_id, 0) == 0
)
entries.append(
(
state.snapshot(include_token_history=include_token_history),
+2
View File
@@ -208,6 +208,8 @@ class OmniGPUModelRunner(GPUModelRunner):
continue
plane.register_request(request_data)
plane.register_receivers(list(getattr(scheduler_output, "pending_input_registrations", [])))
if not getattr(self.model_config, "async_chunk", False):
plane.recv_full_payload_inputs(scheduler_output)
natural_terminal_req_ids = set(getattr(scheduler_output, "data_plane_terminal_req_ids", set()))
aborted_req_ids = set(getattr(scheduler_output, "finished_req_ids", set())).difference(natural_terminal_req_ids)
if natural_terminal_req_ids: