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 (G) (#26117)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Oct 3, 2025 at 13:38 UTC
39b643dc1ac68bb595a9e0a3ea0834e9adc00e92
5 files changed
+56
-108
vllm/model_executor/models/gemma3_mm.py
+14
-21
@@ -36,7 +36,7 @@ from vllm.utils.tensor_schema import TensorSchema, TensorShape
36
from .interfaces import (MultiModalEmbeddings, SupportsLoRA,
37
SupportsMultiModal, SupportsPP)
38
from .siglip import SiglipVisionModel
39
-from .utils import (AutoWeightsLoader, WeightsMapper, flatten_bn,
39
+from .utils import (AutoWeightsLoader, WeightsMapper,
40
init_vllm_registered_model, maybe_prefix)
41
42
logger = init_logger(__name__)
@@ -289,7 +289,7 @@ class Gemma3MultiModalProcessor(BaseMultiModalProcessor[Gemma3ProcessingInfo]):
289
processor=hf_processor)
290
for size in image_sizes
291
]
292
- processed_outputs["num_crops"] = torch.tensor(num_crops)
292
+ processed_outputs["num_patches"] = torch.tensor(num_crops) + 1
293
294
return processed_outputs
295
@@ -298,12 +298,12 @@ class Gemma3MultiModalProcessor(BaseMultiModalProcessor[Gemma3ProcessingInfo]):
298
hf_inputs: BatchFeature,
299
hf_processor_mm_kwargs: Mapping[str, object],
300
) -> Mapping[str, MultiModalFieldConfig]:
301
- num_crops = hf_inputs.get("num_crops", torch.empty(0))
301
+ num_patches = hf_inputs.get("num_patches", torch.empty(0))
302
303
return dict(
304
pixel_values=MultiModalFieldConfig.flat_from_sizes(
305
- "image", num_crops + 1),
306
- num_crops=MultiModalFieldConfig.batched("image"),
305
+ "image", num_patches),
306
+ num_patches=MultiModalFieldConfig.batched("image"),
307
)
308
309
def _get_prompt_updates(
@@ -460,6 +460,8 @@ class Gemma3MultiModalProjector(nn.Module):
460
dummy_inputs=Gemma3DummyInputsBuilder)
461
class Gemma3ForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP,
462
SupportsLoRA):
463
+ merge_by_field_config = True
464
+
465
packed_modules_mapping = {
466
"qkv_proj": [
467
"q_proj",
@@ -526,29 +528,20 @@ class Gemma3ForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP,
528
def _parse_and_validate_image_input(
529
self, **kwargs: object) -> Optional[Gemma3ImageInputs]:
530
pixel_values = kwargs.pop("pixel_values", None)
529
- num_crops = kwargs.pop("num_crops", None)
531
+ num_patches = kwargs.pop("num_patches", None)
532
image_embeds = kwargs.pop("image_embeds", None)
533
assert image_embeds is None, "Gemma3 does not support image_embeds."
534
if pixel_values is None:
535
return None
536
535
- if not isinstance(pixel_values, (torch.Tensor, list)):
536
- raise ValueError("Incorrect type of pixel values. "
537
- f"Got type: {type(pixel_values)}")
538
-
539
- if not isinstance(num_crops, (torch.Tensor, list)):
540
- raise ValueError("Incorrect type of num_crops. "
541
- f"Got type: {type(num_crops)}")
542
-
537
image_size = self.config.vision_config.image_size
538
545
- return Gemma3ImagePixelInputs(
546
- pixel_values=flatten_bn(pixel_values, concat=True),
547
- num_patches=flatten_bn(num_crops, concat=True) + 1,
548
- resolve_bindings={
549
- "h": image_size,
550
- "w": image_size
551
- })
539
+ return Gemma3ImagePixelInputs(pixel_values=pixel_values,
540
+ num_patches=num_patches,
541
+ resolve_bindings={
542
+ "h": image_size,
543
+ "w": image_size
544
+ })
545
546
def _image_pixels_to_features(
547
self,
vllm/model_executor/models/gemma3n_mm.py
+31
-38
@@ -1,7 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
from collections.abc import Iterable, Mapping, Sequence
4
-from typing import Any, Literal, Optional, TypedDict, Union, cast
4
+from typing import Annotated, Any, Literal, Optional, Union, cast
5
6
import numpy as np
7
import torch
@@ -41,6 +41,7 @@ from vllm.multimodal.processing import (BaseMultiModalProcessor,
41
# yapf: enable
42
from vllm.multimodal.profiling import BaseDummyInputsBuilder
43
from vllm.sequence import IntermediateTensors
44
+from vllm.utils.tensor_schema import TensorSchema, TensorShape
45
46
from .interfaces import (MultiModalEmbeddings, SupportsMultiModal,
47
SupportsTranscription)
@@ -54,17 +55,28 @@ TOKENS_PER_IMAGE = 256
55
TOKENS_PER_AUDIO = 188
56
57
57
-class Gemma3nImagePixelInputs(TypedDict):
58
- pixel_values: torch.Tensor
59
- """Shape: `(batch_size * num_images, num_channels, height, width)`"""
58
+class Gemma3nImagePixelInputs(TensorSchema):
59
+ """
60
+ Dimensions:
61
+ - bn: Batch size * number of images
62
+ - c: Number of channels (3)
63
+ - h: Height of each patch
64
+ - w: Width of each patch
65
+ """
66
+ type: Literal["pixel_values"] = "pixel_values"
67
+ pixel_values: Annotated[torch.Tensor, TensorShape("bn", 3, "h", "w")]
68
69
62
-class Gemma3nAudioInputs(TypedDict):
63
- input_features: Union[torch.Tensor, list[torch.Tensor]]
64
- input_features_padded: torch.Tensor
65
- """Shape: `(batch_size * num_audio, seq_length, num_features)`"""
66
- input_features_mask: torch.Tensor
67
- """Shape: `(batch_size * num_audio, seq_length)`"""
70
+class Gemma3nAudioInputs(TensorSchema):
71
+ """
72
+ Dimensions:
73
+ - bn: Batch size * number of audios
74
+ - s: seq_length
75
+ - f: num_features
76
+ """
77
+ type: Literal["audio"] = "audio"
78
+ input_features_padded: Annotated[torch.Tensor, TensorShape("bn", "s", "f")]
79
+ input_features_mask: Annotated[torch.Tensor, TensorShape("bn", "s")]
80
81
82
Gemma3nImageInputs = Gemma3nImagePixelInputs
@@ -212,9 +224,9 @@ class Gemma3nMultiModalProcessor(BaseMultiModalProcessor[Gemma3nProcessingInfo]
224
225
return dict(
226
pixel_values=MultiModalFieldConfig.batched("image"),
215
- input_features=MultiModalFieldConfig.batched("audio"),
227
input_features_padded=MultiModalFieldConfig.batched("audio"),
217
- input_features_mask=MultiModalFieldConfig.batched("audio"))
228
+ input_features_mask=MultiModalFieldConfig.batched("audio"),
229
+ )
230
231
def _get_prompt_updates(
232
self,
@@ -422,6 +434,7 @@ class Gemma3nMultimodalEmbedder(nn.Module):
434
dummy_inputs=Gemma3nDummyInputsBuilder)
435
class Gemma3nForConditionalGeneration(nn.Module, SupportsMultiModal,
436
SupportsTranscription):
437
+ merge_by_field_config = True
438
supported_languages = ISO639_1_SUPPORTED_LANGS
439
440
packed_modules_mapping = {
@@ -482,14 +495,6 @@ class Gemma3nForConditionalGeneration(nn.Module, SupportsMultiModal,
495
device=self.language_model.model.embed_tokens.weight.device,
496
dtype=self.language_model.model.embed_tokens.weight.dtype)
497
485
- @property
486
- def dtype(self):
487
- return next(self.parameters()).dtype
488
-
489
- def _validate_pixel_values(self, data: torch.Tensor) -> torch.Tensor:
490
- # TODO check if there are any
491
- return data
492
-
498
def _parse_and_validate_image_input(
499
self, **kwargs: object) -> Optional[Gemma3nImageInputs]:
500
pixel_values = kwargs.pop("pixel_values", None)
@@ -499,34 +504,22 @@ class Gemma3nForConditionalGeneration(nn.Module, SupportsMultiModal,
504
if pixel_values is None:
505
return None
506
502
- if not isinstance(pixel_values, (torch.Tensor, list)):
503
- raise ValueError("Incorrect type of pixel values. "
504
- f"Got type: {type(pixel_values)}")
505
-
506
- pixel_values = flatten_bn(pixel_values, concat=True)
507
- pixel_values = pixel_values.contiguous()
508
-
509
- return Gemma3nImagePixelInputs(
510
- pixel_values=self._validate_pixel_values(pixel_values), )
507
+ return Gemma3nImagePixelInputs(pixel_values=pixel_values)
508
509
def _parse_and_validate_audio_input(
510
self, **kwargs: object) -> Optional[Gemma3nAudioInputs]:
514
- input_features = kwargs.pop("input_features", None)
515
- if input_features is None:
511
+
512
+ input_features_padded = kwargs.pop("input_features_padded", None)
513
+ if input_features_padded is None:
514
return None
515
516
input_features_mask = kwargs.pop("input_features_mask", None)
517
if input_features_mask is None:
518
return None
519
522
- input_features_padded = kwargs.pop("input_features_padded", None)
523
- if input_features_padded is None:
524
- return None
525
-
520
return Gemma3nAudioInputs(
527
- input_features=input_features,
528
- input_features_mask=input_features_mask,
521
input_features_padded=input_features_padded,
522
+ input_features_mask=input_features_mask,
523
)
524
525
def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
@@ -539,7 +532,7 @@ class Gemma3nForConditionalGeneration(nn.Module, SupportsMultiModal,
532
) and "image" not in mm_input_by_modality:
533
mm_input_by_modality[
534
"image"] = self._parse_and_validate_image_input(**kwargs)
542
- if input_key == "input_features" \
535
+ if input_key == "input_features_padded" \
536
and "audio" not in mm_input_by_modality:
537
mm_input_by_modality[
538
"audio"] = self._parse_and_validate_audio_input(**kwargs)
vllm/model_executor/models/glm4_1v.py
+2
-36
@@ -1319,6 +1319,8 @@ class Glm4vMultiModalProcessor(BaseMultiModalProcessor[Glm4vProcessingInfo]):
1319
)
1320
class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1321
SupportsLoRA, SupportsPP):
1322
+ merge_by_field_config = True
1323
+
1324
packed_modules_mapping = {
1325
"qkv_proj": [
1326
"q_proj",
@@ -1381,22 +1383,6 @@ class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1383
self.make_empty_intermediate_tensors = (
1384
self.language_model.make_empty_intermediate_tensors)
1385
1384
- def _validate_and_reshape_mm_tensor(self, mm_input: object,
1385
- name: str) -> torch.Tensor:
1386
- if not isinstance(mm_input, (torch.Tensor, list)):
1387
- raise ValueError(
1388
- f"Incorrect type of {name}. Got type: {type(mm_input)}")
1389
- if isinstance(mm_input, torch.Tensor):
1390
- if mm_input.ndim == 2:
1391
- return mm_input
1392
- if mm_input.ndim != 3:
1393
- raise ValueError(f"{name} should be 2D or batched 3D tensor. "
1394
- f"Got ndim: {mm_input.ndim} "
1395
- f"(shape={mm_input.shape})")
1396
- return mm_input.reshape(-1, mm_input.shape[-1])
1397
- else:
1398
- return torch.concat(mm_input)
1399
-
1386
def _parse_and_validate_image_input(
1387
self, **kwargs: object) -> Optional[Glm4vImageInputs]:
1388
pixel_values = kwargs.pop("pixel_values", None)
@@ -1407,11 +1393,6 @@ class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1393
return None
1394
1395
if pixel_values is not None:
1410
- pixel_values = self._validate_and_reshape_mm_tensor(
1411
- pixel_values, "image pixel values")
1412
- image_grid_thw = self._validate_and_reshape_mm_tensor(
1413
- image_grid_thw, "image grid_thw")
1414
-
1396
return Glm4vImagePixelInputs(
1397
type="pixel_values",
1398
pixel_values=pixel_values,
@@ -1419,11 +1400,6 @@ class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1400
)
1401
1402
if image_embeds is not None:
1422
- image_embeds = self._validate_and_reshape_mm_tensor(
1423
- image_embeds, "image embeds")
1424
- image_grid_thw = self._validate_and_reshape_mm_tensor(
1425
- image_grid_thw, "image grid_thw")
1426
-
1403
return Glm4vImageEmbeddingInputs(
1404
type="image_embeds",
1405
image_embeds=image_embeds,
@@ -1440,11 +1416,6 @@ class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1416
return None
1417
1418
if pixel_values_videos is not None:
1443
- pixel_values_videos = self._validate_and_reshape_mm_tensor(
1444
- pixel_values_videos, "video pixel values")
1445
- video_grid_thw = self._validate_and_reshape_mm_tensor(
1446
- video_grid_thw, "video grid_thw")
1447
-
1419
return Glm4vVideoPixelInputs(
1420
type="pixel_values_videos",
1421
pixel_values_videos=pixel_values_videos,
@@ -1452,11 +1423,6 @@ class Glm4vForConditionalGeneration(nn.Module, SupportsMultiModal,
1423
)
1424
1425
if video_embeds is not None:
1455
- video_embeds = self._validate_and_reshape_mm_tensor(
1456
- video_embeds, "video embeds")
1457
- video_grid_thw = self._validate_and_reshape_mm_tensor(
1458
- video_grid_thw, "video grid_thw")
1459
-
1426
return Glm4vVideoEmbeddingInputs(
1427
type="video_embeds",
1428
video_embeds=video_embeds,
vllm/model_executor/models/glm4v.py
+6
-9
@@ -43,7 +43,6 @@ from vllm.utils.tensor_schema import TensorSchema, TensorShape
43
from .chatglm import ChatGLMBaseModel, ChatGLMModel
44
from .interfaces import (MultiModalEmbeddings, SupportsLoRA,
45
SupportsMultiModal, SupportsPP)
46
-from .utils import flatten_bn
46
47
48
class GLMVImagePixelInputs(TensorSchema):
@@ -529,8 +528,9 @@ class GLM4VMultiModalProcessor(BaseMultiModalProcessor[GLM4VProcessingInfo]):
528
@MULTIMODAL_REGISTRY.register_processor(GLM4VMultiModalProcessor,
529
info=GLM4VProcessingInfo,
530
dummy_inputs=GLM4VDummyInputsBuilder)
532
-class GLM4VForCausalLM(ChatGLMBaseModel, SupportsLoRA, SupportsPP,
533
- SupportsMultiModal):
531
+class GLM4VForCausalLM(ChatGLMBaseModel, SupportsMultiModal, SupportsLoRA,
532
+ SupportsPP):
533
+ merge_by_field_config = True
534
535
packed_modules_mapping = {
536
"query_key_value": ["query_key_value"],
@@ -574,14 +574,9 @@ class GLM4VForCausalLM(ChatGLMBaseModel, SupportsLoRA, SupportsPP,
574
pixel_values = kwargs.pop("pixel_values", None)
575
576
if pixel_values is not None:
577
- if not isinstance(pixel_values, (torch.Tensor, list)):
578
- raise ValueError("Incorrect type of pixel values. "
579
- f"Got type: {type(pixel_values)}")
580
-
577
expected_h = expected_w = self.config.vision_config["image_size"]
578
return GLMVImagePixelInputs(type="pixel_values",
583
- data=flatten_bn(pixel_values,
584
- concat=True),
579
+ data=pixel_values,
580
resolve_bindings={
581
"h": expected_h,
582
"w": expected_w
@@ -598,6 +593,8 @@ class GLM4VForCausalLM(ChatGLMBaseModel, SupportsLoRA, SupportsPP,
593
def get_language_model(self) -> torch.nn.Module:
594
return self.transformer
595
596
+ get_input_embeddings = SupportsMultiModal.get_input_embeddings
597
+
598
def get_multimodal_embeddings(self,
599
**kwargs: object) -> MultiModalEmbeddings:
600
image_input = self._parse_and_validate_image_input(**kwargs)
vllm/model_executor/models/granite_speech.py
+3
-4
@@ -168,10 +168,8 @@ class GraniteSpeechMultiModalProcessor(
168
# Calculate the number of audio tokens per entry in the batch;
169
# This is used to split the batch back out after padding.
170
audio_token_index = self.info.get_hf_config().audio_token_index
171
- processed_outputs["audio_embed_sizes"] = [
172
- torch.sum(indices == audio_token_index).item()
173
- for indices in processed_outputs["input_ids"]
174
- ]
171
+ processed_outputs["audio_embed_sizes"] = (
172
+ processed_outputs["input_ids"] == audio_token_index).sum(-1)
173
174
return processed_outputs
175
@@ -527,6 +525,7 @@ class GraniteSpeechForConditionalGeneration(
525
SupportsPP,
526
SupportsLoRA,
527
):
528
+ merge_by_field_config = True
529
530
packed_modules_mapping = {
531
"qkv_proj": [