# 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]Speak happily.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]]