[Mypy] Fix mypy typing for Qwen and Qianfan models (#58046)

Signed-off-by: Ashraf Bhuiyan <mbhuiyan@redhat.com>

Ashraf Bhuiyan committed Sep 26, 2026 at 10:31 UTC 5113cf9d168cf71b1573c69500f884b0bccbad89
23 files changed +302 -157
tools/pre_commit/mypy.py
-1
@@ -113,7 +113,6 @@ SEPARATE_GROUPS = [
113
114 EXCLUDE = [
115 r"vllm/model_executor/models/[kK]",
116 - r"vllm/model_executor/models/[qQ]",
116 r"vllm/model_executor/models/[uU]",
117 r"vllm/model_executor/models/[vV]",
118 r"vllm/model_executor/models/[wW]",
vllm/model_executor/models/interfaces.py
+1 -1
@@ -1262,7 +1262,7 @@ class SupportsRealtime(Protocol):
1262 Override in subclasses based on the model's expected output length."""
1263
1264 @classmethod
1265 - async def buffer_realtime_audio(
1265 + def buffer_realtime_audio(
1266 cls,
1267 audio_stream: AsyncGenerator[np.ndarray, None],
1268 input_stream: asyncio.Queue[list[int]],
vllm/model_executor/models/qianfan_ocr.py
+8 -8
@@ -36,14 +36,14 @@ class QianfanOCRProcessingInfo(BaseInternVLProcessingInfo):
36 config = self.get_hf_config()
37 vision_config = config.vision_config
38
39 - kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
40 - kwargs.setdefault("image_size", vision_config.image_size)
41 - kwargs.setdefault("min_dynamic_patch", config.min_dynamic_patch)
42 - kwargs.setdefault("max_dynamic_patch", config.max_dynamic_patch)
43 - kwargs.setdefault("dynamic_image_size", config.dynamic_image_size)
44 - kwargs.setdefault("use_thumbnail", config.use_thumbnail)
39 + merged_kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
40 + merged_kwargs.setdefault("image_size", vision_config.image_size)
41 + merged_kwargs.setdefault("min_dynamic_patch", config.min_dynamic_patch)
42 + merged_kwargs.setdefault("max_dynamic_patch", config.max_dynamic_patch)
43 + merged_kwargs.setdefault("dynamic_image_size", config.dynamic_image_size)
44 + merged_kwargs.setdefault("use_thumbnail", config.use_thumbnail)
45
46 - image_processor = InternVLImageProcessor(**kwargs)
46 + image_processor = InternVLImageProcessor(**merged_kwargs)
47 image_size = image_processor.image_size
48 patch_size = vision_config.patch_size
49 downsample_ratio = config.downsample_ratio
@@ -73,7 +73,7 @@ class QianfanOCRForConditionalGeneration(InternVLChatModel):
73 """
74
75 def _patch_quant_config(
76 - self, config: PreTrainedConfig, quant_config: QuantizationConfig
76 + self, config: PreTrainedConfig, quant_config: QuantizationConfig | None
77 ) -> None:
78 super()._patch_quant_config(config, quant_config)
79 # ignore vit layers to preserve model performance
vllm/model_executor/models/qwen2.py
+7 -6
@@ -187,6 +187,12 @@ class Qwen2Attention(nn.Module):
187 if attn_type == AttentionType.ENCODER_ONLY
188 else Attention
189 )
190 + attention_kwargs: dict[str, Any] = {}
191 + if dual_chunk_attention_config:
192 + attention_kwargs = {
193 + "layer_idx": extract_layer_index(prefix),
194 + "dual_chunk_attention_config": dual_chunk_attention_config,
195 + }
196 self.attn = attn_cls(
197 self.num_heads,
198 self.head_dim,
@@ -196,12 +202,7 @@ class Qwen2Attention(nn.Module):
202 quant_config=quant_config,
203 attn_type=attn_type,
204 prefix=f"{prefix}.attn",
199 - **{
200 - "layer_idx": extract_layer_index(prefix),
201 - "dual_chunk_attention_config": dual_chunk_attention_config,
202 - }
203 - if dual_chunk_attention_config
204 - else {},
205 + **attention_kwargs,
206 )
207
208 def forward(
vllm/model_executor/models/qwen2_5_omni_thinker.py
+58 -24
@@ -42,6 +42,7 @@ from transformers.models.qwen2_5_omni.processing_qwen2_5_omni import (
42 Qwen2_5OmniProcessor,
43 )
44 from transformers.models.whisper import WhisperFeatureExtractor
45 +from typing_extensions import TypeVar
46
47 from vllm.config import VllmConfig
48 from vllm.config.multimodal import MultiModalDummyOptions
@@ -63,7 +64,10 @@ from vllm.model_executor.models.qwen2_audio import (
64 Qwen2AudioProcessingInfo,
65 _get_feat_extract_output_lengths,
66 )
66 -from vllm.model_executor.models.qwen2_vl import Qwen2VLMultiModalDataParser
67 +from vllm.model_executor.models.qwen2_vl import (
68 + Qwen2VLMultiModalDataParser,
69 + Qwen2VLProcessingInfo,
70 +)
71 from vllm.multimodal import MULTIMODAL_REGISTRY
72 from vllm.multimodal.inputs import (
73 ImageItem,
@@ -79,6 +83,7 @@ from vllm.multimodal.parse import (
83 )
84 from vllm.multimodal.processing import (
85 BaseDummyInputsBuilder,
86 + BaseProcessingInfo,
87 )
88 from vllm.multimodal.processing.processor import (
89 BaseMultiModalProcessor,
@@ -118,6 +123,14 @@ except (ImportError, ModuleNotFoundError):
123 logger = init_logger(__name__)
124
125
126 +def _get_feature_tensor(feature: MultiModalFeatureSpec, key: str) -> torch.Tensor:
127 + data = feature.data
128 + assert data is not None
129 + tensor = data[key].data
130 + assert isinstance(tensor, torch.Tensor)
131 + return tensor
132 +
133 +
134 def unpad_and_flat_audio_features(
135 input_audio_features: torch.Tensor,
136 audio_feature_lengths: torch.Tensor,
@@ -347,7 +360,7 @@ class Qwen2_5OmniThinkerMultiModalDataParser(Qwen2VLMultiModalDataParser):
360 def _parse_audio_data(
361 self,
362 data: dict[str, torch.Tensor] | ModalityData[ImageItem],
350 - ) -> ModalityDataItems[Any, Any]:
363 + ) -> ModalityDataItems[Any, Any] | None:
364 if isinstance(data, dict):
365 required, optional = self.embedding_field_sets("audio")
366 data = dict(data)
@@ -406,7 +419,7 @@ class Qwen2_5OmniThinkerProcessingInfo(
419 self,
420 seq_len: int,
421 mm_counts: Mapping[str, int] | None = None,
409 - ) -> Mapping[str, int] | None:
422 + ) -> Mapping[str, int]:
423 mm_counts = mm_counts or {}
424 requested_modalities = {m for m, c in mm_counts.items() if c > 0}
425 mm_max_tokens: dict[str, int] = {}
@@ -436,9 +449,13 @@ class Qwen2_5OmniThinkerProcessingInfo(
449 return mm_max_tokens
450
451
439 -class Qwen2_5OmniThinkerDummyInputsBuilder(
440 - BaseDummyInputsBuilder[Qwen2_5OmniThinkerProcessingInfo]
441 -):
452 +_I = TypeVar("_I", bound=BaseProcessingInfo, default=Qwen2_5OmniThinkerProcessingInfo)
453 +_VI = TypeVar(
454 + "_VI", bound=Qwen2VLProcessingInfo, default=Qwen2_5OmniThinkerProcessingInfo
455 +)
456 +
457 +
458 +class Qwen2_5OmniThinkerDummyInputsBuilder(BaseDummyInputsBuilder[_VI]):
459 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
460 num_audios = mm_counts.get("audio", 0)
461 num_images = mm_counts.get("image", 0)
@@ -462,7 +479,7 @@ class Qwen2_5OmniThinkerDummyInputsBuilder(
479 mm_counts: Mapping[str, int],
480 mm_options: MultiModalDummyOptions,
481 ) -> MultiModalDataDict:
465 - feature_extractor = self.info.get_feature_extractor()
482 + feature_extractor = self.info.get_hf_processor().feature_extractor
483
484 target_audio_length = (
485 min(
@@ -501,9 +518,7 @@ class Qwen2_5OmniThinkerDummyInputsBuilder(
518 return mm_data
519
520
504 -class Qwen2_5OmniThinkerMultiModalProcessor(
505 - BaseMultiModalProcessor[Qwen2_5OmniThinkerProcessingInfo]
506 -):
521 +class Qwen2_5OmniThinkerMultiModalProcessor(BaseMultiModalProcessor[_I]):
522 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
523 return self.dummy_inputs.get_dummy_text(mm_counts)
524
@@ -651,6 +666,7 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
666 for item in mm_kwargs["video"]:
667 if item and item.get("use_audio_in_video"):
668 use_audio_in_video_tensor = item["use_audio_in_video"].data
669 + assert isinstance(use_audio_in_video_tensor, torch.Tensor)
670 if use_audio_in_video_tensor.numel() > 0:
671 use_audio_in_video = bool(use_audio_in_video_tensor.item())
672 break
@@ -749,6 +765,7 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
765 ) -> Sequence[PromptUpdate]:
766 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
767 tokenizer = self.info.get_tokenizer()
768 + assert isinstance(self.info, Qwen2VLProcessingInfo)
769 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
770 vocab = tokenizer.get_vocab()
771
@@ -786,6 +803,7 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
803 if num_features == 0:
804 audios = mm_items.get_items("audio", AudioProcessorItems)
805 audio = audios.get(item_idx)
806 + assert audio is not None
807 raise ValueError(
808 f"The audio {audio} (len={len(audio)}) is too short "
809 "to be represented inside the model"
@@ -887,6 +905,10 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
905
906
907 class Qwen2_5OmniConditionalGenerationMixin:
908 + audio_tower: nn.Module
909 + visual: nn.Module
910 + vllm_config: VllmConfig
911 +
912 def _parse_and_validate_audio_input(
913 self, **kwargs: object
914 ) -> Qwen2_5OmniAudioFeatureInputs | None:
@@ -916,7 +938,7 @@ class Qwen2_5OmniConditionalGenerationMixin:
938
939 def _parse_and_validate_image_input(
940 self,
919 - **kwargs: dict[str, Any],
941 + **kwargs: object,
942 ) -> Qwen2_5_VLImageInputs | None:
943 pixel_values = kwargs.pop("pixel_values", None)
944 image_embeds = kwargs.pop("image_embeds", None)
@@ -932,7 +954,7 @@ class Qwen2_5OmniConditionalGenerationMixin:
954 image_grid_thw=image_grid_thw,
955 )
956
935 - if image_embeds is not None:
957 + else:
958 return Qwen2_5_VLImageEmbeddingInputs(
959 type="image_embeds",
960 image_embeds=image_embeds,
@@ -941,7 +963,7 @@ class Qwen2_5OmniConditionalGenerationMixin:
963
964 def _parse_and_validate_video_input(
965 self,
944 - **kwargs: dict[str, Any],
966 + **kwargs: object,
967 ) -> Qwen2_5_VLVideoInputs | None:
968 pixel_values_videos = kwargs.pop("pixel_values_videos", None)
969 video_embeds = kwargs.pop("video_embeds", None)
@@ -957,7 +979,7 @@ class Qwen2_5OmniConditionalGenerationMixin:
979 video_grid_thw=video_grid_thw,
980 )
981
960 - if video_embeds is not None:
982 + else:
983 if not isinstance(video_embeds, torch.Tensor):
984 raise ValueError(
985 "Incorrect type of video embeddings. "
@@ -1130,7 +1152,13 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1152 )
1153
1154 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1133 - mm_input_by_modality = {}
1155 + mm_input_by_modality: dict[
1156 + str,
1157 + Qwen2_5_VLImageInputs
1158 + | Qwen2_5_VLVideoInputs
1159 + | Qwen2_5OmniAudioFeatureInputs
1160 + | None,
1161 + ] = {}
1162
1163 # Preserve the order of modalities if there are multiple of them
1164 # from the order of kwargs.
@@ -1175,8 +1203,9 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1203 f
1204 for f in mm_features
1205 if f.modality == "video"
1206 + and f.data is not None
1207 and f.data.get("use_audio_in_video")
1179 - and f.data["use_audio_in_video"].data.item()
1208 + and _get_feature_tensor(f, "use_audio_in_video").item()
1209 ]
1210 audios = [f for f in mm_features if f.modality == "audio"]
1211
@@ -1185,7 +1214,9 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1214 paired_audio_offsets: set[int] = set()
1215 for i, video_f in enumerate(videos_with_audio):
1216 if i < len(audios):
1188 - audio_len = audios[i].data["audio_feature_lengths"].data.item()
1217 + audio_len = int(
1218 + _get_feature_tensor(audios[i], "audio_feature_lengths").item()
1219 + )
1220 mapping[video_f.mm_position.offset] = audio_len
1221 paired_audio_offsets.add(audios[i].mm_position.offset)
1222 return mapping, paired_audio_offsets
@@ -1220,9 +1251,10 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1251 for mm_feature in sorted_features:
1252 offset = mm_feature.mm_position.offset
1253 modality = mm_feature.modality
1254 + assert mm_feature.data is not None
1255
1256 if modality == "image":
1225 - t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
1257 + t, h, w = _get_feature_tensor(mm_feature, "image_grid_thw").tolist()
1258 yield (
1259 offset,
1260 "image",
@@ -1234,16 +1266,16 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1266 },
1267 )
1268 elif modality == "video":
1237 - t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
1269 + t, h, w = _get_feature_tensor(mm_feature, "video_grid_thw").tolist()
1270 second_per_grid_ts = 1.0
1271 if mm_feature.data.get("second_per_grid_ts"):
1240 - second_per_grid_ts = mm_feature.data[
1241 - "second_per_grid_ts"
1242 - ].data.item()
1272 + second_per_grid_ts = _get_feature_tensor(
1273 + mm_feature, "second_per_grid_ts"
1274 + ).item()
1275 use_audio_in_video = False
1276 if mm_feature.data.get("use_audio_in_video"):
1277 use_audio_in_video = bool(
1246 - mm_feature.data["use_audio_in_video"].data.item()
1278 + _get_feature_tensor(mm_feature, "use_audio_in_video").item()
1279 )
1280
1281 yield (
@@ -1261,7 +1293,9 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
1293 elif modality == "audio":
1294 # Skip audio that's paired with video (handled in video case)
1295 if offset not in paired_audio_offsets:
1264 - audio_len = mm_feature.data["audio_feature_lengths"].data.item()
1296 + audio_len = _get_feature_tensor(
1297 + mm_feature, "audio_feature_lengths"
1298 + ).item()
1299 yield offset, "audio", {"audio_feature_length": audio_len}
1300
1301 def _compute_interleaved_positions(
vllm/model_executor/models/qwen2_5_vl.py
+37 -24
@@ -654,7 +654,10 @@ class Qwen2_5_VisionTransformer(nn.Module):
654 self.spatial_merge_size = vision_config.spatial_merge_size
655 self.fullatt_block_indexes = vision_config.fullatt_block_indexes
656 self.spatial_merge_unit = self.spatial_merge_size**2
657 - self._rope_by_thw_cache = LRUCache(capacity=1024)
657 + self._rope_by_thw_cache: LRUCache[
658 + tuple[int, int, int],
659 + tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor],
660 + ] = LRUCache(capacity=1024)
661 use_data_parallel = is_vit_use_data_parallel()
662 self.tp_size = (
663 1
@@ -885,7 +888,7 @@ class Qwen2_5_VisionTransformer(nn.Module):
888
889 def prepare_encoder_metadata(
890 self,
888 - grid_thw: list[list[int]],
891 + grid_thw: Sequence[Sequence[int]],
892 *,
893 max_batch_size: int | None = None,
894 max_frames_per_batch: int | None = None,
@@ -927,9 +930,9 @@ class Qwen2_5_VisionTransformer(nn.Module):
930 # patchify
931 rotary_pos_emb_cos = []
932 rotary_pos_emb_sin = []
930 - window_index: list = []
931 - cu_window_seqlens: list = [torch.tensor([0], dtype=torch.int32)]
932 - cu_seqlens: list = []
933 + window_indices: list[torch.Tensor] = []
934 + cu_window_seqlens_parts = [torch.tensor([0], dtype=torch.int32)]
935 + cu_seqlens_parts: list[torch.Tensor] = []
936
937 window_index_id = 0
938 cu_window_seqlens_last = 0
@@ -946,26 +949,26 @@ class Qwen2_5_VisionTransformer(nn.Module):
949 cu_seqlens_thw,
950 ) = self.get_rope_by_thw(t, h, w)
951
949 - window_index.append(window_index_thw + window_index_id)
952 + window_indices.append(window_index_thw + window_index_id)
953 window_index_id += t * llm_h * llm_w
954
955 cu_seqlens_window_thw = cu_seqlens_window_thw + cu_window_seqlens_last
956 cu_window_seqlens_last = cu_seqlens_window_thw[-1]
954 - cu_window_seqlens.append(cu_seqlens_window_thw)
957 + cu_window_seqlens_parts.append(cu_seqlens_window_thw)
958
959 rotary_pos_emb_cos.append(cos_thw)
960 rotary_pos_emb_sin.append(sin_thw)
961
959 - cu_seqlens.append(cu_seqlens_thw)
962 + cu_seqlens_parts.append(cu_seqlens_thw)
963
964 rotary_pos_emb_cos = torch.cat(rotary_pos_emb_cos)
965 rotary_pos_emb_sin = torch.cat(rotary_pos_emb_sin)
963 - window_index = torch.cat(window_index)
966 + window_index = torch.cat(window_indices)
967 # compute reverse indices
968 reverse_indices = self.invert_permutation(window_index)
966 - cu_window_seqlens = torch.cat(cu_window_seqlens)
969 + cu_window_seqlens = torch.cat(cu_window_seqlens_parts)
970 cu_window_seqlens = torch.unique_consecutive(cu_window_seqlens)
968 - cu_seqlens = torch.cat(cu_seqlens)
971 + cu_seqlens = torch.cat(cu_seqlens_parts)
972 cu_seqlens = torch.cumsum(cu_seqlens, dim=0, dtype=torch.int32)
973 cu_seqlens = F.pad(cu_seqlens, (1, 0), "constant", 0)
974
@@ -1088,6 +1091,7 @@ class Qwen2_5_VisionTransformer(nn.Module):
1091
1092 seq_len = hidden_states.shape[0]
1093 if encoder_metadata is None:
1094 + assert grid_thw is not None
1095 encoder_metadata = self.prepare_encoder_metadata(grid_thw)
1096
1097 rotary_pos_emb_cos = encoder_metadata["rotary_pos_emb_cos"]
@@ -1162,7 +1166,7 @@ class Qwen2_5_VLDummyInputsBuilder(Qwen2VLDummyInputsBuilder):
1166 )
1167 videos = [v.copy() for v in videos]
1168
1165 - video_items = []
1169 + video_items: list[VideoItem] = []
1170 for video in videos:
1171 video_num_frames = video.shape[0]
1172 video_metadata = {
@@ -1216,6 +1220,7 @@ class Qwen2_5_VLMultiModalProcessor(Qwen2VLMultiModalProcessor):
1220 hf_data = hf_inputs.hf_data
1221
1222 if videos := hf_data.get("videos"):
1223 + assert isinstance(videos, list)
1224 # HF expects video metadata as a separate argument.
1225 hf_data["videos"] = [video for video, _ in videos]
1226 hf_data["video_metadata"] = [
@@ -1336,17 +1341,22 @@ class Qwen2_5_VLForConditionalGeneration(
1341 tokens_per_second = getattr(self.config.vision_config, "tokens_per_second", 1.0)
1342 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1343 offset = mm_feature.mm_position.offset
1344 + data = mm_feature.data
1345 + assert data is not None
1346 if mm_feature.modality == "image":
1340 - t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
1347 + grid = data["image_grid_thw"].data
1348 + assert isinstance(grid, torch.Tensor)
1349 + t, h, w = grid.tolist()
1350 assert t == 1, f"Image must have 1 frame, got {t}"
1351 yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1352 elif mm_feature.modality == "video":
1344 - t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
1353 + grid = data["video_grid_thw"].data
1354 + assert isinstance(grid, torch.Tensor)
1355 + t, h, w = grid.tolist()
1356 second_per_grid_ts = 1.0
1346 - if mm_feature.data.get("second_per_grid_ts", None):
1347 - second_per_grid_ts = mm_feature.data[
1348 - "second_per_grid_ts"
1349 - ].data.item()
1357 + if seconds := data.get("second_per_grid_ts"):
1358 + assert isinstance(seconds.data, torch.Tensor)
1359 + second_per_grid_ts = seconds.data.item()
1360 t_factor = second_per_grid_ts * tokens_per_second
1361 yield (
1362 offset,
@@ -1409,7 +1419,7 @@ class Qwen2_5_VLForConditionalGeneration(
1419 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1420 super().__init__()
1421 config: Qwen2_5_VLConfig = vllm_config.model_config.hf_config
1412 - multimodal_config = vllm_config.model_config.multimodal_config
1422 + multimodal_config = vllm_config.model_config.get_multimodal_config()
1423
1424 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1425 self.config = config
@@ -1458,7 +1468,7 @@ class Qwen2_5_VLForConditionalGeneration(
1468 image_grid_thw=image_grid_thw,
1469 )
1470
1461 - if image_embeds is not None:
1471 + else:
1472 return Qwen2_5_VLImageEmbeddingInputs(
1473 type="image_embeds",
1474 image_embeds=image_embeds,
@@ -1484,7 +1494,7 @@ class Qwen2_5_VLForConditionalGeneration(
1494 second_per_grid_ts=second_per_grid_ts,
1495 )
1496
1487 - if video_embeds is not None:
1497 + else:
1498 return Qwen2_5_VLVideoEmbeddingInputs(
1499 type="video_embeds",
1500 video_embeds=video_embeds,
@@ -1549,8 +1559,7 @@ class Qwen2_5_VLForConditionalGeneration(
1559 )
1560 emb = torch.cat([emb, positions], dim=1)
1561 image_embeds_out.append(emb)
1552 - image_embeds_split = image_embeds_out
1553 - return tuple(image_embeds_split)
1562 + return tuple(image_embeds_out)
1563
1564 def _process_video_input(
1565 self, video_input: Qwen2_5_VLVideoInputs
@@ -1614,6 +1623,7 @@ class Qwen2_5_VLForConditionalGeneration(
1623 tokens_per_second = self.config.vision_config.tokens_per_second
1624
1625 video_embeds_out = []
1626 + assert self.video_pruning_rate is not None
1627 for emb, size, video_second_per_grid_t in zip(
1628 video_embeds_split, grid_thw_list, second_per_grid_ts
1629 ):
@@ -1702,7 +1712,9 @@ class Qwen2_5_VLForConditionalGeneration(
1712 return mm_embeddings_out, positions, mrope_positions_delta
1713
1714 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1705 - mm_input_by_modality = {}
1715 + mm_input_by_modality: dict[
1716 + str, Qwen2_5_VLImageInputs | Qwen2_5_VLVideoInputs | None
1717 + ] = {}
1718
1719 # Preserve the order of modalities if there are multiple of them
1720 # from the order of kwargs.
@@ -1808,6 +1820,7 @@ class Qwen2_5_VLForConditionalGeneration(
1820 def get_max_frames_per_video(self) -> int:
1821 mm_registry = MULTIMODAL_REGISTRY
1822 info = mm_registry.get_processing_info(self.model_config)
1823 + assert isinstance(info, Qwen2VLProcessingInfo)
1824 max_frames_per_video = info.get_num_frames_with_most_features(
1825 seq_len=self.model_config.max_model_len,
1826 mm_counts={"video": self.multimodal_config.get_limit_per_prompt("video")},
vllm/model_executor/models/qwen2_audio.py
+4 -1
@@ -286,11 +286,14 @@ class Qwen2AudioMultiModalProcessor(BaseMultiModalProcessor[Qwen2AudioProcessing
286
287 def get_replacement_qwen2_audio(item_idx: int):
288 if "audio_num_tokens" in out_mm_data:
289 - num_features = int(out_mm_data["audio_num_tokens"][item_idx])
289 + audio_num_tokens = out_mm_data["audio_num_tokens"]
290 + assert isinstance(audio_num_tokens, torch.Tensor)
291 + num_features = int(audio_num_tokens[item_idx])
292 elif audio_output_lengths:
293 num_features = audio_output_lengths[item_idx]
294 else:
295 audio_embeds = out_mm_data["audio_embeds"][item_idx]
296 + assert isinstance(audio_embeds, torch.Tensor)
297 assert len(audio_embeds.shape) == 2, "audio_embeds must be a 2D tensor"
298 num_features = audio_embeds.shape[0]
299
vllm/model_executor/models/qwen2_moe.py
+8 -6
@@ -153,6 +153,7 @@ class Qwen2MoeSparseMoeBlock(nn.Module):
153 prefix=f"{prefix}.shared_expert_gate",
154 )
155
156 + self.shared_expert: Qwen2MoeMLP | None
157 if config.shared_expert_intermediate_size > 0:
158 self.shared_expert = Qwen2MoeMLP(
159 hidden_size=config.hidden_size,
@@ -252,6 +253,12 @@ class Qwen2MoeAttention(nn.Module):
253 rope_parameters=rope_parameters,
254 dual_chunk_attention_config=dual_chunk_attention_config,
255 )
256 + attention_kwargs: dict[str, Any] = {}
257 + if dual_chunk_attention_config:
258 + attention_kwargs = {
259 + "layer_idx": extract_layer_index(prefix),
260 + "dual_chunk_attention_config": dual_chunk_attention_config,
261 + }
262 self.attn = Attention(
263 self.num_heads,
264 self.head_dim,
@@ -260,12 +267,7 @@ class Qwen2MoeAttention(nn.Module):
267 cache_config=cache_config,
268 quant_config=quant_config,
269 prefix=f"{prefix}.attn",
263 - **{
264 - "layer_idx": extract_layer_index(prefix),
265 - "dual_chunk_attention_config": dual_chunk_attention_config,
266 - }
267 - if dual_chunk_attention_config
268 - else {},
270 + **attention_kwargs,
271 )
272
273 def forward(
vllm/model_executor/models/qwen2_vl.py
+17 -10
@@ -1211,17 +1211,23 @@ class Qwen2VLForConditionalGeneration(
1211 tokens_per_second = getattr(self.config.vision_config, "tokens_per_second", 1.0)
1212 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1213 offset = mm_feature.mm_position.offset
1214 + data = mm_feature.data
1215 + assert data is not None
1216 if mm_feature.modality == "image":
1215 - t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
1217 + image_grid_thw = data["image_grid_thw"]
1218 + assert isinstance(image_grid_thw.data, torch.Tensor)
1219 + t, h, w = image_grid_thw.data.tolist()
1220 assert t == 1, f"Image must have 1 frame, got {t}"
1221 yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1222 elif mm_feature.modality == "video":
1219 - t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
1223 + video_grid_thw = data["video_grid_thw"]
1224 + assert isinstance(video_grid_thw.data, torch.Tensor)
1225 + t, h, w = video_grid_thw.data.tolist()
1226 second_per_grid_ts = 1.0
1221 - if mm_feature.data.get("second_per_grid_ts", None):
1222 - second_per_grid_ts = mm_feature.data[
1223 - "second_per_grid_ts"
1224 - ].data.item()
1227 + second_per_grid_ts_field = data.get("second_per_grid_ts")
1228 + if second_per_grid_ts_field is not None:
1229 + assert isinstance(second_per_grid_ts_field.data, torch.Tensor)
1230 + second_per_grid_ts = second_per_grid_ts_field.data.item()
1231 t_factor = second_per_grid_ts * tokens_per_second
1232 yield (
1233 offset,
@@ -1285,7 +1291,7 @@ class Qwen2VLForConditionalGeneration(
1291 super().__init__()
1292 config: Qwen2VLConfig = vllm_config.model_config.hf_config
1293 quant_config = vllm_config.quant_config
1288 - multimodal_config = vllm_config.model_config.multimodal_config
1294 + multimodal_config = vllm_config.model_config.get_multimodal_config()
1295 self.model_config = vllm_config.model_config
1296 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1297 self.config = config
@@ -1328,7 +1334,7 @@ class Qwen2VLForConditionalGeneration(
1334 image_grid_thw=image_grid_thw,
1335 )
1336
1331 - if image_embeds is not None:
1337 + else:
1338 return Qwen2VLImageEmbeddingInputs(
1339 type="image_embeds",
1340 image_embeds=image_embeds,
@@ -1352,7 +1358,7 @@ class Qwen2VLForConditionalGeneration(
1358 video_grid_thw=video_grid_thw,
1359 )
1360
1355 - if video_embeds is not None:
1361 + else:
1362 return Qwen2VLVideoEmbeddingInputs(
1363 type="video_embeds",
1364 video_embeds=video_embeds,
@@ -1411,7 +1417,7 @@ class Qwen2VLForConditionalGeneration(
1417 return video_embeds.split(sizes)
1418
1419 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1414 - modalities = {}
1420 + modalities: dict[str, Qwen2VLImageInputs | Qwen2VLVideoInputs | None] = {}
1421
1422 # Preserve the order of modalities if there are multiple of them
1423 # from the order of kwargs.
@@ -1481,6 +1487,7 @@ class Qwen2VLForConditionalGeneration(
1487 def get_max_frames_per_video(self) -> int:
1488 mm_registry = MULTIMODAL_REGISTRY
1489 info = mm_registry.get_processing_info(self.model_config)
1490 + assert isinstance(info, Qwen2VLProcessingInfo)
1491 max_frames_per_video = info.get_num_frames_with_most_features(
1492 seq_len=self.model_config.max_model_len,
1493 mm_counts={"video": self.multimodal_config.get_limit_per_prompt("video")},
vllm/model_executor/models/qwen3.py
+7 -6
@@ -130,6 +130,12 @@ class Qwen3Attention(nn.Module):
130 if attn_type == AttentionType.ENCODER_ONLY
131 else Attention
132 )
133 + attention_kwargs: dict[str, Any] = {}
134 + if dual_chunk_attention_config:
135 + attention_kwargs = {
136 + "layer_idx": extract_layer_index(prefix),
137 + "dual_chunk_attention_config": dual_chunk_attention_config,
138 + }
139 self.attn = attn_cls(
140 self.num_heads,
141 self.head_dim,
@@ -140,12 +146,7 @@ class Qwen3Attention(nn.Module):
146 per_layer_sliding_window=per_layer_sliding_window,
147 prefix=f"{prefix}.attn",
148 attn_type=attn_type,
143 - **{
144 - "layer_idx": extract_layer_index(prefix),
145 - "dual_chunk_attention_config": dual_chunk_attention_config,
146 - }
147 - if dual_chunk_attention_config
148 - else {},
149 + **attention_kwargs,
150 )
151 self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
152 self.k_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
vllm/model_executor/models/qwen3_5.py
+9 -4
@@ -25,6 +25,7 @@
25 """Inference-only Qwen3.5 Series compatible with HuggingFace weights."""
26
27 from collections.abc import Iterable
28 +from typing import ClassVar
29
30 import torch
31 from torch import nn
@@ -54,6 +55,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
55 VocabParallelEmbedding,
56 )
57 from vllm.multimodal import MULTIMODAL_REGISTRY
58 +from vllm.multimodal.inputs import MultiModalFeatureSpec
59 from vllm.sequence import IntermediateTensors
60 from vllm.tokenizers.registry import cached_tokenizer_from_config
61 from vllm.transformers_utils.configs.qwen3_5 import Qwen3_5Config, Qwen3_5TextConfig
@@ -442,7 +444,7 @@ class Qwen3_5ForCausalLMBase(
444 def get_mrope_input_positions(
445 self,
446 input_tokens: list[int],
445 - mm_features: list[object],
447 + mm_features: list[MultiModalFeatureSpec],
448 ) -> tuple[torch.Tensor, int]:
449 positions = torch.arange(len(input_tokens), dtype=torch.long)
450 return positions.unsqueeze(0).expand(3, -1), 0
@@ -488,7 +490,7 @@ class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid)
490 nn.Module.__init__(self)
491 config: Qwen3_5Config = vllm_config.model_config.hf_config
492 quant_config = vllm_config.quant_config
491 - multimodal_config = vllm_config.model_config.multimodal_config
493 + multimodal_config = vllm_config.model_config.get_multimodal_config()
494
495 self.config = config
496 self.model_config = vllm_config.model_config
@@ -648,6 +650,9 @@ class Qwen3_5ForConditionalGeneration(Qwen3VLForConditionalGeneration, IsHybrid)
650
651
652 class Qwen3_5_MoeMixtureOfExperts(MixtureOfExperts):
653 + language_model: Qwen3_5ForCausalLM
654 + num_local_physical_experts: int
655 +
656 def update_physical_experts_metadata(
657 self,
658 num_physical_experts: int,
@@ -700,14 +705,14 @@ class Qwen3_5MoeForConditionalGeneration(
705 Qwen3_5ForConditionalGeneration, Qwen3_5_MoeMixtureOfExperts
706 ):
707 # For MoE LoRA weights loading
703 - is_3d_moe_weight: bool = True
708 + is_3d_moe_weight: ClassVar[bool] = True
709
710 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "model"):
711 # protocols have not __init__ method, so we need to use nn.Module.__init__
712 nn.Module.__init__(self)
713 config: Qwen3_5MoeConfig = vllm_config.model_config.hf_config
714 quant_config = vllm_config.quant_config
710 - multimodal_config = vllm_config.model_config.multimodal_config
715 + multimodal_config = vllm_config.model_config.get_multimodal_config()
716
717 self.config = config
718 self.model_config = vllm_config.model_config
vllm/model_executor/models/qwen3_5_mtp.py
+1 -1
@@ -286,7 +286,7 @@ class Qwen3_5MTP(LocalArgmaxMixin, nn.Module, SupportsMultiModal, SupportsPP):
286
287 return inputs_embeds
288
289 - def forward(
289 + def forward( # type: ignore[override]
290 self,
291 input_ids: torch.Tensor,
292 positions: torch.Tensor,
vllm/model_executor/models/qwen3_asr.py
+2 -1
@@ -274,7 +274,7 @@ class Qwen3ASRMultiModalDataParser(MultiModalDataParser):
274
275
276 class Qwen3ASRMultiModalProcessor(
277 - Qwen3OmniMoeThinkerMultiModalProcessor,
277 + Qwen3OmniMoeThinkerMultiModalProcessor[Qwen3ASRProcessingInfo]
278 ):
279 def _get_mm_fields_config(
280 self,
@@ -316,6 +316,7 @@ class Qwen3ASRMultiModalProcessor(
316 if num_features == 0:
317 audios = mm_items.get_items("audio", AudioProcessorItems)
318 audio = audios.get(item_idx)
319 + assert audio is not None
320 raise ValueError(
321 f"The audio {audio} (len={len(audio)}) is too short "
322 "to be represented inside the model"
vllm/model_executor/models/qwen3_asr_forced_aligner.py
+3 -2
@@ -72,8 +72,9 @@ class Qwen3ASRForcedAlignerForTokenClassification(
72
73 # Remove the unused generation head created by the base class;
74 # the forced aligner uses a classifier head instead.
75 - self.language_model.lm_head = None
76 - self.language_model.logits_processor = None
75 + language_model: nn.Module = self.language_model
76 + language_model.lm_head = None
77 + language_model.logits_processor = None
78
79 self.classify_num = thinker_config.classify_num
80
vllm/model_executor/models/qwen3_asr_realtime.py
+1
@@ -120,6 +120,7 @@ class Qwen3ASRRealtimeMultiModalProcessor(Qwen3ASRMultiModalProcessor):
120 )
121
122 audio_data = audios[0]
123 + assert audio_data is not None
124 audio_feature_lengths = audio_data.get("audio_feature_lengths")
125 if audio_feature_lengths is not None:
126 if isinstance(audio_feature_lengths.data, torch.Tensor):
vllm/model_executor/models/qwen3_dflash.py
+12 -5
@@ -92,7 +92,9 @@ def _dflash_layer_causal(config: Qwen3Config, layer_idx: int) -> bool:
92 if override is not None:
93 return bool(override)
94 layer_types = getattr(config, "layer_types", None)
95 - return bool(layer_types) and layer_types[layer_idx] == _SLIDING_ATTENTION
95 + if not layer_types:
96 + return False
97 + return layer_types[layer_idx] == _SLIDING_ATTENTION
98
99
100 def dflash_has_any_non_causal(config: Qwen3Config) -> bool:
@@ -105,6 +107,7 @@ def dflash_has_any_non_causal(config: Qwen3Config) -> bool:
107
108 def _get_dflash_fc_input_size(vllm_config: VllmConfig) -> int:
109 spec_config = vllm_config.speculative_config
110 + assert spec_config is not None
111 config = spec_config.draft_model_config.hf_config
112 aux_layers = get_eagle3_aux_layers_from_config(spec_config)
113 num_features_to_use = len(aux_layers) if aux_layers else config.num_hidden_layers
@@ -418,7 +421,9 @@ class DFlashQwen3Model(nn.Module):
421 prefix: str = "",
422 ) -> None:
423 super().__init__()
421 - self.config = vllm_config.speculative_config.draft_model_config.hf_config
424 + speculative_config = vllm_config.speculative_config
425 + assert speculative_config is not None
426 + self.config = speculative_config.draft_model_config.hf_config
427 self.vocab_size = self.config.vocab_size
428 self.quant_config = get_draft_quant_config(vllm_config)
429 _add_global_draft_layer_exclusions(
@@ -759,7 +764,9 @@ class DFlashQwen3ForCausalLM(Qwen3ForCausalLM):
764
765 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
766 nn.Module.__init__(self)
762 - self.draft_model_config = vllm_config.speculative_config.draft_model_config
767 + speculative_config = vllm_config.speculative_config
768 + assert speculative_config is not None
769 + self.draft_model_config = speculative_config.draft_model_config
770 self.config = self.draft_model_config.hf_config
771 if getattr(self.config, "draft_vocab_size", None) is None:
772 self.config.draft_vocab_size = getattr(self.config, "vocab_size", None)
@@ -801,7 +808,7 @@ class DFlashQwen3ForCausalLM(Qwen3ForCausalLM):
808 ) -> torch.Tensor:
809 return self.model.embed_input_ids(input_ids)
810
804 - def forward(
811 + def forward( # type: ignore[override]
812 self,
813 input_ids: torch.Tensor,
814 positions: torch.Tensor,
@@ -896,7 +903,7 @@ class DFlashQwen3ForCausalLM(Qwen3ForCausalLM):
903 model_weights["model.mask_embedding"] = mask_embedding
904 self.model.has_separate_mask_embedding = True
905
899 - orig_to_new_substr = {}
906 + orig_to_new_substr: dict[str, None] = {}
907 if not includes_draft_id_mapping:
908 orig_to_new_substr["draft_id_to_target_id"] = None
909 if not includes_embed_tokens:
vllm/model_executor/models/qwen3_dspark.py
+3 -1
@@ -233,7 +233,9 @@ class Qwen3DSparkModel(DFlashQwen3Model):
233 class Qwen3DSparkForCausalLM(DFlashQwen3ForCausalLM):
234 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
235 nn.Module.__init__(self)
236 - self.draft_model_config = vllm_config.speculative_config.draft_model_config
236 + speculative_config = vllm_config.speculative_config
237 + assert speculative_config is not None
238 + self.draft_model_config = speculative_config.draft_model_config
239 self.config = self.draft_model_config.hf_config
240 if getattr(self.config, "draft_vocab_size", None) is None:
241 self.config.draft_vocab_size = getattr(self.config, "vocab_size", None)
vllm/model_executor/models/qwen3_eagle3.py
+10 -5
@@ -101,7 +101,7 @@ class Qwen3Eagle3DecoderLayer(Qwen3DecoderLayer):
101 hidden_states = self.hidden_norm(hidden_states)
102 return hidden_states, residual
103
104 - def forward(
104 + def forward( # type: ignore[override]
105 self,
106 positions: torch.Tensor,
107 embeds: torch.Tensor,
@@ -159,7 +159,9 @@ class Qwen3Eagle3Model(nn.Module):
159 prefix: str = "",
160 ) -> None:
161 super().__init__()
162 - self.config = vllm_config.speculative_config.draft_model_config.hf_config
162 + speculative_config = vllm_config.speculative_config
163 + assert speculative_config is not None
164 + self.config = speculative_config.draft_model_config.hf_config
165 self.vocab_size = self.config.vocab_size
166
167 # Get drafter's quantization config
@@ -210,6 +212,7 @@ class Qwen3Eagle3Model(nn.Module):
212 self.config, "target_hidden_size", self.config.hidden_size
213 )
214 self.fc_input_size = target_hidden_size * num_aux_features
215 + self.input_norm: RMSNorm | None
216 if self.norm_before_fc:
217 self.input_norm = RMSNorm(
218 self.fc_input_size,
@@ -282,7 +285,9 @@ class Qwen3Eagle3Model(nn.Module):
285 class Eagle3Qwen3ForCausalLM(Qwen3ForCausalLM):
286 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
287 nn.Module.__init__(self)
285 - self.config = vllm_config.speculative_config.draft_model_config.hf_config
288 + speculative_config = vllm_config.speculative_config
289 + assert speculative_config is not None
290 + self.config = speculative_config.draft_model_config.hf_config
291 # Ensure draft_vocab_size is set
292 # default to the base vocab size when absent
293 if getattr(self.config, "draft_vocab_size", None) is None:
@@ -318,7 +323,7 @@ class Eagle3Qwen3ForCausalLM(Qwen3ForCausalLM):
323 else:
324 self.draft_id_to_target_id = None
325
321 - self.use_parallel_drafting = vllm_config.speculative_config.parallel_drafting
326 + self.use_parallel_drafting = speculative_config.parallel_drafting
327
328 if self.use_parallel_drafting:
329 self.register_buffer(
@@ -335,7 +340,7 @@ class Eagle3Qwen3ForCausalLM(Qwen3ForCausalLM):
340 ) -> torch.Tensor:
341 return self.model.embed_input_ids(input_ids)
342
338 - def forward(
343 + def forward( # type: ignore[override]
344 self,
345 input_ids: torch.Tensor,
346 positions: torch.Tensor,
vllm/model_executor/models/qwen3_moe.py
+9 -6
@@ -177,6 +177,8 @@ class Qwen3MoeSparseMoeBlock(nn.Module):
177 shared_expert_intermediate_size = getattr(
178 config, "shared_expert_intermediate_size", 0
179 )
180 + self.shared_expert_gate: ReplicatedLinear | None
181 + self.shared_expert: Qwen3MoeMLP | None
182 if shared_expert_intermediate_size > 0:
183 self.shared_expert_gate = ReplicatedLinear(
184 config.hidden_size,
@@ -302,6 +304,12 @@ class Qwen3MoeAttention(nn.Module):
304 rope_parameters=rope_parameters,
305 dual_chunk_attention_config=dual_chunk_attention_config,
306 )
307 + attention_kwargs: dict[str, Any] = {}
308 + if dual_chunk_attention_config:
309 + attention_kwargs = {
310 + "layer_idx": extract_layer_index(prefix),
311 + "dual_chunk_attention_config": dual_chunk_attention_config,
312 + }
313 self.attn = Attention(
314 self.num_heads,
315 self.head_dim,
@@ -310,12 +318,7 @@ class Qwen3MoeAttention(nn.Module):
318 cache_config=cache_config,
319 quant_config=quant_config,
320 prefix=f"{prefix}.attn",
313 - **{
314 - "layer_idx": extract_layer_index(prefix),
315 - "dual_chunk_attention_config": dual_chunk_attention_config,
316 - }
317 - if dual_chunk_attention_config
318 - else {},
321 + **attention_kwargs,
322 )
323
324 self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
vllm/model_executor/models/qwen3_next.py
+3
@@ -757,6 +757,9 @@ class Qwen3NextModel(nn.Module, EagleModelMixin):
757
758
759 class QwenNextMixtureOfExperts(MixtureOfExperts):
760 + model: Qwen3NextModel
761 + num_local_physical_experts: int
762 +
763 def update_physical_experts_metadata(
764 self,
765 num_physical_experts: int,
vllm/model_executor/models/qwen3_omni_moe_thinker.py
+60 -25
@@ -24,7 +24,7 @@
24
25 from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
26 from functools import partial
27 -from typing import Any, cast
27 +from typing import Any, ClassVar
28
29 import numpy as np
30 import torch
@@ -41,12 +41,13 @@ from transformers.models.qwen3_omni_moe.processing_qwen3_omni_moe import (
41 Qwen3OmniMoeProcessor,
42 )
43 from transformers.models.whisper import WhisperFeatureExtractor
44 +from typing_extensions import TypeVar
45
46 from vllm.compilation.decorators import support_torch_compile
47 from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
48 from vllm.config.speech_to_text import SpeechToTextParams
49 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
49 -from vllm.inputs import PromptType
50 +from vllm.inputs import PromptType, TextPrompt
51 from vllm.logger import init_logger
52 from vllm.model_executor.layers.activation import _ACTIVATION_REGISTRY
53 from vllm.model_executor.layers.attention.mm_encoder_attention import (
@@ -73,6 +74,7 @@ from vllm.multimodal.inputs import (
74 )
75 from vllm.multimodal.parse import AudioProcessorItems, MultiModalDataItems
76 from vllm.multimodal.processing.processor import (
77 + BaseProcessingInfo,
78 HFMultiModalInputs,
79 MultiModalProcessingResult,
80 MultiModalPromptUpdates,
@@ -102,12 +104,15 @@ from .qwen2_5_omni_thinker import (
104 Qwen2_5OmniThinkerDummyInputsBuilder,
105 Qwen2_5OmniThinkerMultiModalDataParser,
106 Qwen2_5OmniThinkerMultiModalProcessor,
107 + _get_feature_tensor,
108 check_interleaved_audio_video,
109 merge_interleaved_embeddings,
110 )
111 from .qwen2_5_vl import (
112 Qwen2_5_VisionAttention,
113 + Qwen2_5_VLImageInputs,
114 Qwen2_5_VLProcessingInfo,
115 + Qwen2_5_VLVideoInputs,
116 )
117 from .qwen3_moe import Qwen3MoeForCausalLM, Qwen3MoeModel
118 from .utils import (
@@ -963,7 +968,7 @@ class Qwen3Omni_VisionTransformer(nn.Module):
968 def forward(
969 self,
970 x: torch.Tensor,
966 - grid_thw: list[list[int]],
971 + grid_thw: torch.Tensor,
972 ) -> torch.Tensor:
973 hidden_states = x.to(device=self.device, dtype=self.dtype)
974 hidden_states = self.patch_embed(hidden_states)
@@ -1201,7 +1206,7 @@ class Qwen3OmniMoeThinkerProcessingInfo(
1206 self,
1207 seq_len: int,
1208 mm_counts: Mapping[str, int] | None = None,
1204 - ) -> Mapping[str, int] | None:
1209 + ) -> Mapping[str, int]:
1210 mm_counts = mm_counts or {}
1211 requested_modalities = {m for m, c in mm_counts.items() if c > 0}
1212 mm_max_tokens: dict[str, int] = {}
@@ -1231,11 +1236,15 @@ class Qwen3OmniMoeThinkerProcessingInfo(
1236 return mm_max_tokens
1237
1238
1234 -Qwen3OmniMoeThinkerDummyInputsBuilder = Qwen2_5OmniThinkerDummyInputsBuilder
1239 +Qwen3OmniMoeThinkerDummyInputsBuilder = Qwen2_5OmniThinkerDummyInputsBuilder[
1240 + Qwen3OmniMoeThinkerProcessingInfo
1241 +]
1242 +
1243 +_I = TypeVar("_I", bound=BaseProcessingInfo, default=Qwen3OmniMoeThinkerProcessingInfo)
1244
1245
1246 class Qwen3OmniMoeThinkerMultiModalProcessor(
1238 - Qwen2_5OmniThinkerMultiModalProcessor,
1247 + Qwen2_5OmniThinkerMultiModalProcessor[_I],
1248 ):
1249 def _get_hf_mm_inputs(
1250 self,
@@ -1246,7 +1255,8 @@ class Qwen3OmniMoeThinkerMultiModalProcessor(
1255
1256 audios = hf_inputs.hf_data.get("audio")
1257 if audios:
1249 - feature_extractor = self.info.get_feature_extractor()
1258 + assert isinstance(audios, (list, tuple))
1259 + feature_extractor = self.info.get_hf_processor().feature_extractor
1260 hop_length = feature_extractor.hop_length
1261
1262 def pad_to_hop_length(x: np.ndarray) -> np.ndarray:
@@ -1283,9 +1293,10 @@ class Qwen3OmniMoeThinkerMultiModalProcessor(
1293 ):
1294 audios = mm_data.get("audio", [])
1295 if audios:
1286 - feature_extractor = self.info.get_feature_extractor(
1296 + assert isinstance(audios, (list, tuple))
1297 + feature_extractor = self.info.get_hf_processor(
1298 **hf_processor_mm_kwargs
1288 - )
1299 + ).feature_extractor
1300 hop_length = feature_extractor.hop_length
1301
1302 audio_num_frames = []
@@ -1421,6 +1432,7 @@ class Qwen3OmniMoeThinkerMultiModalProcessor(
1432 ) -> Sequence[PromptUpdate]:
1433 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
1434 tokenizer = self.info.get_tokenizer()
1435 + assert isinstance(self.info, Qwen2_5_VLProcessingInfo)
1436 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
1437 vocab = tokenizer.get_vocab()
1438
@@ -1460,6 +1472,7 @@ class Qwen3OmniMoeThinkerMultiModalProcessor(
1472 if num_features == 0:
1473 audios = mm_items.get_items("audio", AudioProcessorItems)
1474 audio = audios.get(item_idx)
1475 + assert audio is not None
1476 raise ValueError(
1477 f"The audio {audio} (len={len(audio)}) is too short "
1478 "to be represented inside the model"
@@ -1607,8 +1620,8 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
1620 Qwen3OmniMoeConditionalGenerationMixin,
1621 SupportsTranscription,
1622 ):
1610 - is_3d_moe_weight: bool = True
1611 - supports_tower_connector_lora: bool = True
1623 + is_3d_moe_weight: ClassVar[bool] = True
1624 + supports_tower_connector_lora: ClassVar[bool] = True
1625 hf_to_vllm_mapper = WeightsMapper(
1626 orig_to_new_prefix={
1627 "thinker.lm_head.": "language_model.lm_head.",
@@ -1769,7 +1782,13 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
1782 self.deepstack_input_embeds_num_tokens = 0
1783
1784 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1772 - mm_input_by_modality = {}
1785 + mm_input_by_modality: dict[
1786 + str,
1787 + Qwen2_5_VLImageInputs
1788 + | Qwen2_5_VLVideoInputs
1789 + | Qwen2_5OmniAudioFeatureInputs
1790 + | None,
1791 + ] = {}
1792
1793 # Preserve the order of modalities if there are multiple of them
1794 # from the order of kwargs.
@@ -1862,6 +1881,7 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
1881 if self.visual.deepstack_visual_indexes is not None and any(
1882 has_vision_embeddings
1883 ):
1884 + multimodal_embeddings = list(multimodal_embeddings)
1885 multiscale_len = len(self.visual.deepstack_visual_indexes)
1886 multimodal_embeddings_multiscale = []
1887
@@ -2006,8 +2026,9 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2026 f
2027 for f in mm_features
2028 if f.modality == "video"
2029 + and f.data is not None
2030 and f.data.get("use_audio_in_video")
2010 - and f.data["use_audio_in_video"].data.item()
2031 + and _get_feature_tensor(f, "use_audio_in_video").item()
2032 ]
2033 audios = [f for f in mm_features if f.modality == "audio"]
2034
@@ -2015,7 +2036,9 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2036 paired_audio_offsets: set[int] = set()
2037 for i, video_f in enumerate(videos_with_audio):
2038 if i < len(audios):
2018 - audio_len = audios[i].data["audio_feature_lengths"].data.item()
2039 + audio_len = int(
2040 + _get_feature_tensor(audios[i], "audio_feature_lengths").item()
2041 + )
2042 mapping[video_f.mm_position.offset] = audio_len
2043 paired_audio_offsets.add(audios[i].mm_position.offset)
2044 return mapping, paired_audio_offsets
@@ -2043,9 +2066,10 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2066 for mm_feature in sorted_features:
2067 offset = mm_feature.mm_position.offset
2068 modality = mm_feature.modality
2069 + assert mm_feature.data is not None
2070
2071 if modality == "image":
2048 - t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
2072 + t, h, w = _get_feature_tensor(mm_feature, "image_grid_thw").tolist()
2073 yield (
2074 offset,
2075 "image",
@@ -2057,15 +2081,15 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2081 },
2082 )
2083 elif modality == "video":
2060 - t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
2084 + t, h, w = _get_feature_tensor(mm_feature, "video_grid_thw").tolist()
2085 second_per_grid_ts = 2.0
2086 if mm_feature.data.get("second_per_grid_ts"):
2063 - second_per_grid_ts = mm_feature.data[
2064 - "second_per_grid_ts"
2065 - ].data.item()
2087 + second_per_grid_ts = _get_feature_tensor(
2088 + mm_feature, "second_per_grid_ts"
2089 + ).item()
2090 use_audio_in_video = bool(
2091 mm_feature.data.get("use_audio_in_video")
2068 - and mm_feature.data["use_audio_in_video"].data.item()
2092 + and _get_feature_tensor(mm_feature, "use_audio_in_video").item()
2093 )
2094
2095 yield (
@@ -2082,7 +2106,9 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2106 )
2107 elif modality == "audio":
2108 if offset not in paired_audio_offsets:
2085 - audio_len = mm_feature.data["audio_feature_lengths"].data.item()
2109 + audio_len = _get_feature_tensor(
2110 + mm_feature, "audio_feature_lengths"
2111 + ).item()
2112 yield offset, "audio", {"audio_feature_length": audio_len}
2113
2114 def _compute_interleaved_positions(
@@ -2170,8 +2196,14 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2196 to_language = "en"
2197
2198 # Get full language names from supported_languages mapping
2173 - full_lang_name = cls.supported_languages.get(language, "")
2174 - full_lang_name_to = cls.supported_languages.get(to_language, "")
2199 + full_lang_name = (
2200 + cls.supported_languages.get(language, "") if language is not None else ""
2201 + )
2202 + full_lang_name_to = (
2203 + cls.supported_languages.get(to_language, "")
2204 + if to_language is not None
2205 + else ""
2206 + )
2207
2208 if task_type == "transcribe" and full_lang_name:
2209 instruction += f" into {full_lang_name}"
@@ -2201,8 +2233,11 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
2233 )
2234
2235 audio_data = (audio, stt_config.sample_rate)
2204 - prompts_dict = {"multi_modal_data": {"audio": audio_data}, "prompt": prompt}
2205 - return cast(PromptType, prompts_dict)
2236 + prompts_dict: TextPrompt = {
2237 + "multi_modal_data": {"audio": audio_data},
2238 + "prompt": prompt,
2239 + }
2240 + return prompts_dict
2241
2242 def get_mrope_input_positions(
2243 self,
vllm/model_executor/models/qwen3_vl.py
+34 -16
@@ -582,7 +582,9 @@ class Qwen3_VisionTransformer(nn.Module):
582 else []
583 )
584 self.num_grid_per_side = int(self.num_position_embeddings**0.5)
585 - self._rot_pos_ids_cache = LRUCache(capacity=1024)
585 + self._rot_pos_ids_cache: LRUCache[tuple[int, int, int], torch.Tensor] = (
586 + LRUCache(capacity=1024)
587 + )
588
589 use_data_parallel = is_vit_use_data_parallel()
590 self.tp_size = (
@@ -705,7 +707,7 @@ class Qwen3_VisionTransformer(nn.Module):
707 self._rot_pos_ids_cache[cache_key] = result
708 return result
709
708 - def rot_pos_emb(self, grid_thw: list[list[int]]):
710 + def rot_pos_emb(self, grid_thw: Sequence[Sequence[int]]):
711 max_grid_size = max(max(h, w) for _, h, w in grid_thw)
712 pos_ids = [
713 self.rot_pos_ids(h, w, self.spatial_merge_size)
@@ -731,7 +733,9 @@ class Qwen3_VisionTransformer(nn.Module):
733
734 return cos_combined, sin_combined
735
734 - def fast_pos_embed_interpolate(self, grid_thw: list[list[int]]) -> torch.Tensor:
736 + def fast_pos_embed_interpolate(
737 + self, grid_thw: Sequence[Sequence[int]]
738 + ) -> torch.Tensor:
739 interpolate_fn = (
740 triton_pos_embed_interpolate if HAS_TRITON else pos_embed_interpolate_native
741 )
@@ -752,7 +756,7 @@ class Qwen3_VisionTransformer(nn.Module):
756
757 def prepare_encoder_metadata(
758 self,
755 - grid_thw_list: list[list[int]],
759 + grid_thw_list: Sequence[Sequence[int]],
760 *,
761 max_batch_size: int | None = None,
762 max_frames_per_batch: int | None = None,
@@ -1005,9 +1009,10 @@ class Qwen3VLProcessingInfo(Qwen2VLProcessingInfo):
1009 self,
1010 seq_len: int,
1011 mm_counts: Mapping[str, int],
1012 + max_frames_per_video: int = DUMMY_VIDEO_NUM_FRAMES,
1013 ) -> int:
1014 return super().get_num_frames_with_most_features(
1010 - seq_len, mm_counts, max_frames_per_video=DUMMY_VIDEO_NUM_FRAMES
1015 + seq_len, mm_counts, max_frames_per_video=max_frames_per_video
1016 )
1017
1018 def get_max_video_tokens(
@@ -1253,7 +1258,7 @@ class Qwen3VLDummyInputsBuilder(BaseDummyInputsBuilder[Qwen3VLProcessingInfo]):
1258 )
1259 videos = [v.copy() for v in videos]
1260
1256 - video_items = []
1261 + video_items: list[VideoItem] = []
1262 for video in videos:
1263 video_num_frames = video.shape[0]
1264 video_metadata = {
@@ -1339,6 +1344,7 @@ class Qwen3VLMultiModalProcessor(BaseMultiModalProcessor[Qwen3VLProcessingInfo])
1344 # are processed into several image patches
1345 video_input_ids_lst: list[list[int]] = []
1346 if videos := hf_data.pop("videos", []):
1347 + assert isinstance(videos, list)
1348 video_grid_thw_lst = []
1349 pixel_values_videos_lst = []
1350 timestamps_per_video = []
@@ -1361,7 +1367,7 @@ class Qwen3VLMultiModalProcessor(BaseMultiModalProcessor[Qwen3VLProcessingInfo])
1367
1368 # NOTE: a copy of is created to update do_sample_frames,
1369 # otherwise mm_hash for the object will be incorrect.
1364 - video_mm_kwargs = dict(**hf_kwargs)
1370 + video_mm_kwargs: dict[str, Any] = dict(hf_kwargs)
1371 merged = self.info.ctx.get_merged_mm_kwargs(hf_kwargs, modality="video")
1372 if merged.keys() & {"size", "min_pixels", "max_pixels"}:
1373 video_size = dict(self.info.get_video_processor().size)
@@ -1551,6 +1557,7 @@ class Qwen3VLMultiModalProcessor(BaseMultiModalProcessor[Qwen3VLProcessingInfo])
1557 sampled_fps = sampled_fps[item_idx]
1558
1559 timestamps = out_item["timestamps"].data
1560 + assert isinstance(timestamps, (list, torch.Tensor))
1561 assert len(timestamps) == grid_thw[0], (
1562 f"The timestamps length({len(timestamps)}) should be equal "
1563 f"video length ({grid_thw[0]})."
@@ -1617,7 +1624,7 @@ class Qwen3VLMultiModalProcessor(BaseMultiModalProcessor[Qwen3VLProcessingInfo])
1624 def get_video_repl(
1625 *,
1626 tokens_per_frame: list[int],
1620 - timestamps: list[float | int] | torch.Tensor,
1627 + timestamps: Sequence[float] | torch.Tensor,
1628 tokenizer: TokenizerLike,
1629 vision_start_token_id: int,
1630 vision_end_token_id: int,
@@ -1842,7 +1849,7 @@ class Qwen3VLForConditionalGeneration(
1849 super().__init__()
1850 config: Qwen3VLConfig = vllm_config.model_config.hf_config
1851 quant_config = vllm_config.quant_config
1845 - multimodal_config = vllm_config.model_config.multimodal_config
1852 + multimodal_config = vllm_config.model_config.get_multimodal_config()
1853
1854 self.config = config
1855 self.model_config = vllm_config.model_config
@@ -2006,6 +2013,7 @@ class Qwen3VLForConditionalGeneration(
2013 def get_max_frames_per_video(self) -> int:
2014 mm_registry = MULTIMODAL_REGISTRY
2015 info = mm_registry.get_processing_info(self.model_config)
2016 + assert isinstance(info, Qwen3VLProcessingInfo)
2017 max_frames_per_video = info.get_num_frames_with_most_features(
2018 seq_len=self.model_config.max_model_len,
2019 mm_counts={"video": self.multimodal_config.get_limit_per_prompt("video")},
@@ -2254,7 +2262,7 @@ class Qwen3VLForConditionalGeneration(
2262 image_grid_thw=image_grid_thw,
2263 )
2264
2257 - if image_embeds is not None:
2265 + else:
2266 return Qwen2_5_VLImageEmbeddingInputs(
2267 type="image_embeds",
2268 image_embeds=image_embeds,
@@ -2282,7 +2290,7 @@ class Qwen3VLForConditionalGeneration(
2290 timestamps=timestamps,
2291 )
2292
2285 - if video_embeds is not None:
2293 + else:
2294 return Qwen2_5_VLVideoEmbeddingInputs(
2295 type="video_embeds",
2296 video_embeds=video_embeds,
@@ -2405,6 +2413,7 @@ class Qwen3VLForConditionalGeneration(
2413
2414 # Apply EVS to each video.
2415 video_embeds_out = []
2416 + assert video_input.timestamps is not None
2417 for video_idx, (emb, size) in enumerate(zip(video_embeds_split, grid_thw_list)):
2418 # Compute positions.
2419 timestamps = video_input.timestamps[video_idx]
@@ -2412,6 +2421,7 @@ class Qwen3VLForConditionalGeneration(
2421
2422 t, h, w = size
2423 if self.is_multimodal_pruning_enabled:
2424 + assert self.video_pruning_rate is not None
2425 # Compute the retention mask for each video (EVS or VidCom2).
2426 if self.video_pruning_method == "vidcom2":
2427 mask_fn = vidcom2_compute_retention_mask
@@ -2583,7 +2593,7 @@ class Qwen3VLForConditionalGeneration(
2593 {
2594 "video_grid_thw": MultiModalFieldElem(
2595 data=torch.tensor(video_grid_thw),
2586 - field=None, # HACK.
2596 + field=MultiModalFieldConfig.batched("video").field,
2597 ),
2598 }
2599 ),
@@ -2611,7 +2621,9 @@ class Qwen3VLForConditionalGeneration(
2621 return expanded_positions
2622
2623 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
2614 - mm_input_by_modality = {}
2624 + mm_input_by_modality: dict[
2625 + str, Qwen2_5_VLImageInputs | Qwen2_5_VLVideoInputs | None
2626 + ] = {}
2627 for input_key in kwargs:
2628 if (
2629 input_key in ("pixel_values", "image_embeds")
@@ -2659,14 +2671,20 @@ class Qwen3VLForConditionalGeneration(
2671 """
2672 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
2673 offset = mm_feature.mm_position.offset
2674 + data = mm_feature.data
2675 + assert data is not None
2676 if mm_feature.modality == "image":
2663 - t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
2677 + grid = data["image_grid_thw"].data
2678 + assert isinstance(grid, torch.Tensor)
2679 + t, h, w = grid.tolist()
2680 assert t == 1, f"Image must have 1 frame, got {t}"
2681 llm_grid_h = h // spatial_merge_size
2682 llm_grid_w = w // spatial_merge_size
2683 yield offset, llm_grid_h, llm_grid_w, llm_grid_h * llm_grid_w
2684 elif mm_feature.modality == "video":
2669 - t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
2685 + grid = data["video_grid_thw"].data
2686 + assert isinstance(grid, torch.Tensor)
2687 + t, h, w = grid.tolist()
2688 llm_grid_h = h // spatial_merge_size
2689 llm_grid_w = w // spatial_merge_size
2690
@@ -2721,7 +2739,7 @@ class Qwen3VLForConditionalGeneration(
2739 mm_features: list[MultiModalFeatureSpec],
2740 config: Qwen3VLConfig,
2741 ):
2724 - llm_pos_ids_list = []
2742 + llm_pos_ids_list: list[np.ndarray] = []
2743 st = 0
2744 for (
2745 offset,
vllm/model_executor/models/qwen3_vl_moe.py
+8 -4
@@ -25,6 +25,7 @@
25 """Inference-only Qwen3-VL-MoE model compatible with HuggingFace weights."""
26
27 from itertools import islice
28 +from typing import ClassVar
29
30 import torch
31 from transformers.models.qwen3_vl_moe.configuration_qwen3_vl_moe import (
@@ -176,6 +177,9 @@ class Qwen3MoeLLMForCausalLM(Qwen3MoeForCausalLM):
177
178
179 class Qwen3VLMoeMixtureOfExperts(MixtureOfExperts):
180 + language_model: Qwen3MoeLLMForCausalLM
181 + num_local_physical_experts: int
182 +
183 def update_physical_experts_metadata(
184 self,
185 num_physical_experts: int,
@@ -223,7 +227,7 @@ class Qwen3VLMoeMixtureOfExperts(MixtureOfExperts):
227 class Qwen3VLMoeForConditionalGeneration(
228 Qwen3VLForConditionalGeneration, Qwen3VLMoeMixtureOfExperts
229 ):
226 - is_3d_moe_weight: bool = True
230 + is_3d_moe_weight: ClassVar[bool] = True
231 packed_modules_mapping = {
232 "qkv_proj": [
233 "q_proj",
@@ -236,7 +240,7 @@ class Qwen3VLMoeForConditionalGeneration(
240 super(Qwen3VLForConditionalGeneration, self).__init__()
241 config: Qwen3VLMoeConfig = vllm_config.model_config.hf_config
242 quant_config = vllm_config.quant_config
239 - multimodal_config = vllm_config.model_config.multimodal_config
243 + multimodal_config = vllm_config.model_config.get_multimodal_config()
244
245 self.config = config
246 self.model_config = vllm_config.model_config
@@ -293,8 +297,8 @@ class Qwen3VLMoeForConditionalGeneration(
297 )
298
299 # Whether to include the gate_up_proj mapping is determined by
296 - # the language model.
297 - self.packed_modules_mapping = (
300 + # the language model. Keep this override local to the instance.
301 + self.packed_modules_mapping = ( # type: ignore[misc]
302 self.packed_modules_mapping | self.language_model.packed_modules_mapping
303 )
304