[Model] Add Tencent AuK speech generation and editing (encoder + diffusion pipeline) (#7385)

Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
Co-authored-by: Sy03 <1370724210@qq.com>
This commit is contained in:
Yueqian LinandSy03 authored and GitHub committed 2026-09-11 20:09:58 +00:00
1 parent d3493384cf
commit c111f3a2d6
28 files changed
+4986

No files matched your search

@@ -0,0 +1,439 @@
#!/usr/bin/env python
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""AuK transformer parity: the correctness oracle for the in-tree port.
Checks that :class:`AuKTransformer` reproduces the upstream ``auk`` reference backbone
on the released ``auk_base`` weights. Both models are built from the same
state dict and fed the same inputs, under the reference's own inference
configuration: fp32 weights with ``torch.autocast(bfloat16)``. That
configuration matters. The reference keeps its rotary frequencies in a buffer,
so casting its weights to bf16 would round both the frequencies and the
integer positions and move the numbers for reasons unrelated to the port.
Cases:
* ``single`` - one conditional forward
* ``cfg`` - the doubled ``cfg_infer`` forward, both branches
* ``masked`` - batch of 2 with padded targets, references and text
* ``rope`` - the local rotary implementation against ``x_transformers``, and
its survival of a cast to bf16
* ``sample`` - an 8-step Euler trajectory against the reference backbone
driven by a transcription of upstream's ODE loop
* ``generator`` - the sampler's two noise paths agreeing
* ``cache`` - the text projection cache and ``clear_cache()``
* ``mask flag`` - ``attn_mask_enabled=False`` reproducing the reference with
its own flag off
This directory deliberately has no ``__init__.py``, unlike its siblings.
pytest's prepend import mode puts the nearest non-package directory on
``sys.path``, and an ``auk`` package here would shadow the upstream ``auk``
distribution this test compares against.
Needs the released checkpoint and a GPU, so it runs locally rather than in
CI. Point ``AUK_CKPT_DIR`` at a directory holding ``auk_base.safetensors`` and
``config.yaml``; the test skips when that is unset or when the upstream
``auk`` package is not importable::
AUK_CKPT_DIR=/path/to/ckpts/AuK CUDA_VISIBLE_DEVICES=6 \
python -m pytest tests/diffusion/models/auk/test_auk_transformer_parity.py -v -s
"""
from __future__ import annotations
import importlib.util
import math
import os
from pathlib import Path
import pytest
import torch
import yaml
from vllm_omni.diffusion.models.auk.auk_transformer import (
AuKTransformer,
Rotary,
_apply_rope,
dit_state_dict,
sample_latents,
)
CKPT_DIR = os.environ.get("AUK_CKPT_DIR")
# Tolerances. The port evaluates rotary embeddings in fp32 and skips the
# reference's redundant uncond text projection, so the two graphs are not
# bit-identical under autocast; they agree well inside bf16 noise.
FORWARD_ATOL = 3e-2
FORWARD_COS = 0.999
SAMPLE_ATOL = 5e-2
SAMPLE_COS = 0.999
ROPE_ATOL = 1e-5
TARGET_FRAMES = 300
REF_FRAMES = 150
TEXT_TOKENS = 40
SHORT_LENS = (220, 90, 25) # target, reference, text for the padded batch row
TIME = 0.37
SAMPLE_STEPS = 8
SWAY_COEF = -1.0
SEED = 1234
DEVICE = "cuda:0"
AUTOCAST_DTYPE = torch.bfloat16
def _skip_reason() -> str | None:
if CKPT_DIR is None:
return "AUK_CKPT_DIR is not set"
if not Path(CKPT_DIR, "auk_base.safetensors").is_file():
return f"no auk_base.safetensors under {CKPT_DIR}"
try:
# Resolving the submodule, not just "auk", also catches the shadow an
# __init__.py in this directory would create.
upstream = importlib.util.find_spec("auk.model")
except ModuleNotFoundError:
upstream = None
if upstream is None:
return "the upstream 'auk' package is not importable or is shadowed by this directory"
if not torch.cuda.is_available():
return "no CUDA device"
return None
SKIP_REASON = _skip_reason()
pytestmark = [
pytest.mark.local_model,
pytest.mark.diffusion,
pytest.mark.gpu,
pytest.mark.cuda,
pytest.mark.skipif(SKIP_REASON is not None, reason=str(SKIP_REASON)),
]
def _arch() -> dict:
"""Read the backbone geometry out of the checkpoint's config."""
config = yaml.safe_load(Path(CKPT_DIR, "config.yaml").read_text())["model"]
return {
"dim": config["arch"]["dim"],
"heads": config["arch"]["heads"],
"dim_head": config["arch"].get("dim_head", 64),
"ff_mult": config["arch"]["ff_mult"],
"text_hidden_dim": config["arch"]["text_hidden_dim"],
"num_layers": config["arch"]["num_layers"],
"num_single_layers": config["arch"]["num_single_layers"],
"latent_dim": config["vae"]["latent_dim"],
}
def _stats(port: torch.Tensor, ref: torch.Tensor) -> tuple[float, float, float]:
"""Return max abs diff, cosine similarity and the reference's RMS."""
a = port.flatten().float()
b = ref.flatten().float()
return (
(a - b).abs().max().item(),
torch.nn.functional.cosine_similarity(a, b, dim=0).item(),
b.pow(2).mean().sqrt().item(),
)
def _assert_close(name: str, port: torch.Tensor, ref: torch.Tensor, atol: float, min_cos: float) -> None:
max_abs, cos, rms = _stats(port, ref)
print(f"[{name}] max|Δ|={max_abs:.4e} cos={cos:.8f} ref_rms={rms:.4f} rel={max_abs / rms:.3e}")
assert max_abs < atol, f"{name}: max|Δ|={max_abs:.4e} >= {atol:.1e} (reference rms {rms:.4f})"
assert cos > min_cos, f"{name}: cos={cos:.8f} <= {min_cos}"
def _autocast():
return torch.autocast("cuda", dtype=AUTOCAST_DTYPE)
@pytest.fixture(scope="module")
def models() -> tuple[torch.nn.Module, AuKTransformer]:
"""The reference and the port, same weights, fp32 on the accelerator."""
from auk.model.flux2_edit import Flux2Edit
from safetensors.torch import load_file
arch = _arch()
device = torch.device(DEVICE)
state = dit_state_dict(load_file(str(Path(CKPT_DIR, "auk_base.safetensors")), device="cpu"))
reference = Flux2Edit(dropout=0.0, attn_backend="torch", attn_mask_enabled=True, **arch)
reference.load_state_dict(state, strict=True)
reference = reference.to(device).eval().requires_grad_(False)
port = AuKTransformer(**arch)
# strict=True is the point of the port: every released tensor lands.
port.load_state_dict(state, strict=True)
port = port.to(device).eval().requires_grad_(False)
del state
return reference, port
@pytest.fixture(scope="module")
def inputs() -> dict[str, torch.Tensor]:
"""Fixed random inputs, as a batch of 2 whose second row is padded."""
device = torch.device(DEVICE)
generator = torch.Generator(device="cpu").manual_seed(SEED)
arch = _arch()
def randn(*shape: int) -> torch.Tensor:
return torch.randn(*shape, generator=generator, dtype=torch.float32).to(device)
def pad_mask(width: int, short: int) -> torch.Tensor:
mask = torch.zeros(2, width, dtype=torch.bool, device=device)
mask[0, :] = True
mask[1, :short] = True
return mask
mask2 = pad_mask(TARGET_FRAMES, SHORT_LENS[0])
ref_mask2 = pad_mask(REF_FRAMES, SHORT_LENS[1])
c_mask2 = pad_mask(TEXT_TOKENS, SHORT_LENS[2])
# Padding rows are all-zero in the text encoder's output, so keep the
# inputs consistent with the masks.
x2 = randn(2, TARGET_FRAMES, arch["latent_dim"]) * mask2.unsqueeze(-1)
ref2 = randn(2, REF_FRAMES, arch["latent_dim"]) * ref_mask2.unsqueeze(-1)
text2 = randn(2, TEXT_TOKENS, arch["text_hidden_dim"]) * c_mask2.unsqueeze(-1)
return {
"x": x2[:1].contiguous(),
"ref": ref2[:1].contiguous(),
"text": text2[:1].contiguous(),
"c_mask": c_mask2[:1].contiguous(),
"x2": x2,
"ref2": ref2,
"text2": text2,
"mask2": mask2,
"ref_mask2": ref_mask2,
"c_mask2": c_mask2,
"time": torch.tensor(TIME, dtype=torch.float32, device=device),
}
def test_single_forward(models, inputs) -> None:
"""One conditional forward: reference against port."""
reference, port = models
kwargs = dict(text=inputs["text"], time=inputs["time"], c_mask=inputs["c_mask"], ref=inputs["ref"])
with torch.no_grad(), _autocast():
want = reference(x=inputs["x"], **kwargs)
got = port(inputs["x"], **kwargs)
assert got.shape == want.shape == (1, TARGET_FRAMES, port.latent_dim)
_assert_close("single", got, want, FORWARD_ATOL, FORWARD_COS)
def test_cfg_forward(models, inputs) -> None:
"""The doubled cfg_infer forward, cond branch first."""
reference, port = models
kwargs = dict(
text=inputs["text"],
time=inputs["time"],
c_mask=inputs["c_mask"],
ref=inputs["ref"],
cfg_infer=True,
)
with torch.no_grad(), _autocast():
want = reference(x=inputs["x"], **kwargs)
got = port(inputs["x"], **kwargs)
assert got.shape == want.shape == (2, TARGET_FRAMES, port.latent_dim)
_assert_close("cfg-cond", got[:1], want[:1], FORWARD_ATOL, FORWARD_COS)
_assert_close("cfg-uncond", got[1:], want[1:], FORWARD_ATOL, FORWARD_COS)
def test_masked_batch_forward(models, inputs) -> None:
"""Batch of 2 with padded targets, references and text.
Only valid target frames are compared. Padded frames are unconstrained:
the reference leaves them to whatever masked attention emits there.
"""
reference, port = models
kwargs = dict(
text=inputs["text2"],
time=inputs["time"],
mask=inputs["mask2"],
c_mask=inputs["c_mask2"],
ref=inputs["ref2"],
ref_mask=inputs["ref_mask2"],
)
with torch.no_grad(), _autocast():
want = reference(x=inputs["x2"], **kwargs)
got = port(inputs["x2"], **kwargs)
assert got.shape == want.shape == (2, TARGET_FRAMES, port.latent_dim)
valid = inputs["mask2"].unsqueeze(-1).expand_as(got)
_assert_close("masked", got[valid], want[valid], FORWARD_ATOL, FORWARD_COS)
def _set_mask_flag(reference, port, enabled: bool) -> None:
"""Flip attn_mask_enabled on both models in place, so no second copy is loaded."""
for module in port.modules():
if hasattr(module, "attn_mask_enabled"):
module.attn_mask_enabled = enabled
for blocks in (reference.transformer_blocks, reference.single_transformer_blocks):
for block in blocks:
block.attn.processor.attn_mask_enabled = enabled
def test_mask_flag_disabled(models, inputs) -> None:
"""``attn_mask_enabled=False`` must track the reference with its own flag off.
With the flag off both models run attention unmasked and still zero the
padded outputs, so the padded batch is compared in full.
"""
reference, port = models
kwargs = dict(
text=inputs["text2"],
time=inputs["time"],
mask=inputs["mask2"],
c_mask=inputs["c_mask2"],
ref=inputs["ref2"],
ref_mask=inputs["ref_mask2"],
)
with torch.no_grad(), _autocast():
masked = port(inputs["x2"], **kwargs)
_set_mask_flag(reference, port, False)
try:
want = reference(x=inputs["x2"], **kwargs)
got = port(inputs["x2"], **kwargs)
finally:
_set_mask_flag(reference, port, True)
_assert_close("mask-flag-off", got, want, FORWARD_ATOL, FORWARD_COS)
# Guard against a vacuous pass: the flag has to actually change something.
assert (got.float() - masked.float()).abs().max().item() > 1e-3
def test_sampler_generator_matches_seed(models, inputs) -> None:
"""A generator seeded with 0 must draw exactly what ``seed=0`` draws."""
_, port = models
common = dict(
gen_frames=TARGET_FRAMES,
nfe=2,
cfg_strength=0.0,
sway_sampling_coef=SWAY_COEF,
text=inputs["text"],
c_mask=inputs["c_mask"],
ref=inputs["ref"],
ref_mask=None,
)
with torch.no_grad(), _autocast():
by_seed = sample_latents(port, seed=0, **common)
by_generator = sample_latents(port, generator=torch.Generator(device=DEVICE).manual_seed(0), **common)
print(f"[generator] max|Δ|={(by_generator.float() - by_seed.float()).abs().max().item():.4e}")
torch.testing.assert_close(by_generator, by_seed, rtol=0, atol=0)
def test_cache_needs_clearing(models, inputs) -> None:
"""The cached text projection is reused until ``clear_cache()``.
``sample_latents`` clears on the way out, so a pipeline that reuses the
module across requests is safe without doing anything. A caller driving
``forward(cache=True)`` itself has to clear when the text changes.
"""
_, port = models
other_text = inputs["text"] * 0.5
kwargs = dict(time=inputs["time"], c_mask=inputs["c_mask"], ref=inputs["ref"], cfg_infer=True)
port.clear_cache()
with torch.no_grad(), _autocast():
want = port(inputs["x"], inputs["text"], **kwargs)
port(inputs["x"], other_text, cache=True, **kwargs)
stale = port(inputs["x"], inputs["text"], cache=True, **kwargs)
port.clear_cache()
fresh = port(inputs["x"], inputs["text"], cache=True, **kwargs)
port.clear_cache()
# The cache is real: the second call reused the first call's projection.
assert (stale.float() - want.float()).abs().max().item() > 1e-3
torch.testing.assert_close(fresh, want, rtol=0, atol=0)
assert port.text_cond is None and port.text_uncond is None
def test_rope_matches_x_transformers() -> None:
"""The local rotary implementation against x_transformers, in fp32."""
from x_transformers.x_transformers import RotaryEmbedding, apply_rotary_pos_emb
device = torch.device(DEVICE)
dim_head, seq_len = 64, TEXT_TOKENS + REF_FRAMES + TARGET_FRAMES
q = torch.randn(2, 24, seq_len, dim_head, dtype=torch.float32, device=device)
freqs = RotaryEmbedding(dim_head).to(device).forward_from_seq_len(seq_len)[0]
want = apply_rotary_pos_emb(q, freqs)
got = _apply_rope(q, Rotary(dim_head).to(device)(seq_len))
max_abs, cos, _ = _stats(got, want)
print(f"[rope] max|Δ|={max_abs:.4e} cos={cos:.10f}")
assert max_abs < ROPE_ATOL, f"rope: max|Δ|={max_abs:.4e} >= {ROPE_ATOL:.1e}"
def test_rope_survives_half_precision_cast() -> None:
"""Casting the module to bf16 must not round the rotary frequencies.
The reference stores them in a buffer, so ``.to(bfloat16)`` rounds both the
frequencies and the integer positions and silently changes every rotation.
The port recomputes them instead.
"""
dim_head, seq_len = 64, TEXT_TOKENS + REF_FRAMES + TARGET_FRAMES
exact = Rotary(dim_head)(seq_len)
got = Rotary(dim_head).to(torch.bfloat16)(seq_len)
assert got.dtype == torch.float32
torch.testing.assert_close(got, exact, rtol=0, atol=0)
def _reference_euler(reference, *, x, text, c_mask, ref, steps, cfg_strength, sway) -> torch.Tensor:
"""Upstream's ODE loop, transcribed.
``CFMEdit.sample`` hands the backbone to ``torchdiffeq.odeint(method=
"euler")``, which on a fixed grid is ``y += (t1 - t0) * f(t0, y)``.
Constructing a real ``CFMEdit`` would drag in the Qwen-Omni text encoder,
so only the backbone here comes from upstream.
"""
t = torch.linspace(0, 1, steps + 1, device=x.device, dtype=torch.float32)
if sway is not None:
t = t + sway * (torch.cos(math.pi / 2 * t) - 1 + t)
shared = dict(text=text, c_mask=c_mask, ref=ref, mask=None, ref_mask=None, cache=True)
for i in range(steps):
if cfg_strength < 1e-5:
v = reference(x=x, time=t[i], **shared)
else:
pred = reference(x=x, time=t[i], cfg_infer=True, **shared)
v_cond, v_uncond = torch.chunk(pred, 2, dim=0)
v = v_cond + (v_cond - v_uncond) * cfg_strength
x = x + (t[i + 1] - t[i]) * v
reference.clear_cache()
return x
@pytest.mark.parametrize("cfg_strength", [0.0, 2.0])
def test_euler_sample(models, inputs, cfg_strength: float) -> None:
"""An 8-step Euler trajectory, with and without guidance."""
reference, port = models
shared = dict(text=inputs["text"], c_mask=inputs["c_mask"], ref=inputs["ref"])
with torch.no_grad(), _autocast():
got = sample_latents(
port,
gen_frames=TARGET_FRAMES,
ref_mask=None,
nfe=SAMPLE_STEPS,
cfg_strength=cfg_strength,
sway_sampling_coef=SWAY_COEF,
seed=SEED,
**shared,
)
# Re-seeding reproduces the noise sample_latents drew for itself.
torch.manual_seed(SEED)
noise = torch.randn(
TARGET_FRAMES, port.latent_dim, device=inputs["ref"].device, dtype=inputs["ref"].dtype
).unsqueeze(0)
want = _reference_euler(
reference, x=noise, steps=SAMPLE_STEPS, cfg_strength=cfg_strength, sway=SWAY_COEF, **shared
)
assert got.shape == want.shape == (1, TARGET_FRAMES, port.latent_dim)
_assert_close(f"sample-cfg{cfg_strength:g}", got, want, SAMPLE_ATOL, SAMPLE_COS)
@@ -0,0 +1,254 @@
#!/usr/bin/env python
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""AuK VAE parity: the numeric oracle for the vendored BigVGAN-flow codec.
Checks ``AuKVAE`` against the AuK reference implementation on real speech, with
both models loaded from the same released ``vae.safetensors``:
* encoder statistics (mean / log-std) agree,
* ``encode(sample=False)`` reproduces the reference mean path,
* ``encode(sample=True)`` reproduces ``encoding_and_normalization`` for a
matching RNG seed,
* ``decode`` reproduces ``denormalize`` + ``inference_from_latents``, in fp32
and (reported, not gated tightly) under bf16 autocast,
* the waveform survives a mean-path round trip.
Runs on GPU in fp32 and needs both the released checkpoint and the reference
``auk`` package importable, so it is a local_model test rather than CI. Point
``AUK_CKPT_DIR`` at the directory holding ``vae.safetensors`` and ``AUK_REF_WAV``
at a speech clip (default ``$AUK_CKPT_DIR/ref.wav``)::
AUK_CKPT_DIR=... AUK_REF_WAV=... python -m pytest \
tests/diffusion/models/auk/test_auk_vae_parity.py -v -s
Unlike its sibling directories this one carries no ``__init__.py`` on purpose: it would put
``tests/diffusion/models`` on ``sys.path`` ahead of site-packages, and this directory's own name
would then shadow the reference ``auk`` package the parity check imports.
"""
from __future__ import annotations
import importlib.util
import os
from pathlib import Path
import pytest
import soundfile as sf
import torch
import torchaudio.functional as AF
from vllm_omni.diffusion.models.auk.auk_vae import AuKVAE
pytestmark = [pytest.mark.local_model, pytest.mark.diffusion, pytest.mark.gpu, pytest.mark.cuda]
# The released model.vae.model_init_kwargs, shared by both models under test.
VAE_CONFIG = {
"upsample_rates": [5, 4, 3, 2, 2, 2],
"upsample_kernel_sizes": [10, 8, 6, 4, 4, 4],
"upsample_initial_channel": 1536,
"resblock_kernel_sizes": [3, 7, 11],
"resblock_dilation_sizes": [[1, 3, 5], [1, 3, 5], [1, 3, 5]],
"downsample_rates": [2, 2, 2, 3, 4, 5],
"downsample_channels": [12, 24, 48, 96, 192, 384, 768],
"snake_logscale": True,
"latent_dim": 64,
"use_vae": True,
"causal": True,
"flow_hidden_channels": 256,
"act_causal": True,
}
SAMPLE_RATE = 24000
HOP_SIZE = 480
LATENT_DIM = 64
SEED = 1234
# Keep the clip short: the parity run shares its GPU with other jobs.
MAX_SECONDS = 4.0
TOLERANCE = 1e-5
DEVICE = "cuda"
_CKPT_DIR = os.environ.get("AUK_CKPT_DIR")
_VAE_PATH = Path(_CKPT_DIR) / "vae.safetensors" if _CKPT_DIR else None
_REF_WAV = Path(os.environ.get("AUK_REF_WAV") or (Path(_CKPT_DIR) / "ref.wav" if _CKPT_DIR else "unset"))
def _reference_importable() -> bool:
"""Resolve ``auk.model``, not ``auk``.
An ``__init__.py`` in this directory would make its own name resolve as ``auk`` and satisfy a
top-level probe, leaving the fixtures to fail at setup instead of skipping. The submodule
probe fails in that case, which is the honest answer.
"""
try:
return importlib.util.find_spec("auk.model") is not None
except (ImportError, ValueError):
return False
def _skip_reason() -> str | None:
if not _CKPT_DIR:
return "set AUK_CKPT_DIR to the directory holding vae.safetensors"
if _VAE_PATH is None or not _VAE_PATH.is_file():
return f"no vae.safetensors under {_CKPT_DIR}"
if not _REF_WAV.is_file():
return f"no reference clip at {_REF_WAV} (set AUK_REF_WAV)"
if not _reference_importable():
return "cannot import the reference auk.model package (an __init__.py here would shadow it)"
if not torch.cuda.is_available():
return "parity runs in fp32 on GPU"
return None
pytestmark.append(pytest.mark.skipif(_skip_reason() is not None, reason=_skip_reason() or ""))
def _max_abs(lhs: torch.Tensor, rhs: torch.Tensor) -> float:
return (lhs.float() - rhs.float()).abs().max().item()
def _snr_db(signal: torch.Tensor, estimate: torch.Tensor) -> float:
noise = (signal.float() - estimate.float()).pow(2).sum()
return (10.0 * torch.log10(signal.float().pow(2).sum() / noise)).item()
@pytest.fixture(scope="module")
def clip() -> torch.Tensor:
"""Mono 24 kHz speech, trimmed to a whole number of latent frames."""
audio, sample_rate = sf.read(str(_REF_WAV), dtype="float32", always_2d=True) # (T, C)
wav = torch.from_numpy(audio).mean(dim=1, keepdim=True).transpose(0, 1)
if sample_rate != SAMPLE_RATE:
wav = AF.resample(wav, sample_rate, SAMPLE_RATE)
samples = min(int(MAX_SECONDS * SAMPLE_RATE), wav.size(-1)) // HOP_SIZE * HOP_SIZE
wav = wav[:, :samples].contiguous().to(DEVICE)
print(f"\n[clip] {_REF_WAV.name}: {samples} samples, {samples / SAMPLE_RATE:.2f} s, {samples // HOP_SIZE} frames")
return wav
@pytest.fixture(scope="module")
def reference(clip: torch.Tensor) -> dict[str, torch.Tensor]:
"""Reference tensors from the upstream model, which is then released so one model fits at a time."""
from auk.model.vae import load_vae_model
from auk.model.vae.bigvgan_flow_vae import BigVGANFlowVAEConfig
model = load_vae_model(
vae_name="BigVGANFlowVAE",
vae_cfg=BigVGANFlowVAEConfig.from_dict(VAE_CONFIG),
vae_ckpt=str(_VAE_PATH),
map_location="cpu",
)
model = model.to(DEVICE).eval()
model.requires_grad_(False)
with torch.no_grad():
mean, log_std = model.audio_encoder(clip.unsqueeze(1)).chunk(2, 1)
scale = torch.sqrt(model.global_log_std.float())
mean_latents = (mean.transpose(1, 2).float() - model.global_mean.float()) / scale
torch.manual_seed(SEED)
sampled_latents, _ = model.encoding_and_normalization(clip.unsqueeze(1))
decoded = model.inference_from_latents(model.denormalize(mean_latents).permute(0, 2, 1))
tensors = {
"mean": mean.clone(),
"log_std": log_std.clone(),
"mean_latents": mean_latents.clone(),
"sampled_latents": sampled_latents.clone(),
"decoded": decoded.squeeze(1).clone(),
}
del model
torch.accelerator.empty_cache()
return tensors
@pytest.fixture(scope="module")
def port(reference: dict[str, torch.Tensor]) -> tuple[AuKVAE, list[str], list[str]]:
"""The vendored module, loaded from the same checkpoint. Depends on ``reference`` for load ordering.
Placed on the accelerator before loading so weight norm is folded there; see ``load_weights``.
"""
model = AuKVAE.from_config(VAE_CONFIG).to(DEVICE)
missing, unexpected = model.load_weights(_VAE_PATH)
model = model.eval()
model.requires_grad_(False)
return model, missing, unexpected
def test_checkpoint_covers_every_inference_tensor(port: tuple[AuKVAE, list[str], list[str]]) -> None:
model, missing, unexpected = port
print(f"[keys] missing={len(missing)} unexpected={len(unexpected)}")
print(f"[keys] unexpected prefixes={sorted({key.split('.')[0] for key in unexpected})}")
assert missing == []
assert unexpected and all(key.startswith("flow.") for key in unexpected)
assert (model.hop_size, model.sample_rate, model.latent_dim) == (HOP_SIZE, SAMPLE_RATE, LATENT_DIM)
def test_encoder_statistics_match(
clip: torch.Tensor, reference: dict[str, torch.Tensor], port: tuple[AuKVAE, list[str], list[str]]
) -> None:
model = port[0]
with torch.no_grad():
mean, log_std = model.audio_encoder(clip.unsqueeze(1)).chunk(2, 1)
mean_diff = _max_abs(mean, reference["mean"])
log_std_diff = _max_abs(log_std, reference["log_std"])
print(f"[encoder] max|dmean|={mean_diff:.3e} max|dlog_std|={log_std_diff:.3e}")
assert mean_diff < TOLERANCE
assert log_std_diff < TOLERANCE
# Folded and hook-computed weights are bit-identical on one device, so the whole encoder is.
assert (mean_diff, log_std_diff) == (0.0, 0.0)
def test_encode_mean_path_matches(
clip: torch.Tensor, reference: dict[str, torch.Tensor], port: tuple[AuKVAE, list[str], list[str]]
) -> None:
model = port[0]
with torch.no_grad():
latents = model.encode(clip)
assert latents.shape == (1, clip.size(-1) // HOP_SIZE, LATENT_DIM)
diff = _max_abs(latents, reference["mean_latents"])
print(f"[encode mean] shape={tuple(latents.shape)} max|d|={diff:.3e}")
assert diff == 0.0
def test_encode_sampled_matches_seeded_reference(
clip: torch.Tensor, reference: dict[str, torch.Tensor], port: tuple[AuKVAE, list[str], list[str]]
) -> None:
model = port[0]
generator = torch.Generator(device=DEVICE)
generator.manual_seed(SEED)
with torch.no_grad():
latents = model.encode(clip, sample=True, generator=generator)
diff = _max_abs(latents, reference["sampled_latents"])
spread = _max_abs(latents, reference["mean_latents"])
print(f"[encode sample] max|d vs seeded reference|={diff:.3e} max|d vs mean path|={spread:.3e}")
assert diff < TOLERANCE
assert diff == 0.0 # a fresh device Generator replays the default one after torch.manual_seed
assert spread > TOLERANCE # the draw actually moved the latents
def test_decode_matches(reference: dict[str, torch.Tensor], port: tuple[AuKVAE, list[str], list[str]]) -> None:
model = port[0]
latents = reference["mean_latents"]
with torch.no_grad():
wav = model.decode(latents)
assert wav.shape == (1, latents.size(1) * HOP_SIZE)
diff = _max_abs(wav, reference["decoded"])
print(f"[decode fp32] shape={tuple(wav.shape)} max|d|={diff:.3e}")
assert diff < TOLERANCE
assert diff == 0.0
with torch.no_grad(), torch.autocast(device_type="cuda", dtype=torch.bfloat16):
wav_bf16 = model.decode(latents)
bf16_diff = _max_abs(wav_bf16, reference["decoded"])
print(f"[decode bf16 autocast] max|d| vs fp32 reference={bf16_diff:.3e} snr={_snr_db(wav, wav_bf16):.2f} dB")
assert torch.isfinite(wav_bf16).all()
assert bf16_diff < 0.5 # sanity only: bf16 is reported, not held to fp32 parity
def test_round_trip_snr(clip: torch.Tensor, port: tuple[AuKVAE, list[str], list[str]]) -> None:
model = port[0]
with torch.no_grad():
wav = model.decode(model.encode(clip))
assert wav.shape == clip.shape
snr = _snr_db(clip, wav)
print(f"[round trip] snr={snr:.2f} dB peak={wav.abs().max().item():.4f}")
assert snr > 10.0
@@ -0,0 +1,514 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""AuK stage-1 pipeline: request parsing on CPU, generation parity on a GPU.
The CPU tests stub the transformer, the codec and the ODE so they exercise only
what the pipeline itself owns: how a request turns into a target length, a
sampling schedule and a noise generator.
The parity test replays the saved upstream reference for the zero-shot TTS case
and reports latent, per-frame and waveform distances rather than asserting bit
equality, because two differences live in the conditioning rather than in the
port: the saved text condition is an fp32 thinker forward while the reference
generated under bf16 autocast, and the reference's stochastic VAE draw came from
the process-global RNG seeded at 1234.
It replays with ``vae_sample=False``. That is the deliberate choice: the
reference re-seeds the global RNG immediately before drawing the ODE noise, so a
per-request generator reproduces that noise exactly (verified bit-identical) as
long as nothing else has drawn from it. Sampling the VAE posterior consumes the
generator first, which moves the noise and produces a different, equally valid
realization; comparing latents then measures the noise, not the port. The
posterior mean costs a reference latent that is 0.045 MSE (0.969 cosine) away
from the draw the reference used, which is the smaller of the two errors.
Generate the reference artifacts once with the upstream package installed
(``tools/auk_parity_reference.py --auk-repo ... --ckpt-dir ... --qwen-dir ...
--out /path/to/auk-parity``; it writes ``parity_ref/<variant>`` and
``fusion_ref``), then run::
AUK_OMNI_CKPT_DIR=/path/to/auk-omni-base \
AUK_PARITY_REF=/path/to/auk-parity/parity_ref/base \
python -m pytest -s tests/diffusion/models/auk/test_pipeline_auk.py
``AUK_OMNI_CKPT_DIR`` is the assembled directory this pipeline loads, which is
not the same thing as the released AuK snapshot that the transformer and codec
parity tests in this directory read from ``AUK_CKPT_DIR``. ``AUK_CKPT_DIR`` is
accepted as a fallback, and the test skips rather than fails when the directory
it resolves to is not an assembled one.
"""
from __future__ import annotations
import json
import math
import os
from pathlib import Path
from typing import Any
import numpy as np
import pytest
import soundfile as sf
import torch
import torch.nn.functional as F
import torchaudio
from torch import nn
from vllm_omni.diffusion.data import OmniDiffusionConfig
from vllm_omni.diffusion.models.auk import pipeline_auk
from vllm_omni.diffusion.models.auk.pipeline_auk import AuKPipeline
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
LATENT_DIM = 64
HOP = 480
SAMPLE_RATE = 24000
TEXT_HIDDEN_DIM = 2048
# The distilled student's grid, exact values from the released checkpoint.
FLASH_T_GRID = [0.0, 0.07612049579620361, 0.2928932309150696, 0.6173166036605835, 1.0]
CKPT_DIR = os.environ.get("AUK_OMNI_CKPT_DIR") or os.environ.get("AUK_CKPT_DIR")
PARITY_REF = os.environ.get("AUK_PARITY_REF")
# An assembled directory is the only thing this pipeline can load.
IS_ASSEMBLED = bool(CKPT_DIR) and (Path(CKPT_DIR).expanduser() / "config.json").is_file()
# Measured on the released base checkpoint: mean frame cosine 0.961 to 0.968,
# relative latent MSE 0.051 to 0.063, log-mel distance 0.34. That spread is
# bf16 against fp32 DiT weights plus where the codec folds its weight norm,
# neither of which is worth gating on. A port that has lost the conditioning
# sits at 0.110 cosine, 1.82 relative MSE and 2.88 log-mel, so these gates
# still separate the two by an order of magnitude.
PARITY_MIN_FRAME_COSINE = 0.93
PARITY_MAX_RELATIVE_MSE = 0.15
PARITY_MAX_MEL_DISTANCE = 0.6
class _StubTransformer(nn.Module):
"""Records its construction arguments; owns one weight so strict loading runs."""
def __init__(
self,
*,
dim: int,
heads: int,
dim_head: int,
ff_mult: float,
latent_dim: int,
text_hidden_dim: int,
num_layers: int,
num_single_layers: int,
attn_mask_enabled: bool = True,
) -> None:
super().__init__()
self.init_kwargs = {
"dim": dim,
"heads": heads,
"dim_head": dim_head,
"ff_mult": ff_mult,
"latent_dim": latent_dim,
"text_hidden_dim": text_hidden_dim,
"num_layers": num_layers,
"num_single_layers": num_single_layers,
"attn_mask_enabled": attn_mask_enabled,
}
self.latent_dim = latent_dim
self.proj = nn.Linear(latent_dim, latent_dim, bias=False)
class _StubVAE(nn.Module):
"""Deterministic stand-in for the codec, one latent frame per hop."""
hop_size = HOP
sample_rate = SAMPLE_RATE
latent_dim = LATENT_DIM
def __init__(self) -> None:
super().__init__()
self.encode_calls: list[dict[str, Any]] = []
self.decode_calls = 0
self.weights_path: str | None = None
self.config: dict[str, Any] = {}
@classmethod
def from_config(cls, cfg: dict[str, Any]) -> _StubVAE:
vae = cls()
vae.config = dict(cfg)
return vae
def load_weights(self, path: str, **_: Any) -> tuple[list[str], list[str]]:
self.weights_path = str(path)
return [], []
def encode(
self,
wav: torch.Tensor,
*,
sample: bool = False,
generator: torch.Generator | None = None,
) -> torch.Tensor:
frames = wav.shape[-1] // HOP
self.encode_calls.append({"samples": int(wav.shape[-1]), "sample": sample, "generator": generator})
if sample:
return torch.randn(1, frames, LATENT_DIM, generator=generator, device=wav.device)
return torch.zeros(1, frames, LATENT_DIM, device=wav.device)
def decode(self, latents: torch.Tensor) -> torch.Tensor:
self.decode_calls += 1
return torch.zeros(1, latents.shape[1] * HOP, device=latents.device)
def _stub_sampler(calls: list[dict[str, Any]]):
"""Record the ODE arguments and return noise drawn from the request generator."""
def sample_latents(dit: nn.Module, **kwargs: Any) -> torch.Tensor:
calls.append(kwargs)
shape = (1, kwargs["gen_frames"], kwargs["latent_dim"])
generator = kwargs.get("generator")
if generator is None:
return torch.zeros(*shape, device=kwargs["device"], dtype=kwargs["dtype"])
return torch.randn(*shape, generator=generator, device=kwargs["device"], dtype=kwargs["dtype"])
return sample_latents
def _write_checkpoint(root: Path, variant: str) -> Path:
"""Write a tiny assembled checkpoint the stubs can load."""
from safetensors.torch import save_file
model_dir = root / f"auk-{variant}"
model_dir.mkdir(parents=True, exist_ok=True)
config = {
"model_type": "auk",
"architectures": ["AuKForConditionalGeneration"],
"dit": {
"dim": 16,
"heads": 2,
"dim_head": 8,
"ff_mult": 2,
"text_hidden_dim": TEXT_HIDDEN_DIM,
"num_layers": 1,
"num_single_layers": 1,
"attn_mask_enabled": True,
},
"vae": {
"latent_dim": LATENT_DIM,
"downsample_rate": HOP,
"target_sample_rate": SAMPLE_RATE,
"model_init_kwargs": {"latent_dim": LATENT_DIM},
},
"variant": variant,
"flash_t_grid": FLASH_T_GRID,
"defaults": {"nfe": 32, "cfg": 2.0, "sway": -1.0},
}
(model_dir / "config.json").write_text(json.dumps(config))
save_file(
{
"transformer.proj.weight": torch.zeros(LATENT_DIM, LATENT_DIM),
"layer_weights": torch.zeros(36),
"layer_scale": torch.ones(1),
},
str(model_dir / "auk.safetensors"),
)
save_file({"global_mean": torch.zeros(LATENT_DIM)}, str(model_dir / "vae.safetensors"))
return model_dir
@pytest.fixture
def build_pipeline(tmp_path, monkeypatch):
"""Build an AuKPipeline whose transformer, codec and ODE are stubs."""
monkeypatch.setattr(pipeline_auk, "get_local_device", lambda: torch.device("cpu"))
monkeypatch.setattr(pipeline_auk, "AuKTransformer", _StubTransformer)
monkeypatch.setattr(pipeline_auk, "AuKVAE", _StubVAE)
def _build(variant: str = "base") -> tuple[AuKPipeline, list[dict[str, Any]]]:
calls: list[dict[str, Any]] = []
monkeypatch.setattr(pipeline_auk, "sample_latents", _stub_sampler(calls))
od_config = OmniDiffusionConfig(
model=str(_write_checkpoint(tmp_path, variant)),
dtype=torch.float32,
model_class_name="AuKPipeline",
)
return AuKPipeline(od_config=od_config), calls
return _build
def _prompt(
*,
tokens: int = 8,
audio: Any = None,
knobs: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build the diffusion prompt the stage input processor would emit."""
prompt: dict[str, Any] = {
"prompt": "",
"prompt_embeds": torch.zeros(tokens, TEXT_HIDDEN_DIM),
"additional_information": {"auk": dict(knobs or {})},
}
if audio is not None:
prompt["multi_modal_data"] = {"audio": audio}
return prompt
def _batch(prompt: dict[str, Any], **sampling: Any) -> DiffusionRequestBatch:
request = OmniDiffusionRequest(
prompt=prompt,
sampling_params=OmniDiffusionSamplingParams(**sampling),
request_id="auk-test-0",
)
return DiffusionRequestBatch(requests=[request])
def _silence(seconds: float, sample_rate: int = SAMPLE_RATE) -> tuple[np.ndarray, int]:
return np.zeros(int(seconds * sample_rate), dtype=np.float32), sample_rate
@pytest.mark.core_model
@pytest.mark.cpu
class TestRequestParsing:
def test_pipeline_declares_audio_output(self, build_pipeline):
pipeline, _ = build_pipeline()
assert pipeline.support_audio_output is True
assert pipeline.audio_sample_rate == SAMPLE_RATE
assert pipeline.supports_request_batch is False
# The warmup request cannot carry an encoder-stage text condition.
assert pipeline.dummy_run_num_frames == 0
def test_config_reaches_the_transformer(self, build_pipeline):
pipeline, _ = build_pipeline()
assert pipeline.dit.init_kwargs["latent_dim"] == LATENT_DIM
assert pipeline.dit.init_kwargs["text_hidden_dim"] == TEXT_HIDDEN_DIM
assert pipeline.dit.init_kwargs["attn_mask_enabled"] is True
def test_gen_seconds_sets_the_target_length(self, build_pipeline):
pipeline, calls = build_pipeline()
outputs = pipeline.forward(_batch(_prompt(audio=_silence(2.0), knobs={"gen_seconds": 6.0}), seed=1))
assert calls[0]["gen_frames"] == math.ceil(6.0 * SAMPLE_RATE / HOP)
assert calls[0]["ref"].shape == (1, 100, LATENT_DIM)
assert calls[0]["ref_mask"].shape == (1, 100)
assert calls[0]["c_mask"].shape == (1, 8)
assert outputs[0].output.shape == (300 * HOP,)
assert outputs[0].output.dtype is torch.float32
def test_target_length_falls_back_to_the_source_clip(self, build_pipeline):
pipeline, calls = build_pipeline()
pipeline.forward(_batch(_prompt(audio=_silence(2.0), knobs={"gen_seconds": None}), seed=1))
assert calls[0]["gen_frames"] == 100
def test_text_only_request_without_a_duration_is_rejected(self, build_pipeline):
pipeline, calls = build_pipeline()
with pytest.raises(ValueError, match="gen_seconds"):
pipeline.forward(_batch(_prompt(knobs={"gen_seconds": None}), seed=1))
assert calls == []
def test_text_only_request_gets_an_empty_reference(self, build_pipeline):
pipeline, calls = build_pipeline()
pipeline.forward(_batch(_prompt(knobs={"gen_seconds": 3.5}), seed=1))
assert calls[0]["ref"].shape == (1, 0, LATENT_DIM)
assert calls[0]["gen_frames"] == 175
def test_source_clip_is_resampled_to_the_codec_rate(self, build_pipeline):
pipeline, _ = build_pipeline()
pipeline.forward(_batch(_prompt(audio=_silence(1.0, 16000), knobs={"gen_seconds": 1.0}), seed=1))
assert pipeline.vae.encode_calls[0]["samples"] == SAMPLE_RATE
def test_stereo_source_is_mixed_to_mono(self, build_pipeline):
pipeline, _ = build_pipeline()
stereo = np.zeros((2, SAMPLE_RATE), dtype=np.float32)
pipeline.forward(_batch(_prompt(audio=(stereo, SAMPLE_RATE), knobs={"gen_seconds": 1.0}), seed=1))
assert pipeline.vae.encode_calls[0]["samples"] == SAMPLE_RATE
def test_missing_text_condition_is_rejected(self, build_pipeline):
pipeline, _ = build_pipeline()
prompt = _prompt(knobs={"gen_seconds": 1.0})
del prompt["prompt_embeds"]
with pytest.raises(ValueError, match="prompt_embeds"):
pipeline.forward(_batch(prompt, seed=1))
def test_base_variant_uses_the_requested_schedule(self, build_pipeline):
pipeline, calls = build_pipeline()
pipeline.forward(
_batch(
_prompt(audio=_silence(1.0), knobs={"gen_seconds": 1.0, "sway": -1.0}),
seed=1,
num_inference_steps=16,
guidance_scale=3.0,
)
)
assert calls[0]["nfe"] == 16
assert calls[0]["cfg_strength"] == 3.0
assert calls[0]["sway_sampling_coef"] == -1.0
assert calls[0]["t_grid"] is None
def test_schedule_defaults_come_from_the_checkpoint(self, build_pipeline):
pipeline, calls = build_pipeline()
pipeline.forward(_batch(_prompt(audio=_silence(1.0), knobs={"gen_seconds": 1.0, "sway": None}), seed=1))
assert calls[0]["nfe"] == 32
assert calls[0]["cfg_strength"] == 2.0
assert calls[0]["sway_sampling_coef"] == -1.0
def test_flash_variant_locks_the_distilled_recipe(self, build_pipeline):
pipeline, calls = build_pipeline("flash")
pipeline.forward(
_batch(
_prompt(audio=_silence(1.0), knobs={"gen_seconds": 1.0, "sway": -1.0}),
seed=1,
num_inference_steps=32,
guidance_scale=2.0,
)
)
assert calls[0]["nfe"] == 4
assert calls[0]["cfg_strength"] == 0.0
assert calls[0]["sway_sampling_coef"] is None
assert calls[0]["t_grid"] == FLASH_T_GRID
def test_noise_comes_from_a_seeded_request_generator(self, build_pipeline):
pipeline, calls = build_pipeline()
outputs = pipeline.forward(
_batch(_prompt(knobs={"gen_seconds": 1.0}), seed=0, output_type="latent"),
)
expected = torch.randn(1, 50, LATENT_DIM, generator=torch.Generator().manual_seed(0))
assert torch.equal(outputs[0].output, expected)
# The global RNG is never seeded on the pipeline's behalf.
assert calls[0]["seed"] is None
assert isinstance(calls[0]["generator"], torch.Generator)
def test_latent_output_type_skips_the_decoder(self, build_pipeline):
pipeline, _ = build_pipeline()
outputs = pipeline.forward(_batch(_prompt(knobs={"gen_seconds": 1.0}), seed=1, output_type="latent"))
assert outputs[0].output.shape == (1, 50, LATENT_DIM)
assert pipeline.vae.decode_calls == 0
def test_vae_sample_draws_from_the_request_generator(self, build_pipeline):
pipeline, _ = build_pipeline()
pipeline.forward(_batch(_prompt(audio=_silence(1.0), knobs={"gen_seconds": 1.0, "vae_sample": True}), seed=1))
call = pipeline.vae.encode_calls[0]
assert call["sample"] is True
assert isinstance(call["generator"], torch.Generator)
def test_one_request_per_forward(self, build_pipeline):
pipeline, _ = build_pipeline()
batch = _batch(_prompt(knobs={"gen_seconds": 1.0}), seed=1)
batch.requests.append(batch.requests[0])
with pytest.raises(AssertionError, match="one request per forward"):
pipeline.forward(batch)
def _reference_audio_path(messages: list[dict[str, Any]]) -> str:
for message in messages:
for item in message.get("content") or ():
if isinstance(item, dict) and item.get("type") == "audio":
path = item.get("audio") or item.get("audio_url")
if path:
return str(path)
raise AssertionError("the saved reference case carries no source clip")
def _log_mel_distance(left: torch.Tensor, right: torch.Tensor, sample_rate: int) -> float:
"""Mean absolute log-mel difference between two mono waveforms."""
mel = torchaudio.transforms.MelSpectrogram(sample_rate=sample_rate, n_fft=1024, hop_length=256, n_mels=80)
length = min(left.shape[-1], right.shape[-1])
spectra = [torch.log(mel(wav[..., :length].float()) + 1e-5) for wav in (left, right)]
return float((spectra[0] - spectra[1]).abs().mean())
@pytest.mark.local_model
@pytest.mark.diffusion
@pytest.mark.skipif(
not (IS_ASSEMBLED and PARITY_REF),
reason="needs AUK_PARITY_REF and AUK_OMNI_CKPT_DIR pointing at an assembled checkpoint directory",
)
def test_zero_shot_tts_tracks_the_upstream_reference():
"""Replay the saved zero-shot TTS case and report the distances."""
parity_dir = Path(PARITY_REF)
fusion_dir = Path(os.environ.get("AUK_FUSION_REF") or parity_dir.parent.parent / "fusion_ref")
reference = torch.load(parity_dir / "zs_tts_en.pt", map_location="cpu", weights_only=False)
fusion = torch.load(fusion_dir / "zs_tts_audio.pt", map_location="cpu", weights_only=False)
# soundfile rather than torchaudio.load: the repo's test convention, and
# torchaudio's decoder needs an ffmpeg the port environment does not have.
samples, source_rate = sf.read(_reference_audio_path(reference["messages"]), dtype="float32", always_2d=True)
source = torch.from_numpy(samples).transpose(0, 1)
knobs = {"gen_seconds": reference["gen_seconds"], "sway": -1.0, "t_grid": None, "vae_sample": False}
prompt = {
"prompt": "",
# The encoder stage emits bf16; the saved fusion is an fp32 forward.
"prompt_embeds": fusion["fused"].to(torch.bfloat16),
"multi_modal_data": {"audio": (source, int(source_rate))},
"additional_information": {"auk": knobs},
}
od_config = OmniDiffusionConfig(model=CKPT_DIR, dtype=torch.bfloat16, model_class_name="AuKPipeline")
pipeline = AuKPipeline(od_config=od_config)
def run(output_type: str) -> torch.Tensor:
batch = _batch(
dict(prompt),
seed=reference["seed"],
num_inference_steps=32,
guidance_scale=2.0,
output_type=output_type,
)
return pipeline.forward(batch)[0].output
# Two deterministic runs: the ODE is seeded, so the latents belong to the
# waveform even though the decode happens in the second call.
latents = run("latent")
waveform = run("pt")
ref_frames = int(reference["ref_latent_lens"][0])
expected = reference["generated"][:, ref_frames:, :].float()
assert latents.shape == expected.shape
assert torch.isfinite(latents).all()
assert torch.isfinite(waveform).all()
assert waveform.shape[-1] == expected.shape[1] * HOP
cosine = F.cosine_similarity(latents[0], expected[0], dim=-1)
metrics = {
"gen_frames": int(expected.shape[1]),
"ref_frames": ref_frames,
"latent_mse": float(torch.mean((latents - expected) ** 2)),
"latent_relative_mse": float(torch.mean((latents - expected) ** 2) / expected.var()),
"frame_cosine_mean": float(cosine.mean()),
"frame_cosine_min": float(cosine.min()),
"log_mel_distance": _log_mel_distance(waveform, reference["audio"].reshape(-1), int(reference["sr"])),
"rms_port": float(waveform.pow(2).mean().sqrt()),
"rms_reference": float(reference["audio"].reshape(-1).pow(2).mean().sqrt()),
}
print("\nAuK zero-shot TTS parity:", json.dumps(metrics, indent=1))
assert metrics["frame_cosine_mean"] > PARITY_MIN_FRAME_COSINE
assert metrics["latent_relative_mse"] < PARITY_MAX_RELATIVE_MSE
assert metrics["log_mel_distance"] < PARITY_MAX_MEL_DISTANCE
+121
View File
@@ -0,0 +1,121 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Offline E2E smoke test for AuK (encoder stage -> diffusion stage).
AuK ships as ``config.yaml`` plus loose safetensors and needs the Qwen2.5-Omni-3B
snapshot next to it, so the test expects an assembled directory produced by
``tools/prepare_auk_checkpoint.py`` and skips when it is not available:
VLLM_OMNI_AUK_MODEL_DIR=/path/to/auk-omni python -m pytest tests/e2e/offline_inference/test_auk.py
Two requests are exercised: zero-shot TTS from a reference clip and text-only
instruct TTS. The checks are structural (duration, sample rate, level,
finiteness); fidelity against the upstream implementation is covered by the
parity suites under ``tests/diffusion/models/auk``.
"""
from __future__ import annotations
import functools
import math
import os
from pathlib import Path
import numpy as np
import pytest
import soundfile as sf
from tests.helpers.mark import hardware_test
from tests.helpers.media import get_asset_path
from tests.helpers.runtime import OmniRunner
from tests.helpers.stage_config import get_deploy_config_path
from vllm_omni.model_extras.auk import HOP, SAMPLE_RATE, auk_prompt, auk_sampling_params
MODEL_DIR_ENV = "VLLM_OMNI_AUK_MODEL_DIR"
# Any clean speech clip works as the cloning reference; reuse the vendored
# CosyVoice3 prompt so no new binary asset is needed.
REFERENCE_WAV_PATH = get_asset_path("cosyvoice3/zero_shot_prompt.wav")
ZERO_SHOT_TEXT = "The quick brown fox jumps over the lazy dog."
ZERO_SHOT_SECONDS = 3.0
# The upstream cookbook template: description first, then the content.
INSTRUCT_TEXT = (
'Generate speech based on the following description: "A calm young woman speaking warmly and slowly.". '
'The content to speak is: "Welcome back, how was your day?".'
)
INSTRUCT_SECONDS = 2.5
# 8 Euler steps keep the test short; the base recipe is 32.
TEST_NFE = 8
_model_dir = os.environ.get(MODEL_DIR_ENV)
pytestmark = [
pytest.mark.slow,
pytest.mark.tts,
pytest.mark.skipif(not _model_dir, reason=f"set {MODEL_DIR_ENV} to an assembled AuK directory"),
pytest.mark.parametrize(
"omni_runner",
[pytest.param((str(Path(_model_dir or ".").resolve()), get_deploy_config_path("auk.yaml"), {}), id="auk")],
indirect=True,
),
]
@functools.lru_cache(maxsize=1)
def _load_reference_wav() -> tuple[np.ndarray, int]:
audio, sr = sf.read(str(REFERENCE_WAV_PATH), dtype="float32", always_2d=False)
if isinstance(audio, np.ndarray) and audio.ndim > 1:
audio = np.mean(audio, axis=-1)
return np.asarray(audio, dtype=np.float32), int(sr)
def _audio_from_output(output) -> tuple[np.ndarray, int]:
mm = getattr(output, "multimodal_output", None) or {}
assert "audio" in mm, f"no audio in the final output; keys={sorted(mm)}"
audio = mm["audio"]
if isinstance(audio, list):
audio = np.concatenate([np.asarray(a, dtype=np.float32).reshape(-1) for a in audio])
if hasattr(audio, "detach"):
audio = audio.detach().cpu().float().numpy()
audio = np.asarray(audio, dtype=np.float32).reshape(-1)
sr = mm.get("audio_sample_rate", SAMPLE_RATE)
if isinstance(sr, list) and sr:
sr = sr[-1]
if hasattr(sr, "item"):
sr = sr.item()
return audio, int(sr)
def _check_waveform(audio: np.ndarray, sr: int, *, gen_seconds: float) -> None:
assert sr == SAMPLE_RATE
expected = math.ceil(gen_seconds * SAMPLE_RATE / HOP) * HOP
assert audio.size == expected, f"expected {expected} samples for {gen_seconds}s, got {audio.size}"
assert np.isfinite(audio).all(), "non-finite samples"
assert np.abs(audio).max() <= 1.0 + 1e-6, "audio exceeds full scale"
assert float(np.sqrt(np.mean(audio**2))) > 1e-3, "audio is silent"
@hardware_test(res={"cuda": "H100"}, num_cards=1)
def test_auk_zero_shot_tts(omni_runner: OmniRunner) -> None:
"""A reference clip plus an instruction yields speech of the requested length."""
wav, sr = _load_reference_wav()
prompt = auk_prompt(
f"Say the following with the same voice: '{ZERO_SHOT_TEXT}'",
(wav, sr),
gen_seconds=ZERO_SHOT_SECONDS,
)
outputs = omni_runner.omni.generate(prompt, auk_sampling_params(nfe=TEST_NFE, cfg=2.0, seed=0))
assert outputs, "no outputs returned"
audio, out_sr = _audio_from_output(outputs[0])
_check_waveform(audio, out_sr, gen_seconds=ZERO_SHOT_SECONDS)
@hardware_test(res={"cuda": "H100"}, num_cards=1)
def test_auk_instruct_tts_text_only(omni_runner: OmniRunner) -> None:
"""A text-only request (no reference audio) takes the no-prompt-audio path."""
prompt = auk_prompt(INSTRUCT_TEXT, None, gen_seconds=INSTRUCT_SECONDS)
assert prompt["prompt"].rstrip().endswith("<|im_start|>assistant\n".rstrip())
assert "|<no_prompt_audio>|" in prompt["prompt"]
outputs = omni_runner.omni.generate(prompt, auk_sampling_params(nfe=TEST_NFE, cfg=2.0, seed=0))
assert outputs, "no outputs returned"
audio, out_sr = _audio_from_output(outputs[0])
_check_waveform(audio, out_sr, gen_seconds=INSTRUCT_SECONDS)
@@ -0,0 +1,188 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Unit tests for the AuK encoder -> diffusion stage input processor."""
from types import SimpleNamespace
from typing import Any
import numpy as np
import pytest
import torch
from vllm_omni.model_executor.stage_input_processors.auk import encoder2dit
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
HIDDEN = 2048
def _request_output(
mm_output: Any,
completion_mm_output: Any = None,
request_id: str | None = None,
) -> SimpleNamespace:
"""Build a minimal encoder-stage output mock."""
return SimpleNamespace(
outputs=[SimpleNamespace(token_ids=[0], multimodal_output=completion_mm_output)],
multimodal_output=mm_output,
request_id=request_id,
)
def _payload(text_cond: Any) -> dict[str, Any]:
"""The slot the encoder actually emits: hidden_states.output."""
return {"hidden_states": {"output": text_cond}}
def _prompt(
*,
audio: bool = False,
gen_seconds: float | None = None,
has_audio: bool | None = None,
) -> dict[str, Any]:
knobs: dict[str, Any] = {
"gen_seconds": gen_seconds,
"sway": -1.0,
"t_grid": None,
"vae_sample": False,
}
if has_audio is not None:
knobs["has_audio"] = has_audio
prompt: dict[str, Any] = {
"prompt": "<|im_start|>system\n",
"additional_information": {"auk": knobs},
}
if audio:
prompt["multi_modal_data"] = {"audio": (np.zeros(24000, dtype=np.float32), 24000)}
return prompt
class TestTextCondExtraction:
def test_tensor_output(self):
cond = torch.zeros(7, HIDDEN, dtype=torch.bfloat16)
out = encoder2dit([_request_output(_payload(cond))], _prompt())
assert out is not None
assert out["prompt"] == ""
assert out["prompt_embeds"].shape == (7, HIDDEN)
assert out["prompt_embeds"].dtype == torch.bfloat16
assert not out["prompt_embeds"].is_cuda
def test_list_of_tensors_is_concatenated(self):
parts = [torch.zeros(3, HIDDEN), torch.ones(4, HIDDEN)]
out = encoder2dit([_request_output(_payload(parts))], _prompt())
assert out["prompt_embeds"].shape == (7, HIDDEN)
assert torch.equal(out["prompt_embeds"][3:], torch.ones(4, HIDDEN))
def test_single_element_list(self):
out = encoder2dit([_request_output(_payload([torch.zeros(5, HIDDEN)]))], _prompt())
assert out["prompt_embeds"].shape == (5, HIDDEN)
def test_numpy_output(self):
cond = np.zeros((4, HIDDEN), dtype=np.float32)
out = encoder2dit([_request_output(_payload(cond))], _prompt())
assert isinstance(out["prompt_embeds"], torch.Tensor)
assert out["prompt_embeds"].shape == (4, HIDDEN)
def test_completion_output_fallback(self):
cond = torch.zeros(6, HIDDEN)
out = encoder2dit(
[_request_output(None, completion_mm_output=_payload(cond))],
_prompt(),
)
assert out["prompt_embeds"].shape == (6, HIDDEN)
def test_hidden_states_as_a_struct(self):
"""A deserialized payload arrives as a struct, not a mapping."""
cond = torch.zeros(9, HIDDEN)
mm_output = {"hidden_states": SimpleNamespace(output=cond, layers=None)}
out = encoder2dit([_request_output(mm_output)], _prompt())
assert out["prompt_embeds"].shape == (9, HIDDEN)
def test_flattened_payload_key(self):
cond = torch.zeros(8, HIDDEN)
out = encoder2dit([_request_output({"hidden_states.output": cond})], _prompt())
assert out["prompt_embeds"].shape == (8, HIDDEN)
def test_direct_text_cond_key(self):
cond = torch.zeros(2, HIDDEN)
out = encoder2dit([_request_output({"text_cond": cond})], _prompt())
assert out["prompt_embeds"].shape == (2, HIDDEN)
def test_no_source_outputs_returns_none(self):
assert encoder2dit([], _prompt()) is None
def test_missing_payload_raises(self):
with pytest.raises(ValueError, match="hidden_states.output"):
encoder2dit([_request_output({})], _prompt())
def test_empty_hidden_states_slot_raises(self):
with pytest.raises(ValueError, match="hidden_states.output"):
encoder2dit([_request_output({"hidden_states": {}})], _prompt())
def test_empty_multimodal_output_raises(self):
with pytest.raises(ValueError, match="hidden_states.output"):
encoder2dit([_request_output(None)], _prompt())
def test_empty_list_raises(self):
with pytest.raises(ValueError, match="empty text condition"):
encoder2dit([_request_output(_payload([]))], _prompt())
def test_wrong_rank_raises(self):
with pytest.raises(ValueError, match=r"\[tokens, hidden\]"):
encoder2dit([_request_output(_payload(torch.zeros(2, 7, HIDDEN)))], _prompt())
class TestKnobPropagation:
def test_knobs_are_forwarded(self):
prompt = _prompt(audio=True, gen_seconds=6.0)
prompt["additional_information"]["auk"]["t_grid"] = [0.0, 0.5, 1.0]
prompt["additional_information"]["auk"]["vae_sample"] = True
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], prompt)
knobs = out["additional_information"]["auk"]
assert knobs["gen_seconds"] == 6.0
assert knobs["sway"] == -1.0
assert knobs["t_grid"] == [0.0, 0.5, 1.0]
assert knobs["vae_sample"] is True
def test_sampling_knobs_are_not_copied(self):
"""nfe/cfg/seed ride the stage-1 sampling params, not the prompt."""
prompt = _prompt()
prompt["additional_information"]["auk"].update({"nfe": 8, "cfg": 1.0, "seed": 3})
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], prompt)
knobs = out["additional_information"]["auk"]
assert set(knobs) == {"gen_seconds", "sway", "t_grid", "vae_sample", "has_audio"}
def test_multi_modal_data_is_forwarded(self):
prompt = _prompt(audio=True)
out = encoder2dit(
[_request_output(_payload(torch.zeros(3, HIDDEN)))],
prompt,
requires_multimodal_data=True,
)
assert out["multi_modal_data"] is prompt["multi_modal_data"]
def test_text_only_request_has_no_multi_modal_data(self):
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], _prompt())
assert out["multi_modal_data"] is None
assert out["additional_information"]["auk"]["has_audio"] is False
def test_has_audio_declared_by_the_prompt_wins(self):
prompt = _prompt(audio=True, has_audio=True)
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], prompt)
assert out["additional_information"]["auk"]["has_audio"] is True
def test_has_audio_inferred_from_multi_modal_data(self):
prompt = _prompt(audio=True)
prompt["additional_information"]["auk"].pop("has_audio", None)
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], prompt)
assert out["additional_information"]["auk"]["has_audio"] is True
def test_prompt_as_list(self):
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], [_prompt(gen_seconds=2.0)])
assert out["additional_information"]["auk"]["gen_seconds"] == 2.0
def test_missing_prompt_yields_empty_knobs(self):
out = encoder2dit([_request_output(_payload(torch.zeros(3, HIDDEN)))], None)
knobs = out["additional_information"]["auk"]
assert knobs["gen_seconds"] is None
assert knobs["has_audio"] is False
+157
View File
@@ -0,0 +1,157 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Unit tests for the AuK request helpers."""
from __future__ import annotations
import numpy as np
import pytest
from vllm import SamplingParams
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.model_extras.auk import (
NO_PROMPT_AUDIO,
auk_prompt,
auk_sampling_params,
resolve_gen_frames,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
INSTRUCTION = "Say the following with the same voice: 'Ladies and gentlemen'"
EXPECTED_WITH_AUDIO = (
"<|im_start|>system\n"
"You are a helpful assistant.<|im_end|>\n"
"<|im_start|>user\n"
f"{INSTRUCTION}<|audio_bos|><|AUDIO|><|audio_eos|><|im_end|>\n"
"<|im_start|>assistant\n"
)
EXPECTED_TEXT_ONLY = (
"<|im_start|>system\n"
"You are a helpful assistant.<|im_end|>\n"
"<|im_start|>user\n"
f"{INSTRUCTION}{NO_PROMPT_AUDIO}<|im_end|>\n"
"<|im_start|>assistant\n"
)
def _audio() -> tuple[np.ndarray, int]:
return np.zeros(24000, dtype=np.float32), 24000
class TestPromptRendering:
def test_with_audio(self):
audio = _audio()
prompt = auk_prompt(INSTRUCTION, audio, gen_seconds=6.0)
assert prompt["prompt"] == EXPECTED_WITH_AUDIO
assert prompt["multi_modal_data"] == {"audio": audio}
assert prompt["additional_information"]["auk"]["has_audio"] is True
def test_text_only_appends_the_marker(self):
prompt = auk_prompt(INSTRUCTION, gen_seconds=6.0)
assert prompt["prompt"] == EXPECTED_TEXT_ONLY
assert "multi_modal_data" not in prompt
assert prompt["additional_information"]["auk"]["has_audio"] is False
def test_marker_is_not_appended_twice(self):
once = auk_prompt(INSTRUCTION, gen_seconds=6.0)
twice = auk_prompt(INSTRUCTION + NO_PROMPT_AUDIO, gen_seconds=6.0)
assert once["prompt"] == twice["prompt"]
assert twice["prompt"].count(NO_PROMPT_AUDIO) == 1
def test_marker_is_not_added_when_audio_is_present(self):
prompt = auk_prompt(INSTRUCTION, _audio())
assert NO_PROMPT_AUDIO not in prompt["prompt"]
def test_knob_defaults(self):
knobs = auk_prompt(INSTRUCTION, _audio())["additional_information"]["auk"]
assert knobs == {
"gen_seconds": None,
"sway": -1.0,
"t_grid": None,
"vae_sample": False,
"has_audio": True,
}
def test_knobs_are_carried(self):
grid = [0.0, 0.25, 1.0]
knobs = auk_prompt(
INSTRUCTION,
_audio(),
gen_seconds=3.5,
sway=0.0,
t_grid=grid,
vae_sample=True,
)["additional_information"]["auk"]
assert knobs["gen_seconds"] == 3.5
assert knobs["sway"] == 0.0
assert knobs["vae_sample"] is True
assert knobs["t_grid"] == grid
# A copy, so a caller mutating its list cannot reach the request.
assert knobs["t_grid"] is not grid
def test_sampling_knobs_are_absent_from_the_prompt(self):
knobs = auk_prompt(INSTRUCTION, _audio())["additional_information"]["auk"]
assert not {"nfe", "cfg", "seed"} & set(knobs)
class TestSamplingParams:
def test_two_stages_in_order(self):
params = auk_sampling_params()
assert len(params) == 2
assert isinstance(params[0], SamplingParams)
assert isinstance(params[1], OmniDiffusionSamplingParams)
def test_encoder_stage_is_prefill_only(self):
encoder = auk_sampling_params()[0]
assert encoder.max_tokens == 1
assert encoder.temperature == 0.0
assert encoder.detokenize is False
def test_defaults_are_the_base_recipe(self):
diffusion = auk_sampling_params()[1]
assert diffusion.num_inference_steps == 32
assert diffusion.guidance_scale == 2.0
assert diffusion.seed == 0
def test_overrides(self):
encoder, diffusion = auk_sampling_params(nfe=4, cfg=0.0, seed=None)
assert diffusion.num_inference_steps == 4
assert diffusion.guidance_scale == 0.0
assert diffusion.seed is None
assert encoder.seed is None
class TestDurationPolicy:
@pytest.mark.parametrize(
("gen_seconds", "expected"),
[
(6.0, 300),
(0.02, 1),
(0.001, 1),
(5.59, 280),
(1.0, 50),
],
)
def test_explicit_duration_rounds_up(self, gen_seconds: float, expected: int):
assert resolve_gen_frames(gen_seconds, 0) == expected
def test_explicit_duration_wins_over_the_source(self):
assert resolve_gen_frames(2.0, 500) == 100
def test_source_length_is_the_default(self):
assert resolve_gen_frames(None, 137) == 137
def test_text_only_without_a_duration_raises(self):
with pytest.raises(ValueError, match="gen_seconds is required"):
resolve_gen_frames(None, 0)
@pytest.mark.parametrize("gen_seconds", [0.0, -1.0])
def test_non_positive_duration_raises(self, gen_seconds: float):
with pytest.raises(ValueError, match="must be positive"):
resolve_gen_frames(gen_seconds, 100)
def test_custom_rate(self):
assert resolve_gen_frames(1.0, 0, sample_rate=16000, hop=320) == 50
@@ -83,6 +83,10 @@ EXCLUDED_MODELS = [
"Gr00tN1d7Pipeline",
"Pi0Pipeline",
"SanaWmPipeline",
# Audio pipeline conditioned by a separate Qwen2.5-Omni encoder stage;
# covered by tests/diffusion/models/auk (CPU pipeline tests, parity) and
# the real-checkpoint E2E test instead of a tiny model.
"AuKPipeline",
]