main
py 329 lines 12.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from collections.abc import Iterable
5
6 import regex as re
7 import torch
8 import torch.nn as nn
9
10 from vllm.config import VllmConfig
11 from vllm.distributed import get_pp_group
12 from vllm.model_executor.layers.fused_moe import (
13 fused_moe_make_expert_params_mapping,
14 )
15 from vllm.model_executor.layers.linear import (
16 ReplicatedLinear,
17 )
18 from vllm.model_executor.layers.logits_processor import LogitsProcessor
19 from vllm.model_executor.layers.vocab_parallel_embedding import (
20 ParallelLMHead,
21 VocabParallelEmbedding,
22 )
23 from vllm.model_executor.model_loader.mtp_validation import (
24 is_mtp_completeness_check_enabled,
25 )
26 from vllm.model_executor.model_loader.weight_utils import (
27 default_weight_loader,
28 maybe_remap_kv_scale_name,
29 )
30 from vllm.model_executor.models.utils import (
31 maybe_prefix,
32 )
33 from vllm.sequence import IntermediateTensors
34
35 from .model import (
36 MiniMAXGemmaRMSNorm,
37 MiniMaxM3DecoderLayer,
38 )
39
40
41 class MiniMaxM3MultiTokenPredictorLayer(nn.Module):
42 def __init__(self, vllm_config: VllmConfig, prefix: str) -> None:
43 super().__init__()
44
45 assert vllm_config.speculative_config is not None
46 config = vllm_config.speculative_config.draft_model_config.hf_config
47 quant_config = vllm_config.quant_config
48
49 self.enorm = MiniMAXGemmaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
50 self.hnorm = MiniMAXGemmaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
51 self.eh_proj = ReplicatedLinear(
52 config.hidden_size * 2,
53 config.hidden_size,
54 bias=False,
55 quant_config=quant_config,
56 prefix=f"{prefix}.eh_proj",
57 )
58 self.transformer_layer = MiniMaxM3DecoderLayer(
59 vllm_config=vllm_config,
60 prefix=prefix,
61 force_sparse_attn=True,
62 force_moe=True,
63 is_mtp_block=True,
64 )
65 self.final_layernorm = MiniMAXGemmaRMSNorm(
66 config.hidden_size, eps=config.rms_norm_eps
67 )
68
69 def forward(
70 self,
71 input_ids: torch.Tensor,
72 positions: torch.Tensor,
73 previous_hidden_states: torch.Tensor,
74 inputs_embeds: torch.Tensor | None = None,
75 spec_step_index: int = 0,
76 ) -> torch.Tensor:
77 assert inputs_embeds is not None
78 # Mask out inputs at position 0, as not needed by MTP.
79 inputs_embeds = torch.where(positions.unsqueeze(-1) == 0, 0, inputs_embeds)
80
81 # Combine the normalized token embeddings with the normalized
82 # previous hidden states.
83 inputs_embeds = self.enorm(inputs_embeds)
84 previous_hidden_states = self.hnorm(previous_hidden_states)
85 hidden_states, _ = self.eh_proj(
86 torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
87 )
88
89 # Apply transformer layer.
90 hidden_states, residual = self.transformer_layer(
91 positions=positions,
92 hidden_states=hidden_states,
93 residual=None,
94 )
95
96 hidden_states += residual
97 return hidden_states
98
99
100 class MiniMaxM3MultiTokenPredictor(nn.Module):
101 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
102 super().__init__()
103
104 assert vllm_config.speculative_config is not None
105 # Use the draft (MTP) config, not the target model's. This is flat for a
106 # standalone checkpoint, and the promoted text_config for a bundled one.
107 config = vllm_config.speculative_config.draft_model_config.hf_config
108 self.num_mtp_layers = config.num_mtp_modules
109 self.layers = torch.nn.ModuleDict(
110 {
111 str(idx): MiniMaxM3MultiTokenPredictorLayer(
112 vllm_config, f"{prefix}.layers.{idx}"
113 )
114 for idx in range(self.num_mtp_layers)
115 }
116 )
117 self.embed_tokens = VocabParallelEmbedding(
118 config.vocab_size,
119 config.hidden_size,
120 prefix=maybe_prefix(prefix, "embed_tokens"),
121 )
122
123 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
124 return self.embed_tokens(input_ids)
125
126 def forward(
127 self,
128 input_ids: torch.Tensor,
129 positions: torch.Tensor,
130 previous_hidden_states: torch.Tensor,
131 inputs_embeds: torch.Tensor | None = None,
132 spec_step_idx: int = 0,
133 ) -> torch.Tensor:
134 if inputs_embeds is None:
135 inputs_embeds = self.embed_tokens(input_ids)
136 current_step_idx = spec_step_idx % self.num_mtp_layers
137 return self.layers[str(current_step_idx)](
138 input_ids,
139 positions,
140 previous_hidden_states,
141 inputs_embeds,
142 current_step_idx,
143 )
144
145
146 class MiniMaxM3MTP(nn.Module):
147 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
148 super().__init__()
149
150 if vllm_config.use_v2_model_runner and get_pp_group().world_size > 1:
151 # Preserve embeddings from loaders that bypass load_weights.
152 self.has_own_embed_tokens = vllm_config.load_config.load_format != "dummy"
153
154 assert vllm_config.speculative_config is not None
155 self.config = vllm_config.speculative_config.draft_model_config.hf_config
156 self.quant_config = vllm_config.quant_config
157 self.model = MiniMaxM3MultiTokenPredictor(
158 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
159 )
160 self.lm_head = ParallelLMHead(
161 self.config.vocab_size,
162 self.config.hidden_size,
163 quant_config=self.quant_config,
164 prefix=maybe_prefix(prefix, "lm_head"),
165 )
166 self.logits_processor = LogitsProcessor(self.config.vocab_size)
167
168 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
169 return self.model.embed_input_ids(input_ids)
170
171 def forward(
172 self,
173 input_ids: torch.Tensor | None,
174 positions: torch.Tensor,
175 hidden_states: torch.Tensor,
176 intermediate_tensors: IntermediateTensors | None = None,
177 inputs_embeds: torch.Tensor | None = None,
178 spec_step_idx: int = 0,
179 ) -> torch.Tensor:
180 return self.model(
181 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
182 )
183
184 def compute_logits(
185 self,
186 hidden_states: torch.Tensor,
187 spec_step_idx: int = 0,
188 ) -> torch.Tensor | None:
189 current_step_idx = spec_step_idx % self.model.num_mtp_layers
190 mtp_layer = self.model.layers[str(current_step_idx)]
191 return self.logits_processor(
192 self.lm_head, mtp_layer.final_layernorm(hidden_states)
193 )
194
195 def _get_mtp_layer_idx_from_weight_name(self, name: str) -> int | None:
196 """Return the MTP layer index in *.mtp.layers.{idx}.*, else None."""
197 match = re.search(r"\.mtp\.layers\.(\d+)\.", name)
198 return int(match.group(1)) if match else None
199
200 def _map_checkpoint_name(self, name: str) -> str | None:
201 """Map a full checkpoint key to this MTP module's parameter name.
202
203 The MTP module only owns the *.mtp.layers.* weights plus the token
204 embedding and LM head, which the checkpoint shares with the main model.
205 Everything else belongs to other modules and is ignored here by returning
206 None.
207 """
208 # In the bundled checkpoint, the MTP weights are prefixed with
209 # "language_model". The standalone MTP checkpoint has no such prefix.
210 # Strip it if present.
211 name = name.removeprefix("language_model.")
212
213 if name == "model.embed_tokens.weight":
214 return "model.embed_tokens.weight"
215 if name == "lm_head.weight":
216 return "lm_head.weight"
217 if "model.mtp.layers" in name:
218 if "weight_scale_inv" in name:
219 # The checkpoint stores block scales as "weight_scale_inv".
220 # The ModelOpt MXFP8 layers expose them as "weight_scale".
221 name = name.replace("weight_scale_inv", "weight_scale")
222 # Strip "mtp" from prefix.
223 return name.replace(".mtp.", ".")
224 return None
225
226 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
227 # Map q/k/v projections to qkv_proj, and gate/up projections to gate_up_proj.
228 stacked_params_mapping: list[tuple[str, str, int | str]] = [
229 # (param_name, shard_name, shard_id)
230 (".qkv_proj", ".q_proj", "q"),
231 (".qkv_proj", ".k_proj", "k"),
232 (".qkv_proj", ".v_proj", "v"),
233 (".qkv_proj", ".index_q_proj", "index_q"),
234 (".qkv_proj", ".index_k_proj", "index_k"),
235 (".gate_up_proj", ".gate_proj", 0),
236 (".gate_up_proj", ".up_proj", 1),
237 ]
238
239 # Map expert weights w1/w2/w3 to gate/down/up.
240 # (param_name, weight_name, expert_id, shard_id)
241 expert_params_mapping = fused_moe_make_expert_params_mapping(
242 self,
243 ckpt_gate_proj_name="w1",
244 ckpt_down_proj_name="w2",
245 ckpt_up_proj_name="w3",
246 num_experts=self.config.num_local_experts,
247 )
248
249 params_dict = dict(self.named_parameters())
250 loaded_params: set[str] = set()
251 loaded_mtp_layers: set[int] = set()
252 for name, loaded_weight in weights:
253 mtp_layer = self._get_mtp_layer_idx_from_weight_name(name)
254 mapped_name = self._map_checkpoint_name(name)
255 if mapped_name is None:
256 # This weight does not belong to the MTP module, so skip it.
257 continue
258 name = mapped_name
259
260 for param_name, weight_name, shard_id in stacked_params_mapping:
261 if weight_name not in name:
262 continue
263
264 # Routed experts (w1/w2/w3) are handled below. Don't let the
265 # stacked mapping rewrite them.
266 if ("block_sparse_moe.experts." in name) and name not in params_dict:
267 continue
268 name = name.replace(weight_name, param_name)
269 if name not in params_dict:
270 continue
271 param = params_dict[name]
272 weight_loader = param.weight_loader
273 weight_loader(param, loaded_weight, shard_id)
274 break
275 else:
276 for (
277 param_name,
278 weight_name,
279 expert_id,
280 expert_shard_id,
281 ) in expert_params_mapping:
282 if weight_name not in name:
283 continue
284
285 name = name.replace(weight_name, param_name)
286 if name not in params_dict:
287 continue
288 param = params_dict[name]
289 weight_loader = param.weight_loader
290 weight_loader(
291 param,
292 loaded_weight,
293 name,
294 shard_id=expert_shard_id,
295 expert_id=expert_id,
296 )
297 break
298 else:
299 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
300 if remapped_name is None or remapped_name not in params_dict:
301 continue
302 name = remapped_name
303 param = params_dict[name]
304 weight_loader = getattr(
305 param, "weight_loader", default_weight_loader
306 )
307 weight_loader(param, loaded_weight)
308
309 loaded_params.add(name)
310 if mtp_layer is not None:
311 loaded_mtp_layers.add(mtp_layer)
312
313 # Validate that weights were loaded for each MTP layer.
314 for layer_idx in range(self.model.num_mtp_layers):
315 if (
316 layer_idx not in loaded_mtp_layers
317 and is_mtp_completeness_check_enabled()
318 ):
319 raise ValueError(
320 f"Failed to load MTP layer {layer_idx} weights from checkpoint."
321 )
322
323 if hasattr(self, "has_own_embed_tokens"):
324 if "model.embed_tokens.weight" in loaded_params:
325 self.has_own_embed_tokens = True
326 elif is_mtp_completeness_check_enabled():
327 # A complete checkpoint without embeddings borrows the target's.
328 self.has_own_embed_tokens = False
329 return loaded_params