main
py 1,352 lines 48.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 #
4 # Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved.
5 # Adapted from vllm/model_executor/models/qwen2_5_vl.py
6 # Copyright 2025 The vLLM team.
7 # Copyright 2025 The Qwen Team.
8 #
9 # This file is a part of the vllm-ascend project.
10 #
11 # Licensed under the Apache License, Version 2.0 (the "License");
12 # you may not use this file except in compliance with the License.
13 # You may obtain a copy of the License at
14 #
15 # http://www.apache.org/licenses/LICENSE-2.0
16 #
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
22
23 from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
24 from functools import lru_cache, partial
25 from typing import Annotated, Literal, Optional, TypeAlias
26
27 import torch
28 import torch.nn as nn
29 import torch.nn.functional as F
30 from einops import rearrange
31 from torchvision.transforms import v2
32 from transformers.utils import logging
33
34 from vllm.config import VllmConfig
35 from vllm.distributed import parallel_state
36 from vllm.distributed import utils as dist_utils
37 from vllm.model_executor.layers.activation import _ACTIVATION_REGISTRY
38 from vllm.model_executor.layers.attention.mm_encoder_attention import MMEncoderAttention
39 from vllm.model_executor.layers.layernorm import RMSNorm
40 from vllm.model_executor.layers.linear import (
41 ColumnParallelLinear,
42 MergedColumnParallelLinear,
43 QKVParallelLinear,
44 RowParallelLinear,
45 )
46 from vllm.model_executor.layers.quantization import QuantizationConfig
47 from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQConfig
48 from vllm.model_executor.layers.rotary_embedding.common import ApplyRotaryEmb
49 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
50 from vllm.model_executor.models.interfaces import (
51 MultiModalEmbeddings,
52 SupportsLoRA,
53 SupportsMRoPE,
54 SupportsMultiModal,
55 SupportsPP,
56 )
57 from vllm.model_executor.models.module_mapping import MultiModelKeys
58 from vllm.model_executor.models.qwen2_5_vl import (
59 Qwen2_5_VLDummyInputsBuilder,
60 Qwen2_5_VLMultiModalProcessor,
61 Qwen2_5_VLProcessingInfo,
62 )
63 from vllm.model_executor.models.utils import (
64 AutoWeightsLoader,
65 WeightsMapper,
66 init_vllm_registered_model,
67 maybe_prefix,
68 )
69 from vllm.multimodal import MULTIMODAL_REGISTRY
70 from vllm.multimodal.inputs import (
71 MultiModalFeatureSpec,
72 MultiModalKwargsItems,
73 )
74 from vllm.multimodal.parse import MultiModalDataItems
75 from vllm.multimodal.processing import (
76 PromptReplacement,
77 PromptUpdate,
78 PromptUpdateDetails,
79 )
80 from vllm.sequence import IntermediateTensors
81 from vllm.utils.tensor_schema import TensorSchema, TensorShape
82 from vllm.v1.attention.backends.registry import AttentionBackendEnum
83
84 from .vision import get_vit_attn_backend
85
86 logger = logging.get_logger(__name__)
87
88
89 class OpenPanguVisionAttention(nn.Module):
90 def __init__(
91 self,
92 embed_dim: int,
93 num_heads: int,
94 projection_size: int,
95 quant_config: QuantizationConfig | None = None,
96 prefix: str = "",
97 ) -> None:
98 super().__init__()
99 self.hidden_size_per_attention_head = dist_utils.divide(
100 projection_size, num_heads
101 )
102 self.tp_size = parallel_state.get_tensor_model_parallel_world_size()
103 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
104 self.num_attention_heads_per_partition = dist_utils.divide(
105 num_heads, self.tp_size
106 )
107
108 self.qkv = QKVParallelLinear(
109 hidden_size=embed_dim,
110 head_size=self.hidden_size_per_attention_head,
111 total_num_heads=num_heads,
112 total_num_kv_heads=num_heads,
113 bias=True,
114 quant_config=quant_config,
115 prefix=f"{prefix}.qkv",
116 )
117 self.proj = RowParallelLinear(
118 input_size=projection_size,
119 output_size=embed_dim,
120 quant_config=quant_config,
121 prefix=f"{prefix}.proj",
122 )
123 self.attn = MMEncoderAttention(
124 num_heads=self.num_attention_heads_per_partition,
125 head_size=self.hidden_size_per_attention_head,
126 scale=self.hidden_size_per_attention_head**-0.5,
127 prefix=f"{prefix}.attn",
128 )
129 self.apply_rotary_emb = ApplyRotaryEmb(enforce_enable=True)
130
131 def forward(
132 self,
133 x: torch.Tensor,
134 cu_seqlens: torch.Tensor,
135 cos: torch.Tensor,
136 sin: torch.Tensor,
137 ) -> torch.Tensor:
138 seq_length, _ = x.size()
139 x, bias = self.qkv(x)
140 if bias is not None:
141 x = x + bias
142 q, k, v = x.chunk(3, dim=1)
143
144 q, k, v = (
145 rearrange(
146 x, "s (b n d) -> b s n d", d=self.hidden_size_per_attention_head, b=1
147 ).contiguous()
148 for x in (q, k, v)
149 )
150 qk_concat = torch.cat([q, k], dim=0)
151 qk_rotated = self.apply_rotary_emb(qk_concat, cos, sin)
152 q, k = torch.chunk(qk_rotated, 2, dim=0)
153
154 max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max()
155 context_layer = self.attn(
156 query=q,
157 key=k,
158 value=v,
159 cu_seqlens=cu_seqlens,
160 max_seqlen=max_seqlen,
161 )
162 context_layer = rearrange(
163 context_layer, "b s h d -> s (b h d)", b=1
164 ).contiguous()
165 output, bias = self.proj(context_layer)
166 if bias is not None:
167 output = output + bias
168 return output
169
170
171 class OpenPanguVisionMLP(nn.Module):
172 def __init__(
173 self,
174 in_features: int,
175 hidden_features: int,
176 bias: bool = False,
177 act_fn: Callable[[torch.Tensor], torch.Tensor] = F.silu,
178 vision_config=None,
179 quant_config: QuantizationConfig | None = None,
180 prefix: str = "",
181 ):
182 super().__init__()
183 self.hidden_act = vision_config.hidden_act
184 if self.hidden_act == "silu":
185 tp_size = parallel_state.get_tensor_model_parallel_world_size()
186 if hidden_features % tp_size != 0:
187 hidden_features = (hidden_features + tp_size - 1) // tp_size * tp_size
188 self.gate_up_proj = MergedColumnParallelLinear(
189 input_size=in_features,
190 output_sizes=[hidden_features] * 2,
191 bias=bias,
192 quant_config=quant_config,
193 prefix=f"{prefix}.gate_up_proj",
194 )
195 else:
196 self.up_proj = ColumnParallelLinear(
197 in_features,
198 hidden_features,
199 bias=bias,
200 quant_config=quant_config,
201 prefix=f"{prefix}.up_proj",
202 )
203
204 self.down_proj = RowParallelLinear(
205 hidden_features,
206 in_features,
207 bias=bias,
208 quant_config=quant_config,
209 prefix=f"{prefix}.down_proj",
210 )
211 self.act_fn = act_fn
212
213 def forward(self, x: torch.Tensor):
214 if self.hidden_act == "silu":
215 x, _ = self.gate_up_proj(x)
216 else:
217 x, _ = self.up_proj(x)
218 x = self.act_fn(x)
219 x, _ = self.down_proj(x)
220 return x
221
222
223 class OpenPanguVisionBlock(nn.Module):
224 def __init__(
225 self,
226 dim: int,
227 num_heads: int,
228 mlp_hidden_dim: int,
229 act_fn: Callable[[torch.Tensor], torch.Tensor] = F.silu,
230 norm_layer: Callable[[int], nn.Module] | None = None,
231 vision_config=None,
232 quant_config: QuantizationConfig | None = None,
233 prefix: str = "",
234 ) -> None:
235 super().__init__()
236 if norm_layer is None:
237 norm_layer = partial(nn.LayerNorm, eps=1e-6)
238 self.norm1 = norm_layer(dim)
239 self.norm2 = norm_layer(dim)
240 self.attn = OpenPanguVisionAttention(
241 embed_dim=dim,
242 num_heads=num_heads,
243 projection_size=dim,
244 quant_config=quant_config,
245 prefix=f"{prefix}.attn",
246 )
247 self.mlp = OpenPanguVisionMLP(
248 dim,
249 mlp_hidden_dim,
250 act_fn=act_fn,
251 bias=True,
252 vision_config=vision_config,
253 quant_config=quant_config,
254 prefix=f"{prefix}.mlp",
255 )
256
257 def forward(
258 self,
259 hidden_states: torch.Tensor,
260 cu_seqlens: torch.Tensor,
261 cos: torch.Tensor,
262 sin: torch.Tensor,
263 ) -> torch.Tensor:
264 hidden_states = hidden_states + self.attn(
265 self.norm1(hidden_states), cu_seqlens=cu_seqlens, cos=cos, sin=sin
266 )
267 hidden_states = hidden_states + self.mlp(self.norm2(hidden_states))
268 return hidden_states
269
270
271 class OpenPanguVisionRotaryEmbedding(nn.Module):
272 def __init__(self, dim: int, theta: float = 10000.0) -> None:
273 super().__init__()
274 self.inv_freq = 1.0 / (
275 theta ** (torch.arange(0, dim, 2, dtype=torch.float) / dim)
276 )
277 self._seq_len_cached = 0
278 self._freqs_cached = None
279
280 def update_freqs_cache(self, seqlen: int) -> None:
281 if seqlen > self._seq_len_cached:
282 seqlen *= 2
283 self._seq_len_cached = seqlen
284 seq = torch.arange(
285 seqlen, device=self.inv_freq.device, dtype=self.inv_freq.dtype
286 )
287 freqs = torch.outer(seq, self.inv_freq)
288 self._freqs_cached = freqs
289
290 def forward(self, seqlen: int) -> torch.Tensor:
291 self.update_freqs_cache(seqlen)
292 return (
293 self._freqs_cached[:seqlen]
294 if self._freqs_cached is not None
295 else self._freqs_cached
296 )
297
298
299 class OpenPanguVisionPatchEmbed(nn.Module):
300 def __init__(
301 self,
302 patch_size: int = 14,
303 temporal_patch_size: int = 2,
304 in_channels: int = 3,
305 hidden_size: int = 1152,
306 ) -> None:
307 super().__init__()
308 self.patch_size = patch_size
309 self.temporal_patch_size = temporal_patch_size
310 self.hidden_size = hidden_size
311 self.input_size = (
312 self.patch_size * self.patch_size * in_channels * self.temporal_patch_size
313 )
314
315 kernel_size = (temporal_patch_size, patch_size, patch_size)
316 self.proj = nn.Conv3d(
317 in_channels,
318 hidden_size,
319 kernel_size=kernel_size,
320 stride=kernel_size,
321 bias=False,
322 )
323
324 def forward(self, x: torch.Tensor) -> torch.Tensor:
325 if x.shape[-1] != self.input_size:
326 x = torch.cat(
327 [
328 x.reshape(-1, self.patch_size * self.patch_size),
329 x.reshape(-1, self.patch_size * self.patch_size),
330 ],
331 dim=-1,
332 ).reshape(-1, self.input_size)
333 x = x.matmul(self.proj.weight.data.view(self.hidden_size, -1).transpose(0, 1))
334 return x
335
336
337 class OpenPanguVisionPatchMerger(nn.Module):
338 def __init__(
339 self,
340 d_model: int,
341 context_dim: int,
342 norm_layer: Callable[[int], nn.Module] | None = None,
343 spatial_merge_size: int = 2,
344 quant_config: QuantizationConfig | None = None,
345 prefix: str = "",
346 ) -> None:
347 super().__init__()
348 if norm_layer is None:
349 norm_layer = partial(nn.LayerNorm, eps=1e-6)
350 self.hidden_size = context_dim * (spatial_merge_size**2)
351 self.ln_q = norm_layer(context_dim)
352 self.mlp = nn.Sequential(
353 ColumnParallelLinear(
354 self.hidden_size,
355 self.hidden_size,
356 bias=True,
357 quant_config=quant_config,
358 prefix=f"{prefix}.mlp.0",
359 return_bias=False,
360 ),
361 nn.GELU(),
362 RowParallelLinear(
363 self.hidden_size,
364 d_model,
365 bias=True,
366 quant_config=quant_config,
367 prefix=f"{prefix}.mlp.2",
368 return_bias=False,
369 ),
370 )
371
372 def forward(self, x: torch.Tensor) -> torch.Tensor:
373 return self.mlp(self.ln_q(x).view(-1, self.hidden_size))
374
375
376 class OpenPanguVisionTransformer(nn.Module):
377 def __init__(
378 self,
379 vision_config,
380 out_hidden_size,
381 hidden_size,
382 norm_eps: float = 1e-6,
383 quant_config: QuantizationConfig | None = None,
384 prefix: str = "",
385 interleaved=False,
386 ) -> None:
387 super().__init__()
388 self.hidden_size = vision_config.hidden_size
389 self.num_heads = vision_config.num_heads
390 self.window_size = vision_config.window_size
391 self.patch_size = vision_config.patch_size
392 self.spatial_merge_size = vision_config.spatial_merge_size
393 self.fullatt_block_indexes = vision_config.fullatt_block_indexes
394 self.spatial_merge_unit = self.spatial_merge_size**2
395
396 norm_layer = partial(RMSNorm, eps=norm_eps)
397 self.interleaved = interleaved
398 self.out_hidden_size = vision_config.out_hidden_size
399 self.hidden_act = vision_config.hidden_act
400
401 head_dim = self.hidden_size // self.num_heads
402 self.attn_backend = get_vit_attn_backend(
403 head_size=head_dim,
404 dtype=torch.get_default_dtype(),
405 )
406
407 if self.attn_backend not in {
408 AttentionBackendEnum.FLASH_ATTN,
409 }:
410 raise RuntimeError(
411 f"Pangu-VL does not support {self.attn_backend} backend now."
412 )
413 self.rotary_pos_emb = OpenPanguVisionRotaryEmbedding(head_dim // 2)
414 self.patch_embed = OpenPanguVisionPatchEmbed(
415 patch_size=vision_config.patch_size,
416 temporal_patch_size=vision_config.temporal_patch_size,
417 in_channels=vision_config.in_channels,
418 hidden_size=self.hidden_size,
419 )
420 self.blocks = nn.ModuleList(
421 [
422 OpenPanguVisionBlock(
423 dim=self.hidden_size,
424 num_heads=self.num_heads,
425 mlp_hidden_dim=vision_config.intermediate_size,
426 act_fn=_ACTIVATION_REGISTRY[vision_config.hidden_act],
427 vision_config=vision_config,
428 norm_layer=norm_layer,
429 quant_config=quant_config,
430 prefix=f"{prefix}.blocks.{layer_idx}",
431 )
432 for layer_idx in range(vision_config.depth)
433 ]
434 )
435 self.tp_size = parallel_state.get_tensor_model_parallel_world_size()
436 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
437 self.hidden_size_per_attention_head = dist_utils.divide(
438 self.hidden_size, self.num_heads
439 )
440
441 self.select_layer = getattr(
442 vision_config, "mm_unit_vision_select_layer", [-1, -3]
443 )
444 self.select_index = [vision_config.depth + i for i in self.select_layer]
445 self.select_index = self.select_index[::-1]
446 self.select_layer = [-1 * (i + 1) for i in range(len(self.select_index))]
447
448 self.take_indices = self.select_index
449
450 self.final_layernorm = RMSNorm(self.hidden_size, eps=norm_eps)
451 self.merger = nn.ModuleList(
452 [
453 OpenPanguVisionPatchMerger(
454 d_model=vision_config.out_hidden_size,
455 context_dim=self.hidden_size,
456 norm_layer=norm_layer,
457 spatial_merge_size=self.spatial_merge_size,
458 quant_config=quant_config,
459 prefix=f"{prefix}.merger.{i}",
460 )
461 for i in range(len(self.select_layer))
462 ]
463 )
464 self.vision_projection = ProjectionSingle(out_hidden_size, hidden_size)
465
466 @property
467 def dtype(self) -> torch.dtype:
468 return self.patch_embed.proj.weight.dtype
469
470 @property
471 def device(self) -> torch.device:
472 return self.patch_embed.proj.weight.device
473
474 def cal_cos_sin(self, rotary_pos_emb):
475 cos = rotary_pos_emb.cos()
476 sin = rotary_pos_emb.sin()
477 return cos, sin
478
479 def rot_pos_emb(self, grid_thw: torch.Tensor) -> torch.Tensor:
480 # see https://github.com/huggingface/transformers/blob/main/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py for details. #L209 # noqa: E501
481 pos_ids = []
482 for t, h, w in grid_thw:
483 hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
484 wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
485 hpos_ids = (
486 hpos_ids.reshape(
487 h // self.spatial_merge_size,
488 self.spatial_merge_size,
489 w // self.spatial_merge_size,
490 self.spatial_merge_size,
491 )
492 .permute(0, 2, 1, 3)
493 .flatten()
494 )
495 wpos_ids = (
496 wpos_ids.reshape(
497 h // self.spatial_merge_size,
498 self.spatial_merge_size,
499 w // self.spatial_merge_size,
500 self.spatial_merge_size,
501 )
502 .permute(0, 2, 1, 3)
503 .flatten()
504 )
505 pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
506 pos_ids = torch.cat(pos_ids, dim=0)
507 max_grid_size = grid_thw[:, 1:].max()
508 rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
509 rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
510 return rotary_pos_emb
511
512 def get_window_index(self, grid_thw):
513 # see https://github.com/huggingface/transformers/blob/main/src/transformers/models/qwen2_5_vl/modular_qwen2_5_vl.py for details. #L238 # noqa: E501
514 window_index: list = []
515 cu_window_seqlens: list = [0]
516 window_index_id = 0
517 vit_merger_window_size = (
518 self.window_size // self.spatial_merge_size // self.patch_size
519 )
520
521 for grid_t, grid_h, grid_w in grid_thw:
522 llm_grid_h = grid_h // self.spatial_merge_size
523 llm_grid_w = grid_w // self.spatial_merge_size
524 index = torch.arange(grid_t * llm_grid_h * llm_grid_w).reshape(
525 grid_t, llm_grid_h, llm_grid_w
526 )
527 pad_h = vit_merger_window_size - llm_grid_h % vit_merger_window_size
528 pad_w = vit_merger_window_size - llm_grid_w % vit_merger_window_size
529 num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
530 num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
531 index_padded = F.pad(index, (0, pad_w, 0, pad_h), "constant", -100)
532 index_padded = index_padded.reshape(
533 grid_t,
534 num_windows_h,
535 vit_merger_window_size,
536 num_windows_w,
537 vit_merger_window_size,
538 )
539 index_padded = index_padded.permute(0, 1, 3, 2, 4).reshape(
540 grid_t,
541 num_windows_h * num_windows_w,
542 vit_merger_window_size,
543 vit_merger_window_size,
544 )
545 seqlens = (index_padded != -100).sum([2, 3]).reshape(-1)
546 index_padded = index_padded.reshape(-1)
547 index_new = index_padded[index_padded != -100]
548 window_index.append(index_new + window_index_id)
549 cu_seqlens_tmp = (
550 seqlens.cumsum(0) * self.spatial_merge_unit + cu_window_seqlens[-1]
551 )
552 cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
553 window_index_id += (grid_t * llm_grid_h * llm_grid_w).item()
554 window_index = torch.cat(window_index, dim=0)
555 return window_index, cu_window_seqlens
556
557 def forward(
558 self,
559 x: torch.Tensor,
560 grid_thw: torch.Tensor,
561 ) -> torch.Tensor:
562 # compute cu_seqlens
563 cu_seqlens = (
564 torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0])
565 .to(torch.int32)
566 .to(x.device)
567 )
568 cu_seqlens = torch.cumsum(cu_seqlens, dim=0, dtype=torch.int32)
569 cu_seqlens = F.pad(cu_seqlens, (1, 0), "constant", 0)
570
571 x = self.patch_embed(x)
572
573 rotary_pos_emb = self.rot_pos_emb(grid_thw)
574
575 window_index, cu_window_seqlens = self.get_window_index(grid_thw)
576 cu_window_seqlens = torch.tensor(
577 cu_window_seqlens,
578 device=x.device,
579 dtype=grid_thw.dtype if torch.jit.is_tracing() else torch.int32,
580 )
581 cu_window_seqlens = torch.unique_consecutive(cu_window_seqlens)
582 seq_len, _ = x.size()
583 x = x.reshape(seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
584 x = x[window_index, :, :]
585 x = x.reshape(seq_len, -1)
586 rotary_pos_emb = rotary_pos_emb.reshape(
587 seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1
588 )
589 rotary_pos_emb = rotary_pos_emb[window_index, :, :]
590 rotary_pos_emb = rotary_pos_emb.reshape(seq_len, -1)
591
592 cos, sin = self.cal_cos_sin(rotary_pos_emb.to(x.dtype))
593
594 intermediates = []
595 for layer_num, blk in enumerate(self.blocks):
596 if layer_num in self.fullatt_block_indexes:
597 cu_seqlens_now = cu_seqlens
598 else:
599 cu_seqlens_now = cu_window_seqlens
600 x = blk(x, cu_seqlens=cu_seqlens_now, cos=cos, sin=sin)
601 if layer_num in self.take_indices:
602 ln_hs = self.final_layernorm(x)
603 intermediates.append(ln_hs)
604
605 image_embeddings_list = []
606 for idx, sl in enumerate(self.select_layer):
607 image_embeddings_list.append(self.merger[idx](intermediates[sl]))
608 x = sum(image_embeddings_list)
609
610 reverse_indices = torch.argsort(window_index)
611 x = x[reverse_indices, :]
612 x = self.vision_projection(x)
613 return x
614
615 def load_weights(self, weights) -> set[str]:
616 def _padding_weight(name: str, w: torch.Tensor) -> torch.Tensor:
617 if "gate_proj" in name or "up_proj" in name:
618 dim, size = 0, w.size(0)
619 elif "down_proj" in name:
620 dim, size = 1, w.size(-1)
621 else:
622 return w
623 pad_len = -size % self.tp_size
624 if pad_len == 0:
625 return w
626 pad = [0] * (w.ndim * 2)
627 pad[-(dim + 1) * 2 + 1] = pad_len
628 return F.pad(w, pad, mode="constant", value=0)
629
630 stacked_params_mapping: list[tuple[str, str, str | int]] = [
631 ("attn.qkv.", "attn.q.", "q"),
632 ("attn.qkv.", "attn.k.", "k"),
633 ("attn.qkv.", "attn.v.", "v"),
634 ]
635 if self.hidden_act == "silu":
636 stacked_params_mapping.extend(
637 [
638 ("gate_up_proj", "gate_proj", 0),
639 ("gate_up_proj", "up_proj", 1),
640 ]
641 )
642 params_dict = dict(self.named_parameters(remove_duplicate=False))
643 loaded_params: set[str] = set()
644
645 for name, loaded_weight in weights:
646 if self.hidden_act == "silu":
647 loaded_weight = _padding_weight(name, loaded_weight)
648 for param_name, weight_name, shard_id in stacked_params_mapping:
649 if weight_name not in name:
650 continue
651 name = name.replace(weight_name, param_name)
652
653 param = params_dict[name]
654 weight_loader = param.weight_loader
655 weight_loader(param, loaded_weight, shard_id)
656 break
657 else:
658 param = params_dict[name]
659 weight_loader = getattr(param, "weight_loader", default_weight_loader)
660 weight_loader(param, loaded_weight)
661 loaded_params.add(name)
662 return loaded_params
663
664
665 class ProjectionSingle(nn.Module):
666 def __init__(self, i_hidden_size: int, t_hidden_size: int):
667 super().__init__()
668 self.act = F.silu
669 self.fc1 = nn.Linear(i_hidden_size, t_hidden_size, bias=True)
670
671 def forward(self, hidden_states):
672 x = self.act(hidden_states)
673 return self.fc1(x)
674
675
676 class OpenPanguVLProcessingInfo(Qwen2_5_VLProcessingInfo):
677 def get_hf_config(self):
678 return self.ctx.model_config.hf_config
679
680 def get_hf_processor(self, **kwargs: object):
681 kwargs.pop("min_pixels", None)
682 kwargs.pop("max_pixels", None)
683 kwargs.pop("size", None)
684 fps = kwargs.pop("fps", None)
685 if fps is not None:
686 kwargs["fps"] = fps
687
688 return self.ctx.get_hf_processor(
689 **kwargs,
690 )
691
692
693 class OpenPanguVLImagePixelInputs(TensorSchema):
694 type: Literal["pixel_values"]
695
696 pixel_values: Annotated[
697 torch.Tensor,
698 TensorShape("np", "cps"),
699 ]
700 image_grid_thw: Annotated[
701 torch.Tensor,
702 TensorShape("ni", 3),
703 ]
704
705
706 class OpenPanguVLImageEmbeddingInputs(TensorSchema):
707 type: Literal["image_embeds"]
708
709 image_embeds: Annotated[
710 torch.Tensor,
711 TensorShape("nf", "hs"),
712 ]
713 image_grid_thw: Annotated[
714 torch.Tensor,
715 TensorShape("ni", 3),
716 ]
717
718
719 class OpenPanguVLVideoPixelInputs(TensorSchema):
720 type: Literal["pixel_values_videos"]
721
722 pixel_values_videos: Annotated[
723 torch.Tensor,
724 TensorShape("np", "ctps"),
725 ]
726 video_grid_thw: Annotated[
727 torch.Tensor,
728 TensorShape("nv", 3),
729 ]
730
731
732 class OpenPanguVLVideoEmbeddingInputs(TensorSchema):
733 type: Literal["video_embeds"]
734
735 video_embeds: Annotated[
736 torch.Tensor,
737 TensorShape("nf", "hs"),
738 ]
739 video_grid_thw: Annotated[
740 torch.Tensor,
741 TensorShape("nv", 3),
742 ]
743
744
745 OpenPanguVLImageInputs: TypeAlias = (
746 OpenPanguVLImagePixelInputs | OpenPanguVLImageEmbeddingInputs
747 )
748 OpenPanguVLVideoInputs: TypeAlias = (
749 OpenPanguVLVideoPixelInputs | OpenPanguVLVideoEmbeddingInputs
750 )
751
752
753 class OpenPanguVLMultiModalProcessor(Qwen2_5_VLMultiModalProcessor):
754 def _get_prompt_updates(
755 self,
756 mm_items: MultiModalDataItems,
757 hf_processor_mm_kwargs: Mapping[str, object],
758 out_mm_kwargs: MultiModalKwargsItems,
759 ) -> Sequence[PromptUpdate]:
760 hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
761 image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
762 tokenizer = self.info.get_tokenizer()
763 vocab = tokenizer.get_vocab()
764 image_token = hf_processor.image_token
765 video_token = hf_processor.video_token
766 vision_start_token = hf_processor.vision_start_token
767 vision_end_token = hf_processor.vision_end_token
768 image_token_id = vocab[image_token]
769 video_token_id = vocab[video_token]
770 vision_start_token_id = vocab[vision_start_token]
771 vision_end_token_id = vocab[vision_end_token]
772 placeholder = {
773 "image": image_token_id,
774 "video": video_token_id,
775 }
776
777 merge_length = image_processor.merge_size**2
778
779 def get_replacement_openpangu_vision(item_idx: int, modality: str):
780 out_item = out_mm_kwargs[modality][item_idx]
781 grid_thw = out_item[f"{modality}_grid_thw"].data
782 if not isinstance(grid_thw, torch.Tensor):
783 raise TypeError("Expected 'grid_thw' to be a Tensor")
784 if modality == "image":
785 image_token_id_total = [image_token_id] * (
786 int(grid_thw.prod()) // merge_length
787 )
788 return image_token_id_total
789 else:
790 # When modality is video
791 grid_t, grid_h, grid_w = grid_thw
792 video_seq_length_per_time = (grid_h * grid_w).item() // merge_length
793 video_token_id_per_time = (
794 [vision_start_token_id]
795 + [video_token_id] * video_seq_length_per_time
796 + [vision_end_token_id]
797 )
798 video_token_id_total = video_token_id_per_time * grid_t.item()
799 video_token_id_middle = video_token_id_total[1:-1]
800 return PromptUpdateDetails.select_token_id(
801 video_token_id_middle,
802 embed_token_id=video_token_id,
803 )
804
805 return [
806 PromptReplacement(
807 modality=modality,
808 target=[placeholder[modality]],
809 replacement=partial(
810 get_replacement_openpangu_vision, modality=modality
811 ),
812 )
813 for modality in ("image", "video")
814 ]
815
816
817 class OpenPanguVLDummyInputsBuilder(Qwen2_5_VLDummyInputsBuilder):
818 pass
819
820
821 @MULTIMODAL_REGISTRY.register_processor(
822 OpenPanguVLMultiModalProcessor,
823 info=OpenPanguVLProcessingInfo,
824 dummy_inputs=OpenPanguVLDummyInputsBuilder,
825 )
826 class OpenPanguVLForConditionalGeneration(
827 nn.Module, SupportsMultiModal, SupportsLoRA, SupportsPP, SupportsMRoPE
828 ):
829 hf_to_vllm_mapper = WeightsMapper(
830 orig_to_new_prefix={
831 "model.language_model.": "language_model.model.",
832 "model.visual.": "visual.",
833 "lm_head.": "language_model.lm_head.",
834 "model.": "language_model.model.",
835 }
836 )
837 packed_modules_mapping = {
838 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
839 "gate_up_proj": ["gate_proj", "up_proj"],
840 }
841
842 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
843 super().__init__()
844 config = vllm_config.model_config.hf_config
845 self.config = config
846 self.vllm_config = vllm_config
847 quant_config = vllm_config.quant_config
848
849 with self._mark_tower_model(vllm_config, {"image", "video"}):
850 self.visual = OpenPanguVisionTransformer(
851 vision_config=config.vision_config,
852 out_hidden_size=config.vision_config.out_hidden_size,
853 hidden_size=config.hidden_size,
854 norm_eps=getattr(config.vision_config, "rms_norm_eps", 1e-6),
855 quant_config=self._maybe_ignore_quant_config(quant_config),
856 prefix=maybe_prefix(prefix, "visual"),
857 )
858
859 with self._mark_language_model(vllm_config):
860 self.language_model = init_vllm_registered_model(
861 vllm_config=vllm_config,
862 prefix=maybe_prefix(prefix, "openpangu.language_model"),
863 architectures=["PanguEmbeddedForCausalLM"],
864 )
865
866 self.make_empty_intermediate_tensors = (
867 self.language_model.make_empty_intermediate_tensors
868 )
869 self._parse_preprocess_params(config.vision_config)
870
871 def _parse_preprocess_params(self, vision_config):
872 self.channel = vision_config.in_channels
873 self.patch_size = vision_config.patch_size
874 from vllm.multimodal import MULTIMODAL_REGISTRY
875
876 image_processor = (
877 MULTIMODAL_REGISTRY.create_processor(self.vllm_config.model_config)
878 .info.get_hf_processor()
879 .image_processor
880 )
881 self.do_rescale = image_processor.do_rescale
882 self.rescale_factor = image_processor.rescale_factor
883 self.do_normalize = image_processor.do_normalize
884 self.image_mean: tuple[float, ...] = tuple(image_processor.image_mean)
885 self.image_std: tuple[float, ...] = tuple(image_processor.image_std)
886
887 def _maybe_ignore_quant_config(
888 self, quant_config: QuantizationConfig | None
889 ) -> QuantizationConfig | None:
890 if isinstance(quant_config, AutoGPTQConfig):
891 return None
892 return quant_config
893
894 def _validate_and_reshape_mm_tensor(
895 self, mm_input: object, name: str
896 ) -> torch.Tensor:
897 if not isinstance(mm_input, (torch.Tensor, list)):
898 raise ValueError(f"Incorrect type of {name}. Got type: {type(mm_input)}")
899 if isinstance(mm_input, torch.Tensor):
900 if mm_input.ndim == 2:
901 return mm_input
902 if mm_input.ndim != 3:
903 raise ValueError(
904 f"{name} should be 2D or batched 3D tensor. "
905 f"Got ndim: {mm_input.ndim} "
906 f"(shape={mm_input.shape})"
907 )
908 return torch.concat(list(mm_input))
909 else:
910 return torch.concat(mm_input)
911
912 def _parse_and_validate_image_input(
913 self, **kwargs: object
914 ) -> OpenPanguVLImageInputs | None:
915 pixel_values = kwargs.pop("pixel_values", None)
916 image_embeds = kwargs.pop("image_embeds", None)
917 image_grid_thw = kwargs.pop("image_grid_thw", None)
918
919 if pixel_values is None and image_embeds is None:
920 return None
921
922 if pixel_values is not None:
923 pixel_values = self._validate_and_reshape_mm_tensor(
924 pixel_values, "image pixel values"
925 )
926 image_grid_thw = self._validate_and_reshape_mm_tensor(
927 image_grid_thw, "image grid_thw"
928 )
929
930 if not isinstance(pixel_values, (torch.Tensor, list)):
931 raise ValueError(
932 "Incorrect type of image pixel values. "
933 f"Got type: {type(pixel_values)}"
934 )
935
936 return OpenPanguVLImagePixelInputs(
937 type="pixel_values",
938 pixel_values=pixel_values,
939 image_grid_thw=image_grid_thw,
940 )
941
942 assert image_embeds is not None
943 image_embeds = self._validate_and_reshape_mm_tensor(
944 image_embeds, "image embeds"
945 )
946 image_grid_thw = self._validate_and_reshape_mm_tensor(
947 image_grid_thw, "image grid_thw"
948 )
949
950 if not isinstance(image_embeds, torch.Tensor):
951 raise ValueError(
952 f"Incorrect type of image embeddings. Got type: {type(image_embeds)}"
953 )
954 return OpenPanguVLImageEmbeddingInputs(
955 type="image_embeds",
956 image_embeds=image_embeds,
957 image_grid_thw=image_grid_thw,
958 )
959
960 def _parse_and_validate_video_input(
961 self, **kwargs: object
962 ) -> OpenPanguVLVideoInputs | None:
963 pixel_values_videos = kwargs.pop("pixel_values_videos", None)
964 video_embeds = kwargs.pop("video_embeds", None)
965 video_grid_thw = kwargs.pop("video_grid_thw", None)
966
967 if pixel_values_videos is None and video_embeds is None:
968 return None
969
970 if pixel_values_videos is not None:
971 pixel_values_videos = self._validate_and_reshape_mm_tensor(
972 pixel_values_videos, "video pixel values"
973 )
974 video_grid_thw = self._validate_and_reshape_mm_tensor(
975 video_grid_thw, "video grid_thw"
976 )
977
978 return OpenPanguVLVideoPixelInputs(
979 type="pixel_values_videos",
980 pixel_values_videos=pixel_values_videos,
981 video_grid_thw=video_grid_thw,
982 )
983
984 assert video_embeds is not None
985 video_embeds = self._validate_and_reshape_mm_tensor(
986 video_embeds, "video embeds"
987 )
988 video_grid_thw = self._validate_and_reshape_mm_tensor(
989 video_grid_thw, "video grid_thw"
990 )
991
992 if not isinstance(video_embeds, torch.Tensor):
993 raise ValueError(
994 f"Incorrect type of video embeddings. Got type: {type(video_embeds)}"
995 )
996 return OpenPanguVLVideoEmbeddingInputs(
997 type="video_embeds",
998 video_embeds=video_embeds,
999 video_grid_thw=video_grid_thw,
1000 )
1001
1002 def _parse_and_validate_multimodal_inputs(
1003 self, **kwargs: object
1004 ) -> dict[str, OpenPanguVLImageInputs | OpenPanguVLVideoInputs | None]:
1005 mm_input_by_modality: dict[
1006 str, OpenPanguVLImageInputs | OpenPanguVLVideoInputs | None
1007 ] = {}
1008 for input_key in kwargs:
1009 if (
1010 input_key in ("pixel_values", "image_embeds")
1011 and "image" not in mm_input_by_modality
1012 ):
1013 mm_input_by_modality["image"] = self._parse_and_validate_image_input(
1014 **kwargs
1015 )
1016 if (
1017 input_key in ("pixel_values_videos", "video_embeds")
1018 and "video" not in mm_input_by_modality
1019 ):
1020 mm_input_by_modality["video"] = self._parse_and_validate_video_input(
1021 **kwargs
1022 )
1023 return mm_input_by_modality
1024
1025 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None:
1026 mm_input_by_modality = self._parse_and_validate_multimodal_inputs(**kwargs)
1027 if not mm_input_by_modality:
1028 return None
1029
1030 multimodal_embeddings: tuple[torch.Tensor, ...] = ()
1031
1032 for modality in mm_input_by_modality:
1033 if modality == "image":
1034 image_input = mm_input_by_modality["image"]
1035 assert isinstance(image_input, OpenPanguVLImageInputs)
1036 vision_embeddings = self._process_image_input(image_input)
1037 multimodal_embeddings = (
1038 multimodal_embeddings
1039 if not vision_embeddings
1040 else (multimodal_embeddings + vision_embeddings)
1041 )
1042 if modality == "video":
1043 video_input = mm_input_by_modality["video"]
1044 assert isinstance(video_input, OpenPanguVLVideoInputs)
1045 video_embeddings = self._process_video_input(video_input)
1046 multimodal_embeddings = (
1047 multimodal_embeddings
1048 if not video_embeddings
1049 else (multimodal_embeddings + video_embeddings)
1050 )
1051 return multimodal_embeddings
1052
1053 def get_input_embeddings(
1054 self,
1055 input_ids: torch.Tensor,
1056 multimodal_embeddings: MultiModalEmbeddings | None = None,
1057 ) -> torch.Tensor:
1058 if multimodal_embeddings is None:
1059 return self.language_model.embed_input_ids(input_ids)
1060
1061 mm_token_ids = input_ids.new_tensor(
1062 [self.config.image_token_id, self.config.video_token_id]
1063 )
1064 is_multimodal = torch.isin(input_ids, mm_token_ids)
1065 return self.embed_input_ids(
1066 input_ids,
1067 multimodal_embeddings,
1068 is_multimodal=is_multimodal,
1069 )
1070
1071 def _process_image_input(
1072 self, image_input: OpenPanguVLImageInputs
1073 ) -> tuple[torch.Tensor, ...]:
1074 grid_thw = image_input["image_grid_thw"]
1075 if grid_thw.ndim != 2:
1076 raise ValueError(f"grid_thw.ndim must be 2, but it is {grid_thw.ndim}")
1077
1078 if image_input["type"] == "image_embeds":
1079 image_embeds = image_input["image_embeds"].type(self.visual.dtype)
1080 else:
1081 pixel_values = image_input["pixel_values"].type(self.visual.dtype)
1082 # rescale and normalize
1083 pixel_values = pixel_values.reshape(
1084 -1, self.channel, self.patch_size, self.patch_size
1085 )
1086 pixel_values = rescale_and_normalize(
1087 pixel_values,
1088 self.do_rescale,
1089 self.rescale_factor,
1090 self.do_normalize,
1091 self.image_mean,
1092 self.image_std,
1093 )
1094 pixel_values = pixel_values.reshape(
1095 -1, self.channel * self.patch_size * self.patch_size
1096 )
1097 image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1098
1099 # Split concatenated embeddings for each image item.
1100 merge_size = self.visual.spatial_merge_size
1101 sizes = grid_thw.prod(-1) // merge_size // merge_size
1102 return image_embeds.split(sizes.tolist())
1103
1104 def _process_video_input(
1105 self, video_input: OpenPanguVLVideoInputs
1106 ) -> tuple[torch.Tensor, ...]:
1107 grid_thw = video_input["video_grid_thw"]
1108 if grid_thw.ndim != 2:
1109 raise ValueError(f"grid_thw.ndim must be 2, but it is {grid_thw.ndim}")
1110
1111 if video_input["type"] == "video_embeds":
1112 video_embeds = video_input["video_embeds"].type(self.visual.dtype)
1113 else:
1114 pixel_values_videos = video_input["pixel_values_videos"].type(
1115 self.visual.dtype
1116 )
1117 video_embeds = self.visual(pixel_values_videos, grid_thw=grid_thw)
1118
1119 # Split concatenated embeddings for each video item.
1120 merge_size = self.visual.spatial_merge_size
1121 sizes = grid_thw.prod(-1) // merge_size // merge_size
1122
1123 return video_embeds.split(sizes.tolist())
1124
1125 def forward(
1126 self,
1127 input_ids: torch.Tensor,
1128 positions: torch.Tensor,
1129 intermediate_tensors: IntermediateTensors | None = None,
1130 inputs_embeds: torch.Tensor | None = None,
1131 **kwargs: object,
1132 ) -> torch.Tensor | IntermediateTensors:
1133 if intermediate_tensors is not None:
1134 inputs_embeds = None
1135
1136 hidden_states = self.language_model.model(
1137 input_ids=input_ids,
1138 positions=positions,
1139 intermediate_tensors=intermediate_tensors,
1140 inputs_embeds=inputs_embeds,
1141 )
1142 return hidden_states
1143
1144 def compute_logits(
1145 self,
1146 hidden_states: torch.Tensor,
1147 sampling_metadata=None,
1148 ) -> torch.Tensor | None:
1149 return self.language_model.compute_logits(hidden_states)
1150
1151 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1152 loader = AutoWeightsLoader(self)
1153 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1154
1155 def get_mm_mapping(self) -> MultiModelKeys:
1156 """Get the module prefix in multimodal models."""
1157 return MultiModelKeys.from_string_field(
1158 language_model="language_model",
1159 connector="visual.merger.",
1160 tower_model="visual.",
1161 )
1162
1163 @classmethod
1164 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
1165 if modality.startswith("image"):
1166 return "[unused18][unused19][unused20]"
1167 if modality.startswith("video"):
1168 return "[unused18][unused32][unused20]"
1169
1170 raise ValueError("Only image or video modality is supported")
1171
1172 def iter_mm_grid_thw(
1173 self, mm_features: list[MultiModalFeatureSpec]
1174 ) -> Iterator[tuple[str, int, int, int, int]]:
1175 spatial_merge_size = self.config.vision_config.spatial_merge_size
1176 for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1177 offset = mm_feature.mm_position.offset
1178 modality = mm_feature.modality
1179 feature_data = mm_feature.data
1180 assert feature_data is not None
1181 if modality == "image":
1182 grid_thw_item = feature_data["image_grid_thw"]
1183 grid_thw = grid_thw_item.data
1184 assert isinstance(grid_thw, torch.Tensor)
1185 t, h, w = grid_thw.tolist()
1186 assert t == 1, f"Image must have 1 frame, got {t}"
1187 yield (
1188 modality,
1189 offset,
1190 1,
1191 h // spatial_merge_size,
1192 w // spatial_merge_size,
1193 )
1194 elif modality == "video":
1195 grid_thw_item = feature_data["video_grid_thw"]
1196 grid_thw = grid_thw_item.data
1197 assert isinstance(grid_thw, torch.Tensor)
1198 t, h, w = grid_thw.tolist()
1199 yield (
1200 modality,
1201 offset,
1202 t,
1203 h // spatial_merge_size,
1204 w // spatial_merge_size,
1205 )
1206 else:
1207 raise ValueError(f"Unsupported modality: {modality}")
1208
1209 def get_mrope_input_positions(
1210 self,
1211 input_tokens: list[int],
1212 mm_features: list[MultiModalFeatureSpec],
1213 ) -> tuple[torch.Tensor, int]:
1214 llm_pos_ids_list: list = []
1215 st = 0
1216
1217 for (
1218 modality,
1219 offset,
1220 llm_grid_t,
1221 llm_grid_h,
1222 llm_grid_w,
1223 ) in self.iter_mm_grid_thw(mm_features):
1224 text_len = offset - st
1225 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1226 llm_pos_ids_list.append(
1227 torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx
1228 )
1229 if modality == "video":
1230 eot_bot_pos = torch.full((3, 1), 0, dtype=torch.long)
1231 offset_pos = max(llm_grid_h, llm_grid_w)
1232 current_pos = text_len + st_idx
1233 grid_h = (
1234 torch.arange(llm_grid_h)
1235 .view(-1, 1)
1236 .expand(-1, llm_grid_w)
1237 .flatten()
1238 )
1239 grid_w = (
1240 torch.arange(llm_grid_w)
1241 .view(1, -1)
1242 .expand(llm_grid_h, -1)
1243 .flatten()
1244 )
1245 frame_pos = torch.stack(
1246 [
1247 torch.full_like(grid_h, 0, dtype=torch.long), # t
1248 grid_h, # h
1249 grid_w, # w
1250 ]
1251 )
1252 llm_pos_ids_list.append(frame_pos + current_pos)
1253 for _ in range(llm_grid_t - 1):
1254 current_pos = current_pos + offset_pos
1255 llm_pos_ids_list.append(eot_bot_pos + current_pos)
1256 llm_pos_ids_list.append(eot_bot_pos + current_pos + 1)
1257 llm_pos_ids_list.append(frame_pos + current_pos + 2)
1258 current_pos += 2
1259 st = (
1260 offset + llm_grid_t * llm_grid_h * llm_grid_w + (llm_grid_t - 1) * 2
1261 )
1262 else:
1263 t_index = (
1264 (
1265 torch.arange(llm_grid_t)
1266 .view(-1, 1)
1267 .expand(-1, llm_grid_h * llm_grid_w)
1268 )
1269 .long()
1270 .flatten()
1271 )
1272 h_index = (
1273 torch.arange(llm_grid_h)
1274 .view(1, -1, 1)
1275 .expand(llm_grid_t, -1, llm_grid_w)
1276 .flatten()
1277 )
1278 w_index = (
1279 torch.arange(llm_grid_w)
1280 .view(1, 1, -1)
1281 .expand(llm_grid_t, llm_grid_h, -1)
1282 .flatten()
1283 )
1284 llm_pos_ids_list.append(
1285 torch.stack([t_index, h_index, w_index]) + text_len + st_idx
1286 )
1287 st = offset + llm_grid_t * llm_grid_h * llm_grid_w
1288 if st < len(input_tokens):
1289 st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1290 text_len = len(input_tokens) - st
1291 llm_pos_ids_list.append(
1292 torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx
1293 )
1294 llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1)
1295 mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
1296 return llm_positions, mrope_position_delta
1297
1298
1299 def rescale(image, scale):
1300 return image * scale
1301
1302
1303 def normalize(image, mean, std):
1304 return v2.functional.normalize(image, mean, std)
1305
1306
1307 @lru_cache(maxsize=10)
1308 def _fuse_mean_std_and_rescale_factor(
1309 do_normalize: bool | None = None,
1310 image_mean: float | tuple[float, ...] | None = None,
1311 image_std: float | tuple[float, ...] | None = None,
1312 do_rescale: bool | None = None,
1313 rescale_factor: float | None = None,
1314 device: Optional["torch.device"] = None,
1315 ) -> tuple:
1316 if do_rescale and do_normalize:
1317 # Fused rescale and normalize
1318 assert rescale_factor is not None
1319 assert image_mean is not None
1320 assert image_std is not None
1321 image_mean = torch.tensor(image_mean, device=device) * (1.0 / rescale_factor)
1322 image_std = torch.tensor(image_std, device=device) * (1.0 / rescale_factor)
1323 do_rescale = False
1324 return image_mean, image_std, do_rescale
1325
1326
1327 def rescale_and_normalize(
1328 images: "torch.Tensor",
1329 do_rescale: bool,
1330 rescale_factor: float,
1331 do_normalize: bool,
1332 image_mean: float | tuple[float, ...],
1333 image_std: float | tuple[float, ...],
1334 dtype: torch.dtype = torch.bfloat16,
1335 ) -> "torch.Tensor":
1336 """Rescale and normalize images."""
1337 image_mean, image_std, do_rescale = _fuse_mean_std_and_rescale_factor(
1338 do_normalize=do_normalize,
1339 image_mean=image_mean,
1340 image_std=image_std,
1341 do_rescale=do_rescale,
1342 rescale_factor=rescale_factor,
1343 device=images.device,
1344 )
1345 # if/elif as we use fused rescale and normalize if both are set to True
1346 if do_normalize:
1347 images = normalize(images.to(dtype=torch.float32), image_mean, image_std)
1348 elif do_rescale:
1349 images = rescale(images, rescale_factor)
1350 images = images.to(dtype)
1351
1352 return images