Signed-off-by: wangyu <410167048@qq.com> Signed-off-by: lishunyang12 <lishunyang12@163.com> Signed-off-by: [Your Name] <your.email@example.com> Signed-off-by: wangyu <53896905+yenuo26@users.noreply.github.com> Signed-off-by: Bo Li <22713281+bobboli@users.noreply.github.com> Co-authored-by: lishunyang12 <lishunyang12@163.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Bo Li <22713281+bobboli@users.noreply.github.com>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from huggingface_hub import snapshot_download
|
|
|
|
from tests.e2e.online_serving.nemotron_voicechat_realtime_duplex import parse_args, run
|
|
from tests.helpers.mark import hardware_test
|
|
from tests.helpers.runtime import OmniServerParams, get_model_prefix
|
|
from tests.helpers.stage_config import get_deploy_config_path
|
|
|
|
MODEL = os.environ.get("VLLM_TEST_NEMOTRON_VOICECHAT_MODEL", "nvidia/NVIDIA-NemotronLabs-VoiceChat-11B")
|
|
DEPLOY_CONFIG = get_deploy_config_path("nemotron_labs_voicechat_duplex.yaml")
|
|
TOKENIZER = os.environ.get("VLLM_TEST_NEMOTRON_VOICECHAT_LLM_PATH")
|
|
|
|
# The client gives up after CLIENT_TIMEOUT_S. The server-side stage-input
|
|
# deadline must fire first, otherwise a stalled stream surfaces as a bare
|
|
# client TimeoutError with no server-side error at all. See #6816.
|
|
CLIENT_TIMEOUT_S = 300
|
|
SERVER_INPUT_WAIT_TIMEOUT_S = 240
|
|
|
|
_SERVER_ENV: dict[str, str] = {"VLLM_OMNI_INPUT_WAIT_TIMEOUT_S": str(SERVER_INPUT_WAIT_TIMEOUT_S)}
|
|
if TOKENIZER:
|
|
_SERVER_ENV["NEMOTRON_VOICECHAT_LLM_PATH"] = TOKENIZER
|
|
|
|
pytestmark = [pytest.mark.full_model, pytest.mark.omni]
|
|
|
|
|
|
@hardware_test(res={"cuda": ["H100", "B200"]}, num_cards=1)
|
|
@pytest.mark.parametrize(
|
|
"omni_server",
|
|
[
|
|
pytest.param(
|
|
OmniServerParams(
|
|
model=MODEL,
|
|
stage_config_path=DEPLOY_CONFIG,
|
|
env_dict=_SERVER_ENV,
|
|
),
|
|
id="native-duplex",
|
|
)
|
|
],
|
|
indirect=True,
|
|
)
|
|
def test_native_duplex_turn_taking_streams_model_audio(omni_server, tmp_path: Path) -> None:
|
|
model_prefix = get_model_prefix()
|
|
root = Path(model_prefix) / MODEL if model_prefix else Path(MODEL)
|
|
if not root.is_dir():
|
|
root = Path(snapshot_download(MODEL, local_files_only=True))
|
|
args = parse_args(
|
|
(
|
|
f"--url ws://{omni_server.host}:{omni_server.port}/v1/realtime --model {omni_server.model} "
|
|
f"--input-wav {root / 'turn_taking.wav'} --input-channel 0 --max-frames 190 "
|
|
f"--minimum-audio-chunks 48 --minimum-audio-rms 0.001 --no-realtime --timeout-s {CLIENT_TIMEOUT_S} "
|
|
f"--output-dir {tmp_path / 'native_duplex'}"
|
|
).split()
|
|
)
|
|
result = asyncio.run(run(args))
|
|
assert result["ok"] is True
|
|
assert result["input_frames"] == 190
|
|
assert result["event_counts"]["response.speak"] > 0
|
|
assert result["audio_bytes"] >= result["input_frames"] * 1764 * 2 // 4
|