main
py 1,081 lines 37.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # adapted from https://huggingface.co/OpenGVLab/InternVL2-4B/blob/main/modeling_internvl_chat.py
5 # --------------------------------------------------------
6 # InternVL
7 # Copyright (c) 2023 OpenGVLab
8 # Licensed under The MIT License [see LICENSE for details]
9 # --------------------------------------------------------
10 from abc import abstractmethod
11 from collections.abc import Hashable, Iterable, Mapping, Sequence
12 from functools import cached_property
13 from typing import Annotated, Any, Literal, TypeAlias, TypedDict, TypeVar
14
15 import torch
16 import torch.nn as nn
17 from transformers import BatchFeature, PreTrainedConfig
18
19 from vllm.config import VllmConfig
20 from vllm.config.multimodal import MultiModalDummyOptions
21 from vllm.inputs import MultiModalDataDict
22 from vllm.model_executor.layers.quantization import QuantizationConfig
23 from vllm.model_executor.layers.quantization.auto_awq import AutoAWQConfig
24 from vllm.model_executor.models.intern_vit import (
25 InternVisionModel,
26 )
27 from vllm.model_executor.models.module_mapping import MultiModelKeys
28 from vllm.multimodal import MULTIMODAL_REGISTRY
29 from vllm.multimodal.inputs import (
30 BatchedTensorInputs,
31 MultiModalFieldConfig,
32 MultiModalKwargsItem,
33 MultiModalKwargsItems,
34 )
35 from vllm.multimodal.parse import (
36 ImageEmbeddingItems,
37 ImageProcessorItems,
38 ImageSize,
39 MultiModalDataItems,
40 )
41 from vllm.multimodal.processing import (
42 BaseDummyInputsBuilder,
43 BaseMultiModalProcessor,
44 BaseProcessingInfo,
45 PromptReplacement,
46 PromptUpdate,
47 cached_encode,
48 )
49 from vllm.sequence import IntermediateTensors
50 from vllm.transformers_utils.processors.internvl import (
51 InternVLImageProcessor,
52 InternVLProcessor,
53 InternVLVideoProcessor,
54 )
55 from vllm.utils.tensor_schema import TensorSchema, TensorShape
56
57 from .interfaces import (
58 MultiModalEmbeddings,
59 SupportsEncoderCudaGraph,
60 SupportsLoRA,
61 SupportsMultiModal,
62 SupportsPP,
63 )
64 from .utils import (
65 AutoWeightsLoader,
66 WeightsMapper,
67 init_vllm_registered_model,
68 maybe_prefix,
69 )
70
71
72 class InternVLImagePixelInputs(TensorSchema):
73 """Dimensions:
74 - bn: Batch size * number of images
75 - bnp: Batch size * number of images * (1 + num_patches)
76 - c: Number of channels (3)
77 - h: Height of each image patch
78 - w: Width of each image patch
79 """
80
81 type: Literal["pixel_values"]
82 pixel_values_flat: Annotated[torch.Tensor, TensorShape("bnp", 3, "h", "w")]
83 num_patches: Annotated[torch.Tensor, TensorShape("bn")]
84
85
86 class InternVLImageEmbeddingInputs(TensorSchema):
87 """Dimensions:
88 - n: Number of images
89 - f: Total image feature size
90 - h: Hidden size (must match the hidden size of language model backbone)
91 """
92
93 type: Literal["image_embeds"]
94 data: Annotated[torch.Tensor | list[torch.Tensor], TensorShape("n", "f", "h")]
95
96
97 InternVLImageInputs: TypeAlias = InternVLImagePixelInputs | InternVLImageEmbeddingInputs
98
99
100 class InternVLVideoPixelInputs(TensorSchema):
101 """Dimensions:
102 - bvf: Batch size * number of videos * num_frames
103 - bn: Batch size * number of images
104 - c: Number of channels (3)
105 - h: Height of each video frame
106 - w: Width of each video frame
107 """
108
109 type: Literal["pixel_values_videos"]
110 pixel_values_flat: Annotated[torch.Tensor, TensorShape("bvf", 3, "h", "w")]
111 num_patches: Annotated[torch.Tensor, TensorShape("bn")]
112
113
114 class InternVLVideoEmbeddingInputs(TensorSchema):
115 """Dimensions:
116 - n: Number of videos
117 - f: Total video feature size
118 - h: Hidden size (must match the hidden size of language model backbone)
119 """
120
121 type: Literal["video_embeds"]
122 data: Annotated[torch.Tensor | list[torch.Tensor], TensorShape("n", "f", "h")]
123
124
125 InternVLVideoInputs: TypeAlias = InternVLVideoPixelInputs | InternVLVideoEmbeddingInputs
126
127
128 class InternVLMultiModalInputs(TypedDict, total=False):
129 images: InternVLImageInputs | None
130 videos: InternVLVideoInputs | None
131
132
133 class BaseInternVLProcessingInfo(BaseProcessingInfo):
134 """Basic image-only ProcessingInfo for InternVL-style models."""
135
136 @abstractmethod
137 def get_hf_processor(self, **kwargs: object) -> InternVLProcessor:
138 raise NotImplementedError
139
140 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
141 return {"image": None}
142
143 def get_num_image_tokens(
144 self,
145 *,
146 image_width: int,
147 image_height: int,
148 processor: InternVLProcessor,
149 ) -> int:
150 return processor.get_num_image_tokens(
151 image_width=image_width,
152 image_height=image_height,
153 )
154
155 def get_image_size_with_most_features(self) -> ImageSize:
156 processor = self.get_hf_processor()
157 image_processor = processor.image_processor
158
159 base_size = image_processor.image_size
160 target_ratios = processor.resolve_target_ratios()
161
162 largest_feature_size, largest_feature_pinpoint = 0, None
163 for wr, hr in target_ratios:
164 width, height = base_size * wr, base_size * hr
165
166 feat_size = self.get_num_image_tokens(
167 image_width=width,
168 image_height=height,
169 processor=processor,
170 )
171 if feat_size > largest_feature_size:
172 largest_feature_size = feat_size
173 largest_feature_pinpoint = ImageSize(width=width, height=height)
174
175 if largest_feature_size == 0 or largest_feature_pinpoint is None:
176 raise ValueError("Cannot have a largest feature size of 0!")
177
178 return largest_feature_pinpoint
179
180 def get_max_image_tokens(self) -> int:
181 processor = self.get_hf_processor()
182 target_width, target_height = self.get_image_size_with_most_features()
183
184 return self.get_num_image_tokens(
185 image_width=target_width,
186 image_height=target_height,
187 processor=processor,
188 )
189
190
191 _I = TypeVar("_I", bound=BaseInternVLProcessingInfo)
192
193
194 class BaseInternVLDummyInputsBuilder(BaseDummyInputsBuilder[_I]):
195 """Basic image-only DummyInputsBuilder for InternVL-style models."""
196
197 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
198 num_images = mm_counts.get("image", 0)
199
200 return "<image>" * num_images
201
202 def get_dummy_mm_data(
203 self,
204 seq_len: int,
205 mm_counts: Mapping[str, int],
206 mm_options: MultiModalDummyOptions,
207 ) -> MultiModalDataDict:
208 target_width, target_height = self.info.get_image_size_with_most_features()
209
210 return {
211 "image": self._get_dummy_images(
212 width=target_width,
213 height=target_height,
214 num_images=mm_counts.get("image", 0),
215 overrides=mm_options.get("image"),
216 )
217 }
218
219
220 class BaseInternVLMultiModalProcessor(BaseMultiModalProcessor[_I]):
221 """Basic image-only MultiModalProcessor for InternVL-style models."""
222
223 def _postprocess_hf_mm_data(
224 self,
225 mm_data: Mapping[str, object],
226 hf_processor_mm_kwargs: Mapping[str, object],
227 processed_data: BatchFeature,
228 ) -> BatchFeature:
229 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
230 image_token_id = hf_processor.ctx_image_token_id
231
232 # Since there may be extra tokens in the feature placeholders,
233 # we need to pass the image token ID to the model to select the
234 # tokens to merge from the vision encoder outputs
235 processed_data["image_token_id"] = torch.tensor(image_token_id)
236
237 return processed_data
238
239 def _get_image_fields_config(self, hf_inputs: BatchFeature):
240 image_num_patches = hf_inputs.get("image_num_patches", torch.empty(0))
241 num_images = len(image_num_patches)
242
243 return dict(
244 pixel_values_flat=MultiModalFieldConfig.flat_from_sizes(
245 "image", image_num_patches
246 ),
247 image_num_patches=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
248 image_embeds=MultiModalFieldConfig.batched("image"),
249 image_token_id=MultiModalFieldConfig.shared(
250 "image", num_images, keep_on_cpu=True
251 ),
252 )
253
254 def _get_mm_fields_config(
255 self,
256 hf_inputs: BatchFeature,
257 hf_processor_mm_kwargs: Mapping[str, object],
258 ) -> Mapping[str, MultiModalFieldConfig]:
259 return self._get_image_fields_config(hf_inputs)
260
261 def _get_prompt_repl_image(
262 self,
263 mm_items: MultiModalDataItems,
264 hf_processor: InternVLProcessor,
265 out_mm_data: BatchedTensorInputs,
266 ):
267 tokenizer = self.info.get_tokenizer()
268
269 if "image_num_patches" in out_mm_data:
270 image_num_patches = out_mm_data["image_num_patches"]
271 assert isinstance(image_num_patches, torch.Tensor)
272 image_num_patches = image_num_patches.tolist()
273 elif "image_embeds" in out_mm_data:
274 # TODO: Use image size information in dictionary embedding inputs
275 # to compute num_patches (similar to Qwen2-VL)
276 image_num_patches = [None] * len(out_mm_data["image_embeds"])
277 else:
278 image_num_patches = []
279
280 def get_replacement_internvl(item_idx: int):
281 images = mm_items.get_items(
282 "image", (ImageEmbeddingItems, ImageProcessorItems)
283 )
284
285 if isinstance(images, ImageEmbeddingItems):
286 feature_size = images.get_feature_size(item_idx)
287 else:
288 assert isinstance(images, ImageProcessorItems)
289 image_size = images.get_image_size(item_idx)
290 feature_size = self.info.get_num_image_tokens(
291 image_width=image_size.width,
292 image_height=image_size.height,
293 processor=hf_processor,
294 )
295
296 num_patches = image_num_patches[item_idx]
297 if num_patches is not None:
298 assert isinstance(num_patches, int)
299
300 return hf_processor.get_image_repl(num_patches, num_features=feature_size)
301
302 return PromptReplacement(
303 modality="image",
304 target=cached_encode(tokenizer, "<image>", add_special_tokens=False),
305 replacement=get_replacement_internvl,
306 )
307
308 def _get_prompt_updates(
309 self,
310 mm_items: MultiModalDataItems,
311 hf_processor_mm_kwargs: Mapping[str, object],
312 out_mm_kwargs: MultiModalKwargsItems,
313 ) -> Sequence[PromptUpdate]:
314 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
315 out_mm_data = out_mm_kwargs.get_data()
316
317 return [
318 self._get_prompt_repl_image(mm_items, hf_processor, out_mm_data),
319 ]
320
321
322 class InternVLProcessingInfo(BaseInternVLProcessingInfo):
323 """InternVL ProcessingInfo extended for video processing."""
324
325 def get_image_processor(self, **kwargs):
326 config = self.get_hf_config()
327 vision_config = config.vision_config
328
329 kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
330 kwargs.setdefault("image_size", vision_config.image_size)
331 kwargs.setdefault("min_dynamic_patch", config.min_dynamic_patch)
332 kwargs.setdefault("max_dynamic_patch", config.max_dynamic_patch)
333 kwargs.setdefault("dynamic_image_size", config.dynamic_image_size)
334 kwargs.setdefault("use_thumbnail", config.use_thumbnail)
335
336 return InternVLImageProcessor(**kwargs)
337
338 def get_video_processor(self, **kwargs):
339 config = self.get_hf_config()
340 vision_config = config.vision_config
341
342 kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
343 kwargs.setdefault("image_size", vision_config.image_size)
344
345 return InternVLVideoProcessor(**kwargs)
346
347 @cached_property
348 def ctx_video_token(self):
349 text_model_type = self.get_hf_config().get_text_config().model_type
350 ctx_video_token_map = {
351 "qwen2": "<|video_pad|>",
352 "qwen3": "<|video_pad|>",
353 "qwen3_moe": "<|video_pad|>",
354 "gpt_oss": "<|reserved_200000|>",
355 }
356
357 if text_model_type not in ctx_video_token_map:
358 return None
359
360 ctx_video_token = ctx_video_token_map[text_model_type]
361 if ctx_video_token not in self.get_tokenizer().get_vocab():
362 return None
363
364 return ctx_video_token
365
366 def get_hf_processor(self, **kwargs: object) -> InternVLProcessor:
367 config = self.get_hf_config()
368 vision_config = config.vision_config
369
370 image_processor = self.get_image_processor(**kwargs)
371 image_size = image_processor.image_size
372 patch_size = vision_config.patch_size
373 downsample_ratio = config.downsample_ratio
374 image_seq_length = int((image_size // patch_size) ** 2 * (downsample_ratio**2))
375
376 ctx_video_token = self.ctx_video_token
377 video_processor = (
378 self.get_video_processor(**kwargs) if ctx_video_token else None
379 )
380
381 return InternVLProcessor(
382 tokenizer=self.get_tokenizer(),
383 image_processor=image_processor,
384 video_processor=video_processor,
385 image_seq_length=image_seq_length,
386 ctx_video_token=ctx_video_token,
387 )
388
389 def get_supported_mm_limits(self):
390 video_limit = {"video": None} if self.ctx_video_token else {}
391 return {**super().get_supported_mm_limits(), **video_limit}
392
393 def get_num_frames_with_most_features(
394 self,
395 seq_len: int,
396 mm_counts: Mapping[str, int],
397 ) -> int:
398 max_images = mm_counts.get("image", 0)
399 max_videos = mm_counts.get("video", 0)
400
401 processor = self.get_hf_processor()
402 num_image_token = processor.image_seq_length
403
404 max_image_tokens = self.get_max_image_tokens() * max_images
405 max_total_frames = (seq_len - max_image_tokens) // num_image_token
406 max_frames_per_video = max_total_frames // max(max_videos, 1)
407
408 return max(max_frames_per_video, 1)
409
410
411 class InternVLDummyInputsBuilder(
412 BaseInternVLDummyInputsBuilder[InternVLProcessingInfo]
413 ):
414 """InternVL DummyInputsBuilder extended for video support."""
415
416 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
417 num_videos = mm_counts.get("video", 0)
418
419 return super().get_dummy_text(mm_counts) + "<video>" * num_videos
420
421 def get_dummy_mm_data(
422 self,
423 seq_len: int,
424 mm_counts: Mapping[str, int],
425 mm_options: MultiModalDummyOptions,
426 ) -> MultiModalDataDict:
427 dummy_image = super().get_dummy_mm_data(seq_len, mm_counts, mm_options)
428 if self.info.ctx_video_token:
429 config = self.info.get_hf_config()
430 image_size: int = config.vision_config.image_size
431 target_num_frames = self.info.get_num_frames_with_most_features(
432 seq_len, mm_counts
433 )
434 num_videos = mm_counts.get("video", 0)
435 video_overrides = mm_options.get("video")
436 dummy_video = {
437 "video": self._get_dummy_videos(
438 width=image_size,
439 height=image_size,
440 num_frames=target_num_frames,
441 num_videos=num_videos,
442 overrides=video_overrides,
443 )
444 }
445 else:
446 dummy_video = {}
447 return {**dummy_image, **dummy_video}
448
449
450 class InternVLMultiModalProcessor(
451 BaseInternVLMultiModalProcessor[InternVLProcessingInfo]
452 ):
453 """InternVL MultiModalProcessor extended for video support."""
454
455 def _postprocess_hf_mm_data(
456 self,
457 mm_data: Mapping[str, object],
458 hf_processor_mm_kwargs: Mapping[str, object],
459 processed_data: BatchFeature,
460 ) -> BatchFeature:
461 processed_data = super()._postprocess_hf_mm_data(
462 mm_data,
463 hf_processor_mm_kwargs,
464 processed_data,
465 )
466
467 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
468 if (video_token_id := hf_processor.ctx_video_token_id) is not None:
469 processed_data["video_token_id"] = torch.tensor(video_token_id)
470
471 return processed_data
472
473 def _get_video_fields_config(self, hf_inputs: BatchFeature):
474 video_num_patches = hf_inputs.get("video_num_patches", torch.empty(0))
475 num_videos = len(video_num_patches)
476
477 return dict(
478 pixel_values_flat_video=MultiModalFieldConfig.flat_from_sizes(
479 "video", video_num_patches
480 ),
481 video_num_patches=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
482 video_token_id=MultiModalFieldConfig.shared(
483 "video", num_videos, keep_on_cpu=True
484 ),
485 )
486
487 def _get_mm_fields_config(
488 self,
489 hf_inputs: BatchFeature,
490 hf_processor_mm_kwargs: Mapping[str, object],
491 ) -> Mapping[str, MultiModalFieldConfig]:
492 fields = self._get_image_fields_config(hf_inputs)
493 if self.info.ctx_video_token:
494 fields |= self._get_video_fields_config(hf_inputs)
495
496 return fields
497
498 def _get_prompt_repl_video(
499 self,
500 mm_items: MultiModalDataItems,
501 hf_processor: InternVLProcessor,
502 out_mm_data: BatchedTensorInputs,
503 ):
504 tokenizer = self.info.get_tokenizer()
505
506 if "video_num_patches" in out_mm_data:
507 video_num_patches_tensor = out_mm_data["video_num_patches"]
508 assert isinstance(video_num_patches_tensor, torch.Tensor)
509 video_num_patches: list[int] = video_num_patches_tensor.tolist()
510 else:
511 video_num_patches = []
512
513 def get_video_replacement_internvl(item_idx: int):
514 num_patches = video_num_patches[item_idx]
515 if num_patches is not None:
516 assert isinstance(num_patches, int)
517
518 return hf_processor.get_video_repl(num_patches)
519
520 return PromptReplacement(
521 modality="video",
522 target=cached_encode(tokenizer, "<video>", add_special_tokens=False),
523 replacement=get_video_replacement_internvl,
524 )
525
526 def _get_prompt_updates(
527 self,
528 mm_items: MultiModalDataItems,
529 hf_processor_mm_kwargs: Mapping[str, object],
530 out_mm_kwargs: MultiModalKwargsItems,
531 ) -> Sequence[PromptUpdate]:
532 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
533 out_mm_data = out_mm_kwargs.get_data()
534
535 prompt_repls = [
536 self._get_prompt_repl_image(mm_items, hf_processor, out_mm_data),
537 ]
538 if self.info.ctx_video_token is not None:
539 prompt_repls.append(
540 self._get_prompt_repl_video(mm_items, hf_processor, out_mm_data)
541 )
542
543 return prompt_repls
544
545
546 @MULTIMODAL_REGISTRY.register_processor(
547 InternVLMultiModalProcessor,
548 info=InternVLProcessingInfo,
549 dummy_inputs=InternVLDummyInputsBuilder,
550 )
551 class InternVLChatModel(
552 nn.Module,
553 SupportsMultiModal,
554 SupportsPP,
555 SupportsLoRA,
556 SupportsEncoderCudaGraph,
557 ):
558 supports_encoder_tp_data = True
559 supports_tower_connector_lora = True
560
561 hf_to_vllm_mapper = WeightsMapper(
562 orig_to_new_prefix=dict.fromkeys(
563 [
564 "action_embed",
565 "temporal_embed",
566 "track_embed",
567 "track_embed_decoder",
568 "box_token",
569 "cg_criterion",
570 "cg_model",
571 "loc_encoder",
572 "loc_decoder",
573 "sam",
574 "temporal_token",
575 "track_token",
576 ]
577 )
578 )
579
580 @classmethod
581 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
582 if modality.startswith("image"):
583 return "<image>"
584 if modality.startswith("video"):
585 return "<video>"
586
587 raise ValueError("Only image or video modality is supported")
588
589 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
590 super().__init__()
591
592 config = vllm_config.model_config.hf_config
593 quant_config = vllm_config.quant_config
594 multimodal_config = vllm_config.model_config.get_multimodal_config()
595
596 self.config = config
597 self.multimodal_config = multimodal_config
598 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
599 self._patch_quant_config(config, quant_config)
600
601 image_size = config.force_image_size or config.vision_config.image_size
602 patch_size = config.vision_config.patch_size
603 self.patch_size = patch_size
604 self.patch_tokens = (image_size // patch_size) ** 2
605 self.num_image_token = int(self.patch_tokens * (config.downsample_ratio**2))
606 self.downsample_ratio = config.downsample_ratio
607 self.ps_version = config.ps_version
608
609 with self._mark_tower_model(vllm_config, {"image", "video"}):
610 self.vision_model = self._init_vision_model(
611 config,
612 quant_config=quant_config,
613 prefix=maybe_prefix(prefix, "vision_model"),
614 )
615 self.mlp1 = self._init_mlp1(config)
616
617 with self._mark_language_model(vllm_config):
618 self.language_model = init_vllm_registered_model(
619 vllm_config=vllm_config,
620 hf_config=config.text_config,
621 prefix=maybe_prefix(prefix, "language_model"),
622 )
623
624 self.img_context_token_id: int | None = None
625 self.video_context_token_id: int | None = None
626
627 self.make_empty_intermediate_tensors = (
628 self.language_model.make_empty_intermediate_tensors
629 )
630
631 def _patch_quant_config(
632 self, config: PreTrainedConfig, quant_config: QuantizationConfig | None
633 ):
634 # the awq models from OpenGVLab missing `modules_to_not_convert`
635 # patch the quant_config to add `modules_to_not_convert` back
636 if isinstance(quant_config, AutoAWQConfig):
637 text_config = config.text_config
638 llm_quant_config = getattr(text_config, "quantization_config", None)
639 if (not quant_config.modules_to_not_convert) and (
640 llm_quant_config is not None
641 ):
642 quant_config.modules_to_not_convert.append("vision_model")
643
644 def _init_vision_model(
645 self,
646 config: PreTrainedConfig,
647 quant_config: QuantizationConfig | None,
648 *,
649 prefix: str,
650 ):
651 vision_feature_layer = config.select_layer
652 if vision_feature_layer < 0:
653 num_hidden_layers = (
654 config.vision_config.num_hidden_layers + vision_feature_layer + 1
655 )
656 else:
657 num_hidden_layers = vision_feature_layer + 1
658
659 return InternVisionModel(
660 config.vision_config,
661 quant_config=quant_config,
662 num_hidden_layers_override=num_hidden_layers,
663 prefix=prefix,
664 )
665
666 def _init_mlp1(self, config: PreTrainedConfig) -> nn.Module:
667 vit_hidden_size = config.vision_config.hidden_size
668 llm_hidden_size = config.text_config.hidden_size
669
670 return nn.Sequential(
671 nn.LayerNorm(vit_hidden_size * int(1 / self.downsample_ratio) ** 2),
672 nn.Linear(
673 vit_hidden_size * int(1 / self.downsample_ratio) ** 2, llm_hidden_size
674 ),
675 nn.GELU(),
676 nn.Linear(llm_hidden_size, llm_hidden_size),
677 )
678
679 def pixel_shuffle(self, x, scale_factor=0.5):
680 n, w, h, c = x.size()
681 # N, W, H, C --> N, W, H * scale, C // scale
682 x = x.view(n, w, int(h * scale_factor), int(c / scale_factor))
683 # N, W, H * scale, C // scale --> N, H * scale, W, C // scale
684 x = x.permute(0, 2, 1, 3).contiguous()
685 x = x.view(
686 n,
687 int(h * scale_factor),
688 int(w * scale_factor),
689 int(c / (scale_factor * scale_factor)),
690 )
691 if self.ps_version == "v1":
692 pass
693 else:
694 x = x.permute(0, 2, 1, 3).contiguous()
695 return x
696
697 def extract_feature(self, pixel_values: torch.Tensor) -> torch.Tensor:
698 vit_embeds = self.vision_model(pixel_values=pixel_values)
699 vit_embeds = vit_embeds[:, 1:, :]
700
701 h = w = int(vit_embeds.shape[1] ** 0.5)
702 vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], h, w, -1)
703 vit_embeds = self.pixel_shuffle(vit_embeds, scale_factor=self.downsample_ratio)
704 vit_embeds = vit_embeds.reshape(vit_embeds.shape[0], -1, vit_embeds.shape[-1])
705 vit_embeds = self.mlp1(vit_embeds)
706 return vit_embeds
707
708 def _parse_and_validate_image_input(
709 self, **kwargs: object
710 ) -> InternVLImageInputs | None:
711 pixel_values_flat = kwargs.pop("pixel_values_flat", None)
712 image_num_patches = kwargs.pop("image_num_patches", None)
713 image_embeds = kwargs.pop("image_embeds", None)
714
715 if pixel_values_flat is None and image_embeds is None:
716 return None
717
718 if image_embeds is not None:
719 return InternVLImageEmbeddingInputs(
720 type="image_embeds",
721 data=image_embeds,
722 )
723
724 image_token_id = kwargs["image_token_id"]
725 if isinstance(image_token_id, torch.Tensor):
726 image_token_id = image_token_id.flatten().unique().item()
727
728 assert isinstance(image_token_id, int)
729 self.img_context_token_id = image_token_id
730
731 if pixel_values_flat is not None:
732 expected_h = expected_w = self.config.vision_config.image_size
733 resolve_bindings = {"h": expected_h, "w": expected_w}
734
735 return InternVLImagePixelInputs(
736 type="pixel_values",
737 pixel_values_flat=pixel_values_flat,
738 num_patches=image_num_patches,
739 resolve_bindings=resolve_bindings,
740 )
741
742 raise AssertionError("This line should be unreachable.")
743
744 def _parse_and_validate_video_input(
745 self, **kwargs: object
746 ) -> InternVLVideoInputs | None:
747 pixel_values_flat_video = kwargs.pop("pixel_values_flat_video", None)
748 video_num_patches = kwargs.pop("video_num_patches", None)
749 video_embeds = kwargs.pop("video_embeds", None)
750
751 if pixel_values_flat_video is None and video_embeds is None:
752 return None
753
754 if video_embeds is not None:
755 return InternVLVideoEmbeddingInputs(
756 type="video_embeds",
757 data=video_embeds,
758 )
759
760 video_token_id = kwargs["video_token_id"]
761 if isinstance(video_token_id, torch.Tensor):
762 video_token_id = video_token_id.flatten().unique().item()
763
764 assert isinstance(video_token_id, int)
765 self.video_context_token_id = video_token_id
766
767 if pixel_values_flat_video is not None:
768 expected_h = expected_w = self.config.vision_config.image_size
769 resolve_bindings = {"h": expected_h, "w": expected_w}
770
771 return InternVLVideoPixelInputs(
772 type="pixel_values_videos",
773 pixel_values_flat=pixel_values_flat_video,
774 num_patches=video_num_patches,
775 resolve_bindings=resolve_bindings,
776 )
777
778 raise AssertionError("This line should be unreachable.")
779
780 def _process_vision_input(
781 self,
782 image_input: InternVLImageInputs | InternVLVideoInputs,
783 ) -> tuple[torch.Tensor, ...]:
784 if (
785 image_input["type"] == "image_embeds"
786 or image_input["type"] == "video_embeds"
787 ):
788 return image_input["data"]
789
790 image_embeds = self.extract_feature(image_input["pixel_values_flat"])
791
792 num_patches = image_input["num_patches"]
793
794 # Only one image in the current batch
795 if len(num_patches) == 1:
796 return (image_embeds.view(-1, self.config.text_config.hidden_size),)
797
798 # NOTE: Image embeddings are split into separate tensors for each image
799 # by the size of each embedding.
800 feature_size = image_embeds.shape[1]
801 image_embeds = image_embeds.view(-1, self.config.text_config.hidden_size)
802 image_feature_sizes = [
803 num_patches * feature_size for num_patches in num_patches
804 ]
805 return image_embeds.split(image_feature_sizes)
806
807 def _parse_and_validate_multimodal_inputs(
808 self, **kwargs: object
809 ) -> InternVLMultiModalInputs:
810 modalities: InternVLMultiModalInputs = {}
811
812 # Preserve the order of modalities if there are multiple of them
813 # from the order of kwargs.
814 for input_key in kwargs:
815 if (
816 input_key in ("pixel_values_flat", "image_embeds")
817 and "images" not in modalities
818 ):
819 modalities["images"] = self._parse_and_validate_image_input(**kwargs)
820 if input_key in ("pixel_values_flat_video",) and "videos" not in modalities:
821 modalities["videos"] = self._parse_and_validate_video_input(**kwargs)
822
823 return modalities
824
825 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
826 modalities = self._parse_and_validate_multimodal_inputs(**kwargs)
827 if not modalities:
828 return []
829
830 # The result multimodal_embeddings is tuple of tensors, with each
831 # tensor correspoending to a multimodal data item (image or video).
832 multimodal_embeddings: tuple[torch.Tensor, ...] = ()
833
834 # NOTE: It is important to iterate over the keys in this dictionary
835 # to preserve the order of the modalities.
836 for modality in modalities:
837 if modality == "images":
838 image_input = modalities["images"]
839 if image_input is not None:
840 image_embeddings = self._process_vision_input(image_input)
841 multimodal_embeddings += tuple(image_embeddings)
842 if modality == "videos":
843 video_input = modalities["videos"]
844 if video_input is not None:
845 video_embeddings = self._process_vision_input(video_input)
846 multimodal_embeddings += tuple(video_embeddings)
847
848 return multimodal_embeddings
849
850 def embed_input_ids(
851 self,
852 input_ids: torch.Tensor,
853 multimodal_embeddings: MultiModalEmbeddings | None = None,
854 *,
855 is_multimodal: torch.Tensor | None = None,
856 ) -> torch.Tensor:
857 # This is to satisfy the type checker for each overload
858 if multimodal_embeddings is None or is_multimodal is None:
859 return super().embed_input_ids(input_ids)
860
861 return super().embed_input_ids(
862 input_ids,
863 multimodal_embeddings=multimodal_embeddings,
864 is_multimodal=is_multimodal,
865 )
866
867 def forward(
868 self,
869 input_ids: torch.Tensor | None,
870 positions: torch.Tensor,
871 intermediate_tensors: IntermediateTensors | None = None,
872 inputs_embeds: torch.Tensor | None = None,
873 **kwargs: object,
874 ) -> IntermediateTensors:
875 if intermediate_tensors is not None:
876 inputs_embeds = None
877
878 forward_kwargs = {
879 "input_ids": input_ids,
880 "positions": positions,
881 "intermediate_tensors": intermediate_tensors,
882 "inputs_embeds": inputs_embeds,
883 }
884
885 hidden_states = self.language_model.model(**forward_kwargs)
886 return hidden_states
887
888 def compute_logits(
889 self,
890 hidden_states: torch.Tensor,
891 ) -> torch.Tensor | None:
892 return self.language_model.compute_logits(hidden_states)
893
894 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
895 loader = AutoWeightsLoader(self)
896 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
897
898 def get_mm_mapping(self) -> MultiModelKeys:
899 """Get the module prefix in multimodal models."""
900 return MultiModelKeys.from_string_field(
901 language_model="language_model",
902 connector="mlp1",
903 tower_model="vision_model",
904 )
905
906 def get_mm_lora_token_counts(
907 self,
908 *,
909 modality: str,
910 mm_kwargs: MultiModalKwargsItem | None,
911 num_mm_embeds: int,
912 ) -> tuple[int, int | None]:
913 del modality, mm_kwargs
914 if num_mm_embeds <= 0 or self.num_image_token <= 0:
915 return 0, 0
916
917 num_patches = num_mm_embeds // self.num_image_token
918 tower_tokens = num_patches * (self.patch_tokens + 1)
919 return tower_tokens, num_patches * self.num_image_token
920
921 # -- SupportsEncoderCudaGraph protocol methods --
922
923 def get_encoder_cudagraph_config(self):
924 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphConfig
925
926 return EncoderCudaGraphConfig(
927 modalities=["image", "video"],
928 # InternVision uses standard ViT attention (no rotary embeddings,
929 # no variable-length sequence metadata), so the only graph-recorded
930 # buffer is pixel_values_flat itself.
931 buffer_keys=["pixel_values_flat"],
932 out_hidden_size=self.config.text_config.hidden_size,
933 )
934
935 def get_input_modality(
936 self,
937 mm_kwargs: dict[str, Any],
938 ) -> str:
939 if "pixel_values_flat" in mm_kwargs:
940 return "image"
941 return "video"
942
943 def get_encoder_cudagraph_budget_range(
944 self,
945 vllm_config: "VllmConfig",
946 ) -> tuple[int, int]:
947 # Min: 1 tile → num_image_token output tokens.
948 min_budget = self.num_image_token
949 max_budget = min(
950 vllm_config.scheduler_config.max_num_batched_tokens,
951 vllm_config.model_config.max_model_len,
952 )
953 return (min_budget, max_budget)
954
955 def _get_internvl_patches_list(
956 self,
957 mm_kwargs: dict[str, Any],
958 ) -> list[int]:
959 """Return per-item tile counts as a plain list of ints."""
960 if self.get_input_modality(mm_kwargs) == "image":
961 patches = mm_kwargs.get("image_num_patches", [])
962 else:
963 patches = mm_kwargs.get("video_num_patches", [])
964 if isinstance(patches, torch.Tensor):
965 return patches.tolist()
966 return [int(n) for n in patches]
967
968 def get_encoder_cudagraph_item_specs(
969 self,
970 mm_kwargs: dict[str, Any],
971 ):
972 from vllm.v1.worker.encoder_cudagraph_defs import EncoderItemSpec
973
974 return [
975 EncoderItemSpec(
976 input_size=n,
977 output_tokens=n * self.num_image_token,
978 )
979 for n in self._get_internvl_patches_list(mm_kwargs)
980 ]
981
982 def select_encoder_cudagraph_items(
983 self,
984 mm_kwargs: dict[str, Any],
985 indices: list[int],
986 ) -> dict[str, Any]:
987 modality = self.get_input_modality(mm_kwargs)
988 pv_key = (
989 "pixel_values_flat" if modality == "image" else "pixel_values_flat_video"
990 )
991 patches_key = (
992 "image_num_patches" if modality == "image" else "video_num_patches"
993 )
994
995 pixel_values = mm_kwargs[pv_key]
996 patches_list = self._get_internvl_patches_list(mm_kwargs)
997
998 if len(indices) == 0:
999 return {pv_key: pixel_values[:0], patches_key: []}
1000
1001 # Compute cumulative tile offsets for slicing pixel_values.
1002 cum_patches = [0]
1003 for n in patches_list:
1004 cum_patches.append(cum_patches[-1] + n)
1005
1006 selected_pv = torch.cat(
1007 [pixel_values[cum_patches[i] : cum_patches[i + 1]] for i in indices]
1008 )
1009 selected_patches = [patches_list[i] for i in indices]
1010
1011 return {pv_key: selected_pv, patches_key: selected_patches}
1012
1013 def prepare_encoder_cudagraph_capture_inputs(
1014 self,
1015 token_budget: int,
1016 max_batch_size: int,
1017 max_frames_per_batch: int,
1018 device: torch.device,
1019 dtype: torch.dtype,
1020 path: str = "default",
1021 axis_keys: tuple[Hashable, ...] | None = None,
1022 ):
1023 from vllm.v1.worker.encoder_cudagraph_defs import (
1024 EncoderCudaGraphCaptureInputs,
1025 )
1026
1027 # Size the buffer to hold the maximum possible tiles for this budget.
1028 total_tiles = max(token_budget // self.num_image_token, 1)
1029 image_size = self.config.vision_config.image_size
1030
1031 dummy_pixel_values = torch.randn(
1032 total_tiles, 3, image_size, image_size, device=device, dtype=dtype
1033 )
1034
1035 return EncoderCudaGraphCaptureInputs(
1036 values={"pixel_values_flat": dummy_pixel_values},
1037 )
1038
1039 def prepare_encoder_cudagraph_replay_buffers(
1040 self,
1041 mm_kwargs: dict[str, Any],
1042 max_batch_size: int,
1043 max_frames_per_batch: int,
1044 path: str = "default",
1045 ):
1046 from vllm.v1.worker.encoder_cudagraph_defs import (
1047 EncoderCudaGraphReplayBuffers,
1048 )
1049
1050 modality = self.get_input_modality(mm_kwargs)
1051 pv_key = (
1052 "pixel_values_flat" if modality == "image" else "pixel_values_flat_video"
1053 )
1054 return EncoderCudaGraphReplayBuffers(
1055 values={"pixel_values_flat": mm_kwargs[pv_key]},
1056 )
1057
1058 def encoder_cudagraph_forward(
1059 self,
1060 values: dict[str, torch.Tensor],
1061 path: str = "default",
1062 ) -> torch.Tensor:
1063 # The graph is always captured with pixel_values_flat as the input
1064 # buffer. During video replay the manager copies video tiles into
1065 # this same buffer before calling graph.replay(), so we always read
1066 # from pixel_values_flat here.
1067 pixel_values = values["pixel_values_flat"]
1068 out = self.extract_feature(pixel_values) # [N, num_image_token, H]
1069 return out.view(-1, self.config.text_config.hidden_size)
1070
1071 def encoder_eager_forward(
1072 self,
1073 mm_kwargs: dict[str, Any],
1074 path: str = "default",
1075 ) -> torch.Tensor:
1076 if self.get_input_modality(mm_kwargs) == "image":
1077 pixel_values = mm_kwargs["pixel_values_flat"]
1078 else:
1079 pixel_values = mm_kwargs["pixel_values_flat_video"]
1080 out = self.extract_feature(pixel_values) # [N, num_image_token, H]
1081 return out.view(-1, self.config.text_config.hidden_size)