[WIP][Bugfix][MiniCPM-o] Fix Full duplex bugs for minicpm-challenge (#5496)

Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
Sy03 authored and GitHub committed 2026-07-28 15:59:26 +08:00
1 parent fb89ab43ea
commit ffa9a6af69
60 files changed
+3696 -634

No files matched your search

+122 -1
View File
@@ -14,7 +14,7 @@ from vllm.v1.engine import FinishReason
from vllm.v1.engine.output_processor import OutputProcessor as VLLMOutputProcessor
from vllm_omni.outputs import output_processor
from vllm_omni.outputs.output_modality import OutputModalityNames
from vllm_omni.outputs.output_modality import OutputModality, OutputModalityNames
from vllm_omni.outputs.output_processor import MultimodalOutputProcessor, OmniRequestState
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -452,6 +452,127 @@ def test_no_detokenizer_make_request_output():
assert AUDIO in result.outputs[0].multimodal_output
def test_no_detokenizer_process_outputs_returns_nonterminal_audio_chunk(monkeypatch):
"""Generation-stage MM-only chunks must reach the orchestrator.
StagePool registers generation requests without a per-request collector,
so the processor return value is the only path from Stage2 to
Orchestrator._handle_processed_outputs().
"""
state = _make_no_detok_state(RequestOutputKind.DELTA)
processor = object.__new__(MultimodalOutputProcessor)
processor.output_modality = OutputModality.AUDIO
processor.request_states = {"r": state}
upstream_result = SimpleNamespace(request_outputs=[], reqs_to_abort=[])
monkeypatch.setattr(
VLLMOutputProcessor,
"process_outputs",
lambda *_args, **_kwargs: upstream_result,
)
engine_output = SimpleNamespace(
request_id="r",
multimodal_output={
"model_outputs": torch.arange(8, dtype=torch.float32),
"sr": torch.tensor(24000, dtype=torch.int32),
},
output_type="audio",
pooling_output=None,
new_token_ids=[],
finish_reason=None,
stop_reason=None,
kv_transfer_params=None,
routed_experts=None,
num_cached_tokens=0,
)
result = processor.process_outputs([engine_output])
assert len(result.request_outputs) == 1
assert result.request_outputs[0].request_id == "r"
assert AUDIO in result.request_outputs[0].outputs[0].multimodal_output
def _make_mm_only_output_processor(monkeypatch):
processor = object.__new__(MultimodalOutputProcessor)
processor.output_modality = OutputModality.AUDIO
processor.request_states = {"r": _make_no_detok_state(RequestOutputKind.DELTA)}
monkeypatch.setattr(
VLLMOutputProcessor,
"process_outputs",
lambda *_args, **_kwargs: SimpleNamespace(
request_outputs=[],
reqs_to_abort=[],
),
)
def finish_request(req_state):
processor.request_states.pop(req_state.request_id, None)
processor._finish_request = finish_request
return processor
def _audio_engine_output(*, is_segment_finished: bool, is_last_chunk: bool):
return SimpleNamespace(
request_id="r",
multimodal_output={
"model_outputs": torch.arange(8, dtype=torch.float32),
"meta.tts_is_last_chunk": torch.tensor([int(is_last_chunk)]),
},
output_type="audio",
pooling_output=None,
new_token_ids=[],
finish_reason=FinishReason.STOP,
stop_reason=None,
kv_transfer_params=None,
routed_experts=None,
num_cached_tokens=0,
is_segment_finished=is_segment_finished,
)
def test_mm_only_segment_finish_retains_request_state_for_next_audio_chunk(monkeypatch):
processor = _make_mm_only_output_processor(monkeypatch)
first = processor.process_outputs([_audio_engine_output(is_segment_finished=True, is_last_chunk=False)])
assert "r" in processor.request_states
assert len(first.request_outputs) == 1
assert first.request_outputs[0].finished is False
second = processor.process_outputs([_audio_engine_output(is_segment_finished=False, is_last_chunk=True)])
assert len(second.request_outputs) == 1
assert second.request_outputs[0].finished is True
assert "r" not in processor.request_states
def test_mm_only_nonfinal_audio_retains_state_without_segment_marker(monkeypatch):
processor = _make_mm_only_output_processor(monkeypatch)
first = processor.process_outputs([_audio_engine_output(is_segment_finished=False, is_last_chunk=False)])
assert "r" in processor.request_states
assert len(first.request_outputs) == 1
assert first.request_outputs[0].finished is False
second = processor.process_outputs([_audio_engine_output(is_segment_finished=False, is_last_chunk=True)])
assert len(second.request_outputs) == 1
assert second.request_outputs[0].finished is True
assert "r" not in processor.request_states
def test_mm_only_terminal_finish_removes_request_state(monkeypatch):
processor = _make_mm_only_output_processor(monkeypatch)
result = processor.process_outputs([_audio_engine_output(is_segment_finished=False, is_last_chunk=True)])
assert len(result.request_outputs) == 1
assert result.request_outputs[0].finished is True
assert "r" not in processor.request_states
def test_no_detokenizer_make_request_output_with_routed_experts():
"""make_request_output accepts the routed_experts arg that the multimodal
output channel (_process_mm_only_outputs) passes positionally, and attaches