main
py 68 lines 2.62 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Regression test for chunk timestamp offset drift in _preprocess_speech_to_text."""
4
5 from __future__ import annotations
6
7 from unittest.mock import AsyncMock, MagicMock, patch
8
9 import numpy as np
10 import pytest
11
12 from vllm.config.speech_to_text import SpeechToTextConfig
13 from vllm.entrypoints.speech_to_text.base.serving import SpeechToTextBaseServing
14
15 SR = 16_000
16 _PATCH = "vllm.entrypoints.speech_to_text.base.serving"
17
18
19 @pytest.mark.asyncio
20 async def test_chunk_offsets_are_cumulative_not_nominal():
21 """chunk_start_offsets must be cumulative actual chunk lengths, not the old approach
22 of 'idx * max_audio_clip_s'. When split_audio places a boundary before the
23 nominal 30 s mark, the old formula drifts; the fixed formula stays exact.
24 """
25 # Chunks shorter than exactly 30 s, as split_audio produces when a quiet
26 # region falls inside the 1 s overlap window before the nominal boundary.
27 chunk_lengths = [int(29.5 * SR), int(29.7 * SR), int(5.0 * SR)]
28 chunks = [np.zeros(n, dtype=np.float32) for n in chunk_lengths]
29
30 duration = sum(chunk_lengths) / SR
31
32 expected_offsets = [0.0, 29.5, 29.5 + 29.7] # cumulative seconds
33 wrong_offsets = [0.0, 30.0, 60.0] # what the old bug produced
34
35 serving = SpeechToTextBaseServing.__new__(SpeechToTextBaseServing)
36 serving._decode_and_chunk_speech_async = AsyncMock(return_value=(chunks, duration))
37 serving.asr_config = SpeechToTextConfig(
38 sample_rate=float(SR),
39 max_audio_clip_s=30,
40 overlap_chunk_second=1,
41 min_energy_split_window_size=1600,
42 )
43 serving.model_cls = MagicMock()
44 serving.model_cls.validate_language.side_effect = lambda lang: lang
45 serving.model_cls.supports_explicit_language_detection = False
46 serving.model_cls.get_generation_prompt.return_value = {}
47 serving.model_config = MagicMock()
48 serving.task_type = "transcribe"
49 serving.renderer = MagicMock()
50 serving.renderer.render_cmpl_async = AsyncMock(
51 return_value=[MagicMock()] * len(chunks)
52 )
53
54 request = MagicMock()
55 request.language = "en"
56 request.to_language = None
57 request.response_format = "json"
58 request.build_stt_params.return_value = MagicMock()
59
60 with patch(f"{_PATCH}.parse_model_prompt", return_value=MagicMock()):
61 _, _, offsets = await serving._preprocess_speech_to_text(
62 request=request,
63 audio_data=b"\x00",
64 request_id="test",
65 )
66
67 assert offsets == pytest.approx(expected_offsets, abs=1e-6)
68 assert offsets != pytest.approx(wrong_offsets, abs=1e-6)