main
py 450 lines 15.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # ruff: noqa: E501
4
5 # Adapted from
6 # https://github.com/lgai-exaone/transformers/blob/add-exaone4/src/transformers/models/exaone4/modeling_exaone4.py
7 # Copyright 2025 The LG CNS Gen AI Solution Delivery Team.
8 # Copyright 2025 The LG AI Research and HuggingFace Inc. team. All rights reserved.
9 #
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 """Inference-only Exaone model compatible with HuggingFace weights."""
23
24 from collections.abc import Iterable
25 from itertools import islice
26
27 import torch
28 from torch import nn
29 from transformers import Exaone4Config
30
31 from vllm.compilation.decorators import support_torch_compile
32 from vllm.config import CacheConfig, VllmConfig
33 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
34 from vllm.model_executor.layers.activation import SiluAndMul, SiluAndMulWithClamp
35 from vllm.model_executor.layers.attention import Attention
36 from vllm.model_executor.layers.layernorm import RMSNorm
37 from vllm.model_executor.layers.linear import (
38 MergedColumnParallelLinear,
39 QKVParallelLinear,
40 RowParallelLinear,
41 )
42 from vllm.model_executor.layers.logits_processor import LogitsProcessor
43 from vllm.model_executor.layers.quantization import QuantizationConfig
44 from vllm.model_executor.layers.rotary_embedding import get_rope
45 from vllm.model_executor.layers.vocab_parallel_embedding import (
46 ParallelLMHead,
47 VocabParallelEmbedding,
48 )
49 from vllm.sequence import IntermediateTensors
50 from vllm.transformers_utils.config import set_default_rope_theta
51
52 from .interfaces import SupportsLoRA, SupportsPP
53 from .utils import (
54 AutoWeightsLoader,
55 PPMissingLayer,
56 WeightsMapper,
57 extract_layer_index,
58 make_empty_intermediate_tensors_factory,
59 make_layers,
60 maybe_prefix,
61 )
62
63
64 class Exaone4GatedMLP(nn.Module):
65 def __init__(
66 self,
67 hidden_size: int,
68 intermediate_size: int,
69 hidden_act: str,
70 quant_config: QuantizationConfig | None = None,
71 reduce_results: bool = True,
72 bias: bool = False,
73 swiglu_limit: float | None = None,
74 prefix: str = "",
75 use_data_parallel: bool = False,
76 ) -> None:
77 super().__init__()
78 self.gate_up_proj = MergedColumnParallelLinear(
79 input_size=hidden_size,
80 output_sizes=[intermediate_size] * 2,
81 bias=bias,
82 quant_config=quant_config,
83 prefix=f"{prefix}.gate_up_proj",
84 disable_tp=use_data_parallel,
85 )
86 self.down_proj = RowParallelLinear(
87 input_size=intermediate_size,
88 output_size=hidden_size,
89 bias=bias,
90 quant_config=quant_config,
91 reduce_results=reduce_results,
92 prefix=f"{prefix}.down_proj",
93 disable_tp=use_data_parallel,
94 )
95 if hidden_act != "silu":
96 raise ValueError(
97 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
98 )
99 self.act_fn = (
100 SiluAndMul() if swiglu_limit is None else SiluAndMulWithClamp(swiglu_limit)
101 )
102
103 def forward(self, x):
104 gate_up, _ = self.gate_up_proj(x)
105 x = self.act_fn(gate_up)
106 x, _ = self.down_proj(x)
107 return x
108
109
110 class Exaone4Attention(nn.Module):
111 def __init__(
112 self,
113 config: Exaone4Config,
114 hidden_size: int,
115 num_heads: int,
116 num_kv_heads: int,
117 max_position_embeddings: int = 8192,
118 quant_config: QuantizationConfig | None = None,
119 bias: bool = False,
120 cache_config: CacheConfig | None = None,
121 prefix: str = "",
122 ) -> None:
123 super().__init__()
124 self.hidden_size = hidden_size
125 tp_size = get_tensor_model_parallel_world_size()
126 self.total_num_heads = num_heads
127 assert self.total_num_heads % tp_size == 0
128 self.num_heads = self.total_num_heads // tp_size
129 self.total_num_kv_heads = num_kv_heads
130 if self.total_num_kv_heads >= tp_size:
131 # Number of KV heads is greater than TP size, so we partition
132 # the KV heads across multiple tensor parallel GPUs.
133 assert self.total_num_kv_heads % tp_size == 0
134 else:
135 # Number of KV heads is less than TP size, so we replicate
136 # the KV heads across multiple tensor parallel GPUs.
137 assert tp_size % self.total_num_kv_heads == 0
138 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
139 # MistralConfig has an optional head_dim introduced by Mistral-Nemo
140 self.head_dim = getattr(config, "head_dim", None)
141 if self.head_dim is None:
142 self.head_dim = self.hidden_size // self.total_num_heads
143 self.q_size = self.num_heads * self.head_dim
144 self.kv_size = self.num_kv_heads * self.head_dim
145 self.scaling = self.head_dim**-0.5
146 self.max_position_embeddings = max_position_embeddings
147
148 self.qkv_proj = QKVParallelLinear(
149 hidden_size=hidden_size,
150 head_size=self.head_dim,
151 total_num_heads=self.total_num_heads,
152 total_num_kv_heads=self.total_num_kv_heads,
153 bias=bias,
154 quant_config=quant_config,
155 prefix=f"{prefix}.qkv_proj",
156 )
157
158 self.o_proj = RowParallelLinear(
159 input_size=self.total_num_heads * self.head_dim,
160 output_size=hidden_size,
161 bias=bias,
162 quant_config=quant_config,
163 prefix=f"{prefix}.o_proj",
164 )
165
166 self.q_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
167 self.k_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
168
169 is_neox_style = True
170
171 layer_idx = extract_layer_index(prefix)
172 is_sliding = config.layer_types[layer_idx] == "sliding_attention"
173 self.sliding_window = config.sliding_window if is_sliding else None
174
175 # apply rotary embeddings to every layer in full attention models
176 self.apply_rope_all_layers = "sliding_attention" not in config.layer_types
177
178 set_default_rope_theta(config, default_theta=1000000)
179 self.rotary_emb = get_rope(
180 self.head_dim,
181 max_position=max_position_embeddings,
182 rope_parameters=config.rope_parameters,
183 is_neox_style=is_neox_style,
184 )
185 self.attn = Attention(
186 self.num_heads,
187 self.head_dim,
188 self.scaling,
189 num_kv_heads=self.num_kv_heads,
190 cache_config=cache_config,
191 quant_config=quant_config,
192 per_layer_sliding_window=self.sliding_window,
193 prefix=f"{prefix}.attn",
194 )
195
196 def forward(
197 self,
198 positions: torch.Tensor,
199 hidden_states: torch.Tensor,
200 ) -> torch.Tensor:
201 qkv, _ = self.qkv_proj(hidden_states)
202 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
203
204 q = q.unflatten(-1, (self.num_heads, self.head_dim))
205 q = self.q_norm(q)
206 q = q.flatten(-2, -1)
207 k = k.unflatten(-1, (self.num_kv_heads, self.head_dim))
208 k = self.k_norm(k)
209 k = k.flatten(-2, -1)
210
211 if self.sliding_window or self.apply_rope_all_layers:
212 q, k = self.rotary_emb(positions, q, k)
213 attn_output = self.attn(q, k, v)
214 output, _ = self.o_proj(attn_output)
215 return output
216
217
218 class Exaone4DecoderLayer(nn.Module):
219 def __init__(
220 self,
221 config: Exaone4Config,
222 cache_config: CacheConfig | None = None,
223 quant_config: QuantizationConfig | None = None,
224 prefix: str = "",
225 ) -> None:
226 super().__init__()
227 self.hidden_size = config.hidden_size
228 max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
229 # Support abacusai/Smaug-72B-v0.1 with attention_bias
230 attention_bias = getattr(config, "attention_bias", False) or getattr(
231 config, "bias", False
232 )
233
234 self.self_attn = Exaone4Attention(
235 config=config,
236 hidden_size=self.hidden_size,
237 num_heads=config.num_attention_heads,
238 num_kv_heads=getattr(
239 config, "num_key_value_heads", config.num_attention_heads
240 ),
241 max_position_embeddings=max_position_embeddings,
242 quant_config=quant_config,
243 bias=attention_bias,
244 cache_config=cache_config,
245 prefix=f"{prefix}.self_attn",
246 )
247 self.mlp = Exaone4GatedMLP(
248 hidden_size=self.hidden_size,
249 intermediate_size=config.intermediate_size,
250 hidden_act=config.hidden_act,
251 quant_config=quant_config,
252 bias=getattr(config, "mlp_bias", False),
253 prefix=f"{prefix}.mlp",
254 )
255 self.post_attention_layernorm = RMSNorm(
256 config.hidden_size, eps=config.rms_norm_eps
257 )
258 self.post_feedforward_layernorm = RMSNorm(
259 config.hidden_size, eps=config.rms_norm_eps
260 )
261
262 def forward(
263 self,
264 positions: torch.Tensor,
265 hidden_states: torch.Tensor,
266 residual: torch.Tensor | None,
267 ) -> tuple[torch.Tensor, torch.Tensor]:
268 residual = hidden_states
269
270 # Self Attention
271 hidden_states = self.self_attn(
272 positions=positions,
273 hidden_states=hidden_states,
274 )
275
276 # Use post-LN
277 hidden_states = self.post_attention_layernorm(hidden_states)
278 hidden_states = residual + hidden_states
279
280 residual = hidden_states
281
282 # Fully Connected
283 hidden_states = self.mlp(hidden_states)
284
285 # Use post-LN
286 hidden_states = self.post_feedforward_layernorm(hidden_states)
287 hidden_states = residual + hidden_states
288
289 return hidden_states, residual
290
291
292 @support_torch_compile
293 class Exaone4Model(nn.Module):
294 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
295 super().__init__()
296
297 config = vllm_config.model_config.hf_config
298 cache_config = vllm_config.cache_config
299 quant_config = vllm_config.quant_config
300
301 self.config = config
302 self.quant_config = quant_config
303
304 self.vocab_size = config.vocab_size
305 if get_pp_group().is_first_rank or (
306 config.tie_word_embeddings and get_pp_group().is_last_rank
307 ):
308 self.embed_tokens = VocabParallelEmbedding(
309 self.vocab_size,
310 config.hidden_size,
311 quant_config=quant_config,
312 )
313 else:
314 self.embed_tokens = PPMissingLayer()
315 self.start_layer, self.end_layer, self.layers = make_layers(
316 config.num_hidden_layers,
317 lambda prefix: Exaone4DecoderLayer(
318 config=config,
319 cache_config=cache_config,
320 quant_config=quant_config,
321 prefix=prefix,
322 ),
323 prefix=f"{prefix}.layers",
324 )
325 if get_pp_group().is_last_rank:
326 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
327 else:
328 self.norm = PPMissingLayer()
329
330 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
331 ["hidden_states", "residual"], config.hidden_size
332 )
333
334 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
335 return self.embed_tokens(input_ids)
336
337 def forward(
338 self,
339 input_ids: torch.Tensor | None,
340 positions: torch.Tensor,
341 intermediate_tensors: IntermediateTensors | None,
342 inputs_embeds: torch.Tensor | None = None,
343 ) -> torch.Tensor | IntermediateTensors:
344 if get_pp_group().is_first_rank:
345 if inputs_embeds is not None:
346 hidden_states = inputs_embeds
347 else:
348 hidden_states = self.embed_input_ids(input_ids)
349 residual = None
350 else:
351 assert intermediate_tensors is not None
352 hidden_states = intermediate_tensors["hidden_states"]
353 residual = intermediate_tensors["residual"]
354
355 for layer in islice(self.layers, self.start_layer, self.end_layer):
356 hidden_states, residual = layer(
357 positions,
358 hidden_states,
359 residual,
360 )
361
362 if not get_pp_group().is_last_rank:
363 return IntermediateTensors(
364 {"hidden_states": hidden_states, "residual": residual}
365 )
366
367 hidden_states = self.norm(hidden_states)
368 return hidden_states
369
370
371 class Exaone4ForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
372 hf_to_vllm_mapper = WeightsMapper(
373 orig_to_new_stacked={
374 # weight_name: (param_name, shard_id)
375 ".q_proj": (".qkv_proj", "q"),
376 ".k_proj": (".qkv_proj", "k"),
377 ".v_proj": (".qkv_proj", "v"),
378 ".gate_proj": (".gate_up_proj", 0),
379 ".up_proj": (".gate_up_proj", 1),
380 }
381 )
382 packed_modules_mapping = {
383 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
384 "gate_up_proj": ["gate_proj", "up_proj"],
385 }
386
387 # LoRA specific attributes
388 embedding_modules = {
389 "embed_tokens": "input_embeddings",
390 "lm_head": "output_embeddings",
391 }
392
393 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
394 super().__init__()
395 config = vllm_config.model_config.hf_config
396 quant_config = vllm_config.quant_config
397
398 self.config = config
399 self.quant_config = quant_config
400
401 self.model = Exaone4Model(
402 vllm_config=vllm_config,
403 prefix=maybe_prefix(prefix, "model"),
404 )
405 if get_pp_group().is_last_rank:
406 self.lm_head = ParallelLMHead(
407 config.vocab_size,
408 config.hidden_size,
409 quant_config=quant_config,
410 prefix=maybe_prefix(prefix, "lm_head"),
411 )
412 if config.tie_word_embeddings:
413 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
414
415 logit_scale = getattr(config, "logit_scale", 1.0)
416 self.logits_processor = LogitsProcessor(
417 config.vocab_size, scale=logit_scale
418 )
419 else:
420 self.lm_head = PPMissingLayer()
421
422 self.make_empty_intermediate_tensors = (
423 self.model.make_empty_intermediate_tensors
424 )
425
426 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
427 return self.model.embed_input_ids(input_ids)
428
429 def forward(
430 self,
431 input_ids: torch.Tensor | None,
432 positions: torch.Tensor,
433 intermediate_tensors: IntermediateTensors | None = None,
434 inputs_embeds: torch.Tensor | None = None,
435 ) -> torch.Tensor | IntermediateTensors:
436 model_output = self.model(
437 input_ids, positions, intermediate_tensors, inputs_embeds
438 )
439 return model_output
440
441 def compute_logits(
442 self,
443 hidden_states: torch.Tensor,
444 ) -> torch.Tensor | None:
445 logits = self.logits_processor(self.lm_head, hidden_states)
446 return logits
447
448 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
449 loader = AutoWeightsLoader(self)
450 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)