main
py 375 lines 12.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # ruff: noqa: E501
4
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 """Inference-only EXAONE-4.5 model compatible with HuggingFace weights."""
17
18 from collections.abc import Callable, Iterable
19 from functools import partial
20
21 import einops
22 import torch
23 import torch.nn as nn
24 from transformers.models.exaone4_5 import (
25 Exaone4_5_Config,
26 Exaone4_5_Processor,
27 )
28 from transformers.models.exaone4_5.configuration_exaone4_5 import Exaone4_5_VisionConfig
29
30 from vllm.compilation.decorators import (
31 should_torch_compile_mm_encoder,
32 support_torch_compile,
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.logger import init_logger
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 QKVParallelLinear, RowParallelLinear
41 from vllm.model_executor.layers.quantization import QuantizationConfig
42 from vllm.model_executor.layers.rotary_embedding.common import (
43 ApplyRotaryEmb,
44 )
45 from vllm.model_executor.models.exaone4 import Exaone4GatedMLP as Exaone4_5_VisionMLP
46 from vllm.model_executor.models.qwen2_5_vl import (
47 Qwen2_5_VisionTransformer,
48 Qwen2_5_VLForConditionalGeneration,
49 Qwen2VLProcessingInfo,
50 )
51 from vllm.multimodal import MULTIMODAL_REGISTRY
52
53 from .qwen2_vl import Qwen2VLDummyInputsBuilder as Exaone4_5_DummyInputsBuilder
54 from .qwen2_vl import Qwen2VLMultiModalProcessor as Exaone4_5_MultiModalProcessor
55 from .utils import (
56 AutoWeightsLoader,
57 WeightsMapper,
58 init_vllm_registered_model,
59 maybe_prefix,
60 )
61
62 logger = init_logger(__name__)
63
64
65 # === Vision Encoder === #
66
67
68 class EXAONE4_5_VisionAttention(nn.Module):
69 def __init__(
70 self,
71 embed_dim: int,
72 num_heads: int,
73 num_kv_heads: int,
74 projection_size: int,
75 quant_config: QuantizationConfig | None = None,
76 prefix: str = "",
77 use_data_parallel: bool = False,
78 ) -> None:
79 super().__init__()
80 # Per attention head and per partition values.
81 self.tp_size = (
82 1
83 if use_data_parallel
84 else parallel_state.get_tensor_model_parallel_world_size()
85 )
86 self.tp_rank = parallel_state.get_tensor_model_parallel_rank()
87 self.hidden_size_per_attention_head = dist_utils.divide(
88 projection_size, num_heads
89 )
90 self.num_attention_heads_per_partition = dist_utils.divide(
91 num_heads, self.tp_size
92 )
93
94 self.total_num_heads = num_heads
95 self.total_num_kv_heads = num_kv_heads
96 self.num_heads = num_heads // self.tp_size
97 self.num_kv_heads = max(1, num_kv_heads // self.tp_size)
98
99 self.head_dim = embed_dim // num_heads
100 self.q_size = self.num_heads * self.head_dim
101 self.kv_size = self.num_kv_heads * self.head_dim
102
103 self.qkv = QKVParallelLinear(
104 hidden_size=embed_dim,
105 head_size=self.hidden_size_per_attention_head,
106 total_num_heads=self.total_num_heads,
107 total_num_kv_heads=self.total_num_kv_heads,
108 bias=True,
109 quant_config=quant_config,
110 prefix=f"{prefix}.qkv",
111 disable_tp=use_data_parallel,
112 )
113
114 self.proj = RowParallelLinear(
115 input_size=projection_size,
116 output_size=embed_dim,
117 bias=True,
118 quant_config=quant_config,
119 prefix=f"{prefix}.proj",
120 disable_tp=use_data_parallel,
121 )
122
123 self.attn = MMEncoderAttention(
124 num_heads=self.num_attention_heads_per_partition,
125 head_size=self.hidden_size_per_attention_head,
126 num_kv_heads=self.num_kv_heads,
127 scale=self.hidden_size_per_attention_head**-0.5,
128 prefix=f"{prefix}.attn",
129 )
130
131 self.apply_rotary_emb = ApplyRotaryEmb(enforce_enable=True)
132
133 def split_qkv(self, qkv: torch.Tensor) -> tuple[torch.Tensor, ...]:
134 # qkv: [s, b, (h + 2*hk) * d]
135 s, b, _ = qkv.shape
136 h = self.num_heads
137 hk = self.num_kv_heads
138 d = self.head_dim
139
140 qkv = qkv.view(s, b, h + 2 * hk, d)
141
142 q = qkv[:, :, :h, :]
143 k = qkv[:, :, h : h + hk, :]
144 v = qkv[:, :, h + hk :, :]
145
146 # [s, b, h, d] -> [b, s, h, d]
147 return (
148 q.permute(1, 0, 2, 3).contiguous(),
149 k.permute(1, 0, 2, 3).contiguous(),
150 v.permute(1, 0, 2, 3).contiguous(),
151 )
152
153 def forward(
154 self,
155 x: torch.Tensor,
156 cu_seqlens: torch.Tensor,
157 rotary_pos_emb_cos: torch.Tensor,
158 rotary_pos_emb_sin: torch.Tensor,
159 max_seqlen: int | None = None,
160 sequence_lengths: torch.Tensor
161 | None = None, # Only used for FlashInfer CuDNN backend
162 ) -> torch.Tensor:
163 # [s, b, c] --> [s, b, head * 3 * head_dim]
164 x, _ = self.qkv(x)
165 seq_len, batch_size, _ = x.shape
166
167 q, k, v = self.split_qkv(x)
168 q = self.apply_rotary_emb(
169 q,
170 rotary_pos_emb_cos,
171 rotary_pos_emb_sin,
172 )
173
174 k = self.apply_rotary_emb(
175 k,
176 rotary_pos_emb_cos,
177 rotary_pos_emb_sin,
178 )
179
180 context_layer = self.attn(
181 query=q,
182 key=k,
183 value=v,
184 cu_seqlens=cu_seqlens,
185 max_seqlen=max_seqlen,
186 sequence_lengths=sequence_lengths,
187 )
188
189 context_layer = einops.rearrange(
190 context_layer, "b s h d -> s b (h d)", b=batch_size
191 ).contiguous()
192
193 output, _ = self.proj(context_layer)
194 return output
195
196
197 @support_torch_compile(
198 dynamic_arg_dims={
199 "x": 0,
200 "cu_seqlens": 0,
201 "sequence_lengths": 0,
202 "rotary_pos_emb_cos": 0,
203 "rotary_pos_emb_sin": 0,
204 },
205 enable_if=should_torch_compile_mm_encoder,
206 is_encoder=True,
207 )
208 class Exaone4_5_VisionBlock(nn.Module):
209 def __init__(
210 self,
211 dim: int,
212 num_heads: int,
213 num_kv_heads: int,
214 mlp_hidden_dim: int,
215 hidden_act: str = "silu",
216 norm_layer: Callable[[int], nn.Module] | None = None,
217 quant_config: QuantizationConfig | None = None,
218 prefix: str = "",
219 use_data_parallel: bool = False,
220 ) -> None:
221 super().__init__()
222 if norm_layer is None:
223 norm_layer = partial(nn.LayerNorm, eps=1e-6)
224 self.norm1 = norm_layer(dim)
225 self.norm2 = norm_layer(dim)
226 self.attn = EXAONE4_5_VisionAttention(
227 embed_dim=dim,
228 num_heads=num_heads,
229 num_kv_heads=num_kv_heads,
230 projection_size=dim,
231 quant_config=quant_config,
232 prefix=f"{prefix}.attn",
233 use_data_parallel=use_data_parallel,
234 )
235 self.mlp = Exaone4_5_VisionMLP(
236 dim,
237 mlp_hidden_dim,
238 hidden_act=hidden_act,
239 bias=True,
240 quant_config=quant_config,
241 prefix=f"{prefix}.mlp",
242 use_data_parallel=use_data_parallel,
243 )
244
245 def forward(
246 self,
247 x: torch.Tensor,
248 cu_seqlens: torch.Tensor,
249 rotary_pos_emb_cos: torch.Tensor,
250 rotary_pos_emb_sin: torch.Tensor,
251 max_seqlen: int | None = None, # Only used for Flash Attention
252 seqlens: list[int] | None = None, # Only used for xFormers
253 # Only used for FlashInfer CuDNN backend
254 sequence_lengths: torch.Tensor | None = None,
255 ) -> torch.Tensor:
256 x_attn = self.attn(
257 self.norm1(x),
258 cu_seqlens=cu_seqlens,
259 rotary_pos_emb_cos=rotary_pos_emb_cos,
260 rotary_pos_emb_sin=rotary_pos_emb_sin,
261 max_seqlen=max_seqlen,
262 sequence_lengths=sequence_lengths,
263 )
264 x_fused_norm, residual = self.norm2(x, residual=x_attn)
265 x = residual + self.mlp(x_fused_norm)
266 return x
267
268
269 class EXAONE4_5_VisionTransformer(Qwen2_5_VisionTransformer):
270 def __init__(
271 self,
272 vision_config: Exaone4_5_VisionConfig,
273 norm_eps: float = 1e-6,
274 quant_config: QuantizationConfig | None = None,
275 prefix: str = "",
276 use_data_parallel: bool = False,
277 ) -> None:
278 super().__init__(
279 vision_config=vision_config,
280 norm_eps=norm_eps,
281 quant_config=quant_config,
282 prefix=prefix,
283 )
284 depth = vision_config.depth
285 self.num_kv_heads = vision_config.num_key_value_heads
286
287 norm_layer = partial(RMSNorm, eps=norm_eps)
288
289 self.blocks = nn.ModuleList(
290 [
291 Exaone4_5_VisionBlock(
292 dim=self.hidden_size,
293 num_heads=self.num_heads,
294 num_kv_heads=self.num_kv_heads,
295 mlp_hidden_dim=vision_config.intermediate_size,
296 hidden_act=vision_config.hidden_act,
297 norm_layer=norm_layer,
298 quant_config=quant_config,
299 prefix=f"{prefix}.blocks.{layer_idx}",
300 use_data_parallel=use_data_parallel,
301 )
302 for layer_idx in range(depth)
303 ]
304 )
305
306
307 class Exaone4_5_ProcessingInfo(Qwen2VLProcessingInfo):
308 def get_hf_config(self):
309 return self.ctx.get_hf_config(Exaone4_5_Config)
310
311 def get_hf_processor(self, **kwargs: object) -> Exaone4_5_Processor:
312 return self.ctx.get_hf_processor(
313 Exaone4_5_Processor,
314 **kwargs,
315 )
316
317
318 @MULTIMODAL_REGISTRY.register_processor(
319 Exaone4_5_MultiModalProcessor,
320 info=Exaone4_5_ProcessingInfo,
321 dummy_inputs=Exaone4_5_DummyInputsBuilder,
322 )
323 class Exaone4_5_ForConditionalGeneration(Qwen2_5_VLForConditionalGeneration):
324 hf_to_vllm_mapper = Qwen2_5_VLForConditionalGeneration.hf_to_vllm_mapper | (
325 WeightsMapper(orig_to_new_prefix={"mtp.": None})
326 )
327
328 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
329 nn.Module.__init__(self)
330
331 config: Exaone4_5_Config = vllm_config.model_config.hf_config
332 self.vllm_config = vllm_config
333 multimodal_config = vllm_config.model_config.multimodal_config
334 assert multimodal_config is not None
335
336 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
337 self.config = config
338 self.multimodal_config = multimodal_config
339 self.is_multimodal_pruning_enabled = (
340 multimodal_config.is_multimodal_pruning_enabled()
341 )
342
343 with self._mark_tower_model(vllm_config, {"image", "video"}):
344 self.visual = EXAONE4_5_VisionTransformer(
345 config.vision_config,
346 norm_eps=getattr(config, "rms_norm_eps", 1e-6),
347 quant_config=self.quant_config,
348 prefix=maybe_prefix(prefix, "visual"),
349 use_data_parallel=self.use_data_parallel,
350 )
351
352 with self._mark_language_model(vllm_config):
353 self.language_model = init_vllm_registered_model(
354 vllm_config=vllm_config,
355 prefix=maybe_prefix(prefix, "language_model"),
356 hf_config=config.get_text_config(),
357 architectures=["Exaone4ForCausalLM"],
358 )
359
360 self.make_empty_intermediate_tensors = (
361 self.language_model.make_empty_intermediate_tensors
362 )
363
364 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
365 loader = AutoWeightsLoader(self)
366 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
367
368 @classmethod
369 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
370 if modality.startswith("image"):
371 return "<vision><|image_pad|></vision>"
372 if modality.startswith("video"):
373 return "<vision><|video_pad|></vision>"
374
375 raise ValueError("Only image or video modality is supported")