[Mypy] Fix mypy typing for Voxtral and vision models (#58251)
Signed-off-by: Ashraf Bhuiyan <mbhuiyan@redhat.com> Co-authored-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com>
Ashraf Bhuiyan committed
Sep 28, 2026 at 15:52 UTC
2619b6b72b24a8c1963ef6b29427c53b826023d1
5 files changed
+34
-20
tests/models/multimodal/generation/test_voxtral_realtime.py
+11
-1
@@ -16,7 +16,9 @@ from vllm import LLM, SamplingParams
16
from vllm.assets.audio import AudioAsset
17
from vllm.config import CUDAGraphMode
18
from vllm.engine.arg_utils import AsyncEngineArgs
19
+from vllm.engine.protocol import StreamingInput
20
from vllm.platforms import current_platform
21
+from vllm.renderers.inputs.preprocess import parse_model_prompt
22
from vllm.utils.math_utils import cdiv
23
from vllm.v1.engine.async_llm import AsyncLLM
24
from vllm.v1.kv_cache_interface import SlidingWindowSpec
@@ -222,6 +224,14 @@ async def test_voxtral_realtime_generator(audio_assets, tokenizer, async_engine)
224
sampling_params = SamplingParams(temperature=0.0, max_tokens=1)
225
audio_config = tokenizer.instruct_tokenizer.audio_encoder.audio_config
226
227
+ async def input_stream(audio_buffer):
228
+ async for prompt in audio_buffer.get_input_stream():
229
+ parsed_prompt = parse_model_prompt(async_engine.model_config, prompt)
230
+ (engine_input,) = await async_engine.renderer.render_cmpl_async(
231
+ [parsed_prompt]
232
+ )
233
+ yield StreamingInput(prompt=engine_input)
234
+
235
output_tokens_list = []
236
for i, audio_asset in enumerate(audio_assets):
237
output_tokens = []
@@ -241,7 +251,7 @@ async def test_voxtral_realtime_generator(audio_assets, tokenizer, async_engine)
251
request_id = f"session-{i}"
252
253
async for resp in async_engine.generate(
244
- prompt=buffer.get_input_stream(),
254
+ prompt=input_stream(buffer),
255
sampling_params=sampling_params,
256
request_id=request_id,
257
):
tools/pre_commit/mypy.py
-1
@@ -113,7 +113,6 @@ SEPARATE_GROUPS = [
113
114
EXCLUDE = [
115
r"vllm/model_executor/models/[kK]",
116
- r"vllm/model_executor/models/[vV]",
116
r"vllm/model_executor/models/[wW]",
117
r"vllm/model_executor/models/[zZ]",
118
]
vllm/model_executor/models/vision.py
+7
-4
@@ -5,7 +5,7 @@ import itertools
5
import math
6
from abc import ABC, abstractmethod
7
from collections.abc import Callable
8
-from typing import Final, Generic, Literal, Protocol, TypeAlias, TypeVar
8
+from typing import Generic, Literal, Protocol, TypeAlias, TypeVar
9
10
import torch
11
from transformers import PreTrainedConfig
@@ -24,10 +24,12 @@ from vllm.v1.attention.backends.registry import AttentionBackendEnum
24
logger = init_logger(__name__)
25
26
_C = TypeVar("_C", bound=PreTrainedConfig)
27
+_C_co = TypeVar("_C_co", bound=PreTrainedConfig, covariant=True)
28
29
29
-class _RootConfig(Protocol[_C]):
30
- vision_config: _C
30
+class _RootConfig(Protocol[_C_co]):
31
+ @property
32
+ def vision_config(self) -> _C_co: ...
33
34
35
class VisionEncoderInfo(ABC, Generic[_C]):
@@ -60,7 +62,8 @@ class VisionEncoderInfo(ABC, Generic[_C]):
62
63
64
class VisionLanguageConfig(Protocol):
63
- vision_config: Final[PreTrainedConfig]
65
+ @property
66
+ def vision_config(self) -> PreTrainedConfig: ...
67
68
69
def get_vision_encoder_info(hf_config: VisionLanguageConfig) -> VisionEncoderInfo:
vllm/model_executor/models/voxtral.py
+4
-3
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import math
5
-from collections.abc import Iterable, Mapping, Sequence
5
+from collections.abc import Callable, Iterable, Mapping, Sequence
6
from functools import partial
7
from typing import cast
8
@@ -317,7 +317,7 @@ class VoxtralForConditionalGeneration(
317
318
# update quant config to so that ignored module and target module names
319
# match the vLLM model names
320
- if hasattr(vllm_config, "quant_config"):
320
+ if vllm_config.quant_config is not None:
321
vllm_config.quant_config = self.maybe_update_quant_config(
322
vllm_config.quant_config
323
)
@@ -724,6 +724,7 @@ class VoxtralEncoderModel(nn.Module):
724
self.config = cast(WhisperConfig, vllm_config.model_config.hf_config)
725
self.dtype: torch.dtype = vllm_config.model_config.dtype
726
self.is_causal = getattr(self.config, "is_causal", False)
727
+ WhisperEncoderCls: Callable[..., WhisperEncoder | WhisperCausalEncoder]
728
if self.is_causal:
729
WhisperEncoderCls = WhisperCausalEncoder
730
else:
@@ -830,7 +831,7 @@ class VoxtralEncoderModel(nn.Module):
831
return results
832
833
def load_weight(self, weight: tuple[str, torch.Tensor]) -> str:
833
- stacked_params_mapping = [
834
+ stacked_params_mapping: list[tuple[str, str, str | int]] = [
835
# (param_name, shard_name, shard_id)
836
("qkv_proj", "q_proj", "q"),
837
("qkv_proj", "k_proj", "k"),
vllm/model_executor/models/voxtral_realtime.py
+12
-11
@@ -16,7 +16,6 @@ from mistral_common.tokens.tokenizers.audio import Audio, AudioConfig
16
from vllm.compilation.decorators import support_torch_compile
17
from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
18
from vllm.config.speech_to_text import SpeechToTextParams
19
-from vllm.engine.protocol import StreamingInput
19
from vllm.inputs import PromptType, TokensPrompt
20
from vllm.logger import init_logger
21
from vllm.model_executor.models.interfaces import MultiModalEmbeddings, SupportsRealtime
@@ -26,6 +25,7 @@ from vllm.model_executor.models.voxtral import (
25
VoxtralMultiModalProcessor,
26
VoxtralProcessingInfo,
27
)
28
+from vllm.model_executor.models.whisper_causal import WhisperCausalEncoder
29
from vllm.multimodal import MULTIMODAL_REGISTRY
30
from vllm.multimodal.parse import MultiModalDataItems
31
from vllm.multimodal.processing import ProcessorInputs, TimingContext
@@ -56,7 +56,7 @@ class VoxtralRealtimeMultiModalProcessor(VoxtralMultiModalProcessor):
56
mm_items: MultiModalDataItems,
57
mm_res: MultiModalProcessingResult,
58
) -> tuple[list[int], Mapping[str, list[PlaceholderFeaturesInfo]]]:
59
- mm_kwargs = mm_res.kwargs
59
+ mm_kwargs = mm_res.kwargs.require_data()
60
61
# there are no placeholder audio tokens for streaming
62
# so we need to build the place placeholder positions manually
@@ -69,7 +69,9 @@ class VoxtralRealtimeMultiModalProcessor(VoxtralMultiModalProcessor):
69
tokenizer = self.info.get_tokenizer()
70
audio_config = tokenizer.instruct.audio_encoder.audio_config
71
72
- num_audio_samples = audios[0]["audio_arrays"].data.shape[0]
72
+ audio_array = audios[0]["audio_arrays"].data
73
+ assert isinstance(audio_array, (torch.Tensor, np.ndarray))
74
+ num_audio_samples = audio_array.shape[0]
75
length = audio_config.num_audio_tokens(num_audio_samples)
76
77
features_info = PlaceholderFeaturesInfo(
@@ -162,7 +164,7 @@ class VoxtralRealtimeBuffer:
164
for token in tokens:
165
await self._token_queue.put(token)
166
165
- async def get_input_stream(self) -> AsyncGenerator[StreamingInput]:
167
+ async def get_input_stream(self) -> AsyncGenerator[TokensPrompt]:
168
for frame_size, num_tokens in self._generate_frame_size_and_num_tokens():
169
next_tokens = [await self._token_queue.get() for _ in range(num_tokens)]
170
@@ -188,11 +190,9 @@ class VoxtralRealtimeBuffer:
190
191
self._leftover = audio_array[stride:]
192
191
- yield StreamingInput(
192
- TokensPrompt(
193
- prompt_token_ids=next_tokens,
194
- multi_modal_data={"audio": (frame, None)},
195
- )
193
+ yield TokensPrompt(
194
+ prompt_token_ids=next_tokens,
195
+ multi_modal_data={"audio": (frame, None)},
196
)
197
198
@@ -274,8 +274,8 @@ class VoxtralRealtimeGeneration(VoxtralForConditionalGeneration, SupportsRealtim
274
token_task = asyncio.create_task(feed_tokens())
275
276
try:
277
- async for streaming_input in buffer.get_input_stream():
278
- yield streaming_input.prompt
277
+ async for prompt in buffer.get_input_stream():
278
+ yield prompt
279
finally:
280
audio_task.cancel()
281
token_task.cancel()
@@ -420,6 +420,7 @@ class VoxtralRealtimeGeneration(VoxtralForConditionalGeneration, SupportsRealtim
420
421
seq_lens = [mel.shape[1] for mel in mel_features]
422
# [total_num_20ms_frames, hidden_size]
423
+ assert isinstance(self.whisper_encoder.whisper_encoder, WhisperCausalEncoder)
424
audio_embeddings = self.whisper_encoder.whisper_encoder.forward_conv(
425
mel_features
426
)