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 14, 05:30 UTC
[Model] Use merge_by_field_config for MM models (M-N) (#26710)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Oct 14, 2025 at 01:27 UTC
afc47e4de7a1bcce8ca8b7f767ee2994eba6a923
11 files changed
+126
-330
vllm/model_executor/models/interns1.py
+10
-4
@@ -631,8 +631,11 @@ class InternS1ForConditionalGeneration(
631
)
632
633
image_token_id = kwargs["image_token_id"]
634
- assert isinstance(image_token_id, torch.Tensor)
635
- self.img_context_token_id = image_token_id.flatten().unique().item()
634
+ if isinstance(image_token_id, torch.Tensor):
635
+ image_token_id = image_token_id.flatten().unique().item()
636
+
637
+ assert isinstance(image_token_id, int)
638
+ self.img_context_token_id = image_token_id
639
640
if pixel_values is not None:
641
h, w = self.config.vision_config.image_size
@@ -665,8 +668,11 @@ class InternS1ForConditionalGeneration(
668
)
669
670
video_token_id = kwargs["video_token_id"]
668
- assert isinstance(video_token_id, torch.Tensor)
669
- self.video_context_token_id = video_token_id.flatten().unique().item()
671
+ if isinstance(video_token_id, torch.Tensor):
672
+ video_token_id = video_token_id.flatten().unique().item()
673
+
674
+ assert isinstance(video_token_id, int)
675
+ self.video_context_token_id = video_token_id
676
677
if pixel_values_flat_video is not None:
678
h, w = self.config.vision_config.image_size
vllm/model_executor/models/internvl.py
+10
-4
@@ -1232,8 +1232,11 @@ class InternVLChatModel(nn.Module, SupportsMultiModal, SupportsPP, SupportsLoRA)
1232
)
1233
1234
image_token_id = kwargs["image_token_id"]
1235
- assert isinstance(image_token_id, torch.Tensor)
1236
- self.img_context_token_id = image_token_id.flatten().unique().item()
1235
+ if isinstance(image_token_id, torch.Tensor):
1236
+ image_token_id = image_token_id.flatten().unique().item()
1237
+
1238
+ assert isinstance(image_token_id, int)
1239
+ self.img_context_token_id = image_token_id
1240
1241
if pixel_values_flat is not None:
1242
expected_h = expected_w = self.config.vision_config.image_size
@@ -1265,8 +1268,11 @@ class InternVLChatModel(nn.Module, SupportsMultiModal, SupportsPP, SupportsLoRA)
1268
)
1269
1270
video_token_id = kwargs["video_token_id"]
1268
- assert isinstance(video_token_id, torch.Tensor)
1269
- self.video_context_token_id = video_token_id.flatten().unique().item()
1271
+ if isinstance(video_token_id, torch.Tensor):
1272
+ video_token_id = video_token_id.flatten().unique().item()
1273
+
1274
+ assert isinstance(video_token_id, int)
1275
+ self.video_context_token_id = video_token_id
1276
1277
if pixel_values_flat_video is not None:
1278
expected_h = expected_w = self.config.vision_config.image_size
vllm/model_executor/models/midashenglm.py
+27
-53
@@ -26,7 +26,7 @@
26
import collections
27
import collections.abc
28
from collections.abc import Callable, Iterable, Mapping, Sequence
29
-from typing import Any, TypeAlias, TypedDict, cast
29
+from typing import Annotated, Any, TypeAlias, cast
30
31
import numpy as np
32
import torch
@@ -62,6 +62,7 @@ from vllm.multimodal.processing import (
62
from vllm.multimodal.profiling import BaseDummyInputsBuilder
63
from vllm.sequence import IntermediateTensors
64
from vllm.transformers_utils.configs.midashenglm import DashengConfig
65
+from vllm.utils.tensor_schema import TensorSchema, TensorShape
66
67
from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
68
from .utils import AutoWeightsLoader, init_vllm_registered_model, maybe_prefix
@@ -508,11 +509,16 @@ class AudioProjectorSubsample(nn.Module):
509
510
511
# === Audio Inputs === #
511
-class MiDashengLMAudioInputs(TypedDict):
512
- input_values: torch.Tensor
513
- """Shape: `(num_audios, num_sampling_points)`"""
514
- audio_length: torch.Tensor
515
- """Shape: `(num_audios, 1)`"""
512
+class MiDashengLMAudioInputs(TensorSchema):
513
+ """
514
+
515
+ Dimensions:
516
+ - bn: Batch size * number of audios
517
+ - p: Number of sampling points
518
+ """
519
+
520
+ input_values: Annotated[torch.Tensor, TensorShape("n", "p")]
521
+ audio_length: Annotated[torch.Tensor, TensorShape("n")]
522
523
524
class MiDashengLMProcessingInfo(BaseProcessingInfo):
@@ -676,6 +682,8 @@ class MiDashengLMMultiModalProcessor(
682
dummy_inputs=MiDashengLMDummyInputsBuilder,
683
)
684
class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
685
+ merge_by_field_config = True
686
+
687
packed_modules_mapping = {
688
"qkv_proj": [
689
"q_proj",
@@ -728,26 +736,6 @@ class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
736
self.decoder.make_empty_intermediate_tensors
737
)
738
731
- def _validate_and_reshape_mm_tensor(
732
- self, mm_input: object, name: str
733
- ) -> torch.Tensor:
734
- if not isinstance(mm_input, (torch.Tensor, list)):
735
- raise ValueError(f"Incorrect type of {name}. Got type: {type(mm_input)}")
736
- if isinstance(mm_input, torch.Tensor):
737
- return mm_input.reshape(-1, *mm_input.shape[2:])
738
-
739
- if name == "input_values":
740
- max_length = max(tensor.shape[1] for tensor in mm_input)
741
- padded_mm_input = [
742
- torch.nn.functional.pad(tensor, (0, max_length - tensor.shape[1]))
743
- if tensor.shape[1] < max_length
744
- else tensor
745
- for tensor in mm_input
746
- ]
747
- return torch.concat(padded_mm_input)
748
-
749
- return torch.concat(mm_input)
750
-
739
def _parse_and_validate_audio_input(
740
self, **kwargs: object
741
) -> MiDashengLMAudioInputs | None:
@@ -756,16 +744,11 @@ class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
744
745
if input_values is None:
746
return None
759
- input_values = self._validate_and_reshape_mm_tensor(
760
- input_values, "input_values"
761
- )
762
- audio_length = self._validate_and_reshape_mm_tensor(
763
- audio_length, "audio_length"
764
- )
765
- if not isinstance(input_values, (torch.Tensor, list)):
766
- raise ValueError(
767
- "Incorrect type of audio input features. "
768
- f"Got type: {type(input_values)}"
747
+
748
+ if isinstance(input_values, list):
749
+ input_values = torch.nn.utils.rnn.pad_sequence(
750
+ input_values,
751
+ batch_first=True,
752
)
753
754
return MiDashengLMAudioInputs(
@@ -773,7 +756,10 @@ class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
756
audio_length=audio_length,
757
)
758
776
- def _process_audio_input(self, audio_input: MiDashengLMAudioInputs) -> torch.Tensor:
759
+ def _process_audio_input(
760
+ self,
761
+ audio_input: MiDashengLMAudioInputs,
762
+ ) -> tuple[torch.Tensor, ...]:
763
# Process audio through encoder and projector
764
input_values = audio_input["input_values"]
765
audio_length = audio_input["audio_length"]
@@ -783,17 +769,13 @@ class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
769
audio_embeddings = audio_embeddings.to(audio_input["input_values"].dtype)
770
batch_size, max_audio_tokens, embed_dim = audio_embeddings.shape
771
786
- audio_length_np = (
787
- audio_length.cpu().numpy()
788
- if isinstance(audio_length, torch.Tensor)
789
- else audio_length
790
- )
772
audio_output_lengths = [
773
max(1, calculate_mel_frames_dasheng(int(length))) # at least one frame
793
- for length in audio_length_np
774
+ for length in audio_length.tolist()
775
]
795
- audio_output_lengths = torch.tensor(audio_output_lengths).to(
796
- audio_embeddings.device
776
+ audio_output_lengths = torch.tensor(
777
+ audio_output_lengths,
778
+ device=audio_embeddings.device,
779
)
780
781
audio_feature_mask = torch.arange(
@@ -826,14 +808,6 @@ class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
808
) -> torch.Tensor | IntermediateTensors:
809
if intermediate_tensors is not None:
810
inputs_embeds = None
829
- elif inputs_embeds is None:
830
- multimodal_embeddings = self.get_multimodal_embeddings(**kwargs)
831
- inputs_embeds = self.get_input_embeddings(
832
- input_ids,
833
- multimodal_embeddings,
834
- is_multimodal=input_ids == self.config.audio_token_id,
835
- )
836
- input_ids = None
811
812
return self.decoder.model(
813
input_ids,
vllm/model_executor/models/minicpmo.py
+5
-42
@@ -71,7 +71,7 @@ from .minicpmv import (
71
MiniCPMVProcessingInfo,
72
_minicpmv_field_config,
73
)
74
-from .utils import AutoWeightsLoader, cast_overflow_tensors, flatten_bn, maybe_prefix
74
+from .utils import AutoWeightsLoader, cast_overflow_tensors, maybe_prefix
75
76
CPU_DEVICE = torch.device("cpu")
77
@@ -132,15 +132,11 @@ MiniCPMOAudioInputs: TypeAlias = (
132
133
134
def _minicpmo_field_config(hf_inputs: Mapping[str, torch.Tensor]):
135
- audio_features = hf_inputs.get("audio_features", torch.empty(0))
136
- num_audios = len(audio_features)
137
-
135
return dict(
136
**_minicpmv_field_config(hf_inputs),
137
audio_features=MultiModalFieldConfig.batched("audio"),
138
audio_feature_lens=MultiModalFieldConfig.batched("audio"),
139
audio_embeds=MultiModalFieldConfig.batched("audio"),
143
- audio_token_id=MultiModalFieldConfig.shared("audio", num_audios),
140
)
141
142
@@ -332,10 +328,6 @@ class MiniCPMOMultiModalProcessor(MiniCPMVMultiModalProcessor[MiniCPMOProcessing
328
]
329
audio_inputs["audio_features"] = unpadded_audio_features
330
335
- tokenizer = self.info.get_tokenizer()
336
- unk_token_id = tokenizer.get_vocab()["<unk>"]
337
- audio_inputs["audio_token_id"] = torch.tensor(unk_token_id)
338
-
331
return audio_inputs
332
333
def process_mm_inputs(
@@ -436,12 +428,10 @@ class MiniCPMWhisperEncoderLayer(nn.Module):
428
attention_mask: torch.Tensor,
429
) -> torch.Tensor:
430
residual = hidden_states
439
- past_key_values = None
431
hidden_states = self.self_attn_layer_norm(hidden_states)
441
- hidden_states, attn_weights, past_key_values = self.self_attn(
432
+ hidden_states, _ = self.self_attn(
433
hidden_states=hidden_states,
434
attention_mask=attention_mask,
444
- past_key_value=past_key_values,
435
)
436
hidden_states = nn.functional.dropout(
437
hidden_states, p=self.dropout, training=self.training
@@ -567,8 +557,6 @@ class MiniCPMO(MiniCPMV2_6):
557
vllm_config=vllm_config, prefix=maybe_prefix(prefix, "apm")
558
)
559
570
- self.audio_token_id = None
571
-
560
def init_audio_module(self, *, vllm_config: VllmConfig, prefix: str = ""):
561
# Do not use parameters temporarily
562
audio_config = self.config.audio_config
@@ -731,43 +719,18 @@ class MiniCPMO(MiniCPMV2_6):
719
if audio_features is None and audio_embeds is None:
720
return None
721
734
- audio_token_id = kwargs.pop("audio_token_id")
735
- if audio_token_id is not None:
736
- assert isinstance(audio_token_id, torch.Tensor)
737
- self.mm_token_ids.add(audio_token_id.flatten().unique().item())
738
-
722
if audio_embeds is not None:
740
- if not isinstance(audio_embeds, (torch.Tensor, list)):
741
- raise ValueError(
742
- f"Incorrect type of audio_embeds. Got type: {type(audio_embeds)}"
743
- )
744
-
745
- audio_embeds_flat = flatten_bn(audio_embeds)
746
-
723
return MiniCPMOAudioEmbeddingInputs(
724
type="audio_embeds",
749
- audio_embeds=audio_embeds_flat,
750
- )
751
-
752
- if not isinstance(audio_features, (torch.Tensor, list)):
753
- raise ValueError(
754
- f"Incorrect type of audio_features. Got type: {type(audio_features)}"
725
+ audio_embeds=audio_embeds,
726
)
727
728
audio_feature_lens = kwargs.pop("audio_feature_lens")
758
- if not isinstance(audio_feature_lens, (torch.Tensor, list)):
759
- raise ValueError(
760
- "Incorrect type of audio_feature_lens. "
761
- f"Got type: {type(audio_feature_lens)}"
762
- )
763
-
764
- audio_features_flat = flatten_bn(audio_features)
765
- audio_feature_lens_flat = flatten_bn(audio_feature_lens)
729
730
return MiniCPMOAudioFeatureInputs(
731
type="audio_features",
769
- audio_features=audio_features_flat,
770
- audio_feature_lens=audio_feature_lens_flat,
732
+ audio_features=audio_features,
733
+ audio_feature_lens=audio_feature_lens,
734
)
735
736
def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
vllm/model_executor/models/minicpmv.py
+8
-56
@@ -114,7 +114,7 @@ class MiniCPMVImagePixelInputs(TensorSchema):
114
115
type: Literal["pixel_values"] = "pixel_values"
116
117
- # Note that the image size may vary, so we pass it as a list instead of a
117
+ # Note that the patch size may vary, so we pass it as a list instead of a
118
# batched tensor.
119
pixel_values: Annotated[
120
list[torch.Tensor],
@@ -453,12 +453,6 @@ def get_version_by_config(config: PretrainedConfig) -> tuple[int, ...]:
453
454
455
def _minicpmv_field_config(hf_inputs: Mapping[str, torch.Tensor]):
456
- pixel_values = hf_inputs.get("pixel_values", torch.empty(0))
457
- num_images = len(pixel_values)
458
-
459
- video_pixel_values = hf_inputs.get("video_pixel_values", torch.empty(0))
460
- num_videos = len(video_pixel_values)
461
-
456
return dict(
457
pixel_values=MultiModalFieldConfig.batched("image"),
458
image_sizes=MultiModalFieldConfig.batched("image"),
@@ -468,8 +462,6 @@ def _minicpmv_field_config(hf_inputs: Mapping[str, torch.Tensor]):
462
video_image_sizes=MultiModalFieldConfig.batched("video"),
463
video_tgt_sizes=MultiModalFieldConfig.batched("video"),
464
video_embeds=MultiModalFieldConfig.batched("video"),
471
- image_token_id=MultiModalFieldConfig.shared("image", num_images),
472
- video_token_id=MultiModalFieldConfig.shared("video", num_videos),
465
)
466
467
@@ -792,10 +784,6 @@ class MiniCPMVMultiModalProcessor(BaseMultiModalProcessor[_I]):
784
out_keys={"pixel_values", "image_sizes", "tgt_sizes"},
785
)
786
795
- tokenizer = self.info.get_tokenizer()
796
- unk_token_id = tokenizer.get_vocab()["<unk>"]
797
- image_inputs["image_token_id"] = torch.tensor(unk_token_id)
798
-
787
return image_inputs
788
789
def process_videos(
@@ -831,10 +819,6 @@ class MiniCPMVMultiModalProcessor(BaseMultiModalProcessor[_I]):
819
820
video_inputs = {f"video_{k}": v for k, v in video_inputs.items()}
821
834
- tokenizer = self.info.get_tokenizer()
835
- unk_token_id = tokenizer.get_vocab()["<unk>"]
836
- video_inputs["video_token_id"] = torch.tensor(unk_token_id)
837
-
822
return video_inputs
823
824
def process_mm_inputs(
@@ -1021,6 +1005,8 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1005
instantiated.
1006
"""
1007
1008
+ merge_by_field_config = True
1009
+
1010
supports_encoder_tp_data = True
1011
1012
@classmethod
@@ -1066,7 +1052,6 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1052
prefix=maybe_prefix(prefix, "resampler"),
1053
)
1054
1069
- self.mm_token_ids = set[int]()
1055
self.make_empty_intermediate_tensors = self.llm.make_empty_intermediate_tensors
1056
1057
def _parse_and_validate_vision_input(
@@ -1080,43 +1065,17 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1065
if pixel_values is None and image_embeds is None:
1066
return None
1067
1083
- image_token_id = kwargs.pop("image_token_id")
1084
- if image_token_id is not None:
1085
- assert isinstance(image_token_id, torch.Tensor)
1086
- self.mm_token_ids.add(image_token_id.flatten().unique().item())
1087
-
1068
if image_embeds is not None:
1089
- if not isinstance(image_embeds, (torch.Tensor, list)):
1090
- raise ValueError(
1091
- f"Incorrect type of image_embeds for {modality=}. "
1092
- f"Got type: {type(image_embeds)}"
1093
- )
1094
-
1095
- image_embeds_flat = flatten_bn(image_embeds)
1096
-
1069
return MiniCPMVImageEmbeddingInputs(
1070
type="image_embeds",
1099
- image_embeds=image_embeds_flat,
1100
- )
1101
-
1102
- if not isinstance(pixel_values, (torch.Tensor, list)):
1103
- raise ValueError(
1104
- f"Incorrect type of pixel_values for {modality=}. "
1105
- f"Got type: {type(pixel_values)}"
1071
+ image_embeds=image_embeds,
1072
)
1073
1074
tgt_sizes = kwargs.pop("tgt_sizes")
1109
- if not isinstance(tgt_sizes, (torch.Tensor, list)):
1110
- raise ValueError(
1111
- f"Incorrect type of tgt_sizes for {modality=}. "
1112
- f"Got type: {type(tgt_sizes)}"
1113
- )
1114
-
1115
- num_slices = [[len(p) for p in ps] for ps in pixel_values]
1116
- num_slices_flat = flatten_bn(torch.tensor(num_slices))
1075
1118
- pixel_values_flat = flatten_bn(flatten_2d_lists(pixel_values))
1119
- tgt_sizes_flat = flatten_bn(flatten_2d_lists(tgt_sizes), concat=True)
1076
+ num_slices_flat = torch.tensor([len(ps) for ps in pixel_values])
1077
+ pixel_values_flat = flatten_bn(pixel_values)
1078
+ tgt_sizes_flat = flatten_bn(tgt_sizes, concat=True)
1079
1080
return MiniCPMVImagePixelInputs(
1081
type="pixel_values",
@@ -1142,15 +1101,8 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
1101
input_key in ("video_pixel_values", "video_embeds")
1102
and "videos" not in modalities
1103
):
1145
-
1146
- def _image_key(video_key: str):
1147
- if video_key == "video_token_id":
1148
- return "image_token_id"
1149
-
1150
- return video_key.removeprefix("video_")
1151
-
1104
modalities["videos"] = self._parse_and_validate_vision_input(
1153
- "videos", **{_image_key(k): v for k, v in kwargs.items()}
1105
+ "videos", **{k.removeprefix("video_"): v for k, v in kwargs.items()}
1106
)
1107
1108
return modalities
vllm/model_executor/models/mllama4.py
+10
-13
@@ -71,7 +71,7 @@ from .interfaces import (
71
SupportsPP,
72
)
73
from .llama4 import Llama4ForCausalLM
74
-from .utils import AutoWeightsLoader, flatten_bn, maybe_prefix
74
+from .utils import AutoWeightsLoader, maybe_prefix
75
from .vision import run_dp_sharded_vision_model
76
77
@@ -86,7 +86,7 @@ class Llama4ImagePatchInputs(TensorSchema):
86
87
type: Literal["pixel_values"] = "pixel_values"
88
89
- flat_data: Annotated[
89
+ pixel_values: Annotated[
90
torch.Tensor,
91
TensorShape("total_num_chunks", "num_channels", "image_size", "image_size"),
92
]
@@ -96,7 +96,7 @@ class Llama4ImagePatchInputs(TensorSchema):
96
The number of total patches for each image in the batch.
97
98
This is used to split the embeddings which has the first two dimensions
99
- flattened just like `flat_data`.
99
+ flattened just like `pixel_values`.
100
"""
101
102
aspect_ratios: Annotated[torch.Tensor, TensorShape("batch_size", 2)]
@@ -725,6 +725,8 @@ class Mllama4DummyInputsBuilder(BaseDummyInputsBuilder[Mllama4ProcessingInfo]):
725
class Llama4ForConditionalGeneration(
726
nn.Module, SupportsMultiModal, SupportsPP, SupportsEagle3
727
):
728
+ merge_by_field_config = True
729
+
730
packed_modules_mapping = {
731
"qkv_proj": ["q_proj", "k_proj", "v_proj"],
732
"gate_up_proj": ["gate_proj", "up_proj"],
@@ -798,17 +800,12 @@ class Llama4ForConditionalGeneration(
800
if pixel_values is None:
801
return None
802
801
- # num_images x num_chunks, channel, image_size, image_size
802
- # TODO: confirm handling for variable lengths
803
- flat_pixel_values = flatten_bn(pixel_values, concat=True)
804
- patches_per_image = flatten_bn(kwargs.pop("patches_per_image"))
803
+ patches_per_image = kwargs.pop("patches_per_image")
804
aspect_ratios = kwargs.pop("aspect_ratios")
806
- if aspect_ratios.ndim == 3:
807
- aspect_ratios = aspect_ratios.squeeze(1)
805
806
return Llama4ImagePatchInputs(
807
type="pixel_values",
811
- flat_data=flat_pixel_values,
808
+ pixel_values=pixel_values,
809
patches_per_image=patches_per_image,
810
aspect_ratios=aspect_ratios,
811
)
@@ -817,16 +814,16 @@ class Llama4ForConditionalGeneration(
814
self, image_input: Llama4ImagePatchInputs
815
) -> MultiModalEmbeddings:
816
assert self.vision_model and self.multi_modal_projector
820
- flat_data = image_input["flat_data"]
817
+ pixel_values = image_input["pixel_values"]
818
patches_per_image = image_input["patches_per_image"].tolist()
819
820
# shard image input
821
if self.use_data_parallel:
822
vision_embeddings_flat = run_dp_sharded_vision_model(
826
- flat_data, self.vision_model
823
+ pixel_values, self.vision_model
824
)
825
else:
829
- vision_embeddings_flat = self.vision_model(flat_data)
826
+ vision_embeddings_flat = self.vision_model(pixel_values)
827
828
vision_embeddings_flat = self.multi_modal_projector(vision_embeddings_flat)
829
vllm/model_executor/models/molmo.py
+18
-40
@@ -75,7 +75,6 @@ from .interfaces import (
75
from .utils import (
76
AutoWeightsLoader,
77
WeightsMapper,
78
- flatten_bn,
78
is_pp_missing_parameter,
79
make_empty_intermediate_tensors_factory,
80
make_layers,
@@ -97,28 +96,19 @@ class MolmoImageInputs(TensorSchema):
96
"""
97
Dimensions:
98
- bn: Batch size * number of images
100
- - nc: Number of crops (dynamic)
99
+ - bnc: Batch size * number of images * number of crops (dynamic)
100
- np: Number of patches
101
- tp: Token sequence positions
102
- pd: Patch dimension
103
"""
104
106
- images: Annotated[
107
- torch.Tensor | list[torch.Tensor],
108
- TensorShape("bn", "nc", "np", "pd", dynamic_dims={"nc"}),
109
- ]
110
- # Number of crops may vary per batch and image, so pass it as a list.
105
+ images: Annotated[torch.Tensor, TensorShape("bnc", "np", "pd")]
106
112
- image_masks: Annotated[
113
- torch.Tensor | list[torch.Tensor] | None,
114
- TensorShape("bn", "nc", "np", dynamic_dims={"nc"}),
115
- ]
107
+ image_masks: Annotated[torch.Tensor | None, TensorShape("bnc", "np")]
108
+
109
+ image_input_idx: Annotated[torch.Tensor, TensorShape("bnc", "tp")]
110
+ """An index tensor that maps image features to their corresponding patch tokens."""
111
117
- image_input_idx: Annotated[
118
- torch.Tensor | list[torch.Tensor],
119
- TensorShape("bn", "nc", "tp", dynamic_dims={"nc"}),
120
- ]
121
- # An index tensor that maps image features to their corresponding patch tokens.
112
num_crops: Annotated[torch.Tensor, TensorShape("bn")]
113
114
@@ -1363,6 +1353,8 @@ class MolmoMultiModalProcessor(BaseMultiModalProcessor[MolmoProcessingInfo]):
1353
class MolmoForCausalLM(
1354
nn.Module, SupportsMultiModal, SupportsPP, SupportsLoRA, SupportsQuant
1355
):
1356
+ merge_by_field_config = True
1357
+
1358
hf_to_vllm_mapper = WeightsMapper(
1359
orig_to_new_substr={
1360
# vision backbone mapping
@@ -1451,18 +1443,12 @@ class MolmoForCausalLM(
1443
if images is None:
1444
return None
1445
1454
- if not isinstance(num_crops, (torch.Tensor, list)):
1455
- raise ValueError(
1456
- f"Incorrect type of num_crops. Got type: {type(num_crops)}"
1457
- )
1458
- num_crops = flatten_bn(num_crops, concat=True)
1459
-
1446
img_patch_id = kwargs.pop("img_patch_id", None)
1461
- if not isinstance(img_patch_id, torch.Tensor):
1462
- raise ValueError(
1463
- f"Incorrect type of img_patch_id. Got type: {type(img_patch_id)}"
1464
- )
1465
- self.img_patch_id = img_patch_id.flatten().unique().item()
1447
+ if isinstance(img_patch_id, torch.Tensor):
1448
+ img_patch_id = img_patch_id.item()
1449
+
1450
+ assert isinstance(img_patch_id, int)
1451
+ self.img_patch_id = img_patch_id
1452
1453
return MolmoImageInputs(
1454
images=images,
@@ -1481,17 +1467,9 @@ class MolmoForCausalLM(
1467
num_crops = image_input["num_crops"]
1468
1469
# Call the vision backbone on the whole batch at once
1484
- images_flat = flatten_bn(images, concat=True)
1485
- image_masks_flat = (
1486
- None if image_masks is None else flatten_bn(image_masks, concat=True)
1487
- )
1488
- image_input_idx_flat = flatten_bn(image_input_idx, concat=True)
1489
-
1490
- image_features_flat = self.vision_backbone(
1491
- images=images_flat.unsqueeze(0),
1492
- image_masks=(
1493
- None if image_masks_flat is None else image_masks_flat.unsqueeze(0)
1494
- ),
1470
+ image_features = self.vision_backbone(
1471
+ images=images.unsqueeze(0),
1472
+ image_masks=None if image_masks is None else image_masks.unsqueeze(0),
1473
).squeeze(0)
1474
1475
# Only the features corresponding to patch tokens are relevant
@@ -1499,8 +1477,8 @@ class MolmoForCausalLM(
1477
results = []
1478
num_crops_list = num_crops.tolist()
1479
for feats, img_idx in zip(
1502
- image_features_flat.split(num_crops_list),
1503
- image_input_idx_flat.split(num_crops_list),
1480
+ image_features.split(num_crops_list),
1481
+ image_input_idx.split(num_crops_list),
1482
):
1483
is_valid = img_idx >= 0
1484
valid_img_idx = img_idx[is_valid]
vllm/model_executor/models/nano_nemotron_vl.py
+27
-113
@@ -11,7 +11,7 @@ import copy
11
import warnings
12
from abc import ABC, abstractmethod
13
from collections.abc import Iterable, Mapping, Sequence
14
-from typing import Annotated, Any, Literal, TypeAlias, TypedDict, TypeVar
14
+from typing import Annotated, Any, Literal, TypeAlias, TypeVar
15
16
import numpy.typing as npt
17
import torch
@@ -40,7 +40,6 @@ from vllm.model_executor.models.module_mapping import MultiModelKeys
40
from vllm.model_executor.models.nemotron_h import NemotronHForCausalLM
41
from vllm.model_executor.models.radio import RadioModel
42
from vllm.model_executor.models.utils import (
43
- flatten_bn,
43
init_vllm_registered_model,
44
maybe_prefix,
45
)
@@ -96,31 +95,35 @@ MAX_FRAMES = 16
95
DEFAULT_NUM_TILES = 12
96
97
99
-class NanoNemotronVLImagePixelInputs(TypedDict):
100
- type: Literal["pixel_values"]
101
- pixel_values_flat: torch.Tensor
98
+class NanoNemotronVLImagePixelInputs(TensorSchema):
99
"""
103
- Shape:
104
- `(batch_size * num_images * (1 + num_patches), num_channels, height, width)`
100
+ Dimensions:
101
+ - bn: Batch size * number of images
102
+ - bnp: Batch size * number of images * (1 + num_patches)
103
+ - c: Number of channels (3)
104
+ - h: Height of each image patch
105
+ - w: Width of each image patch
106
"""
107
107
- num_patches: torch.Tensor
108
- """Shape: `(batch_size * num_images)`"""
109
-
108
+ type: Literal["pixel_values"]
109
+ pixel_values_flat: Annotated[torch.Tensor, TensorShape("bnp", 3, "h", "w")]
110
+ num_patches: Annotated[torch.Tensor, TensorShape("bn")]
111
111
-class NanoNemotronVLImageEmbeddinInputs(TypedDict):
112
- type: Literal["image_embeds"]
113
- data: torch.Tensor | list[torch.Tensor]
114
- """
115
- A tensor of shape `(num_images, total_image_feature_size, hidden_size)`
116
- or a list of tensors of shape `(total_image_feature_size, hidden_size)`
112
118
- `hidden_size` must match the hidden size of language model backbone.
113
+class NanoNemotronVLImageEmbeddingInputs(TensorSchema):
114
+ """
115
+ Dimensions:
116
+ - n: Number of images
117
+ - f: Total image feature size
118
+ - h: Hidden size (must match the hidden size of language model backbone)
119
"""
120
121
+ type: Literal["image_embeds"]
122
+ data: Annotated[torch.Tensor | list[torch.Tensor], TensorShape("n", "f", "h")]
123
+
124
125
NanoNemotronVLImageInputs: TypeAlias = (
123
- NanoNemotronVLImagePixelInputs | NanoNemotronVLImageEmbeddinInputs
126
+ NanoNemotronVLImagePixelInputs | NanoNemotronVLImageEmbeddingInputs
127
)
128
129
@@ -710,37 +713,12 @@ class NanoNemotronVLProcessingInfo(BaseNanoNemotronVLProcessingInfo):
713
class NanoNemotronBaseVLMultiModalProcessor(BaseMultiModalProcessor[_I]):
714
"""Basic image-only MultiModalProcessor for InternVL-style models."""
715
713
- def _call_hf_processor(
714
- self,
715
- prompt: str,
716
- mm_data: Mapping[str, object],
717
- mm_kwargs: Mapping[str, object],
718
- tok_kwargs: Mapping[str, object],
719
- ) -> BatchFeature:
720
- processed_outputs = super()._call_hf_processor(
721
- prompt=prompt,
722
- mm_data=mm_data,
723
- mm_kwargs=mm_kwargs,
724
- tok_kwargs=tok_kwargs,
725
- )
726
-
727
- hf_processor = self.info.get_hf_processor(**mm_kwargs)
728
- image_token_id = hf_processor.image_token_id
729
-
730
- # Since there may be extra tokens in the feature placeholders,
731
- # we need to pass the image token ID to the model to select the
732
- # tokens to merge from the vision encoder outputs
733
- processed_outputs["image_token_id"] = torch.tensor(image_token_id)
734
-
735
- return processed_outputs
736
-
716
def _get_mm_fields_config(
717
self,
718
hf_inputs: BatchFeature,
719
hf_processor_mm_kwargs: Mapping[str, object],
720
) -> Mapping[str, MultiModalFieldConfig]:
721
image_num_patches = hf_inputs.get("image_num_patches", torch.empty(0))
743
- num_images = len(image_num_patches)
722
723
return dict(
724
pixel_values_flat=MultiModalFieldConfig.flat_from_sizes(
@@ -748,7 +726,6 @@ class NanoNemotronBaseVLMultiModalProcessor(BaseMultiModalProcessor[_I]):
726
),
727
image_num_patches=MultiModalFieldConfig.batched("image"),
728
image_embeds=MultiModalFieldConfig.batched("image"),
751
- image_token_id=MultiModalFieldConfig.shared("image", num_images),
729
)
730
731
def _get_prompt_updates(
@@ -814,25 +791,6 @@ class NanoNemotronVLMultiModalProcessor(
791
):
792
"""MultiModalProcessor extended for video support"""
793
817
- def _call_hf_processor(
818
- self,
819
- prompt: str,
820
- mm_data: Mapping[str, object],
821
- mm_kwargs: Mapping[str, object],
822
- tok_kwargs: Mapping[str, object],
823
- ) -> BatchFeature:
824
- processed_outputs = super()._call_hf_processor(
825
- prompt, mm_data, mm_kwargs, tok_kwargs
826
- )
827
-
828
- hf_processor = self.info.get_hf_processor(**mm_kwargs)
829
- if (
830
- self.info.supports_video
831
- and (video_token_id := hf_processor.video_token_id) is not None
832
- ):
833
- processed_outputs["video_token_id"] = torch.tensor(video_token_id)
834
- return processed_outputs
835
-
794
def _get_mm_fields_config(
795
self,
796
hf_inputs: BatchFeature,
@@ -841,13 +799,12 @@ class NanoNemotronVLMultiModalProcessor(
799
image_fields = super()._get_mm_fields_config(hf_inputs, hf_processor_mm_kwargs)
800
if self.info.supports_video:
801
video_num_patches = hf_inputs.get("video_num_patches", torch.empty(0))
844
- num_videos = len(video_num_patches)
802
+
803
video_fields = dict(
804
pixel_values_flat_video=MultiModalFieldConfig.flat_from_sizes(
805
"video", video_num_patches
806
),
807
video_num_patches=MultiModalFieldConfig.batched("video"),
850
- video_token_id=MultiModalFieldConfig.shared("video", num_videos),
808
)
809
else:
810
video_fields = {}
@@ -999,6 +956,8 @@ class NanoNemotronVLDummyInputsBuilder(
956
class NemotronH_Nano_VL_V2(
957
nn.Module, HasInnerState, IsHybrid, SupportsMultiModal, SupportsMultiModalPruning
958
):
959
+ merge_by_field_config = True
960
+
961
@classmethod
962
def get_placeholder_str(cls, modality: str, i: int) -> str | None:
963
if modality.startswith("image"):
@@ -1051,8 +1010,6 @@ class NemotronH_Nano_VL_V2(
1010
)
1011
self.mlp1 = self.mlp1.to(self.language_model.config.torch_dtype)
1012
1054
- self.img_context_token_id = None
1055
- self.video_context_token_id = None
1013
self.config = config
1014
self.model_config = vllm_config.model_config
1015
@@ -1106,37 +1063,12 @@ class NemotronH_Nano_VL_V2(
1063
return None
1064
1065
if image_embeds is not None:
1109
- if not isinstance(image_embeds, (torch.Tensor, list)):
1110
- raise ValueError(
1111
- "Incorrect type of image embeddings. "
1112
- f"Got type: {type(image_embeds)}"
1113
- )
1114
-
1115
- return NanoNemotronVLImageEmbeddinInputs(
1066
+ return NanoNemotronVLImageEmbeddingInputs(
1067
type="image_embeds",
1117
- data=flatten_bn(image_embeds),
1068
+ data=image_embeds,
1069
)
1070
1120
- image_token_id = kwargs["image_token_id"]
1121
- assert isinstance(image_token_id, torch.Tensor)
1122
- self.img_context_token_id = image_token_id.flatten().unique().item()
1123
-
1071
if pixel_values_flat is not None:
1125
- if not isinstance(pixel_values_flat, (torch.Tensor, list)):
1126
- raise ValueError(
1127
- "Incorrect type of pixel values. "
1128
- f"Got type: {type(pixel_values_flat)}"
1129
- )
1130
-
1131
- if not isinstance(image_num_patches, (torch.Tensor, list)):
1132
- raise ValueError(
1133
- "Incorrect type of image_num_patches. "
1134
- f"Got type: {type(image_num_patches)}"
1135
- )
1136
-
1137
- pixel_values_flat = flatten_bn(pixel_values_flat, concat=True)
1138
- image_num_patches = flatten_bn(image_num_patches, concat=True)
1139
-
1072
return NanoNemotronVLImagePixelInputs(
1073
type="pixel_values",
1074
pixel_values_flat=pixel_values_flat,
@@ -1285,28 +1217,10 @@ class NemotronH_Nano_VL_V2(
1217
if video_embeds is not None:
1218
return NanoNemotronVLVideoEmbeddingInputs(
1219
type="video_embeds",
1288
- data=flatten_bn(video_embeds),
1220
+ data=video_embeds,
1221
)
1222
1291
- video_token_id = kwargs["video_token_id"]
1292
- assert isinstance(video_token_id, torch.Tensor)
1293
- self.video_context_token_id = video_token_id.flatten().unique().item()
1294
-
1223
if pixel_values_flat_video is not None:
1296
- if not isinstance(pixel_values_flat_video, (torch.Tensor, list)):
1297
- raise ValueError(
1298
- "Incorrect type of pixel values. "
1299
- f"Got type: {type(pixel_values_flat_video)}"
1300
- )
1301
-
1302
- if not isinstance(video_num_patches, (torch.Tensor, list)):
1303
- raise ValueError(
1304
- "Incorrect type of image_num_patches. "
1305
- f"Got type: {type(video_num_patches)}"
1306
- )
1307
-
1308
- pixel_values_flat_video = flatten_bn(pixel_values_flat_video, concat=True)
1309
- video_num_patches = flatten_bn(video_num_patches, concat=True)
1224
expected_h = expected_w = self.config.force_image_size
1225
resolve_bindings = {"h": expected_h, "w": expected_w}
1226
vllm/model_executor/models/nemotron_vl.py
+5
-2
@@ -496,8 +496,11 @@ class LlamaNemotronVLChatModel(nn.Module, SupportsMultiModal, SupportsPP, Suppor
496
)
497
498
image_token_id = kwargs["image_token_id"]
499
- assert isinstance(image_token_id, torch.Tensor)
500
- self.img_context_token_id = image_token_id.flatten().unique().item()
499
+ if isinstance(image_token_id, torch.Tensor):
500
+ image_token_id = image_token_id.flatten().unique().item()
501
+
502
+ assert isinstance(image_token_id, int)
503
+ self.img_context_token_id = image_token_id
504
505
if pixel_values_flat is not None:
506
return InternVLImagePixelInputs(
vllm/model_executor/models/skyworkr1v.py
+5
-2
@@ -814,8 +814,11 @@ class SkyworkR1VChatModel(nn.Module, SupportsMultiModal, SupportsPP):
814
)
815
816
image_token_id = kwargs["image_token_id"]
817
- assert isinstance(image_token_id, torch.Tensor)
818
- self.img_context_token_id = image_token_id.flatten().unique().item()
817
+ if isinstance(image_token_id, torch.Tensor):
818
+ image_token_id = image_token_id.flatten().unique().item()
819
+
820
+ assert isinstance(image_token_id, int)
821
+ self.img_context_token_id = image_token_id
822
823
if pixel_values_flat is not None:
824
return SkyworkR1VImagePixelInputs(
vllm/multimodal/utils.py
+1
-1
@@ -432,7 +432,7 @@ def group_mm_kwargs_by_modality(
432
433
if device is not None:
434
mm_kwargs_group = json_map_leaves(
435
- lambda x: x.to(device=device),
435
+ lambda x: x.to(device=device) if isinstance(x, torch.Tensor) else x,
436
mm_kwargs_group,
437
)
438
else: