Signed-off-by: Yuekai Zhang <zhangyuekai@foxmail.com> Signed-off-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: Yueqian Lin <70319226+linyueqian@users.noreply.github.com>
85 lines
3.0 KiB
Python
85 lines
3.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""E2E online tests for Audex (Nemotron-Labs-Audex-2B) text-to-audio.
|
|
|
|
Covers /v1/audio/speech through the ``audex_tta`` deployment:
|
|
the ``audex_tta`` adapter builds the caption prompt and the serving layer
|
|
injects the RVQ phase contract plus the official default guidance
|
|
(cfg_scale 3.0) server-side.
|
|
"""
|
|
|
|
import os
|
|
|
|
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
|
|
|
import pytest
|
|
|
|
from tests.helpers.mark import hardware_test
|
|
from tests.helpers.runtime import OmniServerParams
|
|
from tests.helpers.stage_config import get_deploy_config_path
|
|
|
|
pytestmark = [pytest.mark.slow, pytest.mark.tts]
|
|
|
|
MODEL = "nvidia/Nemotron-Labs-Audex-2B"
|
|
CAPTION = "Heavy rain falling on a tin roof."
|
|
SAMPLE_RATE = 16_000
|
|
|
|
audex_tta_server_params = [
|
|
pytest.param(
|
|
OmniServerParams(
|
|
model=MODEL,
|
|
stage_config_path=get_deploy_config_path("audex_tta.yaml"),
|
|
server_args=["--trust-remote-code"],
|
|
),
|
|
id="audex_tta",
|
|
)
|
|
]
|
|
# 30B-A3B variant: opt-in (pulls ~60 GB); enable with AUDEX_E2E_30B=1.
|
|
_AUDEX_30B_MODEL = os.environ.get("VLLM_OMNI_AUDEX_30B_MODEL_DIR") or "nvidia/Nemotron-Labs-Audex-30B-A3B"
|
|
if os.environ.get("AUDEX_E2E_30B") == "1":
|
|
audex_tta_server_params.append(
|
|
pytest.param(
|
|
OmniServerParams(
|
|
model=_AUDEX_30B_MODEL,
|
|
stage_config_path=get_deploy_config_path("audex_tta_30b.yaml"),
|
|
server_args=["--trust-remote-code"],
|
|
),
|
|
id="online_tta_30b",
|
|
)
|
|
)
|
|
|
|
|
|
@hardware_test(res={"cuda": "H100"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", audex_tta_server_params, indirect=True)
|
|
def test_audex_tta_online_smoke(omni_server, openai_client, run_level) -> None:
|
|
"""A caption returns a valid finite non-silent WAV at the default cfg 3.0."""
|
|
import io
|
|
import wave
|
|
|
|
import numpy as np
|
|
import requests
|
|
|
|
url = f"{openai_client.base_url.rstrip('/')}/v1/audio/speech"
|
|
resp = requests.post(
|
|
url,
|
|
json={"model": omni_server.model, "input": CAPTION, "response_format": "wav"},
|
|
timeout=600,
|
|
)
|
|
assert resp.status_code == 200, resp.text[:200]
|
|
assert resp.content[:4] == b"RIFF", "not a WAV response"
|
|
|
|
with wave.open(io.BytesIO(resp.content)) as wav:
|
|
assert wav.getframerate() == SAMPLE_RATE, f"unexpected sample rate {wav.getframerate()}"
|
|
frames = wav.readframes(wav.getnframes())
|
|
duration_s = wav.getnframes() / wav.getframerate()
|
|
|
|
audio = np.frombuffer(frames, dtype=np.int16).astype(np.float32) / 32768.0
|
|
assert audio.size > 0, "empty audio body"
|
|
assert np.isfinite(audio).all(), "audio contains NaN/Inf"
|
|
|
|
if run_level in {"advanced_model", "full_model"}:
|
|
# The decode cap is 500 frames = 10 s at 50 fps.
|
|
assert 0.1 <= duration_s <= 10.5, f"unexpected duration={duration_s:.3f}s"
|
|
rms = float(np.sqrt(np.mean(np.square(audio))))
|
|
assert rms > 1e-4, f"audio is near-silent (rms={rms})"
|