[WIP][Bugfix][MiniCPM-o] Fix Full duplex bugs for minicpm-challenge (#5496)
Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
60 files changed
+3696
-634
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user