main
py 488 lines 17.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2025 The MiniMax AI team.
5 # Copyright 2023 The vLLM team.
6 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7 #
8 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 # and OPT implementations in this library. It has been modified from its
10 # original forms to accommodate minor architectural differences compared
11 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 #
13 # Licensed under the Apache License, Version 2.0 (the "License");
14 # you may not use this file except in compliance with the License.
15 # You may obtain a copy of the License at
16 #
17 # http://www.apache.org/licenses/LICENSE-2.0
18 #
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
24 """Inference-only MiniMaxM2 model."""
25
26 from collections.abc import Iterable
27 from itertools import islice
28 from typing import Any
29
30 import torch
31 from torch import nn
32 from transformers import PreTrainedConfig
33
34 from vllm.compilation.decorators import support_torch_compile
35 from vllm.config import CacheConfig, ModelConfig, VllmConfig
36 from vllm.distributed import (
37 get_pp_group,
38 get_tensor_model_parallel_rank,
39 get_tensor_model_parallel_world_size,
40 )
41 from vllm.model_executor.layers.attention import Attention
42 from vllm.model_executor.layers.fused_moe import FusedMoEFactory
43 from vllm.model_executor.layers.fused_moe.router.gate_linear import GateLinear
44 from vllm.model_executor.layers.layernorm import RMSNorm
45 from vllm.model_executor.layers.linear import (
46 QKVParallelLinear,
47 RowParallelLinear,
48 )
49 from vllm.model_executor.layers.logits_processor import LogitsProcessor
50 from vllm.model_executor.layers.minimax_rms_norm import MiniMaxText01RMSNormTP
51 from vllm.model_executor.layers.quantization import QuantizationConfig
52 from vllm.model_executor.layers.rotary_embedding import get_rope
53 from vllm.model_executor.layers.vocab_parallel_embedding import (
54 ParallelLMHead,
55 VocabParallelEmbedding,
56 )
57 from vllm.sequence import IntermediateTensors
58
59 from .interfaces import EagleModelMixin, SupportsEagle3, SupportsLoRA, SupportsPP
60 from .utils import (
61 AutoWeightsLoader,
62 PPMissingLayer,
63 WeightsMapper,
64 make_empty_intermediate_tensors_factory,
65 make_layers,
66 maybe_prefix,
67 )
68
69
70 class MiniMaxM2MoE(nn.Module):
71 def __init__(
72 self,
73 config: PreTrainedConfig,
74 quant_config: QuantizationConfig | None = None,
75 prefix: str = "",
76 ):
77 super().__init__()
78 self.tp_size = get_tensor_model_parallel_world_size()
79
80 if self.tp_size > config.num_local_experts:
81 raise ValueError(
82 f"Tensor parallel size {self.tp_size} is greater than "
83 f"the number of experts {config.num_local_experts}."
84 )
85 self.use_routing_bias = getattr(config, "use_routing_bias", False)
86 if self.use_routing_bias:
87 self.e_score_correction_bias = nn.Parameter(
88 torch.empty(config.num_local_experts, dtype=torch.float32)
89 )
90 self.e_score_correction_bias.weight_loader = (
91 MiniMaxM2MoE.ebias_weight_loader
92 )
93 else:
94 self.e_score_correction_bias = None
95
96 self.experts = FusedMoEFactory(
97 num_experts=config.num_local_experts,
98 top_k=config.num_experts_per_tok,
99 scoring_func=config.scoring_func,
100 e_score_correction_bias=self.e_score_correction_bias,
101 hidden_size=config.hidden_size,
102 intermediate_size=config.intermediate_size,
103 renormalize=True,
104 quant_config=quant_config,
105 prefix=f"{prefix}.experts",
106 router_logits_dtype=torch.float32,
107 ckpt_names=("w1", "w2", "w3"),
108 )
109
110 self.gate = GateLinear(
111 config.hidden_size,
112 config.num_local_experts,
113 bias=False,
114 params_dtype=torch.float32,
115 out_dtype=torch.float32,
116 prefix=f"{prefix}.gate",
117 )
118
119 @staticmethod
120 def ebias_weight_loader(param: nn.Parameter, loaded_weight: torch.Tensor) -> None:
121 assert param.size() == loaded_weight.size()
122 param.data.copy_(loaded_weight.to(torch.float32))
123
124 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
125 num_tokens, hidden_dim = hidden_states.shape
126 hidden_states = hidden_states.view(-1, hidden_dim)
127
128 # router_logits: (num_tokens, n_experts)
129 router_logits, _ = self.gate(hidden_states)
130 final_hidden_states = self.experts(
131 hidden_states=hidden_states, router_logits=router_logits
132 )
133
134 return final_hidden_states.view(num_tokens, hidden_dim)
135
136
137 class MiniMaxM2Attention(nn.Module):
138 def __init__(
139 self,
140 hidden_size: int,
141 num_heads: int,
142 num_kv_heads: int,
143 rotary_dim: int,
144 rope_parameters: dict[str, Any] | None = None,
145 attn_window_size: int | None = None,
146 max_position_embeddings: int = 8192,
147 head_dim: int | None = None,
148 rms_norm_eps: float = 1e-06,
149 qkv_bias: bool = False,
150 cache_config: CacheConfig | None = None,
151 quant_config: QuantizationConfig | None = None,
152 prefix: str = "",
153 ) -> None:
154 super().__init__()
155 self.hidden_size = hidden_size
156 tp_size = get_tensor_model_parallel_world_size()
157 self.total_num_heads = num_heads
158 assert self.total_num_heads % tp_size == 0
159 self.num_heads = self.total_num_heads // tp_size
160 self.total_num_kv_heads = num_kv_heads
161 if self.total_num_kv_heads >= tp_size:
162 # Number of KV heads is greater than TP size, so we partition
163 # the KV heads across multiple tensor parallel GPUs.
164 assert self.total_num_kv_heads % tp_size == 0
165 else:
166 # Number of KV heads is less than TP size, so we replicate
167 # the KV heads across multiple tensor parallel GPUs.
168 assert tp_size % self.total_num_kv_heads == 0
169 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
170 self.head_dim = head_dim or (hidden_size // self.total_num_heads)
171 self.q_size = self.num_heads * self.head_dim
172 self.kv_size = self.num_kv_heads * self.head_dim
173 self.scaling = self.head_dim**-0.5
174 self.max_position_embeddings = max_position_embeddings
175
176 self.qkv_proj = QKVParallelLinear(
177 hidden_size,
178 self.head_dim,
179 self.total_num_heads,
180 self.total_num_kv_heads,
181 bias=qkv_bias,
182 quant_config=quant_config,
183 prefix=f"{prefix}.qkv_proj",
184 )
185
186 self.o_proj = RowParallelLinear(
187 self.total_num_heads * self.head_dim,
188 hidden_size,
189 bias=False,
190 quant_config=quant_config,
191 prefix=f"{prefix}.o_proj",
192 )
193
194 if (
195 rope_parameters is not None
196 and "partial_rotary_factor" not in rope_parameters
197 ):
198 rope_parameters["partial_rotary_factor"] = rotary_dim / self.head_dim
199 self.rotary_emb = get_rope(
200 self.head_dim,
201 max_position=max_position_embeddings,
202 rope_parameters=rope_parameters,
203 )
204 self.attn = Attention(
205 self.num_heads,
206 self.head_dim,
207 self.scaling,
208 num_kv_heads=self.num_kv_heads,
209 per_layer_sliding_window=attn_window_size,
210 cache_config=cache_config,
211 quant_config=quant_config,
212 prefix=f"{prefix}.attn",
213 )
214
215 self.q_norm = MiniMaxText01RMSNormTP(
216 self.head_dim * self.total_num_heads, eps=rms_norm_eps
217 )
218 if self.total_num_kv_heads >= tp_size:
219 self.k_norm = MiniMaxText01RMSNormTP(
220 self.head_dim * self.total_num_kv_heads, eps=rms_norm_eps
221 )
222 else:
223 # KV heads are replicated across TP ranks; shard k_norm weight by
224 # total_num_kv_heads rather than tp_size to avoid incorrect sharding.
225 num_kv_head_replicas = tp_size // self.total_num_kv_heads
226 self.k_norm = MiniMaxText01RMSNormTP(
227 self.head_dim * self.total_num_kv_heads,
228 eps=rms_norm_eps,
229 weight_shard_world_size=self.total_num_kv_heads,
230 weight_shard_rank=get_tensor_model_parallel_rank()
231 // num_kv_head_replicas,
232 )
233
234 def forward(
235 self,
236 positions: torch.Tensor,
237 hidden_states: torch.Tensor,
238 ) -> torch.Tensor:
239 qkv, _ = self.qkv_proj(hidden_states)
240 q, k, v = MiniMaxText01RMSNormTP.forward_qkv(
241 self.q_norm, self.k_norm, qkv, self.q_size, self.kv_size
242 )
243 q, k = self.rotary_emb(positions, q, k)
244 attn_output = self.attn(q, k, v)
245 output, _ = self.o_proj(attn_output)
246 return output
247
248
249 class MiniMaxM2DecoderLayer(nn.Module):
250 def __init__(
251 self,
252 config: PreTrainedConfig,
253 prefix: str,
254 model_config: ModelConfig,
255 cache_config: CacheConfig | None = None,
256 quant_config: QuantizationConfig | None = None,
257 ) -> None:
258 super().__init__()
259 self.hidden_size = config.hidden_size
260 max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
261 if hasattr(config, "max_model_len") and isinstance(config.max_model_len, int):
262 max_position_embeddings = max(
263 config.max_position_embeddings, config.max_model_len
264 )
265 # DecoderLayers are created with `make_layers` which passes the prefix
266 # with the layer's index.
267 layer_idx = int(prefix.split(sep=".")[-1])
268
269 self.layer_idx = layer_idx
270 self.self_attn = MiniMaxM2Attention(
271 hidden_size=self.hidden_size,
272 num_heads=config.num_attention_heads,
273 num_kv_heads=config.num_key_value_heads,
274 rotary_dim=config.rotary_dim,
275 rope_parameters=config.rope_parameters,
276 max_position_embeddings=max_position_embeddings,
277 rms_norm_eps=config.rms_norm_eps,
278 qkv_bias=getattr(config, "attention_bias", False),
279 head_dim=getattr(config, "head_dim", None),
280 cache_config=cache_config,
281 quant_config=quant_config,
282 prefix=f"{prefix}.self_attn",
283 )
284
285 self.block_sparse_moe = MiniMaxM2MoE(
286 config=config,
287 quant_config=quant_config,
288 prefix=f"{prefix}.mlp",
289 )
290 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
291 self.post_attention_layernorm = RMSNorm(
292 config.hidden_size, eps=config.rms_norm_eps
293 )
294
295 def forward(
296 self,
297 positions: torch.Tensor,
298 hidden_states: torch.Tensor,
299 residual: torch.Tensor | None,
300 ) -> torch.Tensor:
301 # Self Attention
302 if residual is None:
303 residual = hidden_states
304 hidden_states = self.input_layernorm(hidden_states)
305 else:
306 hidden_states, residual = self.input_layernorm(hidden_states, residual)
307 hidden_states = self.self_attn(
308 positions=positions,
309 hidden_states=hidden_states,
310 )
311
312 # Fully Connected
313 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
314
315 hidden_states = self.block_sparse_moe(hidden_states)
316
317 return hidden_states, residual
318
319
320 @support_torch_compile
321 class MiniMaxM2Model(nn.Module, EagleModelMixin):
322 fall_back_to_pt_during_load = False
323
324 hf_to_vllm_mapper = WeightsMapper(
325 orig_to_new_stacked={
326 # weight_name: (param_name, shard_id)
327 ".q_proj": (".qkv_proj", "q"),
328 ".k_proj": (".qkv_proj", "k"),
329 ".v_proj": (".qkv_proj", "v"),
330 }
331 )
332
333 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
334 super().__init__()
335
336 config = vllm_config.model_config.hf_config
337 model_config = vllm_config.model_config
338 cache_config = vllm_config.cache_config
339 quant_config = vllm_config.quant_config
340 self.config = config
341
342 self.vocab_size = config.vocab_size
343
344 if get_pp_group().is_first_rank:
345 self.embed_tokens = VocabParallelEmbedding(
346 config.vocab_size,
347 config.hidden_size,
348 quant_config=quant_config,
349 prefix=f"{prefix}.embed_tokens",
350 )
351 else:
352 self.embed_tokens = PPMissingLayer()
353
354 self.start_layer, self.end_layer, self.layers = make_layers(
355 config.num_hidden_layers,
356 lambda prefix: MiniMaxM2DecoderLayer(
357 config,
358 prefix,
359 model_config=model_config,
360 cache_config=cache_config,
361 quant_config=quant_config,
362 ),
363 prefix=f"{prefix}.layers",
364 )
365
366 if get_pp_group().is_last_rank:
367 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
368 else:
369 self.norm = PPMissingLayer()
370 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
371 ["hidden_states", "residual"], config.hidden_size
372 )
373
374 # Drop spec-decode (MTP) layers; they are appended after the main
375 # decoder layers and have no destination in the main model.
376 if num_mtp := getattr(config, "num_mtp_modules", 0):
377 base = config.num_hidden_layers
378 self.hf_to_vllm_mapper = self.hf_to_vllm_mapper | WeightsMapper(
379 orig_to_new_prefix={f"layers.{base + i}.": None for i in range(num_mtp)}
380 )
381
382 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
383 return self.embed_tokens(input_ids)
384
385 def forward(
386 self,
387 input_ids: torch.Tensor | None,
388 positions: torch.Tensor,
389 intermediate_tensors: IntermediateTensors | None,
390 inputs_embeds: torch.Tensor | None = None,
391 ) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
392 if get_pp_group().is_first_rank:
393 if inputs_embeds is not None:
394 hidden_states = inputs_embeds
395 else:
396 hidden_states = self.embed_input_ids(input_ids)
397 residual = None
398 else:
399 assert intermediate_tensors is not None
400 hidden_states = intermediate_tensors["hidden_states"]
401 residual = intermediate_tensors["residual"]
402
403 aux_hidden_states = self._maybe_add_hidden_state([], 0, hidden_states, residual)
404 for idx, layer in enumerate(
405 islice(self.layers, self.start_layer, self.end_layer)
406 ):
407 hidden_states, residual = layer(positions, hidden_states, residual)
408 self._maybe_add_hidden_state(
409 aux_hidden_states, idx + 1, hidden_states, residual
410 )
411
412 if not get_pp_group().is_last_rank:
413 return IntermediateTensors(
414 {"hidden_states": hidden_states, "residual": residual}
415 )
416 hidden_states, _ = self.norm(hidden_states, residual)
417
418 if len(aux_hidden_states) > 0:
419 return hidden_states, aux_hidden_states
420
421 return hidden_states
422
423 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
424 loader = AutoWeightsLoader(self)
425 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
426
427
428 class MiniMaxM2ForCausalLM(nn.Module, SupportsLoRA, SupportsPP, SupportsEagle3):
429 hf_to_vllm_mapper = MiniMaxM2Model.hf_to_vllm_mapper
430 packed_modules_mapping = {
431 "qkv_proj": [
432 "q_proj",
433 "k_proj",
434 "v_proj",
435 ],
436 }
437
438 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
439 super().__init__()
440 config = vllm_config.model_config.hf_config
441 quant_config = vllm_config.quant_config
442 self.config = config
443 self.quant_config = quant_config
444 if hasattr(vllm_config.model_config, "max_model_len"):
445 self.config.max_model_len = vllm_config.model_config.max_model_len
446 self.model = MiniMaxM2Model(
447 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
448 )
449 if get_pp_group().is_last_rank:
450 self.lm_head = ParallelLMHead(
451 config.vocab_size,
452 config.hidden_size,
453 quant_config=quant_config,
454 prefix=maybe_prefix(prefix, "lm_head"),
455 )
456 else:
457 self.lm_head = PPMissingLayer()
458 self.logits_processor = LogitsProcessor(config.vocab_size)
459 self.make_empty_intermediate_tensors = (
460 self.model.make_empty_intermediate_tensors
461 )
462
463 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
464 return self.model.embed_input_ids(input_ids)
465
466 def forward(
467 self,
468 input_ids: torch.Tensor | None,
469 positions: torch.Tensor,
470 intermediate_tensors: IntermediateTensors | None = None,
471 inputs_embeds: torch.Tensor | None = None,
472 **kwargs,
473 ) -> torch.Tensor | IntermediateTensors:
474 hidden_states = self.model(
475 input_ids, positions, intermediate_tensors, inputs_embeds
476 )
477 return hidden_states
478
479 def compute_logits(
480 self,
481 hidden_states: torch.Tensor,
482 ) -> torch.Tensor | None:
483 logits = self.logits_processor(self.lm_head, hidden_states)
484 return logits
485
486 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
487 loader = AutoWeightsLoader(self)
488 return loader.load_weights(weights)