main
py 443 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 ZhipuAI 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 GLM-4.5, GLM-4.6, GLM-4.7 MTP
25 model compatible with HuggingFace weights."""
26
27 import typing
28 from collections.abc import Callable, Iterable
29
30 import torch
31 import torch.nn as nn
32 from transformers import PreTrainedConfig
33
34 from vllm.config import CacheConfig, ParallelConfig, VllmConfig
35 from vllm.model_executor.layers.fused_moe import (
36 MoERunner,
37 fused_moe_make_expert_params_mapping,
38 )
39 from vllm.model_executor.layers.fused_moe.utils import (
40 is_model_fused_shared_expert_compatible,
41 )
42 from vllm.model_executor.layers.layernorm import RMSNorm
43 from vllm.model_executor.layers.logits_processor import LogitsProcessor
44 from vllm.model_executor.layers.quantization import QuantizationConfig
45 from vllm.model_executor.layers.vocab_parallel_embedding import (
46 ParallelLMHead,
47 VocabParallelEmbedding,
48 )
49 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
50 from vllm.sequence import IntermediateTensors
51
52 from .glm4_moe import (
53 Glm4MixtureOfExperts,
54 Glm4MoE,
55 Glm4MoeDecoderLayer,
56 )
57 from .utils import get_spec_layer_idx_from_weight_name, maybe_prefix
58
59
60 class SharedHead(nn.Module):
61 def __init__(
62 self,
63 config: PreTrainedConfig,
64 prefix: str,
65 quant_config: QuantizationConfig | None = None,
66 ) -> None:
67 super().__init__()
68 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
69 self.head = ParallelLMHead(
70 config.vocab_size,
71 config.hidden_size,
72 quant_config=quant_config,
73 prefix=maybe_prefix(prefix, "head"),
74 )
75
76 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
77 return self.norm(hidden_states)
78
79
80 class Glm4MoeMultiTokenPredictorLayer(nn.Module):
81 def __init__(
82 self,
83 config: PreTrainedConfig,
84 prefix: str,
85 cache_config: CacheConfig | None = None,
86 quant_config: QuantizationConfig | None = None,
87 parallel_config: ParallelConfig | None = None,
88 ) -> None:
89 super().__init__()
90 self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
91 self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
92 self.eh_proj = nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False)
93 self.shared_head = SharedHead(
94 config=config, prefix=prefix, quant_config=quant_config
95 )
96 assert parallel_config is not None
97 self.enable_eplb = parallel_config.enable_eplb
98 self.mtp_block = Glm4MoeDecoderLayer(
99 config=config,
100 cache_config=cache_config,
101 quant_config=quant_config,
102 prefix=prefix,
103 enable_eplb=self.enable_eplb,
104 )
105
106 def forward(
107 self,
108 input_ids: torch.Tensor,
109 positions: torch.Tensor,
110 previous_hidden_states: torch.Tensor,
111 inputs_embeds: torch.Tensor | None = None,
112 spec_step_index: int = 0,
113 ) -> torch.Tensor:
114 assert inputs_embeds is not None
115 # masking inputs at position 0, as not needed by MTP
116 inputs_embeds = torch.where(positions.unsqueeze(-1) == 0, 0, inputs_embeds)
117 inputs_embeds = self.enorm(inputs_embeds)
118 previous_hidden_states = self.hnorm(previous_hidden_states)
119
120 hidden_states = self.eh_proj(
121 torch.cat([inputs_embeds, previous_hidden_states], dim=-1)
122 )
123
124 hidden_states, residual = self.mtp_block(
125 positions=positions, hidden_states=hidden_states, residual=None
126 )
127 hidden_states = residual + hidden_states
128 return hidden_states
129
130
131 class Glm4MoeMultiTokenPredictor(nn.Module):
132 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
133 super().__init__()
134 config = vllm_config.model_config.hf_config
135 self.mtp_start_layer_idx = config.num_hidden_layers
136 self.num_mtp_layers = config.num_nextn_predict_layers
137 # to map the exact layer index from weights
138 self.layers = torch.nn.ModuleDict(
139 {
140 str(idx): Glm4MoeMultiTokenPredictorLayer(
141 config,
142 f"{prefix}.layers.{idx}",
143 cache_config=vllm_config.cache_config,
144 quant_config=vllm_config.quant_config,
145 parallel_config=vllm_config.parallel_config,
146 )
147 for idx in range(
148 self.mtp_start_layer_idx,
149 self.mtp_start_layer_idx + self.num_mtp_layers,
150 )
151 }
152 )
153 self.embed_tokens = VocabParallelEmbedding(
154 config.vocab_size,
155 config.hidden_size,
156 )
157 self.logits_processor = LogitsProcessor(config.vocab_size)
158
159 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
160 return self.embed_tokens(input_ids)
161
162 def forward(
163 self,
164 input_ids: torch.Tensor,
165 positions: torch.Tensor,
166 previous_hidden_states: torch.Tensor,
167 inputs_embeds: torch.Tensor | None = None,
168 spec_step_idx: int = 0,
169 ) -> torch.Tensor:
170 if inputs_embeds is None:
171 inputs_embeds = self.embed_tokens(input_ids)
172 current_step_idx = spec_step_idx % self.num_mtp_layers
173 return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
174 input_ids,
175 positions,
176 previous_hidden_states,
177 inputs_embeds,
178 current_step_idx,
179 )
180
181 def compute_logits(
182 self,
183 hidden_states: torch.Tensor,
184 spec_step_idx: int = 0,
185 ) -> torch.Tensor:
186 current_step_idx = spec_step_idx % self.num_mtp_layers
187 mtp_layer = self.layers[str(self.mtp_start_layer_idx + current_step_idx)]
188 logits = self.logits_processor(
189 mtp_layer.shared_head.head, mtp_layer.shared_head(hidden_states)
190 )
191 return logits
192
193
194 class Glm4MoeMTP(nn.Module, Glm4MixtureOfExperts):
195 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
196 super().__init__()
197 self.config = vllm_config.model_config.hf_config
198 self.model = Glm4MoeMultiTokenPredictor(
199 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
200 )
201
202 # Set MoE hyperparameters
203 self.num_moe_layers = self.config.num_nextn_predict_layers
204 self.num_expert_groups = self.config.n_group
205
206 self.moe_layers: list[MoERunner] = []
207 self.moe_mlp_layers: list[Glm4MoE] = []
208 example_moe = None
209 for layer in self.model.layers.values():
210 assert isinstance(layer, Glm4MoeMultiTokenPredictorLayer)
211 layer = layer.mtp_block
212 assert isinstance(layer, Glm4MoeDecoderLayer)
213 if isinstance(layer.mlp, Glm4MoE):
214 example_moe = layer.mlp
215 self.moe_mlp_layers.append(layer.mlp)
216 self.moe_layers.append(layer.mlp.experts)
217 self.extract_moe_parameters(example_moe)
218 self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
219 self.model.layers.values(),
220 Glm4MoE,
221 "mtp_block.mlp",
222 )
223
224 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
225 return self.model.embed_input_ids(input_ids)
226
227 def forward(
228 self,
229 input_ids: torch.Tensor | None,
230 positions: torch.Tensor,
231 hidden_states: torch.Tensor,
232 intermediate_tensors: IntermediateTensors | None = None,
233 inputs_embeds: torch.Tensor | None = None,
234 spec_step_idx: int = 0,
235 ) -> torch.Tensor:
236 hidden_states = self.model(
237 input_ids, positions, hidden_states, inputs_embeds, spec_step_idx
238 )
239 return hidden_states
240
241 def compute_logits(
242 self,
243 hidden_states: torch.Tensor,
244 spec_step_idx: int = 0,
245 ) -> torch.Tensor | None:
246 return self.model.compute_logits(hidden_states, spec_step_idx)
247
248 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
249 stacked_params_mapping = [
250 # (param_name, shard_name, shard_id)
251 ("qkv_proj", "q_proj", "q"),
252 ("qkv_proj", "k_proj", "k"),
253 ("qkv_proj", "v_proj", "v"),
254 ("gate_up_proj", "gate_proj", 0),
255 ("gate_up_proj", "up_proj", 1),
256 ]
257
258 # Params for weights, fp8 weight scales, fp8 activation scales
259 # (param_name, weight_name, expert_id, shard_id)
260 num_experts = self.config.n_routed_experts
261 if self.is_fused_shared_expert_enabled and self.config.n_shared_experts:
262 num_experts += self.config.n_shared_experts
263 expert_params_mapping = fused_moe_make_expert_params_mapping(
264 self,
265 ckpt_gate_proj_name="gate_proj",
266 ckpt_down_proj_name="down_proj",
267 ckpt_up_proj_name="up_proj",
268 num_experts=num_experts,
269 )
270
271 params_dict = dict(self.named_parameters())
272 loaded_params: set[str] = set()
273 for name, loaded_weight in weights:
274 if name == "lm_head.weight":
275 spec_layer = self.model.mtp_start_layer_idx
276 name = f"model.layers.{spec_layer}.shared_head.head.weight"
277 elif name == "model.embed_tokens.weight":
278 spec_layer = self.model.mtp_start_layer_idx
279 else:
280 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
281 if spec_layer is None:
282 continue
283 name = self._rewrite_spec_layer_name(spec_layer, name)
284
285 is_fusion_moe_shared_experts_layer = (
286 self.is_fused_shared_expert_enabled and ("mlp.shared_experts" in name)
287 )
288
289 for param_name, weight_name, shard_id in stacked_params_mapping:
290 # Skip non-stacked layers and experts (experts handled below).
291 if weight_name not in name:
292 continue
293 # We have mlp.experts[0].gate_proj in the checkpoint.
294 # Since we handle the experts below in expert_params_mapping,
295 # we need to skip here BEFORE we update the name, otherwise
296 # name will be updated to mlp.experts[0].gate_up_proj, which
297 # will then be updated below in expert_params_mapping
298 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
299 if ("mlp.experts." in name) and name not in params_dict:
300 continue
301 if is_fusion_moe_shared_experts_layer:
302 continue
303 name = name.replace(weight_name, param_name)
304 # Skip loading extra bias for GPTQ models.
305 if name.endswith(".bias") and name not in params_dict:
306 continue
307
308 param = params_dict[name]
309 weight_loader = param.weight_loader
310 weight_loader(param, loaded_weight, shard_id)
311 break
312 else:
313 # FSE: split a widened mlp.shared_experts tensor into
314 # n_shared_experts chunks; see deepseek_v2.py for details.
315 num_chunks = 1
316 split_dim = 0
317 chunk_size = 0
318 if is_fusion_moe_shared_experts_layer:
319 num_chunks = getattr(self.config, "n_shared_experts", 1) or 1
320 split_dim = (
321 1
322 if ("down_proj.weight" in name and loaded_weight.ndim > 1)
323 else 0
324 )
325 total = loaded_weight.shape[split_dim]
326 if total % num_chunks != 0:
327 raise ValueError(
328 f"FSE shared-expert weight {name} has dim "
329 f"{total} along axis {split_dim} which is "
330 f"not divisible by "
331 f"n_shared_experts={num_chunks}."
332 )
333 chunk_size = total // num_chunks
334
335 for j in range(num_chunks):
336 chunk_name = name
337 weight_to_load = loaded_weight
338
339 if is_fusion_moe_shared_experts_layer:
340 chunk_slice = slice(j * chunk_size, (j + 1) * chunk_size)
341 if loaded_weight.ndim == 1:
342 weight_to_load = loaded_weight[chunk_slice]
343 elif split_dim == 0:
344 weight_to_load = loaded_weight[chunk_slice, :]
345 else:
346 weight_to_load = loaded_weight[:, chunk_slice]
347 chunk_name = name.replace(
348 "mlp.shared_experts",
349 f"mlp.experts.{self.config.n_routed_experts + j}",
350 )
351
352 is_expert_weight = False
353 for mapping in expert_params_mapping:
354 param_name, weight_name, expert_id, shard_id = mapping
355 if weight_name not in chunk_name:
356 continue
357
358 is_expert_weight = True
359 name_mapped = chunk_name.replace(weight_name, param_name)
360
361 param = params_dict[name_mapped]
362 # Use return_success so we don't blindly mark
363 # remote-expert replicas as loaded on this rank.
364 weight_loader = typing.cast(
365 Callable[..., bool], param.weight_loader
366 )
367 success = weight_loader(
368 param,
369 weight_to_load,
370 name_mapped,
371 shard_id=shard_id,
372 expert_id=expert_id,
373 return_success=True,
374 )
375 if success:
376 if not is_fusion_moe_shared_experts_layer:
377 name = name_mapped
378 else:
379 loaded_params.add(name_mapped)
380 break
381 else:
382 if is_expert_weight:
383 # Expert weight not local to this rank; skip.
384 continue
385
386 # Skip loading extra bias for GPTQ models.
387 if name.endswith(".bias") and name not in params_dict:
388 continue
389 # Some checkpoints include weight scale tensors for
390 # the LM head even when the quantized head isn't
391 # built. Skip them if the model does not expose a
392 # matching parameter to avoid KeyError during load.
393 if name.endswith(".weight_scale") and name not in params_dict:
394 continue
395
396 # According to DeepSeek-V3 Technical Report, MTP
397 # modules share the embedding layer. We only load
398 # the first weights.
399 if (
400 spec_layer != self.model.mtp_start_layer_idx
401 and ".layers" not in name
402 ):
403 continue
404
405 param = params_dict[name]
406 weight_loader = getattr(
407 param, "weight_loader", default_weight_loader
408 )
409 weight_loader(param, loaded_weight)
410 if not is_fusion_moe_shared_experts_layer:
411 loaded_params.add(name)
412 return loaded_params
413
414 def _rewrite_spec_layer_name(self, spec_layer: int, name: str) -> str:
415 """Rewrite the weight name to match the format of the original model.
416 Add .mtp_block for modules in transformer layer block for spec layer
417 and rename shared layer weights to be top level.
418 """
419 spec_layer_weight_names = [
420 "embed_tokens",
421 "enorm",
422 "hnorm",
423 "eh_proj",
424 "shared_head",
425 ]
426 shared_weight_names = ["embed_tokens"]
427 spec_layer_weight = False
428 shared_weight = False
429 for weight_name in spec_layer_weight_names:
430 if weight_name in name:
431 spec_layer_weight = True
432 if weight_name in shared_weight_names:
433 shared_weight = True
434 break
435 if not spec_layer_weight:
436 # treat rest weights as weights for transformer layer block
437 name = name.replace(
438 f"model.layers.{spec_layer}.", f"model.layers.{spec_layer}.mtp_block."
439 )
440 elif shared_weight:
441 # treat shared weights as top level weights
442 name = name.replace(f"model.layers.{spec_layer}.", "model.")
443 return name