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 15, 17:15 UTC
[Bugfix] Check dimensions of multimodal embeddings in V1 (#15816)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Apr 1, 2025 at 00:01 UTC
09e974d483ab80052fc2586e7d72231c280f46b6
14 files changed
+98
-37
examples/offline_inference/vision_language.py
+5
-4
@@ -68,7 +68,7 @@ def run_blip2(questions: list[str], modality: str) -> ModelRequestData:
68
# See https://huggingface.co/Salesforce/blip2-opt-2.7b/discussions/15#64ff02f3f8cf9e4f5b038262 #noqa
69
prompts = [f"Question: {question} Answer:" for question in questions]
70
engine_args = EngineArgs(
71
- model="Salesforce/blip2-opt-2.7b",
71
+ model="Salesforce/blip2-opt-6.7b",
72
disable_mm_preprocessor_cache=args.disable_mm_preprocessor_cache,
73
)
74
@@ -128,7 +128,8 @@ def run_florence2(questions: list[str], modality: str) -> ModelRequestData:
128
engine_args = EngineArgs(
129
model="microsoft/Florence-2-large",
130
tokenizer="facebook/bart-large",
131
- max_num_seqs=8,
131
+ max_model_len=4096,
132
+ max_num_seqs=2,
133
trust_remote_code=True,
134
dtype="bfloat16",
135
disable_mm_preprocessor_cache=args.disable_mm_preprocessor_cache,
@@ -511,7 +512,7 @@ def run_mllama(questions: list[str], modality: str) -> ModelRequestData:
512
engine_args = EngineArgs(
513
model=model_name,
514
max_model_len=4096,
514
- max_num_seqs=16,
515
+ max_num_seqs=2,
516
disable_mm_preprocessor_cache=args.disable_mm_preprocessor_cache,
517
)
518
@@ -700,7 +701,7 @@ def run_pixtral_hf(questions: list[str], modality: str) -> ModelRequestData:
701
# NOTE: Need L40 (or equivalent) to avoid OOM
702
engine_args = EngineArgs(
703
model=model_name,
703
- max_model_len=8192,
704
+ max_model_len=6144,
705
max_num_seqs=2,
706
disable_mm_preprocessor_cache=args.disable_mm_preprocessor_cache,
707
)
tests/distributed/test_pipeline_parallel.py
+1
-1
@@ -217,7 +217,7 @@ EMBEDDING_MODELS = { # type: ignore[var-annotated]
217
218
MULTIMODAL_MODELS = {
219
# [Decoder-only]
220
- "Salesforce/blip2-opt-2.7b": PPTestSettings.fast(),
220
+ "Salesforce/blip2-opt-6.7b": PPTestSettings.fast(),
221
"facebook/chameleon-7b": PPTestSettings.fast(),
222
"adept/fuyu-8b": PPTestSettings.fast(),
223
"THUDM/glm-4v-9b": PPTestSettings.fast(),
tests/models/decoder_only/vision_language/test_models.py
+4
-4
@@ -34,8 +34,6 @@ REQUIRES_V0_MODELS = [
34
# V1 Test: no way to fall back for head_dim = 80
35
# https://github.com/vllm-project/vllm/issues/14524
36
"qwen_vl",
37
- "h2ovl",
38
- "blip2",
37
# V1 Test: not enough KV cache space in C1.
38
"fuyu",
39
]
@@ -161,7 +159,8 @@ VLM_TEST_SETTINGS = {
159
marks=[large_gpu_mark(min_gb=64)],
160
),
161
"blip2": VLMTestInfo(
164
- models=["Salesforce/blip2-opt-2.7b"],
162
+ # TODO: Change back to 2.7b once head_dim = 80 is supported
163
+ models=["Salesforce/blip2-opt-6.7b"],
164
test_type=VLMTestType.IMAGE,
165
prompt_formatter=lambda img_prompt: f"Question: {img_prompt} Answer:",
166
img_idx_to_prompt=lambda idx: "",
@@ -248,7 +247,8 @@ VLM_TEST_SETTINGS = {
247
"h2ovl": VLMTestInfo(
248
models = [
249
"h2oai/h2ovl-mississippi-800m",
251
- "h2oai/h2ovl-mississippi-2b",
250
+ # TODO: Re-enable once head_dim = 80 is supported
251
+ # "h2oai/h2ovl-mississippi-2b",
252
],
253
test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
254
prompt_formatter=lambda img_prompt: f"<|prompt|>{img_prompt}<|end|><|answer|>", # noqa: E501
tests/models/registry.py
+2
-1
@@ -259,7 +259,8 @@ _CROSS_ENCODER_EXAMPLE_MODELS = {
259
_MULTIMODAL_EXAMPLE_MODELS = {
260
# [Decoder-only]
261
"AriaForConditionalGeneration": _HfExamplesInfo("rhymes-ai/Aria"),
262
- "Blip2ForConditionalGeneration": _HfExamplesInfo("Salesforce/blip2-opt-2.7b"), # noqa: E501
262
+ "Blip2ForConditionalGeneration": _HfExamplesInfo("Salesforce/blip2-opt-2.7b", # noqa: E501
263
+ extras={"6b": "Salesforce/blip2-opt-6.7b"}), # noqa: E501
264
"ChameleonForConditionalGeneration": _HfExamplesInfo("facebook/chameleon-7b"), # noqa: E501
265
"DeepseekVLV2ForCausalLM": _HfExamplesInfo("deepseek-ai/deepseek-vl2-tiny", # noqa: E501
266
extras={"fork": "Isotr0py/deepseek-vl2-tiny"}, # noqa: E501
vllm/model_executor/models/florence2.py
+2
-1
@@ -875,7 +875,8 @@ class Florence2MultiModalProcessor(
875
Florence2MultiModalProcessor,
876
info=Florence2ProcessingInfo,
877
dummy_inputs=Florence2DummyInputsBuilder)
878
-class Florence2ForConditionalGeneration(nn.Module, SupportsMultiModal):
878
+class Florence2ForConditionalGeneration(nn.Module, SupportsMultiModal,
879
+ SupportsV0Only):
880
881
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
882
super().__init__()
vllm/model_executor/models/fuyu.py
+14
-9
@@ -39,7 +39,6 @@ from vllm.multimodal.processing import (BaseMultiModalProcessor,
39
PromptUpdate, PromptUpdateDetails)
40
from vllm.multimodal.profiling import BaseDummyInputsBuilder, ProcessorInputs
41
from vllm.sequence import IntermediateTensors
42
-from vllm.utils import flatten_2d_lists
42
43
from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
44
from .utils import (AutoWeightsLoader, flatten_bn, maybe_prefix,
@@ -66,10 +65,13 @@ class FuyuImagePatchInputs(TypedDict):
65
This is used to split the embeddings which has the first two dimensions
66
flattened just like `flat_data`.
67
"""
68
+
69
embed_is_patch: Union[torch.Tensor, list[torch.Tensor]]
70
"""
71
A boolean mask indicating which image embeddings correspond
72
to patch tokens.
73
+
74
+ Shape: `(batch_size * num_images, num_embeds)`
75
"""
76
77
@@ -322,16 +324,18 @@ class FuyuForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
324
def _parse_and_validate_image_input(
325
self, **kwargs: object) -> Optional[FuyuImagePatchInputs]:
326
image_patches = kwargs.pop("image_patches", None)
325
- embed_is_patch = kwargs.pop("embed_is_patch", None)
327
if image_patches is not None:
328
if not isinstance(image_patches, (torch.Tensor, list)):
329
raise ValueError("Incorrect type of image patches. "
330
f"Got type: {type(image_patches)}")
331
332
+ embed_is_patch = kwargs.pop("embed_is_patch")
333
if not isinstance(embed_is_patch, (torch.Tensor, list)):
334
raise ValueError("Incorrect type of embed_is_patch. "
335
f"Got type: {type(embed_is_patch)}")
336
+
337
image_patches_flat = flatten_bn(image_patches)
338
+ embed_is_patch = flatten_bn(embed_is_patch)
339
340
return FuyuImagePatchInputs(
341
type="image_patches",
@@ -351,6 +355,7 @@ class FuyuForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
355
assert self.vision_embed_tokens is not None
356
vision_embeddings_flat, _ = self.vision_embed_tokens(
357
image_patches_flat)
358
+
359
return vision_embeddings_flat.split(patches_per_image, dim=0)
360
361
def get_multimodal_embeddings(
@@ -358,13 +363,13 @@ class FuyuForCausalLM(nn.Module, SupportsMultiModal, SupportsPP):
363
image_input = self._parse_and_validate_image_input(**kwargs)
364
if image_input is None:
365
return None
361
- vision_embeddings = self._process_image_input(image_input)
362
- #return vision_embeddings
363
- return flatten_2d_lists(
364
- scatter_patch_features(*args) for args in zip(
365
- vision_embeddings,
366
- image_input["embed_is_patch"],
367
- ))
366
+
367
+ image_features = self._process_image_input(image_input)
368
+
369
+ return scatter_patch_features(
370
+ image_features,
371
+ image_input["embed_is_patch"],
372
+ )
373
374
def get_input_embeddings(
375
self,
vllm/model_executor/models/gemma3_mm.py
+4
-2
@@ -613,7 +613,7 @@ class Gemma3ForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP,
613
def _process_image_input(
614
self,
615
image_input: Gemma3ImageInputs,
616
- ) -> tuple[torch.Tensor, ...]:
616
+ ) -> list[torch.Tensor]:
617
assert self.vision_tower is not None
618
619
pixel_values = image_input["pixel_values"]
@@ -625,7 +625,9 @@ class Gemma3ForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP,
625
)
626
image_embeds = self.multi_modal_projector(image_features)
627
628
- return image_embeds.split(num_patches.tolist())
628
+ return [
629
+ e.flatten(0, 1) for e in image_embeds.split(num_patches.tolist())
630
+ ]
631
632
def get_multimodal_embeddings(
633
self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
vllm/model_executor/models/idefics3.py
+7
-2
@@ -733,7 +733,10 @@ class Idefics3ForConditionalGeneration(nn.Module, SupportsMultiModal,
733
pixel_attention_mask=pixel_attention_mask,
734
)
735
736
- def _process_image_input(self, image_input: ImageInputs) -> torch.Tensor:
736
+ def _process_image_input(
737
+ self,
738
+ image_input: ImageInputs,
739
+ ) -> Union[torch.Tensor, list[torch.Tensor]]:
740
if image_input["type"] == "image_embeds":
741
return image_input["data"]
742
@@ -741,7 +744,9 @@ class Idefics3ForConditionalGeneration(nn.Module, SupportsMultiModal,
744
image_features = self.model.connector(image_features)
745
746
num_patches = image_input["num_patches"]
744
- return image_features.split(num_patches.tolist())
747
+ return [
748
+ e.flatten(0, 1) for e in image_features.split(num_patches.tolist())
749
+ ]
750
751
def get_multimodal_embeddings(
752
self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
vllm/model_executor/models/llava_next_video.py
+5
-4
@@ -406,20 +406,21 @@ class LlavaNextVideoForConditionalGeneration(nn.Module, SupportsMultiModal,
406
h, w)
407
stacked_embeddings = self._video_pixels_to_features(
408
self.vision_tower, stacked_pixels)
409
- return stacked_embeddings.view(b, num_frames,
410
- *stacked_embeddings.shape[1:])
409
+ embeds = stacked_embeddings.view(b, num_frames,
410
+ *stacked_embeddings.shape[1:])
411
412
elif is_list_of(video_pixels, torch.Tensor):
413
frames_per_videos = [v.shape[0] for v in video_pixels]
414
stacked_pixels = torch.cat(video_pixels, dim=0)
415
stacked_embeddings = self._video_pixels_to_features(
416
self.vision_tower, stacked_pixels)
417
- return torch.split(stacked_embeddings, frames_per_videos, dim=0)
418
-
417
+ embeds = torch.split(stacked_embeddings, frames_per_videos, dim=0)
418
else:
419
raise ValueError(
420
f"Unsupported type of video input {type(video_pixels)}")
421
422
+ return [e.flatten(0, 1) for e in embeds]
423
+
424
def get_multimodal_embeddings(
425
self, **kwargs: object) -> Optional[MultiModalEmbeddings]:
426
video_input = self._parse_and_validate_video_input(**kwargs)
vllm/model_executor/models/minicpmv.py
+5
-2
@@ -919,8 +919,11 @@ class MiniCPMVBaseModel(nn.Module, SupportsMultiModal, SupportsPP):
919
920
image_features_flat = self.get_vision_hidden_states(image_input)
921
922
- # Reconstruct the batch dimension
923
- return image_features_flat.split(image_input["num_slices"].tolist())
922
+ num_slices = image_input["num_slices"]
923
+ return [
924
+ e.flatten(0, 1)
925
+ for e in image_features_flat.split(num_slices.tolist())
926
+ ]
927
928
def _process_multimodal_inputs(self, modalities: dict):
929
# The result multimodal_embeddings is tuple of tensors, with each
vllm/model_executor/models/vision.py
+1
-1
@@ -204,7 +204,7 @@ def scatter_patch_features(
204
(e_is_patch.shape[0], patches_one.shape[-1]),
205
fill_value=torch.nan,
206
)
207
- embed_one[e_is_patch] = patches_one.flatten(0, -2)
207
+ embed_one[e_is_patch] = patches_one
208
return embed_one
209
210
return tuple(
vllm/v1/worker/gpu_model_runner.py
+12
-6
@@ -41,6 +41,8 @@ from vllm.v1.utils import bind_kv_cache
41
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
42
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
43
44
+from .utils import sanity_check_mm_encoder_outputs
45
+
46
if TYPE_CHECKING:
47
import xgrammar as xgr
48
@@ -867,6 +869,11 @@ class GPUModelRunner(LoRAModelRunnerMixin):
869
curr_group_outputs = self.model.get_multimodal_embeddings(
870
**batched_mm_inputs)
871
872
+ sanity_check_mm_encoder_outputs(
873
+ curr_group_outputs,
874
+ expected_num_items=len(grouped_mm_inputs),
875
+ )
876
+
877
for output in curr_group_outputs:
878
encoder_outputs.append(output)
879
@@ -1490,12 +1497,11 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1497
# Run multimodal encoder.
1498
dummy_encoder_outputs = self.model.get_multimodal_embeddings(
1499
**batched_dummy_mm_inputs)
1493
- assert len(dummy_encoder_outputs) == max_num_mm_items, (
1494
- "Expected dimension 0 of encoder outputs to match the number "
1495
- f"of multimodal data items: {max_num_mm_items}, got "
1496
- f"{len(dummy_encoder_outputs)=} instead. This is most likely "
1497
- "due to the 'get_multimodal_embeddings' method of the model "
1498
- "not implemented correctly.")
1500
+
1501
+ sanity_check_mm_encoder_outputs(
1502
+ dummy_encoder_outputs,
1503
+ expected_num_items=max_num_mm_items,
1504
+ )
1505
1506
# Cache the dummy encoder outputs.
1507
self.encoder_cache["tmp"] = dict(enumerate(dummy_encoder_outputs))
vllm/v1/worker/tpu_model_runner.py
+7
@@ -37,6 +37,8 @@ from vllm.v1.sample.tpu.sampler import Sampler as TPUSampler
37
from vllm.v1.utils import bind_kv_cache
38
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
39
40
+from .utils import sanity_check_mm_encoder_outputs
41
+
42
if TYPE_CHECKING:
43
from vllm.v1.core.sched.output import SchedulerOutput
44
@@ -512,6 +514,11 @@ class TPUModelRunner:
514
curr_group_outputs = self.model.get_multimodal_embeddings(
515
**batched_mm_inputs)
516
517
+ sanity_check_mm_encoder_outputs(
518
+ curr_group_outputs,
519
+ expected_num_items=len(grouped_mm_inputs),
520
+ )
521
+
522
for output in curr_group_outputs:
523
encoder_outputs.append(output)
524
vllm/v1/worker/utils.py
new
+29
@@ -0,0 +1,29 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+import torch
3
+
4
+
5
+def sanity_check_mm_encoder_outputs(
6
+ mm_embeddings: object,
7
+ expected_num_items: int,
8
+) -> None:
9
+ """
10
+ Perform sanity checks for the result of
11
+ :meth:`vllm.model_executor.models.SupportsMultiModal.get_multimodal_embeddings`.
12
+ """
13
+ assert isinstance(mm_embeddings, (list, tuple, torch.Tensor)), (
14
+ "Expected multimodal embeddings to be a list/tuple of 2D tensors, "
15
+ f"or a single 3D tensor, but got {type(mm_embeddings)} "
16
+ "instead. This is most likely due to incorrect implementation "
17
+ "of the model's `get_multimodal_embeddings` method.")
18
+
19
+ assert len(mm_embeddings) == expected_num_items, (
20
+ "Expected number of multimodal embeddings to match number of "
21
+ f"input items: {expected_num_items}, but got {len(mm_embeddings)=} "
22
+ "instead. This is most likely due to incorrect implementation "
23
+ "of the model's `get_multimodal_embeddings` method.")
24
+
25
+ assert all(e.ndim == 2 for e in mm_embeddings), (
26
+ "Expected multimodal embeddings to be a sequence of 2D tensors, "
27
+ f"but got tensors with shapes {[e.shape for e in mm_embeddings]} "
28
+ "instead. This is most likely due to incorrect implementation "
29
+ "of the model's `get_multimodal_embeddings` method.")