main
py 1,192 lines 41.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import math
18 from collections.abc import Iterable, Iterator, Mapping, Sequence
19 from functools import partial
20 from typing import Annotated, Literal
21
22 import numpy as np
23 import torch
24 import torch.nn as nn
25 from einops import rearrange
26 from transformers import BaseImageProcessor, BatchFeature, PreTrainedConfig
27 from transformers.activations import GELUActivation
28 from transformers.image_utils import ChannelDimension
29 from transformers.modeling_outputs import (
30 BaseModelOutputWithPooling,
31 )
32 from transformers.utils import torch_int
33
34 from vllm.config import VllmConfig
35 from vllm.config.multimodal import MultiModalDummyOptions
36 from vllm.distributed import parallel_state
37 from vllm.distributed import utils as dist_utils
38 from vllm.inputs import MultiModalDataDict
39 from vllm.model_executor.layers.attention import (
40 MMEncoderAttention,
41 )
42 from vllm.model_executor.layers.conv import Conv2dLayer
43 from vllm.model_executor.layers.linear import (
44 QKVParallelLinear,
45 RowParallelLinear,
46 )
47 from vllm.model_executor.layers.quantization import QuantizationConfig
48 from vllm.model_executor.layers.rotary_embedding.common import (
49 ApplyRotaryEmb,
50 )
51 from vllm.multimodal import MULTIMODAL_REGISTRY
52 from vllm.multimodal.inputs import (
53 MultiModalFeatureSpec,
54 MultiModalFieldConfig,
55 MultiModalKwargsItems,
56 )
57 from vllm.multimodal.parse import (
58 ImageProcessorItems,
59 ImageSize,
60 MultiModalDataItems,
61 )
62 from vllm.multimodal.processing import (
63 BaseDummyInputsBuilder,
64 BaseMultiModalProcessor,
65 BaseProcessingInfo,
66 PromptReplacement,
67 PromptUpdate,
68 )
69 from vllm.sequence import IntermediateTensors
70 from vllm.utils.tensor_schema import TensorSchema, TensorShape
71 from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d
72 from vllm.v1.attention.backends.registry import AttentionBackendEnum
73
74 from .ernie45 import Ernie4_5ForCausalLM
75 from .interfaces import MultiModalEmbeddings, SupportsMRoPE, SupportsMultiModal
76 from .siglip import SiglipMLP
77 from .utils import (
78 AutoWeightsLoader,
79 PPMissingLayer,
80 WeightsMapper,
81 maybe_prefix,
82 )
83 from .vision import get_vit_attn_backend
84
85
86 def smart_resize(
87 height: int,
88 width: int,
89 factor: int = 28,
90 min_pixels: int = 28 * 28 * 130,
91 max_pixels: int = 28 * 28 * 1280,
92 ):
93 """Rescales the image so that the following conditions are met:
94
95 1. Both dimensions (height and width) are divisible by 'factor'.
96
97 2. The total number of pixels is within the range ['min_pixels', 'max_pixels'].
98
99 3. The aspect ratio of the image is maintained as closely as possible.
100
101 """
102 if height < factor:
103 width = round((width * factor) / height)
104 height = factor
105
106 if width < factor:
107 height = round((height * factor) / width)
108 width = factor
109
110 if max(height, width) / min(height, width) > 200:
111 raise ValueError(
112 f"absolute aspect ratio must be smaller than 200, "
113 f"got {max(height, width) / min(height, width)}"
114 )
115 h_bar = round(height / factor) * factor
116 w_bar = round(width / factor) * factor
117 if h_bar * w_bar > max_pixels:
118 beta = math.sqrt((height * width) / max_pixels)
119 h_bar = math.floor(height / beta / factor) * factor
120 w_bar = math.floor(width / beta / factor) * factor
121 elif h_bar * w_bar < min_pixels:
122 beta = math.sqrt(min_pixels / (height * width))
123 h_bar = math.ceil(height * beta / factor) * factor
124 w_bar = math.ceil(width * beta / factor) * factor
125 return h_bar, w_bar
126
127
128 class PaddleOCRVLProcessingInfo(BaseProcessingInfo):
129 def get_hf_config(self):
130 return self.ctx.get_hf_config()
131
132 def get_hf_processor(self, **kwargs: object):
133 return self.ctx.get_hf_processor(**kwargs)
134
135 def get_image_processor(self, **kwargs: object):
136 return self.get_hf_processor(**kwargs).image_processor
137
138 def get_supported_mm_limits(self):
139 return {"image": None}
140
141 def get_num_image_tokens(
142 self,
143 *,
144 image_width: int,
145 image_height: int,
146 image_processor: BaseImageProcessor,
147 mm_kwargs: Mapping[str, object],
148 ) -> int:
149 hf_config = self.get_hf_config()
150 vision_config = hf_config.vision_config
151 patch_size = vision_config.patch_size
152 merge_size = vision_config.spatial_merge_size
153
154 if self.ctx.model_config.trust_remote_code:
155 # Defined in HF Hub repo
156 min_pixels_key = "min_pixels"
157 max_pixels_key = "max_pixels"
158 else:
159 # Defined in Transformers library (requires v5.0 or above)
160 min_pixels_key = "shortest_edge"
161 max_pixels_key = "longest_edge"
162
163 mm_kwargs = self.ctx.get_merged_mm_kwargs(mm_kwargs)
164 size = image_processor.size
165 if override_size := mm_kwargs.get("size"):
166 size = size | override_size
167 if (override_min_pixels := mm_kwargs.get("min_pixels")) is not None:
168 size = size | {min_pixels_key: override_min_pixels}
169 if (override_max_pixels := mm_kwargs.get("max_pixels")) is not None:
170 size = size | {max_pixels_key: override_max_pixels}
171
172 resized_height, resized_width = smart_resize(
173 height=image_height,
174 width=image_width,
175 factor=patch_size * merge_size,
176 min_pixels=size[min_pixels_key],
177 max_pixels=size[max_pixels_key],
178 )
179 preprocessed_size = ImageSize(width=resized_width, height=resized_height)
180
181 grid_t = 1
182 grid_h = preprocessed_size.height // patch_size
183 grid_w = preprocessed_size.width // patch_size
184
185 num_patches = grid_t * grid_h * grid_w
186 num_image_tokens = num_patches // (merge_size**2)
187
188 return num_image_tokens
189
190 def get_image_size_with_most_features(self) -> ImageSize:
191 hf_config = self.get_hf_config()
192 image_processor = self.get_image_processor()
193
194 # See `smart_resize` for the calculation of the image size.
195 merge_size = hf_config.vision_config.spatial_merge_size
196 patch_size = hf_config.vision_config.patch_size
197 factor = merge_size * patch_size
198 if self.ctx.model_config.trust_remote_code:
199 # Defined in HF Hub repo
200 max_pixels = image_processor.max_pixels
201 else:
202 # Defined in Transformers library (requires v5.0 or above)
203 max_pixels = image_processor.size.longest_edge
204 max_num_tokens = max_pixels // (factor**2)
205 # Find factors of max_num_tokens close to its square root
206 # to create a dummy image with a reasonable aspect ratio.
207 h_patches = int(math.sqrt(max_num_tokens))
208 max_num_tokens -= max_num_tokens % h_patches
209 w_patches = max_num_tokens // h_patches
210 return ImageSize(height=h_patches * factor, width=w_patches * factor)
211
212
213 class PaddleOCRVLDummyInputsBuilder(BaseDummyInputsBuilder[PaddleOCRVLProcessingInfo]):
214 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
215 num_images = mm_counts.get("image", 0)
216
217 processor = self.info.get_hf_processor()
218 image_token = processor.image_token
219
220 return image_token * num_images
221
222 def get_dummy_mm_data(
223 self,
224 seq_len: int,
225 mm_counts: Mapping[str, int],
226 mm_options: MultiModalDummyOptions,
227 ) -> MultiModalDataDict:
228 max_image_size = self.info.get_image_size_with_most_features()
229
230 return {
231 "image": self._get_dummy_images(
232 width=max_image_size.width,
233 height=max_image_size.height,
234 num_images=mm_counts.get("image", 0),
235 overrides=mm_options.get("image"),
236 )
237 }
238
239
240 class PaddleOCRVLMultiModalProcessor(
241 BaseMultiModalProcessor[PaddleOCRVLProcessingInfo]
242 ):
243 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
244 return self.dummy_inputs.get_dummy_text(mm_counts)
245
246 def _call_hf_processor(
247 self,
248 hf_data: Mapping[str, object],
249 hf_kwargs: Mapping[str, object],
250 ) -> BatchFeature:
251 final_mm_kwargs = dict(hf_kwargs or {})
252 final_mm_kwargs.setdefault("images_kwargs", {})
253 # vLLM use PIL.Image, always set channel_last
254 final_mm_kwargs["input_data_format"] = ChannelDimension.LAST
255 return self.info.ctx.call_hf_processor(
256 self.info.get_hf_processor(**final_mm_kwargs),
257 hf_data,
258 hf_kwargs,
259 )
260
261 def _postprocess_hf_mm_data(
262 self,
263 hf_data: Mapping[str, object],
264 hf_kwargs: Mapping[str, object],
265 processed_data: BatchFeature,
266 ) -> BatchFeature:
267 if not hf_data:
268 return processed_data
269
270 num_patches_per_image = processed_data["image_grid_thw"].prod(-1)
271 processed_data["pixel_values"] = processed_data["pixel_values"].split(
272 num_patches_per_image.tolist()
273 )
274 return processed_data
275
276 def _get_mm_fields_config(
277 self,
278 hf_inputs: BatchFeature,
279 hf_processor_mm_kwargs: Mapping[str, object],
280 ) -> Mapping[str, MultiModalFieldConfig]:
281 return dict(
282 pixel_values=MultiModalFieldConfig.batched("image"),
283 image_grid_thw=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
284 )
285
286 def _get_prompt_updates(
287 self,
288 mm_items: MultiModalDataItems,
289 hf_processor_mm_kwargs: Mapping[str, object],
290 out_mm_kwargs: MultiModalKwargsItems,
291 ) -> Sequence[PromptUpdate]:
292 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
293 hf_config = self.info.get_hf_config()
294 image_token_id = hf_config.image_token_id
295
296 def get_replacement(item_idx: int, image_processor):
297 images = mm_items.get_items("image", ImageProcessorItems)
298
299 image_size = images.get_image_size(item_idx)
300 num_image_tokens = self.info.get_num_image_tokens(
301 image_width=image_size.width,
302 image_height=image_size.height,
303 image_processor=image_processor,
304 mm_kwargs=hf_processor_mm_kwargs,
305 )
306
307 return [image_token_id] * num_image_tokens
308
309 return [
310 PromptReplacement(
311 modality="image",
312 target=[image_token_id],
313 replacement=partial(get_replacement, image_processor=image_processor),
314 ),
315 ]
316
317
318 class Projector(nn.Module):
319 def __init__(
320 self,
321 text_config: PreTrainedConfig,
322 vision_config: PreTrainedConfig,
323 prefix: str = "",
324 ):
325 super().__init__()
326 self.text_config = text_config
327 self.vision_config = vision_config
328 self.merge_kernel_size = (2, 2)
329
330 self.hidden_size = (
331 self.vision_config.hidden_size
332 * self.merge_kernel_size[0]
333 * self.merge_kernel_size[1]
334 )
335
336 self.pre_norm = torch.nn.LayerNorm(self.vision_config.hidden_size, eps=1e-05)
337 self.linear_1 = nn.Linear(self.hidden_size, self.hidden_size, bias=True)
338 self.act = GELUActivation()
339 self.linear_2 = nn.Linear(
340 self.hidden_size, self.text_config.hidden_size, bias=True
341 )
342
343 def forward(
344 self,
345 image_features: torch.Tensor,
346 image_grid_thw: torch.Tensor,
347 ) -> torch.Tensor:
348 m1, m2 = self.merge_kernel_size
349 if isinstance(image_features, (list, tuple)):
350 processed_features = list()
351 for image_feature, image_grid in zip(image_features, image_grid_thw):
352 image_feature = self.pre_norm(image_feature)
353 t, h, w = image_grid
354
355 image_feature = rearrange(
356 image_feature,
357 "(t h p1 w p2) d -> (t h w) (p1 p2 d)",
358 t=t,
359 h=h // m1,
360 p1=m1,
361 w=w // m2,
362 p2=m2,
363 )
364 hidden_states = self.linear_1(image_feature)
365 hidden_states = self.act(hidden_states)
366 hidden_states = self.linear_2(hidden_states)
367 processed_features.append(hidden_states)
368
369 return processed_features
370
371 dims = image_features.shape[:-1]
372 dim = image_features.shape[-1]
373 image_features = image_features.view(np.prod(dims), dim)
374 hidden_states = self.pre_norm(image_features).view(-1, self.hidden_size)
375 hidden_states = self.linear_1(hidden_states)
376 hidden_states = self.act(hidden_states)
377 hidden_states = self.linear_2(hidden_states)
378
379 return hidden_states.view(*dims, -1)
380
381
382 class PaddleOCRImagePixelInputs(TensorSchema):
383 type: Literal["pixel_values"]
384 pixel_values: Annotated[
385 torch.Tensor,
386 TensorShape("bn", "p", 3, "patch_size", "patch_size", dynamic_dims={"p"}),
387 ]
388 image_grid_thw: Annotated[
389 torch.Tensor,
390 TensorShape("bn", 3),
391 ]
392
393
394 class SiglipVisionEmbeddings(nn.Module):
395 def __init__(self, config: PreTrainedConfig):
396 super().__init__()
397 self.config = config
398 self.embed_dim = config.hidden_size
399 self.image_size = config.image_size
400 self.patch_size = config.patch_size
401
402 self.patch_embedding = Conv2dLayer(
403 in_channels=config.num_channels,
404 out_channels=self.embed_dim,
405 kernel_size=self.patch_size,
406 stride=self.patch_size,
407 padding="valid",
408 )
409
410 self.num_patches = (self.image_size // self.patch_size) ** 2
411 self.num_positions = self.num_patches
412 self.cache_position_embedding: dict[tuple[int, int], torch.Tensor] = {}
413 self.cache_position_count: dict[tuple[int, int], int] = {}
414 self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
415
416 self.register_buffer(
417 "position_ids",
418 torch.arange(self.num_positions).expand((1, -1)),
419 persistent=False,
420 )
421
422 def interpolate_pos_encoding(
423 self,
424 embeddings: torch.Tensor,
425 height: int,
426 width: int,
427 is_after_patchify: bool = False,
428 ) -> torch.Tensor:
429 num_positions = self.position_embedding.weight.shape[0]
430
431 patch_pos_embed = self.position_embedding.weight.unsqueeze(0)
432
433 dim = embeddings.shape[-1]
434
435 if is_after_patchify:
436 new_height = height
437 new_width = width
438 else:
439 new_height = height // self.patch_size
440 new_width = width // self.patch_size
441
442 sqrt_num_positions = torch_int(num_positions**0.5)
443 patch_pos_embed = patch_pos_embed.reshape(
444 1, sqrt_num_positions, sqrt_num_positions, dim
445 )
446 patch_pos_embed = patch_pos_embed.permute(0, 3, 1, 2)
447
448 patch_pos_embed = nn.functional.interpolate(
449 patch_pos_embed,
450 size=(new_height, new_width),
451 mode="bilinear",
452 align_corners=False,
453 )
454
455 patch_pos_embed = patch_pos_embed.permute(0, 2, 3, 1).view(1, -1, dim)
456 return patch_pos_embed
457
458 def fetch_position_embedding_lfu_cache(
459 self, embeddings: torch.Tensor, h: int, w: int, max_cache: int = 20
460 ):
461 grid = (h, w)
462 if grid in self.cache_position_embedding:
463 self.cache_position_count[grid] += 1
464 return self.cache_position_embedding[grid]
465
466 if len(self.cache_position_embedding) >= max_cache:
467 min_hit_grid = min(
468 self.cache_position_count,
469 key=self.cache_position_count.__getitem__,
470 )
471 self.cache_position_count.pop(min_hit_grid)
472 self.cache_position_embedding.pop(min_hit_grid)
473
474 position_embedding = self.interpolate_pos_encoding(embeddings, h, w, True)
475 self.cache_position_count[grid] = 1
476 self.cache_position_embedding[grid] = position_embedding
477 return position_embedding
478
479 def forward(
480 self,
481 pixel_values: torch.FloatTensor,
482 position_ids: torch.Tensor | None = None,
483 image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
484 interpolate_pos_encoding=False,
485 ) -> torch.Tensor:
486 if pixel_values.dim() == 4:
487 pixel_values = pixel_values.unsqueeze(0)
488 if pixel_values.dim() == 5:
489 if position_ids is None:
490 raise ValueError(
491 "position_ids cannot be None when pixel_values.dim() is 5."
492 )
493 (
494 batch_size,
495 sequence_len,
496 channel,
497 height,
498 width,
499 ) = pixel_values.shape
500 target_dtype = self.patch_embedding.weight.dtype
501 pixel_values = rearrange(pixel_values, "b l c h w -> (b l) c h w")
502 patch_embeds = self.patch_embedding(pixel_values.to(dtype=target_dtype))
503 embeddings = patch_embeds.flatten(-2).squeeze(-1)
504
505 start = 0
506 tmp_embeddings = list()
507 assert image_grid_thw is not None
508 for image_grid in image_grid_thw:
509 t, h, w = image_grid
510 end = start + t * h * w
511 image_embeddings = embeddings[start:end, :]
512 position_embedding = (
513 self.interpolate_pos_encoding(image_embeddings, h, w, True)
514 .squeeze(0)
515 .repeat(t, 1)
516 )
517 image_embeddings = image_embeddings + position_embedding
518 tmp_embeddings.append(image_embeddings)
519 start = end
520 embeddings = torch.concat(tmp_embeddings, dim=0).unsqueeze(0)
521
522 return embeddings
523 else:
524 raise ValueError(
525 "Unsupported pixel_values dimension:"
526 f" {pixel_values.dim()}. Expected 4 or 5."
527 )
528
529
530 def all_gather_interleave(local_tensor: torch.Tensor, hidden_size: int, tp_size: int):
531 """All-gather the input tensor interleavely across model parallel group."""
532 import torch.distributed as dist
533
534 gathered_tensors = [torch.zeros_like(local_tensor) for _ in range(tp_size)]
535 dist.all_gather(
536 gathered_tensors, local_tensor, group=parallel_state.get_tp_group().device_group
537 )
538
539 gathered_tensors_split = [
540 torch.split(tensor, hidden_size // tp_size, -1) for tensor in gathered_tensors
541 ]
542 ordered_tensors = [
543 tensor for pair in zip(*gathered_tensors_split) for tensor in pair
544 ]
545 result_tensor = torch.cat(ordered_tensors, dim=-1)
546 return result_tensor
547
548
549 class SiglipAttention(nn.Module):
550 """SigLIP vision attention adapted from Qwen2.5-VisionAttention."""
551
552 def __init__(
553 self,
554 *,
555 embed_dim: int,
556 num_heads: int,
557 projection_size: int,
558 quant_config: QuantizationConfig | None = None,
559 prefix: str = "",
560 ) -> None:
561 super().__init__()
562
563 self.tp_size = parallel_state.get_tensor_model_parallel_world_size()
564 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
565 self.hidden_size_per_attention_head = dist_utils.divide(
566 projection_size, num_heads
567 )
568 self.num_attention_heads_per_partition = dist_utils.divide(
569 num_heads, self.tp_size
570 )
571
572 self.qkv_proj = QKVParallelLinear(
573 hidden_size=embed_dim,
574 head_size=self.hidden_size_per_attention_head,
575 total_num_heads=num_heads,
576 total_num_kv_heads=num_heads,
577 bias=True,
578 quant_config=quant_config,
579 prefix=f"{prefix}.qkv_proj",
580 )
581 self.out_proj = RowParallelLinear(
582 input_size=projection_size,
583 output_size=embed_dim,
584 quant_config=quant_config,
585 prefix=f"{prefix}.out_proj",
586 )
587 self.attn = MMEncoderAttention(
588 num_heads=self.num_attention_heads_per_partition,
589 head_size=self.hidden_size_per_attention_head,
590 scale=self.hidden_size_per_attention_head**-0.5,
591 prefix=f"{prefix}.attn",
592 )
593 self.apply_rotary_emb = ApplyRotaryEmb(
594 enforce_enable=True,
595 enable_fp32_compute=True,
596 )
597
598 def split_qkv(self, qkv: torch.Tensor) -> tuple[torch.Tensor, ...]:
599 seq_len, bs, _ = qkv.shape
600 if self.tp_size > 1:
601 qkv = all_gather_interleave(qkv, self.qkv_proj.hidden_size, self.tp_size)
602
603 q, k, v = qkv.chunk(3, dim=2)
604
605 if self.tp_size > 1:
606 splitter = partial(
607 dist_utils.split_tensor_along_last_dim, num_partitions=self.tp_size
608 )
609 q = splitter(q)[self.tp_rank]
610 k = splitter(k)[self.tp_rank]
611 v = splitter(v)[self.tp_rank]
612
613 new_shape = (
614 seq_len,
615 bs,
616 self.num_attention_heads_per_partition,
617 self.hidden_size_per_attention_head,
618 )
619 q, k, v = (x.view(*new_shape) for x in (q, k, v))
620 return q, k, v
621
622 def forward(
623 self,
624 hidden_states: torch.Tensor,
625 *,
626 cu_seqlens: torch.Tensor,
627 rotary_pos_emb: torch.Tensor | None,
628 max_seqlen: torch.Tensor | None,
629 ) -> torch.Tensor:
630 batch_size, _, _ = hidden_states.shape
631
632 x = rearrange(hidden_states, "b s d -> s b d")
633 x, _ = self.qkv_proj(x)
634 q, k, v = self.split_qkv(x)
635 q, k, v = (rearrange(t, "s b h d -> b s h d") for t in (q, k, v))
636
637 if rotary_pos_emb is not None:
638 qk_concat = torch.cat([q, k], dim=0)
639 qk_rotated = self.apply_rotary_emb(
640 qk_concat,
641 rotary_pos_emb.cos(),
642 rotary_pos_emb.sin(),
643 )
644 q, k = torch.chunk(qk_rotated, 2, dim=0)
645
646 context_layer = self.attn(
647 query=q,
648 key=k,
649 value=v,
650 cu_seqlens=cu_seqlens,
651 max_seqlen=max_seqlen,
652 )
653 context_layer = rearrange(context_layer, "b s h d -> b s (h d)")
654
655 output, _ = self.out_proj(context_layer)
656 return output
657
658
659 class SigLIPRotaryEmbedding(nn.Module):
660 def __init__(self, dim: int, theta: float = 10000.0) -> None:
661 super().__init__()
662 self.dim = dim
663 self.theta = theta
664 self.rope_init()
665
666 def rope_init(self):
667 inv_freq = 1.0 / (
668 self.theta ** (torch.arange(0, self.dim, 2, dtype=torch.float) / self.dim)
669 )
670 self.register_buffer("inv_freq", inv_freq, persistent=False)
671
672 def forward(self, seqlen: int) -> torch.Tensor:
673 seq = torch.arange(
674 seqlen,
675 device=self.inv_freq.device,
676 dtype=self.inv_freq.dtype,
677 )
678 freqs = torch.outer(seq, self.inv_freq)
679 return freqs
680
681
682 class SiglipEncoderLayer(nn.Module):
683 def __init__(
684 self,
685 config: PreTrainedConfig,
686 quant_config: QuantizationConfig | None = None,
687 prefix: str = "",
688 ):
689 super().__init__()
690 self.embed_dim = config.hidden_size
691 self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
692 self.self_attn = SiglipAttention(
693 embed_dim=config.hidden_size,
694 num_heads=config.num_attention_heads,
695 projection_size=config.hidden_size,
696 quant_config=quant_config,
697 prefix=f"{prefix}.self_attn",
698 )
699 self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
700 self.mlp = SiglipMLP(
701 config,
702 quant_config=quant_config,
703 prefix=f"{prefix}.mlp",
704 )
705
706 def forward(
707 self,
708 hidden_states: torch.Tensor,
709 *,
710 cu_seqlens: torch.Tensor,
711 rotary_pos_emb: torch.Tensor | None,
712 max_seqlen: torch.Tensor | None,
713 ) -> torch.Tensor:
714 residual = hidden_states
715
716 hidden_states = self.layer_norm1(hidden_states)
717 hidden_states = self.self_attn(
718 hidden_states=hidden_states,
719 cu_seqlens=cu_seqlens,
720 rotary_pos_emb=rotary_pos_emb,
721 max_seqlen=max_seqlen,
722 )
723
724 hidden_states = residual + hidden_states
725
726 residual = hidden_states
727 hidden_states = self.layer_norm2(hidden_states)
728 hidden_states = self.mlp(hidden_states)
729
730 hidden_states = residual + hidden_states
731
732 return hidden_states
733
734
735 class SiglipEncoder(nn.Module):
736 def __init__(
737 self,
738 config: PreTrainedConfig,
739 quant_config: QuantizationConfig | None = None,
740 prefix: str = "",
741 ):
742 super().__init__()
743 self.config = config
744 embed_dim = config.hidden_size
745 num_heads = config.num_attention_heads
746 head_dim = embed_dim // num_heads
747
748 self.attn_backend = get_vit_attn_backend(
749 head_size=head_dim,
750 dtype=torch.get_default_dtype(),
751 )
752
753 self.layers = nn.ModuleList(
754 [
755 SiglipEncoderLayer(
756 config,
757 quant_config=quant_config,
758 prefix=f"{prefix}.layers.{layer_idx}",
759 )
760 for layer_idx in range(config.num_hidden_layers)
761 ]
762 )
763 self.rotary_pos_emb = SigLIPRotaryEmbedding(head_dim // 2)
764
765 @staticmethod
766 def flatten_list(image_grid_thw):
767 tmp_image_grid_thw = list()
768 for image_grid in image_grid_thw:
769 if isinstance(image_grid, list):
770 tmp_image_grid_thw.extend(image_grid)
771 else:
772 tmp_image_grid_thw.append(image_grid)
773 return tmp_image_grid_thw
774
775 def forward(
776 self,
777 inputs_embeds,
778 cu_seqlens: torch.Tensor | None = None,
779 image_grid_thw: list[tuple[int, int, int] | list[tuple[int, int, int]]]
780 | None = None,
781 height_position_ids: torch.Tensor | None = None,
782 width_position_ids: torch.Tensor | None = None,
783 ) -> torch.Tensor:
784 device = inputs_embeds.device
785 hidden_states = inputs_embeds
786
787 flatten_image_grid_thw = self.flatten_list(image_grid_thw)
788
789 if width_position_ids is None or height_position_ids is None:
790 split_hids = list()
791 split_wids = list()
792 for t, h, w in flatten_image_grid_thw:
793 image_pids = torch.arange(t * h * w, device=device) % (h * w)
794 sample_hids = image_pids // w
795 sample_wids = image_pids % w
796 split_hids.append(sample_hids)
797 split_wids.append(sample_wids)
798 width_position_ids = torch.concat(split_wids, dim=0)
799 height_position_ids = torch.concat(split_hids, dim=0)
800
801 pids = torch.stack(
802 [height_position_ids, width_position_ids],
803 dim=-1,
804 )
805 # The ids are built from the grids above, so `h`/`w` bound them
806 # and the table size is known on the host.
807 max_grid_size = max(max(h, w) for _, h, w in flatten_image_grid_thw)
808 rope_emb_max_grid = self.rotary_pos_emb(max_grid_size)
809 rotary_pos_emb = rope_emb_max_grid[pids].flatten(1)
810
811 if cu_seqlens is None:
812 raise ValueError("cu_seqlens cannot be None for SiglipEncoder.")
813 if not isinstance(cu_seqlens, torch.Tensor):
814 cu_seqlens = async_tensor_h2d(cu_seqlens, dtype=torch.int32, device=device)
815 else:
816 cu_seqlens = cu_seqlens.to(device=device, non_blocking=True)
817
818 max_seqlen = None
819 if self.attn_backend in {
820 AttentionBackendEnum.FLASH_ATTN,
821 AttentionBackendEnum.ROCM_AITER_FA,
822 AttentionBackendEnum.TRITON_ATTN,
823 }:
824 max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max()
825
826 hidden_states = inputs_embeds
827 for encoder_layer in self.layers:
828 hidden_states = encoder_layer(
829 hidden_states,
830 cu_seqlens=cu_seqlens,
831 rotary_pos_emb=rotary_pos_emb,
832 max_seqlen=max_seqlen,
833 )
834 return hidden_states
835
836
837 class SiglipVisionTransformer(nn.Module):
838 def __init__(
839 self,
840 config: PreTrainedConfig,
841 quant_config: QuantizationConfig | None = None,
842 prefix: str = "",
843 ):
844 super().__init__()
845 self.config = config
846 embed_dim = config.hidden_size
847
848 self.embeddings = SiglipVisionEmbeddings(config)
849 self.encoder = SiglipEncoder(
850 config,
851 quant_config=quant_config,
852 prefix=f"{prefix}.encoder",
853 )
854 self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
855
856 def forward(
857 self,
858 pixel_values: torch.Tensor,
859 interpolate_pos_encoding: bool | None = False,
860 position_ids: torch.Tensor | None = None,
861 height_position_ids: torch.Tensor | None = None,
862 width_position_ids: torch.Tensor | None = None,
863 cu_seqlens: torch.Tensor | None = None,
864 image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
865 ) -> torch.Tensor:
866 hidden_states = self.embeddings(
867 pixel_values,
868 interpolate_pos_encoding=interpolate_pos_encoding,
869 position_ids=position_ids,
870 image_grid_thw=image_grid_thw,
871 )
872
873 last_hidden_state = self.encoder(
874 inputs_embeds=hidden_states,
875 cu_seqlens=cu_seqlens,
876 image_grid_thw=image_grid_thw,
877 height_position_ids=height_position_ids,
878 width_position_ids=width_position_ids,
879 )
880
881 last_hidden_state = self.post_layernorm(last_hidden_state)
882 return last_hidden_state
883
884
885 class SiglipVisionModel(nn.Module):
886 hf_to_vllm_mapper = WeightsMapper(
887 orig_to_new_stacked={
888 ".q_proj": (".qkv_proj", "q"),
889 ".k_proj": (".qkv_proj", "k"),
890 ".v_proj": (".qkv_proj", "v"),
891 },
892 # The SigLIP attention pooling head and packing pos embedding are
893 # present in the checkpoint but absent from this vision tower.
894 orig_to_new_substr={
895 "head.attention": None,
896 "head.layernorm": None,
897 "head.mlp": None,
898 "head.probe": None,
899 "packing_position_embedding": None,
900 },
901 )
902
903 def __init__(
904 self,
905 config,
906 quant_config: QuantizationConfig | None = None,
907 prefix: str = "",
908 ):
909 super().__init__()
910
911 self.vision_model = SiglipVisionTransformer(
912 config,
913 quant_config=quant_config,
914 prefix=maybe_prefix(prefix, "vision_model"),
915 )
916 self.quant_config = quant_config
917
918 @property
919 def dtype(self) -> torch.dtype:
920 return self.vision_model.embeddings.patch_embedding.weight.dtype
921
922 @property
923 def device(self) -> torch.device:
924 return self.vision_model.embeddings.patch_embedding.weight.device
925
926 def get_input_embeddings(self) -> nn.Module:
927 return self.vision_model.embeddings.patch_embedding
928
929 def forward(
930 self,
931 pixel_values,
932 interpolate_pos_encoding: bool = False,
933 position_ids: torch.Tensor | None = None,
934 image_grid_thw: Sequence[tuple[int, int, int]] | None = None,
935 cu_seqlens: torch.Tensor | None = None,
936 ) -> BaseModelOutputWithPooling:
937 return self.vision_model(
938 pixel_values=pixel_values,
939 interpolate_pos_encoding=interpolate_pos_encoding,
940 position_ids=position_ids,
941 image_grid_thw=image_grid_thw,
942 cu_seqlens=cu_seqlens,
943 )
944
945 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
946 loader = AutoWeightsLoader(self)
947 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
948
949
950 @MULTIMODAL_REGISTRY.register_processor(
951 PaddleOCRVLMultiModalProcessor,
952 info=PaddleOCRVLProcessingInfo,
953 dummy_inputs=PaddleOCRVLDummyInputsBuilder,
954 )
955 class PaddleOCRVLForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsMRoPE):
956 hf_to_vllm_mapper = WeightsMapper(
957 orig_to_new_prefix={
958 "model.": "language_model.model.",
959 "lm_head.": "language_model.lm_head.",
960 }
961 )
962
963 @classmethod
964 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
965 if modality.startswith("image"):
966 return "<|IMAGE_START|><|IMAGE_PLACEHOLDER|><|IMAGE_END|>"
967
968 raise ValueError("Only image modality is supported")
969
970 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
971 super().__init__()
972 config = vllm_config.model_config.hf_config
973 if hasattr(config, "text_config"):
974 text_config = config.text_config.to_dict()
975 unsafe_keys = ["model_type", "architectures", "tie_word_embeddings"]
976 for key in unsafe_keys:
977 text_config.pop(key, None)
978 config.update(text_config)
979
980 quant_config = vllm_config.quant_config
981
982 self.config = config
983
984 with self._mark_tower_model(vllm_config, "image"):
985 self.visual = SiglipVisionModel(
986 config=config.vision_config,
987 quant_config=quant_config,
988 prefix=maybe_prefix(prefix, "visual"),
989 )
990 self.mlp_AR = Projector(config, config.vision_config)
991
992 with self._mark_language_model(vllm_config):
993 self.language_model = Ernie4_5ForCausalLM(
994 vllm_config=vllm_config,
995 prefix=maybe_prefix(prefix, "language_model"),
996 )
997
998 for layer in self.language_model.model.layers:
999 if not isinstance(layer, PPMissingLayer):
1000 layer.self_attn.rotary_emb.is_neox_style = True
1001
1002 self.make_empty_intermediate_tensors = (
1003 self.language_model.make_empty_intermediate_tensors
1004 )
1005
1006 def compute_logits(
1007 self,
1008 hidden_states: torch.Tensor,
1009 ) -> torch.Tensor | None:
1010 return self.language_model.compute_logits(hidden_states)
1011
1012 def iter_mm_grid_thw(
1013 self, mm_features: list[MultiModalFeatureSpec]
1014 ) -> Iterator[tuple[int, int, int, int, float]]:
1015 """Iterate over multimodal features and yield grid information.
1016
1017 Args:
1018 mm_features: List of multimodal feature specifications
1019
1020 Yields:
1021 Tuple of (offset, grid_t, grid_h, grid_w, t_factor) for each frame/image
1022
1023 """
1024 spatial_merge_size = self.config.vision_config.spatial_merge_size
1025 tokens_per_second = getattr(self.config.vision_config, "tokens_per_second", 1.0)
1026 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1027 offset = mm_feature.mm_position.offset
1028 feature_data = mm_feature.data
1029 assert feature_data is not None
1030 if mm_feature.modality == "image":
1031 grid_data = feature_data["image_grid_thw"].data
1032 assert isinstance(grid_data, torch.Tensor)
1033 t, h, w = grid_data.tolist()
1034 assert t == 1, f"Image must have 1 frame, got {t}"
1035 yield offset, 1, h // spatial_merge_size, w // spatial_merge_size, 1.0
1036 elif mm_feature.modality == "video":
1037 grid_data = feature_data["video_grid_thw"].data
1038 assert isinstance(grid_data, torch.Tensor)
1039 t, h, w = grid_data.tolist()
1040 second_per_grid_ts = 1.0
1041 second_per_grid_item = feature_data.get("second_per_grid_ts")
1042 if second_per_grid_item is not None:
1043 second_per_grid_data = second_per_grid_item.data
1044 assert isinstance(second_per_grid_data, torch.Tensor)
1045 second_per_grid_ts = second_per_grid_data.item()
1046 t_factor = second_per_grid_ts * tokens_per_second
1047 yield (
1048 offset,
1049 t,
1050 h // spatial_merge_size,
1051 w // spatial_merge_size,
1052 t_factor,
1053 )
1054 else:
1055 raise ValueError(f"Unsupported modality: {mm_feature.modality}")
1056
1057 def get_mrope_input_positions(
1058 self,
1059 input_tokens: list[int],
1060 mm_features: list[MultiModalFeatureSpec],
1061 ) -> tuple[torch.Tensor, int]:
1062 llm_pos_ids_list: list = []
1063 st = 0
1064
1065 for (
1066 offset,
1067 llm_grid_t,
1068 llm_grid_h,
1069 llm_grid_w,
1070 t_factor,
1071 ) in self.iter_mm_grid_thw(mm_features):
1072 text_len = offset - st
1073 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1074 llm_pos_ids_list.append(
1075 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1076 )
1077
1078 grid_indices = np.indices((llm_grid_t, llm_grid_h, llm_grid_w))
1079 if t_factor != 1.0:
1080 grid_indices[0] = (grid_indices[0] * t_factor).astype(np.int64)
1081
1082 llm_pos_ids_list.append(grid_indices.reshape(3, -1) + text_len + st_idx)
1083 st = offset + llm_grid_t * llm_grid_h * llm_grid_w
1084
1085 if st < len(input_tokens):
1086 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1087 text_len = len(input_tokens) - st
1088 llm_pos_ids_list.append(
1089 np.broadcast_to(np.arange(text_len), (3, text_len)) + st_idx
1090 )
1091
1092 llm_positions = np.concatenate(llm_pos_ids_list, axis=1).reshape(3, -1)
1093 mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
1094
1095 return torch.from_numpy(llm_positions), mrope_position_delta
1096
1097 def _parse_and_validate_image_input(
1098 self, **kwargs: object
1099 ) -> PaddleOCRImagePixelInputs | None:
1100 pixel_values = kwargs.pop("pixel_values", None)
1101 image_grid_thw = kwargs.pop("image_grid_thw", None)
1102
1103 if pixel_values is None:
1104 return None
1105
1106 return PaddleOCRImagePixelInputs(
1107 type="pixel_values",
1108 pixel_values=pixel_values,
1109 image_grid_thw=image_grid_thw,
1110 )
1111
1112 def forward(
1113 self,
1114 input_ids: torch.Tensor | None,
1115 positions: torch.Tensor,
1116 intermediate_tensors: IntermediateTensors | None = None,
1117 inputs_embeds: torch.Tensor | None = None,
1118 **kwargs,
1119 ):
1120 if intermediate_tensors is not None:
1121 inputs_embeds = None
1122
1123 return self.language_model(
1124 input_ids, positions, intermediate_tensors, inputs_embeds
1125 )
1126
1127 def encode_image(
1128 self, pixel_values: torch.Tensor, image_grid_thw: torch.Tensor
1129 ) -> torch.Tensor:
1130 pixel_values = pixel_values.type(self.visual.dtype)
1131 siglip_position_ids = list()
1132 image_grid_hws = list()
1133 cu_seqlens = [0]
1134
1135 thw_tuple = tuple(image_grid_thw.tolist())
1136 numel = np.prod(thw_tuple)
1137 image_grid_hws.append(thw_tuple)
1138 image_position_ids = torch.arange(numel) % np.prod(thw_tuple[1:])
1139 siglip_position_ids.append(image_position_ids)
1140 cu_seqlens.append(cu_seqlens[-1] + numel)
1141
1142 # Both are built on the host; concat straight into a pinned buffer
1143 # so the H2D copy stays non-blocking.
1144 siglip_position_ids = torch.concat(
1145 siglip_position_ids,
1146 dim=0,
1147 out=torch.empty(
1148 sum(t.numel() for t in siglip_position_ids),
1149 dtype=torch.int64,
1150 pin_memory=PIN_MEMORY,
1151 ),
1152 ).to(pixel_values.device, non_blocking=True)
1153 cu_seqlens = async_tensor_h2d(
1154 cu_seqlens, dtype=torch.int32, device=pixel_values.device
1155 )
1156
1157 vision_outputs = self.visual(
1158 pixel_values=pixel_values,
1159 image_grid_thw=image_grid_hws,
1160 position_ids=siglip_position_ids,
1161 interpolate_pos_encoding=True,
1162 cu_seqlens=cu_seqlens,
1163 )
1164 return vision_outputs
1165
1166 def _process_image_input(
1167 self, image_input: PaddleOCRImagePixelInputs
1168 ) -> MultiModalEmbeddings:
1169 pixel_values = image_input.pixel_values
1170 image_grid_thw = image_input.image_grid_thw
1171 vision_outputs = tuple(
1172 self.encode_image(pixel, grid).squeeze(0)
1173 for pixel, grid in zip(pixel_values, image_grid_thw)
1174 )
1175 image_embeds = self.mlp_AR(vision_outputs, image_grid_thw)
1176 return image_embeds
1177
1178 def embed_multimodal(self, **kwargs) -> MultiModalEmbeddings:
1179 image_input = self._parse_and_validate_image_input(**kwargs)
1180 if image_input is None:
1181 return ()
1182
1183 multimodal_embeddings: tuple[torch.Tensor, ...] = ()
1184 image_embeds = self._process_image_input(image_input)
1185 multimodal_embeddings += tuple(image_embeds)
1186
1187 return multimodal_embeddings
1188
1189 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1190 loader = AutoWeightsLoader(self)
1191 autoloaded_weights = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1192 return autoloaded_weights