main
py 482 lines 17 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # SPDX-FileCopyrightText: Copyright 2025 NAVER Cloud HyperCLOVA team
4
5 # Adapted from
6 # https://github.com/huggingface/transformers/blob/v4.28.0/src/transformers/models/llama/modeling_llama.py
7 # Copyright 2025 NAVER Cloud HyperCLOVA team. All rights reserved.
8 # Copyright 2023 The vLLM team.
9 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
10 #
11 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
12 # and OPT implementations in this library. It has been modified from its
13 # original forms to accommodate minor architectural differences compared
14 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
15 #
16 # Licensed under the Apache License, Version 2.0 (the "License");
17 # you may not use this file except in compliance with the License.
18 # You may obtain a copy of the License at
19 #
20 # http://www.apache.org/licenses/LICENSE-2.0
21 #
22 # Unless required by applicable law or agreed to in writing, software
23 # distributed under the License is distributed on an "AS IS" BASIS,
24 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 # See the License for the specific language governing permissions and
26 # limitations under the License.
27 """Inference-only HyperCLOVAX model compatible with HuggingFace weights."""
28
29 from collections.abc import Iterable
30 from itertools import islice
31
32 import torch
33 from torch import nn
34
35 from vllm.compilation.decorators import support_torch_compile
36 from vllm.config import CacheConfig, VllmConfig
37 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
38 from vllm.model_executor.layers.activation import SiluAndMul
39 from vllm.model_executor.layers.attention import Attention
40 from vllm.model_executor.layers.layernorm import RMSNorm
41 from vllm.model_executor.layers.linear import (
42 MergedColumnParallelLinear,
43 QKVParallelLinear,
44 RowParallelLinear,
45 )
46 from vllm.model_executor.layers.logits_processor import LogitsProcessor
47 from vllm.model_executor.layers.quantization import QuantizationConfig
48 from vllm.model_executor.layers.rotary_embedding import get_rope
49 from vllm.model_executor.layers.vocab_parallel_embedding import (
50 ParallelLMHead,
51 VocabParallelEmbedding,
52 )
53 from vllm.sequence import IntermediateTensors
54 from vllm.transformers_utils.configs.hyperclovax import HyperCLOVAXConfig
55
56 from .interfaces import SupportsLoRA, SupportsPP
57 from .utils import (
58 AutoWeightsLoader,
59 PPMissingLayer,
60 WeightsMapper,
61 make_empty_intermediate_tensors_factory,
62 make_layers,
63 maybe_prefix,
64 )
65
66
67 class HyperCLOVAXMLP(nn.Module):
68 def __init__(
69 self,
70 hidden_size: int,
71 intermediate_size: int,
72 hidden_act: str,
73 quant_config: QuantizationConfig | None = None,
74 bias: bool = False,
75 prefix: str = "",
76 reduce_results: bool = True,
77 disable_tp: bool = False,
78 ) -> None:
79 super().__init__()
80 self.gate_up_proj = MergedColumnParallelLinear(
81 input_size=hidden_size,
82 output_sizes=[intermediate_size] * 2,
83 bias=bias,
84 quant_config=quant_config,
85 disable_tp=disable_tp,
86 prefix=f"{prefix}.gate_up_proj",
87 )
88 self.down_proj = RowParallelLinear(
89 input_size=intermediate_size,
90 output_size=hidden_size,
91 bias=bias,
92 quant_config=quant_config,
93 reduce_results=reduce_results,
94 disable_tp=disable_tp,
95 prefix=f"{prefix}.down_proj",
96 )
97 if hidden_act != "silu":
98 raise ValueError(
99 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
100 )
101 self.act_fn = SiluAndMul()
102
103 def forward(self, x):
104 x, _ = self.gate_up_proj(x)
105 x = self.act_fn(x)
106 x, _ = self.down_proj(x)
107 return x
108
109
110 class HyperCLOVAXAttention(nn.Module):
111 def __init__(
112 self,
113 config: HyperCLOVAXConfig,
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 dual_chunk_attention_config: dict | None = None,
123 ) -> None:
124 super().__init__()
125 self.hidden_size = hidden_size
126 tp_size = get_tensor_model_parallel_world_size()
127 self.total_num_heads = num_heads
128 assert self.total_num_heads % tp_size == 0
129 self.num_heads = self.total_num_heads // tp_size
130 self.total_num_kv_heads = num_kv_heads
131 if self.total_num_kv_heads >= tp_size:
132 # Number of KV heads is greater than TP size, so we partition
133 # the KV heads across multiple tensor parallel GPUs.
134 assert self.total_num_kv_heads % tp_size == 0
135 else:
136 # Number of KV heads is less than TP size, so we replicate
137 # the KV heads across multiple tensor parallel GPUs.
138 assert tp_size % self.total_num_kv_heads == 0
139 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
140 self.head_dim = getattr(
141 config, "head_dim", self.hidden_size // self.total_num_heads
142 )
143 self.q_size = self.num_heads * self.head_dim
144 self.kv_size = self.num_kv_heads * self.head_dim
145 self.scaling = config.attention_multiplier
146
147 self.qkv_proj = QKVParallelLinear(
148 hidden_size=hidden_size,
149 head_size=self.head_dim,
150 total_num_heads=self.total_num_heads,
151 total_num_kv_heads=self.total_num_kv_heads,
152 bias=bias,
153 quant_config=quant_config,
154 prefix=f"{prefix}.qkv_proj",
155 )
156
157 self.o_proj = RowParallelLinear(
158 input_size=self.total_num_heads * self.head_dim,
159 output_size=hidden_size,
160 bias=bias,
161 quant_config=quant_config,
162 prefix=f"{prefix}.o_proj",
163 )
164
165 self.rotary_emb = get_rope(
166 self.head_dim,
167 max_position=max_position_embeddings,
168 is_neox_style=True,
169 rope_parameters=getattr(config, "rope_parameters", None),
170 dual_chunk_attention_config=dual_chunk_attention_config,
171 )
172
173 self.attn = Attention(
174 self.num_heads,
175 self.head_dim,
176 self.scaling,
177 num_kv_heads=self.num_kv_heads,
178 cache_config=cache_config,
179 quant_config=quant_config,
180 prefix=f"{prefix}.attn",
181 )
182
183 def forward(
184 self,
185 positions: torch.Tensor,
186 hidden_states: torch.Tensor,
187 ) -> torch.Tensor:
188 qkv, _ = self.qkv_proj(hidden_states)
189 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
190 q, k = self.rotary_emb(positions, q, k)
191 attn_output = self.attn(q, k, v)
192 output, _ = self.o_proj(attn_output)
193 return output
194
195
196 class HyperCLOVAXDecoderLayer(nn.Module):
197 def __init__(
198 self,
199 vllm_config: VllmConfig,
200 prefix: str = "",
201 ) -> None:
202 super().__init__()
203
204 config = vllm_config.model_config.hf_config
205 cache_config = vllm_config.cache_config
206 quant_config = vllm_config.quant_config
207
208 self.hidden_size = config.hidden_size
209 self.residual_multiplier = config.residual_multiplier
210 max_position_embeddings = getattr(
211 config,
212 "max_position_embeddings",
213 8192,
214 )
215 dual_chunk_attention_config = getattr(
216 config,
217 "dual_chunk_attention_config",
218 None,
219 )
220 attention_bias = getattr(config, "attention_bias", False)
221
222 self.self_attn = HyperCLOVAXAttention(
223 config=config,
224 hidden_size=self.hidden_size,
225 num_heads=config.num_attention_heads,
226 num_kv_heads=getattr(
227 config, "num_key_value_heads", config.num_attention_heads
228 ),
229 max_position_embeddings=max_position_embeddings,
230 quant_config=quant_config,
231 bias=attention_bias,
232 cache_config=cache_config,
233 prefix=f"{prefix}.self_attn",
234 dual_chunk_attention_config=dual_chunk_attention_config,
235 )
236 self.mlp = HyperCLOVAXMLP(
237 hidden_size=self.hidden_size,
238 intermediate_size=config.intermediate_size,
239 hidden_act=config.hidden_act,
240 quant_config=quant_config,
241 bias=getattr(config, "mlp_bias", False),
242 prefix=f"{prefix}.mlp",
243 )
244 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
245 self.post_attention_layernorm = RMSNorm(
246 config.hidden_size, eps=config.rms_norm_eps
247 )
248
249 # post-norm (dual-norm)
250 self.use_post_norm = config.use_post_norm
251 if self.use_post_norm:
252 self.post_norm1 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
253 self.post_norm2 = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
254
255 def forward(
256 self,
257 positions: torch.Tensor,
258 hidden_states: torch.Tensor,
259 residual: torch.Tensor | None,
260 ) -> tuple[torch.Tensor, torch.Tensor]:
261 # Unlike models that use a fused add-norm kernel (e.g. Llama), HyperCLOVAX
262 # applies the residual connection explicitly with a muP scaling factor
263 # (residual + hidden * residual_multiplier). As a result, each layer's
264 # hidden_states output already includes the residual addition, so the
265 # incoming residual is not needed and is reset at the start of each layer.
266 # The residual parameter is kept for interface consistency with other vllm
267 # decoder layers.
268
269 # Self Attention
270 residual = hidden_states
271
272 hidden_states = self.input_layernorm(hidden_states)
273
274 hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)
275 # Custom ln
276 if self.use_post_norm:
277 hidden_states = self.post_norm1(hidden_states)
278
279 # The residual is added outside the layernorm function to apply muP.
280 hidden_states = residual + hidden_states * self.residual_multiplier # muP
281 # Fully Connected
282 residual = hidden_states
283 hidden_states = self.post_attention_layernorm(hidden_states)
284 hidden_states = self.mlp(hidden_states)
285
286 # Custom ln
287 if self.use_post_norm:
288 hidden_states = self.post_norm2(hidden_states)
289
290 # The residual is added outside the layernorm function to apply muP.
291 hidden_states = residual + hidden_states * self.residual_multiplier # muP
292
293 return hidden_states, residual
294
295
296 @support_torch_compile
297 class HyperCLOVAXModel(nn.Module):
298 def __init__(
299 self,
300 *,
301 vllm_config: VllmConfig,
302 prefix: str = "",
303 layer_type: type[nn.Module] = HyperCLOVAXDecoderLayer,
304 ):
305 super().__init__()
306
307 config = vllm_config.model_config.hf_config
308 quant_config = vllm_config.quant_config
309
310 self.config = config
311 self.quant_config = quant_config
312 self.vocab_size = config.vocab_size
313 self.embed_tokens: VocabParallelEmbedding | PPMissingLayer
314 if get_pp_group().is_first_rank or (
315 config.tie_word_embeddings and get_pp_group().is_last_rank
316 ):
317 self.embed_tokens = VocabParallelEmbedding(
318 self.vocab_size,
319 config.hidden_size,
320 quant_config=quant_config,
321 )
322 else:
323 self.embed_tokens = PPMissingLayer()
324 self.start_layer, self.end_layer, self.layers = make_layers(
325 config.num_hidden_layers,
326 lambda prefix: layer_type(vllm_config=vllm_config, prefix=prefix),
327 prefix=f"{prefix}.layers",
328 )
329 self.norm: RMSNorm | PPMissingLayer
330 if get_pp_group().is_last_rank:
331 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
332 else:
333 self.norm = PPMissingLayer()
334
335 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
336 ["hidden_states", "residual"], config.hidden_size
337 )
338
339 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
340 return self.embed_tokens(input_ids)
341
342 def forward(
343 self,
344 input_ids: torch.Tensor | None,
345 positions: torch.Tensor,
346 intermediate_tensors: IntermediateTensors | None,
347 inputs_embeds: torch.Tensor | None = None,
348 ) -> torch.Tensor | IntermediateTensors:
349 if get_pp_group().is_first_rank:
350 if inputs_embeds is not None:
351 hidden_states = inputs_embeds
352 else:
353 assert input_ids is not None
354 hidden_states = self.embed_input_ids(input_ids)
355 residual = None
356
357 hidden_states *= self.config.embedding_multiplier # muP
358 else:
359 assert intermediate_tensors is not None
360 hidden_states = intermediate_tensors["hidden_states"]
361 residual = intermediate_tensors["residual"]
362
363 for layer in islice(self.layers, self.start_layer, self.end_layer):
364 hidden_states, residual = layer(positions, hidden_states, residual)
365
366 if not get_pp_group().is_last_rank:
367 assert residual is not None
368 return IntermediateTensors(
369 {"hidden_states": hidden_states, "residual": residual}
370 )
371
372 # The residual is added outside the layernorm function to apply muP.
373 hidden_states = self.norm(hidden_states)
374 return hidden_states
375
376
377 class HyperCLOVAXForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
378 hf_to_vllm_mapper = WeightsMapper(
379 orig_to_new_stacked={
380 # weight_name: (param_name, shard_id)
381 ".q_proj": (".qkv_proj", "q"),
382 ".k_proj": (".qkv_proj", "k"),
383 ".v_proj": (".qkv_proj", "v"),
384 ".gate_proj": (".gate_up_proj", 0),
385 ".up_proj": (".gate_up_proj", 1),
386 }
387 )
388 packed_modules_mapping = {
389 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
390 "gate_up_proj": ["gate_proj", "up_proj"],
391 }
392
393 # LoRA specific attributes
394 embedding_modules = {
395 "embed_tokens": "input_embeddings",
396 "lm_head": "output_embeddings",
397 }
398
399 def __init__(
400 self,
401 *,
402 vllm_config: VllmConfig,
403 prefix: str = "",
404 layer_type: type[nn.Module] = HyperCLOVAXDecoderLayer,
405 ):
406 super().__init__()
407 config = vllm_config.model_config.hf_config
408 quant_config = vllm_config.quant_config
409 self.config = config
410
411 self.model = self._init_model(
412 vllm_config=vllm_config,
413 prefix=maybe_prefix(prefix, "model"),
414 layer_type=layer_type,
415 )
416
417 self.lm_head: ParallelLMHead | PPMissingLayer
418 if get_pp_group().is_last_rank:
419 self.lm_head = ParallelLMHead(
420 config.vocab_size,
421 config.hidden_size,
422 quant_config=quant_config,
423 prefix=maybe_prefix(prefix, "lm_head"),
424 )
425 if config.tie_word_embeddings:
426 self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
427
428 logit_scale = getattr(config, "logit_scale", 1.0)
429 if hasattr(config, "logits_scaling"):
430 logit_scale *= config.logits_scaling # muP
431 self.logits_processor = LogitsProcessor(
432 config.vocab_size,
433 scale=logit_scale,
434 )
435 else:
436 self.lm_head = PPMissingLayer()
437
438 self.make_empty_intermediate_tensors = ( # type: ignore[method-assign]
439 self.model.make_empty_intermediate_tensors
440 )
441
442 def _init_model(
443 self,
444 vllm_config: VllmConfig,
445 prefix: str = "",
446 layer_type: type[nn.Module] = HyperCLOVAXDecoderLayer,
447 ):
448 return HyperCLOVAXModel(
449 vllm_config=vllm_config,
450 prefix=prefix,
451 layer_type=layer_type,
452 )
453
454 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
455 return self.model.embed_tokens(input_ids)
456
457 def forward( # type: ignore[override]
458 self,
459 input_ids: torch.Tensor | None,
460 positions: torch.Tensor,
461 *,
462 intermediate_tensors: IntermediateTensors | None = None,
463 inputs_embeds: torch.Tensor | None = None,
464 ) -> torch.Tensor | IntermediateTensors:
465 model_output = self.model(
466 input_ids, positions, intermediate_tensors, inputs_embeds
467 )
468 return model_output
469
470 def compute_logits(
471 self,
472 hidden_states: torch.Tensor,
473 ) -> torch.Tensor | None:
474 logits = self.logits_processor(self.lm_head, hidden_states)
475 return logits
476
477 def load_weights(
478 self,
479 weights: Iterable[tuple[str, torch.Tensor]],
480 ) -> set[str]:
481 loader = AutoWeightsLoader(self)
482 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)