@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ba45bedfd1
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 9, 07:30 UTC

[model] Add support for openPangu7B-VL (#32449)

Signed-off-by: hujiaxin <524446785@qq.com> Signed-off-by: Emilie1001 <79921183+Emilie1001@users.noreply.github.com> Co-authored-by: Emilie1001 <79921183+Emilie1001@users.noreply.github.com>

hujiaxin0 committed Jan 30, 2026 at 15:54 UTC ba45bedfd15ab01af2be5ae28af03888f3683063
9 files changed +1601 -1
docs/models/supported_models.md
+1
@@ -705,6 +705,7 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
705 | `Molmo2ForConditionalGeneration` | Molmo2 | T + I<sup>+</sup> / V | `allenai/Molmo2-4B`, `allenai/Molmo2-8B`, `allenai/Molmo2-O-7B` | ✅︎ | ✅︎ |
706 | `NVLM_D_Model` | NVLM-D 1.0 | T + I<sup>+</sup> | `nvidia/NVLM-D-72B`, etc. | | ✅︎ |
707 | `OpenCUAForConditionalGeneration` | OpenCUA-7B | T + I<sup>E+</sup> | `xlangai/OpenCUA-7B` | ✅︎ | ✅︎ |
708 +| `OpenPanguVLForConditionalGeneration` | openpangu-VL | T + I<sup>E+</sup> + V<sup>E+</sup> |`FreedomIntelligence/openPangu-VL-7B` | ✅︎ | ✅︎ |
709 | `Ovis` | Ovis2, Ovis1.6 | T + I<sup>+</sup> | `AIDC-AI/Ovis2-1B`, `AIDC-AI/Ovis1.6-Llama3.2-3B`, etc. | | ✅︎ |
710 | `Ovis2_5` | Ovis2.5 | T + I<sup>+</sup> + V | `AIDC-AI/Ovis2.5-9B`, etc. | | |
711 | `PaddleOCRVLForConditionalGeneration` | Paddle-OCR | T + I<sup>+</sup> | `PaddlePaddle/PaddleOCR-VL`, etc. | | |
examples/offline_inference/vision_language.py
+32
@@ -1394,6 +1394,37 @@ def run_nvlm_d(questions: list[str], modality: str) -> ModelRequestData:
1394 )
1395
1396
1397 +# OpenPangu
1398 +def run_openpangu_vl(questions: list[str], modality: str) -> ModelRequestData:
1399 + model_name = "FreedomIntelligence/openPangu-VL-7B"
1400 +
1401 + engine_args = EngineArgs(
1402 + model=model_name,
1403 + max_model_len=4096,
1404 + max_num_seqs=4,
1405 + trust_remote_code=True,
1406 + enforce_eager=True,
1407 + limit_mm_per_prompt={modality: 1},
1408 + )
1409 +
1410 + if modality == "image":
1411 + placeholder = "[unused19]"
1412 + elif modality == "video":
1413 + placeholder = "[unused32]"
1414 +
1415 + prompts = [
1416 + (
1417 + f"<s>[unused9]系统:[unused10][unused9]用户:[unused18]{placeholder}[unused20]{question}[unused10][unused9]助手:"
1418 + )
1419 + for question in questions
1420 + ]
1421 +
1422 + return ModelRequestData(
1423 + engine_args=engine_args,
1424 + prompts=prompts,
1425 + )
1426 +
1427 +
1428 # Ovis
1429 def run_ovis(questions: list[str], modality: str) -> ModelRequestData:
1430 assert modality == "image"
@@ -2051,6 +2082,7 @@ model_example_map = {
2082 "molmo2": run_molmo2,
2083 "nemotron_vl": run_nemotron_vl,
2084 "NVLM_D": run_nvlm_d,
2085 + "openpangu_vl": run_openpangu_vl,
2086 "ovis": run_ovis,
2087 "ovis2_5": run_ovis2_5,
2088 "paddleocr_vl": run_paddleocr_vl,
examples/offline_inference/vision_language_multi_image.py
+27
@@ -765,6 +765,32 @@ def load_nvlm_d(question: str, image_urls: list[str]) -> ModelRequestData:
765 )
766
767
768 +# OpenPangu
769 +def load_openpangu_vl(question: str, image_urls: list[str]) -> ModelRequestData:
770 + model_name = "FreedomIntelligence/openPangu-VL-7B"
771 +
772 + engine_args = EngineArgs(
773 + model=model_name,
774 + trust_remote_code=True,
775 + max_model_len=8192,
776 + max_num_seqs=2,
777 + enforce_eager=True,
778 + limit_mm_per_prompt={"image": len(image_urls)},
779 + )
780 +
781 + placeholders = "[unused18][unused19][unused20]" * len(image_urls)
782 + prompt = (
783 + f"<s>[unused9]系统:[unused10][unused9]用户:{question}{placeholders}"
784 + "[unused10][unused9]助手:"
785 + )
786 +
787 + return ModelRequestData(
788 + engine_args=engine_args,
789 + prompt=prompt,
790 + image_data=[fetch_image(url) for url in image_urls],
791 + )
792 +
793 +
794 # Ovis
795 def load_ovis(question: str, image_urls: list[str]) -> ModelRequestData:
796 model_name = "AIDC-AI/Ovis2-1B"
@@ -1388,6 +1414,7 @@ model_example_map = {
1414 "mistral3": load_mistral3,
1415 "molmo2": load_molmo2,
1416 "NVLM_D": load_nvlm_d,
1417 + "openpangu_vl": load_openpangu_vl,
1418 "ovis": load_ovis,
1419 "ovis2_5": load_ovis2_5,
1420 "paddleocr_vl": load_paddleocr_vl,
tests/models/registry.py
+6
@@ -873,6 +873,12 @@ _MULTIMODAL_EXAMPLE_MODELS = {
873 "OpenCUAForConditionalGeneration": _HfExamplesInfo(
874 "xlangai/OpenCUA-7B", trust_remote_code=True
875 ),
876 + "OpenPanguVLForConditionalGeneration": _HfExamplesInfo(
877 + "FreedomIntelligence/openPangu-VL-7B",
878 + trust_remote_code=True,
879 + max_model_len=4096,
880 + enforce_eager=True,
881 + ),
882 "Ovis": _HfExamplesInfo(
883 "AIDC-AI/Ovis2-1B",
884 trust_remote_code=True,
vllm/model_executor/layers/rotary_embedding/__init__.py
+16
@@ -15,6 +15,7 @@ from .linear_scaling_rope import LinearScalingRotaryEmbedding
15 from .llama3_rope import Llama3RotaryEmbedding
16 from .llama4_vision_rope import Llama4VisionRotaryEmbedding
17 from .mrope import MRotaryEmbedding
18 +from .mrope_interleaved import MRotaryEmbeddingInterleaved
19 from .ntk_scaling_rope import NTKScalingRotaryEmbedding
20 from .phi3_long_rope_scaled_rope import Phi3LongRoPEScaledRotaryEmbedding
21 from .xdrope import XDRotaryEmbedding
@@ -283,6 +284,21 @@ def get_rope(
284 long_factor,
285 **extra_kwargs,
286 )
287 + elif scaling_type == "openpangu":
288 + mrope_interleaved = rope_parameters.get("mrope_interleaved", False)
289 + if "mrope_section" in rope_parameters and mrope_interleaved:
290 + rotary_emb = MRotaryEmbeddingInterleaved(
291 + head_size,
292 + rotary_dim,
293 + max_position,
294 + base,
295 + is_neox_style,
296 + dtype,
297 + mrope_section=rope_parameters["mrope_section"],
298 + mrope_interleaved=mrope_interleaved,
299 + )
300 + else:
301 + raise ValueError("Pangu mrope lacks necessary parameters.")
302 else:
303 raise ValueError(f"Unknown RoPE scaling type {scaling_type}")
304 _ROPE_DICT[key] = rotary_emb
vllm/model_executor/layers/rotary_embedding/mrope_interleaved.py new
+185
@@ -0,0 +1,185 @@
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/layers/rotary_embedding/__init__.py
6 +# Copyright 2023 The vLLM team.
7 +#
8 +# This file is a part of the vllm-ascend project.
9 +#
10 +# Licensed under the Apache License, Version 2.0 (the "License");
11 +# you may not use this file except in compliance with the License.
12 +# You may obtain a copy of the License at
13 +#
14 +# http://www.apache.org/licenses/LICENSE-2.0
15 +#
16 +# Unless required by applicable law or agreed to in writing, software
17 +# distributed under the License is distributed on an "AS IS" BASIS,
18 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 +# See the License for the specific language governing permissions and
20 +# limitations under the License.
21 +
22 +import torch
23 +
24 +from .mrope import MRotaryEmbedding
25 +
26 +
27 +# MRotaryEmbedding with interleaved
28 +class MRotaryEmbeddingInterleaved(MRotaryEmbedding):
29 + """Rotary Embedding with Multimodal Sections and Interleaved Support."""
30 +
31 + def __init__(
32 + self,
33 + head_size: int,
34 + rotary_dim: int,
35 + max_position_embeddings: int,
36 + base: float,
37 + is_neox_style: bool,
38 + dtype: torch.dtype,
39 + mrope_section: list[int],
40 + mrope_interleaved: bool = True,
41 + ) -> None:
42 + # Enlarge max_position_embeddings for video inputs
43 + self.cache_max_position_num = max_position_embeddings
44 + super().__init__(
45 + head_size,
46 + rotary_dim,
47 + self.cache_max_position_num,
48 + base,
49 + is_neox_style,
50 + dtype,
51 + )
52 +
53 + self.mrope_section = mrope_section
54 + self.mrope_interleaved = mrope_interleaved
55 +
56 + if self.mrope_section is None:
57 + raise ValueError("mrope_section cannot be None.")
58 + if sum(self.mrope_section) != rotary_dim // 2:
59 + raise ValueError("Sum of mrope_section must equal rotary_dim // 2.")
60 + if not self.mrope_interleaved:
61 + raise ValueError(
62 + "mrope_interleaved must be True when mrope_section is provided."
63 + )
64 +
65 + # Generate interleaved indices
66 + if len(mrope_section) == 2:
67 + h_num, w_num = mrope_section[0], mrope_section[1]
68 + mrope_dim = self.get_mrope_interleaved_id_list(h_num, w_num, 0)
69 + elif len(mrope_section) == 3:
70 + t_num, h_num, w_num = mrope_section[0], mrope_section[1], mrope_section[2]
71 + mrope_dim = self.get_mrope_interleaved_id_list(
72 + t_num, h_num, w_num, force_last=True
73 + )
74 + else:
75 + raise AssertionError(
76 + "Cannot support the length of mrope section is not 2 or 3."
77 + )
78 +
79 + mrope_dim = mrope_dim * 2
80 + self.mrope_dim = mrope_dim
81 +
82 + self.layer_cache = None
83 +
84 + def _rebuild_pos_emb(
85 + self,
86 + positions: torch.Tensor,
87 + ) -> tuple[torch.Tensor, torch.Tensor]:
88 + """Interleave the rotary embedding"""
89 + cos_sin = self.cos_sin_cache[positions]
90 + mrope_section_3d = [1] * len(self.mrope_dim)
91 + mrope_dim = self.mrope_dim
92 + cos_sin = torch.cat(
93 + [
94 + m[mrope_dim[i]]
95 + for i, m in enumerate(cos_sin.split(mrope_section_3d, dim=-1))
96 + ],
97 + dim=-1,
98 + )
99 + return cos_sin, torch.arange(cos_sin.shape[0], device=positions.device)
100 +
101 + def forward(
102 + self,
103 + positions: torch.Tensor,
104 + query: torch.Tensor,
105 + key: torch.Tensor | None = None,
106 + ) -> tuple[torch.Tensor, torch.Tensor | None]:
107 + """Forward pass with interleaved rotary embedding."""
108 + cos_sin, positions = self._rebuild_pos_emb(positions)
109 + cos, sin = cos_sin.chunk(2, dim=-1)
110 +
111 + query_shape = query.shape
112 + positions = positions.flatten()
113 + num_tokens = positions.shape[0]
114 + query = query.view(num_tokens, -1, self.head_size)
115 + query_rot = query[..., : self.rotary_dim]
116 + query_pass = query[..., self.rotary_dim :]
117 + query_rot = self.apply_rotary_emb.forward_native(
118 + query_rot,
119 + cos,
120 + sin,
121 + )
122 + query = torch.cat((query_rot, query_pass), dim=-1).reshape(query_shape)
123 +
124 + # key may be None in some cases, e.g. cross-layer KV sharing
125 + if key is not None:
126 + key_shape = key.shape
127 + key = key.view(num_tokens, -1, self.head_size)
128 + key_rot = key[..., : self.rotary_dim]
129 + key_pass = key[..., self.rotary_dim :]
130 + key_rot = self.apply_rotary_emb.forward_native(
131 + key_rot,
132 + cos,
133 + sin,
134 + )
135 + key = torch.cat((key_rot, key_pass), dim=-1).reshape(key_shape)
136 + return query, key
137 +
138 + @staticmethod
139 + def get_mrope_interleaved_id_list(
140 + a: int, b: int, c: int, force_last: bool = False
141 + ) -> list[int]:
142 + """
143 + Generate an interleaved list of indices for multi-modal rotary embedding.
144 +
145 + Args:
146 + a: Number of indices for first modality
147 + b: Number of indices for second modality
148 + c: Number of indices for third modality
149 + force_last: Whether to force the last element to be from the first modality
150 +
151 + Returns:
152 + List of interleaved indices
153 + """
154 + if force_last:
155 + a -= 1
156 +
157 + counts = {0: a, 1: b, 2: c}
158 + placed = {k: 0 for k in counts}
159 + rem = counts.copy()
160 + seq: list[int] = []
161 + last = None
162 +
163 + total = a + b + c
164 + for _ in range(total):
165 + # Candidates: remaining > 0 and ≠ last
166 + cands = [k for k in rem if rem[k] > 0 and k != last]
167 + if not cands:
168 + # If only last remains, relax the condition
169 + cands = [k for k in rem if rem[k] > 0]
170 +
171 + # Select the rarest candidate
172 + try:
173 + best = min(cands, key=lambda k: (placed[k] / counts[k], k))
174 + except KeyError:
175 + best = 0
176 +
177 + seq.append(best)
178 + placed[best] += 1
179 + rem[best] -= 1
180 + last = best
181 +
182 + if force_last:
183 + seq.append(0)
184 +
185 + return seq
vllm/model_executor/models/openpangu.py
+7 -1
@@ -537,10 +537,16 @@ class OpenPanguEmbeddedAttention(nn.Module):
537 if is_gguf and config.model_type == "PanguEmbedded":
538 is_neox_style = False
539
540 + rope_parameters = config.rope_parameters or {}
541 + if rope_parameters is not None and rope_parameters.get(
542 + "mrope_interleaved", False
543 + ):
544 + rope_parameters["rope_type"] = "openpangu"
545 +
546 self.rotary_emb = get_rope(
547 self.head_dim,
548 max_position=self.max_position_embeddings,
543 - rope_parameters=config.rope_parameters,
549 + rope_parameters=rope_parameters,
550 is_neox_style=is_neox_style,
551 )
552
vllm/model_executor/models/openpangu_vl.py new
+1323
@@ -0,0 +1,1323 @@
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
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.gptq import GPTQConfig
48 +from vllm.model_executor.layers.quantization.gptq_marlin import GPTQMarlinConfig
49 +from vllm.model_executor.layers.rotary_embedding.common import ApplyRotaryEmb
50 +from vllm.model_executor.model_loader.weight_utils import default_weight_loader
51 +from vllm.model_executor.models.interfaces import (
52 + MultiModalEmbeddings,
53 + SupportsLoRA,
54 + SupportsMRoPE,
55 + SupportsMultiModal,
56 + SupportsPP,
57 +)
58 +from vllm.model_executor.models.module_mapping import MultiModelKeys
59 +from vllm.model_executor.models.qwen2_5_vl import (
60 + Qwen2_5_VLDummyInputsBuilder,
61 + Qwen2_5_VLMultiModalProcessor,
62 + Qwen2_5_VLProcessingInfo,
63 +)
64 +from vllm.model_executor.models.utils import (
65 + AutoWeightsLoader,
66 + WeightsMapper,
67 + init_vllm_registered_model,
68 + maybe_prefix,
69 +)
70 +from vllm.multimodal import MULTIMODAL_REGISTRY
71 +from vllm.multimodal.inputs import (
72 + MultiModalFeatureSpec,
73 + MultiModalKwargsItems,
74 +)
75 +from vllm.multimodal.parse import MultiModalDataItems
76 +from vllm.multimodal.processing import (
77 + PromptReplacement,
78 + PromptUpdate,
79 + PromptUpdateDetails,
80 +)
81 +from vllm.sequence import IntermediateTensors
82 +from vllm.utils.tensor_schema import TensorSchema, TensorShape
83 +from vllm.v1.attention.backends.registry import AttentionBackendEnum
84 +
85 +from .vision import get_vit_attn_backend
86 +
87 +logger = logging.get_logger(__name__)
88 +
89 +
90 +class OpenPanguVisionAttention(nn.Module):
91 + def __init__(
92 + self,
93 + embed_dim: int,
94 + num_heads: int,
95 + projection_size: int,
96 + quant_config: QuantizationConfig | None = None,
97 + prefix: str = "",
98 + ) -> None:
99 + super().__init__()
100 + self.hidden_size_per_attention_head = dist_utils.divide(
101 + projection_size, num_heads
102 + )
103 + self.tp_size = parallel_state.get_tensor_model_parallel_world_size()
104 + self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
105 + self.num_attention_heads_per_partition = dist_utils.divide(
106 + num_heads, self.tp_size
107 + )
108 +
109 + self.qkv = QKVParallelLinear(
110 + hidden_size=embed_dim,
111 + head_size=self.hidden_size_per_attention_head,
112 + total_num_heads=num_heads,
113 + total_num_kv_heads=num_heads,
114 + bias=True,
115 + quant_config=quant_config,
116 + prefix=f"{prefix}.qkv",
117 + )
118 + self.proj = RowParallelLinear(
119 + input_size=projection_size,
120 + output_size=embed_dim,
121 + quant_config=quant_config,
122 + prefix=f"{prefix}.proj",
123 + )
124 + self.attn = MMEncoderAttention(
125 + num_heads=self.num_attention_heads_per_partition,
126 + head_size=self.hidden_size_per_attention_head,
127 + scale=self.hidden_size_per_attention_head**-0.5,
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 = [
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(
681 + self,
682 + *,
683 + min_pixels: int | None = None,
684 + max_pixels: int | None = None,
685 + size: dict[str, int] | None = None,
686 + fps: float | list[float] | None = None,
687 + **kwargs: object,
688 + ):
689 + if fps is not None:
690 + kwargs["fps"] = fps
691 +
692 + return self.ctx.get_hf_processor(
693 + use_fast=kwargs.pop("use_fast", True),
694 + **kwargs,
695 + )
696 +
697 +
698 +class OpenPanguVLImagePixelInputs(TensorSchema):
699 + type: Literal["pixel_values"]
700 +
701 + pixel_values: Annotated[
702 + torch.Tensor,
703 + TensorShape("np", "cps"),
704 + ]
705 + image_grid_thw: Annotated[
706 + torch.Tensor,
707 + TensorShape("ni", 3),
708 + ]
709 +
710 +
711 +class OpenPanguVLImageEmbeddingInputs(TensorSchema):
712 + type: Literal["image_embeds"]
713 +
714 + image_embeds: Annotated[
715 + torch.Tensor,
716 + TensorShape("nf", "hs"),
717 + ]
718 + image_grid_thw: Annotated[
719 + torch.Tensor,
720 + TensorShape("ni", 3),
721 + ]
722 +
723 +
724 +class OpenPanguVLVideoPixelInputs(TensorSchema):
725 + type: Literal["pixel_values_videos"]
726 +
727 + pixel_values_videos: Annotated[
728 + torch.Tensor,
729 + TensorShape("np", "ctps"),
730 + ]
731 + video_grid_thw: Annotated[
732 + torch.Tensor,
733 + TensorShape("nv", 3),
734 + ]
735 +
736 +
737 +class OpenPanguVLVideoEmbeddingInputs(TensorSchema):
738 + type: Literal["video_embeds"]
739 +
740 + video_embeds: Annotated[
741 + torch.Tensor,
742 + TensorShape("nf", "hs"),
743 + ]
744 + video_grid_thw: Annotated[
745 + torch.Tensor,
746 + TensorShape("nv", 3),
747 + ]
748 +
749 +
750 +class OpenPanguVLMultiModalProcessor(Qwen2_5_VLMultiModalProcessor):
751 + def _get_prompt_updates(
752 + self,
753 + mm_items: MultiModalDataItems,
754 + hf_processor_mm_kwargs: Mapping[str, any],
755 + out_mm_kwargs: MultiModalKwargsItems,
756 + ) -> Sequence[PromptUpdate]:
757 + hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
758 + image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
759 + tokenizer = self.info.get_tokenizer()
760 + vocab = tokenizer.get_vocab()
761 + image_token = hf_processor.image_token
762 + video_token = hf_processor.video_token
763 + vision_start_token = hf_processor.vision_start_token
764 + vision_end_token = hf_processor.vision_end_token
765 + image_token_id = vocab[image_token]
766 + video_token_id = vocab[video_token]
767 + vision_start_token_id = vocab[vision_start_token]
768 + vision_end_token_id = vocab[vision_end_token]
769 + placeholder = {
770 + "image": image_token_id,
771 + "video": video_token_id,
772 + }
773 +
774 + merge_length = image_processor.merge_size**2
775 +
776 + def get_replacement_openpangu_vision(item_idx: int, modality: str):
777 + out_item = out_mm_kwargs[modality][item_idx]
778 + grid_thw = out_item[f"{modality}_grid_thw"].data
779 + if not isinstance(grid_thw, torch.Tensor):
780 + raise TypeError("Expected 'grid_thw' to be a Tensor")
781 + if modality == "image":
782 + image_token_id_total = [image_token_id] * (
783 + int(grid_thw.prod()) // merge_length
784 + )
785 + return image_token_id_total
786 + else:
787 + # When modality is video
788 + grid_t, grid_h, grid_w = grid_thw
789 + video_seq_length_per_time = (grid_h * grid_w).item() // merge_length
790 + video_token_id_per_time = (
791 + [vision_start_token_id]
792 + + [video_token_id] * video_seq_length_per_time
793 + + [vision_end_token_id]
794 + )
795 + video_token_id_total = video_token_id_per_time * grid_t.item()
796 + video_token_id_middle = video_token_id_total[1:-1]
797 + return PromptUpdateDetails.select_token_id(
798 + video_token_id_middle,
799 + embed_token_id=video_token_id,
800 + )
801 +
802 + return [
803 + PromptReplacement(
804 + modality=modality,
805 + target=[placeholder[modality]],
806 + replacement=partial(
807 + get_replacement_openpangu_vision, modality=modality
808 + ),
809 + )
810 + for modality in ("image", "video")
811 + ]
812 +
813 +
814 +class OpenPanguVLDummyInputsBuilder(Qwen2_5_VLDummyInputsBuilder):
815 + pass
816 +
817 +
818 +@MULTIMODAL_REGISTRY.register_processor(
819 + OpenPanguVLMultiModalProcessor,
820 + info=OpenPanguVLProcessingInfo,
821 + dummy_inputs=OpenPanguVLDummyInputsBuilder,
822 +)
823 +class OpenPanguVLForConditionalGeneration(
824 + nn.Module, SupportsMultiModal, SupportsLoRA, SupportsPP, SupportsMRoPE
825 +):
826 + hf_to_vllm_mapper = WeightsMapper(
827 + orig_to_new_prefix={
828 + "model.language_model.": "language_model.model.",
829 + "model.visual.": "visual.",
830 + "lm_head.": "language_model.lm_head.",
831 + "model.": "language_model.model.",
832 + }
833 + )
834 + packed_modules_mapping = {
835 + "qkv_proj": ["q_proj", "k_proj", "v_proj"],
836 + "gate_up_proj": ["gate_proj", "up_proj"],
837 + }
838 +
839 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
840 + super().__init__()
841 + config = vllm_config.model_config.hf_config
842 + self.config = config
843 + self.vllm_config = vllm_config
844 + quant_config = vllm_config.quant_config
845 + self.visual = OpenPanguVisionTransformer(
846 + vision_config=config.vision_config,
847 + out_hidden_size=config.vision_config.out_hidden_size,
848 + hidden_size=config.hidden_size,
849 + norm_eps=getattr(config.vision_config, "rms_norm_eps", 1e-6),
850 + quant_config=self._maybe_ignore_quant_config(quant_config),
851 + prefix=maybe_prefix(prefix, "visual"),
852 + )
853 +
854 + self.language_model = init_vllm_registered_model(
855 + vllm_config=vllm_config,
856 + prefix=maybe_prefix("openpangu", "language_model"),
857 + architectures=["PanguEmbeddedForCausalLM"],
858 + )
859 + self.make_empty_intermediate_tensors = (
860 + self.language_model.make_empty_intermediate_tensors
861 + )
862 + self._parse_preprocess_params(config.vision_config)
863 +
864 + def _parse_preprocess_params(self, vision_config):
865 + self.channel = vision_config.in_channels
866 + self.patch_size = vision_config.patch_size
867 + from vllm.multimodal import MULTIMODAL_REGISTRY
868 +
869 + image_processor = (
870 + MULTIMODAL_REGISTRY.create_processor(self.vllm_config.model_config)
871 + .info.get_hf_processor()
872 + .image_processor
873 + )
874 + self.do_rescale = image_processor.do_rescale
875 + self.rescale_factor = image_processor.rescale_factor
876 + self.do_normalize = image_processor.do_normalize
877 + self.image_mean = tuple(image_processor.image_mean)
878 + self.image_std = tuple(image_processor.image_std)
879 +
880 + def _maybe_ignore_quant_config(self, quant_config: QuantizationConfig):
881 + if isinstance(quant_config, (GPTQConfig, GPTQMarlinConfig)):
882 + return None
883 + return quant_config
884 +
885 + def _validate_and_reshape_mm_tensor(
886 + self, mm_input: object, name: str
887 + ) -> torch.Tensor:
888 + if not isinstance(mm_input, (torch.Tensor, list)):
889 + raise ValueError(f"Incorrect type of {name}. Got type: {type(mm_input)}")
890 + if isinstance(mm_input, torch.Tensor):
891 + if mm_input.ndim == 2:
892 + return mm_input
893 + if mm_input.ndim != 3:
894 + raise ValueError(
895 + f"{name} should be 2D or batched 3D tensor. "
896 + f"Got ndim: {mm_input.ndim} "
897 + f"(shape={mm_input.shape})"
898 + )
899 + return torch.concat(list(mm_input))
900 + else:
901 + return torch.concat(mm_input)
902 +
903 + def _parse_and_validate_image_input(self, **kwargs: object):
904 + pixel_values = kwargs.pop("pixel_values", None)
905 + image_embeds = kwargs.pop("image_embeds", None)
906 + image_grid_thw = kwargs.pop("image_grid_thw", None)
907 +
908 + if pixel_values is None and image_embeds is None:
909 + return None
910 +
911 + if pixel_values is not None:
912 + pixel_values = self._validate_and_reshape_mm_tensor(
913 + pixel_values, "image pixel values"
914 + )
915 + image_grid_thw = self._validate_and_reshape_mm_tensor(
916 + image_grid_thw, "image grid_thw"
917 + )
918 +
919 + if not isinstance(pixel_values, (torch.Tensor, list)):
920 + raise ValueError(
921 + "Incorrect type of image pixel values. "
922 + f"Got type: {type(pixel_values)}"
923 + )
924 +
925 + return OpenPanguVLImagePixelInputs(
926 + type="pixel_values",
927 + pixel_values=pixel_values,
928 + image_grid_thw=image_grid_thw,
929 + )
930 +
931 + if image_embeds is not None:
932 + image_embeds = self._validate_and_reshape_mm_tensor(
933 + image_embeds, "image embeds"
934 + )
935 + image_grid_thw = self._validate_and_reshape_mm_tensor(
936 + image_grid_thw, "image grid_thw"
937 + )
938 +
939 + if not isinstance(image_embeds, torch.Tensor):
940 + raise ValueError(
941 + "Incorrect type of image embeddings. "
942 + f"Got type: {type(image_embeds)}"
943 + )
944 + return OpenPanguVLImageEmbeddingInputs(
945 + type="image_embeds",
946 + image_embeds=image_embeds,
947 + image_grid_thw=image_grid_thw,
948 + )
949 +
950 + def _parse_and_validate_video_input(self, **kwargs: object):
951 + pixel_values_videos = kwargs.pop("pixel_values_videos", None)
952 + video_embeds = kwargs.pop("video_embeds", None)
953 + video_grid_thw = kwargs.pop("video_grid_thw", None)
954 +
955 + if pixel_values_videos is None and video_embeds is None:
956 + return None
957 +
958 + if pixel_values_videos is not None:
959 + pixel_values_videos = self._validate_and_reshape_mm_tensor(
960 + pixel_values_videos, "video pixel values"
961 + )
962 + video_grid_thw = self._validate_and_reshape_mm_tensor(
963 + video_grid_thw, "video grid_thw"
964 + )
965 +
966 + return OpenPanguVLVideoPixelInputs(
967 + type="pixel_values_videos",
968 + pixel_values_videos=pixel_values_videos,
969 + video_grid_thw=video_grid_thw,
970 + )
971 +
972 + if video_embeds is not None:
973 + video_embeds = self._validate_and_reshape_mm_tensor(
974 + video_embeds, "video embeds"
975 + )
976 + video_grid_thw = self._validate_and_reshape_mm_tensor(
977 + video_grid_thw, "video grid_thw"
978 + )
979 +
980 + if not isinstance(video_embeds, torch.Tensor):
981 + raise ValueError(
982 + "Incorrect type of video embeddings. "
983 + f"Got type: {type(video_embeds)}"
984 + )
985 + return OpenPanguVLVideoEmbeddingInputs(
986 + type="video_embeds",
987 + video_embeds=video_embeds,
988 + video_grid_thw=video_grid_thw,
989 + )
990 +
991 + def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
992 + mm_input_by_modality = {}
993 + for input_key in kwargs:
994 + if (
995 + input_key in ("pixel_values", "image_embeds")
996 + and "image" not in mm_input_by_modality
997 + ):
998 + mm_input_by_modality["image"] = self._parse_and_validate_image_input(
999 + **kwargs
1000 + )
1001 + if (
1002 + input_key in ("pixel_values_videos", "video_embeds")
1003 + and "video" not in mm_input_by_modality
1004 + ):
1005 + mm_input_by_modality["video"] = self._parse_and_validate_video_input(
1006 + **kwargs
1007 + )
1008 + return mm_input_by_modality
1009 +
1010 + def get_language_model(self) -> torch.nn.Module:
1011 + return self.language_model
1012 +
1013 + def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None:
1014 + mm_input_by_modality = self._parse_and_validate_multimodal_inputs(**kwargs)
1015 + if not mm_input_by_modality:
1016 + return None
1017 +
1018 + multimodal_embeddings: tuple[torch.Tensor, ...] = ()
1019 +
1020 + for modality in mm_input_by_modality:
1021 + multimodal_input = mm_input_by_modality[modality]
1022 + if modality == "image":
1023 + vision_embeddings = self._process_image_input(multimodal_input)
1024 + multimodal_embeddings = (
1025 + multimodal_embeddings
1026 + if not vision_embeddings
1027 + else (multimodal_embeddings + vision_embeddings)
1028 + )
1029 + if modality == "video":
1030 + video_embeddings = self._process_video_input(multimodal_input)
1031 + multimodal_embeddings = (
1032 + multimodal_embeddings
1033 + if not video_embeddings
1034 + else (multimodal_embeddings + video_embeddings)
1035 + )
1036 + return multimodal_embeddings
1037 +
1038 + def get_input_embeddings(
1039 + self,
1040 + input_ids: torch.Tensor,
1041 + multimodal_embeddings=None,
1042 + ) -> torch.Tensor:
1043 + inputs_embeds = self.language_model.embed_input_ids(input_ids)
1044 + if multimodal_embeddings is not None:
1045 + inputs_embeds = self.embed_input_ids(
1046 + input_ids,
1047 + inputs_embeds,
1048 + multimodal_embeddings,
1049 + [self.config.image_token_id, self.config.video_token_id],
1050 + )
1051 + return inputs_embeds
1052 +
1053 + def _process_image_input(self, image_input) -> tuple[torch.Tensor, ...]:
1054 + grid_thw = image_input["image_grid_thw"]
1055 + if grid_thw.ndim != 2:
1056 + raise ValueError(f"grid_thw.ndim must be 2, but it is {grid_thw.ndim}")
1057 +
1058 + if image_input["type"] == "image_embeds":
1059 + image_embeds = image_input["image_embeds"].type(self.visual.dtype)
1060 + else:
1061 + pixel_values = image_input["pixel_values"].type(self.visual.dtype)
1062 + # rescale and normalize
1063 + pixel_values = pixel_values.reshape(
1064 + -1, self.channel, self.patch_size, self.patch_size
1065 + )
1066 + pixel_values = rescale_and_normalize(
1067 + pixel_values,
1068 + self.do_rescale,
1069 + self.rescale_factor,
1070 + self.do_normalize,
1071 + self.image_mean,
1072 + self.image_std,
1073 + )
1074 + pixel_values = pixel_values.reshape(
1075 + -1, self.channel * self.patch_size * self.patch_size
1076 + )
1077 + image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
1078 +
1079 + # Split concatenated embeddings for each image item.
1080 + merge_size = self.visual.spatial_merge_size
1081 + sizes = grid_thw.prod(-1) // merge_size // merge_size
1082 + return image_embeds.split(sizes.tolist())
1083 +
1084 + def _process_video_input(self, video_input) -> torch.Tensor:
1085 + grid_thw = video_input["video_grid_thw"]
1086 + if grid_thw.ndim != 2:
1087 + raise ValueError(f"grid_thw.ndim must be 2, but it is {grid_thw.ndim}")
1088 +
1089 + if video_input["type"] == "video_embeds":
1090 + video_embeds = video_input["video_embeds"].type(self.visual.dtype)
1091 + else:
1092 + pixel_values_videos = video_input["pixel_values_videos"].type(
1093 + self.visual.dtype
1094 + )
1095 + video_embeds = self.visual(pixel_values_videos, grid_thw=grid_thw)
1096 +
1097 + # Split concatenated embeddings for each video item.
1098 + merge_size = self.visual.spatial_merge_size
1099 + sizes = grid_thw.prod(-1) // merge_size // merge_size
1100 +
1101 + return video_embeds.split(sizes.tolist())
1102 +
1103 + def forward(
1104 + self,
1105 + input_ids: torch.Tensor,
1106 + positions: torch.Tensor,
1107 + intermediate_tensors: IntermediateTensors | None = None,
1108 + inputs_embeds: torch.Tensor | None = None,
1109 + **kwargs: object,
1110 + ) -> torch.Tensor | IntermediateTensors:
1111 + if intermediate_tensors is not None:
1112 + inputs_embeds = None
1113 +
1114 + hidden_states = self.language_model.model(
1115 + input_ids=input_ids,
1116 + positions=positions,
1117 + intermediate_tensors=intermediate_tensors,
1118 + inputs_embeds=inputs_embeds,
1119 + )
1120 + return hidden_states
1121 +
1122 + def compute_logits(
1123 + self,
1124 + hidden_states: torch.Tensor,
1125 + sampling_metadata=None,
1126 + ) -> torch.Tensor | None:
1127 + return self.language_model.compute_logits(hidden_states)
1128 +
1129 + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1130 + loader = AutoWeightsLoader(self)
1131 + return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1132 +
1133 + def get_mm_mapping(self) -> MultiModelKeys:
1134 + """
1135 + Get the module prefix in multimodal models
1136 + """
1137 + return MultiModelKeys.from_string_field(
1138 + language_model="language_model",
1139 + connector="visual.merger.",
1140 + tower_model="visual.",
1141 + )
1142 +
1143 + @classmethod
1144 + def get_placeholder_str(cls, modality: str, i: int) -> str | None:
1145 + if modality.startswith("image"):
1146 + return "[unused18][unused19][unused20]"
1147 + if modality.startswith("video"):
1148 + return "[unused18][unused32][unused20]"
1149 +
1150 + raise ValueError("Only image or video modality is supported")
1151 +
1152 + def iter_mm_grid_thw(
1153 + self, mm_features: list[MultiModalFeatureSpec]
1154 + ) -> Iterator[tuple[str, int, int, int, int]]:
1155 + spatial_merge_size = self.config.vision_config.spatial_merge_size
1156 + for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset):
1157 + offset = mm_feature.mm_position.offset
1158 + modality = mm_feature.modality
1159 + if modality == "image":
1160 + t, h, w = mm_feature.data["image_grid_thw"].data.tolist()
1161 + assert t == 1, f"Image must have 1 frame, got {t}"
1162 + yield (
1163 + modality,
1164 + offset,
1165 + 1,
1166 + h // spatial_merge_size,
1167 + w // spatial_merge_size,
1168 + )
1169 + elif modality == "video":
1170 + t, h, w = mm_feature.data["video_grid_thw"].data.tolist()
1171 + yield (
1172 + modality,
1173 + offset,
1174 + t,
1175 + h // spatial_merge_size,
1176 + w // spatial_merge_size,
1177 + )
1178 + else:
1179 + raise ValueError(f"Unsupported modality: {modality}")
1180 +
1181 + def get_mrope_input_positions(
1182 + self,
1183 + input_tokens: list[int],
1184 + mm_features: list[MultiModalFeatureSpec],
1185 + ) -> tuple[torch.Tensor, int]:
1186 + llm_pos_ids_list: list = []
1187 + st = 0
1188 +
1189 + for (
1190 + modality,
1191 + offset,
1192 + llm_grid_t,
1193 + llm_grid_h,
1194 + llm_grid_w,
1195 + ) in self.iter_mm_grid_thw(mm_features):
1196 + text_len = offset - st
1197 + st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1198 + llm_pos_ids_list.append(
1199 + torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx
1200 + )
1201 + if modality == "video":
1202 + eot_bot_pos = torch.full((3, 1), 0, dtype=torch.long)
1203 + offset_pos = max(llm_grid_h, llm_grid_w)
1204 + current_pos = text_len + st_idx
1205 + grid_h = (
1206 + torch.arange(llm_grid_h)
1207 + .view(-1, 1)
1208 + .expand(-1, llm_grid_w)
1209 + .flatten()
1210 + )
1211 + grid_w = (
1212 + torch.arange(llm_grid_w)
1213 + .view(1, -1)
1214 + .expand(llm_grid_h, -1)
1215 + .flatten()
1216 + )
1217 + frame_pos = torch.stack(
1218 + [
1219 + torch.full_like(grid_h, 0, dtype=torch.long), # t
1220 + grid_h, # h
1221 + grid_w, # w
1222 + ]
1223 + )
1224 + llm_pos_ids_list.append(frame_pos + current_pos)
1225 + for _ in range(llm_grid_t - 1):
1226 + current_pos = current_pos + offset_pos
1227 + llm_pos_ids_list.append(eot_bot_pos + current_pos)
1228 + llm_pos_ids_list.append(eot_bot_pos + current_pos + 1)
1229 + llm_pos_ids_list.append(frame_pos + current_pos + 2)
1230 + current_pos += 2
1231 + st = (
1232 + offset + llm_grid_t * llm_grid_h * llm_grid_w + (llm_grid_t - 1) * 2
1233 + )
1234 + else:
1235 + t_index = (
1236 + (
1237 + torch.arange(llm_grid_t)
1238 + .view(-1, 1)
1239 + .expand(-1, llm_grid_h * llm_grid_w)
1240 + )
1241 + .long()
1242 + .flatten()
1243 + )
1244 + h_index = (
1245 + torch.arange(llm_grid_h)
1246 + .view(1, -1, 1)
1247 + .expand(llm_grid_t, -1, llm_grid_w)
1248 + .flatten()
1249 + )
1250 + w_index = (
1251 + torch.arange(llm_grid_w)
1252 + .view(1, 1, -1)
1253 + .expand(llm_grid_t, llm_grid_h, -1)
1254 + .flatten()
1255 + )
1256 + llm_pos_ids_list.append(
1257 + torch.stack([t_index, h_index, w_index]) + text_len + st_idx
1258 + )
1259 + st = offset + llm_grid_t * llm_grid_h * llm_grid_w
1260 + if st < len(input_tokens):
1261 + st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
1262 + text_len = len(input_tokens) - st
1263 + llm_pos_ids_list.append(
1264 + torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx
1265 + )
1266 + llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1)
1267 + mrope_position_delta = (llm_positions.max() + 1 - len(input_tokens)).item()
1268 + return llm_positions, mrope_position_delta
1269 +
1270 +
1271 +def rescale(image, scale):
1272 + return image * scale
1273 +
1274 +
1275 +def normalize(image, mean, std):
1276 + return v2.functional.normalize(image, mean, std)
1277 +
1278 +
1279 +@lru_cache(maxsize=10)
1280 +def _fuse_mean_std_and_rescale_factor(
1281 + do_normalize: bool | None = None,
1282 + image_mean: float | list[float] | None = None,
1283 + image_std: float | list[float] | None = None,
1284 + do_rescale: bool | None = None,
1285 + rescale_factor: float | None = None,
1286 + device: Optional["torch.device"] = None,
1287 +) -> tuple:
1288 + if do_rescale and do_normalize:
1289 + # Fused rescale and normalize
1290 + image_mean = torch.tensor(image_mean, device=device) * (1.0 / rescale_factor)
1291 + image_std = torch.tensor(image_std, device=device) * (1.0 / rescale_factor)
1292 + do_rescale = False
1293 + return image_mean, image_std, do_rescale
1294 +
1295 +
1296 +def rescale_and_normalize(
1297 + images: "torch.Tensor",
1298 + do_rescale: bool,
1299 + rescale_factor: float,
1300 + do_normalize: bool,
1301 + image_mean: float | list[float],
1302 + image_std: float | list[float],
1303 + dtype: torch.dtype = torch.bfloat16,
1304 +) -> "torch.Tensor":
1305 + """
1306 + Rescale and normalize images.
1307 + """
1308 + image_mean, image_std, do_rescale = _fuse_mean_std_and_rescale_factor(
1309 + do_normalize=do_normalize,
1310 + image_mean=image_mean,
1311 + image_std=image_std,
1312 + do_rescale=do_rescale,
1313 + rescale_factor=rescale_factor,
1314 + device=images.device,
1315 + )
1316 + # if/elif as we use fused rescale and normalize if both are set to True
1317 + if do_normalize:
1318 + images = normalize(images.to(dtype=torch.float32), image_mean, image_std)
1319 + elif do_rescale:
1320 + images = rescale(images, rescale_factor)
1321 + images = images.to(dtype)
1322 +
1323 + return images
vllm/model_executor/models/registry.py
+4
@@ -405,6 +405,10 @@ _MULTIMODAL_MODELS = {
405 "MolmoForCausalLM": ("molmo", "MolmoForCausalLM"),
406 "Molmo2ForConditionalGeneration": ("molmo2", "Molmo2ForConditionalGeneration"),
407 "NVLM_D": ("nvlm_d", "NVLM_D_Model"),
408 + "OpenPanguVLForConditionalGeneration": (
409 + "openpangu_vl",
410 + "OpenPanguVLForConditionalGeneration",
411 + ),
412 "Ovis": ("ovis", "Ovis"),
413 "Ovis2_5": ("ovis2_5", "Ovis2_5"),
414 "PaddleOCRVLForConditionalGeneration": (