Files
2026-08-14 22:06:05 -07:00

364 lines
14 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""CPU coverage for Gepard's prompt layout, streaming-decode window and config.
The e2e test needs a GPU, NeMo and the checkpoint, so it only runs nightly.
These pin the parts that are pure logic and break silently: which frames each
decode covers, which samples survive the lookback trim, the prompt layout, and
that the packaged deploy config sits where the example looks for it.
The codec is replaced by a shift-invariant stand-in whose output depends only
on the codes, which makes "chunked emissions concatenate to one whole-history
decode" an exact assertion — the contract lookback exists to provide.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import torch
import torch.nn as nn
import vllm_omni
from vllm_omni.config.stage_config import load_deploy_config
from vllm_omni.model_executor.models.gepard.configuration_gepard import GepardConfig
from vllm_omni.model_executor.models.gepard.gepard_talker import (
CHUNK_FRAMES,
FIRST_CHUNK_FRAMES,
GepardTalkerForConditionalGeneration,
_GepardState,
)
from vllm_omni.model_executor.models.gepard.nanocodec import (
LOOKBACK_FRAMES,
NanoCodec,
apply_end_of_speech_tail,
)
from vllm_omni.model_executor.models.gepard.prompt import build_gepard_prompt_ids
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
SAMPLES_PER_FRAME = 1024
NUM_HEADS = 32
SAMPLE_RATE = 22050
# Text-token count at or above which the layout is left unrepeated, read from
# the checkpoint defaults so the boundary cases follow the config.
APPLY_BELOW = GepardConfig().text_repetition_apply_below
class _ShiftInvariantCodec:
"""Frame tagged ``v`` -> samples ``v*SPF .. v*SPF+SPF-1``, wherever it sits."""
def __init__(self) -> None:
self.windows: list[int] = [] # frame count of every decode call
def decode_from_codes(self, codes: torch.Tensor, codes_len: torch.Tensor):
assert codes.shape[0] == 1, "streaming decode is per-request"
assert codes.shape[1] == NUM_HEADS, f"expected {NUM_HEADS} code channels"
tags = codes[0, 0, :].to(torch.int64) # (T,)
self.windows.append(int(tags.shape[0]))
offsets = torch.arange(SAMPLES_PER_FRAME, dtype=torch.int64)
audio = (tags.unsqueeze(1) * SAMPLES_PER_FRAME + offsets).reshape(1, -1)
return audio.to(torch.float32), codes_len * SAMPLES_PER_FRAME
def _make_codec() -> tuple[NanoCodec, _ShiftInvariantCodec]:
codec = NanoCodec(codec_id="test/fake-codec", sample_rate=SAMPLE_RATE)
fake = _ShiftInvariantCodec()
codec._codec = fake # bypasses load(); NeMo is never imported
assert codec.is_loaded
return codec, fake
def _make_talker(codec: NanoCodec) -> GepardTalkerForConditionalGeneration:
"""A real talker with only the audio-path attributes populated.
``__init__`` needs a VllmConfig and builds the backbone; the methods under
test touch only these four attributes.
"""
talker = GepardTalkerForConditionalGeneration.__new__(GepardTalkerForConditionalGeneration)
nn.Module.__init__(talker) # sets up _parameters/_buffers/_modules only
talker._codec = codec
talker._audio_queue = []
talker._active_states = {}
talker._deferred_cleanup_ids = set()
return talker
def _frame(tag: int) -> torch.Tensor:
"""One committed frame: 32 per-dimension codes, all carrying the tag."""
return torch.full((NUM_HEADS,), tag, dtype=torch.long)
def _expected_samples(num_frames: int) -> torch.Tensor:
offsets = torch.arange(SAMPLES_PER_FRAME, dtype=torch.int64)
tags = torch.arange(num_frames, dtype=torch.int64).unsqueeze(1)
return (tags * SAMPLES_PER_FRAME + offsets).reshape(-1).to(torch.float32)
def _drain(talker) -> torch.Tensor:
"""Concatenate and clear the queued deltas, as make_omni_output does."""
if not talker._audio_queue:
return torch.empty(0)
out = torch.cat([a.reshape(-1) for _, a in talker._audio_queue])
talker._audio_queue.clear()
return out
def _generate(talker, state, num_frames: int, *, stop_at_end: bool) -> None:
"""Drive the commit loop the way forward() does, one frame per step."""
for tag in range(num_frames):
state.frames.append(_frame(tag))
state.frame_count += 1
state.past_first_step = True
talker._emit_audio(state, is_final=stop_at_end and tag == num_frames - 1)
# Counts straddling both cadence thresholds, where an off-by-one hides.
@pytest.mark.parametrize(
"num_frames",
[
1,
2,
FIRST_CHUNK_FRAMES - 1,
FIRST_CHUNK_FRAMES,
FIRST_CHUNK_FRAMES + 1,
CHUNK_FRAMES - 1,
CHUNK_FRAMES,
CHUNK_FRAMES + 1,
FIRST_CHUNK_FRAMES + CHUNK_FRAMES,
79,
200,
],
)
def test_chunked_emissions_equal_a_whole_history_decode(num_frames: int) -> None:
codec, _fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
_generate(talker, state, num_frames, stop_at_end=True)
torch.testing.assert_close(_drain(talker), _expected_samples(num_frames))
assert state.emitted_frames == num_frames
def test_every_frame_is_emitted_exactly_once() -> None:
"""The trim must not drop or duplicate samples at a chunk boundary."""
codec, _fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
num_frames = FIRST_CHUNK_FRAMES + 2 * CHUNK_FRAMES + 3
_generate(talker, state, num_frames, stop_at_end=True)
audio = _drain(talker)
assert audio.numel() == num_frames * SAMPLES_PER_FRAME
# Tags are globally unique, so a duplicated or dropped frame shows up here.
assert len(torch.unique(audio)) == audio.numel()
def test_decode_windows_carry_lookback_context() -> None:
"""Every decode after the first must re-decode LOOKBACK_FRAMES of context."""
codec, fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
num_frames = FIRST_CHUNK_FRAMES + 2 * CHUNK_FRAMES
_generate(talker, state, num_frames, stop_at_end=True)
assert len(fake.windows) >= 3, "expected a first chunk plus steady-state chunks"
assert fake.windows[0] == FIRST_CHUNK_FRAMES, "the first chunk has no left context"
for window in fake.windows[1:]:
assert window == CHUNK_FRAMES + LOOKBACK_FRAMES
def test_codec_runs_once_per_chunk_not_once_per_frame() -> None:
codec, fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
num_frames = FIRST_CHUNK_FRAMES + 3 * CHUNK_FRAMES
_generate(talker, state, num_frames, stop_at_end=True)
assert len(fake.windows) == 4
assert sum(fake.windows) < num_frames * 2 # far below one decode per frame
def test_final_emit_drains_a_partial_chunk() -> None:
"""A request can end mid-chunk (max_tokens, STOP); the frames committed
since the last cadence boundary must still reach the queue.
Arithmetic only: this drives ``_emit_audio`` directly, so it says nothing
about *whether* the engine reaches it on the request's last step. That
wiring lives in ``test_gepard_wiring.py`` — a bug there leaves this green.
"""
codec, _fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
num_frames = FIRST_CHUNK_FRAMES + CHUNK_FRAMES + 5
_generate(talker, state, num_frames, stop_at_end=False)
assert state.emitted_frames < num_frames, "a partial chunk should still be pending"
talker._emit_audio(state, is_final=True)
torch.testing.assert_close(_drain(talker), _expected_samples(num_frames))
assert state.emitted_frames == num_frames
def test_emitting_with_nothing_pending_is_a_no_op() -> None:
codec, fake = _make_codec()
talker = _make_talker(codec)
state = _GepardState(request_id="req-0")
_generate(talker, state, 4, stop_at_end=True)
calls_after_flush = len(fake.windows)
talker._audio_queue.clear()
talker._emit_audio(state, is_final=True)
assert len(fake.windows) == calls_after_flush, "codec ran with no new frames"
assert talker._audio_queue == []
def test_concurrent_requests_keep_separate_windows() -> None:
codec, _fake = _make_codec()
talker = _make_talker(codec)
states = [_GepardState(request_id=f"req-{i}") for i in range(2)]
for state in states:
_generate(talker, state, FIRST_CHUNK_FRAMES, stop_at_end=True)
by_req: dict[str, list[torch.Tensor]] = {}
for req_id, audio in talker._audio_queue:
by_req.setdefault(req_id, []).append(audio.reshape(-1))
assert set(by_req) == {"req-0", "req-1"}
for chunks in by_req.values():
torch.testing.assert_close(torch.cat(chunks), _expected_samples(FIRST_CHUNK_FRAMES))
def test_end_of_speech_tail_fades_and_pads() -> None:
audio = torch.ones(SAMPLE_RATE) # 1 s of full-scale signal
out = apply_end_of_speech_tail(audio, SAMPLE_RATE, fade_ms=10.0, silence_ms=20.0)
fade = int(SAMPLE_RATE * 10.0 / 1000.0)
pad = int(SAMPLE_RATE * 20.0 / 1000.0)
assert out.numel() == SAMPLE_RATE + pad
assert torch.all(out[-pad:] == 0.0), "trailing pad must be silent"
assert out[SAMPLE_RATE - fade - 1] == 1.0, "the fade must not reach back past fade_ms"
# Monotonically ramped to zero — the pad alone would leave the click.
ramp = out[SAMPLE_RATE - fade : SAMPLE_RATE]
assert torch.all(ramp[:-1] >= ramp[1:])
assert ramp[-1] == 0.0
def test_end_of_speech_tail_is_a_no_op_by_default() -> None:
audio = torch.ones(128)
out = apply_end_of_speech_tail(audio, SAMPLE_RATE, fade_ms=0.0, silence_ms=0.0)
assert out is audio
def _repeat_count(ids: list[int], cfg: GepardConfig) -> int:
"""How many ``[SOT ... EOT]`` copies the layout carries."""
return ids.count(cfg.start_of_text)
def test_gepard_prompt_layout() -> None:
"""The prompt carries the speaker slots and exactly one SOS, at the end."""
cfg = GepardConfig()
ids = build_gepard_prompt_ids([1, 2, 3], config=cfg)
slots = [cfg.speaker_token_base + i for i in range(cfg.num_speaker_prefix)]
assert ids[: cfg.num_speaker_prefix] == slots
assert ids.count(cfg.start_of_speech) == 1, "only the canonical copy may carry SOS"
assert ids[-1] == cfg.start_of_speech, "the first frame is sampled from the SOS position"
def test_short_text_repeats_to_reach_the_target_token_mass() -> None:
"""A short text is repeated until its region holds ~target_text_tokens."""
cfg = GepardConfig()
text = [1, 2, 3, 4] # target 16 / 4 -> 4 copies
ids = build_gepard_prompt_ids(text, config=cfg)
assert _repeat_count(ids, cfg) == 4
assert ids.count(cfg.start_of_speech) == 1, "only the last copy opens the speech region"
assert ids[-1] == cfg.start_of_speech
@pytest.mark.parametrize(
("num_text_tokens", "repeated"),
[
(APPLY_BELOW - 1, True),
(APPLY_BELOW, False),
(APPLY_BELOW + 1, False),
],
)
def test_apply_below_is_the_repetition_boundary(num_text_tokens: int, repeated: bool) -> None:
"""Both sides of the threshold: at apply_below the text is left alone.
The layout has to match training here or WER collapses, and the boundary
is where an off-by-one would be invisible in a single happy-path clip.
"""
cfg = GepardConfig()
ids = build_gepard_prompt_ids(list(range(1, num_text_tokens + 1)), config=cfg)
assert (_repeat_count(ids, cfg) > 1) is repeated
def test_max_repeats_caps_a_one_token_text() -> None:
"""target/1 would ask for 16 copies; the cap keeps the prefill bounded."""
cfg = GepardConfig()
ids = build_gepard_prompt_ids([1], config=cfg)
assert _repeat_count(ids, cfg) == cfg.text_repetition_max_repeats
def test_repetition_thresholds_are_read_from_the_config() -> None:
"""The numbers come from the checkpoint sidecar, not from literals.
A checkpoint whose text_repetition block differs must change the layout;
duplicating the values in the builder would silently ignore it.
"""
cfg = GepardConfig(text_repetition={"enabled": True, "target_text_tokens": 9, "apply_below": 8, "max_repeats": 2})
assert _repeat_count(build_gepard_prompt_ids([1, 2, 3], config=cfg), cfg) == 2 # ceil(9/3)=3, capped at 2
assert _repeat_count(build_gepard_prompt_ids(list(range(8)), config=cfg), cfg) == 1 # at apply_below
def test_repetition_can_be_disabled() -> None:
cfg = GepardConfig(text_repetition={"enabled": False})
ids = build_gepard_prompt_ids([1], config=cfg)
assert _repeat_count(ids, cfg) == 1
assert ids[-1] == cfg.start_of_speech
def test_empty_text_is_rejected() -> None:
"""An empty text would still assemble a valid layout and be voiced.
``[slots, SOT, EOT, SOS]`` parses, prefills and generates: the failure is
audible, not raised, which is the worst shape for a caller to debug.
"""
with pytest.raises(ValueError, match="at least one text token"):
build_gepard_prompt_ids([], config=GepardConfig())
def test_packaged_deploy_config_is_where_the_example_looks_for_it() -> None:
"""The example resolves its default off ``vllm_omni.__file__``; tests reach
the same YAML through ``tests.helpers.stage_config``, so nothing covered the
packaged location until this.
``pipeline: gepard`` is the load-bearing key: the checkpoint self-identifies
as qwen3_5_text, so without the pin the architectures fallback routes it to
the diffusion registry.
This asserts the location, not the example's constant — importing the
example would pull ``Omni`` and NeMo into a CPU test. Running the quick
start verbatim stays a release check.
"""
path = Path(vllm_omni.__file__).resolve().parent / "deploy" / "gepard.yaml"
assert path.is_file(), f"packaged deploy config missing at {path}"
deploy = load_deploy_config(path)
assert deploy.pipeline == "gepard"
assert deploy.stages, "deploy config declares no stages"