[Bugfix][Model] Fix CohereASR streaming audio-token estimate (unit + subsampling) (#53829)

Signed-off-by: hungh <hungh@nvidia.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>

Hung Hoang (黄兴) committed Sep 2, 2026 at 22:55 UTC 3b45d053b4bbc61ce437f00891b52ce5ddde7c5a
2 files changed +43 -3
tests/models/multimodal/test_cohere_asr.py new
+34
@@ -0,0 +1,34 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from types import SimpleNamespace
5 +
6 +import pytest
7 +
8 +from vllm.config import SpeechToTextConfig
9 +from vllm.model_executor.models.cohere_asr import CohereAsrForConditionalGeneration
10 +
11 +
12 +@pytest.mark.parametrize(
13 + ("audio_duration_s", "expected_tokens"),
14 + # window_stride=0.01s @ 16kHz -> 160-sample hop; subsampling_factor=8.
15 + # frames = floor(duration * 16000 / 160); tokens = ceil(frames / 8).
16 + [(1.0, 13), (10.0, 125), (30.0, 375)],
17 +)
18 +def test_get_num_audio_tokens_streaming_estimate(audio_duration_s, expected_tokens):
19 + """The duration-based estimate must convert ``window_stride`` (seconds) to a
20 + sample hop and divide by the encoder subsampling factor. Values are pinned to
21 + concrete numbers so a rounding regression is actually caught."""
22 + model_config = SimpleNamespace(
23 + hf_config=SimpleNamespace(
24 + preprocessor={"window_stride": 0.01, "sample_rate": 16000},
25 + encoder={"subsampling_factor": 8},
26 + )
27 + )
28 + stt_config = SpeechToTextConfig(sample_rate=16000)
29 +
30 + got = CohereAsrForConditionalGeneration.get_num_audio_tokens(
31 + audio_duration_s, stt_config, model_config
32 + )
33 +
34 + assert got == expected_tokens
vllm/model_executor/models/cohere_asr.py
+9 -3
@@ -2177,9 +2177,15 @@ class CohereAsrForConditionalGeneration(
2177 stt_config: SpeechToTextConfig,
2178 model_config: ModelConfig,
2179 ) -> int | None:
2180 - hop_length = model_config.hf_config.preprocessor.get("window_stride")
2181 - assert hop_length is not None
2182 - return math.ceil(audio_duration_s * stt_config.sample_rate / hop_length)
2180 + hf_config = model_config.hf_config
2181 + preprocessor = hf_config.preprocessor
2182 + sample_rate = preprocessor["sample_rate"]
2183 + window_stride = preprocessor["window_stride"]
2184 + hop_length = int(window_stride * sample_rate)
2185 + # Floor-divide to match get_seq_len.
2186 + num_frames = int(audio_duration_s * sample_rate) // hop_length
2187 + subsampling_factor = hf_config.encoder["subsampling_factor"]
2188 + return math.ceil(num_frames / subsampling_factor)
2189
2190 def get_num_encoder_cross_attn_tokens(self, num_encoder_input_tokens: int) -> int:
2191 return self.model.encoder.get_num_encoder_cross_attn_tokens(