[Core][Model] Enable Qwen3-TTS MRV2 and optimize the TTS pipeline (#7781)
Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
103 files changed
+10232
-661
No files matched your search
@@ -0,0 +1,137 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
"""Regression tests for MRv2 Omni AR text detach and generation payload outputs."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from vllm.sampling_params import RequestOutputKind
|
||||
from vllm.v1.engine import FinishReason
|
||||
|
||||
from vllm_omni.engine import OmniEngineCoreOutput
|
||||
from vllm_omni.engine.output_modality import OutputModality
|
||||
from vllm_omni.engine.output_processor import (
|
||||
MultimodalOutputProcessor,
|
||||
OmniRequestState,
|
||||
)
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class _Detokenizer:
|
||||
def __init__(self):
|
||||
self.output_token_ids = []
|
||||
self.output_text = ""
|
||||
self.update_calls = []
|
||||
|
||||
def update(self, token_ids, stop):
|
||||
self.update_calls.append((list(token_ids), stop))
|
||||
self.output_token_ids.extend(token_ids)
|
||||
self.output_text += "X" * len(token_ids)
|
||||
return None
|
||||
|
||||
def get_next_output_text(self, finished, delta):
|
||||
return self.output_text
|
||||
|
||||
def num_output_tokens(self):
|
||||
return len(self.output_token_ids)
|
||||
|
||||
|
||||
def _make_processor(output_modality, detokenizer=None):
|
||||
state = OmniRequestState(
|
||||
request_id="r",
|
||||
external_req_id="r",
|
||||
parent_req=None,
|
||||
request_index=0,
|
||||
lora_request=None,
|
||||
output_kind=RequestOutputKind.FINAL_ONLY,
|
||||
prompt="prompt" if detokenizer else None,
|
||||
prompt_token_ids=[1] if detokenizer else [],
|
||||
prompt_embeds=None,
|
||||
logprobs_processor=MagicMock(logprobs=None, cumulative_logprob=None, prompt_logprobs=None)
|
||||
if detokenizer
|
||||
else None,
|
||||
detokenizer=detokenizer,
|
||||
max_tokens_param=None,
|
||||
arrival_time=0.0,
|
||||
queue=None,
|
||||
log_stats=False,
|
||||
stream_interval=1,
|
||||
)
|
||||
processor = MultimodalOutputProcessor(tokenizer=None, log_stats=False, output_modality=output_modality)
|
||||
processor.request_states["r"] = state
|
||||
processor.external_req_ids["r"].append("r")
|
||||
return processor, state
|
||||
|
||||
|
||||
def _rehydrated_output(*, pooling_output=None, finish_reason=FinishReason.STOP):
|
||||
"""Build the post-StagePool shape without violating vLLM's wire schema."""
|
||||
return SimpleNamespace(
|
||||
request_id="r",
|
||||
new_token_ids=[42],
|
||||
pooling_output=pooling_output,
|
||||
finish_reason=finish_reason,
|
||||
stop_reason=None,
|
||||
kv_transfer_params=None,
|
||||
ec_transfer_params=None,
|
||||
routed_experts=None,
|
||||
trace_headers=None,
|
||||
prefill_stats=None,
|
||||
spec_decode_metrics=None,
|
||||
new_sampling_mask=None,
|
||||
num_nans_in_logits=0,
|
||||
multimodal_output=None,
|
||||
is_segment_finished=False,
|
||||
is_non_final_audio_chunk=False,
|
||||
output_type=None,
|
||||
num_generation_tokens=None,
|
||||
finished=True,
|
||||
)
|
||||
|
||||
|
||||
def test_text_tokens_are_detokenized_when_mrv2_ar_output_has_pooling_payload():
|
||||
detokenizer = _Detokenizer()
|
||||
processor, state = _make_processor(OutputModality.LATENT, detokenizer)
|
||||
|
||||
# StagePool has already rehydrated the MRv2 carrier. Use a structural
|
||||
# output here because upstream EngineCoreOutput strictly validates
|
||||
# pooling_output as torch.Tensor | None on construction.
|
||||
output = _rehydrated_output(pooling_output={"hidden": torch.ones(1, 4)})
|
||||
|
||||
processed = processor.process_outputs([output])
|
||||
|
||||
assert detokenizer.update_calls == [([42], True)]
|
||||
completion = processed.request_outputs[0].outputs[0]
|
||||
assert list(completion.token_ids) == [42]
|
||||
assert completion.text == "X"
|
||||
assert not state.mm_accumulated.is_empty
|
||||
|
||||
|
||||
@pytest.mark.parametrize("streaming", [False, True])
|
||||
def test_generation_stage_mm_delivered_only_with_terminal(streaming):
|
||||
# Non-terminal chunks accumulate; the terminal output carries the audio.
|
||||
# When the first output is already terminal it is delivered directly.
|
||||
processor, _ = _make_processor(OutputModality.AUDIO)
|
||||
audio = torch.ones(1, 320)
|
||||
sr = torch.tensor(24000, dtype=torch.int32)
|
||||
mm_output = OmniEngineCoreOutput(
|
||||
request_id="r",
|
||||
new_token_ids=[],
|
||||
multimodal_output={"model_outputs": audio, "sr": sr},
|
||||
finish_reason=None if streaming else FinishReason.STOP,
|
||||
)
|
||||
|
||||
processed = processor.process_outputs([mm_output])
|
||||
if streaming:
|
||||
assert processed.request_outputs == []
|
||||
processed = processor.process_outputs(
|
||||
[OmniEngineCoreOutput(request_id="r", new_token_ids=[], finish_reason=FinishReason.STOP)]
|
||||
)
|
||||
|
||||
assert len(processed.request_outputs) == 1
|
||||
completion = processed.request_outputs[0].outputs[0]
|
||||
assert completion.text == ""
|
||||
assert torch.equal(completion.multimodal_output["audio"], audio)
|
||||
assert completion.multimodal_output["sr"].item() == 24000
|
||||
Reference in new issue
Block a user