[Misc] Cleanup: use consistent pytest-mock in unit tests (#2698)
Signed-off-by: yuanheng <jonathan.zhaoyh@gmail.com>
This commit is contained in:
1 parent
2c67c30550
commit
e0cdbe9a5d
26 files changed
+1598
-1369
No files matched your search
@@ -2,10 +2,10 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from vllm_omni.model_executor.models.mimo_audio.config_mimo_audio import TALKER_CODEC_PAD_TOKEN_ID
|
||||
from vllm_omni.model_executor.models.mimo_audio.mimo_audio_code2wav import (
|
||||
@@ -51,7 +51,7 @@ def _make_invalid_flat_immediate_eostm(eostm_id: int = 666) -> torch.Tensor:
|
||||
return g.reshape(-1)
|
||||
|
||||
|
||||
def _minimal_model():
|
||||
def _minimal_model(mocker: MockerFixture):
|
||||
"""Avoid __init__ (HF tokenizer paths); only fields used by _batch_decode_waveforms."""
|
||||
model = object.__new__(MiMoAudioToken2WavForConditionalGenerationVLLM)
|
||||
model.device = torch.device("cpu")
|
||||
@@ -59,7 +59,7 @@ def _minimal_model():
|
||||
model.streamer_config = AudioStreamerConfig(group_size=_GROUP, audio_channels=_AC)
|
||||
model.codes = _codes_ns()
|
||||
|
||||
decode_vq = Mock(
|
||||
decode_vq = mocker.Mock(
|
||||
side_effect=lambda audio_codes: torch.ones(
|
||||
audio_codes.shape[1],
|
||||
7,
|
||||
@@ -67,7 +67,7 @@ def _minimal_model():
|
||||
device=audio_codes.device,
|
||||
)
|
||||
)
|
||||
decoder = Mock()
|
||||
decoder = mocker.Mock()
|
||||
|
||||
audio_tok = SimpleNamespace(
|
||||
encoder=SimpleNamespace(decode_vq=decode_vq),
|
||||
@@ -78,9 +78,9 @@ def _minimal_model():
|
||||
return model, audio_tok
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_empty_input_list():
|
||||
def test_batch_decode_waveforms_empty_input_list(mocker: MockerFixture):
|
||||
"""Empty input list returns a single zero-length float32 tensor on model device."""
|
||||
model, _ = _minimal_model()
|
||||
model, _ = _minimal_model(mocker)
|
||||
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(model, [])
|
||||
assert len(out) == 1
|
||||
assert out[0].dtype == torch.float32
|
||||
@@ -88,9 +88,9 @@ def test_batch_decode_waveforms_empty_input_list():
|
||||
assert out[0].device == model.device
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes():
|
||||
def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes(mocker: MockerFixture):
|
||||
"""Single and multi-request batches produce correctly shaped packed hidden states and trimmed waveforms."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
decoder = audio_tok.decoder
|
||||
|
||||
# Single valid request: decoder output rank-3 for double squeeze path
|
||||
@@ -118,9 +118,9 @@ def test_batch_decode_waveforms_single_vs_multiple_decoder_shapes():
|
||||
assert out2[1].shape == (8 * _FTP,)
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_mixed_valid_invalid_requests():
|
||||
def test_batch_decode_waveforms_mixed_valid_invalid_requests(mocker: MockerFixture):
|
||||
"""Mixed valid and invalid requests: invalid slots get empty tensors, valid slots get decoded waveforms."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
valid_a = _make_valid_flat_codes(1)
|
||||
valid_b = _make_valid_flat_codes(1)
|
||||
dummy = _make_dummy_code_tensor()
|
||||
@@ -151,9 +151,9 @@ def test_batch_decode_waveforms_mixed_valid_invalid_requests():
|
||||
assert input_lengths.tolist() == [4, 4]
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_all_invalid_returns_per_request_empty():
|
||||
def test_batch_decode_waveforms_all_invalid_returns_per_request_empty(mocker: MockerFixture):
|
||||
"""All-invalid batch skips decoder entirely and returns empty tensors for every slot."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(
|
||||
model,
|
||||
[None, _make_dummy_code_tensor(), torch.tensor([], dtype=torch.long)],
|
||||
@@ -163,9 +163,9 @@ def test_batch_decode_waveforms_all_invalid_returns_per_request_empty():
|
||||
audio_tok.decoder.assert_not_called()
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_samples():
|
||||
def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_samples(mocker: MockerFixture):
|
||||
"""Decoder output longer than valid_len is trimmed to the exact expected waveform length."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
flat = _make_valid_flat_codes(1)
|
||||
# Longer than valid_len so branch wav = wav[:valid_len] runs
|
||||
audio_tok.decoder.return_value = torch.ones(1, 1, 10_000, dtype=torch.float32)
|
||||
@@ -175,9 +175,9 @@ def test_batch_decode_waveforms_output_shape_trim_when_decoder_returns_extra_sam
|
||||
assert out[0].dtype == torch.float32
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_returns_extra():
|
||||
def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_returns_extra(mocker: MockerFixture):
|
||||
"""Else-branch split: per-request wav[:valid_len] when decoder pads each batch row."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
a = _make_valid_flat_codes(1)
|
||||
b = _make_valid_flat_codes(2)
|
||||
audio_tok.decoder.return_value = torch.ones(2, 1, 10_000, dtype=torch.float32)
|
||||
@@ -189,9 +189,9 @@ def test_batch_decode_waveforms_multi_request_trims_each_row_when_decoder_return
|
||||
assert out[1].dtype == torch.float32
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices():
|
||||
def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices(mocker: MockerFixture):
|
||||
"""Tensor packing order must match valid_indices when invalid requests are in the middle."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
first = _make_valid_flat_codes(1)
|
||||
last = _make_valid_flat_codes(2)
|
||||
inputs = [
|
||||
@@ -212,9 +212,9 @@ def test_batch_decode_waveforms_valid_only_at_edges_maps_to_correct_indices():
|
||||
assert input_lengths.tolist() == [4, 8]
|
||||
|
||||
|
||||
def test_batch_decode_waveforms_output_shapes_1d_float32_for_all_slots():
|
||||
def test_batch_decode_waveforms_output_shapes_1d_float32_for_all_slots(mocker: MockerFixture):
|
||||
"""Every slot is a 1-D float32 vector (empty or waveform), matching downstream expectations."""
|
||||
model, audio_tok = _minimal_model()
|
||||
model, audio_tok = _minimal_model(mocker)
|
||||
inputs = [_make_valid_flat_codes(1), None, _make_valid_flat_codes(1)]
|
||||
audio_tok.decoder.return_value = torch.ones(2, 1, 5000, dtype=torch.float32)
|
||||
out = MiMoAudioToken2WavForConditionalGenerationVLLM._batch_decode_waveforms(model, inputs)
|
||||
|
||||
@@ -10,10 +10,9 @@ Regression test for: https://github.com/vllm-project/vllm/issues/34506
|
||||
- Interleaved (use_audio_in_video) should also work correctly.
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
from vllm.model_executor.models.qwen2_5_omni_thinker import (
|
||||
check_interleaved_audio_video,
|
||||
merge_interleaved_embeddings,
|
||||
@@ -107,7 +106,7 @@ class TestCheckInterleavedAudioVideo:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def make_mock_model(hidden: int = 8):
|
||||
def make_mock_model(mocker: MockerFixture, hidden: int = 8):
|
||||
"""
|
||||
Return a minimal mock of Qwen2_5OmniThinkerForConditionalGeneration
|
||||
that has enough structure to run embed_input_ids.
|
||||
@@ -116,10 +115,10 @@ def make_mock_model(hidden: int = 8):
|
||||
Qwen2_5OmniThinkerForConditionalGeneration,
|
||||
)
|
||||
|
||||
model = Mock(spec=Qwen2_5OmniThinkerForConditionalGeneration)
|
||||
model = mocker.Mock(spec=Qwen2_5OmniThinkerForConditionalGeneration)
|
||||
|
||||
# Config with token IDs
|
||||
cfg = Mock()
|
||||
cfg = mocker.Mock()
|
||||
cfg.video_token_index = VIDEO_TOKEN_ID
|
||||
cfg.audio_token_index = AUDIO_TOKEN_ID
|
||||
model.config = cfg
|
||||
@@ -130,9 +129,9 @@ def make_mock_model(hidden: int = 8):
|
||||
# view with shared memory, which masked_scatter_ cannot handle).
|
||||
return ids.float().unsqueeze(-1).expand(-1, hidden).clone()
|
||||
|
||||
lang_model = Mock()
|
||||
lang_model = mocker.Mock()
|
||||
lang_model.embed_input_ids = fake_lm_embed
|
||||
model.get_language_model = Mock(return_value=lang_model)
|
||||
model.get_language_model = mocker.Mock(return_value=lang_model)
|
||||
|
||||
from vllm.model_executor.models.interfaces import SupportsMultiModal
|
||||
|
||||
@@ -169,7 +168,7 @@ def build_mm_embeds(audio_n, image_n, video_n, hidden, audio_val=10.0, image_val
|
||||
|
||||
|
||||
class TestEmbedInputIds:
|
||||
def _run(self, audio_n, image_n, video_n, hidden=8):
|
||||
def _run(self, mocker: MockerFixture, audio_n, image_n, video_n, hidden=8):
|
||||
"""
|
||||
Run embed_input_ids for a non-interleaved mixed-modality sequence.
|
||||
Returns (result_embeds, input_ids, is_multimodal).
|
||||
@@ -177,33 +176,33 @@ class TestEmbedInputIds:
|
||||
input_ids, is_multimodal = make_token_seq(audio_n, image_n, video_n)
|
||||
mm_embeds = build_mm_embeds(audio_n, image_n, video_n, hidden)
|
||||
|
||||
model, _ = make_mock_model(hidden)
|
||||
model, _ = make_mock_model(mocker, hidden)
|
||||
result = model.embed_input_ids(input_ids, mm_embeds, is_multimodal=is_multimodal)
|
||||
return result, input_ids, is_multimodal
|
||||
|
||||
def test_audio_only(self):
|
||||
def test_audio_only(self, mocker: MockerFixture):
|
||||
"""Audio-only: audio positions get audio embeddings."""
|
||||
audio_n, hidden = 5, 8
|
||||
audio_val = 10.0
|
||||
result, input_ids, is_multimodal = self._run(audio_n, 0, 0, hidden)
|
||||
result, input_ids, is_multimodal = self._run(mocker, audio_n, 0, 0, hidden)
|
||||
|
||||
audio_pos = (input_ids == AUDIO_TOKEN_ID).nonzero(as_tuple=True)[0]
|
||||
assert result[audio_pos].allclose(torch.full((audio_n, hidden), audio_val)), (
|
||||
"Audio positions should get audio embeddings"
|
||||
)
|
||||
|
||||
def test_video_only(self):
|
||||
def test_video_only(self, mocker: MockerFixture):
|
||||
"""Video-only: video positions get video embeddings."""
|
||||
video_n, hidden = 6, 8
|
||||
video_val = 30.0
|
||||
result, input_ids, is_multimodal = self._run(0, 0, video_n, hidden)
|
||||
result, input_ids, is_multimodal = self._run(mocker, 0, 0, video_n, hidden)
|
||||
|
||||
video_pos = (input_ids == VIDEO_TOKEN_ID).nonzero(as_tuple=True)[0]
|
||||
assert result[video_pos].allclose(torch.full((video_n, hidden), video_val)), (
|
||||
"Video positions should get video embeddings"
|
||||
)
|
||||
|
||||
def test_mixed_modalities_audio_goes_to_audio_pos(self):
|
||||
def test_mixed_modalities_audio_goes_to_audio_pos(self, mocker: MockerFixture):
|
||||
"""
|
||||
Regression test for GitHub issue #34506:
|
||||
With audio + image + video (non-interleaved), audio positions must
|
||||
@@ -212,7 +211,7 @@ class TestEmbedInputIds:
|
||||
audio_n, image_n, video_n, hidden = 5, 4, 6, 8
|
||||
audio_val, image_val, video_val = 10.0, 20.0, 30.0
|
||||
|
||||
result, input_ids, is_multimodal = self._run(audio_n, image_n, video_n, hidden)
|
||||
result, input_ids, is_multimodal = self._run(mocker, audio_n, image_n, video_n, hidden)
|
||||
|
||||
audio_pos = (input_ids == AUDIO_TOKEN_ID).nonzero(as_tuple=True)[0]
|
||||
image_pos = (input_ids == IMAGE_TOKEN_ID).nonzero(as_tuple=True)[0]
|
||||
@@ -233,10 +232,10 @@ class TestEmbedInputIds:
|
||||
f"Video emb wrong: expected {video_val}, got mean={mean_v:.1f}"
|
||||
)
|
||||
|
||||
def test_text_positions_unchanged(self):
|
||||
def test_text_positions_unchanged(self, mocker: MockerFixture):
|
||||
"""Text positions should keep their text embeddings."""
|
||||
audio_n, image_n, video_n, hidden = 3, 2, 4, 8
|
||||
result, input_ids, is_multimodal = self._run(audio_n, image_n, video_n, hidden)
|
||||
result, input_ids, is_multimodal = self._run(mocker, audio_n, image_n, video_n, hidden)
|
||||
|
||||
text_pos = (~is_multimodal).nonzero(as_tuple=True)[0]
|
||||
# Text tokens have value TEXT_TOKEN_ID=0, so embed -> 0.0
|
||||
@@ -244,7 +243,7 @@ class TestEmbedInputIds:
|
||||
"Text positions should keep text embeddings"
|
||||
)
|
||||
|
||||
def test_interleaved_use_audio_in_video(self):
|
||||
def test_interleaved_use_audio_in_video(self, mocker: MockerFixture):
|
||||
"""
|
||||
Interleaved (use_audio_in_video): video chunks interleaved with audio.
|
||||
Video embeddings must go to video positions, audio to audio positions.
|
||||
@@ -263,7 +262,7 @@ class TestEmbedInputIds:
|
||||
torch.full((audio_n, hidden), audio_val),
|
||||
]
|
||||
|
||||
model, _ = make_mock_model(hidden)
|
||||
model, _ = make_mock_model(mocker, hidden)
|
||||
result = model.embed_input_ids(input_ids, mm_embeds, is_multimodal=is_multimodal)
|
||||
|
||||
video_pos = (input_ids == VIDEO_TOKEN_ID).nonzero(as_tuple=True)[0]
|
||||
|
||||
@@ -15,9 +15,10 @@ import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
# Direct file import to avoid vllm_omni.__init__ patch dependencies.
|
||||
_BASE = os.path.join(
|
||||
@@ -41,28 +42,31 @@ def _load_module(name: str, filename: str):
|
||||
return mod
|
||||
|
||||
|
||||
def _build_mock_modules() -> dict[str, object]:
|
||||
def _build_mock_modules(mocker: MockerFixture) -> dict[str, object]:
|
||||
"""Build the dict of modules to inject into sys.modules."""
|
||||
platforms_mock = MagicMock()
|
||||
platforms_mock = mocker.MagicMock()
|
||||
platforms_mock.current_omni_platform.supports_torch_inductor.return_value = False
|
||||
|
||||
logger_mock = MagicMock()
|
||||
logger_mock.init_logger = lambda name: MagicMock()
|
||||
logger_mock = mocker.MagicMock()
|
||||
logger_mock.init_logger = lambda name: mocker.MagicMock()
|
||||
|
||||
vllm_config_mod = MagicMock()
|
||||
vllm_config_mod.set_current_vllm_config = lambda cfg: MagicMock(__enter__=MagicMock(), __exit__=MagicMock())
|
||||
vllm_config_mod = mocker.MagicMock()
|
||||
vllm_config_mod.set_current_vllm_config = lambda cfg: mocker.MagicMock(
|
||||
__enter__=mocker.MagicMock(),
|
||||
__exit__=mocker.MagicMock(),
|
||||
)
|
||||
|
||||
weight_utils_mock = MagicMock()
|
||||
weight_utils_mock = mocker.MagicMock()
|
||||
weight_utils_mock.default_weight_loader = lambda p, w: None
|
||||
|
||||
pkg = types.ModuleType("vllm_omni.model_executor.models.qwen3_tts")
|
||||
pkg.__path__ = [os.path.abspath(_BASE)]
|
||||
|
||||
return {
|
||||
"vllm_omni": MagicMock(),
|
||||
"vllm_omni": mocker.MagicMock(),
|
||||
"vllm_omni.platforms": platforms_mock,
|
||||
"vllm.logger": logger_mock,
|
||||
"vllm.config": MagicMock(),
|
||||
"vllm.config": mocker.MagicMock(),
|
||||
"vllm.config.vllm": vllm_config_mod,
|
||||
"vllm.model_executor.model_loader.weight_utils": weight_utils_mock,
|
||||
"vllm_omni.model_executor": types.ModuleType("vllm_omni.model_executor"),
|
||||
@@ -71,38 +75,47 @@ def _build_mock_modules() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _load_target_classes():
|
||||
def _load_target_classes(mocker: MockerFixture):
|
||||
"""Load config and code predictor modules with mocked dependencies.
|
||||
|
||||
Uses patch.dict to ensure sys.modules is always restored, even on failure.
|
||||
Uses mocker.patch.dict to ensure sys.modules is always restored, even on failure.
|
||||
"""
|
||||
mocks = _build_mock_modules()
|
||||
with patch.dict(sys.modules, mocks):
|
||||
config_mod = _load_module(
|
||||
"vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts",
|
||||
"configuration_qwen3_tts.py",
|
||||
)
|
||||
sys.modules["vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts"] = config_mod
|
||||
mocks = _build_mock_modules(mocker)
|
||||
mocker.patch.dict(sys.modules, mocks)
|
||||
config_mod = _load_module(
|
||||
"vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts",
|
||||
"configuration_qwen3_tts.py",
|
||||
)
|
||||
sys.modules["vllm_omni.model_executor.models.qwen3_tts.configuration_qwen3_tts"] = config_mod
|
||||
|
||||
cp_mod = _load_module(
|
||||
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code_predictor_vllm",
|
||||
"qwen3_tts_code_predictor_vllm.py",
|
||||
)
|
||||
cp_mod = _load_module(
|
||||
"vllm_omni.model_executor.models.qwen3_tts.qwen3_tts_code_predictor_vllm",
|
||||
"qwen3_tts_code_predictor_vllm.py",
|
||||
)
|
||||
|
||||
return config_mod, cp_mod
|
||||
|
||||
|
||||
_config_mod, _cp_mod = _load_target_classes()
|
||||
|
||||
Qwen3TTSTalkerCodePredictorConfig = _config_mod.Qwen3TTSTalkerCodePredictorConfig
|
||||
Qwen3TTSTalkerConfig = _config_mod.Qwen3TTSTalkerConfig
|
||||
CodePredictorWrapper = _cp_mod.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM
|
||||
CodePredictorModel = _cp_mod.Qwen3TTSTalkerCodePredictorModelVLLM
|
||||
@pytest.fixture
|
||||
def loaded_target_classes(mocker: MockerFixture):
|
||||
config_mod, cp_mod = _load_target_classes(mocker)
|
||||
return (
|
||||
config_mod.Qwen3TTSTalkerCodePredictorConfig,
|
||||
config_mod.Qwen3TTSTalkerConfig,
|
||||
cp_mod.Qwen3TTSTalkerCodePredictorForConditionalGenerationVLLM,
|
||||
cp_mod.Qwen3TTSTalkerCodePredictorModelVLLM,
|
||||
)
|
||||
|
||||
|
||||
def _make_tiny_config() -> tuple:
|
||||
def _make_tiny_config(loaded_target_classes) -> tuple:
|
||||
"""Create minimal configs for a tiny code predictor model."""
|
||||
cp_config = Qwen3TTSTalkerCodePredictorConfig(
|
||||
(
|
||||
qwen3_tts_talker_code_predictor_config,
|
||||
qwen3_tts_talker_config,
|
||||
_,
|
||||
_,
|
||||
) = loaded_target_classes
|
||||
cp_config = qwen3_tts_talker_code_predictor_config(
|
||||
vocab_size=64,
|
||||
hidden_size=32,
|
||||
intermediate_size=64,
|
||||
@@ -113,16 +126,16 @@ def _make_tiny_config() -> tuple:
|
||||
num_code_groups=4,
|
||||
rms_norm_eps=1e-6,
|
||||
)
|
||||
talker_config = Qwen3TTSTalkerConfig(
|
||||
talker_config = qwen3_tts_talker_config(
|
||||
hidden_size=32,
|
||||
num_code_groups=4,
|
||||
)
|
||||
return cp_config, talker_config
|
||||
|
||||
|
||||
def _make_vllm_config(max_num_seqs: int = 4) -> MagicMock:
|
||||
def _make_vllm_config(mocker: MockerFixture, max_num_seqs: int = 4):
|
||||
"""Create a mock VllmConfig with scheduler_config."""
|
||||
vllm_config = MagicMock()
|
||||
vllm_config = mocker.MagicMock()
|
||||
vllm_config.scheduler_config.max_num_seqs = max_num_seqs
|
||||
return vllm_config
|
||||
|
||||
@@ -130,12 +143,13 @@ def _make_vllm_config(max_num_seqs: int = 4) -> MagicMock:
|
||||
class TestCodePredictorDtypeAlignment:
|
||||
"""Test that code predictor buffers match model parameter dtype."""
|
||||
|
||||
def test_ensure_buffers_uses_given_dtype(self) -> None:
|
||||
def test_ensure_buffers_uses_given_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
|
||||
"""_ensure_buffers should create proj_buf with the given dtype."""
|
||||
cp_config, talker_config = _make_tiny_config()
|
||||
vllm_config = _make_vllm_config()
|
||||
_, _, code_predictor_wrapper, _ = loaded_target_classes
|
||||
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
|
||||
vllm_config = _make_vllm_config(mocker)
|
||||
|
||||
predictor = CodePredictorWrapper(
|
||||
predictor = code_predictor_wrapper(
|
||||
vllm_config=vllm_config,
|
||||
config=cp_config,
|
||||
talker_config=talker_config,
|
||||
@@ -150,12 +164,13 @@ class TestCodePredictorDtypeAlignment:
|
||||
predictor._ensure_buffers(torch.device("cpu"), torch.float32)
|
||||
assert predictor._proj_buf.dtype == torch.float32
|
||||
|
||||
def test_warmup_aligns_buffer_to_model_params(self) -> None:
|
||||
def test_warmup_aligns_buffer_to_model_params(self, mocker: MockerFixture, loaded_target_classes) -> None:
|
||||
"""_warmup_buckets should align proj_buf dtype to model parameters."""
|
||||
cp_config, talker_config = _make_tiny_config()
|
||||
vllm_config = _make_vllm_config(max_num_seqs=2)
|
||||
_, _, code_predictor_wrapper, _ = loaded_target_classes
|
||||
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
|
||||
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
|
||||
|
||||
predictor = CodePredictorWrapper(
|
||||
predictor = code_predictor_wrapper(
|
||||
vllm_config=vllm_config,
|
||||
config=cp_config,
|
||||
talker_config=talker_config,
|
||||
@@ -177,12 +192,13 @@ class TestCodePredictorDtypeAlignment:
|
||||
|
||||
assert predictor._proj_buf.dtype == torch.float16
|
||||
|
||||
def test_setup_compile_caches_model_dtype(self) -> None:
|
||||
def test_setup_compile_caches_model_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
|
||||
"""_setup_compile should cache model parameter dtype."""
|
||||
cp_config, talker_config = _make_tiny_config()
|
||||
vllm_config = _make_vllm_config(max_num_seqs=2)
|
||||
_, _, code_predictor_wrapper, _ = loaded_target_classes
|
||||
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
|
||||
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
|
||||
|
||||
predictor = CodePredictorWrapper(
|
||||
predictor = code_predictor_wrapper(
|
||||
vllm_config=vllm_config,
|
||||
config=cp_config,
|
||||
talker_config=talker_config,
|
||||
@@ -193,12 +209,13 @@ class TestCodePredictorDtypeAlignment:
|
||||
predictor._setup_compile()
|
||||
assert predictor._model_dtype == torch.float16
|
||||
|
||||
def test_forward_with_mismatched_input_dtype(self) -> None:
|
||||
def test_forward_with_mismatched_input_dtype(self, mocker: MockerFixture, loaded_target_classes) -> None:
|
||||
"""forward() should not crash when inputs are float32 but model is float16."""
|
||||
cp_config, talker_config = _make_tiny_config()
|
||||
vllm_config = _make_vllm_config(max_num_seqs=2)
|
||||
_, _, code_predictor_wrapper, _ = loaded_target_classes
|
||||
cp_config, talker_config = _make_tiny_config(loaded_target_classes)
|
||||
vllm_config = _make_vllm_config(mocker, max_num_seqs=2)
|
||||
|
||||
predictor = CodePredictorWrapper(
|
||||
predictor = code_predictor_wrapper(
|
||||
vllm_config=vllm_config,
|
||||
config=cp_config,
|
||||
talker_config=talker_config,
|
||||
@@ -231,10 +248,11 @@ class TestCodePredictorDtypeAlignment:
|
||||
class TestCodePredictorModelDtype:
|
||||
"""Test the inner model forward with different dtypes."""
|
||||
|
||||
def test_model_forward_float16(self) -> None:
|
||||
def test_model_forward_float16(self, loaded_target_classes) -> None:
|
||||
"""Inner model forward should work in float16."""
|
||||
cp_config, _ = _make_tiny_config()
|
||||
model = CodePredictorModel(cp_config, talker_hidden_size=32).to(torch.float16)
|
||||
_, _, _, code_predictor_model = loaded_target_classes
|
||||
cp_config, _ = _make_tiny_config(loaded_target_classes)
|
||||
model = code_predictor_model(cp_config, talker_hidden_size=32).to(torch.float16)
|
||||
|
||||
bsz, seq_len = 1, 4
|
||||
inputs = torch.randn(bsz, seq_len, 32, dtype=torch.float16)
|
||||
@@ -244,10 +262,11 @@ class TestCodePredictorModelDtype:
|
||||
assert output.dtype == torch.float16
|
||||
assert output.shape == (bsz, seq_len, 32)
|
||||
|
||||
def test_model_forward_float32(self) -> None:
|
||||
def test_model_forward_float32(self, loaded_target_classes) -> None:
|
||||
"""Inner model forward should work in float32."""
|
||||
cp_config, _ = _make_tiny_config()
|
||||
model = CodePredictorModel(cp_config, talker_hidden_size=32).to(torch.float32)
|
||||
_, _, _, code_predictor_model = loaded_target_classes
|
||||
cp_config, _ = _make_tiny_config(loaded_target_classes)
|
||||
model = code_predictor_model(cp_config, talker_hidden_size=32).to(torch.float32)
|
||||
|
||||
bsz, seq_len = 1, 4
|
||||
inputs = torch.randn(bsz, seq_len, 32, dtype=torch.float32)
|
||||
|
||||
@@ -10,11 +10,11 @@ Covers:
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
@@ -61,18 +61,18 @@ class TestFishSpeechVoiceCacheIntegration:
|
||||
"""Test the cache-hit / cache-miss / no-cache paths in the model."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_model(self):
|
||||
def mock_model(self, mocker: MockerFixture):
|
||||
"""Create a mock FishSpeechSlowARForConditionalGeneration with cache."""
|
||||
from vllm_omni.utils.voice_cache import VoiceEmbeddingCache
|
||||
|
||||
model = MagicMock()
|
||||
model = mocker.MagicMock()
|
||||
model._voice_cache = VoiceEmbeddingCache(max_entries=4)
|
||||
model._semantic_begin_id = 151678
|
||||
model._num_codebooks = 10
|
||||
model._codebook_size = 4096
|
||||
model.model_path = "/fake/model"
|
||||
model.codebook_embeddings = MagicMock()
|
||||
model.codebook_embeddings.weight = MagicMock()
|
||||
model.codebook_embeddings = mocker.MagicMock()
|
||||
model.codebook_embeddings.weight = mocker.MagicMock()
|
||||
model.codebook_embeddings.weight.device = torch.device("cpu")
|
||||
return model
|
||||
|
||||
@@ -166,9 +166,9 @@ class TestFishSpeechVoiceCacheIntegration:
|
||||
class TestFishSpeechValidatorUploadedVoice:
|
||||
"""Test _validate_fish_tts_request uploaded voice resolution."""
|
||||
|
||||
def test_uploaded_voice_resolves_ref_audio(self):
|
||||
def test_uploaded_voice_resolves_ref_audio(self, mocker: MockerFixture):
|
||||
"""When voice matches an uploaded speaker, ref_audio should be auto-set."""
|
||||
request = MagicMock()
|
||||
request = mocker.MagicMock()
|
||||
request.input = "Hello"
|
||||
request.voice = "alice"
|
||||
request.ref_audio = None
|
||||
@@ -185,17 +185,17 @@ class TestFishSpeechValidatorUploadedVoice:
|
||||
}
|
||||
|
||||
# Simulate: voice in uploaded_speakers, file exists, get_audio returns data URL.
|
||||
with patch("pathlib.Path.exists", return_value=True):
|
||||
voice_lower = request.voice.lower()
|
||||
assert voice_lower in uploaded_speakers
|
||||
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||
voice_lower = request.voice.lower()
|
||||
assert voice_lower in uploaded_speakers
|
||||
|
||||
speaker_info = uploaded_speakers[voice_lower]
|
||||
ref_text_from_upload = speaker_info.get("ref_text")
|
||||
assert ref_text_from_upload == "Hi this is Alice"
|
||||
speaker_info = uploaded_speakers[voice_lower]
|
||||
ref_text_from_upload = speaker_info.get("ref_text")
|
||||
assert ref_text_from_upload == "Hi this is Alice"
|
||||
|
||||
def test_uploaded_voice_without_ref_text_uses_request_ref_text(self):
|
||||
def test_uploaded_voice_without_ref_text_uses_request_ref_text(self, mocker: MockerFixture):
|
||||
"""If upload has no ref_text but request provides it, use request's."""
|
||||
request = MagicMock()
|
||||
request = mocker.MagicMock()
|
||||
request.input = "Hello"
|
||||
request.voice = "bob"
|
||||
request.ref_audio = None
|
||||
|
||||
Reference in new issue
Block a user