main
py 656 lines 24.5 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.7-Flash model compatible with HuggingFace weights."""
25
26 import typing
27 from collections.abc import Callable, Iterable
28 from itertools import islice
29 from typing import TYPE_CHECKING
30
31 import torch
32 from torch import nn
33
34 if TYPE_CHECKING:
35 from transformers.models.glm4_moe_lite import Glm4MoeLiteConfig
36
37 from vllm.compilation.decorators import support_torch_compile
38 from vllm.config import VllmConfig
39 from vllm.distributed import (
40 get_pp_group,
41 )
42 from vllm.logger import init_logger
43 from vllm.model_executor.layers.fused_moe import (
44 fused_moe_make_expert_params_mapping,
45 )
46 from vllm.model_executor.layers.fused_moe.utils import (
47 is_model_fused_shared_expert_compatible,
48 )
49 from vllm.model_executor.layers.layernorm import RMSNorm
50 from vllm.model_executor.layers.logits_processor import LogitsProcessor
51 from vllm.model_executor.layers.vocab_parallel_embedding import (
52 ParallelLMHead,
53 VocabParallelEmbedding,
54 )
55 from vllm.model_executor.model_loader.weight_utils import (
56 default_weight_loader,
57 maybe_remap_kv_scale_name,
58 )
59 from vllm.model_executor.models.deepseek_v2 import (
60 DeepseekV2Attention,
61 DeepseekV2MLAAttention,
62 )
63 from vllm.model_executor.models.glm4_moe import (
64 Glm4MixtureOfExperts,
65 Glm4MoE,
66 Glm4MoeMLP,
67 )
68 from vllm.platforms import current_platform
69 from vllm.sequence import IntermediateTensors
70 from vllm.v1.attention.backends.mla.index_group import (
71 SparseMLAIndexGroupBuilder,
72 get_sparse_mla_index_group_max_rows,
73 )
74
75 from .interfaces import SupportsLoRA, SupportsPP
76 from .utils import (
77 AutoWeightsLoader,
78 PPMissingLayer,
79 get_spec_layer_idx_from_weight_name,
80 is_pp_missing_parameter,
81 make_layers,
82 maybe_prefix,
83 )
84
85 logger = init_logger(__name__)
86
87
88 class Glm4MoeLiteMLP(Glm4MoeMLP):
89 pass
90
91
92 class Glm4MoeLite(Glm4MoE):
93 pass
94
95
96 class Glm4LiteMixtureOfExperts(Glm4MixtureOfExperts):
97 pass
98
99
100 class Glm4MoeLiteAttention(DeepseekV2Attention):
101 pass
102
103
104 class Glm4MoeLiteMLAAttention(DeepseekV2MLAAttention):
105 pass
106
107
108 class Glm4MoeLiteDecoderLayer(nn.Module):
109 def __init__(
110 self,
111 vllm_config: VllmConfig,
112 prefix: str,
113 config: "Glm4MoeLiteConfig | None" = None,
114 topk_indices_buffer: torch.Tensor | None = None,
115 index_group_builder: SparseMLAIndexGroupBuilder | None = None,
116 ) -> None:
117 super().__init__()
118
119 if config is None:
120 config = vllm_config.model_config.hf_config
121 model_config = vllm_config.model_config
122 cache_config = vllm_config.cache_config
123 quant_config = vllm_config.quant_config
124
125 self.hidden_size = config.hidden_size
126 max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
127 moe_layer_freq = getattr(config, "moe_layer_freq", 1)
128 # DecoderLayers are created with `make_layers` which passes the prefix
129 # with the layer's index.
130 layer_idx = int(prefix.split(sep=".")[-1])
131 self.layer_idx = layer_idx
132
133 # verify MLA attention specific fields
134 qk_nope_head_dim = getattr(config, "qk_nope_head_dim", 0)
135 qk_rope_head_dim = getattr(config, "qk_rope_head_dim", 0)
136 v_head_dim = getattr(config, "v_head_dim", 0)
137 kv_lora_rank = getattr(config, "kv_lora_rank", 0)
138
139 attn_cls: Callable[..., Glm4MoeLiteMLAAttention | Glm4MoeLiteAttention]
140 if model_config.use_mla:
141 attn_cls = Glm4MoeLiteMLAAttention
142 else:
143 attn_cls = Glm4MoeLiteAttention
144 attn_kwargs: dict[str, typing.Any] = (
145 {"index_group_builder": index_group_builder}
146 if attn_cls is Glm4MoeLiteMLAAttention
147 else {}
148 )
149
150 self.self_attn = attn_cls(
151 vllm_config=vllm_config,
152 config=config,
153 hidden_size=self.hidden_size,
154 num_heads=config.num_attention_heads,
155 qk_nope_head_dim=qk_nope_head_dim,
156 qk_rope_head_dim=qk_rope_head_dim,
157 v_head_dim=v_head_dim,
158 q_lora_rank=getattr(config, "q_lora_rank", None),
159 kv_lora_rank=kv_lora_rank,
160 max_position_embeddings=max_position_embeddings,
161 cache_config=cache_config,
162 quant_config=quant_config,
163 prefix=f"{prefix}.self_attn",
164 topk_indices_buffer=topk_indices_buffer,
165 **attn_kwargs,
166 )
167
168 if (
169 config.n_routed_experts is not None
170 and layer_idx >= config.first_k_dense_replace
171 and layer_idx % moe_layer_freq == 0
172 ):
173 self.mlp = Glm4MoeLite(
174 config=config,
175 quant_config=quant_config,
176 prefix=f"{prefix}.mlp",
177 )
178 else:
179 self.mlp = Glm4MoeLiteMLP(
180 hidden_size=config.hidden_size,
181 intermediate_size=config.intermediate_size,
182 hidden_act=config.hidden_act,
183 quant_config=quant_config,
184 prefix=f"{prefix}.mlp",
185 )
186 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
187 self.post_attention_layernorm = RMSNorm(
188 config.hidden_size, eps=config.rms_norm_eps
189 )
190 self.routed_scaling_factor = getattr(config, "routed_scaling_factor", 1.0)
191
192 def forward(
193 self,
194 positions: torch.Tensor,
195 hidden_states: torch.Tensor,
196 residual: torch.Tensor | None,
197 llama_4_scaling: torch.Tensor | None = None,
198 ) -> torch.Tensor:
199 # Self Attention
200 if residual is None:
201 residual = hidden_states
202 hidden_states = self.input_layernorm(hidden_states)
203 else:
204 hidden_states, residual = self.input_layernorm(hidden_states, residual)
205
206 attn_kwargs = {
207 "positions": positions,
208 "hidden_states": hidden_states,
209 }
210 attn_kwargs["llama_4_scaling"] = llama_4_scaling
211 hidden_states = self.self_attn(**attn_kwargs)
212
213 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
214 hidden_states = self.mlp(hidden_states)
215
216 return hidden_states, residual
217
218
219 @support_torch_compile(
220 dynamic_arg_dims={
221 "input_ids": 0,
222 "positions": -1,
223 "intermediate_tensors": 0,
224 "inputs_embeds": 0,
225 }
226 )
227 class Glm4MoeLiteModel(nn.Module):
228 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
229 super().__init__()
230
231 config = vllm_config.model_config.hf_config
232 quant_config = vllm_config.quant_config
233 self.config = config
234 self.device = current_platform.device_type
235
236 self.vocab_size = config.vocab_size
237 self.is_v32 = hasattr(config, "index_topk")
238 if self.is_v32:
239 topk_tokens = config.index_topk
240 topk_indices_buffer = torch.empty(
241 vllm_config.scheduler_config.max_num_batched_tokens,
242 topk_tokens,
243 dtype=torch.int32,
244 device=self.device,
245 )
246 else:
247 topk_indices_buffer = None
248 index_group_builder = (
249 SparseMLAIndexGroupBuilder(
250 topk_indices_buffer,
251 get_sparse_mla_index_group_max_rows(vllm_config),
252 )
253 if topk_indices_buffer is not None
254 else None
255 )
256
257 if get_pp_group().is_first_rank:
258 self.embed_tokens = VocabParallelEmbedding(
259 config.vocab_size,
260 config.hidden_size,
261 quant_config=quant_config,
262 prefix=f"{prefix}.embed_tokens",
263 )
264 else:
265 self.embed_tokens = PPMissingLayer()
266
267 self.start_layer, self.end_layer, self.layers = make_layers(
268 config.num_hidden_layers,
269 lambda prefix: Glm4MoeLiteDecoderLayer(
270 vllm_config=vllm_config,
271 config=config,
272 prefix=prefix,
273 topk_indices_buffer=topk_indices_buffer,
274 index_group_builder=index_group_builder,
275 ),
276 prefix=f"{prefix}.layers",
277 )
278
279 self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
280 self.layers,
281 Glm4MoeLite,
282 "mlp",
283 )
284
285 if get_pp_group().is_last_rank:
286 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
287 else:
288 self.norm = PPMissingLayer()
289
290 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
291 return self.embed_tokens(input_ids)
292
293 def forward(
294 self,
295 input_ids: torch.Tensor | None,
296 positions: torch.Tensor,
297 intermediate_tensors: IntermediateTensors | None = None,
298 inputs_embeds: torch.Tensor | None = None,
299 ) -> torch.Tensor | IntermediateTensors:
300 if get_pp_group().is_first_rank:
301 if inputs_embeds is not None:
302 hidden_states = inputs_embeds
303 else:
304 hidden_states = self.embed_input_ids(input_ids)
305 residual = None
306 else:
307 assert intermediate_tensors is not None
308 hidden_states = intermediate_tensors["hidden_states"]
309 residual = intermediate_tensors["residual"]
310
311 for layer in islice(self.layers, self.start_layer, self.end_layer):
312 hidden_states, residual = layer(positions, hidden_states, residual)
313
314 if not get_pp_group().is_last_rank:
315 return IntermediateTensors(
316 {"hidden_states": hidden_states, "residual": residual}
317 )
318
319 hidden_states, _ = self.norm(hidden_states, residual)
320 return hidden_states
321
322 def make_empty_intermediate_tensors(
323 self, batch_size: int, dtype: torch.dtype, device: torch.device
324 ) -> IntermediateTensors:
325 return IntermediateTensors(
326 {
327 "hidden_states": torch.zeros(
328 (batch_size, self.config.hidden_size), dtype=dtype, device=device
329 ),
330 "residual": torch.zeros(
331 (batch_size, self.config.hidden_size), dtype=dtype, device=device
332 ),
333 }
334 )
335
336 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
337 # Params for weights, fp8 weight scales, fp8 activation scales
338 # (param_name, weight_name, expert_id, shard_id)
339 return fused_moe_make_expert_params_mapping(
340 self,
341 ckpt_gate_proj_name="gate_proj",
342 ckpt_down_proj_name="down_proj",
343 ckpt_up_proj_name="up_proj",
344 num_experts=self.config.n_routed_experts,
345 )
346
347 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
348 stacked_params_mapping = [
349 # (param_name, shard_name, shard_id)
350 ("gate_up_proj", "gate_proj", 0),
351 ("gate_up_proj", "up_proj", 1),
352 ]
353 mla_params_mapping = [
354 ("fused_qkv_a_proj", "q_a_proj", 0),
355 ("fused_qkv_a_proj", "kv_a_proj_with_mqa", 1),
356 ]
357
358 stacked_params_mapping.extend(mla_params_mapping)
359
360 # Params for weights, fp8 weight scales, fp8 activation scales
361 # (param_name, weight_name, expert_id, shard_id)
362 expert_params_mapping = fused_moe_make_expert_params_mapping(
363 self,
364 ckpt_gate_proj_name="gate_proj",
365 ckpt_down_proj_name="down_proj",
366 ckpt_up_proj_name="up_proj",
367 num_experts=self.config.n_routed_experts
368 + (
369 self.config.n_shared_experts
370 if self.is_fused_shared_expert_enabled
371 else 0
372 ),
373 )
374
375 params_dict = dict(self.named_parameters())
376 loaded_params: set[str] = set()
377 for name, loaded_weight in weights:
378 if "rotary_emb.inv_freq" in name:
379 continue
380
381 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
382 if spec_layer is not None:
383 continue # skip spec decode layers for main model
384
385 is_fusion_moe_shared_experts_layer = (
386 self.is_fused_shared_expert_enabled and ("mlp.shared_experts" in name)
387 )
388
389 for param_name, weight_name, shard_id in stacked_params_mapping:
390 # Skip non-stacked layers and experts (experts handled below).
391 if weight_name not in name:
392 continue
393 # We have mlp.experts[0].gate_proj in the checkpoint.
394 # Since we handle the experts below in expert_params_mapping,
395 # we need to skip here BEFORE we update the name, otherwise
396 # name will be updated to mlp.experts[0].gate_up_proj, which
397 # will then be updated below in expert_params_mapping
398 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
399 if ("mlp.experts." in name) and name not in params_dict:
400 continue
401 if is_fusion_moe_shared_experts_layer:
402 continue
403 name_mapped = name.replace(weight_name, param_name)
404
405 # QKV fusion is optional, fall back to normal
406 # weight loading if it's not enabled
407 # if go with fusion option, then update name
408 if (
409 param_name == "fused_qkv_a_proj"
410 ) and name_mapped not in params_dict:
411 continue
412 else:
413 name = name_mapped
414 # Skip loading extra bias for GPTQ models.
415 if name.endswith(".bias") and name not in params_dict:
416 continue
417
418 if is_pp_missing_parameter(name, self):
419 continue
420
421 param = params_dict[name]
422 weight_loader = param.weight_loader
423 weight_loader(param, loaded_weight, shard_id)
424 break
425 else:
426 is_expert_weight = False
427
428 # Special handling: when AITER fusion_shared_experts is enabled,
429 # checkpoints may provide a single widened shared_experts tensor
430 # without explicit expert indices
431 # (e.g. ...mlp.shared_experts.gate_proj.weight).
432 # For models with multiple shared experts, split that tensor
433 # evenly into per-shared-expert slices and load them into
434 # appended expert slots mlp.experts.{n_routed_experts + j}.*
435 # accordingly.
436 num_chunks = 1
437 if is_fusion_moe_shared_experts_layer:
438 num_chunks = getattr(self.config, "n_shared_experts", 1) or 1
439 # Determine split axis based on op type
440 # gate/up: ColumnParallel → split along dim 0
441 # down: RowParallel → split along dim 1
442 split_dim = 1 if "down_proj.weight" in name else 0
443 total = loaded_weight.shape[split_dim]
444 assert total % num_chunks == 0, (
445 f"Shared expert weight dim {total} "
446 f"not divisible by num_chunks {num_chunks}"
447 )
448 chunk_size = total // num_chunks
449
450 for j in range(num_chunks):
451 chunk_name = name
452 weight_to_load = loaded_weight
453
454 if is_fusion_moe_shared_experts_layer:
455 if split_dim == 0:
456 weight_to_load = loaded_weight[
457 j * chunk_size : (j + 1) * chunk_size, :
458 ]
459 else:
460 weight_to_load = loaded_weight[
461 :, j * chunk_size : (j + 1) * chunk_size
462 ]
463 # Synthesize an expert-style name so expert mapping
464 # can route it
465 chunk_name = name.replace(
466 "mlp.shared_experts",
467 f"mlp.experts.{self.config.n_routed_experts + j}",
468 )
469
470 # Use expert_params_mapping to locate the destination
471 # param and delegate to its expert-aware weight_loader
472 # with expert_id.
473 for mapping in expert_params_mapping:
474 param_name, weight_name, mapping_expert_id, mapping_shard_id = (
475 mapping
476 )
477 if weight_name not in chunk_name:
478 continue
479
480 # Anyway, this is an expert weight and should not be
481 # attempted to load as other weights later
482 is_expert_weight = True
483
484 # Do not modify `name` since the loop may continue here
485 # Instead, create a new variable
486 name_mapped = chunk_name.replace(weight_name, param_name)
487
488 if is_pp_missing_parameter(name_mapped, self):
489 continue
490
491 param = params_dict[name_mapped]
492 # We should ask the weight loader to return success or
493 # not here since otherwise we may skip experts with
494 # other available replicas.
495 weight_loader = typing.cast(
496 Callable[..., bool], param.weight_loader
497 )
498 success = weight_loader(
499 param,
500 weight_to_load,
501 name_mapped,
502 shard_id=mapping_shard_id,
503 expert_id=mapping_expert_id,
504 return_success=True,
505 )
506 if success:
507 if not is_fusion_moe_shared_experts_layer:
508 name = name_mapped
509 else:
510 loaded_params.add(name_mapped)
511 break
512 else:
513 if is_expert_weight:
514 # We've checked that this is an expert weight
515 # However it's not mapped locally to this rank
516 # So we simply skip it
517 continue
518
519 # Skip loading extra bias for GPTQ models.
520 if name.endswith(".bias") and name not in params_dict:
521 continue
522
523 # Remapping the name of FP8 kv-scale.
524 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
525 if remapped_name is None:
526 continue
527 name = remapped_name
528
529 if is_pp_missing_parameter(name, self):
530 continue
531
532 param = params_dict[name]
533 weight_loader = getattr(
534 param, "weight_loader", default_weight_loader
535 )
536 weight_loader(param, loaded_weight)
537 if not is_fusion_moe_shared_experts_layer:
538 loaded_params.add(name)
539
540 return loaded_params
541
542
543 class Glm4MoeLiteForCausalLM(
544 nn.Module, SupportsPP, SupportsLoRA, Glm4LiteMixtureOfExperts
545 ):
546 packed_modules_mapping = {
547 "gate_up_proj": ["gate_proj", "up_proj"],
548 }
549
550 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
551 super().__init__()
552 config = vllm_config.model_config.hf_config
553 quant_config = vllm_config.quant_config
554 self.config = config
555 self.quant_config = quant_config
556
557 qk_nope_head_dim = getattr(config, "qk_nope_head_dim", 0)
558 qk_rope_head_dim = getattr(config, "qk_rope_head_dim", 0)
559 self.use_mha = config.model_type == "deepseek" or all(
560 dim == 0 for dim in (qk_nope_head_dim, qk_rope_head_dim)
561 )
562
563 if self.use_mha:
564 self.packed_modules_mapping["qkv_proj"] = ["q_proj", "k_proj", "v_proj"]
565
566 # `packed_modules_mapping` needs to be modified before
567 # initializing DeepseekV2Model, as it is passed inplace to
568 # quantization config init and may be used to select the
569 # quant_method for relevant layers during initialization.
570 self.fuse_qkv_a_proj = (
571 hasattr(config, "q_lora_rank") and config.q_lora_rank is not None
572 )
573 if self.fuse_qkv_a_proj:
574 self.packed_modules_mapping["fused_qkv_a_proj"] = [
575 "q_a_proj",
576 "kv_a_proj_with_mqa",
577 ]
578
579 self.model = Glm4MoeLiteModel(
580 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
581 )
582 if get_pp_group().is_last_rank:
583 self.lm_head = ParallelLMHead(
584 config.vocab_size,
585 config.hidden_size,
586 quant_config=quant_config,
587 prefix=maybe_prefix(prefix, "lm_head"),
588 )
589 else:
590 self.lm_head = PPMissingLayer()
591 self.logits_processor = LogitsProcessor(config.vocab_size)
592 self.make_empty_intermediate_tensors = (
593 self.model.make_empty_intermediate_tensors
594 )
595 # Set MoE hyperparameters
596 self.num_moe_layers = (
597 self.config.num_hidden_layers - self.config.first_k_dense_replace
598 )
599 self.set_moe_parameters()
600
601 def set_moe_parameters(self):
602 self.num_expert_groups = getattr(self.config, "n_group", 1)
603
604 self.moe_layers = []
605 self.moe_mlp_layers = []
606 example_moe = None
607 for layer in self.model.layers:
608 if isinstance(layer, PPMissingLayer):
609 continue
610
611 assert isinstance(layer, Glm4MoeLiteDecoderLayer)
612 if isinstance(layer.mlp, Glm4MoeLite):
613 # Pick last one layer since the first ones may be dense layers.
614 example_moe = layer.mlp
615 self.moe_mlp_layers.append(layer.mlp)
616 self.moe_layers.append(layer.mlp.experts)
617
618 self.extract_moe_parameters(example_moe)
619
620 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
621 return self.model.embed_input_ids(input_ids)
622
623 def forward(
624 self,
625 input_ids: torch.Tensor | None,
626 positions: torch.Tensor,
627 intermediate_tensors: IntermediateTensors | None = None,
628 inputs_embeds: torch.Tensor | None = None,
629 ) -> torch.Tensor | IntermediateTensors:
630 hidden_states = self.model(
631 input_ids, positions, intermediate_tensors, inputs_embeds
632 )
633 return hidden_states
634
635 def compute_logits(
636 self,
637 hidden_states: torch.Tensor,
638 ) -> torch.Tensor | None:
639 logits = self.logits_processor(self.lm_head, hidden_states)
640 return logits
641
642 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
643 # Params for weights, fp8 weight scales, fp8 activation scales
644 # (param_name, weight_name, expert_id, shard_id)
645 return fused_moe_make_expert_params_mapping(
646 self,
647 ckpt_gate_proj_name="gate_proj",
648 ckpt_down_proj_name="down_proj",
649 ckpt_up_proj_name="up_proj",
650 num_experts=self.config.n_routed_experts,
651 num_redundant_experts=0,
652 )
653
654 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
655 loader = AutoWeightsLoader(self)
656 return loader.load_weights(weights)