@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / afffd3cc8a
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 13, 12:00 UTC

[Model] Pass `mm_features` directly into `get_mrope_input_positions` (#28399)

Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>

Cyrus Leung committed Nov 11, 2025 at 21:14 UTC afffd3cc8a99ce1cf0f6f1687852e5519d725a3b
15 files changed +225 -272
vllm/model_executor/models/ernie45_vl.py
+13 -22
@@ -34,7 +34,7 @@ import torch
34 import torch.nn as nn
35 import torch.nn.functional as F
36 from einops import rearrange, repeat
37 -from transformers import BatchFeature, PretrainedConfig
37 +from transformers import BatchFeature
38
39 from vllm.attention.backends.registry import AttentionBackendEnum
40 from vllm.attention.layer import (
@@ -58,6 +58,7 @@ from vllm.model_executor.model_loader.weight_utils import default_weight_loader
58 from vllm.multimodal import MULTIMODAL_REGISTRY
59 from vllm.multimodal.inputs import (
60 MultiModalDataDict,
61 + MultiModalFeatureSpec,
62 MultiModalFieldConfig,
63 MultiModalKwargsItems,
64 )
@@ -1433,15 +1434,16 @@ class Ernie4_5_VLMoeForConditionalGeneration(
1434 def get_mrope_input_positions(
1435 self,
1436 input_tokens: list[int],
1436 - hf_config: PretrainedConfig,
1437 - image_grid_thw: list[list[int]] | torch.Tensor,
1438 - video_grid_thw: list[list[int]] | torch.Tensor,
1439 - second_per_grid_ts: list[float] | None = None,
1440 - audio_feature_lengths: torch.Tensor | None = None,
1441 - use_audio_in_video: bool = False,
1437 + mm_features: list[MultiModalFeatureSpec],
1438 ) -> tuple[torch.Tensor, int]:
1443 - """Get mrope input positions and delta value for Ernie VL."""
1439 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1440 + mm_features,
1441 + {"image_grid_thw", "video_grid_thw"},
1442 + )
1443 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1444 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1445
1446 + hf_config = self.config
1447 image_token_id = hf_config.im_patch_id
1448 video_start_token_id = hf_config.video_start_token_id
1449 video_end_token_id = hf_config.video_end_token_id
@@ -1449,10 +1451,7 @@ class Ernie4_5_VLMoeForConditionalGeneration(
1451 temporal_conv_size = hf_config.temporal_conv_size
1452 llm_pos_ids_list: list = []
1453
1452 - if not (image_grid_thw is None and video_grid_thw is None):
1453 - if isinstance(image_grid_thw, torch.Tensor):
1454 - image_grid_thw = image_grid_thw.tolist()
1455 -
1454 + if image_grid_thw or video_grid_thw:
1455 input_token_type: list[str] = []
1456 video_check_flg = False
1457 for token in input_tokens:
@@ -1484,11 +1483,7 @@ class Ernie4_5_VLMoeForConditionalGeneration(
1483 llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1484 )
1485 if modality_type == "image":
1487 - t, h, w = (
1488 - image_grid_thw[mm_data_idx][0],
1489 - image_grid_thw[mm_data_idx][1],
1490 - image_grid_thw[mm_data_idx][2],
1491 - )
1486 + t, h, w = image_grid_thw[mm_data_idx]
1487 llm_grid_t, llm_grid_h, llm_grid_w = (
1488 t,
1489 h // spatial_conv_size,
@@ -1519,11 +1514,7 @@ class Ernie4_5_VLMoeForConditionalGeneration(
1514 mm_data_idx += 1
1515
1516 elif modality_type == "video":
1522 - t, h, w = (
1523 - video_grid_thw[mm_data_idx][0],
1524 - video_grid_thw[mm_data_idx][1],
1525 - video_grid_thw[mm_data_idx][2],
1526 - )
1517 + t, h, w = video_grid_thw[mm_data_idx]
1518 llm_grid_t, llm_grid_h, llm_grid_w = (
1519 t // temporal_conv_size,
1520 h // spatial_conv_size,
vllm/model_executor/models/glm4_1v.py
+13 -19
@@ -37,7 +37,7 @@ import torch
37 import torch.nn as nn
38 import torch.nn.functional as F
39 from einops import rearrange
40 -from transformers import BatchFeature, PretrainedConfig
40 +from transformers import BatchFeature
41 from transformers.models.glm4v.configuration_glm4v import Glm4vVisionConfig
42 from transformers.models.glm4v.image_processing_glm4v import (
43 Glm4vImageProcessor,
@@ -70,6 +70,7 @@ from vllm.model_executor.models.module_mapping import MultiModelKeys
70 from vllm.multimodal import MULTIMODAL_REGISTRY
71 from vllm.multimodal.inputs import (
72 MultiModalDataDict,
73 + MultiModalFeatureSpec,
74 MultiModalFieldConfig,
75 MultiModalKwargsItems,
76 VideoItem,
@@ -1619,25 +1620,23 @@ class Glm4vForConditionalGeneration(
1620 def get_mrope_input_positions(
1621 self,
1622 input_tokens: list[int],
1622 - hf_config: "PretrainedConfig",
1623 - image_grid_thw: list[list[int]] | torch.Tensor | None,
1624 - video_grid_thw: list[list[int]] | torch.Tensor | None,
1625 - second_per_grid_ts: list[float] | None = None,
1626 - audio_feature_lengths: torch.Tensor | None = None,
1627 - use_audio_in_video: bool = False,
1623 + mm_features: list[MultiModalFeatureSpec],
1624 ) -> tuple[torch.Tensor, int]:
1629 - """Get mrope input positions and delta value for GLM4V."""
1625 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1626 + mm_features,
1627 + {"image_grid_thw", "video_grid_thw"},
1628 + )
1629 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1630 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1631
1632 + hf_config = self.config
1633 image_token_id = hf_config.image_token_id
1634 video_start_token_id = hf_config.video_start_token_id
1635 video_end_token_id = hf_config.video_end_token_id
1636 spatial_merge_size = hf_config.vision_config.spatial_merge_size
1637 llm_pos_ids_list: list = []
1638
1637 - if not (image_grid_thw is None and video_grid_thw is None):
1638 - if isinstance(image_grid_thw, torch.Tensor):
1639 - image_grid_thw = image_grid_thw.tolist()
1640 -
1639 + if image_grid_thw or video_grid_thw:
1640 input_token_type: list[str] = []
1641 video_check_flg = False
1642 for token in input_tokens:
@@ -1669,11 +1668,7 @@ class Glm4vForConditionalGeneration(
1668 llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1669 )
1670 if modality_type == "image":
1672 - t, h, w = (
1673 - image_grid_thw[mm_data_idx][0],
1674 - image_grid_thw[mm_data_idx][1],
1675 - image_grid_thw[mm_data_idx][2],
1676 - )
1671 + t, h, w = image_grid_thw[mm_data_idx]
1672 llm_grid_t, llm_grid_h, llm_grid_w = (
1673 t,
1674 h // spatial_merge_size,
@@ -1706,8 +1701,7 @@ class Glm4vForConditionalGeneration(
1701 elif modality_type == "video":
1702 t, h, w = (
1703 video_frame_num,
1709 - image_grid_thw[mm_data_idx][1],
1710 - image_grid_thw[mm_data_idx][2],
1704 + *image_grid_thw[mm_data_idx][1:],
1705 )
1706 llm_grid_t, llm_grid_h, llm_grid_w = (
1707 t,
vllm/model_executor/models/glm4v.py
+13 -19
@@ -15,7 +15,7 @@ from torch import nn
15 from torch.nn import LayerNorm
16 from torchvision import transforms
17 from torchvision.transforms import InterpolationMode
18 -from transformers import BatchFeature, PretrainedConfig, PreTrainedTokenizer, TensorType
18 +from transformers import BatchFeature, PreTrainedTokenizer, TensorType
19 from transformers.image_utils import ImageInput
20 from transformers.tokenization_utils_base import TextInput
21
@@ -36,6 +36,7 @@ from vllm.model_executor.models.module_mapping import MultiModelKeys
36 from vllm.multimodal import MULTIMODAL_REGISTRY
37 from vllm.multimodal.inputs import (
38 MultiModalDataDict,
39 + MultiModalFeatureSpec,
40 MultiModalFieldConfig,
41 MultiModalKwargsItems,
42 )
@@ -622,25 +623,23 @@ class GLM4VForCausalLM(
623 def get_mrope_input_positions(
624 self,
625 input_tokens: list[int],
625 - hf_config: PretrainedConfig,
626 - image_grid_thw: list[list[int]] | torch.Tensor,
627 - video_grid_thw: list[list[int]] | torch.Tensor,
628 - second_per_grid_ts: list[float] | None = None,
629 - audio_feature_lengths: torch.Tensor | None = None,
630 - use_audio_in_video: bool = False,
626 + mm_features: list[MultiModalFeatureSpec],
627 ) -> tuple[torch.Tensor, int]:
632 - """Get mrope input positions and delta value for GLM4V."""
628 + kwargs = MultiModalFeatureSpec.gather_kwargs(
629 + mm_features,
630 + {"image_grid_thw", "video_grid_thw"},
631 + )
632 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
633 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
634
635 + hf_config = self.config
636 image_token_id = hf_config.image_token_id
637 video_start_token_id = hf_config.video_start_token_id
638 video_end_token_id = hf_config.video_end_token_id
639 spatial_merge_size = hf_config.vision_config.spatial_merge_size
640 llm_pos_ids_list: list = []
641
640 - if not (image_grid_thw is None and video_grid_thw is None):
641 - if isinstance(image_grid_thw, torch.Tensor):
642 - image_grid_thw = image_grid_thw.tolist()
643 -
642 + if image_grid_thw or video_grid_thw:
643 input_token_type: list[str] = []
644 video_check_flg = False
645 for token in input_tokens:
@@ -672,11 +671,7 @@ class GLM4VForCausalLM(
671 llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
672 )
673 if modality_type == "image":
675 - t, h, w = (
676 - image_grid_thw[mm_data_idx][0],
677 - image_grid_thw[mm_data_idx][1],
678 - image_grid_thw[mm_data_idx][2],
679 - )
674 + t, h, w = image_grid_thw[mm_data_idx]
675 llm_grid_t, llm_grid_h, llm_grid_w = (
676 t,
677 h // spatial_merge_size,
@@ -709,8 +704,7 @@ class GLM4VForCausalLM(
704 elif modality_type == "video":
705 t, h, w = (
706 video_frame_num,
712 - image_grid_thw[mm_data_idx][1],
713 - image_grid_thw[mm_data_idx][2],
707 + *image_grid_thw[mm_data_idx][1:],
708 )
709 llm_grid_t, llm_grid_h, llm_grid_w = (
710 t,
vllm/model_executor/models/interfaces.py
+6 -16
@@ -16,7 +16,6 @@ import numpy as np
16 import torch
17 import torch.nn as nn
18 from torch import Tensor
19 -from transformers import PretrainedConfig
19 from transformers.models.whisper.tokenization_whisper import LANGUAGES
20 from typing_extensions import Self, TypeIs
21
@@ -32,10 +31,12 @@ from .interfaces_base import VllmModel, is_pooling_model
31 if TYPE_CHECKING:
32 from vllm.config import VllmConfig
33 from vllm.model_executor.models.utils import WeightsMapper
34 + from vllm.multimodal.inputs import MultiModalFeatureSpec
35 from vllm.sequence import IntermediateTensors
36 else:
37 VllmConfig = object
38 WeightsMapper = object
39 + MultiModalFeatureSpec = object
40 IntermediateTensors = object
41
42 logger = init_logger(__name__)
@@ -991,12 +992,7 @@ class SupportsMRoPE(Protocol):
992 def get_mrope_input_positions(
993 self,
994 input_tokens: list[int],
994 - hf_config: PretrainedConfig,
995 - image_grid_thw: list[list[int]] | torch.Tensor | None,
996 - video_grid_thw: list[list[int]] | torch.Tensor | None,
997 - second_per_grid_ts: list[float] | None = None,
998 - audio_feature_lengths: torch.Tensor | None = None,
999 - use_audio_in_video: bool = False,
995 + mm_features: list["MultiModalFeatureSpec"],
996 ) -> tuple[torch.Tensor, int]:
997 """
998 Get M-RoPE input positions and delta value for this specific model.
@@ -1006,17 +1002,11 @@ class SupportsMRoPE(Protocol):
1002
1003 Args:
1004 input_tokens: List of input token IDs
1009 - hf_config: HuggingFace model configuration
1010 - image_grid_thw: Image grid dimensions (t, h, w)
1011 - video_grid_thw: Video grid dimensions (t, h, w)
1012 - second_per_grid_ts: Seconds per grid timestep for videos
1013 - audio_feature_lengths: Audio feature lengths for multimodal models
1014 - use_audio_in_video: Whether to use audio in video for interleaving
1005 + mm_features: Information about each multi-modal data item
1006
1007 Returns:
1017 - Tuple of (llm_positions, mrope_position_delta)
1018 - - llm_positions: Tensor of shape [3, num_tokens]
1019 - with T/H/W positions
1008 + Tuple of `(llm_positions, mrope_position_delta)`
1009 + - llm_positions: Tensor of shape `[3, num_tokens]` with T/H/W positions
1010 - mrope_position_delta: Delta for position calculations
1011 """
1012 ...
vllm/model_executor/models/keye.py
+12 -17
@@ -40,6 +40,7 @@ from vllm.multimodal.inputs import (
40 ImageItem,
41 ModalityData,
42 MultiModalDataDict,
43 + MultiModalFeatureSpec,
44 MultiModalFieldConfig,
45 MultiModalKwargsItems,
46 VideoItem,
@@ -1627,16 +1628,17 @@ class KeyeForConditionalGeneration(
1628 def get_mrope_input_positions(
1629 self,
1630 input_tokens: list[int],
1630 - hf_config: PretrainedConfig,
1631 - image_grid_thw: list[list[int]] | torch.Tensor,
1632 - video_grid_thw: list[list[int]] | torch.Tensor,
1633 - second_per_grid_ts: list[float] | None = None,
1634 - audio_feature_lengths: torch.Tensor | None = None,
1635 - use_audio_in_video: bool = False,
1631 + mm_features: list[MultiModalFeatureSpec],
1632 ) -> tuple[torch.Tensor, int]:
1633 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1634 + mm_features,
1635 + {"image_grid_thw", "video_grid_thw"},
1636 + )
1637 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1638 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1639 +
1640 if isinstance(video_grid_thw, list) and len(video_grid_thw) > 0:
1641 video_grid_thw = video_grid_thw[0]
1639 - """Get mrope input positions and delta value (Keye series)."""
1642
1643 def split_thw(grid_thw: torch.Tensor | list[int]) -> list[list[int]]:
1644 """
@@ -1662,6 +1664,7 @@ class KeyeForConditionalGeneration(
1664
1665 video_grid_thw = split_thw(video_grid_thw)
1666
1667 + hf_config = self.config
1668 image_token_id = hf_config.image_token_id
1669 video_token_id = hf_config.video_token_id
1670 spatial_merge_size = hf_config.vision_config.spatial_merge_size
@@ -1691,20 +1694,12 @@ class KeyeForConditionalGeneration(
1694 ed_video = len(input_tokens) + 1
1695
1696 if ed_image < ed_video:
1694 - t, h, w = (
1695 - image_grid_thw[image_index][0],
1696 - image_grid_thw[image_index][1],
1697 - image_grid_thw[image_index][2],
1698 - )
1697 + t, h, w = image_grid_thw[image_index]
1698 image_index += 1
1699 remain_images -= 1
1700 ed = ed_image
1701 else:
1703 - t, h, w = (
1704 - video_grid_thw[video_index][0],
1705 - video_grid_thw[video_index][1],
1706 - video_grid_thw[video_index][2],
1707 - )
1702 + t, h, w = video_grid_thw[video_index]
1703 video_index += 1
1704 remain_frames -= 1
1705 ed = ed_video
vllm/model_executor/models/keye_vl1_5.py
+12 -17
@@ -21,6 +21,7 @@ from vllm.multimodal import MULTIMODAL_REGISTRY
21 from vllm.multimodal.inputs import (
22 ImageItem,
23 ModalityData,
24 + MultiModalFeatureSpec,
25 MultiModalFieldConfig,
26 MultiModalKwargsItems,
27 VideoItem,
@@ -597,16 +598,17 @@ class KeyeVL1_5ForConditionalGeneration(
598 def get_mrope_input_positions(
599 self,
600 input_tokens: list[int],
600 - hf_config: PretrainedConfig,
601 - image_grid_thw: list[list[int]] | torch.Tensor,
602 - video_grid_thw: list[list[int]] | torch.Tensor,
603 - second_per_grid_ts: list[float] | None = None,
604 - audio_feature_lengths: torch.Tensor | None = None,
605 - use_audio_in_video: bool = False,
601 + mm_features: list[MultiModalFeatureSpec],
602 ) -> tuple[torch.Tensor, int]:
603 + kwargs = MultiModalFeatureSpec.gather_kwargs(
604 + mm_features,
605 + {"image_grid_thw", "video_grid_thw"},
606 + )
607 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
608 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
609 +
610 if isinstance(video_grid_thw, list) and len(video_grid_thw) > 0:
611 video_grid_thw = video_grid_thw[0]
609 - """Get mrope input positions and delta value (Keye series)."""
612
613 def split_thw(grid_thw: torch.Tensor | list[int]) -> list[list[int]]:
614 """
@@ -632,6 +634,7 @@ class KeyeVL1_5ForConditionalGeneration(
634
635 video_grid_thw = split_thw(video_grid_thw)
636
637 + hf_config = self.config
638 image_token_id = hf_config.image_token_id
639 video_token_id = hf_config.video_token_id
640 spatial_merge_size = hf_config.vision_config.spatial_merge_size
@@ -661,20 +664,12 @@ class KeyeVL1_5ForConditionalGeneration(
664 ed_video = len(input_tokens) + 1
665
666 if ed_image < ed_video:
664 - t, h, w = (
665 - image_grid_thw[image_index][0],
666 - image_grid_thw[image_index][1],
667 - image_grid_thw[image_index][2],
668 - )
667 + t, h, w = image_grid_thw[image_index]
668 image_index += 1
669 remain_images -= 1
670 ed = ed_image
671 else:
673 - t, h, w = (
674 - video_grid_thw[video_index][0],
675 - video_grid_thw[video_index][1],
676 - video_grid_thw[video_index][2],
677 - )
672 + t, h, w = video_grid_thw[video_index]
673 video_index += 1
674 remain_frames -= 1
675 ed = ed_video
vllm/model_executor/models/paddleocr_vl.py
+12 -17
@@ -61,6 +61,7 @@ from vllm.model_executor.model_loader.weight_utils import (
61 from vllm.multimodal import MULTIMODAL_REGISTRY
62 from vllm.multimodal.inputs import (
63 MultiModalDataDict,
64 + MultiModalFeatureSpec,
65 MultiModalFieldConfig,
66 MultiModalKwargs,
67 )
@@ -1184,15 +1185,17 @@ class PaddleOCRVLForConditionalGeneration(nn.Module, SupportsMultiModal, Support
1185 def get_mrope_input_positions(
1186 self,
1187 input_tokens: list[int],
1187 - hf_config: PretrainedConfig,
1188 - image_grid_thw: list[list[int]] | torch.Tensor,
1189 - video_grid_thw: list[list[int]] | torch.Tensor,
1190 - second_per_grid_ts: list[float],
1191 - audio_feature_lengths: torch.Tensor | None = None,
1192 - use_audio_in_video: bool = False,
1188 + mm_features: list[MultiModalFeatureSpec],
1189 ) -> tuple[torch.Tensor, int]:
1194 - """Get mrope input positions and delta value."""
1190 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1191 + mm_features,
1192 + {"image_grid_thw", "video_grid_thw", "second_per_grid_ts"},
1193 + )
1194 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1195 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1196 + second_per_grid_ts = kwargs.get("second_per_grid_ts", [])
1197
1198 + hf_config = self.config
1199 image_token_id = hf_config.image_token_id
1200 video_token_id = hf_config.video_token_id
1201 vision_start_token_id = hf_config.vision_start_token_id
@@ -1229,20 +1232,12 @@ class PaddleOCRVLForConditionalGeneration(nn.Module, SupportsMultiModal, Support
1232 else:
1233 ed_video = len(input_tokens) + 1
1234 if ed_image < ed_video:
1232 - t, h, w = (
1233 - image_grid_thw[image_index][0],
1234 - image_grid_thw[image_index][1],
1235 - image_grid_thw[image_index][2],
1236 - )
1235 + t, h, w = image_grid_thw[image_index]
1236 image_index += 1
1237 remain_images -= 1
1238 ed = ed_image
1239 else:
1241 - t, h, w = (
1242 - video_grid_thw[video_index][0],
1243 - video_grid_thw[video_index][1],
1244 - video_grid_thw[video_index][2],
1245 - )
1240 + t, h, w = video_grid_thw[video_index]
1241 video_second_per_grid_t = 1.0
1242 if second_per_grid_ts:
1243 video_second_per_grid_t = second_per_grid_ts[video_index]
vllm/model_executor/models/qwen2_5_omni_thinker.py
+26 -20
@@ -68,6 +68,7 @@ from vllm.multimodal.inputs import (
68 ImageItem,
69 ModalityData,
70 MultiModalDataDict,
71 + MultiModalFeatureSpec,
72 MultiModalFieldConfig,
73 MultiModalKwargsItems,
74 NestedTensors,
@@ -923,21 +924,9 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
924 def get_mrope_input_positions(
925 self,
926 input_tokens: list[int],
926 - hf_config: PretrainedConfig,
927 - image_grid_thw: list[list[int]] | torch.Tensor,
928 - video_grid_thw: list[list[int]] | torch.Tensor,
929 - second_per_grid_ts: list[float] | None = None,
930 - audio_feature_lengths: torch.Tensor | None = None,
931 - use_audio_in_video: bool = False,
927 + mm_features: list[MultiModalFeatureSpec],
928 ) -> tuple[torch.Tensor, int]:
933 - """Get mrope input positions and delta value (Qwen2.5-Omni version).
934 -
935 - Differences from MRotaryEmbedding:
936 - 1. Add audio support (and related `audio_feature_lengths`).
937 - 2. Add `use_audio_in_video` option to read audio from video inputs.
938 - In this case, audio and vision position ids will be split into
939 - chunks and interleaved.
940 -
929 + """
930 Example:
931
932 (V_i are vision position ids, A_i are audio position ids)
@@ -945,11 +934,33 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
934 |V_1 ... V_n|A_1 ... A_n|V_n+1 ... V_2n|A_n+1 ... A_2n|...
935 |vision chunk 1|audio chunk 1|vision chunk 2|audio chunk 2 |...
936 """
937 + kwargs = MultiModalFeatureSpec.gather_kwargs(
938 + mm_features,
939 + {
940 + "image_grid_thw",
941 + "video_grid_thw",
942 + "second_per_grid_ts",
943 + "audio_feature_lengths",
944 + "use_audio_in_video",
945 + },
946 + )
947 + image_grid_thw = kwargs.get("image_grid_thw", [])
948 + video_grid_thw = kwargs.get("video_grid_thw", [])
949 + second_per_grid_ts = kwargs.get("second_per_grid_ts", [])
950 + audio_feature_lengths = kwargs.get("audio_feature_lengths", [])
951 + use_audio_in_video = any(kwargs.get("use_audio_in_video", []))
952 +
953 + image_grid_thw = (torch.stack if image_grid_thw else torch.tensor)(
954 + image_grid_thw
955 + )
956 + video_grid_thw = (torch.stack if video_grid_thw else torch.tensor)(
957 + video_grid_thw
958 + )
959
960 # TODO(fyabc): refactor and share more code with
961 # _vl_get_input_positions_tensor.
962
952 - thinker_config = hf_config.thinker_config
963 + thinker_config = self.config
964 audio_token_id = thinker_config.audio_token_index
965 image_token_id = thinker_config.image_token_index
966 video_token_id = thinker_config.video_token_index
@@ -963,11 +974,6 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
974 thinker_config.vision_config, "tokens_per_second", 25
975 )
976
966 - if isinstance(image_grid_thw, list):
967 - image_grid_thw = torch.tensor(image_grid_thw)
968 - if isinstance(video_grid_thw, list):
969 - video_grid_thw = torch.tensor(video_grid_thw)
970 -
977 src_item = input_tokens
978 audio_seqlens = audio_feature_lengths
979 if not second_per_grid_ts:
vllm/model_executor/models/qwen2_5_vl.py
+17 -19
@@ -35,7 +35,7 @@ import einops
35 import torch
36 import torch.nn as nn
37 import torch.nn.functional as F
38 -from transformers import BatchFeature, PretrainedConfig
38 +from transformers import BatchFeature
39 from transformers.models.qwen2_5_vl import Qwen2_5_VLProcessor
40 from transformers.models.qwen2_5_vl.configuration_qwen2_5_vl import (
41 Qwen2_5_VLConfig,
@@ -75,7 +75,11 @@ from vllm.multimodal.evs import (
75 compute_retention_mask,
76 recompute_mrope_positions,
77 )
78 -from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargs
78 +from vllm.multimodal.inputs import (
79 + MultiModalFeatureSpec,
80 + MultiModalFieldConfig,
81 + MultiModalKwargs,
82 +)
83 from vllm.multimodal.parse import MultiModalDataItems
84 from vllm.multimodal.processing import PromptReplacement, PromptUpdate
85 from vllm.sequence import IntermediateTensors
@@ -1120,15 +1124,17 @@ class Qwen2_5_VLForConditionalGeneration(
1124 def get_mrope_input_positions(
1125 self,
1126 input_tokens: list[int],
1123 - hf_config: PretrainedConfig,
1124 - image_grid_thw: list[list[int]] | torch.Tensor,
1125 - video_grid_thw: list[list[int]] | torch.Tensor,
1126 - second_per_grid_ts: list[float],
1127 - audio_feature_lengths: torch.Tensor | None = None,
1128 - use_audio_in_video: bool = False,
1127 + mm_features: list[MultiModalFeatureSpec],
1128 ) -> tuple[torch.Tensor, int]:
1130 - """Get mrope input positions and delta value."""
1129 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1130 + mm_features,
1131 + {"image_grid_thw", "video_grid_thw", "second_per_grid_ts"},
1132 + )
1133 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1134 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1135 + second_per_grid_ts = kwargs.get("second_per_grid_ts", [])
1136
1137 + hf_config = self.config
1138 image_token_id = hf_config.image_token_id
1139 video_token_id = hf_config.video_token_id
1140 vision_start_token_id = hf_config.vision_start_token_id
@@ -1165,20 +1171,12 @@ class Qwen2_5_VLForConditionalGeneration(
1171 else:
1172 ed_video = len(input_tokens) + 1
1173 if ed_image < ed_video:
1168 - t, h, w = (
1169 - image_grid_thw[image_index][0],
1170 - image_grid_thw[image_index][1],
1171 - image_grid_thw[image_index][2],
1172 - )
1174 + t, h, w = image_grid_thw[image_index]
1175 image_index += 1
1176 remain_images -= 1
1177 ed = ed_image
1178 else:
1177 - t, h, w = (
1178 - video_grid_thw[video_index][0],
1179 - video_grid_thw[video_index][1],
1180 - video_grid_thw[video_index][2],
1181 - )
1179 + t, h, w = video_grid_thw[video_index]
1180 video_second_per_grid_t = 1.0
1181 if second_per_grid_ts:
1182 video_second_per_grid_t = second_per_grid_ts[video_index]
vllm/model_executor/models/qwen2_vl.py
+13 -24
@@ -34,7 +34,7 @@ import torch
34 import torch.nn as nn
35 import torch.nn.functional as F
36 from einops import rearrange, repeat
37 -from transformers import BatchFeature, PretrainedConfig
37 +from transformers import BatchFeature
38 from transformers.models.qwen2_vl import Qwen2VLImageProcessor, Qwen2VLProcessor
39 from transformers.models.qwen2_vl.configuration_qwen2_vl import (
40 Qwen2VLConfig,
@@ -70,6 +70,7 @@ from vllm.multimodal.inputs import (
70 ImageItem,
71 ModalityData,
72 MultiModalDataDict,
73 + MultiModalFeatureSpec,
74 MultiModalFieldConfig,
75 MultiModalKwargsItems,
76 VideoItem,
@@ -1240,21 +1241,17 @@ class Qwen2VLForConditionalGeneration(
1241 def get_mrope_input_positions(
1242 self,
1243 input_tokens: list[int],
1243 - hf_config: PretrainedConfig,
1244 - image_grid_thw: list[list[int]] | torch.Tensor | None,
1245 - video_grid_thw: list[list[int]] | torch.Tensor | None,
1246 - second_per_grid_ts: list[float] | None = None,
1247 - audio_feature_lengths: torch.Tensor | None = None,
1248 - use_audio_in_video: bool = False,
1244 + mm_features: list[MultiModalFeatureSpec],
1245 ) -> tuple[torch.Tensor, int]:
1250 - """Get M-RoPE input positions for Qwen2-VL model."""
1251 - if image_grid_thw is None:
1252 - image_grid_thw = []
1253 - if video_grid_thw is None:
1254 - video_grid_thw = []
1255 - if second_per_grid_ts is None:
1256 - second_per_grid_ts = []
1246 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1247 + mm_features,
1248 + {"image_grid_thw", "video_grid_thw", "second_per_grid_ts"},
1249 + )
1250 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1251 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1252 + second_per_grid_ts = kwargs.get("second_per_grid_ts", [])
1253
1254 + hf_config = self.config
1255 image_token_id = hf_config.image_token_id
1256 video_token_id = hf_config.video_token_id
1257 vision_start_token_id = hf_config.vision_start_token_id
@@ -1291,20 +1288,12 @@ class Qwen2VLForConditionalGeneration(
1288 else:
1289 ed_video = len(input_tokens) + 1
1290 if ed_image < ed_video:
1294 - t, h, w = (
1295 - image_grid_thw[image_index][0],
1296 - image_grid_thw[image_index][1],
1297 - image_grid_thw[image_index][2],
1298 - )
1291 + t, h, w = image_grid_thw[image_index]
1292 image_index += 1
1293 remain_images -= 1
1294 ed = ed_image
1295 else:
1303 - t, h, w = (
1304 - video_grid_thw[video_index][0],
1305 - video_grid_thw[video_index][1],
1306 - video_grid_thw[video_index][2],
1307 - )
1296 + t, h, w = video_grid_thw[video_index]
1297 video_second_per_grid_t = 1.0
1298 if second_per_grid_ts:
1299 video_second_per_grid_t = second_per_grid_ts[video_index]
vllm/model_executor/models/qwen3_omni_moe_thinker.py
+32 -23
@@ -65,7 +65,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
65 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
66 from vllm.model_executor.models.qwen2_audio import Qwen2AudioProcessingInfo
67 from vllm.multimodal import MULTIMODAL_REGISTRY
68 -from vllm.multimodal.inputs import MultiModalKwargsItems
68 +from vllm.multimodal.inputs import MultiModalFeatureSpec, MultiModalKwargsItems
69 from vllm.multimodal.parse import AudioProcessorItems, MultiModalDataItems
70 from vllm.multimodal.processing import (
71 BaseMultiModalProcessor,
@@ -1414,39 +1414,48 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
1414 def get_mrope_input_positions(
1415 self,
1416 input_tokens: list[int],
1417 - hf_config: PretrainedConfig,
1418 - image_grid_thw: list[list[int]] | torch.Tensor | None,
1419 - video_grid_thw: list[list[int]] | torch.Tensor | None,
1420 - second_per_grid_ts: list[float] | None = None,
1421 - audio_feature_lengths: torch.Tensor | None = None,
1422 - use_audio_in_video: bool = False,
1417 + mm_features: list[MultiModalFeatureSpec],
1418 ) -> tuple[torch.Tensor, int]:
1424 - config = hf_config.thinker_config
1425 - if isinstance(image_grid_thw, list):
1426 - image_grid_thw = torch.tensor(image_grid_thw)
1427 - if isinstance(video_grid_thw, list):
1428 - video_grid_thw = torch.tensor(video_grid_thw)
1419 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1420 + mm_features,
1421 + {
1422 + "image_grid_thw",
1423 + "video_grid_thw",
1424 + "second_per_grid_ts",
1425 + "audio_feature_lengths",
1426 + "use_audio_in_video",
1427 + },
1428 + )
1429 + image_grid_thw = kwargs.get("image_grid_thw", [])
1430 + video_grid_thw = kwargs.get("video_grid_thw", [])
1431 + second_per_grid_ts = kwargs.get("second_per_grid_ts", [])
1432 + audio_feature_lengths = kwargs.get("audio_feature_lengths", [])
1433 + use_audio_in_video = any(kwargs.get("use_audio_in_video", []))
1434 +
1435 + image_grid_thw = (torch.stack if image_grid_thw else torch.tensor)(
1436 + image_grid_thw
1437 + )
1438 + video_grid_thw = (torch.stack if video_grid_thw else torch.tensor)(
1439 + video_grid_thw
1440 + )
1441 +
1442 input_ids = torch.tensor(input_tokens)
1443 if input_ids is None or input_ids.ndim != 1:
1444 raise ValueError("_omni3_get_input_positions_tensor expects 1D input_ids")
1445
1446 seq_len = input_ids.shape[0]
1434 - if audio_feature_lengths is not None and not isinstance(
1435 - audio_feature_lengths, torch.Tensor
1436 - ):
1437 - audio_feature_lengths = torch.as_tensor(
1447 +
1448 + if isinstance(audio_feature_lengths, list):
1449 + audio_feature_lengths = torch.tensor(
1450 audio_feature_lengths, dtype=torch.long
1451 )
1440 - if second_per_grid_ts is None:
1441 - if video_grid_thw is not None and video_grid_thw.numel() > 0:
1442 - second_per_grids = torch.ones(
1443 - video_grid_thw.shape[0], dtype=torch.float32
1444 - )
1445 - else:
1446 - second_per_grids = torch.tensor([], dtype=torch.float32)
1452 +
1453 + if not len(second_per_grid_ts) and len(video_grid_thw):
1454 + second_per_grids = torch.ones(len(video_grid_thw), dtype=torch.float32)
1455 else:
1456 second_per_grids = torch.tensor(second_per_grid_ts, dtype=torch.float32)
1457
1458 + config = self.config
1459 spatial_merge_size = config.vision_config.spatial_merge_size
1460 image_token_id = config.image_token_id
1461 video_token_id = config.video_token_id
vllm/model_executor/models/qwen3_vl.py
+12 -18
@@ -34,7 +34,7 @@ import numpy as np
34 import torch
35 import torch.nn as nn
36 import torch.nn.functional as F
37 -from transformers import BatchFeature, PretrainedConfig
37 +from transformers import BatchFeature
38 from transformers.models.qwen2_vl import Qwen2VLImageProcessorFast
39 from transformers.models.qwen2_vl.image_processing_qwen2_vl import (
40 smart_resize as image_smart_resize,
@@ -70,6 +70,7 @@ from vllm.model_executor.models.module_mapping import MultiModelKeys
70 from vllm.multimodal import MULTIMODAL_REGISTRY
71 from vllm.multimodal.inputs import (
72 MultiModalDataDict,
73 + MultiModalFeatureSpec,
74 MultiModalFieldConfig,
75 MultiModalKwargsItem,
76 MultiModalKwargsItems,
@@ -1416,17 +1417,18 @@ class Qwen3VLForConditionalGeneration(
1417 def get_mrope_input_positions(
1418 self,
1419 input_tokens: list[int],
1419 - hf_config: PretrainedConfig,
1420 - image_grid_thw: list[list[int]] | torch.Tensor,
1421 - video_grid_thw: list[list[int]] | torch.Tensor,
1422 - second_per_grid_ts: list[float] | None = None,
1423 - audio_feature_lengths: torch.Tensor | None = None,
1424 - use_audio_in_video: bool = False,
1420 + mm_features: list[MultiModalFeatureSpec],
1421 ) -> tuple[torch.Tensor, int]:
1426 - """Get mrope input positions and delta value."""
1422 + kwargs = MultiModalFeatureSpec.gather_kwargs(
1423 + mm_features,
1424 + {"image_grid_thw", "video_grid_thw"},
1425 + )
1426 + image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
1427 + video_grid_thw = [item.tolist() for item in kwargs.get("video_grid_thw", [])]
1428
1429 video_grid_thw = [[1, h, w] for t, h, w in video_grid_thw for _ in range(t)]
1430
1431 + hf_config = self.config
1432 image_token_id = hf_config.image_token_id
1433 video_token_id = hf_config.video_token_id
1434 vision_start_token_id = hf_config.vision_start_token_id
@@ -1455,20 +1457,12 @@ class Qwen3VLForConditionalGeneration(
1457 else:
1458 ed_video = len(input_tokens) + 1
1459 if ed_image < ed_video:
1458 - t, h, w = (
1459 - image_grid_thw[image_index][0],
1460 - image_grid_thw[image_index][1],
1461 - image_grid_thw[image_index][2],
1462 - )
1460 + t, h, w = image_grid_thw[image_index]
1461 image_index += 1
1462 remain_images -= 1
1463 ed = ed_image
1464 else:
1467 - t, h, w = (
1468 - video_grid_thw[video_index][0],
1469 - video_grid_thw[video_index][1],
1470 - video_grid_thw[video_index][2],
1471 - )
1465 + t, h, w = video_grid_thw[video_index]
1466 video_index += 1
1467 remain_videos -= 1
1468 ed = ed_video
vllm/model_executor/models/transformers/multimodal.py
+27 -12
@@ -27,6 +27,7 @@ from vllm.model_executor.models.utils import WeightsMapper
27 from vllm.multimodal import MultiModalKwargsItems
28 from vllm.multimodal.inputs import (
29 MultiModalDataDict,
30 + MultiModalFeatureSpec,
31 MultiModalFieldConfig,
32 MultiModalInputs,
33 MultiModalUUIDDict,
@@ -38,7 +39,7 @@ from vllm.multimodal.profiling import BaseDummyInputsBuilder
39 from vllm.sequence import IntermediateTensors
40
41 if TYPE_CHECKING:
41 - from transformers import BatchFeature, PretrainedConfig
42 + from transformers import BatchFeature
43
44 from vllm.config import VllmConfig
45 from vllm.config.multimodal import BaseDummyOptions
@@ -367,20 +368,34 @@ class MultiModalMixin(SupportsMultiModal, SupportsMRoPE):
368 def get_mrope_input_positions(
369 self,
370 input_tokens: list[int],
370 - hf_config: "PretrainedConfig",
371 - image_grid_thw: list[list[int]] | torch.Tensor | None,
372 - video_grid_thw: list[list[int]] | torch.Tensor | None,
373 - second_per_grid_ts: list[float] | None = None,
374 - audio_feature_lengths: torch.Tensor | None = None,
375 - use_audio_in_video: bool = False,
371 + mm_features: list[MultiModalFeatureSpec],
372 ) -> tuple[torch.Tensor, int]:
377 - if any((second_per_grid_ts, audio_feature_lengths, use_audio_in_video)):
373 + kwargs = MultiModalFeatureSpec.gather_kwargs(
374 + mm_features,
375 + {
376 + "image_grid_thw",
377 + "video_grid_thw",
378 + "second_per_grid_ts",
379 + "audio_feature_lengths",
380 + "use_audio_in_video",
381 + },
382 + )
383 + if any(
384 + v
385 + for k, v in kwargs.items()
386 + if k not in {"image_grid_thw", "video_grid_thw"}
387 + ):
388 raise NotImplementedError("Transformers backend only supports images.")
389
380 - if isinstance(image_grid_thw, list):
381 - image_grid_thw = torch.tensor(image_grid_thw)
382 - if isinstance(video_grid_thw, list):
383 - video_grid_thw = torch.tensor(video_grid_thw)
390 + image_grid_thw = kwargs.get("image_grid_thw", [])
391 + video_grid_thw = kwargs.get("video_grid_thw", [])
392 +
393 + image_grid_thw = (torch.stack if image_grid_thw else torch.tensor)(
394 + image_grid_thw
395 + )
396 + video_grid_thw = (torch.stack if video_grid_thw else torch.tensor)(
397 + video_grid_thw
398 + )
399
400 mrope_positions, mrope_position_delta = self.model.get_rope_index(
401 input_ids=torch.tensor(input_tokens).unsqueeze(0),
vllm/multimodal/inputs.py
+13
@@ -249,6 +249,19 @@ class MultiModalFeatureSpec:
249 mm_position: PlaceholderRange
250 """e.g., PlaceholderRange(offset=2, length=336)"""
251
252 + @staticmethod
253 + def gather_kwargs(features: list["MultiModalFeatureSpec"], keys: set[str]):
254 + kwargs = defaultdict[str, list[NestedTensors]](list)
255 +
256 + for f in features:
257 + item = f.data
258 + if item is not None:
259 + for k in keys:
260 + if k in item:
261 + kwargs[k].append(item[k].data)
262 +
263 + return dict(kwargs)
264 +
265
266 @dataclass
267 class MultiModalFieldElem:
vllm/v1/worker/gpu_model_runner.py
+4 -29
@@ -892,38 +892,13 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
892 self.input_batch.num_accepted_tokens_cpu[i] = num_tokens
893
894 def _init_mrope_positions(self, req_state: CachedRequestState):
895 - image_grid_thw = []
896 - video_grid_thw = []
897 - second_per_grid_ts = []
898 - audio_feature_lengths = []
899 - use_audio_in_video = False
900 - for mm_feature in req_state.mm_features:
901 - mm_item = mm_feature.data
902 - if mm_item is None:
903 - continue
904 - mm_input = mm_item.get_data()
905 - if (t := mm_input.get("image_grid_thw")) is not None:
906 - image_grid_thw.append(t.tolist())
907 - if (t := mm_input.get("video_grid_thw")) is not None:
908 - video_grid_thw.append(t.tolist())
909 - if (t := mm_input.get("second_per_grid_ts")) is not None:
910 - second_per_grid_ts.append(t)
911 - if (t := mm_input.get("audio_feature_lengths")) is not None:
912 - audio_feature_lengths.append(t)
913 - if mm_input.get("use_audio_in_video") is True:
914 - use_audio_in_video = True
915 -
916 - assert supports_mrope(self.get_model()), "M-RoPE support is not implemented."
895 + model = self.get_model()
896 + assert supports_mrope(model), "M-RoPE support is not implemented."
897
898 req_state.mrope_positions, req_state.mrope_position_delta = (
919 - self.model.get_mrope_input_positions(
899 + model.get_mrope_input_positions(
900 req_state.prompt_token_ids,
921 - hf_config=self.model_config.hf_config,
922 - image_grid_thw=image_grid_thw,
923 - video_grid_thw=video_grid_thw,
924 - second_per_grid_ts=second_per_grid_ts,
925 - audio_feature_lengths=audio_feature_lengths,
926 - use_audio_in_video=use_audio_in_video,
901 + req_state.mm_features,
902 )
903 )
904