@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ecad851cbd
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 15, 03:30 UTC

[Model]Add Tencent HunYuanMoEV1 Model Support (#20114)

Signed-off-by: aiyiwang <aiyiwang@tencent.com> Signed-off-by: Jee Jee Li <pandaleefree@gmail.com> Co-authored-by: quinnrong <quinnrong@tencent.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com>

aiyiwang2025 committed Jul 1, 2025 at 22:28 UTC ecad851cbd0a2a6f9922fc9f3e94bde3f8220176
6 files changed +949 -6
docs/models/supported_models.md
+2 -1
@@ -350,6 +350,7 @@ Specified using `--task generate`.
350 | `GraniteMoeSharedForCausalLM` | Granite MoE Shared | `ibm-research/moe-7b-1b-active-shared-experts` (test model) | ✅︎ | ✅︎ | ✅︎ |
351 | `GritLM` | GritLM | `parasail-ai/GritLM-7B-vllm`. | ✅︎ | ✅︎ | |
352 | `Grok1ModelForCausalLM` | Grok1 | `hpcai-tech/grok-1`. | ✅︎ | ✅︎ | ✅︎ |
353 +| `HunYuanMoEV1ForCausalLM` | Hunyuan-80B-A13B | `tencent/Hunyuan-A13B-Instruct`, `tencent/Hunyuan-A13B-Pretrain`, `tencent/Hunyuan-A13B-Instruct-FP8`etc. | | | ✅︎ |
354 | `InternLMForCausalLM` | InternLM | `internlm/internlm-7b`, `internlm/internlm-chat-7b`, etc. | ✅︎ | ✅︎ | ✅︎ |
355 | `InternLM2ForCausalLM` | InternLM2 | `internlm/internlm2-7b`, `internlm/internlm2-chat-7b`, etc. | ✅︎ | ✅︎ | ✅︎ |
356 | `InternLM3ForCausalLM` | InternLM3 | `internlm/internlm3-8b-instruct`, etc. | ✅︎ | ✅︎ | ✅︎ |
@@ -387,7 +388,7 @@ Specified using `--task generate`.
388 | `TeleChat2ForCausalLM` | TeleChat2 | `Tele-AI/TeleChat2-3B`, `Tele-AI/TeleChat2-7B`, `Tele-AI/TeleChat2-35B`, etc. | ✅︎ | ✅︎ | ✅︎ |
389 | `TeleFLMForCausalLM` | TeleFLM | `CofeAI/FLM-2-52B-Instruct-2407`, `CofeAI/Tele-FLM`, etc. | ✅︎ | ✅︎ | ✅︎ |
390 | `XverseForCausalLM` | XVERSE | `xverse/XVERSE-7B-Chat`, `xverse/XVERSE-13B-Chat`, `xverse/XVERSE-65B-Chat`, etc. | ✅︎ | ✅︎ | ✅︎ |
390 -| `MiniMaxM1ForCausalLM` | MiniMax-Text | `MiniMaxAI/MiniMax-M1-40k`, `MiniMaxAI/MiniMax-M1-80k`etc. | | | |
391 +| `MiniMaxM1ForCausalLM` | MiniMax-Text | `MiniMaxAI/MiniMax-M1-40k`, `MiniMaxAI/MiniMax-M1-80k`etc. | | | |
392 | `MiniMaxText01ForCausalLM` | MiniMax-Text | `MiniMaxAI/MiniMax-Text-01`, etc. | | | |
393 | `Zamba2ForCausalLM` | Zamba2 | `Zyphra/Zamba2-7B-instruct`, `Zyphra/Zamba2-2.7B-instruct`, `Zyphra/Zamba2-1.2B-instruct`, etc. | | | |
394
tests/models/registry.py
+3 -1
@@ -188,6 +188,8 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
188 "GraniteMoeSharedForCausalLM": _HfExamplesInfo("ibm-research/moe-7b-1b-active-shared-experts"), # noqa: E501
189 "Grok1ModelForCausalLM": _HfExamplesInfo("hpcai-tech/grok-1",
190 trust_remote_code=True),
191 + "HunYuanMoEV1ForCausalLM": _HfExamplesInfo("tencent/Hunyuan-A13B-Instruct",
192 + trust_remote_code=True),
193 "InternLMForCausalLM": _HfExamplesInfo("internlm/internlm-chat-7b",
194 trust_remote_code=True),
195 "InternLM2ForCausalLM": _HfExamplesInfo("internlm/internlm2-chat-7b",
@@ -490,4 +492,4 @@ class HfExampleModels:
492 raise ValueError(f"No example model defined for {model_id}")
493
494
493 -HF_EXAMPLE_MODELS = HfExampleModels(_EXAMPLE_MODELS)
495 +HF_EXAMPLE_MODELS = HfExampleModels(_EXAMPLE_MODELS)
\ No newline at end of file
tests/models/test_initialization.py
+2 -1
@@ -33,7 +33,8 @@ def test_can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch):
33
34 # Ensure at least 2 expert per group
35 # Since `grouped_topk` assums top-2
36 - num_experts = getattr(text_config, 'n_group', 1) * 2
36 + n_group = getattr(text_config, 'n_group', None)
37 + num_experts = n_group * 2 if n_group is not None else 2
38
39 text_config.update({
40 "num_layers": 1,
vllm/model_executor/layers/rotary_embedding.py
+44 -3
@@ -533,6 +533,41 @@ class DynamicNTKScalingRotaryEmbedding(RotaryEmbedding):
533 return cache
534
535
536 +class DynamicNTKAlphaRotaryEmbedding(RotaryEmbedding):
537 + """RotaryEmbedding extended with Dynamic NTK alpha.
538 +
539 + Based on the original RotaryEmbedding implementation.
540 + """
541 +
542 + def __init__(
543 + self,
544 + head_size: int,
545 + rotary_dim: int,
546 + max_position_embeddings: int,
547 + base: float,
548 + is_neox_style: bool,
549 + scaling_alpha: float,
550 + dtype: torch.dtype,
551 + ) -> None:
552 + self.scaling_alpha = scaling_alpha
553 + super().__init__(head_size, rotary_dim, max_position_embeddings, base,
554 + is_neox_style, dtype)
555 +
556 + def _compute_cos_sin_cache(self) -> torch.Tensor:
557 + # For Hunyuan DynamicNTKAlphaRotaryEmbedding
558 + max_len = self.max_position_embeddings
559 + base = self.base * self.scaling_alpha**(self.rotary_dim /
560 + (self.rotary_dim - 2))
561 + inv_freq = self._compute_inv_freq(base)
562 + t = torch.arange(max_len, dtype=torch.float)
563 +
564 + freqs = torch.einsum("i,j -> ij", t, inv_freq)
565 + cos = freqs.cos()
566 + sin = freqs.sin()
567 + cache = torch.cat((cos, sin), dim=-1)
568 + return cache
569 +
570 +
571 # Inverse dim formula to find dim based on number of rotations
572 def _yarn_find_correction_dim(num_rotations: int,
573 dim: int,
@@ -1929,9 +1964,15 @@ def get_rope(
1964 mixed_b)
1965 elif scaling_type == "dynamic":
1966 scaling_factor = rope_scaling["factor"]
1932 - rotary_emb = DynamicNTKScalingRotaryEmbedding(
1933 - head_size, rotary_dim, max_position, base, is_neox_style,
1934 - scaling_factor, dtype)
1967 + scaling_alpha = rope_scaling["alpha"]
1968 + if scaling_alpha:
1969 + rotary_emb = DynamicNTKAlphaRotaryEmbedding(
1970 + head_size, rotary_dim, max_position, base, is_neox_style,
1971 + scaling_alpha, dtype)
1972 + else:
1973 + rotary_emb = DynamicNTKScalingRotaryEmbedding(
1974 + head_size, rotary_dim, max_position, base, is_neox_style,
1975 + scaling_factor, dtype)
1976 elif scaling_type == "yarn":
1977 scaling_factor = rope_scaling["factor"]
1978 original_max_position = rope_scaling[
vllm/model_executor/models/hunyuan_v1_moe.py new
+897
@@ -0,0 +1,897 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +# coding=utf-8
5 +# Copyright 2024 The HunYuan team.
6 +# Copyright 2023 The vLLM team.
7 +# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
8 +#
9 +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
10 +# and OPT implementations in this library. It has been modified from its
11 +# original forms to accommodate minor architectural differences compared
12 +# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
13 +#
14 +# Licensed under the Apache License, Version 2.0 (the "License");
15 +# you may not use this file except in compliance with the License.
16 +# You may obtain a copy of the License at
17 +#
18 +# http://www.apache.org/licenses/LICENSE-2.0
19 +#
20 +# Unless required by applicable law or agreed to in writing, software
21 +# distributed under the License is distributed on an "AS IS" BASIS,
22 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 +# See the License for the specific language governing permissions and
24 +# limitations under the License.
25 +"""Inference-only HunYuan model compatible with HuggingFace weights."""
26 +from collections.abc import Iterable
27 +from typing import Any, Optional, Union
28 +
29 +import regex as re
30 +import torch
31 +from torch import nn
32 +from transformers import PretrainedConfig
33 +
34 +from vllm.attention import Attention, AttentionType
35 +from vllm.compilation.decorators import support_torch_compile
36 +from vllm.config import CacheConfig, VllmConfig
37 +from vllm.distributed import (get_pp_group,
38 + get_tensor_model_parallel_world_size,
39 + tensor_model_parallel_all_reduce)
40 +from vllm.model_executor.layers.activation import SiluAndMul
41 +from vllm.model_executor.layers.fused_moe import FusedMoE
42 +from vllm.model_executor.layers.layernorm import RMSNorm
43 +from vllm.model_executor.layers.linear import (ColumnParallelLinear,
44 + MergedColumnParallelLinear,
45 + QKVParallelLinear,
46 + ReplicatedLinear,
47 + RowParallelLinear)
48 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
49 +from vllm.model_executor.layers.quantization.base_config import (
50 + QuantizationConfig)
51 +from vllm.model_executor.layers.rotary_embedding import get_rope
52 +from vllm.model_executor.layers.sampler import SamplerOutput, get_sampler
53 +from vllm.model_executor.layers.vocab_parallel_embedding import (
54 + DEFAULT_VOCAB_PADDING_SIZE, ParallelLMHead, VocabParallelEmbedding)
55 +from vllm.model_executor.model_loader.weight_utils import (
56 + default_weight_loader, maybe_remap_kv_scale_name)
57 +from vllm.model_executor.sampling_metadata import SamplingMetadata
58 +from vllm.sequence import IntermediateTensors
59 +
60 +from .utils import PPMissingLayer, is_pp_missing_parameter, make_layers
61 +
62 +
63 +def _get_cla_factor(config: PretrainedConfig) -> int:
64 + if not getattr(config, "use_cla", False):
65 + return 1
66 + return getattr(config, "cla_share_factor", 1)
67 +
68 +
69 +class HunYuanMLP(nn.Module):
70 +
71 + def __init__(
72 + self,
73 + hidden_size: int,
74 + intermediate_size: int,
75 + hidden_act: str,
76 + quant_config: Optional[QuantizationConfig] = None,
77 + bias: bool = False,
78 + prefix: str = "",
79 + reduce_results: bool = True,
80 + ) -> None:
81 + super().__init__()
82 + self.gate_up_proj = MergedColumnParallelLinear(
83 + input_size=hidden_size,
84 + output_sizes=[intermediate_size] * 2,
85 + bias=bias,
86 + quant_config=quant_config,
87 + prefix=f"{prefix}.gate_up_proj",
88 + )
89 + self.down_proj = RowParallelLinear(
90 + input_size=intermediate_size,
91 + output_size=hidden_size,
92 + bias=bias,
93 + quant_config=quant_config,
94 + prefix=f"{prefix}.down_proj",
95 + reduce_results=reduce_results,
96 + )
97 + if hidden_act != "silu":
98 + raise ValueError(f"Unsupported activation: {hidden_act}. "
99 + "Only silu is supported for now.")
100 + self.act_fn = SiluAndMul()
101 +
102 + def forward(self, x):
103 + gate_up, _ = self.gate_up_proj(x)
104 + x = self.act_fn(gate_up)
105 + x, _ = self.down_proj(x)
106 + return x
107 +
108 +
109 +class HunYuanAttention(nn.Module):
110 +
111 + def __init__(
112 + self,
113 + config: PretrainedConfig,
114 + hidden_size: int,
115 + num_heads: int,
116 + num_kv_heads: int,
117 + rope_theta: float = 10000,
118 + rope_scaling: Optional[dict[str, Any]] = None,
119 + max_position_embeddings: int = 8192,
120 + quant_config: Optional[QuantizationConfig] = None,
121 + bias: bool = False,
122 + cache_config: Optional[CacheConfig] = None,
123 + prefix: str = "",
124 + layer_id: int = -1,
125 + ) -> None:
126 + super().__init__()
127 + self.hidden_size = hidden_size
128 + tp_size = get_tensor_model_parallel_world_size()
129 + self.total_num_heads = num_heads
130 + assert self.total_num_heads % tp_size == 0
131 + self.num_heads = self.total_num_heads // tp_size
132 + self.total_num_kv_heads = num_kv_heads
133 + if self.total_num_kv_heads >= tp_size:
134 + # Number of KV heads is greater than TP size, so we partition
135 + # the KV heads across multiple tensor parallel GPUs.
136 + assert self.total_num_kv_heads % tp_size == 0
137 + else:
138 + # Number of KV heads is less than TP size, so we replicate
139 + # the KV heads across multiple tensor parallel GPUs.
140 + assert tp_size % self.total_num_kv_heads == 0
141 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
142 + # MistralConfig has an optional head_dim introduced by Mistral-Nemo
143 + if hasattr(config, "head_dim"):
144 + self.head_dim = config.head_dim
145 + elif hasattr(config, "attention_head_dim"):
146 + self.head_dim = config.attention_head_dim
147 + else:
148 + self.head_dim = self.hidden_size // self.total_num_heads
149 + self.q_size = self.num_heads * self.head_dim
150 + self.kv_size = self.num_kv_heads * self.head_dim
151 + self.scaling = self.head_dim**-0.5
152 + self.rope_theta = rope_theta
153 + self.max_position_embeddings = max_position_embeddings
154 + self.use_qk_norm = getattr(config, "use_qk_norm", False)
155 + self.layer_id = layer_id
156 +
157 + self.qkv_proj = QKVParallelLinear(
158 + hidden_size=hidden_size,
159 + head_size=self.head_dim,
160 + total_num_heads=self.total_num_heads,
161 + total_num_kv_heads=self.total_num_kv_heads,
162 + bias=bias,
163 + quant_config=quant_config,
164 + prefix=f"{prefix}.qkv_proj",
165 + )
166 +
167 + self.o_proj = RowParallelLinear(
168 + input_size=self.total_num_heads * self.head_dim,
169 + output_size=hidden_size,
170 + bias=bias,
171 + quant_config=quant_config,
172 + prefix=f"{prefix}.o_proj",
173 + )
174 +
175 + self.rotary_emb = get_rope(
176 + self.head_dim,
177 + rotary_dim=self.head_dim,
178 + max_position=max_position_embeddings,
179 + base=rope_theta,
180 + rope_scaling=rope_scaling,
181 + is_neox_style=True,
182 + )
183 + self.attn = Attention(
184 + self.num_heads,
185 + self.head_dim,
186 + self.scaling,
187 + num_kv_heads=self.num_kv_heads,
188 + cache_config=cache_config,
189 + quant_config=quant_config,
190 + prefix=f"{prefix}.attn",
191 + )
192 +
193 + if self.use_qk_norm:
194 + self.query_layernorm = RMSNorm(self.head_dim,
195 + eps=config.rms_norm_eps)
196 + self.key_layernorm = RMSNorm(self.head_dim,
197 + eps=config.rms_norm_eps)
198 +
199 + def forward(
200 + self,
201 + positions: torch.Tensor,
202 + hidden_states: torch.Tensor,
203 + kv_states: Optional[tuple[torch.Tensor]] = None,
204 + ) -> torch.Tensor:
205 + qkv, _ = self.qkv_proj(hidden_states)
206 + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
207 + q, k = self.rotary_emb(positions, q, k)
208 + ori_k = k
209 + if self.use_qk_norm:
210 + q = self.query_layernorm(
211 + q.view(-1, self.num_heads, self.head_dim).contiguous())
212 + k = self.key_layernorm(
213 + k.view(-1, self.num_kv_heads, self.head_dim).contiguous())
214 +
215 + attn_output = self.attn(q, k, v)
216 + # For o_proj
217 + attn_output = attn_output.view(q.shape[0], -1)
218 + output, _ = self.o_proj(attn_output)
219 + return output, (ori_k, v)
220 +
221 +
222 +class HunYuanCrossAttention(nn.Module):
223 +
224 + def __init__(
225 + self,
226 + config: PretrainedConfig,
227 + hidden_size: int,
228 + num_heads: int,
229 + num_kv_heads: int,
230 + rope_theta: float = 10000,
231 + rope_scaling: Optional[dict[str, Any]] = None,
232 + max_position_embeddings: int = 8192,
233 + quant_config: Optional[QuantizationConfig] = None,
234 + bias: bool = False,
235 + cache_config: Optional[CacheConfig] = None,
236 + prefix: str = "",
237 + layer_id: int = -1,
238 + ) -> None:
239 + super().__init__()
240 + self.hidden_size = hidden_size
241 + tp_size = get_tensor_model_parallel_world_size()
242 + self.total_num_heads = num_heads
243 + assert self.total_num_heads % tp_size == 0
244 + self.num_heads = self.total_num_heads // tp_size
245 + self.total_num_kv_heads = num_kv_heads
246 + if self.total_num_kv_heads >= tp_size:
247 + # Number of KV heads is greater than TP size, so we partition
248 + # the KV heads across multiple tensor parallel GPUs.
249 + assert self.total_num_kv_heads % tp_size == 0
250 + else:
251 + # Number of KV heads is less than TP size, so we replicate
252 + # the KV heads across multiple tensor parallel GPUs.
253 + assert tp_size % self.total_num_kv_heads == 0
254 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
255 + # MistralConfig has an optional head_dim introduced by Mistral-Nemo
256 + if hasattr(config, "head_dim"):
257 + self.head_dim = config.head_dim
258 + elif hasattr(config, "attention_head_dim"):
259 + self.head_dim = config.attention_head_dim
260 + else:
261 + self.head_dim = self.hidden_size // self.total_num_heads
262 + self.q_size = self.num_heads * self.head_dim
263 + self.kv_size = self.num_kv_heads * self.head_dim
264 + self.scaling = self.head_dim**-0.5
265 + self.rope_theta = rope_theta
266 + self.max_position_embeddings = max_position_embeddings
267 + self.use_qk_norm = getattr(config, "use_qk_norm", False)
268 + self.layer_id = layer_id
269 +
270 + self.q_proj = ColumnParallelLinear(
271 + hidden_size,
272 + hidden_size,
273 + bias=bias,
274 + quant_config=quant_config,
275 + prefix=f"{prefix}.q_proj",
276 + )
277 +
278 + self.o_proj = RowParallelLinear(
279 + input_size=self.total_num_heads * self.head_dim,
280 + output_size=hidden_size,
281 + bias=bias,
282 + quant_config=quant_config,
283 + prefix=f"{prefix}.o_proj",
284 + )
285 +
286 + self.rotary_emb = get_rope(
287 + self.head_dim,
288 + rotary_dim=self.head_dim,
289 + max_position=max_position_embeddings,
290 + base=rope_theta,
291 + rope_scaling=rope_scaling,
292 + is_neox_style=True,
293 + )
294 + self.attn = Attention(
295 + self.num_heads,
296 + self.head_dim,
297 + self.scaling,
298 + num_kv_heads=self.num_kv_heads,
299 + cache_config=cache_config,
300 + quant_config=quant_config,
301 + prefix=f"{prefix}.attn",
302 + attn_type=AttentionType.ENCODER_DECODER,
303 + )
304 +
305 + if self.use_qk_norm:
306 + self.query_layernorm = RMSNorm(self.head_dim,
307 + eps=config.rms_norm_eps)
308 + self.key_layernorm = RMSNorm(self.head_dim,
309 + eps=config.rms_norm_eps)
310 +
311 + def forward(
312 + self,
313 + positions: torch.Tensor,
314 + hidden_states: torch.Tensor,
315 + kv_states: Optional[tuple[torch.Tensor]] = None,
316 + ) -> torch.Tensor:
317 + assert kv_states is not None
318 + ori_k, v = kv_states # use last layer kv,
319 + k = ori_k
320 + q, _ = self.q_proj(hidden_states)
321 + k_tmp = torch.empty_like(k) # Todo: reduant rotary embedding
322 + q, _ = self.rotary_emb(positions, q, k_tmp)
323 + if self.use_qk_norm:
324 + q = self.query_layernorm(
325 + q.view(-1, self.num_heads, self.head_dim).contiguous())
326 + k = self.key_layernorm(
327 + k.view(-1, self.num_kv_heads, self.head_dim).contiguous())
328 +
329 + attn_output = self.attn(q, k, v)
330 + # For o_proj
331 + attn_output = attn_output.view(q.shape[0], -1)
332 + output, _ = self.o_proj(attn_output)
333 + return output, (ori_k, v)
334 +
335 +
336 +class HunYuanSparseMoeBlock(nn.Module):
337 +
338 + def __init__(
339 + self,
340 + config: PretrainedConfig,
341 + quant_config: Optional[QuantizationConfig] = None,
342 + layer_id: int = -1,
343 + prefix: str = "",
344 + ):
345 + super().__init__()
346 + self.tp_size = get_tensor_model_parallel_world_size()
347 +
348 + if self.tp_size > config.num_experts:
349 + raise ValueError(
350 + f"Tensor parallel size {self.tp_size} is greater than "
351 + f"the number of experts {config.num_experts}.")
352 +
353 + # Get layer_id topk if config.moe_topk is a list
354 + if isinstance(config.moe_topk, list):
355 + assert layer_id >= 0
356 + assert len(config.moe_topk) > layer_id
357 + top_k = config.moe_topk[layer_id]
358 + else:
359 + top_k = config.moe_topk
360 +
361 + # If it is moe, moe_intermediate_size is preferred
362 + intermediate_size = config.intermediate_size
363 + if config.moe_intermediate_size is not None:
364 + intermediate_size = (config.moe_intermediate_size if isinstance(
365 + config.moe_intermediate_size, int) else
366 + config.moe_intermediate_size[layer_id])
367 +
368 + self.experts = FusedMoE(
369 + num_experts=config.num_experts,
370 + top_k=top_k,
371 + hidden_size=config.hidden_size,
372 + intermediate_size=intermediate_size,
373 + reduce_results=False,
374 + renormalize=top_k > 1,
375 + quant_config=quant_config,
376 + prefix=f"{prefix}.experts",
377 + )
378 +
379 + self.gate = ReplicatedLinear(config.hidden_size,
380 + config.num_experts,
381 + bias=False,
382 + quant_config=None,
383 + prefix=f"{prefix}.gate")
384 + if config.use_mixed_mlp_moe > 0:
385 + # Get layer_id num_shared_expert if config.num_shared_expert is
386 + # a list.
387 + if isinstance(config.num_shared_expert, list):
388 + assert layer_id >= 0
389 + assert len(config.num_shared_expert) > layer_id
390 + num_shared_expert = config.num_shared_expert[layer_id]
391 + else:
392 + num_shared_expert = config.num_shared_expert
393 +
394 + self.shared_mlp = HunYuanMLP(
395 + hidden_size=config.hidden_size,
396 + intermediate_size=config.intermediate_size * num_shared_expert,
397 + hidden_act=config.hidden_act,
398 + quant_config=quant_config,
399 + reduce_results=False,
400 + )
401 + else:
402 + self.shared_mlp = None
403 +
404 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
405 + # NOTE: hidden_states can have either 1D or 2D shape.
406 + orig_shape = hidden_states.shape
407 + hidden_dim = hidden_states.shape[-1]
408 + hidden_states = hidden_states.view(-1, hidden_dim)
409 + shared_output = None
410 + if self.shared_mlp is not None:
411 + shared_output = self.shared_mlp(hidden_states)
412 +
413 + # router_logits: (num_tokens, n_experts)
414 + router_logits, _ = self.gate(hidden_states)
415 + final_hidden_states = self.experts(hidden_states=hidden_states,
416 + router_logits=router_logits)
417 + if shared_output is not None:
418 + final_hidden_states = final_hidden_states + shared_output
419 + if self.tp_size > 1:
420 + final_hidden_states = tensor_model_parallel_all_reduce(
421 + final_hidden_states)
422 +
423 + return final_hidden_states.view(orig_shape)
424 +
425 +
426 +class HunYuanDecoderLayer(nn.Module):
427 +
428 + def __init__(
429 + self,
430 + config: PretrainedConfig,
431 + cache_config: Optional[CacheConfig] = None,
432 + quant_config: Optional[QuantizationConfig] = None,
433 + prefix: str = "",
434 + layer_id: int = -1,
435 + ) -> None:
436 + super().__init__()
437 + assert layer_id >= 0
438 + self.layer_id = layer_id
439 + self.hidden_size = config.hidden_size
440 + self.intermediate_size = (config.intermediate_size if isinstance(
441 + config.intermediate_size, int) else
442 + config.intermediate_size[layer_id])
443 + rope_theta = getattr(config, "rope_theta", 10000)
444 + rope_scaling = getattr(config, "rope_scaling", None)
445 + if rope_scaling is not None and getattr(
446 + config, "original_max_position_embeddings", None):
447 + rope_scaling["original_max_position_embeddings"] = (
448 + config.original_max_position_embeddings)
449 + max_position_embeddings = getattr(config, "max_position_embeddings",
450 + 8192)
451 + attention_bias = getattr(config, "attention_bias", False) or getattr(
452 + config, "bias", False)
453 + cla_factor = _get_cla_factor(config)
454 + attention_type = (AttentionType.ENCODER_DECODER
455 + if layer_id >= 0 and layer_id % cla_factor != 0 else
456 + AttentionType.DECODER)
457 + if attention_type == AttentionType.DECODER:
458 + self.self_attn = HunYuanAttention(
459 + config=config,
460 + hidden_size=self.hidden_size,
461 + num_heads=config.num_attention_heads,
462 + num_kv_heads=getattr(config, "num_key_value_heads",
463 + config.num_attention_heads),
464 + rope_theta=rope_theta,
465 + rope_scaling=rope_scaling,
466 + max_position_embeddings=max_position_embeddings,
467 + quant_config=quant_config,
468 + bias=attention_bias,
469 + cache_config=cache_config,
470 + prefix=f"{prefix}.self_attn",
471 + layer_id=layer_id,
472 + )
473 + elif attention_type == AttentionType.ENCODER_DECODER:
474 + self.self_attn = HunYuanCrossAttention(
475 + config=config,
476 + hidden_size=self.hidden_size,
477 + num_heads=config.num_attention_heads,
478 + num_kv_heads=getattr(config, "num_key_value_heads",
479 + config.num_attention_heads),
480 + rope_theta=rope_theta,
481 + rope_scaling=rope_scaling,
482 + max_position_embeddings=max_position_embeddings,
483 + quant_config=quant_config,
484 + bias=attention_bias,
485 + cache_config=cache_config,
486 + prefix=f"{prefix}.self_attn",
487 + layer_id=layer_id,
488 + )
489 + else:
490 + raise RuntimeError(f"Unsupported attention type: {attention_type}")
491 +
492 + self.mlp = HunYuanSparseMoeBlock(
493 + config=config,
494 + quant_config=quant_config,
495 + layer_id=layer_id,
496 + prefix=f"{prefix}.mlp",
497 + )
498 + self.input_layernorm = RMSNorm(config.hidden_size,
499 + eps=config.rms_norm_eps)
500 + self.post_attention_layernorm = RMSNorm(config.hidden_size,
501 + eps=config.rms_norm_eps)
502 +
503 + def forward(
504 + self,
505 + positions: torch.Tensor,
506 + hidden_states: torch.Tensor,
507 + residual: Optional[torch.Tensor],
508 + kv_states: Optional[tuple[torch.Tensor]] = None,
509 + ) -> tuple[torch.Tensor, torch.Tensor]:
510 + # Self Attention
511 + if residual is None:
512 + residual = hidden_states
513 + hidden_states = self.input_layernorm(hidden_states)
514 + else:
515 + hidden_states, residual = self.input_layernorm(
516 + hidden_states, residual)
517 + hidden_states, ori_kv_states = self.self_attn(
518 + positions=positions,
519 + hidden_states=hidden_states,
520 + kv_states=kv_states,
521 + )
522 +
523 + # Fully Connected
524 + hidden_states, residual = self.post_attention_layernorm(
525 + hidden_states, residual)
526 + hidden_states = self.mlp(hidden_states)
527 + return hidden_states, residual, ori_kv_states
528 +
529 +
530 +@support_torch_compile
531 +class HunYuanModel(nn.Module):
532 +
533 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
534 + super().__init__()
535 +
536 + config = vllm_config.model_config.hf_config
537 + cache_config = vllm_config.cache_config
538 + quant_config = vllm_config.quant_config
539 + lora_config = vllm_config.lora_config
540 +
541 + self.config = config
542 + self.quant_config = quant_config
543 + self.padding_idx = config.pad_token_id
544 + lora_vocab = ((lora_config.lora_extra_vocab_size *
545 + (lora_config.max_loras or 1)) if lora_config else 0)
546 + self.vocab_size = config.vocab_size + lora_vocab
547 + self.org_vocab_size = config.vocab_size
548 + if get_pp_group().is_first_rank or (config.tie_word_embeddings
549 + and get_pp_group().is_last_rank):
550 + self.embed_tokens = VocabParallelEmbedding(
551 + self.vocab_size,
552 + config.hidden_size,
553 + org_num_embeddings=config.vocab_size,
554 + quant_config=quant_config,
555 + )
556 + else:
557 + self.embed_tokens = PPMissingLayer()
558 + self.start_layer, self.end_layer, self.layers = make_layers(
559 + config.num_hidden_layers,
560 + lambda prefix: HunYuanDecoderLayer(
561 + config=config,
562 + layer_id=int(prefix.split(".")[-1]),
563 + cache_config=cache_config,
564 + quant_config=quant_config,
565 + prefix=prefix,
566 + ),
567 + prefix=f"{prefix}.layers",
568 + )
569 + if get_pp_group().is_last_rank:
570 + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
571 + else:
572 + self.norm = PPMissingLayer()
573 +
574 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
575 + return self.embed_tokens(input_ids)
576 +
577 + def forward(
578 + self,
579 + input_ids: Optional[torch.Tensor],
580 + positions: torch.Tensor,
581 + intermediate_tensors: Optional[IntermediateTensors],
582 + inputs_embeds: Optional[torch.Tensor] = None,
583 + ) -> Union[torch.Tensor, IntermediateTensors]:
584 + if get_pp_group().is_first_rank:
585 + if inputs_embeds is not None:
586 + hidden_states = inputs_embeds
587 + else:
588 + hidden_states = self.get_input_embeddings(input_ids)
589 + residual = None
590 + else:
591 + assert intermediate_tensors is not None
592 + hidden_states = intermediate_tensors["hidden_states"]
593 + residual = intermediate_tensors["residual"]
594 +
595 + cla_factor = _get_cla_factor(self.config)
596 + prev_kv_states = None
597 + for i in range(self.start_layer, self.end_layer):
598 + layer = self.layers[i]
599 + hidden_states, residual, kv_states = layer(
600 + positions,
601 + hidden_states,
602 + residual,
603 + prev_kv_states,
604 + )
605 +
606 + if (getattr(self.config, "use_cla", False)
607 + and (i - self.start_layer) % cla_factor == 0):
608 + prev_kv_states = kv_states
609 + else:
610 + prev_kv_states = None
611 +
612 + if not get_pp_group().is_last_rank:
613 + return IntermediateTensors({
614 + "hidden_states": hidden_states,
615 + "residual": residual
616 + })
617 +
618 + hidden_states, _ = self.norm(hidden_states, residual)
619 + return hidden_states
620 +
621 +
622 +class HunYuanMoEV1ForCausalLM(nn.Module):
623 + packed_modules_mapping = {
624 + "qkv_proj": [
625 + "q_proj",
626 + "k_proj",
627 + "v_proj",
628 + ],
629 + "gate_up_proj": [
630 + "gate_proj",
631 + "up_proj",
632 + ],
633 + }
634 +
635 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
636 + super().__init__()
637 +
638 + config = vllm_config.model_config.hf_config
639 + quant_config = vllm_config.quant_config
640 + lora_config = vllm_config.lora_config
641 + self.config = config
642 + self.quant_config = quant_config
643 + self.lora_config = lora_config
644 +
645 + self.model = HunYuanModel(vllm_config=vllm_config, prefix="model")
646 + if get_pp_group().is_last_rank:
647 + self.unpadded_vocab_size = config.vocab_size
648 + if lora_config:
649 + self.unpadded_vocab_size += lora_config.lora_extra_vocab_size
650 + self.lm_head = ParallelLMHead(
651 + self.unpadded_vocab_size,
652 + config.hidden_size,
653 + org_num_embeddings=config.vocab_size,
654 + padding_size=DEFAULT_VOCAB_PADDING_SIZE,
655 + quant_config=quant_config,
656 + )
657 + if config.tie_word_embeddings:
658 + self.lm_head.weight = self.model.embed_tokens.weight
659 +
660 + logit_scale = getattr(config, "logit_scale", 1.0)
661 + self.logits_processor = LogitsProcessor(self.unpadded_vocab_size,
662 + config.vocab_size,
663 + logit_scale)
664 + self.sampler = get_sampler()
665 + else:
666 + self.lm_head = PPMissingLayer()
667 +
668 + def forward(
669 + self,
670 + input_ids: torch.Tensor,
671 + positions: torch.Tensor,
672 + intermediate_tensors: Optional[IntermediateTensors] = None,
673 + inputs_embeds: Optional[torch.Tensor] = None,
674 + ) -> Union[torch.Tensor, IntermediateTensors]:
675 + model_output = self.model(input_ids, positions, intermediate_tensors,
676 + inputs_embeds)
677 + return model_output
678 +
679 + def compute_logits(
680 + self,
681 + hidden_states: torch.Tensor,
682 + sampling_metadata: SamplingMetadata,
683 + ) -> Optional[torch.Tensor]:
684 + logits = self.logits_processor(self.lm_head, hidden_states,
685 + sampling_metadata)
686 + return logits
687 +
688 + def sample(
689 + self,
690 + logits: torch.Tensor,
691 + sampling_metadata: SamplingMetadata,
692 + ) -> Optional[SamplerOutput]:
693 + next_tokens = self.sampler(logits, sampling_metadata)
694 + return next_tokens
695 +
696 + def make_empty_intermediate_tensors(
697 + self, batch_size: int, dtype: torch.dtype,
698 + device: torch.device) -> IntermediateTensors:
699 + return IntermediateTensors({
700 + "hidden_states":
701 + torch.zeros((batch_size, self.config.hidden_size),
702 + dtype=dtype,
703 + device=device),
704 + "residual":
705 + torch.zeros((batch_size, self.config.hidden_size),
706 + dtype=dtype,
707 + device=device),
708 + })
709 +
710 + def _split_qkv_weight(self, qkv: torch.Tensor):
711 + num_attention_heads = self.config.num_attention_heads
712 + num_kv_heads = getattr(self.config, "num_key_value_heads",
713 + self.config.num_attention_heads)
714 + num_key_value_groups = num_attention_heads // num_kv_heads
715 + hidden_size = self.config.hidden_size
716 +
717 + if hasattr(self.config, "head_dim"):
718 + attention_head_dim = self.config.head_dim
719 + elif hasattr(self.config, "attention_head_dim"):
720 + attention_head_dim = self.config.attention_head_dim
721 + else:
722 + attention_head_dim = self.config.hidden_size // num_attention_heads
723 +
724 + qkv = qkv.reshape(num_kv_heads, num_key_value_groups + 2,
725 + attention_head_dim, hidden_size)
726 + q, k, v = torch.split(qkv, (num_key_value_groups, 1, 1), dim=1)
727 + q = q.reshape(-1, hidden_size)
728 + k = k.reshape(-1, hidden_size)
729 + v = v.reshape(-1, hidden_size)
730 + return torch.concat((q, k, v))
731 +
732 + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
733 + cla_factor = _get_cla_factor(self.config)
734 + stacked_params_mapping = [
735 + # (param_name, shard_name, shard_id)
736 + (".qkv_proj", ".q_proj", "q"),
737 + (".qkv_proj", ".k_proj", "k"),
738 + (".qkv_proj", ".v_proj", "v"),
739 + (".gate_up_proj", ".gate_proj", 0),
740 + (".gate_up_proj", ".up_proj", 1),
741 + ]
742 +
743 + num_attention_heads = self.config.num_attention_heads
744 + num_kv_heads = getattr(self.config, "num_key_value_heads",
745 + self.config.num_attention_heads)
746 + split_params_mapping = [
747 + (".gate_up_proj", ".gate_and_up_proj", 2, [(1, 1), (0, 1)], None),
748 + (
749 + ".qkv_proj",
750 + ".qkv_proj",
751 + num_attention_heads + num_kv_heads * 2,
752 + [("q", num_attention_heads), ("k", num_kv_heads),
753 + ("v", num_kv_heads)],
754 + self._split_qkv_weight,
755 + ),
756 + ]
757 +
758 + # Params for weights, fp8 weight scales, fp8 activation scales
759 + # (param_name, weight_name, expert_id, shard_id)
760 + expert_params_mapping = FusedMoE.make_expert_params_mapping(
761 + ckpt_gate_proj_name="gate_proj",
762 + ckpt_down_proj_name="down_proj",
763 + ckpt_up_proj_name="up_proj",
764 + num_experts=self.config.num_experts,
765 + )
766 +
767 + params_dict = dict(self.named_parameters())
768 + for name, loaded_weight in weights:
769 + if "rotary_emb.inv_freq" in name:
770 + continue
771 + if "gate_proj_bias" in name:
772 + name = name.replace("gate_proj_bias", "gate_proj.bias")
773 + if "up_proj_bias" in name:
774 + name = name.replace("up_proj_bias", "up_proj.bias")
775 + if ("rotary_emb.cos_cached" in name
776 + or "rotary_emb.sin_cached" in name):
777 + # Models trained using ColossalAI may include these tensors in
778 + # the checkpoint. Skip them.
779 + continue
780 + # With tie_word_embeddings, we can skip lm_head.weight
781 + # The weight might appear unnecessarily in the files if the model is
782 + # processed with quantization, LoRA, fine-tuning, etc.
783 + if self.config.tie_word_embeddings and "lm_head.weight" in name:
784 + continue
785 + if self.quant_config is not None and (
786 + scale_name := self.quant_config.get_cache_scale(name)):
787 + # Loading kv cache scales for compressed-tensors quantization
788 + param = params_dict[scale_name]
789 + weight_loader = getattr(param, "weight_loader",
790 + default_weight_loader)
791 + loaded_weight = loaded_weight[0]
792 + weight_loader(param, loaded_weight)
793 + continue
794 +
795 + is_found = False
796 + for param_name, weight_name, shard_id in stacked_params_mapping:
797 + if weight_name not in name:
798 + continue
799 + if "mlp.experts" in name:
800 + continue
801 + # cross layer only have q_proj, skip qkv pack
802 + if weight_name == ".q_proj":
803 + match = re.search(r"layers\.\d+", name)
804 + if match:
805 + layer_id = int(match.group(0).split(".")[-1])
806 + if cla_factor > 1 and layer_id % cla_factor != 0:
807 + continue
808 + name = name.replace(weight_name, param_name)
809 + # Skip loading extra bias for GPTQ models.
810 + if name.endswith(".bias") and name not in params_dict:
811 + continue
812 +
813 + if is_pp_missing_parameter(name, self):
814 + continue
815 +
816 + param = params_dict[name]
817 + weight_loader = param.weight_loader
818 + weight_loader(param, loaded_weight, shard_id)
819 +
820 + is_found = True
821 + break
822 + if is_found:
823 + continue
824 +
825 + for (
826 + param_name,
827 + weight_name,
828 + den,
829 + split_param,
830 + func,
831 + ) in split_params_mapping:
832 + if weight_name not in name:
833 + continue
834 + name = name.replace(weight_name, param_name)
835 + # Skip loading extra bias for GPTQ models.
836 + if name.endswith(".bias") and name not in params_dict:
837 + continue
838 +
839 + if is_pp_missing_parameter(name, self):
840 + continue
841 +
842 + assert loaded_weight.shape[0] % den == 0
843 + units = loaded_weight.shape[0] // den
844 +
845 + param = params_dict[name]
846 + weight_loader = param.weight_loader
847 + offset = 0
848 + for shard_id, num in split_param:
849 + new_offset = offset + num * units
850 + if func:
851 + weight_loader(param,
852 + func(loaded_weight)[offset:new_offset],
853 + shard_id)
854 + else:
855 + weight_loader(param, loaded_weight[offset:new_offset],
856 + shard_id)
857 + offset = new_offset
858 +
859 + break
860 + else:
861 + # Skip loading extra bias for GPTQ models.
862 + if name.endswith(".bias") and name not in params_dict:
863 + continue
864 + for mapping in expert_params_mapping:
865 + param_name, weight_name, expert_id, shard_id = mapping
866 + if weight_name not in name:
867 + continue
868 + name = name.replace(weight_name, param_name)
869 + # Skip layers on other devices.
870 + if is_pp_missing_parameter(name, self):
871 + continue
872 + param = params_dict[name]
873 + weight_loader = param.weight_loader
874 + weight_loader(
875 + param,
876 + loaded_weight,
877 + name,
878 + shard_id=shard_id,
879 + expert_id=expert_id,
880 + )
881 + break
882 + else:
883 + # Remapping the name of FP8 kv-scale.
884 + name = maybe_remap_kv_scale_name(name, params_dict)
885 + if name is None:
886 + continue
887 +
888 + if is_pp_missing_parameter(name, self):
889 + continue
890 +
891 + if "mlp.gate.wg." in name:
892 + name = name.replace("wg.", "")
893 +
894 + param = params_dict[name]
895 + weight_loader = getattr(param, "weight_loader",
896 + default_weight_loader)
897 + weight_loader(param, loaded_weight)
vllm/model_executor/models/registry.py
+1
@@ -73,6 +73,7 @@ _TEXT_GENERATION_MODELS = {
73 "GraniteMoeSharedForCausalLM": ("granitemoeshared", "GraniteMoeSharedForCausalLM"), # noqa: E501
74 "GritLM": ("gritlm", "GritLM"),
75 "Grok1ModelForCausalLM": ("grok1", "Grok1ForCausalLM"),
76 + "HunYuanMoEV1ForCausalLM": ("hunyuan_v1_moe", "HunYuanMoEV1ForCausalLM"),
77 "InternLMForCausalLM": ("llama", "LlamaForCausalLM"),
78 "InternLM2ForCausalLM": ("internlm2", "InternLM2ForCausalLM"),
79 "InternLM2VEForCausalLM": ("internlm2_ve", "InternLM2VEForCausalLM"),