[Mypy] Fix mypy typing for P models (#54169)
Signed-off-by: Taneem Ibrahim <taneem.ibrahim@gmail.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Taneem Ibrahim committed
Sep 4, 2026 at 15:47 UTC
784cac7c424db2f2fdc879b672abad7e231f5698
9 files changed
+126
-45
tools/pre_commit/mypy.py
-1
@@ -115,7 +115,6 @@ EXCLUDE = [
115
r"vllm/model_executor/models/[kK]",
116
r"vllm/model_executor/models/[nN]",
117
r"vllm/model_executor/models/[oO]",
118
- r"vllm/model_executor/models/[pP]",
118
r"vllm/model_executor/models/[qQ]",
119
r"vllm/model_executor/models/[rR]",
120
r"vllm/model_executor/models/[sS]",
vllm/model_executor/models/paddleocr_vl.py
+20
-14
@@ -412,8 +412,8 @@ class SiglipVisionEmbeddings(nn.Module):
412
413
self.num_patches = (self.image_size // self.patch_size) ** 2
414
self.num_positions = self.num_patches
415
- self.cache_position_embedding = dict()
416
- self.cache_position_count = dict()
415
+ self.cache_position_embedding: dict[tuple[int, int], torch.Tensor] = {}
416
+ self.cache_position_count: dict[tuple[int, int], int] = {}
417
self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
418
419
self.register_buffer(
@@ -469,7 +469,7 @@ class SiglipVisionEmbeddings(nn.Module):
469
if len(self.cache_position_embedding) >= max_cache:
470
min_hit_grid = min(
471
self.cache_position_count,
472
- key=self.cache_position_count.get,
472
+ key=self.cache_position_count.__getitem__,
473
)
474
self.cache_position_count.pop(min_hit_grid)
475
self.cache_position_embedding.pop(min_hit_grid)
@@ -483,8 +483,7 @@ class SiglipVisionEmbeddings(nn.Module):
483
self,
484
pixel_values: torch.FloatTensor,
485
position_ids: torch.Tensor | None = None,
486
- image_grid_thw: list[tuple[int, int, int] | list[tuple[int, int, int]]]
487
- | None = None,
486
+ image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
487
interpolate_pos_encoding=False,
488
) -> torch.Tensor:
489
if pixel_values.dim() == 4:
@@ -508,6 +507,7 @@ class SiglipVisionEmbeddings(nn.Module):
507
508
start = 0
509
tmp_embeddings = list()
510
+ assert image_grid_thw is not None
511
for image_grid in image_grid_thw:
512
t, h, w = image_grid
513
end = start + t * h * w
@@ -864,7 +864,7 @@ class SiglipVisionTransformer(nn.Module):
864
height_position_ids: torch.Tensor | None = None,
865
width_position_ids: torch.Tensor | None = None,
866
cu_seqlens: torch.Tensor | None = None,
867
- image_grid_thw: torch.Tensor | None = None,
867
+ image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
868
) -> torch.Tensor:
869
hidden_states = self.embeddings(
870
pixel_values,
@@ -934,8 +934,7 @@ class SiglipVisionModel(nn.Module):
934
pixel_values,
935
interpolate_pos_encoding: bool = False,
936
position_ids: torch.Tensor | None = None,
937
- image_grid_thw: list[tuple[int, int, int] | list[tuple[int, int, int]]]
938
- | None = None,
937
+ image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
938
cu_seqlens: torch.Tensor | None = None,
939
) -> BaseModelOutputWithPooling:
940
return self.vision_model(
@@ -1029,17 +1028,24 @@ class PaddleOCRVLForConditionalGeneration(nn.Module, SupportsMultiModal, Support
1028
tokens_per_second = getattr(self.config.vision_config, "tokens_per_second", 1.0)
1029
for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1030
offset = mm_feature.mm_position.offset
1031
+ feature_data = mm_feature.data
1032
+ assert feature_data is not None
1033
if mm_feature.modality == "image":
1033
- t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
1034
+ grid_data = feature_data["image_grid_thw"].data
1035
+ assert isinstance(grid_data, torch.Tensor)
1036
+ t, h, w = grid_data.tolist()
1037
assert t == 1, f"Image must have 1 frame, got {t}"
1038
yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1039
elif mm_feature.modality == "video":
1037
- t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
1040
+ grid_data = feature_data["video_grid_thw"].data
1041
+ assert isinstance(grid_data, torch.Tensor)
1042
+ t, h, w = grid_data.tolist()
1043
second_per_grid_ts = 1.0
1039
- if mm_feature.data.get("second_per_grid_ts", None):
1040
- second_per_grid_ts = mm_feature.data[
1041
- "second_per_grid_ts"
1042
- ].data.item()
1044
+ second_per_grid_item = feature_data.get("second_per_grid_ts")
1045
+ if second_per_grid_item is not None:
1046
+ second_per_grid_data = second_per_grid_item.data
1047
+ assert isinstance(second_per_grid_data, torch.Tensor)
1048
+ second_per_grid_ts = second_per_grid_data.item()
1049
t_factor = second_per_grid_ts * tokens_per_second
1050
yield (
1051
offset,
vllm/model_executor/models/paligemma.py
+4
-1
@@ -181,6 +181,7 @@ class PaliGemmaMultiModalProcessor(BaseMultiModalProcessor[PaliGemmaProcessingIn
181
if isinstance(images, ImageEmbeddingItems):
182
num_image_tokens = images.get_feature_size(item_idx)
183
else:
184
+ assert isinstance(images, ImageProcessorItems)
185
image_size = images.get_image_size(item_idx)
186
num_image_tokens = self.info.get_num_image_tokens(
187
image_width=image_size.width,
@@ -197,11 +198,13 @@ class PaliGemmaMultiModalProcessor(BaseMultiModalProcessor[PaliGemmaProcessingIn
198
# Paligemma 1 and 2 have different tokenizer.add_bos_token
199
# Insert <image>*n + <bos> after <bos> for Paligemma 1
200
# Insert <image>*n + <bos> for Paligemma 2
201
+ add_bos_token = getattr(tokenizer, "add_bos_token", None)
202
+ assert isinstance(add_bos_token, bool)
203
return [
204
PromptInsertion(
205
modality="image",
206
target=PromptIndexTargets.prefix(
204
- [bos_token_id] if tokenizer.add_bos_token else []
207
+ [bos_token_id] if add_bos_token else []
208
),
209
insertion=get_insertion,
210
)
vllm/model_executor/models/param2moe.py
+4
-1
@@ -34,6 +34,7 @@ from vllm.model_executor.layers.activation import SiluAndMul
34
from vllm.model_executor.layers.attention import Attention
35
from vllm.model_executor.layers.fused_moe import (
36
FusedMoEFactory,
37
+ MoERunner,
38
)
39
from vllm.model_executor.layers.layernorm import RMSNorm
40
from vllm.model_executor.layers.linear import (
@@ -344,7 +345,7 @@ class Param2MoEMoEBlock(nn.Module):
345
routed_scaling_factor=self.routed_scaling_factor,
346
)
347
347
- def maybe_get_fused_moe(self) -> FusedMoEFactory:
348
+ def maybe_get_fused_moe(self) -> MoERunner:
349
return self.experts
350
351
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
@@ -560,6 +561,8 @@ class Param2MoEModel(nn.Module):
561
class Param2MoEMixtureOfExperts(MixtureOfExperts):
562
"""Implements the vLLM MixtureOfExperts protocol for Param2MoE."""
563
564
+ moe_mlp_layers: list[Param2MoEMoEBlock]
565
+
566
def extract_moe_parameters(self, example_moe: Param2MoEMoEBlock | None) -> None:
567
if example_moe is None:
568
raise RuntimeError(
vllm/model_executor/models/phi3v.py
+1
@@ -453,6 +453,7 @@ class Phi3VMultiModalProcessor(BaseMultiModalProcessor[Phi3VProcessingInfo]):
453
if isinstance(images, ImageEmbeddingItems):
454
num_image_tokens = images.get_feature_size(item_idx)
455
else:
456
+ assert isinstance(images, ImageProcessorItems)
457
image_size = images.get_image_size(item_idx)
458
num_image_tokens = self.info.get_num_image_tokens(
459
image_width=image_size.width,
vllm/model_executor/models/phi4mm.py
+34
-11
@@ -1,6 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import math
4
+import typing
5
from collections.abc import Iterable, Mapping, Sequence
6
from typing import Annotated, Any, Literal, TypeAlias
7
@@ -16,7 +17,7 @@ from transformers import (
17
)
18
19
from vllm.config import VllmConfig
19
-from vllm.config.multimodal import BaseDummyOptions
20
+from vllm.config.multimodal import AudioDummyOptions, BaseDummyOptions
21
from vllm.distributed import get_pp_group
22
from vllm.inputs import MultiModalDataDict
23
from vllm.model_executor.layers.logits_processor import LogitsProcessor
@@ -533,6 +534,11 @@ class Phi4MMAudioEmbeddingInputs(TensorSchema):
534
Phi4MMAudioInputs: TypeAlias = Phi4MMAudioFeatureInputs | Phi4MMAudioEmbeddingInputs
535
536
537
+class Phi4MMParsedModalities(typing.TypedDict, total=False):
538
+ images: Phi4MMImagePixelInputs
539
+ audios: Phi4MMAudioInputs
540
+
541
+
542
def stack_with_pad(
543
tensors: torch.Tensor | list[torch.Tensor],
544
padding_value: int | float = 0,
@@ -611,13 +617,13 @@ class Phi4MMProcessingInfo(BaseProcessingInfo):
617
aspect_ratio = orig_width / orig_height
618
619
# calculate the existing image aspect ratio
614
- target_ratios = set(
620
+ target_ratio_candidates = set(
621
(i, j)
622
for i in range(1, max_num + 1)
623
for j in range(1, max_num + 1)
624
if i * j <= max_num and i * j >= min_num
625
)
620
- target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
626
+ target_ratios = sorted(target_ratio_candidates, key=lambda x: x[0] * x[1])
627
628
# find the closest aspect ratio to the target
629
image_processor = self.get_hf_processor().image_processor
@@ -785,7 +791,8 @@ class Phi4MMProcessingInfo(BaseProcessingInfo):
791
792
# Resample to 16000 or 8000 if needed
793
if sr > 16000:
788
- audio_len //= sr // 16000
794
+ resample_ratio = int(sr // 16000)
795
+ audio_len //= resample_ratio
796
elif 8000 <= sr < 16000:
797
# We'll resample to 16K from 8K
798
audio_len *= 2
@@ -850,6 +857,7 @@ class Phi4MMDummyInputsBuilder(BaseDummyInputsBuilder[Phi4MMProcessingInfo]):
857
858
image_overrides = mm_options.get("image")
859
audio_overrides = mm_options.get("audio")
860
+ assert audio_overrides is None or isinstance(audio_overrides, AudioDummyOptions)
861
862
mm_data = {
863
"image": self._get_dummy_images(
@@ -885,12 +893,20 @@ class Phi4MMMultiModalProcessor(BaseMultiModalProcessor[Phi4MMProcessingInfo]):
893
prompt_text = self.dummy_inputs.get_dummy_text(mm_items.get_all_counts())
894
895
sr = self.info.get_feature_extractor(**hf_processor_mm_kwargs).sampling_rate
888
- if audio_data := mm_data.get("audios", []):
889
- mm_data["audios"] = [(data, sr) for data in audio_data]
896
+ raw_audio_data = mm_data.get("audios", [])
897
+ assert isinstance(raw_audio_data, Sequence)
898
+ audio_data = []
899
+ for data in raw_audio_data:
900
+ assert isinstance(data, (list, np.ndarray, torch.Tensor))
901
+ audio_data.append(data)
902
+
903
+ processor_data = dict(text=prompt_text, **mm_data)
904
+ if audio_data:
905
+ processor_data["audios"] = [(data, sr) for data in audio_data]
906
907
processed_data = self.info.ctx.call_hf_processor(
908
self.info.get_hf_processor(**hf_processor_mm_kwargs),
893
- dict(text=prompt_text, **mm_data),
909
+ processor_data,
910
hf_processor_mm_kwargs,
911
)
912
@@ -960,6 +976,7 @@ class Phi4MMMultiModalProcessor(BaseMultiModalProcessor[Phi4MMProcessingInfo]):
976
if isinstance(images, ImageEmbeddingItems):
977
num_image_tokens = images.get_feature_size(item_idx)
978
else:
979
+ assert isinstance(images, ImageProcessorItems)
980
image_size = images.get_image_size(item_idx)
981
num_image_tokens = self.info.get_num_image_tokens(
982
image_width=image_size.width,
@@ -1201,8 +1218,10 @@ class Phi4MMForCausalLM(nn.Module, SupportsLoRA, SupportsMultiModal):
1218
num_img_tokens=num_img_tokens,
1219
)
1220
1204
- def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1205
- modalities = {}
1221
+ def _parse_and_validate_multimodal_inputs(
1222
+ self, **kwargs: object
1223
+ ) -> Phi4MMParsedModalities:
1224
+ modalities = Phi4MMParsedModalities()
1225
1226
# Preserve the order of modalities if there are multiple of them
1227
# from the order of kwargs.
@@ -1211,12 +1230,16 @@ class Phi4MMForCausalLM(nn.Module, SupportsLoRA, SupportsMultiModal):
1230
input_key in ("input_image_embeds", "image_embeds")
1231
and "images" not in modalities
1232
):
1214
- modalities["images"] = self._parse_and_validate_image_input(**kwargs)
1233
+ image_input = self._parse_and_validate_image_input(**kwargs)
1234
+ assert image_input is not None
1235
+ modalities["images"] = image_input
1236
if (
1237
input_key in ("input_audio_embeds", "audio_embeds")
1238
and "audios" not in modalities
1239
):
1219
- modalities["audios"] = self._parse_and_validate_audio_input(**kwargs)
1240
+ audio_input = self._parse_and_validate_audio_input(**kwargs)
1241
+ assert audio_input is not None
1242
+ modalities["audios"] = audio_input
1243
1244
return modalities
1245
vllm/model_executor/models/phi4mm_audio.py
+48
-10
@@ -7,6 +7,7 @@
7
#!/usr/bin/env python3
8
import abc
9
import math
10
+import typing
11
from typing import Any, Literal
12
13
import numpy as np
@@ -152,7 +153,7 @@ class ConformerEncoderLayer(nn.Module):
153
batch_norm: bool = False,
154
activation: str = "relu",
155
chunk_se: int = 0,
155
- chunk_size: int = 18,
156
+ chunk_size: int | list[int] = 18,
157
conv_activation: str = "relu",
158
conv_glu_type: str = "sigmoid",
159
bias_in_glu: bool = True,
@@ -392,6 +393,7 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
393
else None
394
)
395
if self.relative_attention_bias_type == "t5":
396
+ assert relative_attention_bias_args is not None
397
assert self.num_heads % self.attention_group_size == 0, (
398
"attention_group_size must divide n_head"
399
)
@@ -405,6 +407,7 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
407
else:
408
raise NotImplementedError
409
410
+ assert self.encoder_embedding_config is not None
411
self.encoder_embedding = MeanVarianceNormLayer(
412
self.encoder_embedding_config["input_size"]
413
)
@@ -421,6 +424,7 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
424
the cases and return one of them. Torchscript does support that.
425
"""
426
if self.input_layer == "nemo_conv":
427
+ assert self.nemo_conv_settings is not None
428
# Handle the special causal case
429
subsampling_causal_cond = self.nemo_conv_settings.get(
430
"subsampling", "dw_striding"
@@ -431,19 +435,19 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
435
]
436
is_causal = self.nemo_conv_settings.get("is_causal", False)
437
if is_causal and subsampling_causal_cond:
434
- lens_change = (
435
- torch.ceil(feature_lens / self.time_reduction).long()
436
- if isinstance(feature_lens, Tensor)
437
- else math.ceil(feature_lens / self.time_reduction)
438
- )
438
feature_lens_remainder = feature_lens % self.time_reduction
439
if isinstance(feature_lens, Tensor):
440
+ lens_change = torch.ceil(feature_lens / self.time_reduction).long()
441
lens_change[feature_lens_remainder != 1] += 1
442
- elif feature_lens_remainder != 1:
442
+ return lens_change
443
+
444
+ lens_change = math.ceil(feature_lens / self.time_reduction)
445
+ if feature_lens_remainder != 1:
446
lens_change += 1
447
return lens_change
448
ceil_func = math.ceil if isinstance(feature_lens, int) else torch.ceil
449
return ceil_func(feature_lens / self.time_reduction)
450
+ raise ValueError("unknown input_layer: " + self.input_layer)
451
452
@abc.abstractmethod
453
def forward(self) -> Any:
@@ -476,6 +480,8 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
480
)
481
left_chunk_train_eff = left_chunk[chunk_size_index]
482
else:
483
+ assert chunk_size is not None
484
+ assert isinstance(left_chunk, int)
485
chunk_size_train_eff = chunk_size
486
left_chunk_train_eff = left_chunk
487
@@ -516,7 +522,7 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
522
seq_len: int,
523
batch_size: int,
524
chunk_size: int | list[int],
519
- left_chunk: int | list[int],
525
+ left_chunk: int | list[int] | None,
526
) -> torch.Tensor:
527
chunk_size_train_eff, left_chunk_train_eff = self._chunk_size_selection(
528
chunk_size, left_chunk
@@ -535,6 +541,37 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
541
)
542
return enc_streaming_mask
543
544
+ @typing.overload
545
+ def forward_embeddings(
546
+ self,
547
+ xs_pad: torch.Tensor,
548
+ masks: torch.Tensor,
549
+ chunk_size_nc: None = None,
550
+ left_chunk_nc: int | list[int] | None = None,
551
+ ) -> tuple[
552
+ torch.Tensor,
553
+ torch.Tensor | None,
554
+ torch.Tensor | None,
555
+ torch.Tensor,
556
+ torch.Tensor,
557
+ ]: ...
558
+
559
+ @typing.overload
560
+ def forward_embeddings(
561
+ self,
562
+ xs_pad: torch.Tensor,
563
+ masks: torch.Tensor,
564
+ chunk_size_nc: int | list[int],
565
+ left_chunk_nc: int | list[int] | None = None,
566
+ ) -> tuple[
567
+ torch.Tensor,
568
+ torch.Tensor | None,
569
+ torch.Tensor | None,
570
+ torch.Tensor,
571
+ torch.Tensor,
572
+ torch.Tensor,
573
+ ]: ...
574
+
575
def forward_embeddings(
576
self,
577
xs_pad: torch.Tensor,
@@ -911,6 +948,7 @@ class ConformerEncoder(TransformerEncoderBase):
948
) -> torch.Tensor | None:
949
if self.relative_attention_bias_layer:
950
return self.relative_attention_bias_layer(input_tensor)
951
+ return None
952
953
def calculate_hs_mask(
954
self, xs_pad: torch.Tensor, device: torch.device, mask: torch.Tensor | None
@@ -1148,7 +1186,7 @@ class AudioEmbedding(nn.Module):
1186
if kwargs.get("use_qformer", False):
1187
qformer_config = kwargs.get("qformer_config", {})
1188
qformer_config["attention_dim"] = audio_dim_out
1151
- self.qformer = WindowQformer(**qformer_config)
1189
+ self.qformer: WindowQformer | None = WindowQformer(**qformer_config)
1190
else:
1191
self.qformer = None
1192
@@ -1175,7 +1213,7 @@ class AudioEmbedding(nn.Module):
1213
"{i} should be specified outside of the NeMo dictionary"
1214
)
1215
1178
- self.conv_ds = NemoConvSubsampling(
1216
+ self.conv_ds: NemoConvSubsampling | None = NemoConvSubsampling(
1217
**default_nemo_conv_settings,
1218
)
1219
else:
vllm/model_executor/models/phi4mm_utils.py
+9
-5
@@ -355,7 +355,7 @@ class ConvModule(nn.Module):
355
causal: bool = False,
356
batch_norm: bool = False,
357
chunk_se: int = 0,
358
- chunk_size: int = 18,
358
+ chunk_size: int | list[int] = 18,
359
activation: str = "relu",
360
glu_type: str = "sigmoid",
361
bias_in_glu: bool = True,
@@ -790,7 +790,7 @@ class AbsolutePositionalEncoding(nn.Module):
790
self.d_model = d_model
791
self.xscale = math.sqrt(self.d_model)
792
self.dropout = torch.nn.Dropout(p=dropout_rate)
793
- self.pe = None
793
+ self.pe: torch.Tensor | None = None
794
self.extend_pe(torch.tensor(0.0).expand(1, max_len))
795
self._register_load_state_dict_pre_hook(_pre_hook)
796
@@ -826,6 +826,7 @@ class AbsolutePositionalEncoding(nn.Module):
826
827
"""
828
self.extend_pe(x)
829
+ assert self.pe is not None
830
x = x * self.xscale + self.pe[:, : x.size(1)]
831
return self.dropout(x)
832
@@ -880,7 +881,7 @@ class CausalConv1D(nn.Conv1d):
881
out_channels: int,
882
kernel_size: int,
883
stride: int = 1,
883
- padding: str | int = 0,
884
+ padding: str | int | list[int] | None = 0,
885
dilation: int = 1,
886
groups: int = 1,
887
bias: bool = True,
@@ -888,7 +889,9 @@ class CausalConv1D(nn.Conv1d):
889
device=None,
890
dtype=None,
891
) -> None:
891
- self.cache_drop_size = None
892
+ self.cache_drop_size: int | None = None
893
+ self._left_padding: int
894
+ self._right_padding: int
895
if padding is None:
896
self._left_padding = kernel_size - 1
897
self._right_padding = stride - 1
@@ -933,6 +936,7 @@ class CausalConv1D(nn.Conv1d):
936
else:
937
new_x = F.pad(x, pad=(0, self._right_padding))
938
new_x = torch.cat([cache, new_x], dim=-1)
939
+ assert self.cache_drop_size is not None
940
if self.cache_drop_size > 0:
941
next_cache = new_x[:, :, : -self.cache_drop_size]
942
else:
@@ -965,7 +969,7 @@ class CausalConv2D(nn.Conv2d):
969
out_channels: int,
970
kernel_size: int,
971
stride: int = 1,
968
- padding: str | int = 0,
972
+ padding: str | int | None = 0,
973
dilation: int = 1,
974
groups: int = 1,
975
bias: bool = True,
vllm/model_executor/models/plamo3.py
+6
-2
@@ -218,10 +218,14 @@ class Plamo3AttentionMixer(nn.Module):
218
219
q_shape = q.shape
220
q = q.reshape(q_shape[:-1] + (q_shape[-1] // self.head_dim, self.head_dim))
221
- q = self.q_norm.forward_native(q).reshape(q_shape)
221
+ normalized_q = self.q_norm.forward_native(q)
222
+ assert isinstance(normalized_q, torch.Tensor)
223
+ q = normalized_q.reshape(q_shape)
224
k_shape = k.shape
225
k = k.reshape(k_shape[:-1] + (k_shape[-1] // self.head_dim, self.head_dim))
224
- k = self.k_norm.forward_native(k).reshape(k_shape)
226
+ normalized_k = self.k_norm.forward_native(k)
227
+ assert isinstance(normalized_k, torch.Tensor)
228
+ k = normalized_k.reshape(k_shape)
229
230
q, k = self.rotary_emb(positions, q, k)
231
attn_output = self.attn(q, k, v)