main
py 704 lines 25.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from collections.abc import Iterable
4
5 import numpy as np
6 import torch
7 import torch.nn as nn
8 from einops import rearrange
9 from transformers import PreTrainedConfig
10
11 from vllm.distributed import parallel_state
12 from vllm.distributed import utils as dist_utils
13 from vllm.model_executor.layers.activation import get_act_fn
14 from vllm.model_executor.layers.attention.mm_encoder_attention import (
15 MMEncoderAttention,
16 )
17 from vllm.model_executor.layers.conv import Conv3dLayer
18 from vllm.model_executor.layers.linear import (
19 ColumnParallelLinear,
20 QKVParallelLinear,
21 RowParallelLinear,
22 )
23 from vllm.model_executor.layers.quantization import QuantizationConfig
24 from vllm.model_executor.layers.rotary_embedding.mrope import triton_mrope
25 from vllm.model_executor.layers.rotary_embedding.mrope_vit_setup import (
26 vit_mrope_setup,
27 )
28 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
29 from vllm.model_executor.models.utils import maybe_prefix
30 from vllm.model_executor.models.vision import (
31 get_vit_attn_backend,
32 is_vit_use_data_parallel,
33 )
34
35
36 class MiniMaxVLPatchEmbed(nn.Module):
37 """Conv3d-based patch embedding.
38
39 Takes flat tokens of shape (N, C * temporal_patch_size * patch_size²)
40 and projects each to a hidden-size embedding.
41 """
42
43 def __init__(self, config: PreTrainedConfig) -> None:
44 super().__init__()
45 compression = config.img_token_compression_config
46 temporal_patch_size = compression.get("temporal_patch_size", 2)
47 patch_size = config.patch_size
48 num_channels = config.num_channels
49
50 self.patch_size = patch_size
51 self.temporal_patch_size = temporal_patch_size
52 self.num_channels = num_channels
53 self.hidden_size = config.hidden_size
54
55 self.patch_embedding = Conv3dLayer(
56 in_channels=num_channels,
57 out_channels=config.hidden_size,
58 kernel_size=(temporal_patch_size, patch_size, patch_size),
59 stride=(temporal_patch_size, patch_size, patch_size),
60 bias=False,
61 )
62
63 def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
64 # pixel_values: (N, C * temporal_patch_size * patch_size²)
65 if self.patch_embedding.weight.dtype != pixel_values.dtype:
66 self.patch_embedding = self.patch_embedding.to(pixel_values.dtype)
67 x = pixel_values.reshape(
68 pixel_values.shape[0],
69 self.num_channels,
70 self.temporal_patch_size,
71 self.patch_size,
72 self.patch_size,
73 )
74 return self.patch_embedding(x).reshape(x.shape[0], -1)
75
76
77 class MiniMaxVLAttention(nn.Module):
78 """Multi-head attention with MiniMax's partial 3D RoPE.
79
80 Partial means only the first ``rot_dim`` (< head_dim) dimensions of
81 Q and K are rotated; the remaining dims are passed through unchanged.
82 """
83
84 def __init__(
85 self,
86 embed_dim: int,
87 num_heads: int,
88 quant_config: QuantizationConfig | None = None,
89 prefix: str = "",
90 ) -> None:
91 super().__init__()
92 use_data_parallel = is_vit_use_data_parallel()
93 self.tp_size = (
94 1
95 if use_data_parallel
96 else parallel_state.get_tensor_model_parallel_world_size()
97 )
98 self.head_dim = embed_dim // num_heads
99 self.num_heads_per_partition = dist_utils.divide(num_heads, self.tp_size)
100
101 self.qkv_proj = QKVParallelLinear(
102 hidden_size=embed_dim,
103 head_size=self.head_dim,
104 total_num_heads=num_heads,
105 total_num_kv_heads=num_heads,
106 bias=True,
107 quant_config=quant_config,
108 prefix=f"{prefix}.qkv_proj",
109 disable_tp=use_data_parallel,
110 )
111 self.out_proj = RowParallelLinear(
112 input_size=embed_dim,
113 output_size=embed_dim,
114 bias=True,
115 quant_config=quant_config,
116 prefix=f"{prefix}.out_proj",
117 disable_tp=use_data_parallel,
118 )
119 self.attn = MMEncoderAttention(
120 num_heads=self.num_heads_per_partition,
121 head_size=self.head_dim,
122 prefix=f"{prefix}.attn",
123 )
124 # Partial 3D RoPE geometry: rot_dim is split evenly across t/h/w
125 # (same formula as MiniMaxVLVisionTransformer; rot_dim may be <
126 # head_dim). mrope_section is in half-dim units for triton_mrope.
127 rope_dims = 2 * (self.head_dim // 2)
128 axis_dim = 2 * ((rope_dims // 3) // 2)
129 self.rotary_dim = 3 * axis_dim
130 self.mrope_section = [axis_dim // 2] * 3
131
132 def forward(
133 self,
134 x: torch.Tensor,
135 cu_seqlens: torch.Tensor,
136 rotary_cos: torch.Tensor,
137 rotary_sin: torch.Tensor,
138 max_seqlen: torch.Tensor,
139 sequence_lengths: torch.Tensor | None = None,
140 ) -> torch.Tensor:
141 # x: (N, 1, embed_dim) [seq=N, batch=1, chan=embed_dim]
142 x_qkv, _ = self.qkv_proj(x) # (N, 1, 3 * heads_per_part * head_dim)
143 seq_len, batch_size, _ = x_qkv.shape
144
145 qkv = x_qkv.view(seq_len, 3, self.num_heads_per_partition, self.head_dim)
146 v = qkv[:, 2].unsqueeze(0) # (b=1, N, heads, head_dim) strided view
147
148 # In-place partial 3D RoPE on strided q/k views of the packed qkv
149 # tensor: one kernel launch, no repack/upcast copies. fp32 cos/sin
150 # make the kernel compute in fp32, matching the reference
151 # ``_minimax_rope_applier`` precision.
152 q = qkv[:, 0].reshape(seq_len, -1)
153 k = qkv[:, 1].reshape(seq_len, -1)
154 triton_mrope(
155 q,
156 k,
157 rotary_cos,
158 rotary_sin,
159 self.mrope_section,
160 self.head_dim,
161 self.rotary_dim,
162 mrope_interleaved=False,
163 is_neox_style=True,
164 )
165 q = q.view(batch_size, seq_len, self.num_heads_per_partition, self.head_dim)
166 k = k.view(batch_size, seq_len, self.num_heads_per_partition, self.head_dim)
167
168 # Flash attention → (b, N, heads, head_dim)
169 context = self.attn(
170 query=q,
171 key=k,
172 value=v,
173 cu_seqlens=cu_seqlens,
174 max_seqlen=max_seqlen,
175 sequence_lengths=sequence_lengths,
176 )
177
178 # Back to (N, 1, embed_dim)
179 context = rearrange(context, "b s h d -> s b (h d)", b=batch_size)
180 output, _ = self.out_proj(context)
181 return output
182
183
184 class MiniMaxVLEncoderLayer(nn.Module):
185 """Single CLIP-style transformer block."""
186
187 def __init__(
188 self,
189 config: PreTrainedConfig,
190 quant_config: QuantizationConfig | None = None,
191 prefix: str = "",
192 ) -> None:
193 super().__init__()
194 embed_dim = config.hidden_size
195 self.layer_norm1 = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
196 self.self_attn = MiniMaxVLAttention(
197 embed_dim=embed_dim,
198 num_heads=config.num_attention_heads,
199 quant_config=quant_config,
200 prefix=f"{prefix}.self_attn",
201 )
202 self.layer_norm2 = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
203 use_data_parallel = is_vit_use_data_parallel()
204 self.fc1 = ColumnParallelLinear(
205 config.hidden_size,
206 config.intermediate_size,
207 bias=True,
208 quant_config=quant_config,
209 prefix=f"{prefix}.fc1",
210 disable_tp=use_data_parallel,
211 )
212 self.act = get_act_fn(getattr(config, "hidden_act", "gelu"))
213 self.fc2 = RowParallelLinear(
214 config.intermediate_size,
215 config.hidden_size,
216 bias=True,
217 quant_config=quant_config,
218 prefix=f"{prefix}.fc2",
219 disable_tp=use_data_parallel,
220 )
221
222 def forward(
223 self,
224 x: torch.Tensor,
225 cu_seqlens: torch.Tensor,
226 rotary_cos: torch.Tensor,
227 rotary_sin: torch.Tensor,
228 max_seqlen: torch.Tensor,
229 sequence_lengths: torch.Tensor | None = None,
230 ) -> torch.Tensor:
231 # x: (N, 1, hidden_size)
232 x = x + self.self_attn(
233 self.layer_norm1(x),
234 cu_seqlens,
235 rotary_cos,
236 rotary_sin,
237 max_seqlen,
238 sequence_lengths,
239 )
240 residual = x
241 x, _ = self.fc1(self.layer_norm2(x))
242 x = self.act(x)
243 x, _ = self.fc2(x)
244 return residual + x
245
246
247 class MiniMaxVLEncoder(nn.Module):
248 def __init__(
249 self,
250 config: PreTrainedConfig,
251 num_hidden_layers_override: int | None = None,
252 quant_config: QuantizationConfig | None = None,
253 prefix: str = "",
254 ) -> None:
255 super().__init__()
256 n = (
257 config.num_hidden_layers
258 if num_hidden_layers_override is None
259 else num_hidden_layers_override
260 )
261 self.layers = nn.ModuleList(
262 [
263 MiniMaxVLEncoderLayer(
264 config=config,
265 quant_config=quant_config,
266 prefix=f"{prefix}.layers.{i}",
267 )
268 for i in range(n)
269 ]
270 )
271
272 def forward(
273 self,
274 x: torch.Tensor,
275 cu_seqlens: torch.Tensor,
276 rotary_cos: torch.Tensor,
277 rotary_sin: torch.Tensor,
278 max_seqlen: torch.Tensor,
279 sequence_lengths: torch.Tensor | None = None,
280 ) -> torch.Tensor:
281 for layer in self.layers:
282 x = layer(
283 x,
284 cu_seqlens,
285 rotary_cos,
286 rotary_sin,
287 max_seqlen,
288 sequence_lengths,
289 )
290 return x
291
292
293 class MiniMaxVLVisionTransformer(nn.Module):
294 """CLIP-based ViT with 3D RoPE (t/h/w decomposed).
295
296 Faithfully mirrors the reference ``MiniMaxVLVisionTransformer``.
297 FLASHINFER backend is not supported; standard flash-attn is used.
298 """
299
300 def __init__(
301 self,
302 config: PreTrainedConfig,
303 num_hidden_layers_override: int | None = None,
304 require_post_norm: bool | None = None,
305 quant_config: QuantizationConfig | None = None,
306 prefix: str = "",
307 ) -> None:
308 super().__init__()
309 compression = config.img_token_compression_config
310 self.spatial_merge_size: int = compression.get("spatial_merge_size", 2)
311 self.temporal_patch_size: int = compression.get("temporal_patch_size", 2)
312 self.vision_segment_max_frames: int | None = getattr(
313 config, "vision_segment_max_frames", None
314 )
315 self.use_data_parallel = is_vit_use_data_parallel()
316
317 embed_dim = config.hidden_size
318 head_dim = embed_dim // config.num_attention_heads
319 # Backend selection + sharding info for building encoder metadata.
320 # Defaults to FLASH_ATTN on SM80+; --mm-encoder-attn-backend FLASHINFER
321 # selects the cuDNN ViT prefill path.
322 self.hidden_size = embed_dim
323 self.tp_size = (
324 1
325 if self.use_data_parallel
326 else parallel_state.get_tensor_model_parallel_world_size()
327 )
328 self.attn_backend = get_vit_attn_backend(
329 head_size=head_dim, dtype=torch.get_default_dtype()
330 )
331 rope_dims = 2 * (head_dim // 2)
332
333 # Split rope dims evenly across t/h/w (same formula as the reference)
334 self.t_dim = int(2 * ((rope_dims // 3) // 2))
335 self.h_dim = int(2 * ((rope_dims // 3) // 2))
336 self.w_dim = int(2 * ((rope_dims // 3) // 2))
337 # rot_dim = t_dim + h_dim + w_dim (may be < head_dim)
338
339 rope_theta: float = getattr(config, "rope_theta", 10000.0)
340 inv_freq_t = 1.0 / (
341 rope_theta
342 ** (torch.arange(0, self.t_dim, 2, dtype=torch.float32) / self.t_dim)
343 )
344 inv_freq_h = 1.0 / (
345 rope_theta
346 ** (torch.arange(0, self.h_dim, 2, dtype=torch.float32) / self.h_dim)
347 )
348 inv_freq_w = 1.0 / (
349 rope_theta
350 ** (torch.arange(0, self.w_dim, 2, dtype=torch.float32) / self.w_dim)
351 )
352 self.register_buffer("inv_freq_t", inv_freq_t, persistent=False)
353 self.register_buffer("inv_freq_h", inv_freq_h, persistent=False)
354 self.register_buffer("inv_freq_w", inv_freq_w, persistent=False)
355
356 self.embeddings = MiniMaxVLPatchEmbed(config)
357 self.pre_layrnorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
358
359 n_layers = config.num_hidden_layers
360 if num_hidden_layers_override is None:
361 num_hidden_layers_override = n_layers
362 self.encoder = MiniMaxVLEncoder(
363 config=config,
364 num_hidden_layers_override=num_hidden_layers_override,
365 quant_config=quant_config,
366 prefix=f"{prefix}.encoder",
367 )
368
369 if require_post_norm is None:
370 require_post_norm = num_hidden_layers_override == n_layers
371 self.post_layernorm = (
372 nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
373 if require_post_norm
374 else None
375 )
376
377 # out_hidden_size needed by run_dp_sharded_mrope_vision_model
378 self.out_hidden_size = embed_dim
379
380 # ── Frame-limit helper (mirrors the reference) ───────────────────────
381
382 def _apply_max_frames_limit(self, grid_thw: list[list[int]]) -> list[list[int]]:
383 if self.vision_segment_max_frames is None:
384 return grid_thw
385 max_f = self.vision_segment_max_frames
386 out: list[list[int]] = []
387 for t, h, w in grid_thw:
388 if t <= max_f:
389 out.append([t, h, w])
390 else:
391 for i in range(0, t, max_f):
392 out.append([min(max_f, t - i), h, w])
393 return out
394
395 # ── Encoder metadata (shared by eager forward and CUDA graph replay) ──
396
397 def prepare_encoder_metadata(
398 self,
399 grid_thw: list[list[int]],
400 *,
401 device: torch.device,
402 max_batch_size: int | None = None,
403 max_seqlen_override: int | None = None,
404 ) -> dict[str, torch.Tensor | None]:
405 """Build all grid-dependent forward inputs as standalone tensors.
406
407 The eager path calls this per forward; encoder CUDA graphs call it at
408 capture (dummy grids, padded to fixed buffer sizes) and before each
409 replay (real grids, unpadded — the manager's padding logic completes
410 the captured buffers).
411
412 Args:
413 grid_thw: per-item [t, h, w] patch grids (host metadata).
414 device: device for the attention metadata tensors.
415 max_batch_size: pad cu_seqlens to this many segments with empty
416 (zero-length) trailing segments; None leaves it unpadded.
417 max_seqlen_override: bake this max_seqlen instead of deriving it
418 from the grids; CUDA graph capture passes a worst-case value
419 because max_seqlen is a CPU scalar baked into the graph.
420
421 """
422 limited = self._apply_max_frames_limit(grid_thw)
423
424 # Token-level cumulative sequence lengths (one segment per limited grid).
425 lens = [t * h * w for t, h, w in limited]
426 cu_seqlens_np = np.zeros(len(lens) + 1, dtype=np.int32)
427 np.cumsum(np.array(lens, dtype=np.int32), out=cu_seqlens_np[1:])
428
429 if max_batch_size is not None and len(lens) < max_batch_size:
430 pad = np.full(max_batch_size - len(lens), cu_seqlens_np[-1], dtype=np.int32)
431 cu_seqlens_np = np.concatenate([cu_seqlens_np, pad])
432
433 # Backend-specific encoder metadata. For FLASH_ATTN this returns the raw
434 # token cu_seqlens, the max segment length, and sequence_lengths=None;
435 # for FLASHINFER (cuDNN) it repacks cu_seqlens into element-offset
436 # indptrs, buckets max_seqlen, and builds padded per-sequence lengths.
437 sequence_lengths = MMEncoderAttention.maybe_compute_seq_lens(
438 self.attn_backend, cu_seqlens_np, device
439 )
440 max_seqlen = torch.tensor(
441 max_seqlen_override
442 if max_seqlen_override is not None
443 else MMEncoderAttention.compute_max_seqlen(
444 self.attn_backend, cu_seqlens_np
445 ),
446 dtype=torch.int32,
447 )
448 cu_seqlens = MMEncoderAttention.maybe_recompute_cu_seqlens(
449 self.attn_backend,
450 cu_seqlens_np,
451 self.hidden_size,
452 self.tp_size,
453 device,
454 )
455
456 # 3D RoPE cos/sin: (3, total_N, half_rot_dim) fp32 t/h/w planes for
457 # triton_mrope from one fused setup kernel. Kept fp32 so the kernel
458 # computes the rotation in fp32, matching the reference.
459 rotary_cos, rotary_sin = vit_mrope_setup(
460 self.inv_freq_t,
461 self.inv_freq_h,
462 self.inv_freq_w,
463 limited,
464 self.spatial_merge_size,
465 )
466
467 return {
468 "rotary_cos": rotary_cos,
469 "rotary_sin": rotary_sin,
470 "cu_seqlens": cu_seqlens,
471 "max_seqlen": max_seqlen,
472 "sequence_lengths": sequence_lengths,
473 }
474
475 # ── Forward ──────────────────────────────────────────────────────────
476
477 def forward(
478 self,
479 pixel_values: torch.Tensor,
480 grid_thw: list[list[int]] | None = None,
481 *,
482 encoder_metadata: dict[str, torch.Tensor | None] | None = None,
483 ) -> torch.Tensor:
484 # pixel_values: (total_N, C * temporal_patch_size * patch_size²)
485 # Output: (total_N, hidden_size)
486
487 hidden = self.embeddings(pixel_values) # (total_N, hidden_size)
488 hidden = self.pre_layrnorm(hidden)
489
490 if encoder_metadata is None:
491 assert grid_thw is not None
492 encoder_metadata = self.prepare_encoder_metadata(
493 grid_thw, device=hidden.device
494 )
495
496 cu_seqlens = encoder_metadata["cu_seqlens"]
497 rotary_cos = encoder_metadata["rotary_cos"]
498 rotary_sin = encoder_metadata["rotary_sin"]
499 max_seqlen = encoder_metadata["max_seqlen"]
500 sequence_lengths = encoder_metadata.get("sequence_lengths")
501 assert cu_seqlens is not None
502 assert rotary_cos is not None
503 assert rotary_sin is not None
504 assert max_seqlen is not None
505
506 # Encoder expects (N, 1, hidden_size) — add batch dim
507 hidden = hidden.unsqueeze(1)
508
509 hidden = self.encoder(
510 hidden,
511 cu_seqlens,
512 rotary_cos,
513 rotary_sin,
514 max_seqlen,
515 sequence_lengths=sequence_lengths,
516 )
517 hidden = hidden.squeeze(1) # back to (total_N, hidden_size)
518
519 if self.post_layernorm is not None:
520 hidden = self.post_layernorm(hidden)
521
522 return hidden
523
524
525 class MiniMaxVLMultiModalProjector(nn.Module):
526 """Two-layer MLP projector: vision_hidden → text_hidden."""
527
528 def __init__(
529 self,
530 vision_hidden_size: int,
531 text_hidden_size: int,
532 projector_hidden_size: int | None,
533 multimodal_projector_bias: bool,
534 projector_hidden_act: str = "gelu",
535 quant_config: QuantizationConfig | None = None,
536 prefix: str = "",
537 ) -> None:
538 super().__init__()
539 mid = projector_hidden_size if projector_hidden_size else text_hidden_size
540 use_dp = is_vit_use_data_parallel()
541 self.linear_1 = ColumnParallelLinear(
542 vision_hidden_size,
543 mid,
544 bias=multimodal_projector_bias,
545 quant_config=quant_config,
546 prefix=f"{prefix}.linear_1",
547 disable_tp=use_dp,
548 )
549 self.act = get_act_fn(projector_hidden_act)
550 self.linear_2 = RowParallelLinear(
551 mid,
552 text_hidden_size,
553 bias=multimodal_projector_bias,
554 quant_config=quant_config,
555 prefix=f"{prefix}.linear_2",
556 disable_tp=use_dp,
557 )
558
559 def forward(self, x: torch.Tensor) -> torch.Tensor:
560 x, _ = self.linear_1(x)
561 x = self.act(x)
562 x, _ = self.linear_2(x)
563 return x
564
565
566 class MiniMaxVLPatchMerger(nn.Module):
567 def __init__(
568 self,
569 spatial_merge_size: int,
570 text_hidden_size: int,
571 projector_hidden_size: int | None,
572 patch_merge_bias: bool,
573 projector_hidden_act: str = "gelu",
574 quant_config: QuantizationConfig | None = None,
575 prefix: str = "",
576 ) -> None:
577 super().__init__()
578 self.spatial_merge_size = spatial_merge_size
579 mid = projector_hidden_size if projector_hidden_size else text_hidden_size
580 merge_in = text_hidden_size * spatial_merge_size**2
581 use_dp = is_vit_use_data_parallel()
582 self.linear_1 = ColumnParallelLinear(
583 merge_in,
584 mid,
585 bias=patch_merge_bias,
586 quant_config=quant_config,
587 prefix=f"{prefix}.linear_1",
588 disable_tp=use_dp,
589 )
590 self.act = get_act_fn(projector_hidden_act)
591 self.linear_2 = RowParallelLinear(
592 mid,
593 text_hidden_size,
594 bias=patch_merge_bias,
595 quant_config=quant_config,
596 prefix=f"{prefix}.linear_2",
597 disable_tp=use_dp,
598 )
599
600 def forward(self, x: torch.Tensor) -> torch.Tensor:
601 # x: (N, text_hidden_size) → (N // merge_size², text_hidden_size)
602 x = x.reshape(x.shape[0] // (self.spatial_merge_size**2), -1)
603 x, _ = self.linear_1(x)
604 x = self.act(x)
605 x, _ = self.linear_2(x)
606 return x
607
608
609 class MiniMaxVLVisionModel(nn.Module):
610 """Full vision model: ViT → projector → patch merger."""
611
612 def __init__(
613 self,
614 config: PreTrainedConfig,
615 text_hidden_size: int,
616 projector_hidden_size: int | None = None,
617 quant_config: QuantizationConfig | None = None,
618 prefix: str = "",
619 ) -> None:
620 super().__init__()
621 compression = config.img_token_compression_config
622 spatial_merge_size: int = compression.get("spatial_merge_size", 2)
623 self.spatial_merge_size = spatial_merge_size
624 self.use_data_parallel = is_vit_use_data_parallel()
625
626 # The released checkpoint ships no ``post_layernorm`` weights and
627 # uses ``vision_feature_layer=-1`` with ``vision_feature_select_strategy
628 # ="full"``, i.e. the raw last encoder hidden state (CLIP's
629 # ``last_hidden_state`` is taken before the post layernorm). Applying an
630 # untrained post layernorm here would corrupt the visual features.
631 self.vision_model = MiniMaxVLVisionTransformer(
632 config=config,
633 require_post_norm=False,
634 quant_config=quant_config,
635 prefix=maybe_prefix(prefix, "vision_model"),
636 )
637 self.multi_modal_projector = MiniMaxVLMultiModalProjector(
638 vision_hidden_size=config.hidden_size,
639 text_hidden_size=text_hidden_size,
640 projector_hidden_size=projector_hidden_size,
641 multimodal_projector_bias=getattr(
642 config, "multimodal_projector_bias", True
643 ),
644 projector_hidden_act=getattr(config, "projector_hidden_act", "gelu"),
645 quant_config=quant_config,
646 prefix=maybe_prefix(prefix, "multi_modal_projector"),
647 )
648 self.patch_merge_mlp = MiniMaxVLPatchMerger(
649 spatial_merge_size=spatial_merge_size,
650 text_hidden_size=text_hidden_size,
651 projector_hidden_size=projector_hidden_size,
652 patch_merge_bias=getattr(config, "patch_merge_bias", True),
653 projector_hidden_act=getattr(config, "projector_hidden_act", "gelu"),
654 quant_config=quant_config,
655 prefix=maybe_prefix(prefix, "patch_merge_mlp"),
656 )
657
658 self.dtype = self.vision_model.embeddings.patch_embedding.weight.dtype
659 self.out_hidden_size = text_hidden_size
660
661 def forward(
662 self,
663 pixel_values: torch.Tensor,
664 grid_thw: list[list[int]] | None = None,
665 *,
666 encoder_metadata: dict[str, torch.Tensor | None] | None = None,
667 ) -> torch.Tensor:
668 hidden = self.vision_model(
669 pixel_values=pixel_values,
670 grid_thw=grid_thw,
671 encoder_metadata=encoder_metadata,
672 )
673 if hidden.dim() == 3:
674 hidden = hidden.squeeze(0)
675 hidden = self.multi_modal_projector(hidden)
676 hidden = self.patch_merge_mlp(hidden)
677 return hidden
678
679 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
680 stacked_params_mapping = [
681 # (param_name, shard_name, shard_id)
682 ("qkv_proj.", "q_proj.", "q"),
683 ("qkv_proj.", "k_proj.", "k"),
684 ("qkv_proj.", "v_proj.", "v"),
685 ]
686 params_dict = dict(self.named_parameters(remove_duplicate=False))
687 loaded_params: set[str] = set()
688
689 for name, loaded_weight in weights:
690 for param_name, weight_name, shard_id in stacked_params_mapping:
691 if weight_name not in name:
692 continue
693 name = name.replace(weight_name, param_name)
694
695 param = params_dict[name]
696 weight_loader = param.weight_loader
697 weight_loader(param, loaded_weight, shard_id)
698 break
699 else:
700 param = params_dict[name]
701 weight_loader = getattr(param, "weight_loader", default_weight_loader)
702 weight_loader(param, loaded_weight)
703 loaded_params.add(name)
704 return loaded_params