main
py 537 lines 18.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Implementation of Siglip2VisionModel intended to be only used
4 within a vision language model."""
5
6 from collections.abc import Iterable
7
8 import torch
9 from torch import nn
10 from torch.nn import functional as F
11 from transformers import Siglip2VisionConfig
12
13 from vllm.compilation.decorators import (
14 should_torch_compile_mm_encoder,
15 support_torch_compile,
16 )
17 from vllm.distributed import get_tensor_model_parallel_world_size
18 from vllm.model_executor.layers.activation import get_act_fn
19 from vllm.model_executor.layers.attention import MMEncoderAttention
20 from vllm.model_executor.layers.linear import (
21 ColumnParallelLinear,
22 QKVParallelLinear,
23 ReplicatedLinear,
24 RowParallelLinear,
25 )
26 from vllm.model_executor.layers.quantization import QuantizationConfig
27
28 from .utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
29 from .vision import (
30 is_vit_use_data_parallel,
31 resolve_visual_encoder_outputs,
32 )
33
34
35 class Siglip2VisionEmbeddings(nn.Module):
36 def __init__(self, config: Siglip2VisionConfig):
37 super().__init__()
38 self.config = config
39 self.embed_dim = config.hidden_size
40 self.patch_size = config.patch_size
41 self.patch_embedding = ReplicatedLinear(
42 input_size=config.num_channels * self.patch_size * self.patch_size,
43 output_size=self.embed_dim,
44 return_bias=False,
45 )
46 self.num_patches = config.num_patches
47 self.position_embedding_size = int(self.num_patches**0.5)
48 self.position_embedding = nn.Embedding(self.num_patches, self.embed_dim)
49
50 def forward(
51 self,
52 pixel_values_packed: torch.FloatTensor,
53 spatial_shapes: torch.LongTensor,
54 ) -> torch.Tensor:
55 """Embed patchified pixel values in packed (unpadded) form.
56
57 Args:
58 pixel_values_packed: (1, total_tokens, patch_dim) or
59 (total_tokens, patch_dim), packed in tile order.
60 spatial_shapes: (num_tiles, 2) on CPU (height, width) per tile.
61
62 Returns:
63 (1, total_tokens, embed_dim) packed embeddings.
64
65 """
66 assert spatial_shapes.device.type == "cpu", (
67 "Expected `spatial_shapes` on CPU to avoid device-to-host sync in "
68 "variable-length packing."
69 )
70
71 if pixel_values_packed.dim() == 3:
72 assert pixel_values_packed.shape[0] == 1
73 pixel_values_flat = pixel_values_packed[0]
74 else:
75 pixel_values_flat = pixel_values_packed
76
77 lengths = (spatial_shapes[:, 0] * spatial_shapes[:, 1]).to(dtype=torch.int64)
78 lengths_list = lengths.tolist()
79 total_tokens = int(sum(lengths_list))
80 if total_tokens != pixel_values_flat.shape[0]:
81 raise ValueError(
82 "Packed pixel_values token count does not match spatial_shapes: "
83 f"{pixel_values_flat.shape[0]} vs {total_tokens}."
84 )
85
86 target_dtype = self.patch_embedding.weight.dtype
87 patch_embeds = self.patch_embedding(pixel_values_flat.to(dtype=target_dtype))
88
89 positional_embeddings = self.position_embedding.weight.reshape(
90 self.position_embedding_size, self.position_embedding_size, -1
91 )
92 packed_pos_embeds = self.resize_positional_embeddings_packed(
93 positional_embeddings,
94 spatial_shapes,
95 lengths_list=lengths_list,
96 )
97
98 embeddings = patch_embeds + packed_pos_embeds
99 return embeddings.unsqueeze(0)
100
101 @staticmethod
102 def resize_positional_embeddings_packed(
103 positional_embeddings: torch.Tensor,
104 spatial_shapes: torch.LongTensor,
105 lengths_list: list[int],
106 ) -> torch.Tensor:
107 """Resize positional embeddings per image and return a packed tensor.
108
109 Args:
110 positional_embeddings: (height, width, embed_dim) base grid.
111 spatial_shapes: (batch_size, 2) on CPU, (height, width) per image.
112 lengths_list: flattened token length per image (height * width).
113
114 Returns:
115 (total_tokens, embed_dim) packed positional embeddings, concatenated
116 in the same order as `lengths_list`.
117
118 """
119 assert spatial_shapes.device.type == "cpu"
120
121 embed_dim = positional_embeddings.shape[-1]
122 source_dtype = positional_embeddings.dtype
123
124 total_tokens = int(sum(lengths_list))
125 packed_pos_embeds = torch.empty(
126 (total_tokens, embed_dim),
127 device=positional_embeddings.device,
128 dtype=source_dtype,
129 )
130
131 # (height, width, embed_dim) -> (1, embed_dim, height, width)
132 pos_4d = positional_embeddings.permute(2, 0, 1).unsqueeze(0)
133
134 # Upcast to float32 on CPU because antialias is not supported for
135 # bfloat16/float16 on CPU.
136 if pos_4d.device.type == "cpu":
137 pos_4d = pos_4d.to(torch.float32)
138
139 offset = 0
140 for i, length in enumerate(lengths_list):
141 if length <= 0:
142 continue
143 height, width = spatial_shapes[i].tolist()
144 resized = F.interpolate(
145 pos_4d,
146 size=(height, width),
147 mode="bilinear",
148 align_corners=False,
149 antialias=True,
150 )
151 resized = resized.reshape(embed_dim, height * width).transpose(0, 1)
152 resized = resized.to(source_dtype)
153 packed_pos_embeds[offset : offset + length] = resized
154 offset += length
155
156 return packed_pos_embeds
157
158
159 class Siglip2Attention(nn.Module):
160 """Multi-headed attention from 'Attention Is All You Need' paper."""
161
162 def __init__(
163 self,
164 config: Siglip2VisionConfig,
165 quant_config: QuantizationConfig | None = None,
166 prefix: str = "",
167 ):
168 super().__init__()
169 self.config = config
170 self.embed_dim = config.hidden_size
171 self.num_heads = config.num_attention_heads
172 self.head_dim = self.embed_dim // self.num_heads
173 if self.head_dim * self.num_heads != self.embed_dim:
174 raise ValueError(
175 f"embed_dim must be divisible by num_heads "
176 f"(got `embed_dim`: {self.embed_dim} and `num_heads`:"
177 f" {self.num_heads})."
178 )
179 self.scale = self.head_dim**-0.5
180 self.dropout = config.attention_dropout
181
182 use_data_parallel = is_vit_use_data_parallel()
183 tp_size = 1 if use_data_parallel else get_tensor_model_parallel_world_size()
184 assert self.num_heads % tp_size == 0
185 self.num_heads_per_partition = self.num_heads // tp_size
186
187 self.qkv_proj = QKVParallelLinear(
188 hidden_size=self.embed_dim,
189 head_size=self.head_dim,
190 total_num_heads=self.num_heads,
191 quant_config=quant_config,
192 prefix=f"{prefix}.qkv_proj",
193 disable_tp=use_data_parallel,
194 )
195 self.out_proj = RowParallelLinear(
196 input_size=self.embed_dim,
197 output_size=self.embed_dim,
198 quant_config=quant_config,
199 prefix=f"{prefix}.out_proj",
200 disable_tp=use_data_parallel,
201 )
202 self.attn = MMEncoderAttention(
203 num_heads=self.num_heads_per_partition,
204 head_size=self.head_dim,
205 scale=self.scale,
206 prefix=f"{prefix}.attn",
207 )
208
209 def forward(
210 self,
211 hidden_states: torch.Tensor,
212 cu_seqlens: torch.Tensor,
213 max_seqlen: int | torch.Tensor,
214 ) -> torch.Tensor:
215 qkv, _ = self.qkv_proj(
216 hidden_states
217 ) # batch_size, q_len, 3 * num_heads_per_partition * head_dim
218 bsz, q_len, _ = qkv.shape
219 query_states, key_states, value_states = qkv.chunk(3, dim=-1)
220 query_states = query_states.view(
221 bsz, q_len, self.num_heads_per_partition, self.head_dim
222 )
223 key_states = key_states.view(
224 bsz, q_len, self.num_heads_per_partition, self.head_dim
225 )
226 value_states = value_states.view(
227 bsz, q_len, self.num_heads_per_partition, self.head_dim
228 )
229
230 # Use unified MultiHeadAttention implementation
231 out = self.attn(
232 query=query_states,
233 key=key_states,
234 value=value_states,
235 cu_seqlens=cu_seqlens,
236 max_seqlen=max_seqlen,
237 )
238 out = out.reshape(bsz, q_len, -1)
239 attn_output, _ = self.out_proj(out)
240 return attn_output
241
242
243 class Siglip2MLP(nn.Module):
244 def __init__(
245 self,
246 config: Siglip2VisionConfig,
247 quant_config: QuantizationConfig | None = None,
248 prefix: str = "",
249 ):
250 super().__init__()
251 self.config = config
252 self.activation_fn = get_act_fn(config.hidden_act)
253 use_data_parallel = is_vit_use_data_parallel()
254 self.fc1 = ColumnParallelLinear(
255 config.hidden_size,
256 config.intermediate_size,
257 quant_config=quant_config,
258 prefix=f"{prefix}.fc1",
259 disable_tp=use_data_parallel,
260 )
261 self.fc2 = RowParallelLinear(
262 config.intermediate_size,
263 config.hidden_size,
264 quant_config=quant_config,
265 prefix=f"{prefix}.fc2",
266 disable_tp=use_data_parallel,
267 )
268
269 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
270 hidden_states, _ = self.fc1(hidden_states)
271 hidden_states = self.activation_fn(hidden_states)
272 hidden_states, _ = self.fc2(hidden_states)
273 return hidden_states
274
275
276 @support_torch_compile(
277 dynamic_arg_dims={"hidden_states": [0, 1], "cu_seqlens": 0},
278 enable_if=should_torch_compile_mm_encoder,
279 is_encoder=True,
280 )
281 class Siglip2EncoderLayer(nn.Module):
282 def __init__(
283 self,
284 config: Siglip2VisionConfig,
285 quant_config: QuantizationConfig | None = None,
286 prefix: str = "",
287 ):
288 super().__init__()
289 self.embed_dim = config.hidden_size
290 self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
291 self.self_attn = Siglip2Attention(
292 config,
293 quant_config=quant_config,
294 prefix=f"{prefix}.self_attn",
295 )
296 self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
297 self.mlp = Siglip2MLP(
298 config,
299 quant_config=quant_config,
300 prefix=f"{prefix}.mlp",
301 )
302
303 def forward(
304 self,
305 hidden_states: torch.Tensor,
306 cu_seqlens: torch.Tensor,
307 max_seqlen: int | torch.Tensor,
308 ) -> torch.Tensor:
309 """Args:
310 hidden_states: Input tensor of shape (batch, seq_len, embed_dim).
311 cu_seqlens: Cumulative sequence lengths tensor.
312 max_seqlen: Maximum sequence length.
313
314 """
315 residual = hidden_states
316
317 hidden_states = self.layer_norm1(hidden_states)
318 hidden_states = self.self_attn(
319 hidden_states=hidden_states,
320 cu_seqlens=cu_seqlens,
321 max_seqlen=max_seqlen,
322 )
323 hidden_states = residual + hidden_states
324
325 residual = hidden_states
326 hidden_states = self.layer_norm2(hidden_states)
327 hidden_states = self.mlp(hidden_states)
328 hidden_states = residual + hidden_states
329 return hidden_states
330
331
332 class Siglip2Encoder(nn.Module):
333 """Transformer encoder consisting of `config.num_hidden_layers`
334 self attention layers. Each layer is a [`Siglip2EncoderLayer`].
335
336 Args:
337 config: PreTrainedConfig
338
339 """
340
341 def __init__(
342 self,
343 config: Siglip2VisionConfig,
344 quant_config: QuantizationConfig | None = None,
345 num_hidden_layers_override: int | None = None,
346 prefix: str = "",
347 ):
348 super().__init__()
349 self.config = config
350
351 if num_hidden_layers_override is None:
352 num_hidden_layers = config.num_hidden_layers
353 else:
354 num_hidden_layers = num_hidden_layers_override
355
356 self.layers = nn.ModuleList(
357 [
358 Siglip2EncoderLayer(
359 config=config,
360 quant_config=quant_config,
361 prefix=f"{prefix}.layers.{idx}",
362 )
363 for idx in range(num_hidden_layers)
364 ]
365 )
366
367 def forward(
368 self,
369 inputs_embeds: torch.Tensor,
370 cu_seqlens: torch.Tensor,
371 max_seqlen: int | torch.Tensor,
372 return_all_hidden_states: bool = False,
373 ) -> torch.Tensor | list[torch.Tensor]:
374 hidden_states_pool = [inputs_embeds]
375 hidden_states = inputs_embeds
376
377 for encoder_layer in self.layers:
378 hidden_states = encoder_layer(
379 hidden_states,
380 cu_seqlens=cu_seqlens,
381 max_seqlen=max_seqlen,
382 )
383 if return_all_hidden_states:
384 hidden_states_pool.append(hidden_states)
385 if return_all_hidden_states:
386 return hidden_states_pool
387 return hidden_states
388
389
390 class Siglip2VisionTransformer(nn.Module):
391 def __init__(
392 self,
393 config: Siglip2VisionConfig,
394 quant_config: QuantizationConfig | None = None,
395 num_hidden_layers_override: int | None = None,
396 require_post_norm: bool | None = None,
397 prefix: str = "",
398 ):
399 super().__init__()
400 embed_dim = config.hidden_size
401 self.config = config
402 self.embeddings = Siglip2VisionEmbeddings(config)
403 self.encoder = Siglip2Encoder(
404 config,
405 quant_config=quant_config,
406 num_hidden_layers_override=num_hidden_layers_override,
407 prefix=f"{prefix}.encoder",
408 )
409 num_hidden_layers = config.num_hidden_layers
410 if len(self.encoder.layers) > config.num_hidden_layers:
411 raise ValueError(
412 f"The original encoder only has {num_hidden_layers} "
413 f"layers, but you requested {len(self.encoder.layers)} layers."
414 )
415
416 if require_post_norm is None:
417 require_post_norm = len(self.encoder.layers) == num_hidden_layers
418
419 if require_post_norm:
420 self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
421 else:
422 self.post_layernorm = None
423
424 def get_input_embeddings(self):
425 return self.embeddings
426
427 def forward(
428 self,
429 pixel_values_packed: torch.FloatTensor,
430 spatial_shapes: torch.LongTensor,
431 cu_seqlens: torch.Tensor,
432 max_seqlen: torch.Tensor,
433 select_layers: list[int] | None = None,
434 ) -> torch.Tensor:
435 r"""spatial_shapes (`torch.LongTensor` of shape `(batch_size, 2)`):
436 Tensor containing the spatial dimensions (height, width)
437 of the input images.
438 select_layers (`list[int]` or `None`, defaults to `None`):
439 Layer indices to select hidden states from. Supports negative
440 indices (e.g., -1 for last layer, -2 for second-to-last).
441 If None, returns the last layer output.
442 """
443 hidden_states = self.embeddings(pixel_values_packed, spatial_shapes)
444
445 encoder_outputs = self.encoder(
446 inputs_embeds=hidden_states,
447 cu_seqlens=cu_seqlens,
448 max_seqlen=max_seqlen,
449 return_all_hidden_states=select_layers is not None,
450 )
451
452 encoder_outputs = resolve_visual_encoder_outputs(
453 encoder_outputs,
454 self.post_layernorm,
455 select_layers=select_layers,
456 max_possible_layers=self.config.num_hidden_layers,
457 )
458
459 return encoder_outputs
460
461
462 class Siglip2Model(torch.nn.Module):
463 hf_to_vllm_mapper = WeightsMapper(
464 orig_to_new_stacked={
465 ".q_proj": (".qkv_proj", "q"),
466 ".k_proj": (".qkv_proj", "k"),
467 ".v_proj": (".qkv_proj", "v"),
468 }
469 )
470
471 def __init__(
472 self,
473 config: Siglip2VisionConfig,
474 quant_config: QuantizationConfig | None = None,
475 num_hidden_layers_override: int | None = None,
476 require_post_norm: bool | None = None,
477 prefix: str = "",
478 ):
479 super().__init__()
480
481 self.vision_model = Siglip2VisionTransformer(
482 config,
483 quant_config=quant_config,
484 num_hidden_layers_override=num_hidden_layers_override,
485 require_post_norm=require_post_norm,
486 prefix=maybe_prefix(prefix, "vision_model"),
487 )
488 if self.vision_model.post_layernorm is None:
489 self.hf_to_vllm_mapper = self.hf_to_vllm_mapper | WeightsMapper(
490 orig_to_new_prefix={"vision_model.post_layernorm.": None}
491 )
492
493 def forward(
494 self,
495 pixel_values_packed: torch.FloatTensor,
496 spatial_shapes: torch.LongTensor,
497 cu_seqlens: torch.Tensor,
498 max_seqlen: torch.Tensor,
499 select_layers: list[int] | None = None,
500 ) -> torch.Tensor:
501 """Forward pass through the vision model.
502
503 Args:
504 pixel_values_packed: Packed pixel values for all images.
505 spatial_shapes: Per-image spatial dimensions.
506 cu_seqlens: Cumulative sequence lengths of the packed images.
507 max_seqlen: Longest image sequence in the batch.
508 select_layers: Layer indices to select hidden states from.
509 Supports negative indices (e.g., [-2] for second-to-last).
510 If None, returns the last layer output with post_layernorm.
511 Multiple layers can be selected and will be concatenated.
512
513 """
514 return self.vision_model(
515 pixel_values_packed=pixel_values_packed,
516 spatial_shapes=spatial_shapes,
517 cu_seqlens=cu_seqlens,
518 max_seqlen=max_seqlen,
519 select_layers=select_layers,
520 )
521
522 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
523 loader = AutoWeightsLoader(self)
524
525 # Drop layers omitted by num_hidden_layers_override.
526 layer_count = len(self.vision_model.encoder.layers)
527
528 def _filter(ws):
529 for n, w in ws:
530 if (
531 n.startswith("vision_model.encoder.layers.")
532 and int(n.split(".")[3]) >= layer_count
533 ):
534 continue
535 yield n, w
536
537 return loader.load_weights(_filter(weights), mapper=self.hf_to_vllm_mapper)