[Mypy] Fix typing for M models (#54262)
Signed-off-by: Taneem Ibrahim <taneem.ibrahim@gmail.com>
Taneem Ibrahim committed
Sep 1, 2026 at 10:26 UTC
76f3249fbdff761aa897218913050de0526ccca2
26 files changed
+513
-201
tools/pre_commit/mypy.py
-1
@@ -114,7 +114,6 @@ SEPARATE_GROUPS = [
114
EXCLUDE = [
115
r"vllm/model_executor/models/[kK]",
116
r"vllm/model_executor/models/[lL]",
117
- r"vllm/model_executor/models/[mM]",
117
r"vllm/model_executor/models/[nN]",
118
r"vllm/model_executor/models/[oO]",
119
r"vllm/model_executor/models/[pP]",
vllm/model_executor/models/mamba.py
+2
-2
@@ -52,14 +52,14 @@ class MambaDecoderLayer(nn.Module):
52
model_config: ModelConfig | None = None,
53
cache_config: CacheConfig | None = None,
54
quant_config: QuantizationConfig | None = None,
55
- is_lora_enabled: bool | None = False,
55
+ is_lora_enabled: bool = False,
56
prefix: str = "",
57
) -> None:
58
super().__init__()
59
self.config = config
60
self.is_falcon_mamba = config.model_type == "falcon_mamba"
61
self.is_lora_enabled = is_lora_enabled
62
- mixer_rms_eps = config.mixer_rms_eps if self.is_falcon_mamba else None
62
+ mixer_rms_eps = config.mixer_rms_eps if self.is_falcon_mamba else 1e-5
63
self.mixer = MambaMixer(
64
hidden_size=config.hidden_size,
65
ssm_state_size=config.state_size,
vllm/model_executor/models/medusa.py
+3
-1
@@ -54,7 +54,9 @@ class Medusa(nn.Module):
54
needs to have truncated_vocab_size (=k) as an attribute."""
55
56
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
57
- config = vllm_config.speculative_config.draft_model_config.hf_config
57
+ speculative_config = vllm_config.speculative_config
58
+ assert speculative_config is not None
59
+ config = speculative_config.draft_model_config.hf_config
60
super().__init__()
61
self.config = config
62
self.blocks = nn.ModuleList(
vllm/model_executor/models/mellum.py
+24
-18
@@ -92,24 +92,30 @@ class MellumAttention(Qwen3MoeAttention):
92
rope_parameters=rope_parameters,
93
dual_chunk_attention_config=dual_chunk_attention_config,
94
)
95
- self.attn = Attention(
96
- self.num_heads,
97
- self.head_dim,
98
- self.scaling,
99
- num_kv_heads=self.num_kv_heads,
100
- cache_config=cache_config,
101
- quant_config=quant_config,
102
- per_layer_sliding_window=per_layer_sliding_window,
103
- prefix=f"{prefix}.attn",
104
- **(
105
- {
106
- "layer_idx": extract_layer_index(prefix),
107
- "dual_chunk_attention_config": dual_chunk_attention_config,
108
- }
109
- if dual_chunk_attention_config
110
- else {}
111
- ),
112
- )
95
+ if dual_chunk_attention_config:
96
+ self.attn = Attention(
97
+ self.num_heads,
98
+ self.head_dim,
99
+ self.scaling,
100
+ num_kv_heads=self.num_kv_heads,
101
+ cache_config=cache_config,
102
+ quant_config=quant_config,
103
+ per_layer_sliding_window=per_layer_sliding_window,
104
+ prefix=f"{prefix}.attn",
105
+ layer_idx=extract_layer_index(prefix),
106
+ dual_chunk_attention_config=dual_chunk_attention_config,
107
+ )
108
+ else:
109
+ self.attn = Attention(
110
+ self.num_heads,
111
+ self.head_dim,
112
+ self.scaling,
113
+ num_kv_heads=self.num_kv_heads,
114
+ cache_config=cache_config,
115
+ quant_config=quant_config,
116
+ per_layer_sliding_window=per_layer_sliding_window,
117
+ prefix=f"{prefix}.attn",
118
+ )
119
120
self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
121
self.k_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
vllm/model_executor/models/midashenglm.py
+11
-5
@@ -36,7 +36,7 @@ from torch.nn.functional import scaled_dot_product_attention
36
from transformers import BatchFeature
37
38
from vllm.config import VllmConfig
39
-from vllm.config.multimodal import BaseDummyOptions
39
+from vllm.config.multimodal import AudioDummyOptions, BaseDummyOptions
40
from vllm.distributed import get_tensor_model_parallel_world_size
41
from vllm.inputs import MultiModalDataDict
42
from vllm.model_executor.layers.activation import get_act_fn
@@ -570,6 +570,7 @@ class MiDashengLMDummyInputsBuilder(BaseDummyInputsBuilder[MiDashengLMProcessing
570
num_audios = mm_counts.get("audio", 0)
571
572
audio_overrides = mm_options.get("audio")
573
+ assert audio_overrides is None or isinstance(audio_overrides, AudioDummyOptions)
574
575
return {
576
"audio": self._get_dummy_audios(
@@ -593,6 +594,7 @@ class MiDashengLMMultiModalProcessor(
594
) -> tuple[Mapping[str, object], Mapping[str, object]]:
595
mm_data = dict(mm_data)
596
audios = mm_data.pop("audios", [])
597
+ assert isinstance(audios, list)
598
599
min_audio_len = self.info.get_min_audio_len()
600
processed_audios = [
@@ -637,6 +639,7 @@ class MiDashengLMMultiModalProcessor(
639
640
out_mm_data = out_mm_kwargs.get_data()
641
audio_length = out_mm_data.get("audio_length")
642
+ audio_output_lengths: list[int]
643
if audio_length is None:
644
audio_output_lengths = []
645
else:
@@ -645,10 +648,13 @@ class MiDashengLMMultiModalProcessor(
648
if isinstance(audio_length, torch.Tensor)
649
else audio_length
650
)
648
- audio_output_lengths = [
649
- max(1, calculate_mel_frames_dasheng(int(length))) # at least one frame
650
- for length in audio_length_np
651
- ]
651
+ assert isinstance(audio_length_np, (list, tuple, np.ndarray))
652
+ audio_output_lengths = []
653
+ for length in audio_length_np:
654
+ assert isinstance(length, (int, np.integer))
655
+ audio_output_lengths.append(
656
+ max(1, calculate_mel_frames_dasheng(int(length)))
657
+ )
658
659
def get_replacement_midashenglm(item_idx: int):
660
num_features = audio_output_lengths[item_idx]
vllm/model_executor/models/mimo_audio.py
+18
-11
@@ -531,18 +531,18 @@ class MiMoAudioTokenizerConfig(PretrainedConfig):
531
kernel_size: int = 3,
532
activation_function: str = "gelu",
533
encoder_layers: int = 8,
534
- encoder_skip_layer_id: int = None,
534
+ encoder_skip_layer_id: int | None = None,
535
encoder_attention_heads: int = 12,
536
encoder_ffn_dim: int = 3072,
537
encoder_causal: bool = False,
538
- encoder_attn_window_size: list = None,
538
+ encoder_attn_window_size: list[int] | None = None,
539
decoder_layers: int = 8,
540
decoder_attention_heads: int = 12,
541
decoder_ffn_dim: int = 3072,
542
decoder_kernel_size: int = 3,
543
decoder_stride_size: int = 2,
544
decoder_causal: bool = True,
545
- decoder_attn_window_size: list = None,
545
+ decoder_attn_window_size: list[int] | None = None,
546
nfft: int = 1024,
547
vocoder_dim: int = 512,
548
vocoder_intermediate_dim: int = 4096,
@@ -553,16 +553,16 @@ class MiMoAudioTokenizerConfig(PretrainedConfig):
553
window_size: int = 1024,
554
vocoder_padding: str = "same",
555
fmin: int = 0,
556
- fmax: int = None,
556
+ fmax: int | None = None,
557
num_quantizers: int = 12,
558
- codebook_size: list = None,
558
+ codebook_size: list[int] | None = None,
559
threshold_ema_dead_code: int = 10,
560
position_embedding_type: str = "rope",
561
rope_theta: int = 10000,
562
rope_type: str = "default",
563
ln_type: str = "LayerNorm",
564
vocoder_attention_heads: int = 4,
565
- vocoder_attn_window_size: list = None,
565
+ vocoder_attn_window_size: list[int] | None = None,
566
use_istft_only: bool = False,
567
hybrid_attention: bool = False,
568
hybrid_block_size: int = 8,
@@ -808,17 +808,22 @@ class AudioEncoder(nn.Module):
808
config.rope_type,
809
)
810
811
- attn_window_sizes = []
811
+ encoder_attn_window_size = config.encoder_attn_window_size
812
+ assert encoder_attn_window_size is not None
813
+ assert len(encoder_attn_window_size) == 2
814
+ attn_window_size = (
815
+ encoder_attn_window_size[0],
816
+ encoder_attn_window_size[1],
817
+ )
818
+ attn_window_sizes: list[tuple[int, int]] = []
819
if config.hybrid_attention:
820
for i in range(config.encoder_layers):
821
if i % config.swa_per_block < config.swa_per_block - 1:
815
- attn_window_sizes.append(tuple(config.encoder_attn_window_size))
822
+ attn_window_sizes.append(attn_window_size)
823
else:
824
attn_window_sizes.append((-1, -1))
825
else:
819
- attn_window_sizes = [
820
- tuple(config.encoder_attn_window_size)
821
- ] * config.encoder_layers
826
+ attn_window_sizes = [attn_window_size] * config.encoder_layers
827
828
self.layers = nn.ModuleList(
829
[
@@ -848,6 +853,7 @@ class AudioEncoder(nn.Module):
853
else:
854
self.down_sample_layer = None
855
856
+ self.quantizer: ResidualVectorQuantizer | None
857
if config.num_quantizers != 0:
858
self.quantizer = ResidualVectorQuantizer(
859
dimension=config.d_model,
@@ -976,6 +982,7 @@ class AudioEncoder(nn.Module):
982
983
@torch.no_grad()
984
def decode_vq(self, codes):
985
+ assert self.quantizer is not None
986
self.quantizer.float()
987
return self.quantizer.decode(codes)
988
vllm/model_executor/models/mimo_v2.py
+5
-3
@@ -303,6 +303,7 @@ class MiMoV2Attention(nn.Module):
303
backend_enum = requested
304
else:
305
fa_backend = AttentionBackendEnum.FLASH_ATTN_DIFFKV.get_class()
306
+ assert hasattr(fa_backend, "is_supported_on_current_device")
307
if fa_backend.is_supported_on_current_device(
308
head_size=self.head_dim,
309
head_size_v=self.v_head_dim,
@@ -312,6 +313,7 @@ class MiMoV2Attention(nn.Module):
313
else:
314
backend_enum = AttentionBackendEnum.TRITON_ATTN_DIFFKV
315
attn_backend = backend_enum.get_class()
316
+ assert hasattr(attn_backend, "set_head_size_v")
317
attn_backend.set_head_size_v(self.v_head_dim)
318
logger.info_once("Using %s for attention.", attn_backend.get_name())
319
else:
@@ -636,7 +638,7 @@ class MiMoV2Model(nn.Module, EagleModelMixin):
638
)
639
640
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
639
- stacked_params_mapping = [
641
+ stacked_params_mapping: list[tuple[str, str, str | int]] = [
642
# (param_name, shard_name, shard_id)
643
("qkv_proj", "q_proj", "q"),
644
("qkv_proj", "k_proj", "k"),
@@ -709,7 +711,7 @@ class MiMoV2Model(nn.Module, EagleModelMixin):
711
):
712
continue
713
stacked_matched = False
712
- for param_name, weight_name, shard_id in stacked_params_mapping:
714
+ for param_name, weight_name, stacked_shard_id in stacked_params_mapping:
715
if weight_name not in name:
716
continue
717
name_rewritten = name.replace(weight_name, param_name)
@@ -728,7 +730,7 @@ class MiMoV2Model(nn.Module, EagleModelMixin):
730
731
param = params_dict[name_rewritten]
732
weight_loader = getattr(param, "weight_loader", default_weight_loader)
731
- weight_loader(param, loaded_weight, shard_id)
733
+ weight_loader(param, loaded_weight, stacked_shard_id)
734
loaded_params.add(name_rewritten)
735
736
stacked_matched = True
vllm/model_executor/models/mimo_v2_mtp.py
+10
-5
@@ -255,7 +255,7 @@ class MiMoV2MTP(nn.Module):
255
tp_rank = get_tensor_model_parallel_rank()
256
tp_size = get_tensor_model_parallel_world_size()
257
258
- stacked_params_mapping = [
258
+ stacked_params_mapping: list[tuple[str, str, str | int]] = [
259
("gate_up_proj", "gate_proj", 0),
260
("gate_up_proj", "up_proj", 1),
261
# Flash format: separate projections → fused qkv_proj
@@ -308,8 +308,11 @@ class MiMoV2MTP(nn.Module):
308
if name_rewritten not in params_dict:
309
continue
310
param = params_dict[name_rewritten]
311
- weight_loader = getattr(param, "weight_loader", default_weight_loader)
312
- weight_loader(param, loaded_weight, shard_id)
311
+ weight_loader = getattr(param, "weight_loader", None)
312
+ if weight_loader is None:
313
+ default_weight_loader(param, loaded_weight)
314
+ else:
315
+ weight_loader(param, loaded_weight, shard_id)
316
loaded_params.add(name_rewritten)
317
stacked_matched = True
318
break
@@ -331,8 +334,10 @@ class MiMoV2MTP(nn.Module):
334
0, tp_rank * heads_per_rank, heads_per_rank
335
)
336
334
- weight_loader = getattr(param, "weight_loader", default_weight_loader)
335
- weight_loader(param, loaded_weight)
337
+ direct_weight_loader = getattr(
338
+ param, "weight_loader", default_weight_loader
339
+ )
340
+ direct_weight_loader(param, loaded_weight)
341
loaded_params.add(name)
342
343
return loaded_params
vllm/model_executor/models/mimo_v2_omni.py
+119
-43
@@ -12,9 +12,14 @@ import torch.nn as nn
12
import torch.nn.functional as F
13
from transformers import BatchFeature, PretrainedConfig
14
from transformers.models.qwen2_vl.image_processing_qwen2_vl import smart_resize
15
+from typing_extensions import TypedDict
16
17
from vllm.config import VllmConfig
17
-from vllm.config.multimodal import BaseDummyOptions
18
+from vllm.config.multimodal import (
19
+ BaseDummyOptions,
20
+ ImageDummyOptions,
21
+ VideoDummyOptions,
22
+)
23
from vllm.distributed import parallel_state
24
from vllm.distributed import utils as dist_utils
25
from vllm.inputs import MultiModalDataDict
@@ -70,6 +75,18 @@ from .qwen2_vl import _create_qwen2vl_field_factory
75
from .utils import AutoWeightsLoader, IntermediateTensors, WeightsMapper, maybe_prefix
76
77
78
+class MiMoV2OmniAudioInputs(TypedDict):
79
+ type: str
80
+ audio_features: torch.Tensor | list[torch.Tensor]
81
+ audio_token_lens: torch.Tensor | list[int] | None
82
+
83
+
84
+class MiMoV2OmniMultiModalInputs(TypedDict, total=False):
85
+ image: Qwen2_5_VLImageInputs | None
86
+ video: Qwen2_5_VLVideoInputs | None
87
+ audio: MiMoV2OmniAudioInputs | None
88
+
89
+
90
class MiMoVisionMLP(Qwen2_5_VisionMLP):
91
pass
92
@@ -937,11 +954,14 @@ class MiMoV2OmniMultiModalProcessor(BaseMultiModalProcessor[MiMoV2OmniProcessing
954
# Handle video_audio items: convert video part to (TCHW, timestamps) tuple
955
if "video_audio" in mm_data:
956
va_converted: list[VideoAudioInput] = []
940
- for va_item in mm_data["video_audio"]:
957
+ video_audio_items = mm_data["video_audio"]
958
+ assert isinstance(video_audio_items, Sequence)
959
+ for va_item in video_audio_items:
960
if isinstance(va_item, VideoAudioInput):
961
vid = va_item.video
962
else:
963
# Expect (video_frames, audio_source) tuple
964
+ assert isinstance(va_item, (list, tuple)) and len(va_item) == 2
965
vid, audio_src = va_item
966
va_item = VideoAudioInput(video=vid, audio=audio_src)
967
vid = vid
@@ -976,7 +996,9 @@ class MiMoV2OmniMultiModalProcessor(BaseMultiModalProcessor[MiMoV2OmniProcessing
996
997
if "videos" in mm_data:
998
converted: list[tuple[torch.Tensor, torch.Tensor]] = []
979
- for video in mm_data["videos"]:
999
+ videos = mm_data["videos"]
1000
+ assert isinstance(videos, Sequence)
1001
+ for video in videos:
1002
if (
1003
isinstance(video, tuple)
1004
and len(video) == 2
@@ -1037,30 +1059,47 @@ class MiMoV2OmniMultiModalProcessor(BaseMultiModalProcessor[MiMoV2OmniProcessing
1059
video_end_id = p.video_end_token_id
1060
audio_start_id = p.audio_start_token_id
1061
audio_end_id = p.audio_end_token_id
1062
+ assert isinstance(vision_start_id, int)
1063
+ assert isinstance(vision_end_id, int)
1064
+ assert isinstance(video_start_id, int)
1065
+ assert isinstance(video_end_id, int)
1066
1067
def get_image_replacement(item_idx: int) -> PromptUpdateDetails:
1068
out_item = out_mm_kwargs["image"][item_idx]
1069
grid_thw = out_item["image_grid_thw"].data
1070
+ assert isinstance(grid_thw, torch.Tensor)
1071
n_tokens = int(grid_thw.prod()) // merge_size**2
1045
- return [image_pad_id] * n_tokens
1072
+ return PromptUpdateDetails.from_seq([image_pad_id] * n_tokens)
1073
1074
def get_video_replacement(item_idx: int) -> PromptUpdateDetails:
1075
out_item = out_mm_kwargs["video"][item_idx]
1076
grid_thw = out_item["video_grid_thw"].data
1050
- spt = float(out_item["second_per_grid_ts"].data)
1051
- start = float(out_item["video_start_times"].data)
1077
+ second_per_grid_ts = out_item["second_per_grid_ts"].data
1078
+ video_start_time = out_item["video_start_times"].data
1079
+ assert isinstance(grid_thw, torch.Tensor)
1080
+ assert isinstance(second_per_grid_ts, (int, float, torch.Tensor))
1081
+ assert isinstance(video_start_time, (int, float, torch.Tensor))
1082
+ spt = float(second_per_grid_ts)
1083
+ start = float(video_start_time)
1084
1085
T, H, W = map(int, grid_thw)
1086
n_per_grid = H * W // (merge_size * merge_size)
1087
1088
# Check if this is a video_audio item
1089
n_segs_field = out_item.get("video_audio_n_segs")
1058
- n_segs_val = int(n_segs_field.data) if n_segs_field is not None else 0
1090
+ if n_segs_field is None:
1091
+ n_segs_val = 0
1092
+ else:
1093
+ n_segs_data = n_segs_field.data
1094
+ assert isinstance(n_segs_data, (int, torch.Tensor))
1095
+ n_segs_val = int(n_segs_data)
1096
va_seg_lens: list[int] | None = None
1097
if n_segs_val > 0:
1098
seg_lens_field = out_item.get("video_audio_seg_lens")
1099
if seg_lens_field is not None:
1063
- va_seg_lens = seg_lens_field.data[:n_segs_val].tolist()
1100
+ seg_lens_data = seg_lens_field.data
1101
+ assert isinstance(seg_lens_data, torch.Tensor)
1102
+ va_seg_lens = seg_lens_data[:n_segs_val].tolist()
1103
1104
full: list[int] = [video_start_id]
1105
is_embed_mask: list[bool] = [False]
@@ -1099,6 +1138,9 @@ class MiMoV2OmniMultiModalProcessor(BaseMultiModalProcessor[MiMoV2OmniProcessing
1138
is_embed_mask.append(False)
1139
# Audio tokens for this group
1140
seg_len = va_seg_lens[g]
1141
+ assert audio_start_id is not None
1142
+ assert audio_pad_id is not None
1143
+ assert audio_end_id is not None
1144
full.append(audio_start_id)
1145
is_embed_mask.append(False)
1146
full.extend([audio_pad_id] * seg_len)
@@ -1117,8 +1159,11 @@ class MiMoV2OmniMultiModalProcessor(BaseMultiModalProcessor[MiMoV2OmniProcessing
1159
1160
def get_audio_replacement(item_idx: int) -> PromptUpdateDetails:
1161
out_item = out_mm_kwargs["audio"][item_idx]
1120
- tok_len = int(out_item["audio_token_lens"].data)
1121
- return [audio_pad_id] * tok_len
1162
+ audio_token_lens = out_item["audio_token_lens"].data
1163
+ assert isinstance(audio_token_lens, (int, torch.Tensor))
1164
+ assert audio_pad_id is not None
1165
+ tok_len = int(audio_token_lens)
1166
+ return PromptUpdateDetails.from_seq([audio_pad_id] * tok_len)
1167
1168
updates: list[PromptUpdate] = [
1169
PromptReplacement(
@@ -1166,20 +1211,24 @@ class MiMoV2OmniDummyInputsBuilder(BaseDummyInputsBuilder[MiMoV2OmniProcessingIn
1211
target_num_frames = self.info.get_num_frames_with_most_features(
1212
seq_len, mm_counts
1213
)
1214
+ image_overrides = mm_options.get("image")
1215
+ video_overrides = mm_options.get("video")
1216
+ assert image_overrides is None or isinstance(image_overrides, ImageDummyOptions)
1217
+ assert video_overrides is None or isinstance(video_overrides, VideoDummyOptions)
1218
1219
return {
1220
"image": self._get_dummy_images(
1221
width=target_width,
1222
height=target_height,
1223
num_images=num_images,
1175
- overrides=mm_options.get("image"),
1224
+ overrides=image_overrides,
1225
),
1226
"video": self._get_dummy_videos(
1227
width=target_width,
1228
height=target_height,
1229
num_frames=target_num_frames,
1230
num_videos=num_videos,
1182
- overrides=mm_options.get("video"),
1231
+ overrides=video_overrides,
1232
),
1233
}
1234
@@ -1235,6 +1284,7 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1284
)
1285
audio_config = getattr(config, "audio_config", None)
1286
model_path = vllm_config.model_config.model
1287
+ self.audio_encoder: MimoAudioEncoder | None
1288
if audio_config is not None:
1289
with self._mark_tower_model(vllm_config, "audio"):
1290
self.audio_encoder = MimoAudioEncoder(
@@ -1269,12 +1319,12 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1319
image_grid_thw=image_grid_thw,
1320
)
1321
1272
- if image_embeds is not None:
1273
- return Qwen2_5_VLImageEmbeddingInputs(
1274
- type="image_embeds",
1275
- image_embeds=image_embeds,
1276
- image_grid_thw=image_grid_thw,
1277
- )
1322
+ assert image_embeds is not None
1323
+ return Qwen2_5_VLImageEmbeddingInputs(
1324
+ type="image_embeds",
1325
+ image_embeds=image_embeds,
1326
+ image_grid_thw=image_grid_thw,
1327
+ )
1328
1329
def _parse_and_validate_video_input(
1330
self, **kwargs: object
@@ -1295,13 +1345,13 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1345
second_per_grid_ts=second_per_grid_ts,
1346
)
1347
1298
- if video_embeds is not None:
1299
- return Qwen2_5_VLVideoEmbeddingInputs(
1300
- type="video_embeds",
1301
- video_embeds=video_embeds,
1302
- video_grid_thw=video_grid_thw,
1303
- second_per_grid_ts=second_per_grid_ts,
1304
- )
1348
+ assert video_embeds is not None
1349
+ return Qwen2_5_VLVideoEmbeddingInputs(
1350
+ type="video_embeds",
1351
+ video_embeds=video_embeds,
1352
+ video_grid_thw=video_grid_thw,
1353
+ second_per_grid_ts=second_per_grid_ts,
1354
+ )
1355
1356
def _process_image_input(
1357
self, image_input: Qwen2_5_VLImageInputs
@@ -1339,19 +1389,29 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1389
sizes = (grid_thw.prod(-1) // merge_size // merge_size).tolist()
1390
return video_embeds.split(sizes)
1391
1342
- def _parse_and_validate_audio_input(self, **kwargs: object) -> dict | None:
1392
+ def _parse_and_validate_audio_input(
1393
+ self, **kwargs: object
1394
+ ) -> MiMoV2OmniAudioInputs | None:
1395
audio_features = kwargs.pop("audio_features", None)
1396
audio_token_lens = kwargs.pop("audio_token_lens", None)
1397
if audio_features is None:
1398
return None
1399
+ assert isinstance(audio_features, (torch.Tensor, list))
1400
+ if isinstance(audio_features, list):
1401
+ assert all(isinstance(item, torch.Tensor) for item in audio_features)
1402
+ assert audio_token_lens is None or isinstance(
1403
+ audio_token_lens, (torch.Tensor, list)
1404
+ )
1405
return {
1406
"type": "audio",
1407
"audio_features": audio_features,
1408
"audio_token_lens": audio_token_lens,
1409
}
1410
1353
- def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1354
- mm_input_by_modality = {}
1411
+ def _parse_and_validate_multimodal_inputs(
1412
+ self, **kwargs: object
1413
+ ) -> MiMoV2OmniMultiModalInputs:
1414
+ mm_input_by_modality: MiMoV2OmniMultiModalInputs = {}
1415
1416
# Preserve the order of modalities if there are multiple of them
1417
# from the order of kwargs.
@@ -1376,7 +1436,9 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1436
)
1437
return mm_input_by_modality
1438
1379
- def _process_audio_input(self, audio_input: dict) -> tuple[torch.Tensor, ...]:
1439
+ def _process_audio_input(
1440
+ self, audio_input: MiMoV2OmniAudioInputs
1441
+ ) -> tuple[torch.Tensor, ...]:
1442
mel_specs = audio_input["audio_features"]
1443
if self.audio_encoder is None:
1444
return ()
@@ -1407,38 +1469,52 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1469
1470
# Pre-process va audio: one mel spec per va video → per-video audio embeddings
1471
# keyed by va video index (0-based among va videos only)
1410
- va_audio_embs_list: list[tuple[torch.Tensor, ...]] = []
1472
+ va_audio_embs_list: list[torch.Tensor] = []
1473
if va_audio_features is not None and self.audio_encoder is not None:
1412
- mel_list = (
1413
- list(va_audio_features)
1414
- if isinstance(va_audio_features, torch.Tensor)
1415
- else list(va_audio_features)
1416
- )
1474
+ assert isinstance(va_audio_features, (torch.Tensor, list, tuple))
1475
+ mel_list: list[torch.Tensor] = []
1476
+ for mel_spec in va_audio_features:
1477
+ assert isinstance(mel_spec, torch.Tensor)
1478
+ mel_list.append(mel_spec)
1479
for mel_spec in mel_list:
1480
embs, tok_lens = self.audio_encoder.get_audio_feature([mel_spec])
1481
# tok_lens is a list/tensor with one entry (total tokens for this mel)
1482
va_audio_embs_list.append(embs) # shape (total_tok, hidden)
1483
1484
va_cursor = 0 # index into va_audio_embs_list
1485
+ assert video_audio_n_segs is None or isinstance(
1486
+ video_audio_n_segs, torch.Tensor
1487
+ )
1488
+ assert video_audio_seg_lens is None or isinstance(
1489
+ video_audio_seg_lens, torch.Tensor
1490
+ )
1491
1492
# NOTE: Iterate in dict insertion order to preserve token sequence order.
1493
for modality in mm_input_by_modality:
1426
- multimodal_input = mm_input_by_modality[modality]
1494
if modality == "image":
1428
- multimodal_embeddings.extend(
1429
- self._process_image_input(multimodal_input)
1495
+ image_input = mm_input_by_modality["image"]
1496
+ assert isinstance(
1497
+ image_input,
1498
+ (Qwen2_5_VLImagePixelInputs, Qwen2_5_VLImageEmbeddingInputs),
1499
)
1500
+ multimodal_embeddings.extend(self._process_image_input(image_input))
1501
elif modality == "video":
1432
- video_embs_tuple = self._process_video_input(multimodal_input)
1502
+ video_input = mm_input_by_modality["video"]
1503
+ assert isinstance(
1504
+ video_input,
1505
+ (Qwen2_5_VLVideoPixelInputs, Qwen2_5_VLVideoEmbeddingInputs),
1506
+ )
1507
+ video_embs_tuple = self._process_video_input(video_input)
1508
if video_audio_n_segs is None:
1509
multimodal_embeddings.extend(video_embs_tuple)
1510
else:
1436
- grid_thw = multimodal_input["video_grid_thw"]
1511
+ grid_thw = video_input["video_grid_thw"]
1512
for i, vid_embs in enumerate(video_embs_tuple):
1513
n_segs = int(video_audio_n_segs[i])
1514
if n_segs == 0 or not va_audio_embs_list:
1515
multimodal_embeddings.append(vid_embs)
1516
else:
1517
+ assert video_audio_seg_lens is not None
1518
T = int(grid_thw[i][0])
1519
n_per_grid = vid_embs.shape[0] // T
1520
frames = list(vid_embs.split(n_per_grid, dim=0))
@@ -1459,9 +1535,9 @@ class MiMoV2OmniForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, SupportsQ
1535
)
1536
multimodal_embeddings.append(group_audio_embs[g])
1537
elif modality == "audio":
1462
- multimodal_embeddings.extend(
1463
- self._process_audio_input(multimodal_input)
1464
- )
1538
+ audio_input = mm_input_by_modality["audio"]
1539
+ assert audio_input is not None
1540
+ multimodal_embeddings.extend(self._process_audio_input(audio_input))
1541
return tuple(multimodal_embeddings)
1542
1543
def forward(
vllm/model_executor/models/minicpm.py
+1
@@ -456,6 +456,7 @@ class MiniCPMModel(nn.Module, EagleModelMixin):
456
hidden_states = self.embed_input_ids(input_ids)
457
residual = None
458
else:
459
+ assert intermediate_tensors is not None
460
hidden_states = intermediate_tensors["hidden_states"]
461
residual = intermediate_tensors["residual"]
462
vllm/model_executor/models/minicpm_eagle.py
+18
-3
@@ -26,6 +26,7 @@
26
27
import math
28
from collections.abc import Iterable
29
+from typing import TYPE_CHECKING
30
31
import torch
32
from torch import nn
@@ -55,6 +56,14 @@ from .utils import (
56
process_eagle_weight,
57
)
58
59
+if TYPE_CHECKING:
60
+
61
+ class _EagleMiniCPMSupportsPP:
62
+ pass
63
+
64
+else:
65
+ _EagleMiniCPMSupportsPP = SupportsPP
66
+
67
68
class EagleMiniCPMDecoderLayer(nn.Module):
69
def __init__(
@@ -149,7 +158,9 @@ class EagleMiniCPMModel(nn.Module):
158
):
159
super().__init__()
160
152
- config = vllm_config.speculative_config.draft_model_config.hf_config
161
+ speculative_config = vllm_config.speculative_config
162
+ assert speculative_config is not None
163
+ config = speculative_config.draft_model_config.hf_config
164
cache_config = vllm_config.cache_config
165
quant_config = vllm_config.quant_config
166
@@ -291,7 +302,9 @@ class EagleMiniCPMModel(nn.Module):
302
return loaded_params
303
304
294
-class EagleMiniCPMForCausalLM(nn.Module, SupportsLoRA, SupportsPP, SupportsEagle):
305
+class EagleMiniCPMForCausalLM(
306
+ nn.Module, SupportsLoRA, _EagleMiniCPMSupportsPP, SupportsEagle
307
+):
308
packed_modules_mapping = {
309
"qkv_proj": [
310
"q_proj",
@@ -312,7 +325,9 @@ class EagleMiniCPMForCausalLM(nn.Module, SupportsLoRA, SupportsPP, SupportsEagle
325
326
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
327
super().__init__()
315
- config = vllm_config.speculative_config.draft_model_config.hf_config
328
+ speculative_config = vllm_config.speculative_config
329
+ assert speculative_config is not None
330
+ config = speculative_config.draft_model_config.hf_config
331
cache_config = vllm_config.cache_config
332
quant_config = vllm_config.quant_config
333
vllm/model_executor/models/minicpmo.py
+62
-18
@@ -40,7 +40,7 @@ from transformers.models.whisper.modeling_whisper import (
40
)
41
42
from vllm.config import VllmConfig
43
-from vllm.config.multimodal import BaseDummyOptions
43
+from vllm.config.multimodal import AudioDummyOptions, BaseDummyOptions
44
from vllm.inputs import ModalityData, MultiModalDataDict
45
from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargsItems
46
from vllm.multimodal.inputs import (
@@ -66,8 +66,12 @@ from .minicpmv import (
66
_MAX_FRAMES_PER_VIDEO,
67
MiniCPMV2_6,
68
MiniCPMV4_5,
69
+ MiniCPMVBaseModel,
70
MiniCPMVDummyInputsBuilder,
71
+ MiniCPMVImageEmbeddingInputs,
72
+ MiniCPMVImagePixelInputs,
73
MiniCPMVMultiModalDataParser,
74
+ MiniCPMVMultiModalInputs,
75
MiniCPMVMultiModalProcessor,
76
MiniCPMVProcessingInfo,
77
_minicpmv_field_config,
@@ -181,6 +185,10 @@ MiniCPMOAudioInputs: TypeAlias = (
185
)
186
187
188
+class MiniCPMOMultiModalInputs(MiniCPMVMultiModalInputs, total=False):
189
+ audios: MiniCPMOAudioInputs | None
190
+
191
+
192
def _minicpmo_field_config(hf_inputs: Mapping[str, torch.Tensor]):
193
audio_features = hf_inputs.get("audio_features")
194
audio_feature_lens = hf_inputs.get("audio_feature_lens")
@@ -405,6 +413,7 @@ class MiniCPMODummyInputsBuilder(MiniCPMVDummyInputsBuilder[MiniCPMOProcessingIn
413
)
414
415
audio_overrides = mm_options.get("audio")
416
+ assert audio_overrides is None or isinstance(audio_overrides, AudioDummyOptions)
417
418
audio_mm_data = {
419
"audio": self._get_dummy_audios(
@@ -469,7 +478,8 @@ class MiniCPMOMultiModalProcessor(MiniCPMVMultiModalProcessor[MiniCPMOProcessing
478
if isinstance(lens, torch.Tensor):
479
flat_feature_lens.extend(lens.flatten().tolist())
480
else:
472
- flat_feature_lens.append(int(lens))
481
+ assert isinstance(lens, int)
482
+ flat_feature_lens.append(lens)
483
unpadded_audio_features = [
484
feat[:, :length]
485
for feat, length in zip(
@@ -515,6 +525,9 @@ class MiniCPMOMultiModalProcessor(MiniCPMVMultiModalProcessor[MiniCPMOProcessing
525
audios = mm_items.get_items(
526
"audio", (MiniCPMOAudioEmbeddingItems, AudioProcessorItems)
527
)
528
+ assert isinstance(
529
+ audios, (MiniCPMOAudioEmbeddingItems, AudioProcessorItems)
530
+ )
531
532
if isinstance(audios, MiniCPMOAudioEmbeddingItems):
533
single_audio_embeds = audios.get(item_idx)["audio_embeds"]
@@ -652,7 +665,7 @@ class MiniCPMWhisperEncoder(WhisperEncoder):
665
hidden_states, p=self.dropout, training=self.training
666
)
667
655
- encoder_states = ()
668
+ encoder_states: tuple[torch.Tensor, ...] = ()
669
670
for idx, encoder_layer in enumerate(self.layers):
671
encoder_states = encoder_states + (hidden_states,)
@@ -682,7 +695,13 @@ class MiniCPMWhisperEncoder(WhisperEncoder):
695
)
696
697
685
-class MiniCPMOBaseModel:
698
+if TYPE_CHECKING:
699
+ _MiniCPMOBaseModelBase = MiniCPMVBaseModel
700
+else:
701
+ _MiniCPMOBaseModelBase = object
702
+
703
+
704
+class MiniCPMOBaseModel(_MiniCPMOBaseModelBase):
705
"""Base mixin class for MiniCPM-O models with audio support."""
706
707
# Unlike the vision-only MiniCPM-V models, audio weights are loaded here.
@@ -897,8 +916,12 @@ class MiniCPMOBaseModel:
916
audio_feature_lens=audio_feature_lens,
917
)
918
900
- def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
901
- modalities = super()._parse_and_validate_multimodal_inputs(**kwargs)
919
+ def _parse_and_validate_multimodal_inputs(
920
+ self, **kwargs: object
921
+ ) -> MiniCPMOMultiModalInputs:
922
+ modalities = MiniCPMOMultiModalInputs(
923
+ **super()._parse_and_validate_multimodal_inputs(**kwargs)
924
+ )
925
926
# Preserve the order of modalities if there are multiple of them
927
# from the order of kwargs.
@@ -918,16 +941,33 @@ class MiniCPMOBaseModel:
941
if audio_input["type"] == "audio_embeds":
942
return audio_input["audio_embeds"]
943
944
+ assert isinstance(audio_input, MiniCPMOAudioFeatureInputs)
945
return self.get_audio_hidden_states(audio_input)
946
923
- def _process_multimodal_inputs(self, modalities: dict):
924
- multimodal_embeddings = super()._process_multimodal_inputs(modalities)
925
-
926
- for modality in modalities:
927
- if modality == "audios":
928
- audio_input = modalities["audios"]
929
- audio_embeddings = self._process_audio_input(audio_input)
930
- multimodal_embeddings += tuple(audio_embeddings)
947
+ def _process_multimodal_inputs(self, modalities: Mapping[str, object]):
948
+ base_modalities: MiniCPMVMultiModalInputs = {}
949
+ image_input = modalities.get("images")
950
+ video_input = modalities.get("videos")
951
+ assert image_input is None or isinstance(
952
+ image_input, (MiniCPMVImagePixelInputs, MiniCPMVImageEmbeddingInputs)
953
+ )
954
+ assert video_input is None or isinstance(
955
+ video_input, (MiniCPMVImagePixelInputs, MiniCPMVImageEmbeddingInputs)
956
+ )
957
+ if "images" in modalities:
958
+ base_modalities["images"] = image_input
959
+ if "videos" in modalities:
960
+ base_modalities["videos"] = video_input
961
+ multimodal_embeddings = super()._process_multimodal_inputs(base_modalities)
962
+
963
+ audio_input = modalities.get("audios")
964
+ if audio_input is not None:
965
+ assert isinstance(
966
+ audio_input,
967
+ (MiniCPMOAudioFeatureInputs, MiniCPMOAudioEmbeddingInputs),
968
+ )
969
+ audio_embeddings = self._process_audio_input(audio_input)
970
+ multimodal_embeddings += tuple(audio_embeddings)
971
972
return multimodal_embeddings
973
@@ -983,7 +1023,7 @@ class MiniCPMO(MiniCPMOBaseModel, MiniCPMV2_6):
1023
try:
1024
version_str = str(config.version)
1025
version_parts = version_str.split(".")
986
- version = tuple(int(x) for x in version_parts[:2])
1026
+ version_values = tuple(int(x) for x in version_parts[:2])
1027
except (ValueError, TypeError) as e:
1028
raise ValueError(
1029
f"Invalid model version format in config: {config.version}. "
@@ -991,17 +1031,21 @@ class MiniCPMO(MiniCPMOBaseModel, MiniCPMV2_6):
1031
) from e
1032
else:
1033
# Default to 2.6 for backward compatibility
994
- version = (2, 6)
1034
+ version_values = (2, 6)
1035
1036
# Dispatch class based on version
997
- instance_cls = _MINICPMO_SUPPORT_VERSION.get(version)
1037
+ if len(version_values) == 2:
1038
+ version_key = (version_values[0], version_values[1])
1039
+ instance_cls = _MINICPMO_SUPPORT_VERSION.get(version_key)
1040
+ else:
1041
+ instance_cls = None
1042
if instance_cls is None:
1043
supported_versions = ", ".join(
1044
[f"{v[0]}.{v[1]}" for v in sorted(_MINICPMO_SUPPORT_VERSION.keys())]
1045
)
1046
raise ValueError(
1047
f"Currently, MiniCPMO only supports versions "
1004
- f"{supported_versions}. Got version: {version}"
1048
+ f"{supported_versions}. Got version: {version_values}"
1049
)
1050
1051
return instance_cls(vllm_config=vllm_config, prefix=prefix)
vllm/model_executor/models/minicpmv.py
+43
-13
@@ -41,7 +41,7 @@ from transformers.dynamic_module_utils import (
41
get_class_from_dynamic_module,
42
resolve_trust_remote_code,
43
)
44
-from typing_extensions import TypeVar
44
+from typing_extensions import TypedDict, TypeVar
45
46
from vllm.config import VllmConfig
47
from vllm.config.multimodal import (
@@ -97,7 +97,7 @@ from vllm.transformers_utils.processor import (
97
cached_get_image_processor,
98
)
99
from vllm.transformers_utils.utils import convert_model_repo_to_path
100
-from vllm.utils.collection_utils import flatten_2d_lists
100
+from vllm.utils.collection_utils import flatten_2d_lists, is_list_of
101
from vllm.utils.gpu_sync_debug import gpu_sync_allowed
102
from vllm.utils.tensor_schema import TensorSchema, TensorShape
103
from vllm.utils.torch_utils import set_default_torch_dtype
@@ -386,6 +386,7 @@ class Resampler4_5(Resampler2_5):
386
for i in range(bs):
387
tgt_h, tgt_w = tgt_sizes[i]
388
if temporal_pos_emb:
389
+ assert temporal_ids_flatten is not None
390
if temporal_ids_flatten[i] == -1:
391
pos_embed_temporal.append(
392
torch.zeros(self.embed_dim, dtype=dtype, device=device)
@@ -549,6 +550,11 @@ class MiniCPMVVideoEmbeddingItems(DictEmbeddingItems):
550
return len(self.get(index)["video_image_sizes"])
551
552
553
+class MiniCPMVMultiModalInputs(TypedDict, total=False):
554
+ images: MiniCPMVImageInputs | None
555
+ videos: MiniCPMVImageInputs | None
556
+
557
+
558
class MiniCPMVMultiModalDataParser(MultiModalDataParser):
559
# The per-item sizes are what size the placeholder range.
560
embedding_fields = {
@@ -833,6 +839,7 @@ class MiniCPMVDummyInputsBuilder(BaseDummyInputsBuilder[_I]):
839
840
image_overrides = mm_options.get("image")
841
video_overrides = mm_options.get("video")
842
+ assert image_overrides is None or isinstance(image_overrides, ImageDummyOptions)
843
844
# Convert video overrides to image overrides for per-frame image generation,
845
# and apply num_frames override to num_video_frames.
@@ -842,6 +849,7 @@ class MiniCPMVDummyInputsBuilder(BaseDummyInputsBuilder[_I]):
849
num_video_frames = min(num_video_frames, video_overrides.num_frames)
850
if video_overrides.width or video_overrides.height:
851
video_frame_overrides = ImageDummyOptions(
852
+ count=video_overrides.count,
853
width=video_overrides.width,
854
height=video_overrides.height,
855
)
@@ -1081,6 +1089,9 @@ class MiniCPMVMultiModalProcessor(BaseMultiModalProcessor[_I]):
1089
images = mm_items.get_items(
1090
"image", (MiniCPMVImageEmbeddingItems, ImageProcessorItems)
1091
)
1092
+ assert isinstance(
1093
+ images, (MiniCPMVImageEmbeddingItems, ImageProcessorItems)
1094
+ )
1095
1096
image_size = images.get_image_size(item_idx)
1097
@@ -1097,6 +1108,9 @@ class MiniCPMVMultiModalProcessor(BaseMultiModalProcessor[_I]):
1108
videos = mm_items.get_items(
1109
"video", (MiniCPMVVideoEmbeddingItems, VideoProcessorItems)
1110
)
1111
+ assert isinstance(
1112
+ videos, (MiniCPMVVideoEmbeddingItems, VideoProcessorItems)
1113
+ )
1114
1115
frame_size = videos.get_frame_size(item_idx)
1116
num_frames = videos.get_num_frames(item_idx)
@@ -1199,6 +1213,7 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1213
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1214
config = vllm_config.model_config.hf_config
1215
multimodal_config = vllm_config.model_config.multimodal_config
1216
+ assert multimodal_config is not None
1217
quant_config = vllm_config.quant_config
1218
self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1219
super().__init__()
@@ -1261,10 +1276,15 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1276
image_embeds=image_embeds,
1277
)
1278
1279
+ assert is_list_of(pixel_values, list, check="all")
1280
+ pixel_values_list: list[list[torch.Tensor]] = []
1281
+ for pixel_value in pixel_values:
1282
+ assert is_list_of(pixel_value, torch.Tensor, check="all")
1283
+ pixel_values_list.append(pixel_value)
1284
tgt_sizes = kwargs.pop("tgt_sizes")
1285
1266
- num_slices_flat = torch.tensor([len(ps) for ps in pixel_values])
1267
- pixel_values_flat = flatten_bn(pixel_values)
1286
+ num_slices_flat = torch.tensor([len(ps) for ps in pixel_values_list])
1287
+ pixel_values_flat = flatten_2d_lists(pixel_values_list)
1288
tgt_sizes_flat = flatten_bn(tgt_sizes, concat=True)
1289
1290
return MiniCPMVImagePixelInputs(
@@ -1274,8 +1294,11 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1294
num_slices=num_slices_flat,
1295
)
1296
1277
- def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1278
- modalities = {}
1297
+ def _parse_and_validate_multimodal_inputs(
1298
+ self, **kwargs: object
1299
+ ) -> MiniCPMVMultiModalInputs:
1300
+ kwargs.pop("modality", None)
1301
+ modalities: MiniCPMVMultiModalInputs = {}
1302
1303
# Preserve the order of modalities if there are multiple of them
1304
# from the order of kwargs.
@@ -1304,12 +1327,13 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1327
if image_input["type"] == "image_embeds":
1328
return image_input["image_embeds"]
1329
1330
+ assert isinstance(image_input, MiniCPMVImagePixelInputs)
1331
image_features_flat = self.get_vision_hidden_states(image_input)
1332
1333
num_slices = image_input["num_slices"]
1334
return [e.flatten(0, 1) for e in image_features_flat.split(num_slices.tolist())]
1335
1312
- def _process_multimodal_inputs(self, modalities: dict):
1336
+ def _process_multimodal_inputs(self, modalities: MiniCPMVMultiModalInputs):
1337
# The result multimodal_embeddings is tuple of tensors, with each
1338
# tensor corresponding to a multimodal data item (image or video).
1339
multimodal_embeddings: tuple[torch.Tensor, ...] = ()
@@ -1319,10 +1343,12 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1343
for modality in modalities:
1344
if modality == "images":
1345
image_input = modalities["images"]
1346
+ assert image_input is not None
1347
image_embeddings = self._process_vision_input(image_input)
1348
multimodal_embeddings += tuple(image_embeddings)
1349
if modality == "videos":
1350
video_input = modalities["videos"]
1351
+ assert video_input is not None
1352
video_embeddings = self._process_vision_input(video_input)
1353
multimodal_embeddings += tuple(video_embeddings)
1354
@@ -1915,23 +1941,27 @@ class MiniCPMV(MiniCPMVBaseModel, SupportsMultiModal, SupportsLoRA):
1941
1942
def __new__(cls, *, vllm_config: VllmConfig, prefix: str = ""):
1943
config = vllm_config.model_config.hf_config
1944
+ version_values: tuple[int, ...]
1945
if not hasattr(config, "version"):
1946
if config.hidden_size == 2304 and config.query_num == 64:
1920
- version = (2, 0)
1947
+ version_values = (2, 0)
1948
else:
1922
- version = (2, 5)
1949
+ version_values = (2, 5)
1950
else:
1924
- version = str(config.version).split(".")
1925
- version = tuple([int(x) for x in version])
1951
+ version_values = tuple(int(x) for x in str(config.version).split("."))
1952
# Dispatch class based on version
1927
- instance_cls = _SUPPORT_VERSION.get(version)
1953
+ if len(version_values) == 2:
1954
+ version_key = (version_values[0], version_values[1])
1955
+ instance_cls = _SUPPORT_VERSION.get(version_key)
1956
+ else:
1957
+ instance_cls = None
1958
if instance_cls is None:
1959
supported_versions = ", ".join(
1960
[f"{v[0]}.{v[1]}" for v in sorted(_SUPPORT_VERSION.keys())]
1961
)
1962
raise ValueError(
1963
f"Currently, MiniCPMV only supports versions "
1934
- f"{supported_versions}. Got version: {version}"
1964
+ f"{supported_versions}. Got version: {version_values}"
1965
)
1966
1967
# quant_config references base class members,
vllm/model_executor/models/minicpmv4_6.py
+9
-2
@@ -98,7 +98,9 @@ class MiniCPMV4_6MultiModalProcessor(MiniCPMVMultiModalProcessor):
98
ds = mm_kwargs.get("downsample_mode")
99
if ds is not None:
100
return str(ds)
101
- return self.info._get_downsample_mode()
101
+ info = self.info
102
+ assert isinstance(info, MiniCPMV4_6ProcessingInfo)
103
+ return info._get_downsample_mode()
104
105
def get_image_prompt_texts(
106
self,
@@ -106,7 +108,9 @@ class MiniCPMV4_6MultiModalProcessor(MiniCPMVMultiModalProcessor):
108
image_idx: int = 0,
109
downsample_mode: str | None = None,
110
) -> str:
109
- return self.info.get_slice_image_placeholder(
111
+ info = self.info
112
+ assert isinstance(info, MiniCPMV4_6ProcessingInfo)
113
+ return info.get_slice_image_placeholder(
114
image_size,
115
image_idx=image_idx,
116
downsample_mode=downsample_mode,
@@ -128,6 +132,7 @@ class MiniCPMV4_6MultiModalProcessor(MiniCPMVMultiModalProcessor):
132
# which one is used. Using image_token for video silently produces
133
# garbage descriptions.
134
info = self.info
135
+ assert isinstance(info, MiniCPMV4_6ProcessingInfo)
136
grids, source_tokens, patch_tokens = info._compute_visual_tokens(
137
image_size,
138
max_slice_nums=info.get_video_max_slice_num(),
@@ -1016,6 +1021,7 @@ class MiniCPMV4_6ForConditionalGeneration(
1021
config: MiniCPMV4_6Config = vllm_config.model_config.hf_config
1022
quant_config = vllm_config.quant_config
1023
multimodal_config = vllm_config.model_config.multimodal_config
1024
+ assert multimodal_config is not None
1025
1026
self.config = config
1027
self.multimodal_config = multimodal_config
@@ -1078,6 +1084,7 @@ class MiniCPMV4_6ForConditionalGeneration(
1084
image_embeds=image_embeds,
1085
)
1086
1087
+ assert isinstance(pixel_values, torch.Tensor | list)
1088
tgt_sizes = kwargs.pop("tgt_sizes")
1089
num_slices_flat = torch.tensor([len(ps) for ps in pixel_values])
1090
pixel_values_flat = flatten_bn(pixel_values)
vllm/model_executor/models/mistral.py
+8
-2
@@ -221,15 +221,21 @@ class MistralModel(LlamaModel):
221
intermediate_tensors: IntermediateTensors | None,
222
inputs_embeds: torch.Tensor | None = None,
223
t_cond: torch.Tensor | None = None,
224
+ **extra_layer_kwargs: object,
225
) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
226
return super().forward(
226
- input_ids, positions, intermediate_tensors, inputs_embeds, t_cond=t_cond
227
+ input_ids,
228
+ positions,
229
+ intermediate_tensors,
230
+ inputs_embeds,
231
+ t_cond=t_cond,
232
+ **extra_layer_kwargs,
233
)
234
235
236
class MistralForCausalLM(LlamaForCausalLM):
237
# Mistral: We don't support LoRA on the embedding layers.
232
- embedding_modules: dict[str, str] = {}
238
+ embedding_modules = {}
239
240
# Mistral/Llama models can also be loaded with --load-format mistral
241
# from consolidated.safetensors checkpoints
vllm/model_executor/models/mistral3.py
+1
@@ -196,6 +196,7 @@ class Mistral3HFEncoderInfo(PixtralHFEncoderInfo):
196
image_height = math.floor(image_height / ratio)
197
198
patch_size = self.vision_config.patch_size
199
+ assert isinstance(self.hf_config, Mistral3Config)
200
spatial_merge_size = self.hf_config.spatial_merge_size
201
202
# The HF processor rounds each dimension up to the vision patch size
vllm/model_executor/models/mistral_eagle.py
+25
-4
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from collections.abc import Iterable
5
+from typing import TYPE_CHECKING
6
7
import torch
8
import torch.nn as nn
@@ -33,6 +34,20 @@ from vllm.model_executor.models.utils import (
34
logger = init_logger(__name__)
35
36
37
+if TYPE_CHECKING:
38
+
39
+ class _EagleMistralModelBase(nn.Module):
40
+ pass
41
+
42
+ class _EagleMistralForCausalLMBase(nn.Module):
43
+ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
44
+ raise NotImplementedError
45
+
46
+else:
47
+ _EagleMistralModelBase = MistralModel
48
+ _EagleMistralForCausalLMBase = MistralForCausalLM
49
+
50
+
51
class EagleMistralDecoderLayer(MistralDecoderLayer):
52
def __init__(
53
self,
@@ -47,7 +62,7 @@ class EagleMistralDecoderLayer(MistralDecoderLayer):
62
63
64
@support_torch_compile
50
-class EagleMistralModel(MistralModel):
65
+class EagleMistralModel(_EagleMistralModelBase):
66
def __init__(
67
self,
68
*,
@@ -58,7 +73,9 @@ class EagleMistralModel(MistralModel):
73
# Bypass MistralModel.__init__ to avoid creating duplicate attention
74
# layer entries in the global context.
75
nn.Module.__init__(self)
61
- self.config = vllm_config.speculative_config.draft_model_config.hf_config
76
+ speculative_config = vllm_config.speculative_config
77
+ assert speculative_config is not None
78
+ self.config = speculative_config.draft_model_config.hf_config
79
self.vocab_size = self.config.vocab_size
80
# Get drafter's quantization config
81
self.quant_config = get_draft_quant_config(vllm_config)
@@ -112,7 +129,9 @@ class EagleMistralModel(MistralModel):
129
return hidden_states, hidden_states
130
131
115
-class EagleMistralForCausalLM(MistralForCausalLM, SupportsMultiModalEmbeddings):
132
+class EagleMistralForCausalLM(
133
+ _EagleMistralForCausalLMBase, SupportsMultiModalEmbeddings
134
+):
135
mistral_mapping = MistralForCausalLM.mistral_mapping | {
136
"eagle_linear": "model.fc",
137
}
@@ -121,7 +140,9 @@ class EagleMistralForCausalLM(MistralForCausalLM, SupportsMultiModalEmbeddings):
140
# Bypass MistralForCausalLM.__init__ to use the draft model config
141
# and to avoid creating an lm_head.
142
nn.Module.__init__(self)
124
- self.config = vllm_config.speculative_config.draft_model_config.hf_config
143
+ speculative_config = vllm_config.speculative_config
144
+ assert speculative_config is not None
145
+ self.config = speculative_config.draft_model_config.hf_config
146
target_layer_num = vllm_config.model_config.get_num_layers(
147
vllm_config.parallel_config
148
)
vllm/model_executor/models/mistral_large_3_eagle.py
+15
-3
@@ -4,6 +4,7 @@
4
import copy
5
from collections.abc import Iterable
6
from functools import partial
7
+from typing import TYPE_CHECKING
8
9
import regex
10
import torch
@@ -28,6 +29,15 @@ from .utils import WeightsMapper, make_empty_intermediate_tensors_factory, maybe
29
logger = init_logger(__name__)
30
31
32
+if TYPE_CHECKING:
33
+
34
+ class _EagleMistralLarge3ForCausalLMBase(nn.Module):
35
+ pass
36
+
37
+else:
38
+ _EagleMistralLarge3ForCausalLMBase = MistralLarge3ForCausalLM
39
+
40
+
41
@support_torch_compile
42
class EagleMistralLarge3Model(DeepseekV2Model):
43
def __init__(
@@ -108,7 +118,7 @@ class EagleMistralLarge3Model(DeepseekV2Model):
118
119
120
class EagleMistralLarge3ForCausalLM(
111
- MistralLarge3ForCausalLM, SupportsMultiModalEmbeddings
121
+ _EagleMistralLarge3ForCausalLMBase, SupportsMultiModalEmbeddings
122
):
123
hf_to_vllm_mapper = MistralLarge3ForCausalLM.hf_to_vllm_mapper | WeightsMapper(
124
orig_to_new_regex={
@@ -119,13 +129,15 @@ class EagleMistralLarge3ForCausalLM(
129
)
130
131
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
132
+ speculative_config = vllm_config.speculative_config
133
+ assert speculative_config is not None
134
target_layer_num = vllm_config.model_config.get_num_layers(
135
vllm_config.parallel_config
136
)
125
- vllm_config.model_config = vllm_config.speculative_config.draft_model_config
137
+ vllm_config.model_config = speculative_config.draft_model_config
138
# draft model quantization config may differ from target model
139
self.quant_config = VllmConfig.get_quantization_config(
128
- vllm_config.speculative_config.draft_model_config, vllm_config.load_config
140
+ speculative_config.draft_model_config, vllm_config.load_config
141
)
142
vllm_config.quant_config = self.quant_config
143
self.model_cls = partial(
vllm/model_executor/models/mllama4.py
+10
-7
@@ -620,14 +620,16 @@ class Mllama4MultiModalProcessor(BaseMultiModalProcessor[Mllama4ProcessingInfo])
620
max_num_chunks=self.info.get_max_num_tiles(),
621
patch_size=SizeDict(height=tile_size, width=tile_size),
622
)
623
- best_fit_sizes = [
624
- get_best_fit(
625
- (image.size[1], image.size[0]),
626
- torch.tensor(possible_resolutions),
627
- resize_to_max_canvas=image_processor.resize_to_max_canvas,
623
+ best_fit_sizes = []
624
+ for image in parsed_images:
625
+ assert image is not None
626
+ best_fit_sizes.append(
627
+ get_best_fit(
628
+ (image.size[1], image.size[0]),
629
+ torch.tensor(possible_resolutions),
630
+ resize_to_max_canvas=image_processor.resize_to_max_canvas,
631
+ )
632
)
629
- for image in parsed_images
630
- ]
633
# TODO tile height/width do not necessarily need to match
634
aspect_ratios = [
635
(image_size[0] // tile_size, image_size[1] // tile_size)
@@ -760,6 +762,7 @@ class Llama4ForConditionalGeneration(
762
config = vllm_config.model_config.hf_config
763
quant_config = vllm_config.quant_config
764
multimodal_config = vllm_config.model_config.multimodal_config
765
+ assert multimodal_config is not None
766
self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
767
768
self.vllm_config = vllm_config
vllm/model_executor/models/module_mapping.py
+4
-4
@@ -17,10 +17,10 @@ class MultiModelKeys:
17
18
@staticmethod
19
def from_string_field(
20
- language_model: str | list[str] = None,
21
- connector: str | list[str] = None,
22
- tower_model: str | list[str] = None,
23
- generator: str | list[str] = None,
20
+ language_model: str | list[str] | None = None,
21
+ connector: str | list[str] | None = None,
22
+ tower_model: str | list[str] | None = None,
23
+ generator: str | list[str] | None = None,
24
**kwargs,
25
) -> "MultiModelKeys":
26
def to_list(value):
vllm/model_executor/models/molmo.py
+7
-4
@@ -332,7 +332,7 @@ class VisionTransformer(nn.Module):
332
):
333
super().__init__()
334
scale = config.image_emb_dim**-0.5
335
- self.patch_num = config.image_num_patch
335
+ self.patch_num: tuple[int, int] = config.image_num_patch
336
self.class_embedding = nn.Parameter(torch.randn(config.image_emb_dim) * scale)
337
self.num_prefix_tokens: int = NUM_PREFIX_TOKENS
338
self.positional_embedding = nn.Parameter(
@@ -349,7 +349,7 @@ class VisionTransformer(nn.Module):
349
config, quant_config, prefix=f"{prefix}.transformer"
350
)
351
352
- def add_pos_emb(self, x: torch.Tensor, patch_num: int) -> torch.Tensor:
352
+ def add_pos_emb(self, x: torch.Tensor, patch_num: tuple[int, int]) -> torch.Tensor:
353
cls_emb = self.positional_embedding[0:1]
354
pos_emb = self.positional_embedding[1:]
355
@@ -380,7 +380,7 @@ class VisionTransformer(nn.Module):
380
return x
381
382
def forward(
383
- self, x: torch.Tensor, patch_num: int | None = None
383
+ self, x: torch.Tensor, patch_num: tuple[int, int] | None = None
384
) -> list[torch.Tensor]:
385
"""
386
: param x: (batch_size, num_patch, n_pixels)
@@ -484,12 +484,15 @@ class MolmoAttention(nn.Module):
484
def _apply_qk_norm(
485
self, q: torch.Tensor, k: torch.Tensor
486
) -> tuple[torch.Tensor, torch.Tensor]:
487
+ assert self.q_norm is not None
488
+ assert self.k_norm is not None
489
if self.tp_size > 1:
490
q = tensor_model_parallel_all_gather(q.contiguous())
491
k = tensor_model_parallel_all_gather(k.contiguous())
492
q = self.q_norm(q)
493
k = self.k_norm(k)
494
if self.tp_size > 1:
495
+ assert self.tp_rank is not None
496
splitter = partial(split_tensor_along_last_dim, num_partitions=self.tp_size)
497
q = splitter(q)[self.tp_rank]
498
k = splitter(k)[self.tp_rank]
@@ -1345,7 +1348,7 @@ class MolmoForCausalLM(
1348
vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1349
)
1350
1348
- self.img_patch_id = None
1351
+ self.img_patch_id: int | None = None
1352
1353
self.lm_head = ParallelLMHead(
1354
config.embedding_size or config.vocab_size,
vllm/model_executor/models/molmo2.py
+41
-9
@@ -22,6 +22,7 @@ from transformers import (
22
)
23
from transformers.image_utils import ImageInput
24
from transformers.video_utils import VideoMetadata
25
+from typing_extensions import TypedDict
26
27
from vllm.compilation.decorators import support_torch_compile
28
from vllm.config import CacheConfig, VllmConfig
@@ -157,6 +158,11 @@ class Molmo2VideoInputs(TensorSchema):
158
num_video_tokens: Annotated[torch.Tensor, TensorShape("nv")]
159
160
161
+class Molmo2MultiModalInputs(TypedDict, total=False):
162
+ images: Molmo2ImageInputs | None
163
+ videos: Molmo2VideoInputs | None
164
+
165
+
166
@dataclass
167
class VitConfig:
168
"""Config for a vision transformer"""
@@ -474,7 +480,7 @@ class Molmo2VisionTransformer(nn.Module):
480
super().__init__()
481
scale = config.hidden_size**-0.5
482
self.num_prefix_tokens: int = 0 # no class embeddings
477
- self.patch_num = config.image_num_patch
483
+ self.patch_num: tuple[int, int] = config.image_num_patch
484
self.positional_embedding = nn.Parameter(
485
torch.randn(config.image_num_pos, config.hidden_size) * scale,
486
)
@@ -490,7 +496,7 @@ class Molmo2VisionTransformer(nn.Module):
496
prefix=maybe_prefix(prefix, "transformer"),
497
)
498
493
- def add_pos_emb(self, x: torch.Tensor, patch_num: int) -> torch.Tensor:
499
+ def add_pos_emb(self, x: torch.Tensor, patch_num: tuple[int, int]) -> torch.Tensor:
500
pos_emb = self.positional_embedding
501
502
pos_emb = pos_emb.reshape(
@@ -522,7 +528,7 @@ class Molmo2VisionTransformer(nn.Module):
528
def forward(
529
self,
530
x: torch.Tensor,
525
- patch_num: int | None = None,
531
+ patch_num: tuple[int, int] | None = None,
532
) -> list[torch.Tensor]:
533
"""
534
: param x: (batch_size, num_patch, n_pixels)
@@ -652,6 +658,7 @@ class ImagePoolingAttention(nn.Module):
658
if self.use_pytorch_sdpa:
659
output = self.forward_sdpa(xq, xk, xv, attn_mask)
660
else:
661
+ assert self.attn is not None
662
output = self.attn(xq, xk, xv)
663
664
output, _ = self.o_proj(output)
@@ -954,12 +961,15 @@ class Molmo2Attention(nn.Module):
961
q: torch.Tensor,
962
k: torch.Tensor,
963
) -> tuple[torch.Tensor, torch.Tensor]:
964
+ assert self.q_norm is not None
965
+ assert self.k_norm is not None
966
if self.tp_size > 1:
967
q = tensor_model_parallel_all_gather(q.contiguous())
968
k = tensor_model_parallel_all_gather(k.contiguous())
969
q = self.q_norm(q)
970
k = self.k_norm(k)
971
if self.tp_size > 1:
972
+ assert self.tp_rank is not None
973
splitter = partial(split_tensor_along_last_dim, num_partitions=self.tp_size)
974
q = splitter(q)[self.tp_rank]
975
k = splitter(k)[self.tp_rank]
@@ -1911,10 +1921,10 @@ class Molmo2DummyInputsBuilder(BaseDummyInputsBuilder[Molmo2ProcessingInfo]):
1921
)
1922
videos = [v.copy() for v in videos]
1923
1914
- video_items = []
1924
+ video_items: list[VideoItem] = []
1925
for video in videos:
1926
video_num_frames = video.shape[0]
1917
- video_metadata = {
1927
+ video_metadata: dict[str, int | float | str | bool | list[int]] = {
1928
"fps": 2.0,
1929
"duration": video_num_frames / 2.0,
1930
"total_num_frames": video_num_frames,
@@ -1972,6 +1982,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
1982
image_processor = hf_processor.image_processor
1983
1984
if videos := mm_data.pop("videos", []):
1985
+ assert isinstance(videos, Sequence)
1986
bos_token_id = tokenizer.bos_token_id or tokenizer.eos_token_id
1987
1988
pixel_values_videos_lst = []
@@ -1983,7 +1994,9 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
1994
num_video_tokens_lst = []
1995
1996
for item in videos:
1997
+ assert isinstance(item, tuple) and len(item) == 2
1998
video_array, metadata = item
1999
+ assert isinstance(metadata, Mapping)
2000
2001
# NOTE: metadata.frames_indices indicates
2002
# the sampled frames indices of pre-sampled videos, which is
@@ -2240,6 +2253,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2253
def get_video_replacement_molmo2(item_idx: int):
2254
video, metadata = mm_items["video"][item_idx]
2255
do_sample_frames = hf_processor_mm_kwargs.get("do_sample_frames")
2256
+ assert do_sample_frames is None or isinstance(do_sample_frames, bool)
2257
2258
timestamps = self.info._get_video_second_idx(metadata, do_sample_frames)
2259
ncols, nrows = self.info.get_base_grid_size(video_processor)
@@ -2408,10 +2422,17 @@ class Molmo2ForConditionalGeneration(
2422
image_tokens = kwargs.pop("image_tokens", None)
2423
num_image_tokens = kwargs.pop("num_image_tokens", None)
2424
2425
+ assert isinstance(token_pooling, torch.Tensor)
2426
+ assert isinstance(num_pooled_patches, torch.Tensor)
2427
+ assert isinstance(num_patches, torch.Tensor)
2428
+ assert isinstance(image_tokens, torch.Tensor)
2429
+ assert isinstance(num_image_tokens, torch.Tensor)
2430
+
2431
accum_patches = [0] + num_patches.cumsum(dim=0)[:-1].tolist()
2432
patch_offset = 0
2433
new_token_pooling = token_pooling.clone()
2414
- for i, n in enumerate(num_pooled_patches):
2434
+ for i, n_tensor in enumerate(num_pooled_patches):
2435
+ n = int(n_tensor)
2436
cur_slice = token_pooling[patch_offset : patch_offset + n]
2437
index_offset = int(accum_patches[i])
2438
new_token_pooling[patch_offset : patch_offset + n] = torch.where(
@@ -2443,10 +2464,17 @@ class Molmo2ForConditionalGeneration(
2464
video_tokens = kwargs.pop("video_tokens", None)
2465
num_video_tokens = kwargs.pop("num_video_tokens", None)
2466
2467
+ assert isinstance(token_pooling, torch.Tensor)
2468
+ assert isinstance(num_pooled_patches, torch.Tensor)
2469
+ assert isinstance(num_patches, torch.Tensor)
2470
+ assert isinstance(video_tokens, torch.Tensor)
2471
+ assert isinstance(num_video_tokens, torch.Tensor)
2472
+
2473
accum_patches = [0] + num_patches.cumsum(dim=0)[:-1].tolist()
2474
patch_offset = 0
2475
new_token_pooling = token_pooling.clone()
2449
- for i, n in enumerate(num_pooled_patches):
2476
+ for i, n_tensor in enumerate(num_pooled_patches):
2477
+ n = int(n_tensor)
2478
cur_slice = token_pooling[patch_offset : patch_offset + n]
2479
index_offset = int(accum_patches[i])
2480
new_token_pooling[patch_offset : patch_offset + n] = torch.where(
@@ -2464,8 +2492,10 @@ class Molmo2ForConditionalGeneration(
2492
num_video_tokens=num_video_tokens,
2493
)
2494
2467
- def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
2468
- modalities = {}
2495
+ def _parse_and_validate_multimodal_inputs(
2496
+ self, **kwargs: object
2497
+ ) -> Molmo2MultiModalInputs:
2498
+ modalities: Molmo2MultiModalInputs = {}
2499
2500
for input_key in kwargs:
2501
if input_key in ("pixel_values",) and "images" not in modalities:
@@ -2544,10 +2574,12 @@ class Molmo2ForConditionalGeneration(
2574
for modality in modalities:
2575
if modality == "images":
2576
image_input = modalities["images"]
2577
+ assert image_input is not None
2578
image_embeddings = self._process_image_input(image_input)
2579
multimodal_embeddings += image_embeddings
2580
if modality == "videos":
2581
video_input = modalities["videos"]
2582
+ assert video_input is not None
2583
video_embeddings = self._process_video_input(video_input)
2584
multimodal_embeddings += video_embeddings
2585
vllm/model_executor/models/moonvit.py
+3
-1
@@ -551,9 +551,11 @@ class MoonVitEncoder(nn.Module):
551
max_seqlen: torch.Tensor | None = None,
552
) -> torch.Tensor:
553
if rope_freqs_cis is None:
554
+ assert grid_hw is not None
555
rope_freqs_cis = self.rope_2d.get_freqs_cis_by_seqlens(grid_hws=grid_hw)
556
557
if cu_seqlens is None:
558
+ assert grid_hw is not None
559
lengths = torch.cat(
560
(
561
torch.zeros(1, device=hidden_states.device, dtype=grid_hw.dtype),
@@ -578,7 +580,7 @@ class MoonVitEncoder(nn.Module):
580
def patch_merger(
581
x: torch.Tensor,
582
grid_hw: torch.Tensor,
581
- merge_kernel_size: list[int, int] = (2, 2),
583
+ merge_kernel_size: tuple[int, int] = (2, 2),
584
) -> list[torch.Tensor]:
585
d_model = x.size(-1)
586
vllm/model_executor/models/moss_audio.py
+47
-32
@@ -5,6 +5,7 @@
5
import math
6
from collections.abc import Iterable, Mapping, Sequence
7
from dataclasses import dataclass, field
8
+from functools import partial
9
from typing import Annotated, Any
10
11
import numpy as np
@@ -14,10 +15,11 @@ import torch.nn.functional as F
15
from torch import nn
16
from transformers import BatchFeature, PretrainedConfig, Qwen3Config
17
from transformers.models.whisper import WhisperFeatureExtractor
18
+from typing_extensions import TypedDict
19
20
from vllm.compilation.decorators import support_torch_compile
21
from vllm.config import VllmConfig
20
-from vllm.config.multimodal import BaseDummyOptions
22
+from vllm.config.multimodal import AudioDummyOptions, BaseDummyOptions
23
from vllm.distributed import (
24
get_pp_group,
25
get_tensor_model_parallel_world_size,
@@ -89,6 +91,16 @@ DEFAULT_MOSS_AUDIO_MEL_CONFIG = {
91
"mel_hop_length": 160,
92
"mel_n_fft": 400,
93
}
94
+
95
+
96
+class MossAudioProcessorKwargs(TypedDict):
97
+ audio_token_id: int
98
+ audio_start_id: int
99
+ audio_end_id: int
100
+ enable_time_marker: bool
101
+ mel_config: dict[str, int]
102
+
103
+
104
MOSS_AUDIO_PLACEHOLDER = (
105
f"{MOSS_AUDIO_BOS_TOKEN}{MOSS_AUDIO_TOKEN}{MOSS_AUDIO_EOS_TOKEN}"
106
)
@@ -170,7 +182,9 @@ def _extract_moss_audio_mel_config(
182
for target_key, source_keys in aliases.items():
183
for source_key in source_keys:
184
if source_key in mel_config:
173
- config[target_key] = int(mel_config[source_key])
185
+ value = mel_config[source_key]
186
+ assert isinstance(value, int)
187
+ config[target_key] = value
188
break
189
190
return config
@@ -963,6 +977,9 @@ class MossAudioProcessor:
977
enable_time_marker: bool = False,
978
mel_config: Mapping[str, object] | None = None,
979
) -> None:
980
+ assert hasattr(tokenizer, "encode")
981
+ assert hasattr(tokenizer, "decode")
982
+ assert hasattr(tokenizer, "batch_decode")
983
self.tokenizer = tokenizer
984
self.audio_token_id = int(audio_token_id)
985
self.audio_start_id = int(audio_start_id)
@@ -1229,15 +1246,19 @@ class MossAudioProcessingInfo(BaseProcessingInfo):
1246
1247
@staticmethod
1248
def _get_processor_cache_key(kwargs: Mapping[str, object]) -> tuple[object, ...]:
1232
- mel_config = _normalize_moss_audio_mel_config(
1233
- kwargs.get("mel_config")
1234
- if isinstance(kwargs.get("mel_config"), Mapping)
1235
- else None
1236
- )
1249
+ mel_config_arg = kwargs.get("mel_config")
1250
+ assert mel_config_arg is None or isinstance(mel_config_arg, Mapping)
1251
+ mel_config = _normalize_moss_audio_mel_config(mel_config_arg)
1252
+ audio_token_id = kwargs.get("audio_token_id", MOSS_AUDIO_TOKEN_ID)
1253
+ audio_start_id = kwargs.get("audio_start_id", MOSS_AUDIO_BOS_TOKEN_ID)
1254
+ audio_end_id = kwargs.get("audio_end_id", MOSS_AUDIO_EOS_TOKEN_ID)
1255
+ assert isinstance(audio_token_id, int)
1256
+ assert isinstance(audio_start_id, int)
1257
+ assert isinstance(audio_end_id, int)
1258
return (
1238
- int(kwargs.get("audio_token_id", MOSS_AUDIO_TOKEN_ID)),
1239
- int(kwargs.get("audio_start_id", MOSS_AUDIO_BOS_TOKEN_ID)),
1240
- int(kwargs.get("audio_end_id", MOSS_AUDIO_EOS_TOKEN_ID)),
1259
+ audio_token_id,
1260
+ audio_start_id,
1261
+ audio_end_id,
1262
bool(kwargs.get("enable_time_marker", False)),
1263
tuple(sorted(mel_config.items())),
1264
)
@@ -1248,21 +1269,19 @@ class MossAudioProcessingInfo(BaseProcessingInfo):
1269
self.ctx.get_merged_mm_kwargs({}),
1270
kwargs,
1271
)
1251
- mel_config = _normalize_moss_audio_mel_config(
1252
- merged_kwargs.get("mel_config")
1253
- if isinstance(merged_kwargs.get("mel_config"), Mapping)
1254
- else None
1255
- )
1256
- processor_kwargs = {
1257
- "audio_token_id": int(
1258
- merged_kwargs.get("audio_token_id", MOSS_AUDIO_TOKEN_ID)
1259
- ),
1260
- "audio_start_id": int(
1261
- merged_kwargs.get("audio_start_id", MOSS_AUDIO_BOS_TOKEN_ID)
1262
- ),
1263
- "audio_end_id": int(
1264
- merged_kwargs.get("audio_end_id", MOSS_AUDIO_EOS_TOKEN_ID)
1265
- ),
1272
+ mel_config_arg = merged_kwargs.get("mel_config")
1273
+ assert mel_config_arg is None or isinstance(mel_config_arg, Mapping)
1274
+ mel_config = _normalize_moss_audio_mel_config(mel_config_arg)
1275
+ audio_token_id = merged_kwargs.get("audio_token_id", MOSS_AUDIO_TOKEN_ID)
1276
+ audio_start_id = merged_kwargs.get("audio_start_id", MOSS_AUDIO_BOS_TOKEN_ID)
1277
+ audio_end_id = merged_kwargs.get("audio_end_id", MOSS_AUDIO_EOS_TOKEN_ID)
1278
+ assert isinstance(audio_token_id, int)
1279
+ assert isinstance(audio_start_id, int)
1280
+ assert isinstance(audio_end_id, int)
1281
+ processor_kwargs: MossAudioProcessorKwargs = {
1282
+ "audio_token_id": audio_token_id,
1283
+ "audio_start_id": audio_start_id,
1284
+ "audio_end_id": audio_end_id,
1285
"enable_time_marker": bool(merged_kwargs.get("enable_time_marker", False)),
1286
"mel_config": mel_config,
1287
}
@@ -1326,6 +1345,7 @@ class MossAudioDummyInputsBuilder(BaseDummyInputsBuilder[MossAudioProcessingInfo
1345
) -> MultiModalDataDict:
1346
num_audios = mm_counts.get("audio", 0)
1347
audio_overrides = mm_options.get("audio")
1348
+ assert audio_overrides is None or isinstance(audio_overrides, AudioDummyOptions)
1349
return {
1350
"audio": self._get_dummy_audios(
1351
length=16000,
@@ -1423,7 +1443,7 @@ class MossAudioMultiModalProcessor(BaseMultiModalProcessor[MossAudioProcessingIn
1443
),
1444
)
1445
1426
- prompt_update_specs = [
1446
+ prompt_update_specs: list[tuple[list[int], list[int]]] = [
1447
(
1448
[
1449
processor.audio_start_id,
@@ -1452,12 +1472,7 @@ class MossAudioMultiModalProcessor(BaseMultiModalProcessor[MossAudioProcessingIn
1472
PromptReplacement(
1473
modality="audio",
1474
target=target,
1455
- replacement=(
1456
- lambda item_idx, suffix_token_ids=suffix_token_ids: get_replacement(
1457
- item_idx,
1458
- suffix_token_ids,
1459
- )
1460
- ),
1475
+ replacement=partial(get_replacement, suffix_token_ids=suffix_token_ids),
1476
)
1477
for target, suffix_token_ids in prompt_update_specs
1478
]
vllm/model_executor/models/moss_transcribe_diarize.py
+6
-3
@@ -20,7 +20,7 @@ from torch import nn
20
from transformers import BatchFeature
21
22
from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
23
-from vllm.config.multimodal import BaseDummyOptions
23
+from vllm.config.multimodal import AudioDummyOptions, BaseDummyOptions
24
from vllm.config.speech_to_text import SpeechToTextParams
25
from vllm.inputs import ModalityData, MultiModalDataDict, PromptType, TextPrompt
26
from vllm.model_executor.models.interfaces import (
@@ -299,8 +299,9 @@ class MossTranscribeDiarizeWhisperEncoder(WhisperEncoder):
299
def forward(
300
self,
301
input_features: torch.Tensor,
302
- audio_feature_lengths: torch.Tensor,
302
+ audio_feature_lengths: torch.Tensor | None = None,
303
) -> torch.Tensor:
304
+ assert audio_feature_lengths is not None
305
if input_features.numel() == 0:
306
return input_features.new_empty((1, 0, self.conv1.out_channels))
307
device = self.conv1.weight.device
@@ -431,11 +432,13 @@ class MossTranscribeDiarizeDummyInputsBuilder(
432
return {}
433
434
feature_extractor = self.info.get_feature_extractor()
435
+ audio_overrides = mm_options.get("audio")
436
+ assert audio_overrides is None or isinstance(audio_overrides, AudioDummyOptions)
437
return {
438
"audio": self._get_dummy_audios(
439
length=_get_max_audio_samples(feature_extractor),
440
num_audios=num_audios,
438
- overrides=mm_options.get("audio"),
441
+ overrides=audio_overrides,
442
)
443
}
444
vllm/model_executor/models/muse_glimmer.py
+21
-7
@@ -45,7 +45,11 @@ from transformers import BatchFeature
45
46
from vllm.compilation.decorators import support_torch_compile
47
from vllm.config import CacheConfig, VllmConfig
48
-from vllm.config.multimodal import BaseDummyOptions
48
+from vllm.config.multimodal import (
49
+ BaseDummyOptions,
50
+ ImageDummyOptions,
51
+ VideoDummyOptions,
52
+)
53
from vllm.distributed import (
54
divide,
55
get_pp_group,
@@ -241,12 +245,16 @@ class MuseGlimmerDummyInputsBuilder(BaseDummyInputsBuilder[MuseGlimmerProcessing
245
* int(video_processor.downsample_factor)
246
* video_grid
247
)
248
+ image_overrides = mm_options.get("image")
249
+ video_overrides = mm_options.get("video")
250
+ assert image_overrides is None or isinstance(image_overrides, ImageDummyOptions)
251
+ assert video_overrides is None or isinstance(video_overrides, VideoDummyOptions)
252
return {
253
"image": self._get_dummy_images(
254
width=image_width,
255
height=image_height,
256
num_images=mm_counts.get("image", 0),
249
- overrides=mm_options.get("image"),
257
+ overrides=image_overrides,
258
),
259
"video": self._get_dummy_videos(
260
width=video_size,
@@ -255,7 +263,7 @@ class MuseGlimmerDummyInputsBuilder(BaseDummyInputsBuilder[MuseGlimmerProcessing
263
seq_len, mm_counts
264
),
265
num_videos=mm_counts.get("video", 0),
258
- overrides=mm_options.get("video"),
266
+ overrides=video_overrides,
267
),
268
}
269
@@ -370,7 +378,7 @@ class MuseGlimmerMultiModalProcessor(
378
hf_inputs: BatchFeature,
379
hf_processor_mm_kwargs: Mapping[str, object],
380
) -> Mapping[str, MultiModalFieldConfig]:
373
- fields = {}
381
+ fields: dict[str, MultiModalFieldConfig] = {}
382
if "image_pixel_values" in hf_inputs:
383
fields.update(
384
image_pixel_values=MultiModalFieldConfig.batched("image"),
@@ -410,7 +418,9 @@ class MuseGlimmerMultiModalProcessor(
418
419
def image_replacement(item_idx: int) -> PromptUpdateDetails:
420
out_item = out_mm_kwargs["image"][item_idx]
413
- num_tokens = int(out_item["image_feature_sizes"].data)
421
+ feature_size = out_item["image_feature_sizes"].data
422
+ assert isinstance(feature_size, torch.Tensor)
423
+ num_tokens = int(feature_size.item())
424
replacement = (
425
[image_start_id] + [config.image_token_id] * num_tokens + [image_end_id]
426
)
@@ -427,7 +437,9 @@ class MuseGlimmerMultiModalProcessor(
437
"MuseGlimmer video must contain at least one frame group"
438
)
439
430
- num_tokens = int(out_item["video_feature_sizes"].data)
440
+ feature_size = out_item["video_feature_sizes"].data
441
+ assert isinstance(feature_size, torch.Tensor)
442
+ num_tokens = int(feature_size.item())
443
tokens_per_group, remainder = divmod(num_tokens, num_groups)
444
if remainder:
445
raise ValueError(
@@ -1477,6 +1489,8 @@ class MuseGlimmerForCausalLM(
1489
prefix=maybe_prefix(prefix, "model"),
1490
)
1491
1492
+ self.vision_encoder: MuseGlimmerVisionEncoder | None
1493
+ self.vision_adapter: MuseGlimmerVisionAdapter | None
1494
if self.has_vision:
1495
with self._mark_tower_model(vllm_config, {"image", "video"}):
1496
self.vision_encoder = MuseGlimmerVisionEncoder(
@@ -1616,7 +1630,7 @@ class MuseGlimmerForCausalLM(
1630
image_input = self._parse_and_validate_image_input(**kwargs)
1631
video_input = self._parse_and_validate_video_input(**kwargs)
1632
1619
- embeddings: MultiModalEmbeddings = []
1633
+ embeddings: list[torch.Tensor] = []
1634
for key in kwargs:
1635
if key == "image_pixel_values" and image_input is not None:
1636
embeddings.extend(self._process_image_input(image_input))