[Core][Model] Enable Qwen3-TTS MRV2 and optimize the TTS pipeline (#7781)

Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
Sy03 authored and GitHub committed 2026-09-20 23:03:08 -07:00
1 parent d139c600c7
commit a9e2117f5e
103 files changed
+10232 -661

No files matched your search

@@ -682,6 +682,7 @@ class TestCodePredictorGraphReplay:
return_proj_buf=False,
)
predictor._prefix_graphs_enabled = False
predictor._prefix_reprefill_enabled = False
predictor._bucket_pos_ids = {1: torch.arange(4).unsqueeze(0)}
graph = mocker.Mock()
graph_output = torch.zeros(1, 4, 4)
@@ -1362,3 +1363,38 @@ class TestCodePredictorFusedProjections:
with pytest.raises(RuntimeError, match="missing fused parameters"):
model.load_weights(weights)
class TestMTPExecutionBuckets:
"""Outer MRv2 MTP graph buckets must align with the predictor bucket set."""
def test_configure_mtp_execution_buckets(self, mocker: MockerFixture, loaded_target_classes) -> None:
_ = loaded_target_classes
common_mod = sys.modules["vllm_omni.model_executor.models.common.qwen3_code_predictor"]
mocker.patch.object(common_mod.current_omni_platform, "is_npu", return_value=False)
cp_config, _ = _make_tiny_config(loaded_target_classes)
vllm_config = _make_vllm_config(mocker, max_num_seqs=4)
wrapper = common_mod.CodePredictorWrapper(
vllm_config=vllm_config,
cp_config=cp_config,
wrapper_config=common_mod.CodePredictorWrapperConfig(use_cuda_graphs=False),
talker_hidden_size=cp_config.hidden_size,
)
# Default keeps the legacy power-of-two derivation.
assert wrapper._execution_batch_buckets is None
assert wrapper._batch_bucket_sizes() == [1, 2, 4]
wrapper.configure_mtp_execution_buckets([3, 1, 3, 0, 9])
assert wrapper._batch_bucket_sizes() == [1, 3, 4] # deduplicate/filter declaration and retain max
with pytest.raises(RuntimeError, match="before the first warmup"):
wrapper2 = common_mod.CodePredictorWrapper(
vllm_config=vllm_config,
cp_config=cp_config,
wrapper_config=common_mod.CodePredictorWrapperConfig(use_cuda_graphs=False),
talker_hidden_size=cp_config.hidden_size,
)
wrapper2._bucket_sizes = [1, 2, 4] # stand-in for warmed-up state
wrapper2.configure_mtp_execution_buckets([3])
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""
Tests for CUDA Graph decoder wrapper numerical equivalence.
@@ -142,6 +142,28 @@ def test_padded_output_shape_and_length(decoder, wrapper, seq_len):
assert graph_out.shape[-1] == expected_len
@hardware_test(res={"cuda": "L4"}, num_cards=1)
def test_padded_batch_size_uses_larger_capture_bucket(decoder):
"""Non-exact batch sizes should replay the next captured batch bucket."""
w = CUDAGraphDecoderWrapper(
decoder=decoder,
capture_sizes=[25],
capture_batch_sizes=[1, 4],
num_quantizers=NUM_QUANTIZERS,
enabled=True,
)
w.warmup(DEVICE)
codes = _random_codes(25, batch_size=3)
with torch.no_grad():
eager_out = decoder(codes)
graph_out = w.decode(codes)
assert (4, 25) in w.graphs
assert graph_out.shape == eager_out.shape
torch.testing.assert_close(graph_out, eager_out, atol=0, rtol=0)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@pytest.mark.parametrize("seq_len", [10, 30, 47, 73, 99])
def test_padded_interior_positions_close(decoder, wrapper, seq_len):
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Output metadata must not wait for the preceding model forward on CUDA."""
from types import SimpleNamespace
import pytest
import torch
from tests.helpers.mark import hardware_test
from vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_talker import (
Qwen3TTSTalkerForConditionalGeneration,
)
from vllm_omni.worker.gpu_ar_model_runner import _snapshot_tensor_payload_to_cpu_async
pytestmark = [pytest.mark.core_model]
@hardware_test(res={"cuda": "H100"}, num_cards=1)
@pytest.mark.parametrize("validity_device", ["cpu", "cuda"])
@torch.inference_mode()
def test_output_validity_preserves_values_without_waiting_for_forward(validity_device):
talker = Qwen3TTSTalkerForConditionalGeneration.__new__(Qwen3TTSTalkerForConditionalGeneration)
talker.vllm_config = SimpleNamespace(model_config=SimpleNamespace(async_chunk=True))
hidden = torch.zeros((3, 8), device="cuda")
infos = [
{
"codes": {"audio": torch.zeros((frames, 16), device="cuda", dtype=torch.long)},
"meta": {"codec_frame_valid": torch.tensor(valid, device=validity_device)},
}
for frames, valid in [(2, True), (1, False)]
]
# Warm allocation paths before checking the stream-ordering contract.
talker.make_omni_output(hidden, model_intermediate_buffer=infos)
copy_stream = torch.cuda.Stream()
torch.accelerator.synchronize()
preceding_forward = torch.cuda.Event()
torch.cuda._sleep(500_000_000)
preceding_forward.record()
output = talker.make_omni_output(hidden, model_intermediate_buffer=infos)
waited_for_forward = preceding_forward.query()
validity = output.multimodal_outputs["meta"]["codec_frame_valid"]
snapshot = _snapshot_tensor_payload_to_cpu_async(
output.multimodal_outputs, copy_stream=copy_stream, pin_memory=True
)
snapshot.wait()
assert not waited_for_forward, "output construction synchronized the preceding forward"
assert validity.device.type == "cuda"
assert validity.dtype == torch.int8
cpu_validity = snapshot.payload["meta"]["codec_frame_valid"]
assert cpu_validity.device.type == "cpu"
assert cpu_validity.tolist() == [1, 1, 0]
assert all(info["meta"]["codec_frame_valid"].device.type == validity_device for info in infos)
@@ -0,0 +1,100 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Exercise actual stateless decoder capture, not a mocked capture indicator."""
import pytest
import torch
from tests.helpers.mark import hardware_marks
from vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz.configuration_qwen3_tts_tokenizer_v2 import (
Qwen3TTSTokenizerV2DecoderConfig,
)
from vllm_omni.model_executor.models.qwen3_tts.tokenizer_12hz.modeling_qwen3_tts_tokenizer_v2 import (
Qwen3TTSTokenizerV2DecoderTransformerModel,
)
pytestmark = [pytest.mark.core_model, *hardware_marks(res={"cuda": "L4"}, num_cards=1)]
@pytest.mark.parametrize("batch_size", [1, 2])
@pytest.mark.parametrize("input_mode", ["implicit", "positions"])
@torch.inference_mode()
def test_stateless_decoder_capture_replays_new_inputs(batch_size, input_mode):
sequence_length = 8
torch.manual_seed(42)
config = Qwen3TTSTokenizerV2DecoderConfig(
hidden_size=32,
latent_dim=16,
max_position_embeddings=512,
num_attention_heads=4,
num_key_value_heads=4,
intermediate_size=64,
num_hidden_layers=1,
num_quantizers=2,
sliding_window=16,
decoder_dim=32,
upsample_rates=(2,),
upsampling_ratios=(2,),
)
config._attn_implementation = "sdpa"
model = Qwen3TTSTokenizerV2DecoderTransformerModel._from_config(config).cuda().eval()
inputs = torch.randn(batch_size, sequence_length, config.latent_dim, device="cuda")
positions = {}
if input_mode == "positions":
cache_position = torch.arange(sequence_length, device="cuda")
positions = {"cache_position": cache_position, "position_ids": cache_position.unsqueeze(0)}
stream = torch.cuda.Stream()
stream.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(stream):
for _ in range(3):
model(inputs_embeds=inputs, use_cache=False, **positions)
torch.cuda.current_stream().wait_stream(stream)
torch.accelerator.synchronize()
# No wrapper or eager fallback: an illegal capture must fail this test.
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
captured_output = model(inputs_embeds=inputs, use_cache=False, **positions).last_hidden_state
outputs = []
for _ in range(3):
new_inputs = torch.randn_like(inputs)
expected = model(inputs_embeds=new_inputs, use_cache=False, **positions).last_hidden_state
if input_mode == "implicit":
# Compare the cached mask against the independent live-mask path,
# not only against eager execution using the same cached mask.
reference = model(
inputs_embeds=new_inputs,
use_cache=False,
position_ids=torch.arange(sequence_length, device="cuda").unsqueeze(0),
).last_hidden_state
torch.testing.assert_close(expected, reference, atol=1e-5, rtol=1e-4)
inputs.copy_(new_inputs)
graph.replay()
torch.accelerator.synchronize()
torch.testing.assert_close(captured_output, expected, atol=1e-5, rtol=1e-4)
outputs.append(captured_output.clone())
assert not torch.equal(outputs[0], outputs[1]), "replay must consume the new input"
@pytest.mark.parametrize("batch_size", [1, 2])
@torch.inference_mode()
def test_stateless_codec_wrapper_captures_and_replays_waveform(batch_size):
from tests.model_executor.models.qwen3_tts.test_qwen3_tts_incremental_decode import _make_small_decoder
from vllm_omni.model_executor.models.qwen3_tts.segmented_graph_wrapper import CUDAGraphDecoderWrapper
torch.manual_seed(42)
decoder = _make_small_decoder().cuda().eval()
wrapper = CUDAGraphDecoderWrapper(decoder, num_quantizers=2, async_chunk=False)
# Exercise the production capture method, including quantization and
# waveform convolutions, without accepting warmup's eager fallback.
wrapper._capture_stateless(batch_size, 8, torch.device("cuda"), torch.long)
state = wrapper.stateless_states[(batch_size, 8)]
for _ in range(3):
codes = torch.randint(0, 32, (batch_size, 2, 8), device="cuda")
expected = decoder._forward_exact(codes)
state["input"]["codes"].copy_(codes)
state["graph"].replay()
torch.accelerator.synchronize()
torch.testing.assert_close(state["output"], expected, atol=1e-5, rtol=1e-4)
assert torch.isfinite(state["output"]).all()
@@ -1,5 +1,9 @@
from collections import OrderedDict
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from collections import OrderedDict, UserDict
from types import SimpleNamespace
from typing import Any
import numpy as np
import pytest
@@ -36,6 +40,7 @@ def _make_minimal_talker(
"""
model = Qwen3TTSTalkerForConditionalGeneration.__new__(Qwen3TTSTalkerForConditionalGeneration)
model.talker_config = SimpleNamespace(codec_pad_id=7, num_code_groups=16)
model._codebook_vocab_size = 2048
model._embedding_dtype = torch.bfloat16
if tts_pad_embed is None:
tts_pad_embed = torch.zeros((1, 4), dtype=torch.bfloat16)
@@ -51,6 +56,22 @@ def _make_minimal_talker(
return model
def test_postprocess_batch_gathers_each_request_tail():
from vllm_omni.model_executor.models.output_templates import OwnedBatchTensor
model = _make_minimal_talker()
hidden = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]])
key, values = model.postprocess_batch_mrv2(
hidden_states=hidden,
last_token_indices=torch.tensor([1, 3]),
)
assert key == ("hidden_states", "last")
assert isinstance(values, OwnedBatchTensor) # freshly allocated rows transfer ownership
assert torch.equal(values.tensor, torch.tensor([[3.0, 4.0], [7.0, 8.0]]))
def _make_minimal_builder(
*,
config: SimpleNamespace | None = None,
@@ -95,6 +116,7 @@ def _make_minimal_builder(
)
builder._speaker_cache = None
builder._text_tokenizer = None
builder._projected_token_cache = {}
builder._embedding_dtype = torch.bfloat16
builder._ref_audio_artifact_cache_max_entries = 256
builder._ref_audio_artifact_cache = OrderedDict()
@@ -259,6 +281,30 @@ def test_decode_compacts_long_trailing_text_after_large_offset():
assert torch.equal(update["hidden_states"]["trailing_text"], trailing_text[65:])
def test_decode_marks_codec_frame_validity_from_processed_input_token():
model = _make_minimal_talker()
model.embed_input_ids = lambda input_ids: input_ids.to(torch.float32).reshape(1, 1, 1).expand(1, 1, 4)
common = {
"input_embeds": None,
"text": ["hello"],
"task_type": ["CustomVoice"],
"hidden_states": {
"trailing_text": torch.ones((2, 4)),
"last": torch.ones(4),
},
"meta": {"talker_text_offset": 0},
"_omni_is_prefill": False,
"_omni_num_computed_tokens": 2,
"_omni_prompt_len": 2,
}
_, _, valid_update = model.preprocess(input_ids=torch.tensor([123]), **common)
_, _, eos_update = model.preprocess(input_ids=torch.tensor([4198]), **common)
assert valid_update["meta"]["codec_frame_valid"].item() is True
assert eos_update["meta"]["codec_frame_valid"].item() is False
def test_decode_replay_span_embeds_all_tokens_without_mutating_decode_state():
model = _make_minimal_talker()
@@ -286,14 +332,17 @@ def test_decode_replay_span_embeds_all_tokens_without_mutating_decode_state():
out_embeds.cpu(),
torch.tensor([[101.0] * 4, [202.0] * 4, [303.0] * 4], dtype=torch.bfloat16),
)
assert update == {"meta": {"codec_streaming": True}}
assert update["meta"]["codec_streaming"] is True
assert update["meta"]["codec_frame_valid"].item() is False
def test_decode_batch_preprocess_matches_decode_state_updates():
@pytest.mark.parametrize("mrv2", [False, True])
def test_decode_batch_preprocess_matches_decode_state_updates(mrv2):
tts_pad = torch.full((1, 4), -1.0, dtype=torch.bfloat16)
model = _make_minimal_talker(tts_pad_embed=tts_pad)
def fake_embed_input_ids(input_ids):
assert not mrv2, "MRV2 must reuse the prepared token embeddings"
return input_ids.to(torch.float32).reshape(-1, 1, 1).expand(-1, 1, 4)
model.embed_input_ids = fake_embed_input_ids
@@ -302,7 +351,10 @@ def test_decode_batch_preprocess_matches_decode_state_updates():
last_a = torch.full((4,), 2.0, dtype=torch.float32)
last_b = torch.full((4,), 3.0, dtype=torch.float32)
out_ids, out_embeds, past_hidden, text_step, updates = model.preprocess_decode_batch(
preprocess = model.preprocess_decode_batch_mrv2 if mrv2 else model.preprocess_decode_batch
prepared = torch.tensor([[101.0] * 4, [202.0] * 4], dtype=torch.bfloat16)
out_ids, out_embeds, past_hidden, text_step, updates = preprocess(
**({"input_embeds": prepared} if mrv2 else {}),
input_ids=torch.tensor([101, 202], dtype=torch.long),
req_infos=[
{
@@ -321,15 +373,19 @@ def test_decode_batch_preprocess_matches_decode_state_updates():
)
assert out_ids.tolist() == [101, 202]
if mrv2:
assert out_embeds.data_ptr() == prepared.data_ptr()
assert torch.equal(out_embeds.cpu(), torch.tensor([[101.0] * 4, [202.0] * 4], dtype=torch.bfloat16))
assert torch.equal(past_hidden.cpu(), torch.stack([last_a, last_b]).to(torch.bfloat16))
assert torch.equal(text_step[0].cpu(), trailing_a[1].to(torch.bfloat16))
assert torch.equal(text_step[1].cpu(), tts_pad.reshape(-1).to(torch.bfloat16))
assert updates[0]["meta"]["talker_text_offset"] == 2
assert updates[0]["meta"]["codec_streaming"] is True
assert updates[0]["meta"]["codec_frame_valid"].item() is True
assert "hidden_states" not in updates[0]
assert updates[1]["meta"]["talker_text_offset"] == 0
assert updates[1]["meta"]["codec_streaming"] is False
assert updates[1]["meta"]["codec_frame_valid"].item() is True
assert updates[1]["hidden_states"]["trailing_text"].numel() == 0
@@ -449,11 +505,11 @@ def test_base_voice_clone_batch_preprocess_encodes_ref_code_by_sample_rate():
def __call__(self, texts, *, padding=False):
self.calls.append((texts, padding))
return {"input_ids": [[idx + 1, idx + 2, idx + 3] for idx, _ in enumerate(texts)]}
return UserDict({"input_ids": [[1, 2, 3], [2, 3, 4, 5]]})
text_tok = FakeTextTokenizer()
builder._text_tokenizer = text_tok
buf = {
buf: dict[str, dict[str, Any]] = {
"r1": {
"task_type": ["Base"],
"text": ["one"],
@@ -488,9 +544,17 @@ def test_base_voice_clone_batch_preprocess_encodes_ref_code_by_sample_rate():
assert buf["r2"][NORMALIZED_REF_AUDIO_KEY][0] is wav2
assert len(text_tok.calls) == 2
assert torch.equal(buf["r1"][PRECOMPUTED_TEXT_IDS_KEY], torch.tensor([1, 2, 3]))
assert torch.equal(buf["r2"][PRECOMPUTED_TEXT_IDS_KEY], torch.tensor([2, 3, 4]))
assert torch.equal(buf["r2"][PRECOMPUTED_TEXT_IDS_KEY], torch.tensor([2, 3, 4, 5]))
assert torch.equal(buf["r1"][PRECOMPUTED_REF_IDS_KEY], torch.tensor([1, 2, 3]))
assert torch.equal(buf["r2"][PRECOMPUTED_REF_IDS_KEY], torch.tensor([2, 3, 4]))
assert torch.equal(buf["r2"][PRECOMPUTED_REF_IDS_KEY], torch.tensor([2, 3, 4, 5]))
assert (
buf["r1"][PRECOMPUTED_TEXT_IDS_KEY].untyped_storage().data_ptr()
== buf["r2"][PRECOMPUTED_TEXT_IDS_KEY].untyped_storage().data_ptr()
)
assert (
buf["r1"][PRECOMPUTED_REF_IDS_KEY].untyped_storage().data_ptr()
== buf["r2"][PRECOMPUTED_REF_IDS_KEY].untyped_storage().data_ptr()
)
def test_base_voice_clone_batch_preprocess_reuses_singleton_normalized_audio_without_speech_tokenizer():
@@ -506,7 +570,7 @@ def test_base_voice_clone_batch_preprocess_reuses_singleton_normalized_audio_wit
return {"input_ids": [[7, 8, 9] for _ in texts]}
builder._text_tokenizer = FakeTextTokenizer()
buf = {
buf: dict[str, dict[str, Any]] = {
"r1": {
"task_type": ["Base"],
"text": ["one"],
@@ -539,7 +603,7 @@ def test_base_voice_clone_batch_preprocess_skips_after_initial_prefill_state_exi
builder._encode_ref_audio_batch_fn = lambda *a, **kw: (_ for _ in ()).throw(
AssertionError("speech tokenizer not expected")
)
buf = {
buf: dict[str, dict[str, Any]] = {
"r1": {
"task_type": ["Base"],
"text": ["one"],
@@ -574,7 +638,7 @@ def test_base_voice_clone_batch_preprocess_uses_serving_artifact_cache_key_witho
return {"input_ids": [[7, 8, 9] for _ in texts]}
builder._text_tokenizer = FakeTextTokenizer()
buf = {
buf: dict[str, dict[str, Any]] = {
"r1": {
"task_type": ["Base"],
"text": ["one"],
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Regression tests for cross-request ``codes.ref`` leakage (#4370).
``make_omni_output`` used to collapse every request's reference codec
@@ -11,11 +11,14 @@ vocoder chunks then decoded with another request's reference voice as
context (audible onset timbre deformation at any concurrency > 1).
"""
from types import SimpleNamespace
import pytest
import torch
from vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_talker import (
Qwen3TTSTalkerForConditionalGeneration,
_qwen3_tts_gpu_resident_buffer_keys,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -23,6 +26,18 @@ pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
_NUM_CODE_GROUPS = 16
def test_large_tts_prefill_artifacts_stay_gpu_resident_only_on_mrv2() -> None:
v1_keys = _qwen3_tts_gpu_resident_buffer_keys(False)
v2_keys = _qwen3_tts_gpu_resident_buffer_keys(True)
assert ("embed", "prefill") not in v1_keys
assert ("codes", "ref") not in v1_keys
assert ("meta", "codec_frame_valid") not in v1_keys
assert ("embed", "prefill") in v2_keys
assert ("codes", "ref") in v2_keys
assert ("meta", "codec_frame_valid") in v2_keys
def _make_talker() -> Qwen3TTSTalkerForConditionalGeneration:
# make_omni_output reads no instance state, so skip __init__ (no real
# config / checkpoint needed), same as the preprocess tests.
@@ -89,3 +104,42 @@ def test_make_omni_output_keeps_hidden_states_for_replay_spans_without_audio_cod
assert torch.equal(out.text_hidden_states, hidden)
assert out.multimodal_outputs["codes"]["audio"].shape == (1, _NUM_CODE_GROUPS)
def test_make_omni_output_preserves_per_request_codec_frame_validity() -> None:
talker = _make_talker()
talker.vllm_config = SimpleNamespace(model_config=SimpleNamespace(async_chunk=True))
valid = _info(1, None)
invalid = _info(1, None)
valid["meta"]["codec_frame_valid"] = True
invalid["meta"]["codec_frame_valid"] = False
out = talker.make_omni_output(
torch.zeros((2, 8)),
model_intermediate_buffer=[valid, invalid],
)
assert out.multimodal_outputs["meta"]["codec_frame_valid"].tolist() == [1, 0]
def test_make_omni_output_publishes_ref_once_for_kv_resumed_first_decode() -> None:
talker = _make_talker()
talker.vllm_config = SimpleNamespace(
model_config=SimpleNamespace(async_chunk=True),
)
ref_code = torch.ones((5, _NUM_CODE_GROUPS), dtype=torch.long)
info = _info(1, ref_code)
info["_omni_num_computed_tokens"] = 9
info["_omni_prompt_len"] = 5
first = talker.make_omni_output(
torch.zeros((1, 8)),
model_intermediate_buffer=[info],
)
second = talker.make_omni_output(
torch.zeros((1, 8)),
model_intermediate_buffer=[info],
)
assert first.multimodal_outputs["codes"]["ref"][0] is ref_code
assert "ref" not in second.multimodal_outputs["codes"]
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Unit tests for the Qwen3-TTS leading-silence ban (#4966, #5048).
``compute_logits`` suppresses the checkpoint-derived silence codec vocabulary
@@ -59,6 +59,7 @@ def _make_talker(*, ban_frames: int, logits: torch.Tensor, modes=None):
object.__setattr__(talker, "_silence_mask", silence_mask)
object.__setattr__(talker, "_codec_disallowed_mask", torch.zeros((_VOCAB,), dtype=torch.bool))
object.__setattr__(talker, "_batch_req_ids", req_ids)
object.__setattr__(talker, "_mrv2_silence_ban_mask", None)
object.__setattr__(
talker,
"_req_x_vector_only",
@@ -78,7 +79,26 @@ def _banned(row: torch.Tensor) -> set[int]:
return {int(i) for i in torch.nonzero(torch.isinf(row) & (row < 0)).flatten()}
# -------------------- the step gate --------------------
def test_mrv2_silence_ban_tracks_batch_order_scope_and_decode_window() -> None:
talker = _make_talker(ban_frames=3, logits=torch.zeros((3, _VOCAB)), modes=[True, False, True])
with talker.mrv2_sampling_context(
req_ids=["req2", "req1", "req0"],
num_output_tokens=torch.tensor([3, 0, 1]),
):
logits = talker.compute_logits(torch.zeros((3, 4)))
assert _banned(logits[0]) == set()
assert _banned(logits[1]) == set()
assert _banned(logits[2]) == set(_SILENCE_IDS)
# Prompt logprobs are computed after sampling and must remain unmasked.
assert not torch.isneginf(talker.compute_logits(torch.zeros((3, 4)))).any()
with talker.mrv2_sampling_context(
req_ids=["req2", "req1", "req0"],
num_output_tokens=torch.tensor([4, 1, 3]),
):
assert not torch.isneginf(talker.compute_logits(torch.zeros((3, 4)))).any()
def test_ban_disabled_leaves_logits_unchanged() -> None:
@@ -7,6 +7,9 @@ from types import SimpleNamespace
import pytest
import torch
from vllm_omni.model_executor.stage_input_processors import (
qwen3_tts as qwen3_tts_processors,
)
from vllm_omni.model_executor.stage_input_processors.chunk_size_utils import (
AdaptiveChunkController,
compute_adaptive_emit,
@@ -32,7 +35,52 @@ _FRAME = [1, 2, 3, 4]
_Q = len(_FRAME)
def _req(rid, *, finished, initial_codec_chunk_frames=None, non_streaming_mode=None, full_utterance_decode=None):
@pytest.mark.parametrize("frames", [1, 26])
def test_terminal_without_new_frames_does_not_replay_last_chunk(frames):
tm = _tm(initial_chunk_frames=1)
emitted = 0
for count in range(1, frames + 1):
payload = _call(tm, "r", n_frames=count)
if payload is not None:
emitted += payload.codes.audio.numel() // _Q
tm.put_req_chunk["r"] += 1
terminal = _call(tm, "r", n_frames=frames, finished=True)
assert terminal is not None and terminal.meta.finished.item()
assert terminal.codes.audio.numel() == 0
assert emitted == frames
def test_repeated_nonterminal_callback_does_not_republish_boundary():
tm = _tm(initial_chunk_frames=1)
first = _call(tm, "r", n_frames=1)
assert first is not None
tm.put_req_chunk["r"] += 1
assert _call(tm, "r", n_frames=1) is None
next_chunk = _call(tm, "r", n_frames=26)
assert next_chunk.codes.audio.numel() // _Q == 25
def test_terminal_flush_contains_only_frames_not_previously_emitted():
tm = _tm(initial_chunk_frames=1)
_call(tm, "r", n_frames=1)
tm.put_req_chunk["r"] += 1
_call(tm, "r", n_frames=26)
tm.put_req_chunk["r"] += 1
final = _call(tm, "r", n_frames=30, finished=True)
assert final.codes.audio.numel() // _Q == 4
tm.put_req_chunk["r"] += 1
assert _call(tm, "r", n_frames=30, finished=True).codes.audio.numel() == 0
def _req(
rid,
*,
finished,
initial_codec_chunk_frames=None,
non_streaming_mode=None,
full_utterance_decode=None,
output_token_ids=None,
):
entries = {}
if initial_codec_chunk_frames is not None:
entries["initial_codec_chunk_frames"] = SimpleNamespace(list_data=[initial_codec_chunk_frames])
@@ -40,11 +88,11 @@ def _req(rid, *, finished, initial_codec_chunk_frames=None, non_streaming_mode=N
entries["non_streaming_mode"] = SimpleNamespace(list_data=[non_streaming_mode])
if full_utterance_decode is not None:
entries["full_utterance_decode"] = SimpleNamespace(list_data=[full_utterance_decode])
ai = SimpleNamespace(entries=entries) if entries else None
return SimpleNamespace(
external_req_id=rid,
is_finished=lambda: finished,
additional_information=ai,
additional_information=SimpleNamespace(entries=entries) if entries else None,
output_token_ids=output_token_ids or [],
)
@@ -206,6 +254,112 @@ def test_voicedesign_prompt_mode_with_streaming_keeps_windowed_emit():
assert len(p.codes.audio) == _Q * 25
def test_async_chunk_skips_stop_token_zero_frame_without_gpu_value_check():
tm = _tm()
rid = "r-stop"
tm.code_prompt_token_ids[rid] = [_FRAME[:]]
payload = talker2code2wav_async_chunk(
transfer_manager=tm,
multimodal_output={"codes": {"audio": torch.zeros((1, _Q), dtype=torch.long)}},
request=_req(rid, finished=True, initial_codec_chunk_frames=2, output_token_ids=[2150]),
is_finished=True,
)
assert payload is not None
assert payload.codes.audio.tolist() == _FRAME
assert len(tm.code_prompt_token_ids[rid]) == 1
def test_async_chunk_uses_authoritative_frame_validity_without_token_history():
tm = _tm()
rid = "r-native-validity"
payload = talker2code2wav_async_chunk(
transfer_manager=tm,
multimodal_output={
"codes": {"audio": torch.tensor([[1, 2, 3, 4]])},
"meta": {"codec_frame_valid": False},
},
request=_req(rid, finished=False, initial_codec_chunk_frames=1, output_token_ids=[]),
is_finished=False,
)
assert payload is None
assert tm.code_prompt_token_ids[rid] == []
def test_async_chunk_keeps_final_real_frame_when_eos_was_just_sampled():
tm = _tm()
rid = "r-final-real-frame"
payload = talker2code2wav_async_chunk(
transfer_manager=tm,
multimodal_output={
"codes": {"audio": torch.tensor([[1, 2, 3, 4]])},
"meta": {"codec_frame_valid": True},
},
request=_req(rid, finished=True, initial_codec_chunk_frames=1, output_token_ids=[4198]),
is_finished=True,
)
assert payload is not None
assert payload.codes.audio.tolist() == [1, 2, 3, 4]
def test_async_chunk_batch_matches_scalar_payloads_and_state():
batch_builder = getattr(qwen3_tts_processors, "talker2code2wav_async_chunk_batch", None)
assert callable(batch_builder), "Qwen3-TTS must provide the native MRv2 batch payload builder"
scalar_tm = _tm(chunk_frames=4, left_context=3, initial_chunk_frames=1)
batch_tm = _tm(chunk_frames=4, left_context=3, initial_chunk_frames=1)
requests = [
_req("r-batch-0", finished=False, output_token_ids=[7]),
_req("r-batch-1", finished=False, output_token_ids=[8]),
]
ref_code = torch.tensor([[9, 9, 9, 9], [8, 8, 8, 8]], dtype=torch.long)
# Request 2 has a multi-token span: the last validity flag wins the chunk.
outputs = [
{"codes": {"audio": torch.tensor([[1, 2, 3, 4]]), "ref": ref_code}},
{
"codes": {"audio": torch.tensor([[9, 9, 9, 9], [5, 6, 7, 8]])},
"meta": {"codec_frame_valid": torch.tensor([False, True])},
},
]
scalar_payloads = [
talker2code2wav_async_chunk(
transfer_manager=scalar_tm,
multimodal_output=output,
request=request,
is_finished=False,
)
for request, output in zip(requests, outputs)
]
batch_payloads = batch_builder(
transfer_manager=batch_tm,
pooling_outputs=outputs,
requests=requests,
is_finished=[False, False],
)
assert len(batch_payloads) == len(scalar_payloads)
for scalar, batched in zip(scalar_payloads, batch_payloads):
assert scalar is not None and batched is not None
torch.testing.assert_close(batched.codes.audio, scalar.codes.audio)
assert batched.meta.left_context_size == scalar.meta.left_context_size
assert batched.meta.finished.item() == scalar.meta.finished.item()
assert batched.speaker == scalar.speaker
assert batched.language == scalar.language
for request in requests:
rid = request.external_req_id
torch.testing.assert_close(
torch.stack(batch_tm.code_prompt_token_ids[rid]),
torch.stack(scalar_tm.code_prompt_token_ids[rid]),
)
torch.testing.assert_close(batch_tm.request_payload["r-batch-0"], scalar_tm.request_payload["r-batch-0"])
_CASES = [
# ── IC boundary rule ──────────────────────────────────────────────
# initial_codec_chunk_frames only controls the first emitted chunk.
@@ -703,35 +857,27 @@ class TestRampHelpers:
def test_parse_ramp_mixed_list_returns_none(self):
assert parse_chunk_ramp({"codec_chunk_ramp": [4, "x"]}) is None
def test_parse_ramp_warns_on_tail_mismatch(self, caplog):
import logging
def test_parse_ramp_warns_on_tail_mismatch(self, monkeypatch):
warnings = []
monkeypatch.setattr(
"vllm_omni.model_executor.stage_input_processors.chunk_size_utils.logger.warning",
lambda message, *args: warnings.append(message % args),
)
target_logger = logging.getLogger("vllm_omni.model_executor.stage_input_processors.chunk_size_utils")
target_logger.addHandler(caplog.handler)
prev_level = target_logger.level
target_logger.setLevel(logging.WARNING)
try:
result = parse_chunk_ramp({"codec_chunk_ramp": [4, 4, 8]}, steady=25)
finally:
target_logger.removeHandler(caplog.handler)
target_logger.setLevel(prev_level)
result = parse_chunk_ramp({"codec_chunk_ramp": [4, 4, 8]}, steady=25)
assert result == [4, 4, 8]
assert "reintroduces" in caplog.text
assert any("reintroduces" in message for message in warnings)
def test_parse_ramp_no_warn_on_tail_match(self, caplog):
import logging
def test_parse_ramp_no_warn_on_tail_match(self, monkeypatch):
warnings = []
monkeypatch.setattr(
"vllm_omni.model_executor.stage_input_processors.chunk_size_utils.logger.warning",
lambda message, *args: warnings.append(message % args),
)
target_logger = logging.getLogger("vllm_omni.model_executor.stage_input_processors.chunk_size_utils")
target_logger.addHandler(caplog.handler)
prev_level = target_logger.level
target_logger.setLevel(logging.WARNING)
try:
result = parse_chunk_ramp({"codec_chunk_ramp": [4, 4, 8, 16, 25]}, steady=25)
finally:
target_logger.removeHandler(caplog.handler)
target_logger.setLevel(prev_level)
result = parse_chunk_ramp({"codec_chunk_ramp": [4, 4, 8, 16, 25]}, steady=25)
assert result == [4, 4, 8, 16, 25]
assert "reintroduces" not in caplog.text
assert warnings == []
@pytest.mark.parametrize(
"index,ramp,steady,expected",
@@ -1028,6 +1174,7 @@ class TestChunkRampEmission:
tm.put_req_chunk[rid] = 2
tm.code_prompt_token_ids[rid] = []
tm._qwen3_tts_emitted_frames.pop(rid, None)
tm.ramp_chunk_count.pop(rid, None)
p_seg2_0 = self._emit(tm, rid, 4)
@@ -1051,6 +1198,7 @@ class TestChunkRampEmission:
tm.put_req_chunk[rid] = 1
tm.code_prompt_token_ids[rid] = []
tm._qwen3_tts_emitted_frames.pop(rid, None)
tm.ramp_chunk_count.pop(rid, None)
p_seg2_0 = self._emit(tm, rid, 4)
@@ -1429,6 +1577,7 @@ class TestAdaptiveEmission:
tm.put_req_chunk[rid] = 1
tm.code_prompt_token_ids[rid] = []
tm._qwen3_tts_emitted_frames.pop(rid, None)
tm.ramp_chunk_count.pop(rid, None)
tm._adaptive_states.pop(rid, None)