[BugFix][TTS] Fix MOSS Realtime serving and generation (#5661)

Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
This commit is contained in:
akshatvishu authored and GitHub committed 2026-09-16 11:39:51 -07:00
1 parent 04c55d0164
commit a8434a60da
13 files changed
+771 -64

No files matched your search

@@ -0,0 +1,54 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Regression tests for MOSS audio tokenizer v2 projection modules."""
from __future__ import annotations
import pytest
import torch
import torch.nn as nn
from vllm_omni.model_executor.models.moss_tts.audio_tokenizer_v2 import (
MossAudioTokenizerProjectedTransformer,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_projected_transformer_keeps_learned_projections_when_dimensions_match() -> None:
module = MossAudioTokenizerProjectedTransformer(
input_dimension=8,
output_dimension=8,
d_model=8,
module_type="transformer",
num_heads=1,
num_layers=0,
positional_embedding="rope",
)
assert isinstance(module.input_proj, nn.Linear)
assert isinstance(module.output_proj, nn.Linear)
assert module.input_proj.weight.shape == (8, 8)
assert module.output_proj.weight.shape == (8, 8)
x = torch.randn(2, 8, 5)
lengths = torch.tensor([5, 3], dtype=torch.long)
output, output_lengths = module(x, lengths)
assert output.shape == x.shape
torch.testing.assert_close(output_lengths, lengths)
def test_projected_transformer_keeps_linear_projections_when_dimensions_differ() -> None:
module = MossAudioTokenizerProjectedTransformer(
input_dimension=8,
output_dimension=6,
d_model=10,
module_type="transformer",
num_heads=1,
num_layers=0,
positional_embedding="rope",
)
assert isinstance(module.input_proj, nn.Linear)
assert isinstance(module.output_proj, nn.Linear)
@@ -0,0 +1,86 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
from typing import Any
import pytest
import torch
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_realtime_talker_preserves_state_across_chunked_prefill() -> None:
from vllm_omni.model_executor.models.moss_tts.modeling_moss_tts_talker import (
MossTTSRealtimeTalkerForGeneration,
)
talker = MossTTSRealtimeTalkerForGeneration.__new__(MossTTSRealtimeTalkerForGeneration)
talker.n_vq = 2
talker.audio_pad_token = 1024
talker.text_vocab_size = 16
talker.text_pad_id = 2
talker._batch_state = None
talker._batch_state_spans = None
embedded_audio: list[torch.Tensor | None] = []
def build_input_embeds(input_ids, audio_codes):
embedded_audio.append(audio_codes.clone() if audio_codes is not None else None)
return torch.zeros((input_ids.shape[0], 1))
generated: list[None] = []
def generate_frame(*args, **kwargs):
generated.append(None)
return torch.ones((1, talker.n_vq), dtype=torch.long)
talker._build_input_embeds = build_input_embeds
talker.local_transformer = SimpleNamespace(generate_frame=generate_frame)
talker.local_lm_heads = []
ref_codes = torch.arange(10, dtype=torch.long).reshape(5, 2)
info: dict[str, Any] = {
"codes": {"ref": ref_codes},
"ids": {"all": [7, 8]},
"max_new_frames": [3],
}
chunks = [(0, 2, False), (2, 2, False), (4, 1, True)]
state: dict[str, Any] | None = None
for computed, length, eligible in chunks:
_, _, update = talker.preprocess(
torch.ones(length, dtype=torch.long),
None,
_omni_is_prefill=True,
_omni_num_computed_tokens=computed,
_omni_prompt_len=5,
**info,
)
info.update(update)
if state is None:
state = info["audio_state"]
else:
assert info["audio_state"] is state
output = talker.make_omni_output(
torch.ones((length, 1)),
model_intermediate_buffer=[info],
request_token_spans=[(0, length)],
request_sample_eligible=[eligible],
)
if not eligible:
assert output.multimodal_outputs == {}
logits = talker.compute_logits(torch.ones((1, 1)))
assert logits.argmax().item() == (7 if eligible else talker.text_pad_id)
assert state is not None
assert info["ref_offset"] == 5
assert state["step"] == 1
assert state["text_cursor"] == 1
assert state["remaining_text"] == [7, 8]
assert len(generated) == 1
assert output.multimodal_outputs["codes"]["audio"][0].shape == (1, 2)
for actual, expected in zip(embedded_audio, ref_codes.split((2, 2, 1)), strict=True):
assert actual is not None
torch.testing.assert_close(actual, expected)
@@ -0,0 +1,53 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
import pytest
import torch
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_realtime_talker_stops_at_max_new_frames() -> None:
from vllm_omni.model_executor.models.moss_tts.modeling_moss_tts_talker import (
MossTTSRealtimeTalkerForGeneration,
)
talker = MossTTSRealtimeTalkerForGeneration.__new__(MossTTSRealtimeTalkerForGeneration)
talker.n_vq = 4
talker.audio_pad_token = 1024
talker.text_vocab_size = 8
talker.text_pad_id = 2
talker.audio_eos_id = 3
talker._batch_state = None
talker._build_input_embeds = lambda input_ids, audio_codes: torch.zeros((input_ids.shape[0], 1))
generated: list[None] = []
def generate_frame(*args, **kwargs):
generated.append(None)
return torch.ones((1, talker.n_vq), dtype=torch.long)
talker.local_transformer = SimpleNamespace(generate_frame=generate_frame)
talker.local_lm_heads = []
_, _, info = talker.preprocess(
torch.tensor([1, 2]),
None,
max_new_frames=[2],
)
assert info["audio_state"]["max_new_frames"] == 2
for _ in range(3):
output = talker.make_omni_output(
torch.ones((1, 1)),
runtime_additional_information=[info],
request_token_spans=[(0, 1)],
)
assert len(generated) == 2
assert info["audio_state"]["step"] == 2
assert info["audio_state"]["is_stopping"] is True
assert output.multimodal_outputs == {}
assert talker.compute_logits(torch.ones((1, 1))).argmax().item() == talker.audio_eos_id
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import numpy as np
import pytest
import torch
from vllm_omni.model_executor.models.moss_tts.realtime_prompt import build_realtime_prompt
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _Tokenizer:
def encode(self, text: str, add_special_tokens: bool = False) -> list[int]:
del add_special_tokens
if text == "<|im_start|>assistant\n":
return [20, 21]
return list(range(100, 100 + len(text.split())))
class _Processor:
audio_channel_pad = 1024
audio_bos_token = 1025
def __init__(self) -> None:
self.reference_codes = None
def make_ensemble(self, prompt_audio_tokens):
self.reference_codes = prompt_audio_tokens
return np.full((3, 17), self.audio_channel_pad, dtype=np.int64)
def test_build_realtime_prompt_aligns_reference_and_prefill_text() -> None:
processor = _Processor()
reference_codes = torch.arange(80, dtype=torch.int64).reshape(5, 16)
params = build_realtime_prompt(
_Tokenizer(),
processor,
"one two three",
reference_codes,
)
assert np.array_equal(processor.reference_codes, reference_codes.numpy())
assert params["prompt_token_ids"] == [1024, 1024, 1024, 20, 21, 100, 101, 102]
audio_grid = params["codes"]["ref"]
assert audio_grid.shape == (len(params["prompt_token_ids"]), 16)
assert audio_grid[-1, 0].item() == processor.audio_bos_token
assert "ids" not in params
def test_build_realtime_prompt_streams_text_after_first_twelve_tokens() -> None:
params = build_realtime_prompt(
_Tokenizer(),
_Processor(),
" ".join(f"word{i}" for i in range(15)),
torch.zeros((2, 16), dtype=torch.int64),
)
assert params["prompt_token_ids"][-12:] == list(range(100, 112))
assert params["ids"] == {"all": [112, 113, 114]}
def test_build_realtime_prompt_rejects_wrong_processor_layout(monkeypatch: pytest.MonkeyPatch) -> None:
processor = _Processor()
monkeypatch.setattr(
processor,
"make_ensemble",
lambda prompt_audio_tokens: np.zeros((3, 16), dtype=np.int64),
)
with pytest.raises(ValueError, match=r"expected \(sequence_length, 17\)"):
build_realtime_prompt(
_Tokenizer(),
processor,
"one",
torch.zeros((2, 16), dtype=torch.int64),
)