main
py 1,875 lines 65.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Adapted from
5 # https://github.com/huggingface/transformers/blob/19e6e80e10118f855137b90740936c0b11ac397f/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py
6 # Copyright 2024 The Qwen team.
7 # Copyright 2023 The vLLM team.
8 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
9 #
10 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
11 # and OPT implementations in this library. It has been modified from its
12 # original forms to accommodate minor architectural differences compared
13 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
14 #
15 # Licensed under the Apache License, Version 2.0 (the "License");
16 # you may not use this file except in compliance with the License.
17 # You may obtain a copy of the License at
18 #
19 # http://www.apache.org/licenses/LICENSE-2.0
20 #
21 # Unless required by applicable law or agreed to in writing, software
22 # distributed under the License is distributed on an "AS IS" BASIS,
23 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 # See the License for the specific language governing permissions and
25 # limitations under the License.
26 """Inference-only Qwen2-VL model compatible with HuggingFace weights."""
27
28 import math
29 from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping, Sequence
30 from functools import partial
31 from typing import Annotated, Any, Literal, TypeAlias, cast
32
33 import numpy as np
34 import torch
35 import torch.nn as nn
36 from einops import rearrange
37 from transformers import BatchFeature
38 from transformers.models.qwen2_vl import Qwen2VLImageProcessor, Qwen2VLProcessor
39 from transformers.models.qwen2_vl.configuration_qwen2_vl import (
40 Qwen2VLConfig,
41 Qwen2VLVisionConfig,
42 )
43 from transformers.models.qwen2_vl.image_processing_qwen2_vl import smart_resize
44
45 from vllm.config import VllmConfig
46 from vllm.config.multimodal import MultiModalDummyOptions
47 from vllm.distributed import parallel_state, tensor_model_parallel_all_gather
48 from vllm.distributed import utils as dist_utils
49 from vllm.inputs import ModalityData, MultiModalDataDict
50 from vllm.logger import init_logger
51 from vllm.model_executor.layers.activation import QuickGELU
52 from vllm.model_executor.layers.attention import MMEncoderAttention
53 from vllm.model_executor.layers.conv import Conv3dLayer
54 from vllm.model_executor.layers.fusion.mm_input_norm import (
55 IdentityInputNorm,
56 build_mm_input_norm,
57 )
58 from vllm.model_executor.layers.linear import (
59 ColumnParallelLinear,
60 RowParallelLinear,
61 )
62 from vllm.model_executor.layers.quantization import QuantizationConfig
63 from vllm.model_executor.layers.rotary_embedding import get_rope
64 from vllm.model_executor.layers.rotary_embedding.common import (
65 ApplyRotaryEmb,
66 )
67 from vllm.model_executor.models.module_mapping import MultiModelKeys
68 from vllm.multimodal import MULTIMODAL_REGISTRY
69 from vllm.multimodal.inputs import (
70 ImageItem,
71 MultiModalFeatureSpec,
72 MultiModalFieldConfig,
73 MultiModalKwargsItem,
74 MultiModalKwargsItems,
75 VideoItem,
76 )
77 from vllm.multimodal.parse import (
78 DictEmbeddingItems,
79 ImageSize,
80 ModalityDataItems,
81 MultiModalDataItems,
82 MultiModalDataParser,
83 )
84 from vllm.multimodal.processing import (
85 BaseDummyInputsBuilder,
86 BaseMultiModalProcessor,
87 BaseProcessingInfo,
88 PromptReplacement,
89 PromptUpdate,
90 )
91 from vllm.sequence import IntermediateTensors
92 from vllm.utils.tensor_schema import TensorSchema, TensorShape
93 from vllm.utils.torch_utils import async_tensor_h2d
94 from vllm.v1.attention.backends.registry import AttentionBackendEnum
95 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
96
97 from .interfaces import (
98 MultiModalEmbeddings,
99 SupportsEncoderCudaGraph,
100 SupportsLoRA,
101 SupportsMRoPE,
102 SupportsMultiModal,
103 SupportsPP,
104 )
105 from .utils import (
106 AutoWeightsLoader,
107 WeightsMapper,
108 init_vllm_registered_model,
109 maybe_prefix,
110 )
111 from .vision import (
112 get_vit_attn_backend,
113 is_vit_use_data_parallel,
114 run_dp_sharded_mrope_vision_model,
115 )
116
117 logger = init_logger(__name__)
118
119 # For profile run
120 _MAX_FRAMES_PER_VIDEO = 14
121
122 # === Vision Inputs === #
123
124
125 class Qwen2VLImagePixelInputs(TensorSchema):
126 """Dimensions:
127 - np: The total number of patches over each image over each prompt in
128 the batch
129 - ni: Number of images
130 - cps: Number of channels * patch_size * patch_size
131
132 Historical context:
133 - pixel_values shape: (num_patches, num_channels * patch_size *
134 patch_size)
135 - image_grid_thw shape: (num_images, 3) in (grid_t, grid_h, grid_w)
136 format
137 """
138
139 type: Literal["pixel_values"]
140
141 pixel_values: Annotated[
142 torch.Tensor,
143 TensorShape("np", "cps"),
144 ]
145
146 image_grid_thw: Annotated[
147 torch.Tensor,
148 TensorShape("ni", 3),
149 ]
150
151
152 class Qwen2VLImageEmbeddingInputs(TensorSchema):
153 """Dimensions:
154 - nf: Number of image features
155 - hs: Hidden size
156 - ni: Number of images
157
158 Historical context:
159 - image_embeds shape: (num_image_features, hidden_size)
160 - num_image_features varies based on the number and resolution of the
161 images.
162 - hidden_size must match the hidden size of language model backbone.
163 - image_grid_thw shape: (num_images, 3) in (grid_t, grid_h, grid_w)
164 format
165 """
166
167 type: Literal["image_embeds"]
168
169 image_embeds: Annotated[
170 torch.Tensor,
171 TensorShape("nf", "hs"),
172 ]
173
174 image_grid_thw: Annotated[
175 torch.Tensor,
176 TensorShape("ni", 3),
177 ]
178
179
180 Qwen2VLImageInputs: TypeAlias = Qwen2VLImagePixelInputs | Qwen2VLImageEmbeddingInputs
181
182
183 class Qwen2VLVideoPixelInputs(TensorSchema):
184 """Dimensions:
185 - np: The total number of patches over each video over each prompt in
186 the batch
187 - ctps: Number of channels * temporal_patch_size * patch_size *
188 patch_size
189 - nv: Number of videos
190
191 Historical context:
192 - pixel_values_videos shape: (num_patches, num_channels *
193 temporal_patch_size * patch_size * patch_size)
194 - video_grid_thw shape: (num_videos, 3) in (grid_t, grid_h, grid_w)
195 format
196 """
197
198 type: Literal["pixel_values_videos"]
199
200 pixel_values_videos: Annotated[
201 torch.Tensor,
202 TensorShape("np", "ctps"),
203 ]
204
205 video_grid_thw: Annotated[
206 torch.Tensor,
207 TensorShape("nv", 3),
208 ]
209
210
211 class Qwen2VLVideoEmbeddingInputs(TensorSchema):
212 """Dimensions:
213 - nf: Number of video features
214 - hs: Hidden size
215 - nv: Number of videos
216
217 Historical context:
218 - video_embeds shape: (num_video_features, hidden_size)
219 - num_video_features varies based on the number and resolution of the
220 videos.
221 - hidden_size must match the hidden size of language model backbone.
222 - video_grid_thw shape: (num_videos, 3) in (grid_t, grid_h, grid_w)
223 format
224 """
225
226 type: Literal["video_embeds"]
227
228 video_embeds: Annotated[
229 torch.Tensor,
230 TensorShape("nf", "hs"),
231 ]
232
233 video_grid_thw: Annotated[
234 torch.Tensor,
235 TensorShape("nv", 3),
236 ]
237
238
239 Qwen2VLVideoInputs: TypeAlias = Qwen2VLVideoPixelInputs | Qwen2VLVideoEmbeddingInputs
240
241 # === Vision Encoder === #
242
243
244 class Qwen2VisionMLP(nn.Module):
245 def __init__(
246 self,
247 in_features: int,
248 hidden_features: int,
249 act_layer: type[nn.Module] = QuickGELU,
250 quant_config: QuantizationConfig | None = None,
251 prefix: str = "",
252 ):
253 super().__init__()
254 use_data_parallel = is_vit_use_data_parallel()
255 self.fc1 = ColumnParallelLinear(
256 in_features,
257 hidden_features,
258 quant_config=quant_config,
259 prefix=f"{prefix}.fc1",
260 disable_tp=use_data_parallel,
261 )
262 self.act = act_layer()
263 self.fc2 = RowParallelLinear(
264 hidden_features,
265 in_features,
266 quant_config=quant_config,
267 prefix=f"{prefix}.fc2",
268 disable_tp=use_data_parallel,
269 )
270
271 def forward(self, x: torch.Tensor) -> torch.Tensor:
272 x_parallel, _ = self.fc1(x)
273 x_parallel = self.act(x_parallel)
274 x, _ = self.fc2(x_parallel)
275 return x
276
277
278 class Qwen2VisionAttention(nn.Module):
279 def __init__(
280 self,
281 embed_dim: int,
282 num_heads: int,
283 projection_size: int,
284 quant_config: QuantizationConfig | None = None,
285 prefix: str = "",
286 ) -> None:
287 super().__init__()
288 # Per attention head and per partition values.
289 use_data_parallel = is_vit_use_data_parallel()
290 self.tp_size = (
291 1
292 if use_data_parallel
293 else parallel_state.get_tensor_model_parallel_world_size()
294 )
295 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
296 self.hidden_size_per_attention_head = dist_utils.divide(
297 projection_size, num_heads
298 )
299 self.num_attention_heads_per_partition = dist_utils.divide(
300 num_heads, self.tp_size
301 )
302
303 self.qkv = ColumnParallelLinear(
304 input_size=embed_dim,
305 output_size=3 * projection_size,
306 quant_config=quant_config,
307 prefix=f"{prefix}.qkv",
308 disable_tp=use_data_parallel,
309 )
310 self.proj = RowParallelLinear(
311 input_size=projection_size,
312 output_size=embed_dim,
313 quant_config=quant_config,
314 prefix=f"{prefix}.proj",
315 disable_tp=use_data_parallel,
316 )
317
318 self.attn = MMEncoderAttention(
319 num_heads=self.num_attention_heads_per_partition,
320 head_size=self.hidden_size_per_attention_head,
321 scale=self.hidden_size_per_attention_head**-0.5,
322 prefix=f"{prefix}.attn",
323 )
324
325 self.apply_rotary_emb = ApplyRotaryEmb(enforce_enable=True)
326
327 def split_qkv(self, qkv: torch.Tensor) -> tuple[torch.Tensor, ...]:
328 # [s, b, 3 * head * head_dim]
329 seq_len, bs, _ = qkv.shape
330 if self.tp_size > 1:
331 qkv = tensor_model_parallel_all_gather(qkv)
332
333 # [s, b, 3 * head * head_dim] -> 3 * [s, b, head * head_dim]
334 q, k, v = qkv.chunk(3, dim=2)
335
336 # 3 * [s, b, head * head_dim]
337 if self.tp_size > 1:
338 splitter = partial(
339 dist_utils.split_tensor_along_last_dim, num_partitions=self.tp_size
340 )
341 q = splitter(q)[self.tp_rank]
342 k = splitter(k)[self.tp_rank]
343 v = splitter(v)[self.tp_rank]
344
345 # 3 * [s, b, head * head_dim] -> 3 * [s, b, head, head_dim]
346 new_shape = (
347 seq_len,
348 bs,
349 self.num_attention_heads_per_partition,
350 self.hidden_size_per_attention_head,
351 )
352 q, k, v = (x.view(*new_shape) for x in (q, k, v))
353 return q, k, v
354
355 def forward(
356 self,
357 x: torch.Tensor,
358 cu_seqlens: torch.Tensor,
359 rotary_pos_emb_cos: torch.Tensor,
360 rotary_pos_emb_sin: torch.Tensor,
361 max_seqlen: int | None = None, # Only used for Flash Attention
362 ) -> torch.Tensor:
363 # [s, b, c] --> [s, b, 3 * head * head_dim]
364 x, _ = self.qkv(x)
365
366 # [s, b, 3 * head * head_dim] -> 3 * [s, b, head, head_dim]
367 q, k, v = self.split_qkv(x)
368
369 q, k, v = (rearrange(x, "s b ... -> b s ...") for x in (q, k, v))
370
371 # [2 * b, s, heads, head_dim]
372 qk_concat = torch.cat([q, k], dim=0)
373 qk_rotated = self.apply_rotary_emb(
374 qk_concat,
375 rotary_pos_emb_cos,
376 rotary_pos_emb_sin,
377 )
378 q, k = torch.chunk(qk_rotated, 2, dim=0)
379
380 context_layer = self.attn(
381 query=q,
382 key=k,
383 value=v,
384 cu_seqlens=cu_seqlens,
385 max_seqlen=max_seqlen,
386 )
387
388 context_layer = rearrange(context_layer, "b s h d -> s b (h d)").contiguous()
389
390 output, _ = self.proj(context_layer)
391 return output
392
393
394 class Qwen2VisionBlock(nn.Module):
395 def __init__(
396 self,
397 dim: int,
398 num_heads: int,
399 mlp_ratio: float,
400 act_layer: type[nn.Module] = QuickGELU,
401 norm_layer: Callable[[int], nn.Module] | None = None,
402 quant_config: QuantizationConfig | None = None,
403 prefix: str = "",
404 ) -> None:
405 super().__init__()
406 if norm_layer is None:
407 norm_layer = partial(nn.LayerNorm, eps=1e-6)
408 self.norm1 = norm_layer(dim)
409 self.norm2 = norm_layer(dim)
410 mlp_hidden_dim = int(dim * mlp_ratio)
411
412 self.attn = Qwen2VisionAttention(
413 embed_dim=dim,
414 num_heads=num_heads,
415 projection_size=dim,
416 quant_config=quant_config,
417 prefix=f"{prefix}.attn",
418 )
419 self.mlp = Qwen2VisionMLP(
420 dim,
421 mlp_hidden_dim,
422 act_layer=act_layer,
423 quant_config=quant_config,
424 prefix=f"{prefix}.mlp",
425 )
426
427 def forward(
428 self,
429 x: torch.Tensor,
430 cu_seqlens: torch.Tensor,
431 rotary_pos_emb_cos: torch.Tensor,
432 rotary_pos_emb_sin: torch.Tensor,
433 max_seqlen: int | None = None, # Only used for Flash Attention
434 ) -> torch.Tensor:
435 x = x + self.attn(
436 self.norm1(x),
437 cu_seqlens=cu_seqlens,
438 rotary_pos_emb_cos=rotary_pos_emb_cos,
439 rotary_pos_emb_sin=rotary_pos_emb_sin,
440 max_seqlen=max_seqlen,
441 )
442
443 x = x + self.mlp(self.norm2(x))
444 return x
445
446
447 class Qwen2VisionPatchEmbed(nn.Module):
448 def __init__(
449 self,
450 patch_size: int = 14,
451 temporal_patch_size: int = 2,
452 in_channels: int = 3,
453 embed_dim: int = 1152,
454 ) -> None:
455 super().__init__()
456 self.patch_size = patch_size
457 self.temporal_patch_size = temporal_patch_size
458 self.embed_dim = embed_dim
459
460 kernel_size = (temporal_patch_size, patch_size, patch_size)
461 self.proj = Conv3dLayer(
462 in_channels,
463 embed_dim,
464 kernel_size=kernel_size,
465 stride=kernel_size,
466 bias=False,
467 )
468
469 def forward(self, x: torch.Tensor) -> torch.Tensor:
470 L, C = x.shape
471 x = x.view(L, -1, self.temporal_patch_size, self.patch_size, self.patch_size)
472 x = self.proj(x).view(L, self.embed_dim)
473 return x
474
475
476 class Qwen2VisionPatchMerger(nn.Module):
477 def __init__(
478 self,
479 d_model: int,
480 context_dim: int,
481 norm_layer: Callable[[int], nn.Module] | None = None,
482 spatial_merge_size: int = 2,
483 quant_config: QuantizationConfig | None = None,
484 prefix: str = "",
485 ) -> None:
486 super().__init__()
487 use_data_parallel = is_vit_use_data_parallel()
488 self.hidden_size = context_dim * (spatial_merge_size**2)
489 if norm_layer is None:
490 norm_layer = partial(nn.LayerNorm, eps=1e-6)
491 self.ln_q = norm_layer(context_dim)
492 self.mlp = nn.ModuleList(
493 [
494 ColumnParallelLinear(
495 self.hidden_size,
496 self.hidden_size,
497 bias=True,
498 quant_config=quant_config,
499 prefix=f"{prefix}.mlp.0",
500 disable_tp=use_data_parallel,
501 ),
502 nn.GELU(),
503 RowParallelLinear(
504 self.hidden_size,
505 d_model,
506 bias=True,
507 quant_config=quant_config,
508 prefix=f"{prefix}.mlp.2",
509 disable_tp=use_data_parallel,
510 ),
511 ]
512 )
513
514 def forward(self, x: torch.Tensor) -> torch.Tensor:
515 x = self.ln_q(x)
516 x = x.view(-1, self.hidden_size)
517
518 mlp_fc1, mlp_act, mlp_fc2 = self.mlp
519 x_parallel, _ = mlp_fc1(x)
520 x_parallel = mlp_act(x_parallel)
521 out, _ = mlp_fc2(x_parallel)
522 return out
523
524
525 class Qwen2VisionTransformer(nn.Module):
526 # Vision checkpoints store qkv pre-fused; merge separate q/k/v into qkv.
527 hf_to_vllm_mapper = WeightsMapper(
528 orig_to_new_stacked={
529 ".q_proj": (".qkv", "q"),
530 ".k_proj": (".qkv", "k"),
531 ".v_proj": (".qkv", "v"),
532 }
533 )
534
535 def __init__(
536 self,
537 vision_config: Qwen2VLVisionConfig,
538 norm_eps: float = 1e-6,
539 quant_config: QuantizationConfig | None = None,
540 input_norm: nn.Module | None = None,
541 prefix: str = "",
542 ) -> None:
543 super().__init__()
544
545 patch_size = vision_config.patch_size
546 temporal_patch_size = vision_config.temporal_patch_size
547 spatial_merge_size = vision_config.spatial_merge_size
548 in_channels = vision_config.in_channels
549 hidden_size = vision_config.hidden_size
550 embed_dim = vision_config.embed_dim
551 depth = vision_config.depth
552 num_heads = vision_config.num_heads
553 mlp_ratio = vision_config.mlp_ratio
554
555 self.use_data_parallel = is_vit_use_data_parallel()
556 self.out_hidden_size = vision_config.hidden_size
557
558 self.spatial_merge_size = spatial_merge_size
559 self.num_heads = num_heads
560 self.embed_dim = embed_dim
561
562 self.patch_embed = Qwen2VisionPatchEmbed(
563 patch_size=patch_size,
564 temporal_patch_size=temporal_patch_size,
565 in_channels=in_channels,
566 embed_dim=embed_dim,
567 )
568 self.input_norm = input_norm if input_norm is not None else IdentityInputNorm()
569
570 norm_layer = partial(nn.LayerNorm, eps=norm_eps)
571 head_dim = embed_dim // num_heads
572 self.rotary_pos_emb = get_rope(
573 head_size=head_dim,
574 max_position=8192,
575 is_neox_style=True,
576 rope_parameters={"partial_rotary_factor": 0.5},
577 )
578
579 self.blocks = nn.ModuleList(
580 [
581 Qwen2VisionBlock(
582 dim=embed_dim,
583 num_heads=num_heads,
584 mlp_ratio=mlp_ratio,
585 norm_layer=norm_layer,
586 quant_config=quant_config,
587 prefix=f"{prefix}.blocks.{layer_idx}",
588 )
589 for layer_idx in range(depth)
590 ]
591 )
592 self.merger = Qwen2VisionPatchMerger(
593 d_model=hidden_size,
594 context_dim=embed_dim,
595 norm_layer=norm_layer,
596 quant_config=quant_config,
597 prefix=f"{prefix}.merger",
598 )
599 self.attn_backend = get_vit_attn_backend(
600 head_size=head_dim,
601 dtype=torch.get_default_dtype(),
602 )
603
604 @property
605 def dtype(self) -> torch.dtype:
606 return self.patch_embed.proj.weight.dtype
607
608 @property
609 def device(self) -> torch.device:
610 return self.patch_embed.proj.weight.device
611
612 def rot_pos_emb(
613 self, grid_thw: list[list[int]]
614 ) -> tuple[torch.Tensor, torch.Tensor]:
615 device = self.device
616 pos_ids = []
617 max_grid_size = 0
618 for t, h, w in grid_thw:
619 hpos_ids = torch.arange(h, device=device).unsqueeze(1).expand(-1, w)
620 wpos_ids = torch.arange(w, device=device).unsqueeze(0).expand(h, -1)
621 hpos_ids = (
622 hpos_ids.reshape(
623 h // self.spatial_merge_size,
624 self.spatial_merge_size,
625 w // self.spatial_merge_size,
626 self.spatial_merge_size,
627 )
628 .permute(0, 2, 1, 3)
629 .flatten()
630 )
631 wpos_ids = (
632 wpos_ids.reshape(
633 h // self.spatial_merge_size,
634 self.spatial_merge_size,
635 w // self.spatial_merge_size,
636 self.spatial_merge_size,
637 )
638 .permute(0, 2, 1, 3)
639 .flatten()
640 )
641 pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
642 max_grid_size = max(max_grid_size, h, w)
643 pos_ids = torch.cat(pos_ids, dim=0)
644
645 # Use pre-computed cos_sin_cache from RotaryEmbedding
646 cos, sin = self.rotary_pos_emb.get_cos_sin(max_grid_size)
647
648 cos_combined = cos[pos_ids].flatten(1)
649 sin_combined = sin[pos_ids].flatten(1)
650 return cos_combined, sin_combined
651
652 def compute_attn_mask_seqlen(self, cu_seqlens: torch.Tensor) -> int | None:
653 max_seqlen = None
654 if self.attn_backend in {
655 AttentionBackendEnum.FLASH_ATTN,
656 AttentionBackendEnum.ROCM_AITER_FA,
657 AttentionBackendEnum.TRITON_ATTN,
658 }:
659 max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max()
660 return max_seqlen
661
662 def prepare_encoder_metadata(
663 self,
664 grid_thw: list[list[int]],
665 *,
666 max_batch_size: int | None = None,
667 max_frames_per_batch: int | None = None,
668 max_seqlen_override: int | None = None,
669 device: torch.device | None = None,
670 ) -> dict[str, torch.Tensor]:
671 if device is None:
672 device = self.device
673
674 # Position embeddings.
675 rotary_pos_emb_cos, rotary_pos_emb_sin = self.rot_pos_emb(grid_thw)
676
677 grid_thw_np = np.array(grid_thw, dtype=np.int32)
678 cu_seqlens = np.repeat(
679 grid_thw_np[:, 1] * grid_thw_np[:, 2],
680 grid_thw_np[:, 0],
681 ).cumsum(dtype=np.int32)
682 cu_seqlens = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens])
683 cu_seqlens = torch.from_numpy(cu_seqlens)
684
685 # Keep cu_seqlens shape stable across capture/replay.
686 pad_to = (
687 max_frames_per_batch if max_frames_per_batch is not None else max_batch_size
688 )
689 if pad_to is not None:
690 num_seqs = len(cu_seqlens) - 1
691 if num_seqs < pad_to:
692 cu_seqlens = torch.cat(
693 (
694 cu_seqlens,
695 torch.full(
696 (pad_to - num_seqs,),
697 cu_seqlens[-1],
698 dtype=cu_seqlens.dtype,
699 device=cu_seqlens.device,
700 ),
701 )
702 )
703
704 # Compute (or override) max seqlen used by FA-style backends.
705 if max_seqlen_override is None:
706 max_seqlen = self.compute_attn_mask_seqlen(cu_seqlens)
707 else:
708 max_seqlen = torch.tensor(max_seqlen_override, dtype=torch.int32)
709
710 return {
711 "rotary_pos_emb_cos": rotary_pos_emb_cos,
712 "rotary_pos_emb_sin": rotary_pos_emb_sin,
713 "cu_seqlens": async_tensor_h2d(cu_seqlens, device),
714 "max_seqlen": max_seqlen,
715 }
716
717 def forward(
718 self,
719 x: torch.Tensor,
720 grid_thw: torch.Tensor | list[list[int]] | None,
721 *,
722 encoder_metadata: dict[str, torch.Tensor] | None = None,
723 ) -> torch.Tensor:
724 # patchify
725 x = self.input_norm(x.to(device=self.device), self.dtype)
726 x = self.patch_embed(x)
727
728 if encoder_metadata is None:
729 assert grid_thw is not None
730 grid_thw_list = (
731 grid_thw if isinstance(grid_thw, list) else grid_thw.tolist()
732 )
733 encoder_metadata = self.prepare_encoder_metadata(grid_thw_list)
734
735 rotary_pos_emb_cos = encoder_metadata["rotary_pos_emb_cos"]
736 rotary_pos_emb_sin = encoder_metadata["rotary_pos_emb_sin"]
737 cu_seqlens = encoder_metadata["cu_seqlens"]
738 max_seqlen = encoder_metadata["max_seqlen"]
739
740 # transformers
741 x = x.unsqueeze(1)
742 for blk in self.blocks:
743 x = blk(
744 x,
745 cu_seqlens=cu_seqlens,
746 rotary_pos_emb_cos=rotary_pos_emb_cos,
747 rotary_pos_emb_sin=rotary_pos_emb_sin,
748 max_seqlen=max_seqlen,
749 )
750
751 # adapter
752 x = self.merger(x)
753
754 return x
755
756 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
757 loader = AutoWeightsLoader(self)
758 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
759
760
761 def _create_qwen2vl_field_factory(
762 spatial_merge_size: int,
763 ) -> Callable[
764 [Mapping[str, torch.Tensor]],
765 Mapping[str, MultiModalFieldConfig],
766 ]:
767 def _qwen2vl_field_config(hf_inputs: Mapping[str, torch.Tensor]):
768 image_grid_thw = hf_inputs.get("image_grid_thw", torch.empty((0, 3)))
769 image_pixel_grid_sizes = image_grid_thw.prod(-1)
770 image_embed_grid_sizes = (
771 image_pixel_grid_sizes // spatial_merge_size // spatial_merge_size
772 )
773
774 video_grid_thw = hf_inputs.get("video_grid_thw", torch.empty((0, 3)))
775 video_grid_sizes = video_grid_thw.prod(-1)
776 video_embed_grid_sizes = (
777 video_grid_sizes // spatial_merge_size // spatial_merge_size
778 )
779
780 return dict(
781 pixel_values=MultiModalFieldConfig.flat_from_sizes(
782 "image", image_pixel_grid_sizes
783 ),
784 image_embeds=MultiModalFieldConfig.flat_from_sizes(
785 "image", image_embed_grid_sizes
786 ),
787 image_grid_thw=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
788 pixel_values_videos=MultiModalFieldConfig.flat_from_sizes(
789 "video", video_grid_sizes
790 ),
791 video_embeds=MultiModalFieldConfig.flat_from_sizes(
792 "video", video_embed_grid_sizes
793 ),
794 video_grid_thw=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
795 timestamps=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
796 )
797
798 return _qwen2vl_field_config
799
800
801 class Qwen2VLMultiModalDataParser(MultiModalDataParser):
802 # The patch grid is what sizes the placeholder range.
803 embedding_fields = {
804 "image": {"image_embeds": "values", "image_grid_thw": "metadata"},
805 "video": {"video_embeds": "values", "video_grid_thw": "metadata"},
806 }
807
808 def __init__(self, spatial_merge_size: int, *args, **kwargs):
809 self._spatial_merge_size = spatial_merge_size
810 super().__init__(*args, **kwargs)
811
812 def _parse_image_data(
813 self,
814 data: dict[str, torch.Tensor] | ModalityData[ImageItem],
815 ) -> ModalityDataItems[Any, Any] | None:
816 if isinstance(data, dict):
817 required, optional = self.embedding_field_sets("image")
818 return DictEmbeddingItems(
819 data,
820 modality="image",
821 required_fields=required,
822 optional_fields=optional,
823 fields_factory=_create_qwen2vl_field_factory(self._spatial_merge_size),
824 )
825
826 return super()._parse_image_data(data)
827
828 def _parse_video_data(
829 self,
830 data: dict[str, torch.Tensor] | ModalityData[VideoItem],
831 ) -> ModalityDataItems[Any, Any] | None:
832 if isinstance(data, dict):
833 required, optional = self.embedding_field_sets("video")
834 return DictEmbeddingItems(
835 data,
836 modality="video",
837 required_fields=required,
838 optional_fields=optional,
839 fields_factory=_create_qwen2vl_field_factory(self._spatial_merge_size),
840 )
841
842 return super()._parse_video_data(data)
843
844
845 class Qwen2VLProcessingInfo(BaseProcessingInfo):
846 def get_hf_config(self):
847 return self.ctx.get_hf_config(Qwen2VLConfig)
848
849 def get_hf_processor(self, **kwargs: object) -> Qwen2VLProcessor:
850 return self.ctx.get_hf_processor(
851 Qwen2VLProcessor,
852 **kwargs,
853 )
854
855 def get_image_processor(self, **kwargs: object) -> Qwen2VLImageProcessor:
856 return self.get_hf_processor(**kwargs).image_processor
857
858 def get_data_parser(self):
859 return Qwen2VLMultiModalDataParser(
860 self.get_hf_config().vision_config.spatial_merge_size,
861 expected_hidden_size=self._get_expected_hidden_size(),
862 allow_missing_mm_embeddings=self.allow_missing_mm_embeddings,
863 )
864
865 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
866 return {"image": None, "video": None}
867
868 @staticmethod
869 def _complete_mm_processor_size_aliases(
870 mm_kwargs: Mapping[str, object],
871 ) -> dict[str, object]:
872 """Complete missing Qwen-VL size aliases.
873
874 Qwen-VL accepts ``size.shortest_edge`` / ``min_pixels`` and
875 ``size.longest_edge`` / ``max_pixels`` as equivalent controls. When
876 only one representation is provided with a non-``None`` value, add the
877 missing equivalent representation. If both are already present, leave
878 them unchanged even when their values conflict.
879
880 Apply this independently to the flat kwargs and to each existing
881 ``images_kwargs`` / ``videos_kwargs`` mapping. Values are not propagated
882 between these mappings.
883 """
884
885 def add_missing_size_aliases(
886 kwargs: Mapping[str, object],
887 ) -> dict[str, object]:
888 """Add missing size aliases within a kwargs mapping."""
889 out_kwargs = dict(kwargs)
890 # Copy `size` separately because `out_kwargs` is a shallow copy and
891 # size edges may be added below.
892 raw_size = cast(
893 Mapping[str, object] | None,
894 out_kwargs.get("size"),
895 )
896 size_kwargs = {} if raw_size is None else dict(raw_size)
897
898 for edge_key, pixel_key in (
899 ("shortest_edge", "min_pixels"),
900 ("longest_edge", "max_pixels"),
901 ):
902 # Add the pixel alias from `size` when only the size edge is
903 # present.
904 if pixel_key not in out_kwargs:
905 if edge_key in size_kwargs and size_kwargs[edge_key] is not None:
906 out_kwargs[pixel_key] = size_kwargs[edge_key]
907 continue
908
909 pixel_value = out_kwargs[pixel_key]
910 # HF treats `min_pixels` / `max_pixels` set to None as no size
911 # override, so do not propagate them to the equivalent size edge.
912 if pixel_value is None:
913 continue
914
915 # Add the size edge when only the pixel alias is present.
916 if edge_key not in size_kwargs:
917 size_kwargs[edge_key] = pixel_value
918 out_kwargs["size"] = size_kwargs
919
920 return out_kwargs
921
922 prepared_mm_kwargs = add_missing_size_aliases(mm_kwargs)
923 # Complete aliases independently inside existing image/video kwargs;
924 # flat values are not copied into the nested mappings.
925 for nested_kwargs_key in ("images_kwargs", "videos_kwargs"):
926 nested_kwargs = prepared_mm_kwargs.get(nested_kwargs_key)
927 if isinstance(nested_kwargs, Mapping):
928 prepared_mm_kwargs[nested_kwargs_key] = add_missing_size_aliases(
929 nested_kwargs
930 )
931
932 return prepared_mm_kwargs
933
934 def _merge_and_resolve_mm_processor_kwargs(
935 self,
936 mm_kwargs: Mapping[str, object],
937 ) -> dict[str, Any]:
938 """Merge configured and request Qwen-VL ``mm_processor_kwargs``.
939
940 Request-side ``size.shortest_edge`` / ``min_pixels`` and
941 ``size.longest_edge`` / ``max_pixels`` aliases are completed before the
942 merge so request precedence is preserved across equivalent
943 representations.
944 """
945 # Complete request-side Qwen-VL size aliases before merging with
946 # configured kwargs.
947 prepared_mm_kwargs = self._complete_mm_processor_size_aliases(mm_kwargs)
948
949 return super()._merge_and_resolve_mm_processor_kwargs(prepared_mm_kwargs)
950
951 @staticmethod
952 def _get_vision_size(
953 mm_kwargs: Mapping[str, object],
954 default_size: Mapping[str, int] | None = None,
955 ) -> dict[str, int]:
956 """Resolve Qwen-VL vision size kwargs with optional processor defaults.
957
958 ``size`` overrides any processor defaults, then ``min_pixels`` and
959 ``max_pixels`` override the corresponding size edges.
960 """
961 size = dict(default_size) if default_size is not None else {}
962
963 override_size = cast(
964 Mapping[str, int] | None,
965 mm_kwargs.get("size"),
966 )
967 if override_size is not None:
968 size.update(override_size)
969
970 min_pixels = cast(int | None, mm_kwargs.get("min_pixels"))
971 if min_pixels is not None:
972 size["shortest_edge"] = min_pixels
973
974 max_pixels = cast(int | None, mm_kwargs.get("max_pixels"))
975 if max_pixels is not None:
976 size["longest_edge"] = max_pixels
977
978 return size
979
980 def get_mm_max_tokens_per_item(
981 self,
982 seq_len: int,
983 mm_counts: Mapping[str, int],
984 ) -> Mapping[str, int]:
985 max_image_tokens = self.get_max_image_tokens()
986 max_video_tokens = self.get_max_video_tokens(seq_len, mm_counts)
987 return {"image": max_image_tokens, "video": max_video_tokens}
988
989 def _get_vision_info(
990 self,
991 *,
992 image_width: int,
993 image_height: int,
994 num_frames: int = 1,
995 do_resize: bool = True,
996 image_processor: Qwen2VLImageProcessor,
997 mm_kwargs: Mapping[str, object],
998 modality: Literal["image", "video"] | None = None,
999 ) -> tuple[ImageSize, int]:
1000 hf_config = self.get_hf_config()
1001 vision_config = hf_config.vision_config
1002 patch_size = vision_config.patch_size
1003 merge_size = vision_config.spatial_merge_size
1004 temporal_patch_size = vision_config.temporal_patch_size
1005
1006 if modality is None:
1007 modality = "image"
1008
1009 merged_mm_kwargs = self._merge_and_resolve_mm_processor_kwargs(mm_kwargs)
1010 scope = "images_kwargs" if modality == "image" else "videos_kwargs"
1011 scoped_mm_kwargs = merged_mm_kwargs.get(scope, {})
1012 size = self._get_vision_size(
1013 scoped_mm_kwargs,
1014 default_size=image_processor.size,
1015 )
1016
1017 if do_resize:
1018 resized_height, resized_width = smart_resize(
1019 height=image_height,
1020 width=image_width,
1021 factor=patch_size * merge_size,
1022 min_pixels=size["shortest_edge"],
1023 max_pixels=size["longest_edge"],
1024 )
1025 preprocessed_size = ImageSize(width=resized_width, height=resized_height)
1026 else:
1027 preprocessed_size = ImageSize(width=image_width, height=image_height)
1028
1029 # NOTE: Frames are padded to be divisible by `temporal_patch_size`
1030 # https://github.com/huggingface/transformers/blob/v5.13.0/src/transformers/models/qwen2_vl/video_processing_qwen2_vl.py#L249-L252
1031 padded_num_frames = num_frames + (-num_frames % temporal_patch_size)
1032
1033 grid_t = max(padded_num_frames // temporal_patch_size, 1)
1034 grid_h = preprocessed_size.height // patch_size
1035 grid_w = preprocessed_size.width // patch_size
1036
1037 num_patches = grid_t * grid_h * grid_w
1038 num_vision_tokens = num_patches // (merge_size**2)
1039
1040 return preprocessed_size, num_vision_tokens
1041
1042 def get_num_image_tokens(
1043 self,
1044 *,
1045 image_width: int,
1046 image_height: int,
1047 image_processor: Qwen2VLImageProcessor,
1048 mm_kwargs: Mapping[str, object],
1049 ) -> int:
1050 _, num_image_tokens = self._get_vision_info(
1051 image_width=image_width,
1052 image_height=image_height,
1053 num_frames=1,
1054 image_processor=image_processor,
1055 mm_kwargs=mm_kwargs,
1056 modality="image",
1057 )
1058 return num_image_tokens
1059
1060 def get_num_video_tokens(
1061 self,
1062 *,
1063 image_width: int,
1064 image_height: int,
1065 num_frames: int,
1066 image_processor: Qwen2VLImageProcessor,
1067 mm_kwargs: Mapping[str, object],
1068 ) -> int:
1069 _, num_video_tokens = self._get_vision_info(
1070 image_width=image_width,
1071 image_height=image_height,
1072 num_frames=num_frames,
1073 image_processor=image_processor,
1074 mm_kwargs=mm_kwargs,
1075 modality="video",
1076 )
1077 return num_video_tokens
1078
1079 def get_image_size_with_most_features(
1080 self, max_pixels: int | None = None
1081 ) -> ImageSize:
1082 # NOTE: Simply processing a huge size with _get_vision_info might not give a
1083 # size that maximizes the number of features, i.e., the number of (merged)
1084 # patches. This is because the number of patches limits the allowed aspect
1085 # ratios. For example, suppose the maximum number of patches is 1280. A square
1086 # image cannot be broken down into 1280 patches, so feeding a giant square image
1087 # into _get_vision_info will not yield a size that maximizes the number of
1088 # patches. Therefore, we directly factorize the maximum number of patches into
1089 # height and width. The tricky part is to avoid extreme aspect ratios (>200 for
1090 # qwen2-vl). If we can't find a suitable aspect ratio, we decrease the number of
1091 # patches and retry. This is safe because the processor does not accept extreme
1092 # aspect ratios, so there is no valid post-resize image with the number of
1093 # patches that yields extreme aspect ratios.
1094
1095 hf_config = self.get_hf_config()
1096 vision_config = hf_config.vision_config
1097 patch_size = vision_config.patch_size
1098 merge_size = vision_config.spatial_merge_size
1099
1100 if max_pixels is None:
1101 image_processor = self.get_image_processor()
1102
1103 merged_mm_kwargs = self._merge_and_resolve_mm_processor_kwargs({})
1104 image_mm_kwargs = merged_mm_kwargs.get("images_kwargs", {})
1105 size = self._get_vision_size(
1106 image_mm_kwargs,
1107 default_size=image_processor.size,
1108 )
1109
1110 max_pixels = size["longest_edge"]
1111
1112 unit = patch_size * merge_size
1113 max_seq_len = max_pixels // (unit * unit)
1114
1115 def closest_factor_pair(n: int) -> tuple[int, int]:
1116 # left <= right
1117 for d in range(math.isqrt(n), 0, -1):
1118 if n % d == 0:
1119 return d, n // d
1120 return 1, n
1121
1122 height_factor, width_factor = 1, max_seq_len
1123 for seq_len in range(max_seq_len, 0, -1):
1124 height_factor, width_factor = closest_factor_pair(seq_len)
1125 if width_factor / height_factor <= 200:
1126 break
1127
1128 return ImageSize(width=unit * width_factor, height=unit * height_factor)
1129
1130 def get_max_image_tokens(self) -> int:
1131 image_processor = self.get_image_processor()
1132 target_width, target_height = self.get_image_size_with_most_features()
1133
1134 return self.get_num_image_tokens(
1135 image_width=target_width,
1136 image_height=target_height,
1137 image_processor=image_processor,
1138 mm_kwargs={},
1139 )
1140
1141 def _get_max_video_frames(self, max_tokens: int, start_num_frames: int = 1) -> int:
1142 image_processor = self.get_image_processor()
1143 target_width, target_height = self.get_image_size_with_most_features()
1144
1145 num_frames = start_num_frames
1146
1147 while True:
1148 next_num_frames = num_frames + 1
1149 next_max_tokens = self.get_num_video_tokens(
1150 image_width=target_width,
1151 image_height=target_height,
1152 num_frames=next_num_frames,
1153 image_processor=image_processor,
1154 mm_kwargs={},
1155 )
1156
1157 if next_max_tokens > max_tokens:
1158 break
1159
1160 num_frames = next_num_frames
1161
1162 return num_frames
1163
1164 def get_num_frames_with_most_features(
1165 self,
1166 seq_len: int,
1167 mm_counts: Mapping[str, int],
1168 max_frames_per_video: int = _MAX_FRAMES_PER_VIDEO,
1169 ) -> int:
1170 max_videos = mm_counts.get("video", 0)
1171
1172 max_total_frames = self._get_max_video_frames(seq_len)
1173 max_frames_per_video = min(
1174 max_total_frames // max(max_videos, 1), max_frames_per_video
1175 )
1176
1177 return max(max_frames_per_video, 1)
1178
1179 def get_max_video_tokens(
1180 self,
1181 seq_len: int,
1182 mm_counts: Mapping[str, int],
1183 ) -> int:
1184 image_processor = self.get_image_processor()
1185 target_width, target_height = self.get_image_size_with_most_features()
1186
1187 return self.get_num_video_tokens(
1188 image_width=target_width,
1189 image_height=target_height,
1190 num_frames=self.get_num_frames_with_most_features(seq_len, mm_counts),
1191 image_processor=image_processor,
1192 mm_kwargs={},
1193 )
1194
1195
1196 class Qwen2VLDummyInputsBuilder(BaseDummyInputsBuilder[Qwen2VLProcessingInfo]):
1197 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
1198 num_images = mm_counts.get("image", 0)
1199 num_videos = mm_counts.get("video", 0)
1200
1201 hf_processor = self.info.get_hf_processor()
1202 image_token: str = hf_processor.image_token
1203 video_token: str = hf_processor.video_token
1204
1205 return image_token * num_images + video_token * num_videos
1206
1207 def get_dummy_mm_data(
1208 self,
1209 seq_len: int,
1210 mm_counts: Mapping[str, int],
1211 mm_options: MultiModalDummyOptions,
1212 ) -> MultiModalDataDict:
1213 target_width, target_height = self.info.get_image_size_with_most_features()
1214 target_num_frames = self.info.get_num_frames_with_most_features(
1215 seq_len, mm_counts
1216 )
1217
1218 return {
1219 "image": self._get_dummy_images(
1220 width=target_width,
1221 height=target_height,
1222 num_images=mm_counts.get("image", 0),
1223 overrides=mm_options.get("image"),
1224 ),
1225 "video": self._get_dummy_videos(
1226 width=target_width,
1227 height=target_height,
1228 num_frames=target_num_frames,
1229 num_videos=mm_counts.get("video", 0),
1230 overrides=mm_options.get("video"),
1231 ),
1232 }
1233
1234
1235 class Qwen2VLMultiModalProcessor(BaseMultiModalProcessor[Qwen2VLProcessingInfo]):
1236 def _get_prompt_updates(
1237 self,
1238 mm_items: MultiModalDataItems,
1239 hf_processor_mm_kwargs: Mapping[str, Any],
1240 out_mm_kwargs: MultiModalKwargsItems,
1241 ) -> Sequence[PromptUpdate]:
1242 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
1243 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
1244 tokenizer = self.info.get_tokenizer()
1245 vocab = tokenizer.get_vocab()
1246
1247 placeholder = {
1248 "image": vocab[hf_processor.image_token],
1249 "video": vocab[hf_processor.video_token],
1250 }
1251
1252 merge_length = image_processor.merge_size**2
1253
1254 def get_replacement_qwen2vl(item_idx: int, modality: str):
1255 out_item = out_mm_kwargs[modality][item_idx]
1256 grid_thw = out_item[f"{modality}_grid_thw"].data
1257 assert isinstance(grid_thw, torch.Tensor)
1258
1259 num_tokens = int(grid_thw.prod()) // merge_length
1260 return [placeholder[modality]] * num_tokens
1261
1262 return [
1263 PromptReplacement(
1264 modality=modality,
1265 target=[placeholder[modality]],
1266 replacement=partial(get_replacement_qwen2vl, modality=modality),
1267 )
1268 for modality in ("image", "video")
1269 ]
1270
1271 def _get_mm_fields_config(
1272 self,
1273 hf_inputs: BatchFeature,
1274 hf_processor_mm_kwargs: Mapping[str, object],
1275 ) -> Mapping[str, MultiModalFieldConfig]:
1276 return _create_qwen2vl_field_factory(
1277 self.info.get_hf_config().vision_config.spatial_merge_size
1278 )(hf_inputs)
1279
1280
1281 @MULTIMODAL_REGISTRY.register_processor(
1282 Qwen2VLMultiModalProcessor,
1283 info=Qwen2VLProcessingInfo,
1284 dummy_inputs=Qwen2VLDummyInputsBuilder,
1285 )
1286 class Qwen2VLForConditionalGeneration(
1287 nn.Module,
1288 SupportsMultiModal,
1289 SupportsLoRA,
1290 SupportsPP,
1291 SupportsMRoPE,
1292 SupportsEncoderCudaGraph,
1293 ):
1294 # To ensure correct weight loading and mapping.
1295 hf_to_vllm_mapper = WeightsMapper(
1296 orig_to_new_prefix={
1297 # mapping for new names in checkpoint saved after transformers v4.52
1298 "model.language_model.": "language_model.model.",
1299 "model.visual.": "visual.",
1300 # mapping for original checkpoint
1301 "lm_head.": "language_model.lm_head.",
1302 "model.": "language_model.model.",
1303 }
1304 )
1305
1306 supports_encoder_tp_data = True
1307 supports_mm_device_do_normalize = True
1308 supports_tower_connector_lora = True
1309
1310 def iter_mm_grid_thw(
1311 self, mm_features: list[MultiModalFeatureSpec]
1312 ) -> Iterator[tuple[int, int, int, int, float]]:
1313 """Iterate over multimodal features and yield grid information.
1314
1315 Args:
1316 mm_features: List of multimodal feature specifications
1317
1318 Yields:
1319 Tuple of (offset, grid_t, grid_h, grid_w, t_factor) for each frame/image
1320
1321 """
1322 spatial_merge_size = self.config.vision_config.spatial_merge_size
1323 tokens_per_second = getattr(self.config.vision_config, "tokens_per_second", 1.0)
1324 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1325 offset = mm_feature.mm_position.offset
1326 data = mm_feature.data
1327 assert data is not None
1328 if mm_feature.modality == "image":
1329 image_grid_thw = data["image_grid_thw"]
1330 assert isinstance(image_grid_thw.data, torch.Tensor)
1331 t, h, w = image_grid_thw.data.tolist()
1332 assert t == 1, f"Image must have 1 frame, got {t}"
1333 yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1334 elif mm_feature.modality == "video":
1335 video_grid_thw = data["video_grid_thw"]
1336 assert isinstance(video_grid_thw.data, torch.Tensor)
1337 t, h, w = video_grid_thw.data.tolist()
1338 second_per_grid_ts = 1.0
1339 second_per_grid_ts_field = data.get("second_per_grid_ts")
1340 if second_per_grid_ts_field is not None:
1341 assert isinstance(second_per_grid_ts_field.data, torch.Tensor)
1342 second_per_grid_ts = second_per_grid_ts_field.data.item()
1343 t_factor = second_per_grid_ts * tokens_per_second
1344 yield (
1345 offset,
1346 t,
1347 h // spatial_merge_size,
1348 w // spatial_merge_size,
1349 t_factor,
1350 )
1351 else:
1352 raise ValueError(f"Unsupported modality: {mm_feature.modality}")
1353
1354 def get_mrope_input_positions(
1355 self,
1356 input_tokens: list[int],
1357 mm_features: list[MultiModalFeatureSpec],
1358 ) -> tuple[torch.Tensor, int]:
1359 llm_pos_ids_list: list = []
1360 st = 0
1361
1362 for (
1363 offset,
1364 llm_grid_t,
1365 llm_grid_h,
1366 llm_grid_w,
1367 t_factor,
1368 ) in self.iter_mm_grid_thw(mm_features):
1369 text_len = offset - st
1370 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1371 llm_pos_ids_list.append(
1372 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1373 )
1374
1375 grid_indices = np.indices((llm_grid_t, llm_grid_h, llm_grid_w))
1376 if t_factor != 1.0:
1377 grid_indices[0] = (grid_indices[0] * t_factor).astype(np.int64)
1378 llm_pos_ids_list.append(grid_indices.reshape(3, -1) + text_len + st_idx)
1379 st = offset + llm_grid_t * llm_grid_h * llm_grid_w
1380
1381 if st < len(input_tokens):
1382 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1383 text_len = len(input_tokens) - st
1384 llm_pos_ids_list.append(
1385 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1386 )
1387
1388 llm_positions = np.concatenate(llm_pos_ids_list, axis=1).reshape(3, -1)
1389 mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
1390
1391 return torch.from_numpy(llm_positions), mrope_position_delta
1392
1393 @classmethod
1394 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
1395 if modality.startswith("image"):
1396 return "<|vision_start|><|image_pad|><|vision_end|>"
1397 if modality.startswith("video"):
1398 return "<|vision_start|><|video_pad|><|vision_end|>"
1399
1400 raise ValueError("Only image or video modality is supported")
1401
1402 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1403 super().__init__()
1404 config: Qwen2VLConfig = vllm_config.model_config.hf_config
1405 quant_config = vllm_config.quant_config
1406 multimodal_config = vllm_config.model_config.get_multimodal_config()
1407 self.model_config = vllm_config.model_config
1408 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1409 self.config = config
1410 self.multimodal_config = multimodal_config
1411
1412 with self._mark_tower_model(vllm_config, {"image", "video"}):
1413 self.visual = Qwen2VisionTransformer(
1414 config.vision_config,
1415 norm_eps=getattr(config, "rms_norm_eps", 1e-6),
1416 quant_config=quant_config,
1417 input_norm=build_mm_input_norm(self.model_config),
1418 prefix=maybe_prefix(prefix, "visual"),
1419 )
1420
1421 with self._mark_language_model(vllm_config):
1422 self.language_model = init_vllm_registered_model(
1423 vllm_config=vllm_config,
1424 prefix=maybe_prefix(prefix, "language_model"),
1425 architectures=["Qwen2ForCausalLM"],
1426 )
1427
1428 self.make_empty_intermediate_tensors = (
1429 self.language_model.make_empty_intermediate_tensors
1430 )
1431
1432 def _parse_and_validate_image_input(
1433 self, **kwargs: object
1434 ) -> Qwen2VLImageInputs | None:
1435 pixel_values = kwargs.pop("pixel_values", None)
1436 image_embeds = kwargs.pop("image_embeds", None)
1437 image_grid_thw = kwargs.pop("image_grid_thw", None)
1438
1439 if pixel_values is None and image_embeds is None:
1440 return None
1441
1442 if pixel_values is not None:
1443 return Qwen2VLImagePixelInputs(
1444 type="pixel_values",
1445 pixel_values=pixel_values,
1446 image_grid_thw=image_grid_thw,
1447 )
1448
1449 else:
1450 return Qwen2VLImageEmbeddingInputs(
1451 type="image_embeds",
1452 image_embeds=image_embeds,
1453 image_grid_thw=image_grid_thw,
1454 )
1455
1456 def _parse_and_validate_video_input(
1457 self, **kwargs: object
1458 ) -> Qwen2VLVideoInputs | None:
1459 pixel_values_videos = kwargs.pop("pixel_values_videos", None)
1460 video_embeds = kwargs.pop("video_embeds", None)
1461 video_grid_thw = kwargs.pop("video_grid_thw", None)
1462
1463 if pixel_values_videos is None and video_embeds is None:
1464 return None
1465
1466 if pixel_values_videos is not None:
1467 return Qwen2VLVideoPixelInputs(
1468 type="pixel_values_videos",
1469 pixel_values_videos=pixel_values_videos,
1470 video_grid_thw=video_grid_thw,
1471 )
1472
1473 else:
1474 return Qwen2VLVideoEmbeddingInputs(
1475 type="video_embeds",
1476 video_embeds=video_embeds,
1477 video_grid_thw=video_grid_thw,
1478 )
1479
1480 def _process_image_input(
1481 self, image_input: Qwen2VLImageInputs
1482 ) -> tuple[torch.Tensor, ...]:
1483 grid_thw = image_input["image_grid_thw"]
1484 assert grid_thw.ndim == 2
1485
1486 if image_input["type"] == "image_embeds":
1487 image_embeds = image_input["image_embeds"].type(self.visual.dtype)
1488 else:
1489 pixel_values = image_input["pixel_values"]
1490
1491 if self.use_data_parallel:
1492 return run_dp_sharded_mrope_vision_model(
1493 self.visual,
1494 pixel_values,
1495 grid_thw.tolist(),
1496 rope_type="rope_3d",
1497 )
1498 else:
1499 image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1500
1501 # Split concatenated embeddings for each image item.
1502 merge_size = self.visual.spatial_merge_size
1503 sizes = (grid_thw.prod(-1) // merge_size // merge_size).tolist()
1504 return image_embeds.split(sizes)
1505
1506 def _process_video_input(
1507 self, video_input: Qwen2VLVideoInputs
1508 ) -> tuple[torch.Tensor, ...]:
1509 grid_thw = video_input["video_grid_thw"]
1510 assert grid_thw.ndim == 2
1511
1512 if video_input["type"] == "video_embeds":
1513 video_embeds = video_input["video_embeds"].type(self.visual.dtype)
1514 else:
1515 pixel_values_videos = video_input["pixel_values_videos"]
1516 if self.use_data_parallel:
1517 return run_dp_sharded_mrope_vision_model(
1518 self.visual,
1519 pixel_values_videos,
1520 grid_thw.tolist(),
1521 rope_type="rope_3d",
1522 )
1523 else:
1524 video_embeds = self.visual(pixel_values_videos, grid_thw=grid_thw)
1525
1526 # Split concatenated embeddings for each video item.
1527 merge_size = self.visual.spatial_merge_size
1528 sizes = (grid_thw.prod(-1) // merge_size // merge_size).tolist()
1529 return video_embeds.split(sizes)
1530
1531 def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
1532 modalities: dict[str, Qwen2VLImageInputs | Qwen2VLVideoInputs | None] = {}
1533
1534 # Preserve the order of modalities if there are multiple of them
1535 # from the order of kwargs.
1536 for input_key in kwargs:
1537 if (
1538 input_key in ("pixel_values", "image_embeds")
1539 and "images" not in modalities
1540 ):
1541 modalities["images"] = self._parse_and_validate_image_input(**kwargs)
1542 if (
1543 input_key in ("pixel_values_videos", "video_embeds")
1544 and "videos" not in modalities
1545 ):
1546 modalities["videos"] = self._parse_and_validate_video_input(**kwargs)
1547
1548 return modalities
1549
1550 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
1551 modalities = self._parse_and_validate_multimodal_inputs(**kwargs)
1552 if not modalities:
1553 return []
1554
1555 # The result multimodal_embeddings is tuple of tensors, with each
1556 # tensor correspoending to a multimodal data item (image or video).
1557 multimodal_embeddings: tuple[torch.Tensor, ...] = ()
1558
1559 # NOTE: It is important to iterate over the keys in this dictionary
1560 # to preserve the order of the modalities.
1561 for modality in modalities:
1562 if modality == "images":
1563 image_input = modalities["images"]
1564 image_embeddings = self._process_image_input(image_input)
1565 multimodal_embeddings += tuple(image_embeddings)
1566 if modality == "videos":
1567 video_input = modalities["videos"]
1568 video_embeddings = self._process_video_input(video_input)
1569 multimodal_embeddings += tuple(video_embeddings)
1570
1571 return multimodal_embeddings
1572
1573 # -- SupportsEncoderCudaGraph protocol methods --
1574
1575 def get_encoder_cudagraph_config(self):
1576 from vllm.v1.worker.encoder_cudagraph_defs import (
1577 EncoderCudaGraphConfig,
1578 )
1579
1580 max_frames = self.get_max_frames_per_video()
1581 return EncoderCudaGraphConfig(
1582 modalities=["image", "video"],
1583 buffer_keys=[
1584 "pixel_values",
1585 "rotary_pos_emb_cos",
1586 "rotary_pos_emb_sin",
1587 "cu_seqlens",
1588 "max_seqlen",
1589 ],
1590 out_hidden_size=self.visual.out_hidden_size,
1591 max_frames_per_video=max_frames,
1592 )
1593
1594 def get_input_modality(self, mm_kwargs: dict[str, Any]) -> str:
1595 if "image_grid_thw" in mm_kwargs:
1596 return "image"
1597 return "video"
1598
1599 def get_max_frames_per_video(self) -> int:
1600 mm_registry = MULTIMODAL_REGISTRY
1601 info = mm_registry.get_processing_info(self.model_config)
1602 assert isinstance(info, Qwen2VLProcessingInfo)
1603 max_frames_per_video = info.get_num_frames_with_most_features(
1604 seq_len=self.model_config.max_model_len,
1605 mm_counts={"video": self.multimodal_config.get_limit_per_prompt("video")},
1606 )
1607 return max_frames_per_video
1608
1609 def get_encoder_cudagraph_budget_range(
1610 self,
1611 vllm_config: VllmConfig,
1612 ) -> tuple[int, int]:
1613 # Min: estimated smallest possible encoder input.
1614 # 224x224 image -> 16x16 patches (patch_size=14)
1615 # spatial_merge_size=2 -> 8x8 = 64 tokens
1616 min_budget = 64
1617 # Max: capped by max_num_batched_tokens
1618 max_budget = min(
1619 vllm_config.scheduler_config.max_num_batched_tokens,
1620 self.model_config.max_model_len,
1621 )
1622 return (min_budget, max_budget)
1623
1624 def _get_pixel_values_by_modality(self, mm_kwargs: dict[str, Any]) -> torch.Tensor:
1625 if self.get_input_modality(mm_kwargs) == "image":
1626 pixel_values = mm_kwargs["pixel_values"]
1627 else:
1628 pixel_values = mm_kwargs["pixel_values_videos"]
1629 return pixel_values
1630
1631 def _get_grid_thw_by_modality(self, mm_kwargs: dict[str, Any]) -> list[list[int]]:
1632 grid_thw_key = f"{self.get_input_modality(mm_kwargs)}_grid_thw"
1633 grid_thw = mm_kwargs[grid_thw_key]
1634 if not isinstance(grid_thw, list):
1635 grid_thw = grid_thw.tolist()
1636 return grid_thw
1637
1638 def get_encoder_cudagraph_item_specs(
1639 self,
1640 mm_kwargs: dict[str, Any],
1641 ):
1642 from vllm.v1.worker.encoder_cudagraph_defs import EncoderItemSpec
1643
1644 m = self.visual.spatial_merge_size
1645 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1646 return [
1647 EncoderItemSpec(
1648 input_size=t * h * w,
1649 output_tokens=t * (h // m) * (w // m),
1650 )
1651 for t, h, w in grid_thw
1652 ]
1653
1654 def select_encoder_cudagraph_items(
1655 self, mm_kwargs: dict[str, Any], indices: list[int]
1656 ) -> dict[str, Any]:
1657 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1658 pixel_values = self._get_pixel_values_by_modality(mm_kwargs)
1659
1660 if len(indices) == 0:
1661 if self.get_input_modality(mm_kwargs) == "image":
1662 return {
1663 "pixel_values": pixel_values[:0],
1664 "image_grid_thw": [],
1665 }
1666 else:
1667 return {
1668 "pixel_values_videos": pixel_values[:0],
1669 "video_grid_thw": [],
1670 }
1671
1672 # Compute cumulative patch offsets for slicing pixel_values.
1673 patches_per_item = [t * h * w for t, h, w in grid_thw]
1674 cum_patches = [0]
1675 for p in patches_per_item:
1676 cum_patches.append(cum_patches[-1] + p)
1677
1678 selected_pv = torch.cat(
1679 [pixel_values[cum_patches[i] : cum_patches[i + 1]] for i in indices]
1680 )
1681 selected_grid = [grid_thw[i] for i in indices]
1682
1683 if self.get_input_modality(mm_kwargs) == "image":
1684 return {
1685 "pixel_values": selected_pv,
1686 "image_grid_thw": selected_grid,
1687 }
1688 else:
1689 return {
1690 "pixel_values_videos": selected_pv,
1691 "video_grid_thw": selected_grid,
1692 }
1693
1694 def prepare_encoder_cudagraph_capture_inputs(
1695 self,
1696 token_budget: int,
1697 max_batch_size: int,
1698 max_frames_per_batch: int,
1699 device: torch.device,
1700 dtype: torch.dtype,
1701 path: str = "default",
1702 axis_keys: tuple[Hashable, ...] | None = None,
1703 ):
1704 from vllm.v1.worker.encoder_cudagraph_defs import (
1705 EncoderCudaGraphCaptureInputs,
1706 )
1707
1708 spatial_merge_size = self.visual.spatial_merge_size
1709 # Use ceil so captured capacity is never smaller than token_budget.
1710 per_mm_item_output = (token_budget + max_batch_size - 1) // max_batch_size
1711
1712 frames_per_item = max_frames_per_batch // max_batch_size
1713 if frames_per_item > 1:
1714 tokens_per_frame = (
1715 per_mm_item_output + frames_per_item - 1
1716 ) // frames_per_item
1717 grid_config = [
1718 [
1719 frames_per_item,
1720 spatial_merge_size,
1721 tokens_per_frame * spatial_merge_size,
1722 ]
1723 for _ in range(max_batch_size)
1724 ]
1725 else:
1726 grid_config = [
1727 [1, spatial_merge_size, per_mm_item_output * spatial_merge_size]
1728 for _ in range(max_batch_size)
1729 ]
1730
1731 # Create dummy pixel_values; uint8 when normalization is fused
1732 # on-device. Contents are overwritten before every replay.
1733 patch_embed = self.visual.patch_embed
1734 in_channels = patch_embed.proj.in_channels
1735 patch_size = patch_embed.patch_size
1736 temporal_patch_size = patch_embed.temporal_patch_size
1737 total_patches = sum(t * h * w for t, h, w in grid_config)
1738 flattened_patch_size = (
1739 in_channels * temporal_patch_size * patch_size * patch_size
1740 )
1741 dummy_pixel_values = torch.zeros(
1742 total_patches,
1743 flattened_patch_size,
1744 device=device,
1745 dtype=self.visual.input_norm.input_dtype or dtype,
1746 )
1747
1748 # max_seqlen.item() gets baked into the CUDA graph at capture time.
1749 metadata = self.visual.prepare_encoder_metadata(
1750 grid_config,
1751 max_batch_size=max_batch_size,
1752 max_frames_per_batch=max_frames_per_batch,
1753 max_seqlen_override=token_budget * (spatial_merge_size**2),
1754 device=device,
1755 )
1756
1757 # Capture with image-format kwargs; pixel_values shape is compatible with
1758 # both image and video replay paths.
1759 values = metadata | {
1760 "pixel_values": dummy_pixel_values,
1761 }
1762
1763 return EncoderCudaGraphCaptureInputs(
1764 values=values,
1765 )
1766
1767 def prepare_encoder_cudagraph_replay_buffers(
1768 self,
1769 mm_kwargs: dict[str, Any],
1770 max_batch_size: int,
1771 max_frames_per_batch: int,
1772 path: str = "default",
1773 ) -> EncoderCudaGraphReplayBuffers:
1774 modality = self.get_input_modality(mm_kwargs)
1775 grid_thw_list = self._get_grid_thw_by_modality(mm_kwargs)
1776
1777 if modality == "image":
1778 metadata = self.visual.prepare_encoder_metadata(
1779 grid_thw_list,
1780 max_batch_size=max_batch_size,
1781 )
1782 else:
1783 metadata = self.visual.prepare_encoder_metadata(
1784 grid_thw_list,
1785 max_frames_per_batch=max_frames_per_batch,
1786 )
1787
1788 values = metadata | {
1789 "pixel_values": self._get_pixel_values_by_modality(mm_kwargs),
1790 }
1791 return EncoderCudaGraphReplayBuffers(values=values)
1792
1793 def encoder_cudagraph_forward(
1794 self,
1795 values: dict[str, torch.Tensor],
1796 path: str = "default",
1797 ) -> torch.Tensor:
1798 pixel_values = values.pop("pixel_values")
1799 metadata = values
1800 return self.visual(pixel_values, None, encoder_metadata=metadata)
1801
1802 def encoder_eager_forward(
1803 self,
1804 mm_kwargs: dict[str, Any],
1805 path: str = "default",
1806 ) -> torch.Tensor:
1807 pixel_values = self._get_pixel_values_by_modality(mm_kwargs)
1808 grid_thw = self._get_grid_thw_by_modality(mm_kwargs)
1809 return self.visual(pixel_values, grid_thw)
1810
1811 def forward(
1812 self,
1813 input_ids: torch.Tensor | None,
1814 positions: torch.Tensor,
1815 intermediate_tensors: IntermediateTensors | None = None,
1816 inputs_embeds: torch.Tensor | None = None,
1817 **kwargs: object,
1818 ) -> torch.Tensor | IntermediateTensors:
1819 """Run forward pass for Qwen2-VL.
1820
1821 Args:
1822 input_ids: Flattened (concatenated) input_ids corresponding to a
1823 batch.
1824 positions: Flattened (concatenated) position ids corresponding to a
1825 batch.
1826 **NOTE**: If mrope is enabled (default setting for Qwen2-VL
1827 opensource models), the shape will be `(3, seq_len)`,
1828 otherwise it will be `(seq_len,)`.
1829 intermediate_tensors: Intermediate tensors from prior forward pass.
1830 inputs_embeds: Optional tensor of input embeddings.
1831 **kwargs: Multimodal inputs for this batch, forwarded to the
1832 multimodal embedding path.
1833
1834 """
1835 if intermediate_tensors is not None:
1836 inputs_embeds = None
1837
1838 hidden_states = self.language_model.model(
1839 input_ids=input_ids,
1840 positions=positions,
1841 intermediate_tensors=intermediate_tensors,
1842 inputs_embeds=inputs_embeds,
1843 )
1844 return hidden_states
1845
1846 def compute_logits(
1847 self,
1848 hidden_states: torch.Tensor,
1849 ) -> torch.Tensor | None:
1850 return self.language_model.compute_logits(hidden_states)
1851
1852 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1853 loader = AutoWeightsLoader(self)
1854 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1855
1856 def get_mm_mapping(self) -> MultiModelKeys:
1857 """Get the module prefix in multimodal models."""
1858 return MultiModelKeys.from_string_field(
1859 language_model="language_model",
1860 connector="visual.merger.",
1861 tower_model="visual.",
1862 )
1863
1864 def get_mm_lora_token_counts(
1865 self,
1866 *,
1867 modality: str,
1868 mm_kwargs: MultiModalKwargsItem | None,
1869 num_mm_embeds: int,
1870 ) -> tuple[int, int | None]:
1871 del modality, mm_kwargs
1872 hf_config = self.config
1873 vision_config = hf_config.vision_config
1874 merge_size = vision_config.spatial_merge_size
1875 return num_mm_embeds * merge_size**2, num_mm_embeds