Files
vllm-omni/tests/entrypoints/openai_api/test_gepard_tts_adapter.py
2026-09-15 14:56:51 -07:00

277 lines
11 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU tests for the Gepard-1.0 speech adapter.
Covers detection, the reject matrix, voice default, empty input, extra_params,
max_new_tokens frame bounds, and build() parity with the offline prompt helper.
"""
from __future__ import annotations
import asyncio
from types import SimpleNamespace
import pytest
import torch
from pydantic import ValidationError
from pytest_mock import MockerFixture
from vllm_omni.entrypoints.openai.protocol.audio import OpenAICreateSpeechRequest
from vllm_omni.entrypoints.openai.serving_speech import OmniOpenAIServingSpeech
from vllm_omni.entrypoints.openai.tts_adapters import detect_tts_model_type, resolve_adapter
from vllm_omni.entrypoints.openai.tts_adapters.base import SpeechServingContext
from vllm_omni.entrypoints.openai.tts_adapters.gepard import GepardAdapter
from vllm_omni.model_executor.models.gepard.configuration_gepard import GepardConfig
from vllm_omni.model_executor.models.gepard.prompt import build_gepard_prompt_ids
from vllm_omni.outputs import OmniRequestOutput
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
_MODEL = "nineninesix/gepard-1.0"
def _adapter(*, revision: str | None = None) -> GepardAdapter:
engine = SimpleNamespace(model_config=SimpleNamespace(model=_MODEL, revision=revision))
return GepardAdapter(SpeechServingContext(server=SimpleNamespace(engine_client=engine), engine_client=engine))
def test_gepard_detection_and_registration() -> None:
assert resolve_adapter("gepard") is GepardAdapter
assert GepardAdapter.stage_keys == {"gepard"}
assert GepardAdapter.model_archs == {"GepardTalkerForConditionalGeneration"}
assert detect_tts_model_type("gepard", None) == "gepard"
assert detect_tts_model_type("gepard", "GepardTalkerForConditionalGeneration") == "gepard"
assert detect_tts_model_type(None, "GepardTalkerForConditionalGeneration") == "gepard"
def test_gepard_accepts_omitted_and_default_voice() -> None:
adapter = _adapter()
assert adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.")) is None
assert adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", voice="default")) is None
assert adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", voice="Default")) is None
assert (
adapter.validate(
OpenAICreateSpeechRequest.model_validate({"input": "Hello from Gepard.", "speaker": "default"})
)
is None
)
@pytest.mark.parametrize(
("kwargs", "field"),
[
({"voice": "vivian"}, "voice"),
({"speed": 1.2}, "speed"),
({"extra_params": {"temperature": 0.3}}, "extra_params"),
({"extra_params": {"top_p": 0.9}}, "extra_params"),
({"extra_params": {"top_k": 20}}, "extra_params"),
({"extra_params": {"foo": 1}}, "extra_params"),
({"ref_audio": "https://example.com/ref.wav"}, "ref_audio"),
({"ref_text": "transcript"}, "ref_text"),
({"ref_audio_2": "https://example.com/ref2.wav"}, "ref_audio_2"),
({"speaker_embedding": [0.1, 0.2]}, "speaker_embedding"),
({"x_vector_only_mode": True}, "x_vector_only_mode"),
({"task_type": "Base"}, "task_type"),
({"non_streaming_mode": True}, "non_streaming_mode"),
({"instructions": "speak happily"}, "instructions"),
({"language": "English"}, "language"),
({"ambient_sound": "rain"}, "ambient_sound"),
({"duration_seconds": 2.0}, "duration_seconds"),
({"initial_codec_chunk_frames": 4}, "initial_codec_chunk_frames"),
({"word_timestamps": True}, "word_timestamps"),
({"response_format": "opus"}, "opus"),
],
)
def test_gepard_rejects_unsupported_fields(kwargs: dict, field: str) -> None:
adapter = _adapter()
err = adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", **kwargs))
assert err is not None
assert field in err
@pytest.mark.parametrize("text", ["", " ", "\n\t"])
def test_gepard_rejects_empty_input(text: str) -> None:
adapter = _adapter()
err = adapter.validate(OpenAICreateSpeechRequest(input=text))
assert err is not None
assert "empty" in err.lower()
@pytest.mark.parametrize("max_new_tokens", [0, -1])
def test_gepard_schema_rejects_non_positive_max_new_tokens(max_new_tokens: int) -> None:
with pytest.raises(ValidationError, match="greater than or equal to 1"):
OpenAICreateSpeechRequest(input="Hello from Gepard.", max_new_tokens=max_new_tokens)
def test_gepard_rejects_max_new_tokens_above_frame_budget() -> None:
adapter = _adapter()
err = adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", max_new_tokens=4097))
assert err is not None
assert "max_new_tokens" in err
assert "frames" in err
def test_gepard_accepts_max_new_tokens_bounds() -> None:
adapter = _adapter()
assert adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", max_new_tokens=1)) is None
assert adapter.validate(OpenAICreateSpeechRequest(input="Hello from Gepard.", max_new_tokens=4096)) is None
def test_gepard_apply_sampling_overrides_uses_frames() -> None:
adapter = _adapter()
stage_defaults = [SimpleNamespace(max_tokens=1000)]
overridden = adapter.apply_sampling_overrides(
stage_defaults,
OpenAICreateSpeechRequest(input="Hello from Gepard.", max_new_tokens=32),
)
assert overridden[0].max_tokens == 32
assert stage_defaults[0].max_tokens == 1000
def test_gepard_build_matches_offline_prompt_builder(monkeypatch) -> None:
adapter = _adapter()
cfg = GepardConfig()
monkeypatch.setattr(
"vllm_omni.model_executor.models.gepard.configuration_gepard.GepardConfig.from_checkpoint",
staticmethod(lambda *_a, **_k: cfg),
)
class _Tok:
def __call__(self, text, add_special_tokens=False):
return {"input_ids": [11, 12, 13]}
adapter._tokenizer = _Tok()
adapter._gepard_config = cfg
text = "Hello from Gepard."
prepared = asyncio.run(adapter.build(OpenAICreateSpeechRequest(input=text, seed=7), [], False))
expected = build_gepard_prompt_ids([11, 12, 13], config=cfg)
assert prepared.prompt["prompt_token_ids"] == expected
assert prepared.prompt["additional_information"] == {"text": [text]}
assert prepared.model_type == "gepard"
assert prepared.output_policy.accumulate_nonstreaming is True
def test_gepard_loads_tokenizer_and_config_at_served_revision(monkeypatch) -> None:
adapter = _adapter(revision="abc123")
seen: dict[str, object] = {}
def fake_from_checkpoint(model, backbone_config=None, revision=None):
seen["config"] = (model, revision)
return GepardConfig()
class _Tok:
def __init__(self, *args, **kwargs):
seen["tok"] = (args[0] if args else None, kwargs.get("revision"))
def __call__(self, text, add_special_tokens=False):
return {"input_ids": [11, 12, 13]}
monkeypatch.setattr(
"vllm_omni.model_executor.models.gepard.configuration_gepard.GepardConfig.from_checkpoint",
staticmethod(fake_from_checkpoint),
)
monkeypatch.setattr("transformers.AutoTokenizer.from_pretrained", _Tok)
prepared = asyncio.run(adapter.build(OpenAICreateSpeechRequest(input="Hello from Gepard."), [], False))
assert seen["config"] == (_MODEL, "abc123")
assert seen["tok"] == (_MODEL, "abc123")
assert prepared.prompt["prompt_token_ids"] == build_gepard_prompt_ids([11, 12, 13], config=GepardConfig())
def _gepard_mock_output(sample_rate: int = 22050, num_samples: int = 2048) -> OmniRequestOutput:
class MockCompletionOutput:
def __init__(self) -> None:
self.index = 0
self.text = ""
self.token_ids: list[int] = []
self.finish_reason = "stop"
self.stop_reason = None
self.logprobs = None
class MockRequestOutput:
def __init__(self) -> None:
self.request_id = "speech-gepard-mock"
self.outputs = [MockCompletionOutput()]
self.multimodal_output = {
"audio": torch.zeros(num_samples),
"sr": torch.tensor(sample_rate),
}
self.finished = True
self.prompt_token_ids = None
self.encoder_prompt_token_ids = None
self.num_cached_tokens = None
self.prompt_logprobs = None
self.kv_transfer_params = None
output = OmniRequestOutput.from_stage_output(MockRequestOutput(), stage_id=0, final_output_type="audio")
output.metrics = {"stage_metrics": {"0": {"num_tokens_in": 0, "num_tokens_out": 2}}}
return output
@pytest.fixture
def gepard_server(mocker: MockerFixture):
mocker.patch(
"vllm_omni.entrypoints.openai.tts_adapters.base.load_supported_speakers",
return_value={"default"},
)
mocker.patch(
"vllm_omni.entrypoints.openai.tts_adapters.base.load_codec_frame_rate",
return_value=22050.0,
)
async def _gen(*_args, **_kwargs):
yield _gepard_mock_output()
mock_engine_client = mocker.MagicMock()
mock_engine_client.errored = False
mock_engine_client.model_config = mocker.MagicMock(model=_MODEL, async_chunk=False, revision=None)
mock_engine_client.default_sampling_params_list = [SimpleNamespace(max_tokens=1000, seed=42, extra_args=None)]
mock_engine_client.tts_batch_max_items = 32
mock_engine_client.generate = mocker.MagicMock(side_effect=lambda **_k: _gen())
mock_engine_client.stage_configs = [
SimpleNamespace(
engine_args=SimpleNamespace(model_stage="gepard", model_arch="GepardTalkerForConditionalGeneration"),
tts_args={},
)
]
mock_models = mocker.MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingSpeech(
engine_client=mock_engine_client,
models=mock_models,
request_logger=mocker.MagicMock(),
)
cfg = GepardConfig()
server._adapter._gepard_config = cfg
server._adapter._tokenizer = lambda text, add_special_tokens=False: {"input_ids": [11, 12, 13]}
yield server
server.shutdown()
def test_gepard_prepare_uses_prompt_builder(gepard_server) -> None:
request_id, _generator, _ = asyncio.run(
gepard_server._prepare_speech_generation(OpenAICreateSpeechRequest(input="Hello from Gepard."))
)
assert request_id.startswith("speech-")
prompt = gepard_server.engine_client.generate.call_args.kwargs["prompt"]
assert prompt["prompt_token_ids"] == build_gepard_prompt_ids([11, 12, 13], config=GepardConfig())
@pytest.mark.parametrize("response_format", ["wav", "pcm", "flac", "mp3"])
def test_gepard_mocked_engine_encodes_response_formats(gepard_server, response_format: str) -> None:
audio, media_type = asyncio.run(
gepard_server._generate_audio_bytes(
OpenAICreateSpeechRequest(input="Hello from Gepard.", response_format=response_format)
)
)
assert isinstance(audio, (bytes, str))
assert len(audio) > 0
expected_media_type = {"wav": "audio/wav", "pcm": "audio/pcm", "flac": "audio/flac", "mp3": "audio/mpeg"}
assert media_type == expected_media_type[response_format]
def test_gepard_streaming_rejects_non_pcm_wav() -> None:
with pytest.raises(ValidationError, match="requires response_format='pcm' or 'wav'"):
OpenAICreateSpeechRequest(input="Hello from Gepard.", stream=True, response_format="mp3")