[tts][bugfix] Fix CosyVoice3 sampling and stage handoff (#6424)

Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
This commit is contained in:
akshatvishu authored and GitHub committed 2026-08-22 21:20:57 -07:00
1 parent 0db262de2a
commit 39c16d75a9
11 files changed
+221 -68

No files matched your search

@@ -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 CosyVoice3 components."""
from types import SimpleNamespace
@@ -9,10 +9,41 @@ import torch
import torch.nn as nn
from tests.helpers.mark import hardware_test
from vllm_omni.model_executor.models.cosyvoice3.code2wav_core.hifigan import (
CausalHiFTGenerator,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@pytest.fixture
def causal_hift():
return CausalHiFTGenerator(
base_channels=32,
upsample_rates=[2, 2],
upsample_kernel_sizes=[4, 4],
source_resblock_kernel_sizes=[3, 3],
source_resblock_dilation_sizes=[[1, 3, 5], [1, 3, 5]],
)
def test_causal_hift_moves_stft_window_with_model(causal_hift):
assert causal_hift.get_buffer("stft_window") is causal_hift.stft_window
assert "stft_window" not in causal_hift.state_dict()
causal_hift.to(dtype=torch.float64)
assert causal_hift.stft_window.dtype == torch.float64
def test_causal_hift_stft_moves_window_to_input_device(causal_hift):
waveform = torch.empty((1, 64), device="meta")
real, imag = causal_hift._stft(waveform)
assert real.device == waveform.device
assert imag.device == waveform.device
assert causal_hift.stft_window.device == waveform.device
class TestPreLookaheadLayer:
"""Tests for PreLookaheadLayer."""
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
from vllm_omni.config.model import OmniModelArchConfigConvertor
from vllm_omni.transformers_utils.configs.cosyvoice3 import CosyVoice3Config
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_attention_geometry_matches_qwen_backbone() -> None:
config = CosyVoice3Config()
model_arch_config = OmniModelArchConfigConvertor(config, config.get_text_config()).convert()
assert model_arch_config.total_num_attention_heads == 14
assert model_arch_config.total_num_kv_heads == 2
assert model_arch_config.head_size == 64
@@ -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
from __future__ import annotations
@@ -401,6 +401,52 @@ def test_sample_tolerates_padded_rows_without_history():
assert out.sampled_token_ids.shape == (2, 1)
def test_sample_excludes_non_finite_logits():
model = _make_talker_model()
metadata = _make_sampling_metadata(output_token_ids=[[]])
metadata.temperature.fill_(0.5)
logits = torch.tensor([[float("nan"), 1.0, float("inf"), float("-inf")]], dtype=torch.bfloat16)
out = model.sample(logits, metadata)
assert out is not None
assert out.sampled_token_ids.tolist() == [[1]]
def test_sample_preserves_allowed_token_mask_with_invalid_logits():
model = _make_talker_model()
metadata = _make_sampling_metadata(output_token_ids=[[]])
metadata.allowed_token_ids_mask = torch.tensor([[True, False, True]])
logits = torch.tensor([[float("nan"), 1.0, float("inf")]], dtype=torch.float32)
out = model.sample(logits, metadata)
assert out is not None
assert out.sampled_token_ids.tolist() == [[1]]
def test_sample_rejects_rows_without_finite_logits():
model = _make_talker_model()
metadata = _make_sampling_metadata(output_token_ids=[[]])
metadata.allowed_token_ids_mask = torch.tensor([[True, False, True]])
logits = torch.tensor([[0.0, float("nan"), 0.0]], dtype=torch.float32)
with pytest.raises(ValueError, match="no finite logits"):
model.sample(logits, metadata)
def test_sample_keeps_only_finite_token_after_ras_rejection():
model = _make_talker_model()
metadata = _make_sampling_metadata(output_token_ids=[[1] * 10])
metadata.allowed_token_ids_mask = torch.tensor([[True, False, True]])
logits = torch.tensor([[0.0, 1.0, 0.0]], dtype=torch.float32)
out = model.sample(logits, metadata)
assert out is not None
assert out.sampled_token_ids.tolist() == [[1]]
def test_gpu_ar_model_runner_prefers_model_sampler_when_opted_in():
metadata = _make_sampling_metadata(output_token_ids=[[1, 2, 3]])
expected = SamplerOutput(
@@ -423,9 +469,14 @@ def test_gpu_ar_model_runner_prefers_model_sampler_when_opted_in():
_, GPUARModelRunner = _cosyvoice3_model_and_runner()
runner = object.__new__(GPUARModelRunner)
runner.input_batch = _DummyInputBatch()
def model_sample(logits, sampling_metadata):
calls.append(logits.clone())
return expected
runner.model = SimpleNamespace(
prefer_model_sampler=True,
sample=lambda logits, sampling_metadata: calls.append(logits.clone()) or expected,
sample=model_sample,
)
runner.sampler = lambda **_: (_ for _ in ()).throw(AssertionError("fallback sampler should not be used"))
@@ -459,12 +510,14 @@ def test_gpu_ar_model_runner_supplies_req_output_history_to_model_sampler():
_, GPUARModelRunner = _cosyvoice3_model_and_runner()
runner = object.__new__(GPUARModelRunner)
runner.input_batch = _DummyInputBatch()
def model_sample(logits, sampling_metadata):
seen_histories.append([list(x) for x in sampling_metadata.output_token_ids])
return SamplerOutput(sampled_token_ids=torch.tensor([[7]], dtype=torch.int32), logprobs_tensors=None)
runner.model = SimpleNamespace(
prefer_model_sampler=True,
sample=lambda logits, sampling_metadata: (
seen_histories.append([list(x) for x in sampling_metadata.output_token_ids])
or SamplerOutput(sampled_token_ids=torch.tensor([[7]], dtype=torch.int32), logprobs_tensors=None)
),
sample=model_sample,
)
runner.sampler = lambda **_: (_ for _ in ()).throw(AssertionError("fallback sampler should not be used"))
@@ -504,12 +557,14 @@ def test_gpu_ar_model_runner_repairs_async_placeholders_for_model_sampler():
_, GPUARModelRunner = _cosyvoice3_model_and_runner()
runner = object.__new__(GPUARModelRunner)
runner.input_batch = _DummyInputBatch()
def model_sample(logits, sampling_metadata):
seen_histories.append([list(x) for x in sampling_metadata.output_token_ids])
return SamplerOutput(sampled_token_ids=torch.tensor([[7]], dtype=torch.int32), logprobs_tensors=None)
runner.model = SimpleNamespace(
prefer_model_sampler=True,
sample=lambda logits, sampling_metadata: (
seen_histories.append([list(x) for x in sampling_metadata.output_token_ids])
or SamplerOutput(sampled_token_ids=torch.tensor([[7]], dtype=torch.int32), logprobs_tensors=None)
),
sample=model_sample,
)
runner.sampler = lambda **_: (_ for _ in ()).throw(AssertionError("fallback sampler should not be used"))