@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 803d5c35f3
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 15, 17:15 UTC

[V1] Override `mm_counts` for dummy data creation (#15703)

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

Cyrus Leung committed Mar 30, 2025 at 18:20 UTC 803d5c35f3e8a6547ff7c6e6c322e54cbfec8444
9 files changed +114 -93
tests/models/decoder_only/vision_language/test_models.py
+2 -26
@@ -385,18 +385,7 @@ VLM_TEST_SETTINGS = {
385 ),
386 "minicpmo_26": VLMTestInfo(
387 models=["openbmb/MiniCPM-o-2_6"],
388 - test_type=(VLMTestType.IMAGE),
389 - prompt_formatter=lambda img_prompt: f"<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n{img_prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", # noqa: E501
390 - img_idx_to_prompt=lambda idx: "(<image>./</image>)\n",
391 - max_model_len=4096,
392 - max_num_seqs=2,
393 - get_stop_token_ids=lambda tok: tok.convert_tokens_to_ids(['<|im_end|>', '<|endoftext|>']), # noqa: E501
394 - hf_output_post_proc=model_utils.minicpmv_trunc_hf_output,
395 - patch_hf_runner=model_utils.minicpmo_26_patch_hf_runner,
396 - ),
397 - "minicpmo_26_multi_image": VLMTestInfo(
398 - models=["openbmb/MiniCPM-o-2_6"],
399 - test_type=(VLMTestType.MULTI_IMAGE),
388 + test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
389 prompt_formatter=lambda img_prompt: f"<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n{img_prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", # noqa: E501
390 img_idx_to_prompt=lambda idx: "(<image>./</image>)\n",
391 max_model_len=4096,
@@ -404,22 +393,10 @@ VLM_TEST_SETTINGS = {
393 get_stop_token_ids=lambda tok: tok.convert_tokens_to_ids(['<|im_end|>', '<|endoftext|>']), # noqa: E501
394 hf_output_post_proc=model_utils.minicpmv_trunc_hf_output,
395 patch_hf_runner=model_utils.minicpmo_26_patch_hf_runner,
407 - marks=[large_gpu_mark(min_gb=32)],
396 ),
397 "minicpmv_26": VLMTestInfo(
398 models=["openbmb/MiniCPM-V-2_6"],
411 - test_type=(VLMTestType.IMAGE),
412 - prompt_formatter=lambda img_prompt: f"<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n{img_prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", # noqa: E501
413 - img_idx_to_prompt=lambda idx: "(<image>./</image>)\n",
414 - max_model_len=4096,
415 - max_num_seqs=2,
416 - get_stop_token_ids=lambda tok: tok.convert_tokens_to_ids(['<|im_end|>', '<|endoftext|>']), # noqa: E501
417 - hf_output_post_proc=model_utils.minicpmv_trunc_hf_output,
418 - patch_hf_runner=model_utils.minicpmv_26_patch_hf_runner,
419 - ),
420 - "minicpmv_26_multi_image": VLMTestInfo(
421 - models=["openbmb/MiniCPM-V-2_6"],
422 - test_type=(VLMTestType.MULTI_IMAGE),
399 + test_type=(VLMTestType.IMAGE, VLMTestType.MULTI_IMAGE),
400 prompt_formatter=lambda img_prompt: f"<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n{img_prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", # noqa: E501
401 img_idx_to_prompt=lambda idx: "(<image>./</image>)\n",
402 max_model_len=4096,
@@ -427,7 +404,6 @@ VLM_TEST_SETTINGS = {
404 get_stop_token_ids=lambda tok: tok.convert_tokens_to_ids(['<|im_end|>', '<|endoftext|>']), # noqa: E501
405 hf_output_post_proc=model_utils.minicpmv_trunc_hf_output,
406 patch_hf_runner=model_utils.minicpmv_26_patch_hf_runner,
430 - marks=[large_gpu_mark(min_gb=32)],
407 ),
408 "molmo": VLMTestInfo(
409 models=["allenai/Molmo-7B-D-0924"],
vllm/model_executor/models/llava_next_video.py
+9 -5
@@ -71,7 +71,8 @@ class LlavaNextVideoProcessingInfo(BaseProcessingInfo):
71 max_video_tokens = self.get_num_video_tokens(
72 image_width=target_width,
73 image_height=target_height,
74 - num_frames=self.get_num_frames_with_most_features(seq_len),
74 + num_frames=self.get_num_frames_with_most_features(
75 + seq_len, mm_counts),
76 )
77
78 return {"video": max_video_tokens}
@@ -130,9 +131,12 @@ class LlavaNextVideoProcessingInfo(BaseProcessingInfo):
131
132 return num_frames
133
133 - def get_num_frames_with_most_features(self, seq_len: int) -> int:
134 - mm_config = self.ctx.get_mm_config()
135 - max_videos = mm_config.get_limit_per_prompt("video")
134 + def get_num_frames_with_most_features(
135 + self,
136 + seq_len: int,
137 + mm_counts: Mapping[str, int],
138 + ) -> int:
139 + max_videos = mm_counts.get("video", 0)
140
141 max_total_frames = self._get_max_video_frames(seq_len)
142
@@ -155,7 +159,7 @@ class LlavaNextVideoDummyInputsBuilder(
159 target_width, target_height = \
160 self.info.get_image_size_with_most_features()
161 target_num_frames = \
158 - self.info.get_num_frames_with_most_features(seq_len)
162 + self.info.get_num_frames_with_most_features(seq_len, mm_counts)
163
164 mm_data = {
165 "video":
vllm/model_executor/models/llava_onevision.py
+17 -8
@@ -108,7 +108,7 @@ class LlavaOnevisionProcessingInfo(LlavaNextProcessingInfo):
108 ) -> Mapping[str, int]:
109 return {
110 "image": self.get_max_image_tokens(),
111 - "video": self.get_max_video_tokens(seq_len),
111 + "video": self.get_max_video_tokens(seq_len, mm_counts),
112 }
113
114 # Based on: https://github.com/huggingface/text-generation-inference/blob/v3.0.1/server/text_generation_server/models/vlm_causal_lm.py#L86
@@ -202,10 +202,13 @@ class LlavaOnevisionProcessingInfo(LlavaNextProcessingInfo):
202
203 return num_frames
204
205 - def get_num_frames_with_most_features(self, seq_len: int) -> int:
206 - mm_config = self.ctx.get_mm_config()
207 - max_images = mm_config.get_limit_per_prompt("image")
208 - max_videos = mm_config.get_limit_per_prompt("video")
205 + def get_num_frames_with_most_features(
206 + self,
207 + seq_len: int,
208 + mm_counts: Mapping[str, int],
209 + ) -> int:
210 + max_images = mm_counts.get("image", 0)
211 + max_videos = mm_counts.get("video", 0)
212
213 max_image_tokens = self.get_max_image_tokens() * max_images
214 max_total_frames = self._get_max_video_frames(seq_len -
@@ -215,13 +218,18 @@ class LlavaOnevisionProcessingInfo(LlavaNextProcessingInfo):
218
219 return max(max_frames_per_video, 1)
220
218 - def get_max_video_tokens(self, seq_len: int) -> int:
221 + def get_max_video_tokens(
222 + self,
223 + seq_len: int,
224 + mm_counts: Mapping[str, int],
225 + ) -> int:
226 target_width, target_height = self.get_image_size_with_most_features()
227
228 return self.get_num_video_tokens(
229 image_width=target_width,
230 image_height=target_height,
224 - num_frames=self.get_num_frames_with_most_features(seq_len),
231 + num_frames=self.get_num_frames_with_most_features(
232 + seq_len, mm_counts),
233 )
234
235
@@ -243,7 +251,8 @@ class LlavaOnevisionDummyInputsBuilder(
251 target_width, target_height = \
252 self.info.get_image_size_with_most_features()
253 target_num_frames = \
246 - self.info.get_num_frames_with_most_features(seq_len)
254 + self.info.get_num_frames_with_most_features(seq_len,
255 + mm_counts)
256
257 mm_data = {
258 "image":
vllm/model_executor/models/minicpmo.py
+15 -11
@@ -43,7 +43,8 @@ from vllm.multimodal.parse import (AudioItem, AudioProcessorItems,
43 from vllm.multimodal.processing import PromptReplacement, PromptUpdate
44 from vllm.multimodal.profiling import ProcessorInputs
45
46 -from .minicpmv import (MiniCPMV2_6, MiniCPMVDummyInputsBuilder,
46 +from .minicpmv import (_MAX_FRAMES_PER_VIDEO, MiniCPMV2_6,
47 + MiniCPMVDummyInputsBuilder,
48 MiniCPMVMultiModalDataParser,
49 MiniCPMVMultiModalProcessor, MiniCPMVProcessingInfo,
50 _minicpmv_field_config)
@@ -203,8 +204,8 @@ class MiniCPMOProcessingInfo(MiniCPMVProcessingInfo):
204 return 30
205
206 def get_max_audio_tokens(self) -> int:
206 - return self.get_max_audio_tokens_per_chunk(
207 - ) * self.get_max_audio_chunks_with_most_features()
207 + num_chunks = self.get_max_audio_chunks_with_most_features()
208 + return self.get_max_audio_tokens_per_chunk() * num_chunks
209
210 def get_audio_len_by_num_chunks(self, num_chunks: int) -> int:
211 sampling_rate = self.get_default_audio_sampling_rate()
@@ -212,21 +213,24 @@ class MiniCPMOProcessingInfo(MiniCPMVProcessingInfo):
213 num_tokens_per_chunk = self.get_max_audio_tokens_per_chunk() - 2
214 return int(num_chunks * sampling_rate / num_tokens_per_chunk) + 1
215
215 - def get_num_frames_with_most_features(self, seq_len: int) -> int:
216 - mm_config = self.ctx.get_mm_config()
217 - max_images = mm_config.get_limit_per_prompt("image")
218 - max_videos = mm_config.get_limit_per_prompt("video")
219 - max_audios = mm_config.get_limit_per_prompt("audio")
216 + def get_num_frames_with_most_features(
217 + self,
218 + seq_len: int,
219 + mm_counts: Mapping[str, int],
220 + ) -> int:
221 + max_images = mm_counts.get("image", 0)
222 + max_videos = mm_counts.get("video", 0)
223 + max_audios = mm_counts.get("audio", 0)
224
225 max_image_tokens = self.get_max_image_tokens() * max_images
226 max_audio_tokens = self.get_max_audio_tokens() * max_audios
227 max_total_frames = self.get_max_video_frames(seq_len -
228 max_image_tokens -
229 max_audio_tokens)
230 + max_frames_per_video = min(max_total_frames // max(max_videos, 1),
231 + _MAX_FRAMES_PER_VIDEO)
232
227 - num_frames = max(max_total_frames // max(max_videos, 1), 1)
228 -
229 - return num_frames
233 + return max(max_frames_per_video, 1)
234
235
236 class MiniCPMODummyInputsBuilder(
vllm/model_executor/models/minicpmv.py
+24 -12
@@ -69,6 +69,9 @@ from .utils import (AutoWeightsLoader, flatten_bn, maybe_prefix,
69 merge_multimodal_embeddings)
70 from .vision import scatter_patch_features, select_patch_features
71
72 +# For profile run
73 +_MAX_FRAMES_PER_VIDEO = 16
74 +
75
76 class MiniCPMVImagePixelInputs(TypedDict):
77 type: Literal["pixel_values"]
@@ -369,7 +372,8 @@ class MiniCPMVProcessingInfo(BaseProcessingInfo):
372 ) -> Mapping[str, int]:
373 mm_max_tokens = {"image": self.get_max_image_tokens()}
374 if self.get_model_version() == (2, 6):
372 - mm_max_tokens["video"] = self.get_max_video_tokens(seq_len)
375 + mm_max_tokens["video"] = self.get_max_video_tokens(
376 + seq_len, mm_counts)
377
378 return mm_max_tokens
379
@@ -432,9 +436,14 @@ class MiniCPMVProcessingInfo(BaseProcessingInfo):
436 use_image_id=False,
437 )
438
435 - def get_max_video_tokens(self, seq_len: int) -> int:
436 - return self.get_max_video_frame_tokens(
437 - ) * self.get_num_frames_with_most_features(seq_len)
439 + def get_max_video_tokens(
440 + self,
441 + seq_len: int,
442 + mm_counts: Mapping[str, int],
443 + ) -> int:
444 + num_frames = self.get_num_frames_with_most_features(seq_len, mm_counts)
445 + num_video_tokens_total = self.get_max_video_frame_tokens() * num_frames
446 + return num_video_tokens_total
447
448 def get_video_max_slice_num(self) -> int:
449 return 1
@@ -449,18 +458,21 @@ class MiniCPMVProcessingInfo(BaseProcessingInfo):
458 num_frames = max_tokens // num_frame_tokens
459 return num_frames
460
452 - def get_num_frames_with_most_features(self, seq_len: int) -> int:
453 - mm_config = self.ctx.get_mm_config()
454 - max_images = mm_config.get_limit_per_prompt("image")
455 - max_videos = mm_config.get_limit_per_prompt("video")
461 + def get_num_frames_with_most_features(
462 + self,
463 + seq_len: int,
464 + mm_counts: Mapping[str, int],
465 + ) -> int:
466 + max_images = mm_counts.get("image", 0)
467 + max_videos = mm_counts.get("video", 0)
468
469 max_image_tokens = self.get_max_image_tokens() * max_images
470 max_total_frames = self.get_max_video_frames(seq_len -
471 max_image_tokens)
472 + max_frames_per_video = min(max_total_frames // max(max_videos, 1),
473 + _MAX_FRAMES_PER_VIDEO)
474
461 - num_frames = max(max_total_frames // max(max_videos, 1), 1)
462 -
463 - return num_frames
475 + return max(max_frames_per_video, 1)
476
477
478 _I = TypeVar("_I",
@@ -483,7 +495,7 @@ class MiniCPMVDummyInputsBuilder(BaseDummyInputsBuilder[_I]):
495 video_width, video_height = \
496 self.info.get_video_frame_size_with_most_features()
497 num_video_frames = \
486 - self.info.get_num_frames_with_most_features(seq_len)
498 + self.info.get_num_frames_with_most_features(seq_len, mm_counts)
499
500 mm_data = {
501 "image":
vllm/model_executor/models/qwen2_vl.py
+17 -9
@@ -806,7 +806,7 @@ class Qwen2VLProcessingInfo(BaseProcessingInfo):
806 max_pixels: Optional[int] = None,
807 size: Optional[dict[str, int]] = None,
808 **kwargs: object,
809 - ):
809 + ) -> Qwen2VLImageProcessor:
810 return cached_image_processor_from_config(
811 self.ctx.model_config,
812 **self._get_image_processor_kwargs(min_pixels=min_pixels,
@@ -825,7 +825,7 @@ class Qwen2VLProcessingInfo(BaseProcessingInfo):
825 ) -> Mapping[str, int]:
826 return {
827 "image": self.get_max_image_tokens(),
828 - "video": self.get_max_video_tokens(seq_len),
828 + "video": self.get_max_video_tokens(seq_len, mm_counts),
829 }
830
831 def _get_vision_info(
@@ -941,10 +941,13 @@ class Qwen2VLProcessingInfo(BaseProcessingInfo):
941
942 return num_frames
943
944 - def get_num_frames_with_most_features(self, seq_len: int) -> int:
945 - mm_config = self.ctx.get_mm_config()
946 - max_images = mm_config.get_limit_per_prompt("image")
947 - max_videos = mm_config.get_limit_per_prompt("video")
944 + def get_num_frames_with_most_features(
945 + self,
946 + seq_len: int,
947 + mm_counts: Mapping[str, int],
948 + ) -> int:
949 + max_images = mm_counts.get("image", 0)
950 + max_videos = mm_counts.get("video", 0)
951
952 max_image_tokens = self.get_max_image_tokens() * max_images
953 max_total_frames = self._get_max_video_frames(seq_len -
@@ -954,13 +957,18 @@ class Qwen2VLProcessingInfo(BaseProcessingInfo):
957
958 return max(max_frames_per_video, 1)
959
957 - def get_max_video_tokens(self, seq_len: int) -> int:
960 + def get_max_video_tokens(
961 + self,
962 + seq_len: int,
963 + mm_counts: Mapping[str, int],
964 + ) -> int:
965 target_width, target_height = self.get_image_size_with_most_features()
966
967 return self.get_num_video_tokens(
968 image_width=target_width,
969 image_height=target_height,
963 - num_frames=self.get_num_frames_with_most_features(seq_len),
970 + num_frames=self.get_num_frames_with_most_features(
971 + seq_len, mm_counts),
972 image_processor=None,
973 )
974
@@ -982,7 +990,7 @@ class Qwen2VLDummyInputsBuilder(BaseDummyInputsBuilder[Qwen2VLProcessingInfo]):
990 target_width, target_height = \
991 self.info.get_image_size_with_most_features()
992 target_num_frames = \
985 - self.info.get_num_frames_with_most_features(seq_len)
993 + self.info.get_num_frames_with_most_features(seq_len, mm_counts)
994
995 mm_data = {
996 "image":
vllm/multimodal/profiling.py
+21 -9
@@ -3,7 +3,7 @@
3 from abc import ABC, abstractmethod
4 from collections.abc import Mapping
5 from dataclasses import dataclass, field
6 -from typing import Generic, NamedTuple, TypeVar, cast
6 +from typing import Generic, NamedTuple, Optional, TypeVar, cast
7
8 import numpy as np
9 import numpy.typing as npt
@@ -160,17 +160,19 @@ class MultiModalProfiler(Generic[_I]):
160 def get_and_validate_mm_inputs(
161 self,
162 seq_len: int,
163 + mm_counts: Optional[Mapping[str, int]] = None,
164 ) -> tuple[MultiModalInputs, Mapping[str, int]]:
164 - mm_counts = self.get_mm_limits()
165 + if mm_counts is None:
166 + mm_counts = self.get_mm_limits()
167
168 info = self.processing_info
169 mm_max_tokens_per_item = info.get_mm_max_tokens_per_item(
170 seq_len, mm_counts)
171
170 - if mm_counts.keys() != mm_max_tokens_per_item.keys():
172 + if mm_counts.keys() - mm_max_tokens_per_item.keys():
173 raise AssertionError(
174 "The keys returned by `get_supported_mm_limits` "
173 - f"({set(mm_counts.keys())}) should be the same as those "
175 + f"({set(mm_counts.keys())}) should be a subset of those "
176 "returned by `get_mm_max_tokens_per_item` "
177 f"({set(mm_max_tokens_per_item.keys())})")
178
@@ -193,8 +195,12 @@ class MultiModalProfiler(Generic[_I]):
195 "tokens.")
196 return mm_inputs, total_placeholders_by_modality
197
196 - def get_encoder_dummy_data(self, seq_len: int) -> DummyEncoderData:
197 - mm_inputs, _ = self.get_and_validate_mm_inputs(seq_len)
198 + def get_encoder_dummy_data(
199 + self,
200 + seq_len: int,
201 + mm_counts: Optional[Mapping[str, int]] = None,
202 + ) -> DummyEncoderData:
203 + mm_inputs, _ = self.get_and_validate_mm_inputs(seq_len, mm_counts)
204 mm_inputs = cast(MultiModalEncDecInputs, mm_inputs)
205
206 # For encoder-decoder models, use encoder prompt token ids instead of
@@ -207,9 +213,15 @@ class MultiModalProfiler(Generic[_I]):
213
214 return DummyEncoderData(encoder_prompt_token_ids)
215
210 - def get_decoder_dummy_data(self, seq_len: int) -> DummyDecoderData:
211 - (mm_inputs, total_placeholders_by_modality
212 - ) = self.get_and_validate_mm_inputs(seq_len)
216 + def get_decoder_dummy_data(
217 + self,
218 + seq_len: int,
219 + mm_counts: Optional[Mapping[str, int]] = None,
220 + ) -> DummyDecoderData:
221 + (
222 + mm_inputs,
223 + total_placeholders_by_modality,
224 + ) = self.get_and_validate_mm_inputs(seq_len, mm_counts)
225
226 prompt_token_ids = mm_inputs["prompt_token_ids"]
227 total_len = len(prompt_token_ids)
vllm/multimodal/registry.py
+4 -2
@@ -458,6 +458,7 @@ class MultiModalRegistry:
458 self,
459 model_config: "ModelConfig",
460 seq_len: int,
461 + mm_counts: Optional[Mapping[str, int]] = None,
462 ) -> DummyDecoderData:
463 """
464 Create dummy data for profiling the memory usage of a model.
@@ -466,7 +467,7 @@ class MultiModalRegistry:
467 """
468 processor = self.create_processor(model_config, disable_cache=True)
469 profiler = MultiModalProfiler(processor)
469 - dummy_data = profiler.get_decoder_dummy_data(seq_len)
470 + dummy_data = profiler.get_decoder_dummy_data(seq_len, mm_counts)
471
472 # Having more tokens is over-conservative but otherwise fine
473 token_ids = dummy_data.prompt_token_ids
@@ -481,6 +482,7 @@ class MultiModalRegistry:
482 self,
483 model_config: "ModelConfig",
484 seq_len: int,
485 + mm_counts: Optional[Mapping[str, int]] = None,
486 ) -> DummyEncoderData:
487 """
488 Create dummy data for profiling the memory usage of a model.
@@ -489,7 +491,7 @@ class MultiModalRegistry:
491 """
492 processor = self.create_processor(model_config, disable_cache=True)
493 profiler = MultiModalProfiler(processor)
492 - dummy_data = profiler.get_encoder_dummy_data(seq_len)
494 + dummy_data = profiler.get_encoder_dummy_data(seq_len, mm_counts)
495
496 # Having more tokens is over-conservative but otherwise fine
497 token_ids = dummy_data.prompt_token_ids
vllm/v1/worker/gpu_model_runner.py
+5 -11
@@ -1470,19 +1470,13 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1470 encoder_budget, max_num_mm_items, dummy_data_modality)
1471
1472 # Create dummy batch of multimodal inputs.
1473 - dummy_request_data = self.mm_registry.get_decoder_dummy_data(
1473 + dummy_mm_kwargs = self.mm_registry.get_decoder_dummy_data(
1474 model_config=self.model_config,
1475 seq_len=self.max_num_tokens,
1476 - )
1477 - dummy_mm_data = dummy_request_data.multi_modal_data
1478 -
1479 - # Dummy data definition may contain multiple multimodal items
1480 - # (e.g, multiple images) for a single request, therefore here we
1481 - # always replicate first item by max_num_mm_items times since in V1
1482 - # they are scheduled to be processed separately.
1483 - dummy_mm_item = dummy_mm_data.get_item(
1484 - modality=dummy_data_modality, item_index=0)
1485 - dummy_mm_kwargs = MultiModalKwargs.from_items([dummy_mm_item])
1476 + mm_counts={
1477 + dummy_data_modality: 1
1478 + },
1479 + ).multi_modal_data
1480
1481 batched_dummy_mm_inputs = MultiModalKwargs.batch(
1482 [dummy_mm_kwargs] * max_num_mm_items)