[Model] Accelerate Breeze-TTS-2 non-streaming inference (#7752)

This commit is contained in:
Yueqian Lin authored and GitHub committed 2026-09-22 17:10:15 -07:00
1 parent d2aa07159c
commit 3f473a358b
84 files changed
+8191 -5622

No files matched your search

Whitespace-only changes.
@@ -1,128 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import numpy as np
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.audio_tokenizer import (
BreezeReferenceAudioTokenizer,
resolve_audio_tokenizer_path,
)
def _patch_hf_api(monkeypatch, snapshot_download):
"""Point the resolver's tagged HF helper at a fake API object."""
from vllm.transformers_utils import repo_utils
monkeypatch.setattr(repo_utils, "hf_api", lambda: SimpleNamespace(snapshot_download=snapshot_download))
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _Tokenizer:
def encode(self, audio, sr=None):
assert isinstance(audio, np.ndarray)
assert sr == 16000
return {"audio_codes": [torch.arange(32).reshape(16, 2)]}
def test_reference_audio_tokenizer_normalizes_codebook_major_output():
adapter = BreezeReferenceAudioTokenizer(_Tokenizer())
codes = adapter.encode(np.zeros(32, dtype=np.float32), 16000)
assert tuple(codes.shape) == (2, 16)
assert codes.dtype == torch.int16
assert codes.device.type == "cpu"
def test_reference_audio_tokenizer_rejects_waveform_without_sample_rate():
adapter = BreezeReferenceAudioTokenizer(_Tokenizer())
try:
adapter.encode(np.zeros(32, dtype=np.float32))
except ValueError as exc:
assert "sample_rate is required" in str(exc)
else:
raise AssertionError("missing sample rate should be rejected")
def test_resolve_prefers_local_directory_without_hub_access(tmp_path, monkeypatch):
bundled = tmp_path / "audio_tokenizer"
bundled.mkdir()
def _no_hub(*_args, **_kwargs):
raise AssertionError("local directories must not touch the hub")
_patch_hf_api(monkeypatch, _no_hub)
assert resolve_audio_tokenizer_path(str(tmp_path)) == bundled
def test_resolve_local_directory_without_bundled_tokenizer_returns_none(tmp_path, monkeypatch):
def _no_hub(*_args, **_kwargs):
raise AssertionError("local directories must not touch the hub")
_patch_hf_api(monkeypatch, _no_hub)
assert resolve_audio_tokenizer_path(str(tmp_path)) is None
def test_resolve_repo_id_uses_hf_snapshot(tmp_path, monkeypatch):
snapshot_root = tmp_path / "snapshot"
(snapshot_root / "audio_tokenizer").mkdir(parents=True)
calls = []
def _fake_snapshot_download(repo_id, **kwargs):
calls.append((repo_id, kwargs))
if kwargs.get("local_files_only"):
raise FileNotFoundError("cold cache")
return str(snapshot_root)
_patch_hf_api(monkeypatch, _fake_snapshot_download)
resolved = resolve_audio_tokenizer_path("BreezeBlue/Breeze-TTS-2")
assert resolved == snapshot_root / "audio_tokenizer"
# The cold-cache probe runs first; the online fetch carries the pattern.
assert calls[0] == (
"BreezeBlue/Breeze-TTS-2",
{"allow_patterns": ["audio_tokenizer/*"], "local_files_only": True},
)
assert calls[1] == ("BreezeBlue/Breeze-TTS-2", {"allow_patterns": ["audio_tokenizer/*"]})
def test_resolve_repo_id_prefers_complete_cached_snapshot(tmp_path, monkeypatch):
snapshot_root = tmp_path / "snapshot"
(snapshot_root / "audio_tokenizer").mkdir(parents=True)
calls = []
def _fake_snapshot_download(repo_id, **kwargs):
calls.append((repo_id, kwargs))
assert kwargs.get("local_files_only"), "cached snapshot must not hit the network"
return str(snapshot_root)
_patch_hf_api(monkeypatch, _fake_snapshot_download)
resolved = resolve_audio_tokenizer_path("BreezeBlue/Breeze-TTS-2")
assert resolved == snapshot_root / "audio_tokenizer"
assert len(calls) == 1
def test_resolve_repo_id_without_bundled_tokenizer_returns_none(tmp_path, monkeypatch):
snapshot_root = tmp_path / "snapshot"
snapshot_root.mkdir()
def _fake_snapshot_download(repo_id, **kwargs):
if kwargs.get("local_files_only"):
raise FileNotFoundError("cold cache")
return str(snapshot_root)
_patch_hf_api(monkeypatch, _fake_snapshot_download)
assert resolve_audio_tokenizer_path("BreezeBlue/Breeze-TTS-2") is None
@@ -1,95 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from typing import Any
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_talker import (
BreezeTTS2TalkerForGeneration,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _RequestHead:
def __call__(self, hidden: torch.Tensor) -> torch.Tensor:
# Hidden row 0 selects codec id 2; row 1 selects codec id 3.
code0 = 2 + int(hidden.reshape(-1)[0].item())
logits = torch.zeros((hidden.shape[0], 9), dtype=hidden.dtype)
logits[:, code0] = 1.0
return logits
def _model() -> BreezeTTS2TalkerForGeneration:
model = object.__new__(BreezeTTS2TalkerForGeneration)
model.num_codebooks = 4
model.codebook_vocab_size = 8
model.codebook_size = 8
model.codebook_eos_token_id = 0
model.audio_token_id = 100
model.audio_eos_token_id = 101
model.lm_head = _RequestHead()
model.logits_processor = lambda head, hidden: head(hidden)
model._codec_disallowed_mask = None
model._batch_state = None
model._batch_state_spans = None
model._async_chunk = True
model._golden_dump_dir = None
model._generate_depth_codes = lambda _hidden, code0: _frame(code0).unsqueeze(0)
return model
def _frame(code0: int) -> torch.Tensor:
return torch.tensor([code0, code0 + 1, code0 + 2, code0 + 3], dtype=torch.long)
def _step(model: BreezeTTS2TalkerForGeneration, infos: list[dict[str, Any]], hidden: torch.Tensor):
return model.make_omni_output(
hidden,
model_intermediate_buffer=infos,
request_token_spans=[(0, 1), (1, 2)],
)
def test_two_requests_accumulate_and_emit_independent_frames():
model = _model()
infos = [
{
"breeze_generated_frames": 1,
"breeze_max_new_frames": 2,
"breeze_audio_codes": _frame(2).reshape(1, 4),
},
{"breeze_generated_frames": 0, "breeze_max_new_frames": 3},
]
hidden = torch.tensor([[0.0], [1.0]], dtype=torch.float32)
first = _step(model, infos, hidden)
assert [tuple(item.shape) for item in first.multimodal_outputs["codes"]["audio"]] == [(1, 4), (1, 4)]
assert torch.equal(first.multimodal_outputs["codes"]["audio"][0][0], _frame(2))
assert torch.equal(first.multimodal_outputs["codes"]["audio"][1][0], _frame(3))
second = _step(model, infos, hidden)
# Request 0 reaches its two-frame budget and emits only a finish marker;
# request 1 still has budget and emits its next tail frame.
assert second.multimodal_outputs["codes"]["audio"][0].numel() == 0
assert torch.equal(second.multimodal_outputs["codes"]["audio"][1][0], _frame(3))
# Async streaming only maintains the per-frame tail; the cumulative
# buffer is deliberately not grown (request 0 keeps its pre-seeded
# entry untouched, request 1 never gains one).
assert infos[0]["breeze_audio_codes"].shape == (1, 4)
assert "breeze_audio_codes" not in infos[1]
assert infos[0]["breeze_force_eos"] is True
assert infos[1]["breeze_force_eos"] is False
def test_finished_requests_only_clear_model_owned_batch_mapping():
model = _model()
model._batch_state = [{"id": "a"}, {"id": "b"}]
model._batch_state_spans = [(0, 1), (1, 2)]
model.on_requests_finished(["a", "b"])
assert model._batch_state is None
assert model._batch_state_spans is None
@@ -1,68 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from typing import Any
import pytest
import torch
from torch import nn
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_codec import (
BreezeTTS2MimiCodec,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _BundledTokenizer:
def __init__(self) -> None:
self.model = nn.Linear(2, 2)
def named_parameters(self):
for name, parameter in self.model.named_parameters():
yield f"model.{name}", parameter
def decode(self, _encoded: Any) -> tuple[list[torch.Tensor], int]:
return [torch.zeros(4)], 24_000
def test_bundled_qwen_tokenizer_consumes_unused_root_fallback_weights(tmp_path):
bundled = tmp_path / "audio_tokenizer"
bundled.mkdir()
codec = object.__new__(BreezeTTS2MimiCodec)
codec.model_path = str(tmp_path)
codec.vllm_config = None
# load_weights reuses the path resolved once in __init__.
codec._tokenizer_path = bundled
codec._codec = None
codec._audio_tokenizer = _BundledTokenizer()
codec._loaded_local_weights = False
weights = [
("backbone_model.layers.0.norm.weight", torch.ones(2)),
("codec_model.decoder.layers.0.conv.weight", torch.ones(2)),
("lm_head.weight", torch.ones(2, 2)),
]
loaded = codec.load_weights(iter(weights))
assert "_audio_tokenizer.model.weight" in loaded
assert "_audio_tokenizer.model.bias" in loaded
# The bundled tokenizer was preloaded, so load_weights must not try to copy
# the root Mimi fallback into the nonexistent ``self._codec`` module.
assert codec._codec is None
def test_codec_warmup_with_single_token_returns_empty_audio():
codec = object.__new__(BreezeTTS2MimiCodec)
codec._num_codebooks = 4
codec._codebook_size = 8
codec._sample_rate = 24_000
codec._audio_tokenizer = None
codec._codec = None
codec._async_chunk = False
output = codec.forward(torch.tensor([1], dtype=torch.long), seq_token_counts=[1])
assert output.multimodal_outputs["model_outputs"][0].numel() == 0
assert output.multimodal_outputs["sr"][0].item() == 24_000
@@ -1,113 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
from transformers.models.qwen3 import Qwen3Config
from vllm_omni.model_executor.models.breeze_tts_2.configuration_breeze_tts_2 import (
BreezeTTS2Config,
BreezeTTS2DepthDecoderConfig,
BreezeTTS2TextEncoderConfig,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _config() -> BreezeTTS2Config:
return BreezeTTS2Config(
backbone_config={
"model_type": "qwen3",
"hidden_size": 32,
"intermediate_size": 64,
"num_hidden_layers": 2,
"num_attention_heads": 4,
"num_key_value_heads": 2,
"head_dim": 8,
"vocab_size": 128,
"max_position_embeddings": 512,
},
text_encoder_config={
"model_type": "t5gemma2_text",
"hidden_size": 24,
"intermediate_size": 48,
"num_hidden_layers": 6,
"num_attention_heads": 2,
"num_key_value_heads": 1,
"head_dim": 8,
"vocab_size": 256,
},
depth_decoder_config={
"model_type": "breeze_depth_decoder_model",
"hidden_size": 32,
"backbone_hidden_size": 32,
"audio_embed_size": 32,
"intermediate_size": 64,
"num_hidden_layers": 2,
"num_attention_heads": 4,
"num_key_value_heads": 2,
"head_dim": 8,
"num_codebooks": 16,
"vocab_size": 2051,
"rope_scaling": {
"rope_type": "llama3",
"factor": 32.0,
"low_freq_factor": 0.001953125,
"high_freq_factor": 0.0078125,
"original_max_position_embeddings": 16,
},
},
codec_config={"model_type": "mimi", "codebook_size": 2048, "num_quantizers": 32},
num_codebooks=16,
vocab_size=2051,
text_vocab_size=262158,
)
def test_nested_configs_and_text_config_use_qwen_backbone():
config = _config()
assert isinstance(config.backbone_config, Qwen3Config)
assert config.get_text_config() is config.backbone_config
assert config.get_text_config().vocab_size == 128
assert isinstance(config.text_encoder_config, BreezeTTS2TextEncoderConfig)
assert isinstance(config.depth_decoder_config, BreezeTTS2DepthDecoderConfig)
assert config.num_codebooks == config.depth_decoder_config.num_codebooks == 16
assert config.codec_config["codebook_size"] == 2048
def test_transformers_five_roundtrip_preserves_nested_configs_and_rope():
config = _config()
roundtrip = BreezeTTS2Config(**dict(config.to_dict()))
assert isinstance(roundtrip.backbone_config, Qwen3Config)
assert isinstance(roundtrip.text_encoder_config, BreezeTTS2TextEncoderConfig)
assert isinstance(roundtrip.depth_decoder_config, BreezeTTS2DepthDecoderConfig)
assert roundtrip.depth_decoder_config.rope_parameters["rope_theta"] == 500_000.0
def test_nested_autoconfig_registration_is_idempotent():
import importlib
module = importlib.import_module("vllm_omni.model_executor.models.breeze_tts_2.configuration_breeze_tts_2")
importlib.reload(module)
assert module.BreezeTTS2Config.model_type == "breeze"
def test_text_encoder_defaults_to_upstream_layer_pattern():
config = BreezeTTS2TextEncoderConfig(num_hidden_layers=6)
assert config.layer_types == [
"sliding_attention",
"sliding_attention",
"sliding_attention",
"sliding_attention",
"sliding_attention",
"full_attention",
]
def test_invalid_codebook_layout_is_rejected():
with pytest.raises(ValueError, match="positive"):
BreezeTTS2DepthDecoderConfig(num_codebooks=0)
@@ -1,157 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import pytest
from vllm import SamplingParams
from vllm_omni.entrypoints.openai.tts_adapters.breeze_tts_2 import BreezeTTS2Adapter
from vllm_omni.model_executor.models.breeze_tts_2.prompt_builder import (
BreezeTTS2PromptBuilder,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _Tokenizer:
unk_token_id = -1
def convert_tokens_to_ids(self, token):
return {"<|AUDIO|>": 262144, "<|audio_eos|>": 262145}.get(token, self.unk_token_id)
def __call__(self, texts, *, add_special_tokens=True, padding=False):
if isinstance(texts, str):
texts = [texts]
ids = [[1000 + ord(char) % 1000 for char in text] for text in texts]
if add_special_tokens:
ids = [[2, *row, 1] for row in ids]
return {"input_ids": ids}
def decode(self, ids, *, skip_special_tokens=False):
del skip_special_tokens
return "".join(chr(int(token_id) - 1000) for token_id in ids if token_id not in (1, 2))
def test_instruction_prompt_remains_one_independent_text_segment():
builder = BreezeTTS2PromptBuilder(
_Tokenizer(),
SimpleNamespace(
audio_token_id=262144,
audio_eos_token_id=262145,
pad_token_id=0,
backbone_config=SimpleNamespace(vocab_size=100000),
num_codebooks=16,
codec_config={"codebook_size": 2048},
),
)
prompt = builder.build(
{
"template": "tts_instruction",
"speaker": "S2",
"instruction": "Use a bright and friendly delivery.",
"text": "Hello from Breeze.",
}
)
info = prompt["additional_information"]
assert info["template"] == "tts_instruction"
assert "input_values" not in info
assert bool(info["text_ids_mask"].all())
assert info["text_ids_len"].tolist() == [len(info["prompt_ids"])]
def _request(extra_params=None, **overrides):
base = dict(
input="Hello.",
ref_text=None,
ref_audio=None,
task_type=None,
speed=1.0,
max_new_tokens=None,
language=None,
speaker_embedding=None,
x_vector_only_mode=None,
extra_params=extra_params or {},
)
base.update(overrides)
return SimpleNamespace(**base)
def _adapter() -> BreezeTTS2Adapter:
adapter = object.__new__(BreezeTTS2Adapter)
adapter.ctx = SimpleNamespace(server=SimpleNamespace())
return adapter
def test_cfg_scale_one_is_explicitly_supported_without_companion_request():
adapter = _adapter()
assert adapter.validate(_request({"guidance_scale": 1.0})) is None
assert adapter.validate(_request({"cfg_scale": 1.0})) is None
def test_non_unit_cfg_is_rejected_until_negative_branch_exists():
adapter = _adapter()
assert "guidance_scale=1.0" in adapter.validate(_request({"guidance_scale": 2.0}))
assert "guidance_scale=1.0" in adapter.validate(_request({"cfg_scale": 0.5}))
assert "negative_prompt" in adapter.validate(_request({"negative_prompt": "noisy"}))
def test_unsupported_conditioning_fields_are_rejected_not_ignored():
adapter = _adapter()
assert "language" in adapter.validate(_request(language="en"))
assert "speaker_embedding" in adapter.validate(_request(speaker_embedding=[0.1, 0.2]))
assert "x_vector_only_mode" in adapter.validate(_request(x_vector_only_mode=True))
# Absent fields must keep the request valid.
assert adapter.validate(_request()) is None
assert adapter.validate(_request(x_vector_only_mode=False)) is None
def test_multiple_reference_clips_are_rejected_not_silently_truncated():
adapter = _adapter()
adapter.ctx.server._validate_ref_audio_format = lambda _uri: None
error = adapter.validate(_request(ref_audio=["a.wav", "b.wav"], ref_text="hello"))
assert "exactly one reference clip" in error
assert "2" in error
# A single-element list stays valid and reaches format validation.
assert adapter.validate(_request(ref_audio=["a.wav"], ref_text="hello")) is None
def test_non_greedy_sampling_overrides_are_rejected():
adapter = _adapter()
# Greedy values, including the deploy-config defaults, stay valid.
assert adapter.validate(_request({"temperature": 0.0, "top_p": 1.0, "top_k": -1})) is None
assert adapter.validate(_request({"temperature": 0})) is None
# The talker ignores the sampled code0, so any non-greedy override must be
# refused instead of silently producing greedy audio.
assert "greedy" in adapter.validate(_request({"temperature": 0.7}))
assert "greedy" in adapter.validate(_request({"top_p": 0.9}))
assert "greedy" in adapter.validate(_request({"top_k": 50}))
assert "temperature must be a number" in adapter.validate(_request({"temperature": "warm"}))
@pytest.mark.parametrize("value", [0, -1, float("nan"), float("inf"), "invalid"])
def test_invalid_repetition_penalties_are_rejected(value):
assert "repetition_penalty" in _adapter().validate(_request({"repetition_penalty": value}))
@pytest.mark.parametrize("value", [0.9, 1.0, 1.1, "1.2"])
@pytest.mark.parametrize("max_new_tokens", [None, 32])
def test_repetition_penalty_override_reaches_standard_sampling_metadata(value, max_new_tokens):
adapter = _adapter()
request = _request({"repetition_penalty": value}, max_new_tokens=max_new_tokens)
original = [SamplingParams(max_tokens=128, repetition_penalty=1.1)]
assert adapter.validate(request) is None
updated = adapter.apply_sampling_overrides(original, request)
assert updated[0].repetition_penalty == float(value)
assert updated[0].max_tokens == (128 if max_new_tokens is None else max_new_tokens)
assert original[0].repetition_penalty == 1.1
assert original[0].max_tokens == 128
@@ -1,152 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.configuration_breeze_tts_2 import (
BreezeTTS2DepthDecoderConfig,
BreezeTTS2TextEncoderConfig,
)
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_depth import (
BreezeDepthDecoderForCausalLM,
)
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_text import (
BreezeTTS2TextEncoder,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _depth_config() -> BreezeTTS2DepthDecoderConfig:
return BreezeTTS2DepthDecoderConfig(
hidden_size=16,
backbone_hidden_size=16,
audio_embed_size=16,
intermediate_size=32,
num_hidden_layers=1,
num_attention_heads=4,
num_key_value_heads=2,
head_dim=4,
max_position_embeddings=8,
num_codebooks=4,
vocab_size=8,
rope_scaling={
"rope_type": "llama3",
"rope_theta": 100.0,
"factor": 1.0,
"low_freq_factor": 0.5,
"high_freq_factor": 1.0,
"original_max_position_embeddings": 4,
},
)
def test_depth_decoder_position_specific_logits_and_offsets():
config = _depth_config()
decoder = BreezeDepthDecoderForCausalLM(config)
output = decoder(
input_ids=torch.tensor([[0, 2]]),
backbone_last_hidden_state=torch.randn(1, 16),
use_cache=False,
)
assert output.logits.shape == (1, 1, 8)
with torch.no_grad():
embedding = decoder.model.embed_tokens.weight
expected_first = embedding[2] @ decoder.model.inputs_embeds_projector.weight.T
# The first hidden row is replaced by the backbone state, so compare the
# second input row (codebook 0, id 2 + 0 * vocab_size).
actual_input = decoder.model.embed_tokens(torch.tensor([[2]]))[0, 0]
actual_projected = actual_input @ decoder.model.inputs_embeds_projector.weight.T
assert torch.allclose(expected_first, actual_projected)
def _text_config() -> BreezeTTS2TextEncoderConfig:
return BreezeTTS2TextEncoderConfig(
hidden_size=16,
intermediate_size=32,
num_hidden_layers=2,
num_attention_heads=2,
num_key_value_heads=1,
head_dim=8,
vocab_size=32,
layer_types=["sliding_attention", "full_attention"],
rope_parameters={
"sliding_attention": {"rope_type": "default", "rope_theta": 100.0},
"full_attention": {"rope_type": "default", "rope_theta": 100.0},
},
)
def test_text_encoder_masks_right_padding_independently(monkeypatch):
import vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_text as text_module
class _ParallelEmbedding(torch.nn.Embedding):
def __init__(self, num_embeddings: int, embedding_dim: int):
super().__init__(num_embeddings, embedding_dim)
class _QKVLinear(torch.nn.Module):
def __init__(
self,
hidden_size: int,
head_size: int,
total_num_heads: int,
total_num_kv_heads: int,
**_: object,
):
super().__init__()
output_size = (total_num_heads + 2 * total_num_kv_heads) * head_size
self.linear = torch.nn.Linear(hidden_size, output_size, bias=False)
def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, None]:
return self.linear(hidden_states), None
class _RowLinear(torch.nn.Module):
def __init__(self, input_size: int, output_size: int, **_: object):
super().__init__()
self.linear = torch.nn.Linear(input_size, output_size, bias=False)
def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, None]:
return self.linear(hidden_states), None
class _MergedLinear(torch.nn.Module):
def __init__(self, input_size: int, output_sizes: list[int], **_: object):
super().__init__()
self.linear = torch.nn.Linear(input_size, sum(output_sizes), bias=False)
def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, None]:
return self.linear(hidden_states), None
monkeypatch.setattr(text_module, "get_tensor_model_parallel_world_size", lambda: 1)
monkeypatch.setattr(text_module, "VocabParallelEmbedding", _ParallelEmbedding)
monkeypatch.setattr(text_module, "QKVParallelLinear", _QKVLinear)
monkeypatch.setattr(text_module, "RowParallelLinear", _RowLinear)
monkeypatch.setattr(text_module, "MergedColumnParallelLinear", _MergedLinear)
config = _text_config()
encoder = BreezeTTS2TextEncoder(config).eval()
ids = torch.tensor([[3, 4]])
padded_ids = torch.tensor([[3, 4, 0, 0]])
mask = torch.tensor([[True, True, False, False]])
with torch.inference_mode():
short = encoder(ids)
padded = encoder(padded_ids, attention_mask=mask)
assert short.shape == (1, 2, 16)
assert padded.shape == (1, 4, 16)
assert torch.allclose(short[0, :2], padded[0, :2], atol=1e-5)
def test_text_encoder_weight_loader_reports_fused_target_names():
encoder = object.__new__(BreezeTTS2TextEncoder)
parameter = torch.nn.Parameter(torch.zeros(2, 2))
parameter.weight_loader = lambda param, tensor, shard_id: param.data.copy_(tensor)
encoder.__dict__["named_parameters"] = lambda: [("layers.0.qkv_proj.weight", parameter)]
loaded = encoder.load_weights([("layers.0.q_proj.weight", torch.ones(2, 2))])
assert loaded == {"layers.0.qkv_proj.weight"}
assert torch.equal(parameter.detach(), torch.ones(2, 2))
@@ -1,28 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
from vllm.model_executor.models.registry import ModelRegistry
from vllm_omni.model_executor.models.registry import OmniModelRegistry
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_breeze_architectures_resolve_to_omni_classes():
assert "BreezeForConditionalGeneration" in OmniModelRegistry.get_supported_archs()
assert "BreezeTTS2MimiCodec" in OmniModelRegistry.get_supported_archs()
talker = OmniModelRegistry._try_load_model_cls("BreezeForConditionalGeneration")
codec = OmniModelRegistry._try_load_model_cls("BreezeTTS2MimiCodec")
assert talker.__name__ == "BreezeTTS2TalkerForGeneration"
assert codec.__name__ == "BreezeTTS2MimiCodec"
def test_plugin_registration_publishes_architectures_to_upstream_registry():
from vllm_omni.engine.arg_utils import register_omni_models_to_vllm
register_omni_models_to_vllm()
assert "BreezeForConditionalGeneration" in ModelRegistry.get_supported_archs()
assert "BreezeTTS2MimiCodec" in ModelRegistry.get_supported_archs()
@@ -1,36 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from pathlib import Path
import pytest
import yaml
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
from vllm_omni.model_executor.models.breeze_tts_2.pipeline import BREEZE_TTS_2_PIPELINE
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_breeze_pipeline_is_registered_with_talker_and_codec_stages():
assert OMNI_PIPELINES["breeze"] is BREEZE_TTS_2_PIPELINE
assert [stage.model_stage for stage in BREEZE_TTS_2_PIPELINE.stages] == [
"breeze_tts_2",
"breeze_tts_2_codec",
]
assert BREEZE_TTS_2_PIPELINE.stages[1].requires_full_payload_input is True
assert BREEZE_TTS_2_PIPELINE.stages[0].async_chunk_process_next_stage_input_func.endswith(
"talker2codec_async_chunk"
)
assert BREEZE_TTS_2_PIPELINE.stages[1].retains_state_across_chunks is True
def test_breeze_default_deploy_is_streaming():
deploy_path = Path(__file__).parents[4] / "vllm_omni" / "deploy" / "breeze_tts_2.yaml"
config = yaml.safe_load(deploy_path.read_text(encoding="utf-8"))
assert config["async_chunk"] is True
assert len(config["stages"]) == 2
# Stage 0 must prefill each prompt in one step: the talker builds a codec
# frame from the last hidden row of every scheduled request.
assert config["stages"][0]["enable_chunked_prefill"] is False
assert config["connectors"]["connector_of_shared_memory"]["extra"]["breeze_codec_chunk_frames"] == 8
@@ -1,189 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.prompt_builder import (
BreezeTTS2PromptBuilder,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _Tokenizer:
pad_token_id = 0
eos_token_id = 1
unk_token_id = -1
_special = {
"<|AUDIO|>": 262144,
"<|audio_eos|>": 262145,
}
def convert_tokens_to_ids(self, token):
return self._special.get(token, self.unk_token_id)
def __call__(self, texts, *, add_special_tokens=True, padding=False):
if isinstance(texts, str):
texts = [texts]
ids = []
for text in texts:
values = [1000 + ord(char) % 1000 for char in text]
if add_special_tokens:
values = [2, *values, 1]
ids.append(values)
return {"input_ids": ids}
def decode(self, ids, *, skip_special_tokens=False):
del skip_special_tokens
rendered = []
for token_id in ids:
if token_id == 262144:
rendered.append("<|AUDIO|>")
elif token_id == 262145:
rendered.append("<|audio_eos|>")
elif token_id in (2, 1):
continue
else:
rendered.append(chr(int(token_id) - 1000))
return "".join(rendered)
class _RenderingTokenizer(_Tokenizer):
"""Tiny tokenizer that preserves Breeze placeholders after decode."""
def __call__(self, texts, *, add_special_tokens=True, padding=False):
if isinstance(texts, str):
texts = [texts]
ids = []
for text in texts:
row = []
cursor = 0
while cursor < len(text):
if text.startswith("<|AUDIO|>", cursor):
row.append(262144)
cursor += len("<|AUDIO|>")
elif text.startswith("<|audio_eos|>", cursor):
row.append(262145)
cursor += len("<|audio_eos|>")
else:
row.append(1000 + ord(text[cursor]) % 1000)
cursor += 1
if add_special_tokens:
row = [2, *row, 1]
ids.append(row)
return {"input_ids": ids}
class _ReferenceEncoder:
def __init__(self):
self.calls = []
def encode(self, audio, sample_rate=None):
self.calls.append((audio, sample_rate))
return torch.arange(32, dtype=torch.int16).reshape(2, 16)
def _config():
return SimpleNamespace(
audio_token_id=262144,
audio_eos_token_id=262145,
pad_token_id=0,
backbone_config=SimpleNamespace(vocab_size=100000),
num_codebooks=16,
codec_config={"codebook_size": 2048},
)
def test_builder_accepts_missing_codec_config():
config = _config()
config.codec_config = None
builder = BreezeTTS2PromptBuilder(_Tokenizer(), config)
assert builder.codebook_size == 2048
def test_build_instruction_prompt():
builder = BreezeTTS2PromptBuilder(_Tokenizer(), _config())
prompt = builder.build({"text": "hello", "instruction": "happy", "speaker": "S0"})
assert prompt["prompt_token_ids"]
info = prompt["additional_information"]
assert "input_values" not in info
assert set(prompt["prompt_token_ids"]) == {0}
assert info["prompt_ids"].numel() == len(prompt["prompt_token_ids"])
assert info["text_ids_mask"].dtype == torch.bool
assert info["text_ids_len"].tolist() == [len(prompt["prompt_token_ids"])]
assert bool(info["text_ids_mask"].all())
def test_build_reference_prompt_and_reuse_encoder():
encoder = _ReferenceEncoder()
builder = BreezeTTS2PromptBuilder(_RenderingTokenizer(), _config(), encoder)
prompt = builder.build(
{
"template": "ref_edit_tata",
"text": "target",
"instruction": "calm",
"ref_text": "reference",
"ref_audio": "ref.wav",
"ref_audio_sample_rate": 16000,
}
)
info = prompt["additional_information"]
codes = info["input_values"]
assert tuple(codes.shape) == (2, 16)
assert codes.dtype == torch.int16
assert set(prompt["prompt_token_ids"]) == {0}
assert info["prompt_ids"].tolist().count(262144) == 2
assert info["prompt_ids"].tolist().count(262145) == 1
assert int((~info["text_ids_mask"]).sum()) == 3
assert info["text_ids_len"].numel() == 2
assert encoder.calls == [("ref.wav", 16000)]
def test_build_reference_prompt_preserves_special_tokens_after_rendering():
encoder = _ReferenceEncoder()
builder = BreezeTTS2PromptBuilder(_RenderingTokenizer(), _config(), encoder)
prompt = builder.build(
{
"template": "ref_clone_tata",
"text": "target",
"ref_text": "reference",
"ref_audio": "ref.wav",
"ref_audio_sample_rate": 16000,
}
)
info = prompt["additional_information"]
prompt_ids = info["prompt_ids"].tolist()
assert prompt_ids.count(262144) == 2
assert prompt_ids.count(262145) == 1
assert int((~info["text_ids_mask"]).sum()) == 3
assert len(info["text_ids_len"]) == 2
assert sum(info["text_ids_len"].tolist()) == int(info["text_ids_mask"].sum())
def test_build_reference_prompt_accepts_preencoded_codes_mapping():
builder = BreezeTTS2PromptBuilder(_RenderingTokenizer(), _config())
codes = torch.arange(32, dtype=torch.int16).reshape(2, 16)
prompt = builder.build(
{
"template": "ref_clone_tata",
"text": "target",
"ref_text": "reference",
"codes": {"ref": codes},
}
)
info = prompt["additional_information"]
assert torch.equal(info["input_values"], codes)
assert info["ref_code_len"] == 2
@@ -1,187 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import importlib.util
from pathlib import Path
from types import SimpleNamespace
import numpy as np
import pytest
import soundfile as sf
import torch
from vllm_omni.model_executor.models.breeze_tts_2.prompt_builder import (
BreezeTTS2PromptBuilder,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
_EXAMPLE_PATH = (
Path(__file__).resolve().parents[4]
/ "examples"
/ "offline_inference"
/ "text_to_speech"
/ "breeze_tts_2"
/ "end2end.py"
)
class _Tokenizer:
unk_token_id = -1
def convert_tokens_to_ids(self, token):
return {
"<|AUDIO|>": 262144,
"<|audio_eos|>": 262145,
}.get(token, self.unk_token_id)
def __call__(self, texts, *, add_special_tokens=True, padding=False):
if isinstance(texts, str):
texts = [texts]
ids = [[1000 + ord(char) % 1000 for char in text] for text in texts]
for row_index, text in enumerate(texts):
if "<|AUDIO|>" in text or "<|audio_eos|>" in text:
cursor = 0
row = []
while cursor < len(text):
if text.startswith("<|AUDIO|>", cursor):
row.append(262144)
cursor += len("<|AUDIO|>")
elif text.startswith("<|audio_eos|>", cursor):
row.append(262145)
cursor += len("<|audio_eos|>")
else:
row.append(1000 + ord(text[cursor]) % 1000)
cursor += 1
ids[row_index] = row
if add_special_tokens:
ids = [[2, *row, 1] for row in ids]
return {"input_ids": ids}
def decode(self, ids, *, skip_special_tokens=False):
del skip_special_tokens
rendered = []
for token_id in ids:
if token_id == 262144:
rendered.append("<|AUDIO|>")
elif token_id == 262145:
rendered.append("<|audio_eos|>")
elif token_id in (1, 2):
continue
else:
rendered.append(chr(int(token_id) - 1000))
return "".join(rendered)
class _ReferenceEncoder:
def __init__(self) -> None:
self.calls: list[tuple[object, int | None]] = []
def encode(self, audio, sample_rate=None):
self.calls.append((audio, sample_rate))
return torch.arange(32, dtype=torch.int16).reshape(2, 16)
def _builder() -> tuple[BreezeTTS2PromptBuilder, _ReferenceEncoder]:
encoder = _ReferenceEncoder()
builder = BreezeTTS2PromptBuilder(
_Tokenizer(),
SimpleNamespace(
audio_token_id=262144,
audio_eos_token_id=262145,
pad_token_id=0,
backbone_config=SimpleNamespace(vocab_size=100000),
num_codebooks=16,
codec_config={"codebook_size": 2048},
),
encoder,
)
return builder, encoder
def test_reference_voice_uses_exact_transcript_and_audio_codes():
builder, encoder = _builder()
prompt = builder.build(
{
"template": "ref_clone_tata",
"speaker": "S1",
"ref_text": "exact reference transcript",
"ref_audio": "reference.wav",
"ref_audio_sample_rate": 24_000,
"text": "new target text",
}
)
info = prompt["additional_information"]
assert tuple(info["input_values"].shape) == (2, 16)
assert info["ref_code_len"] == 2
assert info["prompt_ids"].tolist().count(262144) == 2
assert info["text_ids_len"].numel() == 2
assert int(info["text_ids_mask"].sum()) == int(info["text_ids_len"].sum())
assert encoder.calls == [("reference.wav", 24_000)]
def test_reference_plus_instruction_composes_voice_direction_prompt():
builder, _ = _builder()
prompt = builder.build(
{
"template": "ref_edit_tata",
"speaker": "S0",
"ref_text": "exact reference transcript",
"ref_audio": "reference.wav",
"instruction": "speak calmly and warmly",
"text": "new target text",
}
)
info = prompt["additional_information"]
assert info["template"] == "ref_edit_tata"
assert tuple(info["input_values"].shape) == (2, 16)
assert info["prompt_ids"].tolist().count(262144) == 2
assert info["text_ids_len"].numel() == 2
def test_offline_example_reference_payload_feeds_prompt_builder(tmp_path):
"""Smoke: the offline example's payload keys flow into the real builder.
Loads ``end2end.py`` (voxtral-style importlib), reads a real stereo wav
through its ``reference_payload`` helper, and feeds the result into the
real ``BreezeTTS2PromptBuilder`` — guarding the example↔builder field
contract that previously broke via an unread ``ref_audio_path`` key.
"""
spec = importlib.util.spec_from_file_location("breeze_tts_2_end2end", _EXAMPLE_PATH)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sample_rate = 16_000
t = np.arange(sample_rate, dtype=np.float32) / sample_rate
tone = np.sin(2 * np.pi * 220.0 * t)
ref_path = tmp_path / "reference.wav"
sf.write(str(ref_path), np.stack([tone, tone], axis=1), sample_rate)
payload = module.reference_payload(str(ref_path))
# The helper emits exactly the keys the prompt builder consumes.
assert set(payload) == {"ref_audio", "ref_audio_sample_rate"}
assert payload["ref_audio_sample_rate"] == sample_rate
assert payload["ref_audio"].ndim == 1 # stereo down-mixed to one clip
builder, encoder = _builder()
prompt = builder.build(
{
"template": "ref_clone_tata",
"speaker": "S0",
"ref_text": "exact reference transcript",
"text": "new target text",
**payload,
}
)
info = prompt["additional_information"]
assert tuple(info["input_values"].shape) == (2, 16)
assert info["ref_code_len"] == 2
assert info["prompt_ids"].tolist().count(262144) == 2
# The builder received the example's waveform and sample rate verbatim.
assert encoder.calls == [(payload["ref_audio"], sample_rate)]
@@ -1,122 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from typing import Any
import pytest
import torch
from vllm.v1.sample.logits_processor import LogitsProcessors
from vllm.v1.sample.metadata import SamplingMetadata
from tests.model_executor.models.breeze_tts_2.test_talker_state_machine import _depth_codes, _talker
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _model(first_score=11.0, second_score=10.5):
model = _talker()
model._async_chunk = True
logits = torch.full((1, 9), -100.0)
logits[0, 2:4] = torch.tensor([first_score, second_score])
model._main_head_logits = lambda hidden: logits.expand(hidden.shape[0], -1).clone()
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
return model
def _step(model, infos, penalties, *, no_penalties=False):
return model.make_omni_output(
torch.zeros(len(infos), 16),
model_intermediate_buffer=infos,
request_token_spans=[(i, i + 1) for i in range(len(infos))],
sampling_metadata=SamplingMetadata(
temperature=None,
all_greedy=True,
all_random=False,
top_p=None,
top_k=None,
generators={},
max_num_logprobs=None,
no_penalties=no_penalties,
frequency_penalties=torch.zeros(len(infos)),
presence_penalties=torch.zeros(len(infos)),
repetition_penalties=torch.tensor(penalties),
# Text/reference conditioning must never be counted as generated
# codec history, even if the scheduler ids overlap the codec ids.
prompt_token_ids=torch.tensor([[2, 2]] * len(infos)),
output_token_ids=[list(info.get("breeze_generated_code0_ids", [])) for info in infos],
allowed_token_ids_mask=None,
bad_words_token_ids={},
logitsprocs=LogitsProcessors(),
),
)
@pytest.mark.parametrize("scores", [(11.0, 10.5), (-1.0, -1.05)])
def test_penalty_applies_once_per_generated_token_and_respects_logit_sign(scores):
model = _model(*scores)
info = {"prompt_ids": torch.tensor([2]), "input_values": torch.tensor([[2, 3, 4, 5]])}
chosen = []
for _ in range(4):
output = _step(model, [info], [1.1])
chosen.append(output.multimodal_outputs["codes"]["audio"][0][0, 0].item())
assert chosen == [2, 3, 2, 2]
def test_request_history_and_penalty_are_independent():
model = _model()
first: dict[str, Any] = {}
second: dict[str, Any] = {}
_step(model, [first], [1.1])
output = _step(model, [first, second], [1.1, 1.1])
assert [codes[0, 0].item() for codes in output.multimodal_outputs["codes"]["audio"]] == [3, 2]
# Reverse the batch order; a request's history follows its state, while
# each penalty follows the current sampling-metadata row.
output = _step(model, [second, first], [1.1, 1.0])
assert [codes[0, 0].item() for codes in output.multimodal_outputs["codes"]["audio"]] == [3, 2]
def test_disabled_penalties_do_not_read_uninitialized_sampling_buffer():
model = _model()
info: dict[str, Any] = {}
for _ in range(2):
output = _step(model, [info], [float("nan")], no_penalties=True)
assert output.multimodal_outputs["codes"]["audio"][0][0, 0].item() == 2
def test_scheduler_logits_preserve_selected_frame_under_further_sampling_transforms():
model = _model()
info: dict[str, Any] = {}
_step(model, [info], [1.1])
output = _step(model, [info], [1.1])
selected = output.multimodal_outputs["codes"]["audio"][0][0, 0].item()
logits = model.compute_logits(torch.zeros(1, 16))
assert selected == 3
assert logits.argmax(dim=-1).item() == selected
assert torch.isfinite(logits).sum().item() == 1
assert logits[0, selected].item() == 0.0
# The scheduler may apply repetition again and rescale temperature. A
# finite zero for the selected token preserves a valid point mass.
for temperature in (0.1, 0.9, 2.0):
probabilities = torch.softmax(logits / 1.1 / temperature, dim=-1)
assert torch.isfinite(probabilities).all()
assert probabilities[0, selected].item() == 1.0
def test_repetition_penalty_can_select_eos_without_emitting_a_frame():
model = _model()
info: dict[str, Any] = {}
_step(model, [info], [1.1])
logits = torch.full((1, 9), -100.0)
logits[0, 2] = 11.0
logits[0, 8] = 10.5
model._main_head_logits = lambda hidden: logits.expand(hidden.shape[0], -1).clone()
output = _step(model, [info], [1.1])
assert output.multimodal_outputs["codes"]["audio"][0].numel() == 0
assert info["breeze_generated_frames"] == 1
assert model.compute_logits(torch.zeros(1, 16)).argmax(dim=-1).item() == 8
@@ -1,133 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import pytest
import torch
from vllm_omni.model_executor.stage_input_processors.breeze_tts_2 import (
talker2codec,
talker2codec_full_payload,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_talker2codec_flattens_codebook_major():
codes = torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.int16)
output = SimpleNamespace(multimodal_output={"codes": {"audio": codes}})
prompts = talker2codec([output])
assert prompts[0]["prompt_token_ids"] == [1, 3, 5, 2, 4, 6]
assert torch.equal(prompts[0]["multi_modal_data"]["codes"]["audio"], codes.transpose(0, 1))
def test_talker2codec_keeps_empty_request_terminal():
output = SimpleNamespace(multimodal_output={"codes": {}})
prompts = talker2codec([output])
assert prompts == [{"prompt_token_ids": []}]
def test_talker2codec_accepts_engine_core_output_wrapper():
codes = torch.tensor([[7, 8]], dtype=torch.int16)
output = SimpleNamespace(outputs=[SimpleNamespace(multimodal_output={"codes": {"audio": codes}})])
prompts = talker2codec([output])
assert prompts[0]["prompt_token_ids"] == [7, 8]
def test_talker2codec_unwraps_per_request_audio_container():
codes = torch.tensor([[7, 8], [9, 10]], dtype=torch.int16)
output = SimpleNamespace(multimodal_output={"codes": {"audio": [codes]}})
prompts = talker2codec([output])
assert prompts[0]["prompt_token_ids"] == [7, 9, 8, 10]
def test_talker2codec_accepts_flat_full_payload():
output = {"codes.audio": torch.tensor([1, 3, 2, 4], dtype=torch.int16)}
prompts = talker2codec([output])
assert prompts[0]["prompt_token_ids"] == [1, 3, 2, 4]
assert torch.equal(
prompts[0]["multi_modal_data"]["codes"]["audio"],
torch.tensor([1, 3, 2, 4], dtype=torch.int16),
)
def test_talker2codec_full_payload_transposes_accumulated_frames():
payload = talker2codec_full_payload(
transfer_manager=None,
pooling_output={"codes.audio": torch.tensor([[1, 2], [3, 4], [5, 6]])},
request=None,
)
assert payload is not None
assert torch.equal(
payload.codes.audio,
torch.tensor([1, 3, 5, 2, 4, 6]),
)
def test_talker2codec_full_payload_accepts_runner_pooling_output():
payload = talker2codec_full_payload(
pooling_output={"codes.audio": torch.tensor([[9, 8]], dtype=torch.int16)},
request=None,
)
assert payload is not None
assert torch.equal(payload.codes.audio, torch.tensor([9, 8]))
def test_full_payload_zero_frame_finish_sends_explicit_terminal_marker():
payload = talker2codec_full_payload(
pooling_output={"codes.audio": torch.empty(0, 16, dtype=torch.long)},
request=None,
is_finished=True,
)
assert payload is not None
assert payload.codes.audio.numel() == 0
assert payload.meta.code_flat_numel == 0
assert bool(payload.meta.finished.item()) is True
def test_full_payload_missing_codes_finish_sends_explicit_terminal_marker():
payload = talker2codec_full_payload(
pooling_output={},
request=None,
is_finished=True,
)
assert payload is not None
assert payload.codes.audio.numel() == 0
assert bool(payload.meta.finished.item()) is True
def test_full_payload_empty_without_finish_stays_silent():
payload = talker2codec_full_payload(
pooling_output={"codes.audio": torch.empty(0, 16, dtype=torch.long)},
request=None,
is_finished=False,
)
assert payload is None
def test_full_payload_carries_finished_flag_on_nonempty_frames():
payload = talker2codec_full_payload(
pooling_output={"codes.audio": torch.tensor([[1, 2], [3, 4]], dtype=torch.long)},
request=None,
is_finished=True,
)
assert payload is not None
assert payload.codes.audio.numel() == 4
assert bool(payload.meta.finished.item()) is True
@@ -1,243 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import copy
from collections import defaultdict
from types import SimpleNamespace
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_codec import (
BreezeTTS2MimiCodec,
)
from vllm_omni.model_executor.stage_input_processors.breeze_tts_2 import (
talker2codec_async_chunk,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _TransferManager:
def __init__(self, chunk_frames: int = 4):
self.connector = SimpleNamespace(config={"breeze_codec_chunk_frames": chunk_frames})
self.code_prompt_token_ids: defaultdict[str, list[torch.Tensor]] = defaultdict(list)
class _Request:
def __init__(self, request_id: str, finished: bool = False):
self.external_req_id = request_id
self._finished = finished
def is_finished(self):
return self._finished
def test_async_processor_sends_only_unemitted_tail_and_flushes_finish():
manager = _TransferManager(chunk_frames=4)
request = _Request("req-1")
for code0 in range(3):
frame = torch.tensor([code0, code0 + 4, code0 + 8, code0 + 12], dtype=torch.long)
assert talker2codec_async_chunk(manager, {"codes": {"audio": frame}}, request) is None
payload = talker2codec_async_chunk(
manager,
{"codes": {"audio": torch.tensor([3, 7, 11, 15], dtype=torch.long)}},
request,
)
assert payload is not None
assert payload.codes.audio.tolist() == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
assert bool(payload.meta.finished.item()) is False
assert manager.code_prompt_token_ids["req-1"] == []
assert talker2codec_async_chunk(manager, {"codes": {"audio": torch.arange(4)}}, request) is None
payload = talker2codec_async_chunk(manager, None, _Request("req-1", finished=True))
assert payload.codes.audio.tolist() == [0, 1, 2, 3]
assert bool(payload.meta.finished.item()) is True
assert bool(payload.meta.stream_finished.item()) is True
assert payload.meta.codec_streaming is True
class _Decoder:
def __init__(self):
self.calls = []
def batched_chunked_decode(self, codes, lengths, caches=None, **kwargs):
self.calls.append((codes.shape, tuple(lengths), tuple(caches), kwargs))
return [torch.ones(length * 1920, dtype=torch.float32) for length in lengths]
def test_stateful_codec_batches_request_chunks_and_releases_finished_state():
decoder = _Decoder()
codec = object.__new__(BreezeTTS2MimiCodec)
codec._async_chunk = True
codec._num_codebooks = 4
codec._codebook_size = 8
codec._sample_rate = 24_000
codec._audio_tokenizer = SimpleNamespace(model=SimpleNamespace(decoder=decoder))
codec._decoder_state_cache = {}
input_ids = torch.tensor([0, 1, 2, 3, 4, 5, 6, 7], dtype=torch.long)
output = codec.forward(
input_ids,
runtime_additional_information=[
# ``stream_finished`` is the model-visible flag: the connector
# receiver strips ``meta.finished`` while merging runtime info.
{"meta": {"request_id": "live", "stream_finished": False}},
{"meta": {"request_id": "done", "stream_finished": True}},
],
seq_token_counts=[4, 4],
request_ids=["scheduler-live", "scheduler-done"],
)
assert decoder.calls[0][0] == (2, 4, 1)
assert [len(item) for item in output.multimodal_outputs["model_outputs"]] == [1920, 1920]
assert set(codec._decoder_state_cache) == {"scheduler-live"}
codec.on_requests_finished(["scheduler-live"])
assert codec._decoder_state_cache == {}
def test_stateful_codec_rejects_partial_frame_chunk():
decoder = _Decoder()
codec = object.__new__(BreezeTTS2MimiCodec)
codec._async_chunk = True
codec._num_codebooks = 4
codec._codebook_size = 8
codec._sample_rate = 24_000
codec._audio_tokenizer = SimpleNamespace(model=SimpleNamespace(decoder=decoder))
codec._decoder_state_cache = {}
# 5 codes cannot form a whole frame; a silent state reset would corrupt
# the rest of the stream, so the codec must fail loudly.
with pytest.raises(ValueError, match="not divisible"):
codec.forward(
torch.tensor([0, 1, 2, 3, 4], dtype=torch.long),
runtime_additional_information=[{"meta": {"request_id": "bad", "finished": False}}],
seq_token_counts=[5],
request_ids=["scheduler-bad"],
)
assert decoder.calls == []
@pytest.mark.parametrize("token_counts", [(4, 0), (0, 4), (0, 4, 0, 8, 0)])
def test_stateful_codec_terminal_marker_pops_state_without_decoding(token_counts):
decoder = _Decoder()
codec = object.__new__(BreezeTTS2MimiCodec)
codec._async_chunk = True
codec._num_codebooks = 4
codec._codebook_size = 8
codec._sample_rate = 24_000
codec._audio_tokenizer = SimpleNamespace(model=SimpleNamespace(decoder=decoder))
request_ids = [f"scheduler-{index}" for index in range(len(token_counts))]
codec._decoder_state_cache = {request_id: {} for request_id in request_ids}
output = codec.forward(
torch.arange(sum(token_counts), dtype=torch.long) % codec._codebook_size,
runtime_additional_information=[
# Plain ``finished`` covers the fallback for direct in-process
# callers that do not go through the connector receiver.
{"meta": {"finished": count == 0}}
for count in token_counts
],
seq_token_counts=token_counts,
request_ids=request_ids,
)
# Empty terminal markers release their state without consuming an output
# slot belonging to another request, regardless of their batch position.
assert len(decoder.calls) == 1
assert decoder.calls[0][0][0] == sum(count > 0 for count in token_counts)
assert [item.numel() for item in output.multimodal_outputs["model_outputs"]] == [
count // codec._num_codebooks * 1920 for count in token_counts
]
live_request_ids = [request_id for request_id, count in zip(request_ids, token_counts) if count]
assert list(codec._decoder_state_cache) == live_request_ids
assert all(codec._decoder_state_cache[request_id]["prefix_frames"] == 0 for request_id in live_request_ids)
@pytest.mark.parametrize("configured_chunk_frames", [None, 4])
def test_stateful_codec_advances_decoder_cache_after_sliding_window(monkeypatch, tmp_path, configured_chunk_frames):
from vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_tokenizer import Qwen3TTSTokenizer
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 (
Qwen3TTSTokenizerV2Decoder,
)
torch.manual_seed(0)
config = Qwen3TTSTokenizerV2DecoderConfig(
codebook_size=32,
hidden_size=16,
latent_dim=16,
codebook_dim=16,
num_attention_heads=2,
num_key_value_heads=2,
intermediate_size=32,
num_hidden_layers=1,
num_quantizers=2,
decoder_dim=32,
upsample_rates=(2,),
upsampling_ratios=(2,),
sliding_window=72,
)
decoder = Qwen3TTSTokenizerV2Decoder(config).eval()
# Codebooks initialize to zeros before checkpoint loading; populate them
# so different codec IDs produce distinguishable cached frame histories.
with torch.no_grad():
for name, parameter in decoder.quantizer.named_parameters():
if name.endswith("embedding_sum"):
parameter.normal_()
tokenizer = SimpleNamespace(model=SimpleNamespace(decoder=decoder, get_output_sample_rate=lambda: 24_000))
monkeypatch.setattr(Qwen3TTSTokenizer, "from_pretrained", lambda *args, **kwargs: tokenizer)
connector_extra = {} if configured_chunk_frames is None else {"breeze_codec_chunk_frames": configured_chunk_frames}
chunk_frames = configured_chunk_frames or 8
codec = object.__new__(BreezeTTS2MimiCodec)
codec.vllm_config = SimpleNamespace(
model_config=SimpleNamespace(stage_connector_config={"extra": connector_extra}),
device_config=SimpleNamespace(device="cpu"),
)
codec._tokenizer_path = tmp_path / "audio_tokenizer"
codec._audio_tokenizer = None
codec._async_chunk = True
codec._num_codebooks = config.num_quantizers
codec._codebook_size = config.codebook_size
codec._decoder_state_cache = {}
codec.load_weights(iter(()))
# At least two chunks after the decoder's rolling window expose a stale
# cache even when the first padded chunk still produces plausible audio.
total_frames = config.sliding_window + 2 * chunk_frames
tail_frames = 3
codes = torch.randint(0, config.codebook_size, (1, config.num_quantizers, total_frames + tail_frames))
for end in range(chunk_frames, total_frames + 1, chunk_frames):
flat = codes[0, :, end - chunk_frames : end].reshape(-1)
codec.forward(
flat,
runtime_additional_information=[{"meta": {"stream_finished": False}}],
seq_token_counts=[flat.numel()],
request_ids=["scheduler-live"],
)
state = codec._decoder_state_cache["scheduler-live"]
assert state["suffix_frames"] == min(end - chunk_frames, config.sliding_window) + chunk_frames
with torch.inference_mode():
expected_quantized = decoder.quantizer.decode(codes[..., max(0, end - config.sliding_window) : end])
torch.testing.assert_close(state["suffix_quantized"], expected_quantized)
# A short terminal chunk must emit only its real frames, then release
# state. Its waveform should agree with the unpadded single-request path.
with torch.inference_mode():
expected_tail = decoder.chunked_decode(codes[..., -tail_frames:], caches=copy.deepcopy(state))
flat_tail = codes[0, :, -tail_frames:].reshape(-1)
output = codec.forward(
flat_tail,
runtime_additional_information=[{"meta": {"stream_finished": True}}],
seq_token_counts=[flat_tail.numel()],
request_ids=["scheduler-live"],
)
waveform = output.multimodal_outputs["model_outputs"][0]
assert waveform.numel() == tail_frames * decoder.total_upsample
torch.testing.assert_close(waveform, expected_tail.reshape(-1), atol=1e-5, rtol=1e-4)
assert codec._decoder_state_cache == {}
@@ -1,243 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from typing import Any
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_talker import (
BreezeTTS2TalkerForGeneration,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _Head:
def __init__(self) -> None:
self.code0 = 2
def __call__(self, hidden: torch.Tensor) -> torch.Tensor:
logits = torch.zeros((hidden.shape[0], 9), dtype=hidden.dtype)
logits[:, self.code0] = 1.0
return logits
class _Embedding:
def __init__(self) -> None:
self.weight = torch.randn(4 * 8, 16)
def __call__(self, ids: torch.Tensor) -> torch.Tensor:
return self.weight[ids.reshape(-1)].reshape(*ids.shape, -1)
def _talker() -> BreezeTTS2TalkerForGeneration:
model = object.__new__(BreezeTTS2TalkerForGeneration)
model.num_codebooks = 4
model.codebook_vocab_size = 8
model.codebook_size = 8
model.hidden_size = 16
model.codebook_eos_token_id = 0
model.audio_token_id = 100
model.audio_eos_token_id = 101
# Bypass nn.Module registration; this fake is created with object.__new__.
model.__dict__["embed_audio_tokens"] = _Embedding()
model.audio_token_offsets = torch.arange(4) * 8
model.lm_head = _Head()
model.logits_processor = lambda head, hidden: head(hidden)
model._codec_disallowed_mask = None
model._batch_state = None
model._batch_state_spans = None
model._async_chunk = False
model._golden_dump_dir = None
return model
def _depth_codes(_model: Any, _hidden: torch.Tensor, code0: int) -> torch.Tensor:
if code0 >= 8:
return torch.empty(0, dtype=torch.long)
return torch.tensor([code0, code0 + 1, code0 + 2, code0 + 3], dtype=torch.long).unsqueeze(0)
def test_prefill_hidden_generates_and_stores_first_complete_frame():
model = _talker()
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"_omni_is_prefill": True, "breeze_generated_frames": 0, "breeze_max_new_frames": 3}
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
codes = output.multimodal_outputs["codes"]["audio"][0]
assert torch.equal(codes, torch.tensor([[2, 3, 4, 5]]))
assert torch.equal(info["breeze_current_frame"], codes[0])
assert info["breeze_generated_frames"] == 1
assert info["breeze_force_eos"] is False
def test_first_decode_consumes_full_frame_instead_of_scalar_eos_fallback():
model = _talker()
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"_omni_is_prefill": True, "breeze_generated_frames": 0, "breeze_max_new_frames": 3}
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
frame = output.multimodal_outputs["codes"]["audio"][0][0]
decode_info = dict(info)
decode_info.pop("_omni_is_prefill", None)
safe_id, decode_embeds, update = model.preprocess(
torch.tensor([2], dtype=torch.long),
None,
**decode_info,
)
assert safe_id.item() == 2
assert update == {}
expected = model.embed_input_ids(frame.reshape(1, 1, -1)).reshape(1, -1)
assert torch.equal(decode_embeds, expected)
def test_real_decode_rejects_missing_current_frame():
model = _talker()
info = {
"breeze_generated_frames": 1,
"breeze_audio_codes": torch.tensor([[2, 3, 4, 5]]),
}
try:
model.preprocess(torch.tensor([2], dtype=torch.long), None, **info)
except RuntimeError as exc:
assert "breeze_current_frame" in str(exc)
assert "codebooks 1..15" in str(exc)
else:
raise AssertionError("missing breeze_current_frame must fail explicitly")
def test_embed_input_ids_rejects_scalar_codec_id():
model = _talker()
try:
model.embed_input_ids(torch.tensor([[2]]))
except ValueError as exc:
assert "complete codec frames" in str(exc)
else:
raise AssertionError("scalar codec ids must not be embedded")
def test_decode_accumulates_frames_then_natural_eos_stops_without_fake_frame():
model = _talker()
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"breeze_generated_frames": 1, "breeze_max_new_frames": 3}
info["breeze_audio_codes"] = torch.tensor([[2, 3, 4, 5]])
model.lm_head.code0 = 3
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
assert output.multimodal_outputs["codes"]["audio"][0].shape == (2, 4)
# Main-head class 8 is the Breeze backbone EOS (one past vocab_size=8).
model.lm_head.code0 = 8
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
assert output.multimodal_outputs["codes"]["audio"][0].shape == (2, 4)
assert info["breeze_force_eos"] is True
assert info["breeze_force_eos"] is True
logits = model.compute_logits(hidden)
assert torch.equal(logits[0, :8], torch.full((8,), float("-inf")))
assert logits[0, 8].item() == 0.0
def test_frame_budget_forces_eos_on_followup_step():
model = _talker()
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"breeze_generated_frames": 2, "breeze_max_new_frames": 2}
info["breeze_audio_codes"] = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 0]])
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
assert output.multimodal_outputs["codes"]["audio"][0].shape == (2, 4)
assert info["breeze_generated_frames"] == 2
assert info["breeze_force_eos"] is True
def test_async_chunk_mode_skips_full_accumulation():
model = _talker()
model._async_chunk = True
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"breeze_generated_frames": 0, "breeze_max_new_frames": 8}
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
for _ in range(3):
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
assert output.multimodal_outputs["codes"]["audio"][0].shape == (1, 4)
# Streaming keeps only the per-frame tail; the cumulative buffer (and its
# per-step torch.cat) must not exist without the opt-in golden dump.
assert "breeze_audio_codes" not in info
assert info["breeze_generated_frames"] == 3
assert torch.equal(info["breeze_current_frame"], torch.tensor([2, 3, 4, 5]))
def test_async_chunk_mode_keeps_accumulation_for_golden_dump(tmp_path):
model = _talker()
model._async_chunk = True
model._golden_dump_dir = str(tmp_path)
model._generate_depth_codes = lambda hidden, code0: _depth_codes(model, hidden, code0)
info = {"breeze_generated_frames": 0, "breeze_max_new_frames": 8}
hidden = torch.arange(16, dtype=torch.float32).reshape(1, 16)
for _ in range(3):
output = model.make_omni_output(
hidden,
model_intermediate_buffer=[info],
request_token_spans=[(0, 1)],
)
assert output.multimodal_outputs["codes"]["audio"][0].shape == (1, 4)
assert info["breeze_audio_codes"].shape == (3, 4)
assert torch.equal(info["breeze_audio_codes"][-1], torch.tensor([2, 3, 4, 5]))
def test_opt_in_golden_dump_writes_terminal_frames(tmp_path):
model = _talker()
model._golden_dump_dir = str(tmp_path)
info = {
"template": "tts_plain",
"prompt_ids": torch.tensor([1, 2, 3]),
"breeze_audio_codes": torch.tensor([[1, 2, 3, 4]]),
"breeze_generated_frames": 1,
"breeze_force_eos": True,
}
model._dump_golden_frames(info)
dump_path = next(tmp_path.glob("breeze_*.pt"))
payload = torch.load(dump_path, weights_only=False)
assert payload["template"] == "tts_plain"
assert torch.equal(payload["prompt_ids"], torch.tensor([1, 2, 3]))
assert torch.equal(payload["codes"], torch.tensor([[1, 2, 3, 4]]))
assert payload["finished"] is True
@@ -1,73 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import pytest
import torch
from torch import nn
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze_tts_2_talker import (
BreezeTTS2TalkerForGeneration,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _PaddedHead(nn.Module):
def __init__(self) -> None:
super().__init__()
self.weight = nn.Parameter(torch.zeros(2_112, 32), requires_grad=False)
class _DepthDecoder:
def __init__(self) -> None:
self.loaded: list[tuple[str, torch.Tensor]] = []
def load_weights(self, weights):
self.loaded.extend(weights)
return {"model.embed_tokens.weight"}
class _TalkerHost(nn.Module):
def __init__(self) -> None:
super().__init__()
self.config = SimpleNamespace(tie_codebooks_embeddings=True)
self.codebook_vocab_size = 2_051
self.hidden_size = 32
self.lm_head = _PaddedHead()
self.embed_audio_tokens = nn.Embedding(4 * 2_051, 32)
self.depth_decoder = _DepthDecoder()
def _padded_loader(parameter: nn.Parameter, weight: torch.Tensor) -> None:
assert parameter.shape != weight.shape
parameter[: weight.shape[0]].copy_(weight)
def test_lm_head_loader_accepts_vocab_padding():
host = _TalkerHost()
host.lm_head.weight.weight_loader = _padded_loader
checkpoint_weight = torch.full((2_052, 32), 1.5)
loaded = BreezeTTS2TalkerForGeneration.load_weights(
host,
iter([("lm_head.weight", checkpoint_weight)]),
)
assert "lm_head.weight" in loaded
assert torch.equal(host.lm_head.weight[:2_052], checkpoint_weight)
assert torch.count_nonzero(host.lm_head.weight[2_052:]).item() == 0
def test_tied_audio_embedding_reports_both_strict_loading_aliases():
host = _TalkerHost()
checkpoint_weight = torch.full((4 * 2_051, 32), 2.0)
loaded = BreezeTTS2TalkerForGeneration.load_weights(
host,
iter([("depth_decoder.model.embed_tokens.weight", checkpoint_weight)]),
)
assert "embed_audio_tokens.weight" in loaded
assert "depth_decoder.model.embed_tokens.weight" in loaded
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Weight-free Breeze talker and request helpers shared by model tests."""
from dataclasses import dataclass
from unittest.mock import Mock
import torch
from vllm_omni.model_executor.models.breeze_tts_2.first_code_sampler import BreezeFirstCodeSampler
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze import BreezeForConditionalGeneration
@dataclass
class TalkerConfig:
vocab_size: int
eos_token_id: int
@dataclass
class DepthStub:
generate_frames: Mock
def _small_talker():
model = BreezeForConditionalGeneration.__new__(BreezeForConditionalGeneration)
torch.nn.Module.__init__(model)
model._first_code_sampler = BreezeFirstCodeSampler()
model.config = TalkerConfig(vocab_size=8, eos_token_id=7)
model.num_codebooks, model.codebook_size, model.hidden_size = 3, 4, 2
model.lm_head = torch.nn.Linear(2, 8, bias=False)
with torch.no_grad():
model.lm_head.weight.zero_()
model.lm_head.weight[1] = torch.tensor([10.0, 0.0])
model.lm_head.weight[2] = torch.tensor([9.5, 0.0])
model.lm_head.weight[3] = torch.tensor([0.0, 10.0])
model.lm_head.weight[7] = torch.tensor([-10.0, -10.0])
model.depth_decoder = DepthStub(
generate_frames=Mock(side_effect=lambda hidden, first, **kwargs: first[:, None].repeat(1, 3))
)
return model
def _request_info(request_id):
return {
"global_request_id": [request_id],
"breeze_prompt": {"role": "cond", "guidance_scale": 1.0},
"breeze_sampling": {"temperature": 0.0, "top_k": 0, "top_p": 1.0, "repetition_penalty": 1.1},
"breeze_state": {
"generator": torch.Generator().manual_seed(42),
"history": torch.empty(1, 0, dtype=torch.long),
"current": torch.zeros(1, 3, dtype=torch.long),
},
}
@@ -23,7 +23,7 @@ class _FakeDecoder(nn.Module):
def __init__(self, total_upsample: int = _TOTAL_UPSAMPLE):
super().__init__()
self.total_upsample = total_upsample
self.decode_calls: list[dict[str, int | tuple[int, ...]]] = []
self.decode_calls: list[dict[str, object]] = []
self.batched_decode_calls: list[dict[str, object]] = []
self.decode_codes: list[torch.Tensor] = []
self.cudagraph_calls: list[dict[str, int | torch.device]] = []
@@ -91,8 +91,8 @@ class _FakeDecoder(nn.Module):
wav_len = frames * self.total_upsample + 6
wav = torch.arange(wav_len, dtype=torch.float32).view(1, 1, -1)
offsets = torch.arange(batch, dtype=torch.float32).view(batch, 1, 1) * 1000
batched_wav = wav.expand(batch, 1, wav_len) + offsets
return [batched_wav[row, :, : lengths[row] * self.total_upsample] for row in range(batch)]
batched_outputs = wav.expand(batch, 1, wav_len) + offsets
return [batched_outputs[row, :, : lengths[row] * self.total_upsample] for row in range(batch)]
def enable_cudagraph(self, **kwargs):
self.cudagraph_calls.append(kwargs)
@@ -577,6 +577,7 @@ def test_decode_chunking_override_is_passed_to_cudagraph():
_load_weights_noop(model)
assert model.decoder.cudagraph_calls[-1] == {
"capture_modes": ("icl", "xvec"),
"capture_batch_sizes": None,
"stateless_capture_sizes": None,
"device": torch.device("cuda"),
@@ -607,6 +608,13 @@ def test_cudagraph_batch_config_is_preserved():
assert call["capture_batch_sizes"] == [1, 2, 4, 8]
def test_cudagraph_captures_only_modes_used_by_the_model():
model = _make_model(async_chunk=True, device=torch.device("cuda"))
model.decoder_cudagraph_modes = ("xvec",)
_load_weights_noop(model)
assert model.decoder.cudagraph_calls[-1]["capture_modes"] == ("xvec",)
def test_stateless_cudagraph_capture_sizes_are_passed_from_config():
model = _make_model(
async_chunk=False,
@@ -0,0 +1,459 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Check depth attention against HF Llama and request-local sampling state."""
from collections import defaultdict
import numpy as np
import pytest
import torch
import torch.nn.functional as F
from transformers import LlamaConfig, MimiConfig
from transformers.models.llama.modeling_llama import LlamaModel
from transformers.models.mimi.modeling_mimi import MimiConv1d
from tests.model_executor.models.breeze_tts_2_helpers import _request_info, _small_talker
from vllm_omni.engine.serialization import deserialize_additional_information, serialize_additional_information
from vllm_omni.model_executor.models.breeze_tts_2.depth_decoder import (
BreezeDepthDecoder,
sample_graph_logits,
sample_logits,
)
from vllm_omni.model_executor.models.breeze_tts_2.prompt import CFG_UNCOND_SUFFIX, build_breeze_prompt
from vllm_omni.model_executor.models.breeze_tts_2.reference_encoder import BreezeReferenceConv
from vllm_omni.model_executor.stage_input_processors.breeze_tts_2 import expand_cfg_prompts, talker2code2wav_async_chunk
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@pytest.mark.parametrize("parameter", ["temperature", "top_p", "repetition_penalty"])
@pytest.mark.parametrize("value", [float("nan"), float("inf")])
def test_offline_prompt_rejects_nonfinite_sampling(mocker, parameter, value):
with pytest.raises(ValueError, match="sampling"):
build_breeze_prompt(mocker.Mock(), "Hello.", **{parameter: value})
@pytest.mark.parametrize("stride", [2, 3])
def test_reference_convolution_batch_preserves_partial_final_frames(stride):
torch.manual_seed(7)
config = MimiConfig(use_causal_conv=True, pad_mode="constant")
first = MimiConv1d(config, 1, 4, kernel_size=2 * stride, stride=stride)
last = MimiConv1d(config, 4, 4, kernel_size=4, stride=2, pad_mode="replicate", bias=False)
batched_first = BreezeReferenceConv(first, stride)
batched_last = BreezeReferenceConv(last, 2 * stride)
lengths = torch.tensor([37, 62])
batched_first.sample_lengths = batched_last.sample_lengths = lengths
waves = [torch.randn(1, 1, int(length)) for length in lengths]
padded = torch.cat([F.pad(wave, (0, 96 - wave.shape[-1])) for wave in waves])
actual = batched_last(batched_first(padded))
for row, waveform in enumerate(waves):
expected = last(first(waveform))
torch.testing.assert_close(actual[row : row + 1, :, : expected.shape[-1]], expected)
assert not actual[row, :, expected.shape[-1] :].count_nonzero()
def test_reference_conditioning_survives_transport_and_cfg_keeps_true_prompt_lengths(mocker) -> None:
tokenizer = mocker.Mock()
tokenizer.encode.side_effect = lambda text, **kwargs: list(text.encode("utf-8"))
waveform = np.zeros(1921, dtype=np.float32)
prompt = build_breeze_prompt(
tokenizer, "Hello.", "Speak happily.", ref_audio=(waveform, 24000), ref_text="Reference.", guidance_scale=4
)
companion = expand_cfg_prompts(prompt, None)[0].prompt
assert len(prompt["prompt_token_ids"]) > len(companion["prompt_token_ids"])
positive = deserialize_additional_information(serialize_additional_information(prompt["additional_information"]))
negative = deserialize_additional_information(serialize_additional_information(companion["additional_information"]))
assert positive["breeze_prompt"]["target_ids"] == list(b"[S0]<ins_bos>Speak happily.<ins_eos>Hello.")
assert negative["breeze_prompt"]["target_ids"] == list(b"[S0]Hello.")
assert positive["breeze_prompt"]["reference_ids"] == negative["breeze_prompt"]["reference_ids"]
assert positive["breeze_prompt"]["reference_frames"] == 2
assert positive["cfg_group"]["role"] == "cond"
assert negative["cfg_group"]["role"] == "uncond"
torch.testing.assert_close(positive["reference_waveform"], torch.from_numpy(waveform), atol=0, rtol=0)
torch.testing.assert_close(negative["reference_waveform"], positive["reference_waveform"], atol=0, rtol=0)
@pytest.fixture
def depth() -> BreezeDepthDecoder:
torch.manual_seed(42)
config = LlamaConfig(
hidden_size=32,
intermediate_size=64,
num_hidden_layers=2,
num_attention_heads=4,
num_key_value_heads=2,
head_dim=8,
vocab_size=35,
max_position_embeddings=16,
rope_theta=500000.0,
rope_scaling={
"rope_type": "llama3",
"factor": 32.0,
"low_freq_factor": 0.001953125,
"high_freq_factor": 0.0078125,
"original_max_position_embeddings": 16,
},
)
config.num_codebooks = 4
config.audio_embed_size = 48
config._attn_implementation = "eager"
model = BreezeDepthDecoder(config).eval()
torch.nn.init.normal_(model.codebooks_head, std=0.1)
return model
@pytest.fixture
def hf_depth(depth: BreezeDepthDecoder) -> LlamaModel:
reference = LlamaModel(depth.config).eval()
weights = {}
for name, parameter in depth.named_parameters():
if name.startswith("layers."):
if name.endswith("qkv.weight"):
layer = depth.layers[int(name.split(".")[1])]
for part, tensor in zip(
("q", "k", "v"), parameter.split([layer.q_size, layer.kv_size, layer.kv_size]), strict=True
):
weights[name.replace("qkv.weight", f"self_attn.{part}_proj.weight")] = tensor
elif name.endswith("gate_up.weight"):
for part, tensor in zip(("gate", "up"), parameter.chunk(2), strict=True):
weights[name.replace("gate_up.weight", f"mlp.{part}_proj.weight")] = tensor
else:
weights[name.replace(".o_proj.", ".self_attn.o_proj.").replace(".down_proj.", ".mlp.down_proj.")] = (
parameter
)
elif name == "norm.weight":
weights[name] = parameter
result = reference.load_state_dict(weights, strict=False)
assert result.missing_keys == ["embed_tokens.weight"]
assert result.unexpected_keys == []
return reference
@pytest.mark.parametrize("batch", [1, 2])
@torch.inference_mode()
def test_depth_cached_attention_matches_hf_full_sequence(
depth: BreezeDepthDecoder, hf_depth: LlamaModel, batch: int
) -> None:
"""Wrong cache positions or fused-QKV ordering change the reference hidden states."""
embeddings = torch.randn(batch, 4, depth.config.hidden_size)
expected = hf_depth(inputs_embeds=embeddings, use_cache=False).last_hidden_state
shape = (batch, 2, 4, 8)
caches = [(torch.zeros(shape), torch.zeros(shape)) for _ in depth.layers]
actual = []
for start, end in [(0, 2), (2, 3), (3, 4)]:
x = embeddings[:, start:end]
for layer, cache in zip(depth.layers, caches, strict=True):
x = layer(
x,
depth.positions[start:end],
cache,
depth.causal_mask[start:end, :4][None, None],
depth.rope_cos[start:end][None, None],
depth.rope_sin[start:end][None, None],
)
actual.append(depth.norm(x))
torch.testing.assert_close(torch.cat(actual, 1), expected, atol=2e-6, rtol=2e-5)
@pytest.mark.parametrize("guidance_scale", [0.5, 4.0])
@torch.inference_mode()
def test_cfg_depth_matches_hf_full_prefix_at_every_codebook(
depth: BreezeDepthDecoder, hf_depth: LlamaModel, guidance_scale: float
) -> None:
hidden = torch.randn(4, depth.config.audio_embed_size)
first = torch.tensor([7, 11])
prefix = torch.cat((hidden[:, None], depth.embed_tokens(first.repeat(2)[:, None])), dim=1)
expected = [first]
for codebook in range(depth.num_codebooks - 1):
states = hf_depth(inputs_embeds=depth.inputs_embeds_projector(prefix), use_cache=False).last_hidden_state
logits = F.linear(states[:, -1].float(), depth.codebooks_head[codebook].T)
cond, uncond = logits.chunk(2)
token = (uncond + guidance_scale * (cond - uncond))[:, : depth.vocab_size - 3].argmax(-1)
expected.append(token)
embed = depth.embed_tokens((token.repeat(2) + (codebook + 1) * depth.vocab_size)[:, None])
prefix = torch.cat((prefix, embed), dim=1)
actual = depth._generate_frame(
hidden, first, depth._allocate_cache(hidden), 0.0, 0, 1.0, torch.Generator(), guidance_scale=guidance_scale
)
torch.testing.assert_close(actual, torch.stack(expected, dim=-1), atol=0, rtol=0)
@torch.inference_mode()
def test_frame_generation_does_not_reuse_another_requests_cache(depth: BreezeDepthDecoder) -> None:
hidden = torch.randn(1, 48)
first = torch.tensor([7])
def generate(value: torch.Tensor) -> torch.Tensor:
return depth.generate_frame(
value, first, temperature=0.9, top_k=10, top_p=0.8, generator=torch.Generator().manual_seed(99)
)
expected = generate(hidden)
generate(hidden * -3)
torch.testing.assert_close(generate(hidden), expected, atol=0, rtol=0)
def test_top_k_keeps_tied_candidates_and_nucleus_keeps_crossing_token() -> None:
generator = torch.Generator().manual_seed(42)
logits = torch.tensor([[0.0, 0.0, -100.0]]).expand(128, -1)
sampled = sample_logits(logits, 1.0, 1, 1.0, generator)
assert set(sampled.tolist()) == {0, 1}
sampled = sample_logits(torch.tensor([[10.0, 0.0]]), 1.0, 0, 0.1, generator)
assert sampled.item() == 0
@pytest.mark.parametrize("parameters", [(0.0, 0, 1.0), (0.9, 50, 1.0), (0.7, 100, 0.8), (1.2, 0, 0.95)])
def test_graph_sampler_matches_scalar_filtering_with_identical_noise(parameters) -> None:
generator = torch.Generator().manual_seed(42)
logits = torch.randn(32, 2051, generator=generator)
logits[:, -3:] = -torch.inf
logits[:, 10:14] = 4.0
noise = torch.empty_like(logits).exponential_(generator=generator)
expected = sample_logits(logits, *parameters, generator, noise)
actual = sample_graph_logits(logits, torch.tensor(parameters), noise)
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
@pytest.mark.parametrize("top_p", [0.25, 0.5, 0.75])
@pytest.mark.parametrize("top_k", [0, 2])
def test_graph_sampler_excludes_token_after_exact_nucleus_boundary(top_p, top_k) -> None:
# Equal probabilities make the CDF boundary exact in float32. Bias the
# shared noise toward the first token outside that nucleus so retaining
# one extra candidate deterministically changes the sampled token.
logits = torch.tensor([[0.0, 0.0, 0.0, 0.0, -torch.inf, -torch.inf, -torch.inf]])
noise = torch.ones_like(logits)
first_excluded = int(top_p * 4)
noise[:, first_excluded] = 0.001
parameters = (1.0, top_k, top_p)
expected = sample_logits(logits, *parameters, torch.Generator().manual_seed(42), noise)
actual = sample_graph_logits(logits, torch.tensor(parameters), noise)
assert expected.item() != first_excluded
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
def test_codec_chunks_preserve_zero_frames_and_flush_each_request_once(mocker) -> None:
manager = mocker.Mock()
manager.connector.config = {"extra": {"codec_chunk_frames": 5, "initial_codec_chunk_frames": 5}}
manager.code_prompt_token_ids = defaultdict(list)
manager.request_payload = {}
first = mocker.Mock(external_req_id="first")
first.is_finished.return_value = False
second = mocker.Mock(external_req_id="second")
second.is_finished.return_value = False
zeros = {"codes": {"audio": torch.zeros(3, 16, dtype=torch.long)}}
assert talker2code2wav_async_chunk(manager, zeros, first) is None
assert talker2code2wav_async_chunk(manager, zeros, second) is None
codes = torch.arange(64).reshape(4, 16)
output = talker2code2wav_async_chunk(manager, {"codes": {"audio": codes}}, first)
expected = torch.cat((torch.zeros(3, 16, dtype=torch.long), codes))
torch.testing.assert_close(output.codes.audio.reshape(16, -1).T, expected)
final = talker2code2wav_async_chunk(manager, None, first, is_finished=True)
assert final.codes.audio.numel() == 0
assert final.meta.finished.item()
other = talker2code2wav_async_chunk(manager, None, second, is_finished=True)
torch.testing.assert_close(other.codes.audio, torch.zeros(48, dtype=torch.long))
def test_codec_chunk_ramp_keeps_frame_order_and_flushes_partial_tail(mocker) -> None:
manager = mocker.Mock()
manager.connector.config = {"extra": {"codec_chunk_frames": 5, "codec_chunk_ramp": [1, 2, 4, 5]}}
manager.code_prompt_token_ids = defaultdict(list)
manager.request_payload = {}
request = mocker.Mock(external_req_id="ramp")
request.is_finished.return_value = False
outputs = []
for index in range(15):
result = talker2code2wav_async_chunk(manager, {"codes": {"audio": torch.full((1, 16), index)}}, request)
if result is not None:
outputs.append(result.codes.audio.reshape(16, -1).T)
tail = talker2code2wav_async_chunk(manager, None, request, is_finished=True)
outputs.append(tail.codes.audio.reshape(16, -1).T)
assert [len(part) for part in outputs] == [1, 2, 4, 5, 3]
torch.testing.assert_close(torch.cat(outputs), torch.arange(15)[:, None].expand(-1, 16))
assert tail.meta.finished.item()
@torch.inference_mode()
def test_talker_history_and_generators_follow_requests_across_reordering_and_new_admission():
model = _small_talker()
first, second = _request_info("first"), _request_info("second")
# Unequal spans must select the last hidden row of each request.
model.make_omni_output(
torch.tensor([[0.0, 1.0], [0.0, 1.0], [1.0, 0.0], [0.0, 1.0]]),
model_intermediate_buffer=[first, second],
request_token_spans=[(0, 3), (3, 4)],
)
reordered = model.make_omni_output(
torch.tensor([[0.0, 1.0], [1.0, 0.0]]),
model_intermediate_buffer=[second, first],
request_token_spans=[(0, 1), (1, 2)],
)
assert [frame[0, 0].item() for frame in reordered.multimodal_outputs["codes"]["audio"]] == [3, 2]
assert first["breeze_state"]["history"].tolist() == [[1, 2]]
assert second["breeze_state"]["history"].tolist() == [[3, 3]]
generators = model.depth_decoder.generate_frames.call_args.kwargs["generators"]
assert generators[0] is second["breeze_state"]["generator"]
assert generators[1] is first["breeze_state"]["generator"]
newcomer = _request_info("newcomer")
admitted = model.make_omni_output(
torch.tensor([[1.0, 0.0], [1.0, 0.0]]),
model_intermediate_buffer=[newcomer, first],
request_token_spans=[(0, 1), (1, 2)],
)
assert [frame[0, 0].item() for frame in admitted.multimodal_outputs["codes"]["audio"]] == [1, 1]
assert newcomer["breeze_state"]["history"].tolist() == [[1]]
assert first["breeze_state"]["history"].tolist() == [[1, 2, 1]]
assert second["breeze_state"]["history"].tolist() == [[3, 3]]
@torch.inference_mode()
def test_talker_eos_does_not_shift_live_state_or_poison_the_next_batch():
model = _small_talker()
finished, live = _request_info("finished"), _request_info("live")
output = model.make_omni_output(
torch.tensor([[-1.0, -1.0], [0.0, 1.0]]),
model_intermediate_buffer=[finished, live],
request_token_spans=[(0, 1), (1, 2)],
)
assert output.multimodal_outputs["codes"]["audio"][0].numel() == 0
assert finished["breeze_state"]["history"].numel() == 0
assert live["breeze_state"]["current"].tolist() == [[3, 3, 3]]
assert model.compute_logits(torch.zeros(2, 2)).argmax(-1).tolist() == [7, 0]
model.make_omni_output(torch.tensor([[0.0, 1.0]]), model_intermediate_buffer=[live], request_token_spans=[(0, 1)])
assert live["breeze_state"]["history"].tolist() == [[3, 3]]
assert model.compute_logits(torch.zeros(1, 2)).argmax(-1).tolist() == [0]
@pytest.mark.parametrize("temperature", [0.0, 0.9])
@torch.inference_mode()
def test_talker_mixed_eos_and_cfg_matches_independent_request_state(temperature):
"""Batch EOS decisions must not mix CFG state or change a request's RNG stream."""
finished_companion = "finished_cfg" + CFG_UNCOND_SUFFIX
live_companion = "live_cfg" + CFG_UNCOND_SUFFIX
order = ["finished_cfg", "live_plain", finished_companion, "finished_plain", live_companion, "live_cfg"]
hidden_by_id = {
"finished_cfg": [-1.0, -1.0],
finished_companion: [-1.0, -1.0],
"finished_plain": [-1.0, -1.0],
"live_plain": [0.0, 1.0],
"live_cfg": [1.0, 0.0],
live_companion: [1.0, 0.0],
}
def make_infos():
infos = {request_id: _request_info(request_id) for request_id in order}
for index, (request_id, info) in enumerate(infos.items()):
info["breeze_sampling"].update(temperature=temperature, top_k=1)
if "cfg" not in request_id:
# Interleave distinct sampler groups while each request still
# matches its independently decoded state and RNG trajectory.
info["breeze_sampling"].update(temperature=temperature * 0.5, top_p=0.8)
info["breeze_state"]["generator"].manual_seed(42 + index)
info["breeze_state"]["history"] = torch.tensor([[3, 3]])
if "cfg" in request_id:
info["breeze_prompt"]["guidance_scale"] = 4.0
if request_id.endswith(CFG_UNCOND_SUFFIX):
info["breeze_prompt"]["role"] = "uncond"
return infos
def sample_depth(hidden, first, *, temperature, generators, **kwargs):
# Consume request-local RNG state in the downstream phase as well as
# codebook-0 sampling, without requiring depth CUDA graphs on CPU.
tail = (
torch.stack([torch.randint(0, 4, (2,), generator=generator) for generator in generators])
if temperature > 0
else first[:, None].expand(-1, 2)
)
return torch.cat((first[:, None], tail), dim=1)
batched, independent = _small_talker(), _small_talker()
batched.depth_decoder.generate_frames.side_effect = sample_depth
independent.depth_decoder.generate_frames.side_effect = sample_depth
batch_infos, independent_infos = make_infos(), make_infos()
initial_rng = {rid: info["breeze_state"]["generator"].get_state() for rid, info in batch_infos.items()}
saved_frames = []
# The second step drops both EOS requests and moves the live CFG companion
# ahead of its parent, exercising shrinking and reordered batches.
for step_ids in (order, [live_companion, "live_cfg", "live_plain"]):
expected_audio, expected_tokens = {}, {}
for request_id in step_ids:
if request_id.endswith(CFG_UNCOND_SUFFIX):
continue
pair = [request_id]
if independent_infos[request_id]["breeze_prompt"]["guidance_scale"] != 1.0:
pair.append(request_id + CFG_UNCOND_SUFFIX)
result = independent.make_omni_output(
torch.tensor([hidden_by_id[rid] for rid in pair]),
model_intermediate_buffer=[independent_infos[rid] for rid in pair],
request_token_spans=[(i, i + 1) for i in range(len(pair))],
)
tokens = independent.compute_logits(torch.zeros(len(pair), 2)).argmax(-1)
for index, rid in enumerate(pair):
expected_audio[rid] = result.multimodal_outputs["codes"]["audio"][index].clone()
expected_tokens[rid] = tokens[index]
result = batched.make_omni_output(
torch.tensor([hidden_by_id[rid] for rid in step_ids]),
model_intermediate_buffer=[batch_infos[rid] for rid in step_ids],
request_token_spans=[(i, i + 1) for i in range(len(step_ids))],
)
actual_tokens = batched.compute_logits(torch.zeros(len(step_ids), 2)).argmax(-1)
for index, rid in enumerate(step_ids):
actual = result.multimodal_outputs["codes"]["audio"][index]
torch.testing.assert_close(actual, expected_audio[rid], atol=0, rtol=0)
torch.testing.assert_close(actual_tokens[index], expected_tokens[rid], atol=0, rtol=0)
saved_frames.append((actual, actual.clone()))
for rid in order:
actual, expected = batch_infos[rid]["breeze_state"], independent_infos[rid]["breeze_state"]
for key in ("history", "current"):
torch.testing.assert_close(actual[key], expected[key], atol=0, rtol=0)
torch.testing.assert_close(actual["generator"].get_state(), expected["generator"].get_state())
# Unconditional companions never own sampling; an EOS request consumes
# only codebook-0 sampling, with no downstream depth draws.
for rid in (finished_companion, live_companion):
torch.testing.assert_close(batch_infos[rid]["breeze_state"]["generator"].get_state(), initial_rng[rid])
for rid in ("finished_cfg", "finished_plain"):
reference_generator = torch.Generator().set_state(initial_rng[rid])
sampling = batch_infos[rid]["breeze_sampling"]
sample_logits(
torch.zeros(1, batched.config.vocab_size),
sampling["temperature"],
sampling["top_k"],
sampling["top_p"],
reference_generator,
)
torch.testing.assert_close(
batch_infos[rid]["breeze_state"]["generator"].get_state(), reference_generator.get_state()
)
for rid in ("finished_cfg", finished_companion, "finished_plain", live_companion):
assert batch_infos[rid]["breeze_state"]["history"].tolist() == [[3, 3]]
assert batch_infos["live_cfg"]["breeze_state"]["history"].tolist() == [[3, 3, 1, 2]]
assert batch_infos["live_plain"]["breeze_state"]["history"].tolist() == [[3, 3, 3, 3]]
assert batch_infos["live_cfg"]["breeze_state"]["current"] is batch_infos[live_companion]["breeze_state"]["current"]
for original, snapshot in saved_frames:
torch.testing.assert_close(original, snapshot, atol=0, rtol=0)
@torch.inference_mode()
def test_talker_all_eos_preserves_history_and_skips_depth():
model = _small_talker()
infos = [_request_info("first"), _request_info("second")]
for info in infos:
info["breeze_state"]["history"] = torch.tensor([[1, 2]])
output = model.make_omni_output(
torch.tensor([[-1.0, -1.0], [-2.0, -2.0]]),
model_intermediate_buffer=infos,
request_token_spans=[(0, 1), (1, 2)],
)
model.depth_decoder.generate_frames.assert_not_called()
assert model.compute_logits(torch.zeros(2, 2)).argmax(-1).tolist() == [7, 7]
assert all(frame.numel() == 0 for frame in output.multimodal_outputs["codes"]["audio"])
for info in infos:
assert info["breeze_state"]["history"].tolist() == [[1, 2]]
assert info["breeze_state"]["current"].tolist() == [[0, 0, 0]]
@@ -0,0 +1,184 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Check only the caller's grouping seam, with the existing weight-free fixture."""
import pytest
import torch
import torch.nn.functional as F
from tests.model_executor.models.breeze_tts_2_helpers import _request_info, _small_talker
from vllm_omni.model_executor.models.breeze_tts_2.depth_decoder import sample_logits
from vllm_omni.model_executor.models.breeze_tts_2.prompt import CFG_UNCOND_SUFFIX
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@torch.inference_mode()
def test_first_code_groups_preserve_logits_request_order_cfg_and_eos(monkeypatch):
"""Grouping changes dispatch only; head/history arithmetic and routing stay exact."""
model = _small_talker()
cfg_a, cfg_b = "cfg_a" + CFG_UNCOND_SUFFIX, "cfg_b" + CFG_UNCOND_SUFFIX
order = ["plain_a", "plain_b", cfg_a, "finished_a", "cfg_a", "cfg_b", cfg_b, "plain_c"]
tuples = {"a": (0.9, 3, 1.0), "b": (0.7, 2, 0.8), "c": (0.0, 0, 1.0)}
chosen = {"plain_a": 1, "plain_b": 2, "finished_a": 7, "cfg_a": 3, "cfg_b": 0, "plain_c": 2}
hidden = {
"plain_a": [1.0, -0.5],
"plain_b": [-0.5, 1.0],
cfg_a: [0.25, 0.5],
"finished_a": [-1.0, -1.0],
"cfg_a": [0.75, -0.5],
"cfg_b": [0.5, 0.75],
cfg_b: [-0.25, 0.5],
"plain_c": [0.5, -0.75],
}
infos = {rid: _request_info(rid) for rid in order}
for index, (rid, info) in enumerate(infos.items()):
parent = rid.removesuffix(CFG_UNCOND_SUFFIX)
parameters = tuples[parent[-1]]
info["breeze_sampling"].update(
temperature=parameters[0],
top_k=parameters[1],
top_p=parameters[2],
repetition_penalty=2.0 if parent.startswith("cfg") else 1.25,
)
info["breeze_state"]["generator"].manual_seed(100 + index)
info["breeze_state"]["history"] = torch.tensor([[1, 3, 1]])
if parent.startswith("cfg"):
info["breeze_prompt"]["guidance_scale"] = 4.0 if parent == "cfg_a" else 2.0
if rid.endswith(CFG_UNCOND_SUFFIX):
info["breeze_prompt"]["role"] = "uncond"
original_states = {
rid: {key: value.clone() for key, value in info["breeze_state"].items() if key in ("history", "current")}
for rid, info in infos.items()
}
request_for_generator = {id(info["breeze_state"]["generator"]): rid for rid, info in infos.items()}
class RecordingSampler:
def __init__(self):
self.calls = []
def sample(self, logits, parameters, generators):
ids = [request_for_generator[id(generator)] for generator in generators]
self.calls.append((parameters, ids, [row.clone() for row in logits]))
return [torch.tensor([chosen[rid]], dtype=torch.long) for rid in ids]
recorder = RecordingSampler()
model._first_code_sampler = recorder
# Build the scalar pre-grouping reference before spying on actual head
# calls. Both positive and negative repeated logits exercise the penalty.
expected_logits = {}
for rid in chosen:
row = torch.tensor([hidden[rid]], dtype=torch.bfloat16).float()
scores = F.linear(row, model.lm_head.weight)
scale = infos[rid]["breeze_prompt"]["guidance_scale"]
if scale != 1.0:
uncond = F.linear(
torch.tensor([hidden[rid + CFG_UNCOND_SUFFIX]], dtype=torch.bfloat16).float(), model.lm_head.weight
)
scores = uncond + scale * (scores - uncond)
scores[:, model.codebook_size : model.config.eos_token_id] = -torch.inf
history = infos[rid]["breeze_state"]["history"]
selected = scores.gather(1, history)
penalty = infos[rid]["breeze_sampling"]["repetition_penalty"]
scores.scatter_(1, history, torch.where(selected < 0, selected * penalty, selected / penalty))
expected_logits[rid] = scores
actual_linear, head_inputs = F.linear, []
def scalar_head(inputs, weight, bias=None):
assert inputs.dtype == torch.float32 and inputs.shape == (1, model.hidden_size)
assert weight is model.lm_head.weight
head_inputs.append(inputs.clone())
return actual_linear(inputs, weight, bias)
# Prefill-like unequal spans must still select each physical row's last
# hidden state, including a CFG companion preceding its parent.
flat_hidden: list[list[float]] = []
spans: list[tuple[int, int]] = []
for index, rid in enumerate(order):
start = len(flat_hidden)
flat_hidden.extend([[-99.0, -99.0]] * (index % 3))
flat_hidden.append(hidden[rid])
spans.append((start, len(flat_hidden)))
values = torch.tensor(flat_hidden, dtype=torch.bfloat16)
with monkeypatch.context() as head_guard:
head_guard.setattr(F, "linear", scalar_head)
output = model.make_omni_output(
values, model_intermediate_buffer=[infos[rid] for rid in order], request_token_spans=spans
)
assert output.text_hidden_states is values
assert len(head_inputs) == len(chosen) + 2
assert [(parameters, ids) for parameters, ids, _ in recorder.calls] == [
(tuples["a"], ["plain_a", "finished_a", "cfg_a"]),
(tuples["b"], ["plain_b", "cfg_b"]),
(tuples["c"], ["plain_c"]),
]
for _, ids, rows in recorder.calls:
for rid, row in zip(ids, rows, strict=True):
assert row.dtype == torch.float32 and row.shape == (1, model.config.vocab_size)
torch.testing.assert_close(row, expected_logits[rid], atol=0, rtol=0)
# Depth group creation must follow original conditioned order, not the
# first-code sampler's grouped traversal (A,A,A,B,B,C).
live_order = ["plain_a", "plain_b", "cfg_a", "cfg_b", "plain_c"]
calls = model.depth_decoder.generate_frames.call_args_list
assert len(calls) == len(live_order)
for call, rid in zip(calls, live_order, strict=True):
depth_hidden, first = call.args
scale = infos[rid]["breeze_prompt"]["guidance_scale"]
expected_hidden = [hidden[rid]] + ([hidden[rid + CFG_UNCOND_SUFFIX]] if scale != 1 else [])
torch.testing.assert_close(depth_hidden, torch.tensor(expected_hidden, dtype=torch.bfloat16), atol=0, rtol=0)
assert first.tolist() == [chosen[rid]]
assert call.kwargs["generators"] == [infos[rid]["breeze_state"]["generator"]]
assert call.kwargs["guidance_scale"] == scale
assert tuple(call.kwargs[key] for key in ("temperature", "top_k", "top_p")) == tuples[rid[-1]]
assert model.compute_logits(torch.zeros(len(order), 2)).argmax(-1).tolist() == [0, 0, 0, 7, 0, 0, 0, 0]
for index, rid in enumerate(order):
state = infos[rid]["breeze_state"]
audio = output.multimodal_outputs["codes"]["audio"][index]
if rid in live_order:
assert state["history"].tolist() == [[1, 3, 1, chosen[rid]]]
assert audio.tolist() == [[chosen[rid]] * 3]
else:
assert audio.numel() == 0
torch.testing.assert_close(state["history"], original_states[rid]["history"], atol=0, rtol=0)
torch.testing.assert_close(
infos["finished_a"]["breeze_state"]["current"], original_states["finished_a"]["current"], atol=0, rtol=0
)
for parent, companion in (("cfg_a", cfg_a), ("cfg_b", cfg_b)):
assert infos[parent]["breeze_state"]["current"] is infos[companion]["breeze_state"]["current"]
@torch.inference_mode()
def test_first_code_shared_generator_preserves_order_across_parameter_groups(monkeypatch):
"""A shared RNG stream must retain A,B,A request order across tuple groups."""
model = _small_talker()
infos = [_request_info(rid) for rid in ("first_a", "middle_b", "last_a")]
parameters = [(0.9, 3, 1.0), (0.7, 2, 0.8), (0.9, 3, 1.0)]
actual_generator = torch.Generator().manual_seed(2**63 + 19)
expected_generator = torch.Generator().manual_seed(2**63 + 19)
hidden = torch.tensor([[0.2, 0.0], [0.0, 0.2], [0.1, 0.2]], dtype=torch.bfloat16)
expected = []
for index, (info, values) in enumerate(zip(infos, parameters, strict=True)):
info["breeze_sampling"].update(temperature=values[0], top_k=values[1], top_p=values[2])
info["breeze_state"]["generator"] = actual_generator
logits = F.linear(hidden[index : index + 1].float(), model.lm_head.weight)
logits[:, model.codebook_size : model.config.eos_token_id] = -torch.inf
expected.append(sample_logits(logits, *values, expected_generator))
assert all(token.item() != model.config.eos_token_id for token in expected)
sample, calls = model._first_code_sampler.sample, []
def record_sample(logits, settings, generators):
calls.append((settings, len(logits), [generator is actual_generator for generator in generators]))
return sample(logits, settings, generators)
monkeypatch.setattr(model._first_code_sampler, "sample", record_sample)
output = model.make_omni_output(
hidden, model_intermediate_buffer=infos, request_token_spans=[(0, 1), (1, 2), (2, 3)]
)
assert calls == [(values, 1, [True]) for values in parameters]
torch.testing.assert_close(actual_generator.get_state(), expected_generator.get_state(), atol=0, rtol=0)
for info, token, audio in zip(infos, expected, output.multimodal_outputs["codes"]["audio"], strict=True):
torch.testing.assert_close(audio, token[:, None].repeat(1, 3), atol=0, rtol=0)
torch.testing.assert_close(info["breeze_state"]["history"], token[:, None], atol=0, rtol=0)
assert info["breeze_state"]["generator"] is actual_generator
@@ -0,0 +1,307 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""First-code sampler token, RNG, graph-cache and fallback contracts."""
import pytest
import torch
from tests.helpers.mark import hardware_test
from vllm_omni.model_executor.models.breeze_tts_2.depth_decoder import sample_logits
from vllm_omni.model_executor.models.breeze_tts_2.first_code_sampler import BreezeFirstCodeSampler
pytestmark = [pytest.mark.core_model]
PARAMETERS = (0.9, 50, 1.0)
VOCAB = 2052
EOS = 2051
def _logits(count: int, *, device: str, seed: int = 100, vocab: int = VOCAB) -> list[torch.Tensor]:
source = torch.Generator(device="cpu").manual_seed(seed)
values = torch.randn((count, vocab), generator=source, dtype=torch.float32) * 3
if vocab == VOCAB:
values[:, 2048:EOS] = -torch.inf
return list(values.to(device).split(1))
def _generators(seeds: list[int], device: str) -> list[torch.Generator]:
return [torch.Generator(device=device).manual_seed(seed) for seed in seeds]
def _reference(rows, parameters, generators) -> list[torch.Tensor]:
return [sample_logits(row, *parameters, generator) for row, generator in zip(rows, generators, strict=True)]
def _assert_outputs(actual, expected, rows) -> None:
assert isinstance(actual, list) and len(actual) == len(expected)
for result, reference, row in zip(actual, expected, rows, strict=True):
assert result.shape == (1,) and result.dtype == torch.long and result.device == row.device
torch.testing.assert_close(result, reference, atol=0, rtol=0)
def _assert_states(actual, expected) -> None:
for left, right in zip(actual, expected, strict=True):
torch.testing.assert_close(left.get_state(), right.get_state(), atol=0, rtol=0)
@pytest.mark.cpu
def test_empty_and_cpu_sampling_preserve_eager_contract() -> None:
sampler = BreezeFirstCodeSampler()
assert sampler.sample([], PARAMETERS, []) == []
assert not sampler._graphs
rows = _logits(3, device="cpu")
before = [row.clone() for row in rows]
actual_generators = _generators([42, 2**63 + 19, 2**64 - 23], "cpu")
expected_generators = _generators([42, 2**63 + 19, 2**64 - 23], "cpu")
for parameters in (PARAMETERS, (0.7, 20, 0.8), (1.2, 0, 0.95)):
expected = _reference(rows, parameters, expected_generators)
_assert_outputs(sampler.sample(rows, parameters, actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
for row, original in zip(rows, before, strict=True):
torch.testing.assert_close(row, original, atol=0, rtol=0)
assert not sampler._graphs
@pytest.mark.cpu
def test_greedy_never_samples_or_advances_rng(monkeypatch) -> None:
sampler = BreezeFirstCodeSampler()
rows = _logits(3, device="cpu")
generators = _generators([42, 43, 44], "cpu")
original_states = [generator.get_state().clone() for generator in generators]
def unexpected_multinomial(*_args, **_kwargs):
raise AssertionError("Greedy sampling must not call multinomial")
monkeypatch.setattr(torch, "multinomial", unexpected_multinomial)
for parameters in ((0.0, 0, 1.0), (0.0, 1, 0.2)):
_assert_outputs(sampler.sample(rows, parameters, generators), [row.argmax(-1) for row in rows], rows)
for generator, state in zip(generators, original_states, strict=True):
torch.testing.assert_close(generator.get_state(), state, atol=0, rtol=0)
assert not sampler._graphs
@pytest.mark.cpu
@pytest.mark.parametrize(
"invalid", ["extra_generator", "missing_generator", "empty_rows", "multirow", "mixed_vocab", "mixed_dtype"]
)
def test_invalid_batch_fails_before_advancing_any_generator(invalid: str) -> None:
sampler = BreezeFirstCodeSampler()
rows = _logits(2, device="cpu")
generators = _generators([42, 43], "cpu")
if invalid == "extra_generator":
rows = rows[:1]
elif invalid == "missing_generator":
generators = generators[:1]
elif invalid == "empty_rows":
rows = []
elif invalid == "multirow":
rows[1] = rows[1].expand(2, -1)
elif invalid == "mixed_vocab":
rows[1] = rows[1][:, :-1]
else:
rows[1] = rows[1].double()
states = [generator.get_state().clone() for generator in generators]
with pytest.raises(ValueError):
sampler.sample(rows, PARAMETERS, generators)
for generator, state in zip(generators, states, strict=True):
torch.testing.assert_close(generator.get_state(), state, atol=0, rtol=0)
assert not sampler._graphs
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_cuda_replay_preserves_tokens_rng_and_owned_outputs(monkeypatch) -> None:
sampler = BreezeFirstCodeSampler()
seeds = list(range(42, 77)) + [2**63 + 19, 2**64 - 23, 101]
actual_generators = _generators(seeds, "cuda")
expected_generators = _generators(seeds, "cuda")
# Torch also accepts an unspecified CUDA index. Exercise it alongside the
# indexed generators created from serving request tensors.
indexed_device = torch.device("cuda", torch.accelerator.current_device_index())
for pool in (actual_generators, expected_generators):
pool[1] = torch.Generator(device=indexed_device).manual_seed(seeds[1])
orders = (
[0],
[2, 0, 1],
list(range(16)),
[36, *range(16)],
[35, *range(30)],
list(reversed(range(32))),
[35, 1, 2],
)
retained: list[tuple[torch.Tensor, torch.Tensor]] = []
global_state = torch.cuda.get_rng_state().clone()
for sweep in range(2):
if sweep:
for pool in (actual_generators, expected_generators):
pool[2] = torch.Generator(device="cuda").manual_seed(2**64 - 101)
for case, order in enumerate(orders):
rows = _logits(len(order), device="cuda", seed=1000 + sweep * 10 + case)
if sweep and case == 3:
for index, row in enumerate(rows):
row.fill_(-torch.inf)
row[0, EOS if index % 2 else 2] = 0
original = [row.clone() for row in rows]
live_actual = [actual_generators[index] for index in order]
live_expected = [expected_generators[index] for index in order]
expected = _reference(rows, PARAMETERS, live_expected)
actual = sampler.sample(rows, PARAMETERS, live_actual)
_assert_outputs(actual, expected, rows)
_assert_states(actual_generators, expected_generators)
retained.extend((value, reference.clone()) for value, reference in zip(actual, expected, strict=True))
for row, before in zip(rows, original, strict=True):
torch.testing.assert_close(row, before, atol=0, rtol=0)
# Preserve the subsequent 15 independent depth draws, including
# skipping them for EOS. All inactive/companion generators remain
# in the full-pool comparison above and below.
for index, token in enumerate(torch.cat(expected).cpu().tolist()):
if token == EOS:
continue
left = torch.empty((15, 2051), device="cuda")
right = torch.empty_like(left)
for codebook in range(15):
left[codebook].exponential_(generator=live_actual[index])
right[codebook].exponential_(generator=live_expected[index])
torch.testing.assert_close(left, right, atol=0, rtol=0)
_assert_states(actual_generators, expected_generators)
# B1/3/16/17/31/32 must use exactly buckets 1/4/16/32.
assert len(sampler._graphs) == 4
rows = _logits(3, device="cuda", seed=2026)
expected = _reference(rows, PARAMETERS, expected_generators[:3])
def unexpected_multinomial(*_args, **_kwargs):
raise AssertionError("Warmed graph replay must not call Python multinomial")
with monkeypatch.context() as replay_guard:
replay_guard.setattr(torch, "multinomial", unexpected_multinomial)
_assert_outputs(sampler.sample(rows, PARAMETERS, actual_generators[:3]), expected, rows)
_assert_outputs(
sampler.sample(rows, (0.0, 1, 0.2), actual_generators[:3]), [row.argmax(-1) for row in rows], rows
)
_assert_states(actual_generators, expected_generators)
assert len(sampler._graphs) == 4
for value, expected in retained:
torch.testing.assert_close(value, expected, atol=0, rtol=0)
torch.testing.assert_close(torch.cuda.get_rng_state(), global_state, atol=0, rtol=0)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_cuda_parameters_vocab_and_missing_graph_api(monkeypatch) -> None:
sampler = BreezeFirstCodeSampler()
actual_generators = _generators([42, 43, 44], "cuda")
expected_generators = _generators([42, 43, 44], "cuda")
cases = ((0.9, 50, 1.0), (0.7, 100, 0.8), (1.2, 0, 0.95), (1.0, 1, 1.0), (1.0, 0, 0.5))
for index, parameters in enumerate(cases):
rows = _logits(3, device="cuda", seed=200 + index)
if index >= 3:
for row in rows:
row.fill_(-torch.inf)
row[0, :4] = 0 # top-k ties and exact top-p=0.5 boundary.
expected = _reference(rows, parameters, expected_generators)
_assert_outputs(sampler.sample(rows, parameters, actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
assert len(sampler._graphs) == len(cases)
rows = _logits(3, device="cuda", vocab=17)
expected = _reference(rows, PARAMETERS, expected_generators)
_assert_outputs(sampler.sample(rows, PARAMETERS, actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
assert len(sampler._graphs) == len(cases) + 1
class UnavailableGraph:
def __init__(self, *_args, **_kwargs):
raise AssertionError("Unsupported graph API must select the eager fallback")
fallback = BreezeFirstCodeSampler()
expected = _reference(rows, PARAMETERS, expected_generators)
with monkeypatch.context() as api_guard:
api_guard.setattr(torch.cuda, "CUDAGraph", UnavailableGraph)
_assert_outputs(fallback.sample(rows, PARAMETERS, actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
assert not fallback._graphs
# Validate every row before the first eager/captured draw, including an
# invalid device that appears only after an otherwise valid CUDA row.
invalid = [rows[0], rows[1].cpu(), rows[2]]
with pytest.raises(ValueError):
sampler.sample(invalid, PARAMETERS, actual_generators)
_assert_states(actual_generators, expected_generators)
# A late invalid generator must likewise fail before row zero samples.
cpu_generator = torch.Generator(device="cpu").manual_seed(77)
cpu_reference = torch.Generator(device="cpu").manual_seed(77)
mixed_generators = [actual_generators[0], cpu_generator, actual_generators[2]]
with pytest.raises(ValueError):
sampler.sample(rows, PARAMETERS, mixed_generators)
_assert_states(mixed_generators, [expected_generators[0], cpu_reference, expected_generators[2]])
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_cuda_shared_generator_uses_sequential_eager_fallback(monkeypatch) -> None:
sampler = BreezeFirstCodeSampler()
rows = _logits(3, device="cuda")
# The duplicate-generator guard must precede lookup of an existing graph.
sampler.sample(rows, PARAMETERS, _generators([100, 101, 102], "cuda"))
cached = dict(sampler._graphs)
assert len(cached) == 1
actual = torch.Generator(device="cuda").manual_seed(2**64 - 23)
reference = torch.Generator(device="cuda").manual_seed(2**64 - 23)
multinomial = torch.multinomial
calls = []
def eager_spy(*args, **kwargs):
calls.append(kwargs.get("generator"))
return multinomial(*args, **kwargs)
for order in ([0, 1, 2], [2, 0, 1]):
ordered = [rows[index] for index in order]
expected = _reference(ordered, PARAMETERS, [reference] * 3)
calls.clear()
with monkeypatch.context() as fallback_guard:
fallback_guard.setattr(torch, "multinomial", eager_spy)
_assert_outputs(sampler.sample(ordered, PARAMETERS, [actual] * 3), expected, ordered)
assert len(calls) == 3 and all(generator is actual for generator in calls)
_assert_states([actual], [reference])
assert set(sampler._graphs) == set(cached)
assert all(sampler._graphs[key] is value for key, value in cached.items())
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_cuda_cache_limit_falls_back_without_evicting_warmed_graphs(monkeypatch) -> None:
sampler = BreezeFirstCodeSampler()
rows = _logits(1, device="cuda")
actual_generators = _generators([42], "cuda")
expected_generators = _generators([42], "cuda")
for top_k in range(1, 33):
parameters = (0.9, top_k, 1.0)
expected = _reference(rows, parameters, expected_generators)
_assert_outputs(sampler.sample(rows, parameters, actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
cached = dict(sampler._graphs)
assert len(cached) == 32
parameters = (0.9, 33, 1.0)
expected = _reference(rows, parameters, expected_generators)
multinomial = torch.multinomial
calls = []
def eager_spy(*args, **kwargs):
calls.append(kwargs.get("generator"))
return multinomial(*args, **kwargs)
with monkeypatch.context() as fallback_guard:
fallback_guard.setattr(torch, "multinomial", eager_spy)
_assert_outputs(sampler.sample(rows, parameters, actual_generators), expected, rows)
assert len(calls) == 1 and calls[0] is actual_generators[0]
_assert_states(actual_generators, expected_generators)
assert set(sampler._graphs) == set(cached)
assert all(sampler._graphs[key] is value for key, value in cached.items())
expected = _reference(rows, (0.9, 1, 1.0), expected_generators)
def unexpected_multinomial(*_args, **_kwargs):
raise AssertionError("Cache exhaustion must retain existing captured entries")
with monkeypatch.context() as replay_guard:
replay_guard.setattr(torch, "multinomial", unexpected_multinomial)
_assert_outputs(sampler.sample(rows, (0.9, 1, 1.0), actual_generators), expected, rows)
_assert_states(actual_generators, expected_generators)
@@ -0,0 +1,405 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Check captured cache ownership and padded text masking against eager models."""
import pytest
import torch
from transformers import LlamaConfig, T5Gemma2TextConfig
from transformers.models.t5gemma2.modeling_t5gemma2 import T5Gemma2TextEncoder
from vllm.platforms import current_platform
from tests.helpers.mark import hardware_test
from vllm_omni.model_executor.models.breeze_tts_2.depth_decoder import BreezeDepthDecoder, sample_logits
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze import BreezeForConditionalGeneration
from vllm_omni.model_executor.models.breeze_tts_2.text_encoder_graph import (
BreezeTextEncoderCompiled,
BreezeTextEncoderGraph,
)
pytestmark = [pytest.mark.core_model]
@pytest.fixture
def full_precision_matmul():
# Different padded shapes can select different TF32 kernels. Compare
# masking and replay semantics independently of that reduced precision.
original_precision = torch.get_float32_matmul_precision()
torch.set_float32_matmul_precision("highest")
try:
yield
finally:
torch.set_float32_matmul_precision(original_precision)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_text_graph_masks_padding_and_local_attention_across_replays(full_precision_matmul) -> None:
torch.manual_seed(42)
config = T5Gemma2TextConfig(
vocab_size=64,
hidden_size=32,
intermediate_size=64,
num_hidden_layers=2,
num_attention_heads=4,
num_key_value_heads=2,
head_dim=8,
sliding_window=16,
layer_types=["sliding_attention", "full_attention"],
dropout_rate=0.0,
)
config._attn_implementation = "sdpa"
encoder = T5Gemma2TextEncoder(config).to("cuda").eval()
encoder.embed_tokens.eoi_token_index = 17
encoder.embed_tokens.eoi_embedding.fill_(0.4)
projection = torch.nn.Linear(32, 48, bias=False).to("cuda").eval()
graph = BreezeTextEncoderGraph(encoder, projection, 64)
for length in (31, 51, 17, 31):
prompt = torch.randint(0, 64, (1, length), device="cuda")
prompt[:, 3] = 17
expected = projection(encoder(input_ids=prompt).last_hidden_state)[0]
actual = graph.run(prompt)
torch.testing.assert_close(actual, expected, atol=2e-6, rtol=2e-5)
batched = BreezeTextEncoderGraph(encoder, projection, 64, batch_size=3)
prompts = [torch.randint(0, 64, (length,), device="cuda") for length in (31, 51, 17)]
outputs = batched.run_batch(prompts)
for prompt, actual in zip(prompts, outputs, strict=True):
expected = projection(encoder(input_ids=prompt[None]).last_hidden_state)[0]
torch.testing.assert_close(actual, expected, atol=2e-6, rtol=2e-5)
compiled = BreezeTextEncoderCompiled(encoder, projection)
long_prompts = [torch.randint(0, 64, (length,)) for length in (211, 173, 129)]
for batch in ([long_prompts[0]], long_prompts, [long_prompts[1]]):
outputs = compiled.run_batch(batch)
for prompt, actual in zip(batch, outputs, strict=True):
expected = projection(encoder(input_ids=prompt[None].to("cuda")).last_hidden_state)[0]
torch.testing.assert_close(actual, expected, atol=2e-6, rtol=2e-5)
def _make_depth_decoder(vocab_size: int = 35) -> BreezeDepthDecoder:
config = LlamaConfig(
hidden_size=32,
intermediate_size=64,
num_hidden_layers=2,
num_attention_heads=4,
num_key_value_heads=2,
head_dim=8,
vocab_size=vocab_size,
max_position_embeddings=16,
rope_theta=500000.0,
rope_scaling={
"rope_type": "llama3",
"factor": 32.0,
"low_freq_factor": 0.001953125,
"high_freq_factor": 0.0078125,
"original_max_position_embeddings": 16,
},
)
config.num_codebooks = 16
config.audio_embed_size = 48
depth = BreezeDepthDecoder(config).to("cuda").eval()
torch.nn.init.normal_(depth.codebooks_head, std=0.1)
return depth
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_depth_graph_survives_interleaved_batches_and_workspace_allocations(full_precision_matmul, monkeypatch) -> None:
torch.manual_seed(42)
depth = _make_depth_decoder()
hidden = torch.randn(2, 48, device="cuda")
first = torch.tensor([7, 11], device="cuda")
generators = [torch.Generator(device="cuda").manual_seed(seed) for seed in (42, 99)]
expected = torch.cat(
[
depth.generate_frame(hidden[i : i + 1], first[i : i + 1], temperature=0, top_k=0, top_p=1, generator=g)
for i, g in enumerate(generators)
]
)
for batch in (1, 2, 1, 2):
# Unowned allocations made before graph capture would be recycled here.
pressure = [torch.full((batch, 2, depth.num_codebooks, 8), float("nan"), device="cuda") for _ in range(32)]
actual = depth.generate_frames(
hidden[:batch], first[:batch], temperature=0, top_k=0, top_p=1, generators=generators[:batch]
)
torch.testing.assert_close(actual, expected[:batch], atol=0, rtol=0)
del pressure
def sample(batch: int) -> torch.Tensor:
return depth.generate_frames(
hidden[:batch],
first[:batch],
temperature=0.9,
top_k=10,
top_p=0.8,
generators=[torch.Generator(device="cuda").manual_seed(seed) for seed in (42, 99)[:batch]],
)
sampled = sample(1)
sampled_graph = depth._graphs[(1, 1, False)]
greedy_graph = depth._graphs[(1, 1, True)]
assert sampled_graph is not greedy_graph
sample(2)
torch.testing.assert_close(sample(1), sampled, atol=0, rtol=0)
for temperature, top_k, top_p in ((0.0, 0, 1.0), (0.7, 20, 0.95), (0.0, 1, 0.2), (1.2, 0, 0.8)):
rng_before = generators[0].get_state()
actual = depth.generate_frames(
hidden[:1], first[:1], temperature=temperature, top_k=top_k, top_p=top_p, generators=generators[:1]
)
if temperature == 0:
assert depth._graphs[(1, 1, True)] is greedy_graph
torch.testing.assert_close(actual, expected[:1], atol=0, rtol=0)
torch.testing.assert_close(generators[0].get_state(), rng_before, atol=0, rtol=0)
else:
assert depth._graphs[(1, 1, False)] is sampled_graph
torch.testing.assert_close(sample(1), sampled, atol=0, rtol=0)
graph = None
for scale in (4.0, 0.5, 4.0):
expected_cfg = depth._generate_frame(
hidden, first[:1], depth._allocate_cache(hidden), 0, 0, 1, generators[0], guidance_scale=scale
)
actual_cfg = depth.generate_frames(
hidden, first[:1], temperature=0, top_k=0, top_p=1, generators=generators[:1], guidance_scale=scale
)
torch.testing.assert_close(actual_cfg, expected_cfg, atol=0, rtol=0)
if graph is not None:
assert depth._graphs[(2, 2, True)] is graph
graph = depth._graphs[(2, 2, True)]
sample(2)
# Use a still-cold sampled CFG entry to exercise platforms without
# captured generator support, while reusing the existing compiled model.
fallback_key = (2, 2, False)
assert fallback_key not in depth._graphs
reference_generator = torch.Generator(device="cuda").set_state(generators[0].get_state())
inactive_rng = generators[1].get_state()
exponential = torch.Tensor.exponential_
observed_generators = []
def record_eager_noise(tensor, *args, **kwargs):
observed_generators.append(kwargs.get("generator"))
return exponential(tensor, *args, **kwargs)
fallback_graph = None
retained = []
with monkeypatch.context() as platform_guard:
platform_guard.setattr(current_platform, "is_cuda", lambda: False)
for scale in (4.0, 0.5, 4.0):
expected = depth._generate_frame(
hidden,
first[:1],
depth._allocate_cache(hidden),
0.9,
10,
0.8,
reference_generator,
guidance_scale=scale,
)
observed_generators.clear()
with monkeypatch.context() as noise_spy:
noise_spy.setattr(torch.Tensor, "exponential_", record_eager_noise)
actual = depth.generate_frames(
hidden,
first[:1],
temperature=0.9,
top_k=10,
top_p=0.8,
generators=generators[:1],
guidance_scale=scale,
)
entry = depth._graphs[fallback_key]
assert entry.noise_generators is None
assert len(observed_generators) == depth.num_codebooks - 1
assert all(generator is generators[0] for generator in observed_generators)
if fallback_graph is not None:
assert entry is fallback_graph
fallback_graph = entry
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
torch.testing.assert_close(generators[0].get_state(), reference_generator.get_state(), atol=0, rtol=0)
torch.testing.assert_close(generators[1].get_state(), inactive_rng, atol=0, rtol=0)
retained.append((actual, expected.clone()))
for actual, expected in retained:
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_depth_graph_buckets_bound_shapes_and_preserve_requests(full_precision_matmul, monkeypatch) -> None:
def reject_eager_noise(*args, **kwargs):
raise AssertionError("Sampled depth replay must not issue eager RNG draws")
torch.manual_seed(17)
# Match the checkpoint's multinomial draw size, including reserved IDs.
depth = _make_depth_decoder(vocab_size=2051)
hidden = torch.randn(32, 48, device="cuda")
uncond = torch.randn_like(hidden)
first = torch.arange(32, device="cuda")
first_logits = torch.randn(32, depth.vocab_size, device="cuda")
first_logits[:, -3:] = -torch.inf
seeds = [42 + index for index in range(32)]
seeds[0], seeds[-1] = 2**63 + 17, 2**64 - 1
generators = [torch.Generator(device="cuda").manual_seed(seed) for seed in seeds]
reference_generators = [torch.Generator(device="cuda").manual_seed(seed) for seed in seeds]
expected_greedy = torch.cat(
[
depth.generate_frame(
hidden[index : index + 1],
first[index : index + 1],
temperature=0,
top_k=0,
top_p=1,
generator=reference_generators[index],
)
for index in range(32)
]
)
retained: list[tuple[torch.Tensor, torch.Tensor]] = []
for batch in (*range(1, 33), 17, 11, 3, 1):
rows = list(reversed(range(batch)))
actual = depth.generate_frames(
hidden[rows], first[rows], temperature=0, top_k=0, top_p=1, generators=[generators[row] for row in rows]
)
expected = expected_greedy[rows]
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
retained.append((actual, expected.clone()))
assert set(depth._graphs) == {(batch, 1, True) for batch in (1, 2, 4, 8, 16, 32)}
for generator, reference in zip(generators, reference_generators, strict=True):
torch.testing.assert_close(generator.get_state(), reference.get_state(), atol=0, rtol=0)
retired_generators: list[tuple[torch.Generator, torch.Tensor]] = []
for temperature, guidance_scale in ((0.9, 1.0), (0.0, 4.0), (0.9, 4.0)):
batches: tuple[int, ...] = (3, 5, 11, 7, 3, 1)
if guidance_scale == 1:
batches = (16, 17, 31, 32, 17, 31, 16, *batches)
for step, batch in enumerate(batches):
if guidance_scale == 1 and step == 4:
# New requests reuse rows in the same 32-request graph. Their
# seeds must replace the previous owners, not just offsets.
for row, seed in ((0, 2**64 - 1), (16, 2**63 + 321)):
retired_generators.append((generators[row], generators[row].get_state()))
generators[row] = torch.Generator(device="cuda").manual_seed(seed)
reference_generators[row] = torch.Generator(device="cuda").manual_seed(seed)
rows = list(reversed(range(batch)))
batch_hidden = hidden[rows]
if guidance_scale != 1:
batch_hidden = torch.cat((batch_hidden, uncond[rows]))
expected, first_codes = [], []
for row in rows:
# The talker and depth decoder share each request's generator.
# Exercise a codebook-0 sample immediately before graph replay.
row_first = sample_logits(first_logits[row : row + 1], temperature, 10, 0.8, generators[row])
reference_first = sample_logits(
first_logits[row : row + 1], temperature, 10, 0.8, reference_generators[row]
)
torch.testing.assert_close(row_first, reference_first, atol=0, rtol=0)
first_codes.append(row_first)
row_hidden = hidden[row : row + 1]
if guidance_scale != 1:
row_hidden = torch.cat((row_hidden, uncond[row : row + 1]))
expected.append(
depth._generate_frame(
row_hidden,
reference_first,
depth._allocate_cache(row_hidden),
temperature,
10,
0.8,
reference_generators[row],
guidance_scale=guidance_scale if guidance_scale != 1 else None,
)
)
request_bucket = 1 << (batch - 1).bit_length()
branches = 2 if guidance_scale != 1 else 1
graph_key = (request_bucket * branches, branches, temperature == 0)
with monkeypatch.context() as rng_guard:
if temperature > 0 and graph_key in depth._graphs:
# A warmed replay must generate noise within the CUDA
# graph, without dispatching new RNG kernels from Python.
rng_guard.setattr(torch.Tensor, "exponential_", reject_eager_noise)
actual = depth.generate_frames(
batch_hidden,
torch.cat(first_codes),
temperature=temperature,
top_k=10,
top_p=0.8,
generators=[generators[row] for row in rows],
guidance_scale=guidance_scale,
)
expected_frames = torch.cat(expected)
torch.testing.assert_close(actual, expected_frames, atol=0, rtol=0)
retained.append((actual, expected_frames.clone()))
# Check the next codebook-0 sample as well as the depth result: a
# replay must return the advanced RNG stream to its live request.
for row in rows:
actual_next = sample_logits(first_logits[row : row + 1], temperature, 10, 0.8, generators[row])
expected_next = sample_logits(
first_logits[row : row + 1], temperature, 10, 0.8, reference_generators[row]
)
torch.testing.assert_close(actual_next, expected_next, atol=0, rtol=0)
# Padding must not draw RNG, and inactive request streams must not advance.
for generator, reference in zip(generators, reference_generators, strict=True):
torch.testing.assert_close(generator.get_state(), reference.get_state(), atol=0, rtol=0)
for generator, state in retired_generators:
torch.testing.assert_close(generator.get_state(), state, atol=0, rtol=0)
expected_keys = {(batch, 1, True) for batch in (1, 2, 4, 8, 16, 32)}
expected_keys.update((batch, 1, False) for batch in (1, 4, 8, 16, 32))
expected_keys.update((batch, 2, greedy) for batch in (2, 8, 16, 32) for greedy in (False, True))
assert set(depth._graphs) == expected_keys
for (batch_bucket, branches, greedy), entry in depth._graphs.items():
if greedy:
assert entry.noise_generators is None
else:
assert entry.noise_generators is not None
assert len(entry.noise_generators) == batch_bucket // branches
assert {id(generator) for generator in entry.noise_generators}.isdisjoint(
id(generator) for generator in generators
)
for actual, expected in retained:
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@torch.inference_mode()
def test_batched_decode_embedding_matches_scalar_bfloat16() -> None:
# Use the checkpoint's RVQ table dimensions without loading its backbone.
model = BreezeForConditionalGeneration.__new__(BreezeForConditionalGeneration)
torch.nn.Module.__init__(model)
model.num_codebooks, model.hidden_size = 16, 2048
model.register_buffer("offsets", torch.arange(16, device="cuda") * 2051)
model.depth_decoder = torch.nn.Module()
torch.manual_seed(17)
model.depth_decoder.embed_tokens = torch.nn.Embedding(16 * 2051, 2048, device="cuda", dtype=torch.bfloat16)
for seed in (17, 42, 99):
generator = torch.Generator(device="cuda").manual_seed(seed)
frames = torch.randint(0, 2051, (32, 16), device="cuda", generator=generator)
frames[1] = frames[0]
for batch in (1, 2, 3, 4, 5, 6, 7, 8, 16, 32, 7, 1):
# Reverse physical rows and revisit smaller batches after expansion.
current_frames = [row[None] for row in frames[:batch].flip(0)]
if batch > 1:
# CFG branches share their generated audio frame.
current_frames[-2] = current_frames[-1]
infos = [
{
"_omni_is_prefill": False,
"breeze_state": {"current": current},
"global_request_id": [str(index)],
"breeze_prompt": {},
"breeze_sampling": {},
}
for index, current in enumerate(current_frames)
]
ids = torch.zeros(batch, device="cuda", dtype=torch.long)
expected = torch.cat(
[
model.preprocess(input_ids=ids[index : index + 1], input_embeds=None, **info)[1]
for index, info in enumerate(infos)
]
)
output_ids, actual, updates = model.preprocess_decode_batch(input_ids=ids, req_infos=infos)
torch.testing.assert_close(actual, expected, rtol=0, atol=0)
assert output_ids is ids
assert updates == [{} for _ in infos]
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU contracts for Breeze's decode-only embedding preprocessing."""
import pytest
import torch
from vllm_omni.model_executor.models.breeze_tts_2.modeling_breeze import BreezeForConditionalGeneration
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _embedding_talker(dtype):
model = BreezeForConditionalGeneration.__new__(BreezeForConditionalGeneration)
torch.nn.Module.__init__(model)
model.num_codebooks, model.hidden_size = 16, 8
model.register_buffer("offsets", torch.arange(model.num_codebooks) * 7)
model.depth_decoder = torch.nn.Module()
model.depth_decoder.embed_tokens = torch.nn.Embedding(16 * 7, 8, dtype=dtype)
with torch.no_grad():
model.depth_decoder.embed_tokens.weight.copy_(
torch.randn(16 * 7, 8, generator=torch.Generator().manual_seed(17))
)
return model
def _decode_info(request_id, current, seed):
return {
"global_request_id": [request_id],
"breeze_prompt": {"role": "cond", "guidance_scale": 1.0},
"breeze_sampling": {"temperature": 0.9, "top_k": 4, "top_p": 1.0, "repetition_penalty": 1.1},
"breeze_state": {
"current": current,
"history": torch.tensor([[1, 2, 3]], dtype=torch.long),
"generator": torch.Generator().manual_seed(seed),
},
"_omni_is_prefill": False,
}
@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
@torch.inference_mode()
def test_decode_batch_matches_scalar_after_reordering_shrinking_and_cfg(dtype):
model = _embedding_talker(dtype)
shared_frame = torch.arange(16, dtype=torch.long).reshape(1, 16) % 7
infos = [
_decode_info("unconditional", shared_frame, 1),
_decode_info("other", (shared_frame + 1) % 7, 2),
_decode_info("conditioned", shared_frame, 3),
_decode_info("last", (shared_frame + 3) % 7, 4),
]
infos[0]["breeze_prompt"] = {"role": "uncond", "guidance_scale": 4.0}
infos[2]["breeze_prompt"]["guidance_scale"] = 4.0
saved = []
for indices in ([0, 1, 2, 3], [2, 0, 1], [1]):
selected = [infos[index] for index in indices]
# Placeholder IDs deliberately differ from the RVQ frame IDs.
input_ids = torch.arange(len(selected), dtype=torch.long) + 100
expected = torch.cat(
[
model.preprocess(input_ids=input_ids[row : row + 1], input_embeds=None, **info)[1]
for row, info in enumerate(selected)
]
)
state_before = [
(
info["breeze_state"]["current"].clone(),
info["breeze_state"]["history"].clone(),
info["breeze_state"]["generator"].get_state(),
)
for info in infos
]
output_ids, embeds, updates = model.preprocess_decode_batch(input_ids=input_ids, req_infos=selected)
assert output_ids is input_ids
torch.testing.assert_close(embeds, expected, rtol=0, atol=0)
assert updates == [{} for _ in selected]
for info, (current, history, rng) in zip(infos, state_before, strict=True):
state = info["breeze_state"]
assert torch.equal(state["current"], current)
assert torch.equal(state["history"], history)
assert torch.equal(state["generator"].get_state(), rng)
saved.append((embeds, embeds.clone()))
# Simulate the next generated frame. CFG branches share the new frame.
next_frame = (infos[2]["breeze_state"]["current"] + 2) % 7
infos[0]["breeze_state"]["current"] = next_frame
infos[2]["breeze_state"]["current"] = next_frame
infos[1]["breeze_state"]["current"] = (infos[1]["breeze_state"]["current"] + 1) % 7
assert infos[0]["breeze_state"]["current"] is infos[2]["breeze_state"]["current"]
for output, snapshot in saved:
assert torch.equal(output, snapshot)
@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
def test_decode_batch_empty_preserves_embedding_shape_and_dtype(dtype):
model = _embedding_talker(dtype)
ids = torch.empty(0, dtype=torch.long)
output_ids, embeds, updates = model.preprocess_decode_batch(input_ids=ids, req_infos=[])
assert output_ids is ids
assert embeds.shape == (0, 8)
assert embeds.dtype == dtype
assert embeds.device == model.depth_decoder.embed_tokens.weight.device
assert updates == []
@pytest.mark.parametrize("invalid", ["count", "state", "frame_shape", "prefill"])
def test_decode_batch_rejects_invalid_request_state(invalid):
model = _embedding_talker(torch.float32)
info = _decode_info("request", torch.zeros(1, 16, dtype=torch.long), 42)
ids = torch.tensor([0])
if invalid == "count":
ids = torch.tensor([0, 1])
elif invalid == "state":
del info["breeze_state"]
elif invalid == "frame_shape":
info["breeze_state"]["current"] = torch.zeros(2, 16, dtype=torch.long)
else:
info["_omni_is_prefill"] = True
with pytest.raises(ValueError, match="Breeze decode"):
model.preprocess_decode_batch(input_ids=ids, req_infos=[info])
@@ -0,0 +1,50 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""The accelerated runtime preserves Breeze-TTS-2's model identifiers."""
from pathlib import Path
import pytest
import yaml
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
from vllm_omni.entrypoints.openai.tts_adapters import detect_tts_model_type, resolve_adapter
from vllm_omni.entrypoints.openai.tts_adapters.breeze_tts_2 import BreezeTTS2Adapter
from vllm_omni.model_executor.models.breeze_tts_2.pipeline import BREEZE_TTS_2_PIPELINE
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_breeze_tts_2_pipeline_keeps_checkpoint_and_stage_identifiers():
assert OMNI_PIPELINES["breeze"] is BREEZE_TTS_2_PIPELINE
assert BREEZE_TTS_2_PIPELINE.model_arch == "BreezeForConditionalGeneration"
assert BREEZE_TTS_2_PIPELINE.default_deploy_config_name == "breeze_tts_2.yaml"
assert [stage.model_stage for stage in BREEZE_TTS_2_PIPELINE.stages] == [
"breeze_tts_2",
"breeze_tts_2_codec",
]
def test_breeze_tts_2_adapter_remains_registered():
assert resolve_adapter("breeze_tts_2") is BreezeTTS2Adapter
@pytest.mark.parametrize(
"model_stage,model_arch",
[
("breeze_tts_2", None),
("breeze_tts_2_codec", None),
("breeze_tts_2", "BreezeForConditionalGeneration"),
(None, "BreezeForConditionalGeneration"),
],
)
def test_breeze_tts_2_detection_keeps_existing_identifiers(model_stage, model_arch):
assert detect_tts_model_type(model_stage, model_arch) == "breeze_tts_2"
@pytest.mark.parametrize("suffix", ["", "_throughput"])
def test_breeze_deploy_aliases_match_canonical_configuration(suffix):
deploy_dir = Path(__file__).parents[3] / "vllm_omni" / "deploy"
canonical = yaml.safe_load((deploy_dir / f"breeze_tts_2{suffix}.yaml").read_text())
alias = yaml.safe_load((deploy_dir / f"breeze_tts{suffix}.yaml").read_text())
assert canonical == alias
@@ -19,7 +19,7 @@ pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@pytest.fixture
def talker_sampling_params():
deploy_path = Path(__file__).parents[4] / "vllm_omni" / "deploy" / "breeze_tts_2.yaml"
deploy_path = Path(__file__).parents[3] / "vllm_omni" / "deploy" / "breeze_tts_2.yaml"
deploy = yaml.safe_load(deploy_path.read_text(encoding="utf-8"))
params = SamplingParams(
**merge_sampling_constraints(