main
py 1,363 lines 52 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 from typing import ClassVar, Literal
6
7 import torch
8 from torch import nn
9 from transformers import Glm5NextTextConfig
10
11 from vllm.config import ParallelConfig, VllmConfig
12 from vllm.distributed import (
13 get_ep_group,
14 get_pp_group,
15 get_tensor_model_parallel_rank,
16 get_tensor_model_parallel_world_size,
17 tensor_model_parallel_all_gather,
18 )
19 from vllm.logger import init_logger
20 from vllm.model_executor.layers.activation import SiluAndMul, SiluAndMulWithClamp
21 from vllm.model_executor.layers.fused_moe import (
22 FusedMoEFactory,
23 GateLinear,
24 fused_moe_make_expert_params_mapping,
25 )
26 from vllm.model_executor.layers.fusion.mm_input_norm import build_mm_input_norm
27 from vllm.model_executor.layers.layernorm import RMSNorm
28 from vllm.model_executor.layers.linear import (
29 MergedColumnParallelLinear,
30 RowParallelLinear,
31 )
32 from vllm.model_executor.layers.logits_processor import LogitsProcessor
33 from vllm.model_executor.layers.mamba.mamba_utils import (
34 MambaStateCopyFunc,
35 MambaStateCopyFuncCalculator,
36 MambaStateDtypeCalculator,
37 MambaStateShapeCalculator,
38 )
39 from vllm.model_executor.layers.mhc import (
40 MHCFusedPostPreOp,
41 MHCPostOp,
42 MHCPreOp,
43 hc_contract,
44 hc_expand,
45 )
46 from vllm.model_executor.layers.quantization import QuantizationConfig
47 from vllm.model_executor.layers.quantization.utils.quant_utils import (
48 GroupShape,
49 scaled_dequantize,
50 )
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 _get_moe_router_dtype
60 from vllm.model_executor.models.glm4_1v import (
61 Glm4vDummyInputsBuilder,
62 Glm4vForConditionalGeneration,
63 )
64 from vllm.model_executor.models.interfaces import (
65 HasInnerState,
66 IsHybrid,
67 MixtureOfExperts,
68 SupportsPP,
69 )
70 from vllm.model_executor.models.utils import (
71 AutoWeightsLoader,
72 PPMissingLayer,
73 init_vllm_registered_model,
74 is_pp_missing_parameter,
75 make_layers,
76 maybe_prefix,
77 sequence_parallel_chunk,
78 )
79 from vllm.models.common.ops.sequence_parallel import (
80 sp_all_gather,
81 sp_reduce_scatter,
82 sp_shard,
83 )
84 from vllm.multimodal import MULTIMODAL_REGISTRY
85 from vllm.platforms import current_platform
86 from vllm.sequence import IntermediateTensors
87
88 from .attention import Glm5NextMLAAttention
89 from .kda import Glm5NextLinearAttention
90 from .multimodal import (
91 Glm5NextMultiModalProcessor,
92 Glm5NextProcessingInfo,
93 Glm5NextVisionTransformer,
94 )
95
96 logger = init_logger(__name__)
97
98 _MHC_TAU = 0.05
99 """mHC routing temperature. A GLM-5.3-Flash trained value that neither the
100 checkpoint nor `Glm5NextTextConfig` carries."""
101
102 _MHC_POST_MULT_VALUE = 2.0
103 """mHC post-multiplier. A GLM-5.3-Flash trained value that neither the
104 checkpoint nor `Glm5NextTextConfig` carries."""
105
106 _LOGIT_SCALE = 1.0
107 """Output logit scale. A GLM-5.3-Flash trained value that neither the
108 checkpoint nor `Glm5NextTextConfig` carries."""
109
110 _VISION_RMS_NORM_EPS = 1e-6
111 """Vision tower RMSNorm epsilon.
112
113 GLM-5.3-Flash checkpoints ship `vision_config.rms_norm_eps = 1e-5`, but the
114 vision tower was trained with 1e-6. Serving with 1e-5 drifts the RMSNorm and
115 produces repetitive/degraded image descriptions, so force the trained value
116 regardless of the checkpoint field.
117 """
118
119
120 def _is_moe(config: Glm5NextTextConfig) -> bool:
121 return config.n_routed_experts is not None
122
123
124 def _is_kda_layer(config: Glm5NextTextConfig, layer_idx: int) -> bool:
125 layer_types = config.layer_types
126 return (
127 layer_types is not None
128 and layer_idx < len(layer_types)
129 and layer_types[layer_idx] == "linear_attention"
130 )
131
132
133 def _is_linear_attn(config: Glm5NextTextConfig) -> bool:
134 layer_types = config.layer_types
135 return layer_types is not None and "linear_attention" in layer_types
136
137
138 def _validate_supported_config(config: Glm5NextTextConfig) -> None:
139 """Reject checkpoints using config options this implementation lacks.
140
141 The kpool indexer kernels always keep the incomplete trailing pool, so a
142 checkpoint asking otherwise would be served silently wrong.
143 """
144 if config.index_topk is not None and not config.index_kpool_always_select_tail:
145 raise NotImplementedError(
146 "GLM-5.3 sparse indexer requires index_kpool_always_select_tail=True"
147 )
148
149
150 class Glm5NextMLP(nn.Module):
151 def __init__(
152 self,
153 hidden_size: int,
154 intermediate_size: int,
155 hidden_act: str,
156 quant_config: QuantizationConfig | None = None,
157 reduce_results: bool = True,
158 is_sequence_parallel=False,
159 prefix: str = "",
160 swiglu_limit: float | None = None,
161 ) -> None:
162 super().__init__()
163
164 # If is_sequence_parallel, the input and output tensors are sharded
165 # across the ranks within the tp_group. In this case the weights are
166 # replicated and no collective ops are needed.
167 # Otherwise we use standard TP with an allreduce at the end.
168 self.gate_up_proj = MergedColumnParallelLinear(
169 hidden_size,
170 [intermediate_size] * 2,
171 bias=False,
172 quant_config=quant_config,
173 disable_tp=is_sequence_parallel,
174 prefix=f"{prefix}.gate_up_proj",
175 )
176 self.down_proj = RowParallelLinear(
177 intermediate_size,
178 hidden_size,
179 bias=False,
180 quant_config=quant_config,
181 reduce_results=reduce_results,
182 disable_tp=is_sequence_parallel,
183 prefix=f"{prefix}.down_proj",
184 )
185 if hidden_act != "silu":
186 raise ValueError(
187 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
188 )
189
190 self.swiglu_limit = swiglu_limit
191 if self.swiglu_limit is not None:
192 self.act_fn = SiluAndMulWithClamp(swiglu_limit=self.swiglu_limit)
193 else:
194 self.act_fn = SiluAndMul()
195
196 def forward(self, x):
197 gate_up, _ = self.gate_up_proj(x)
198 x = self.act_fn(gate_up)
199 x, _ = self.down_proj(x)
200 return x
201
202
203 class Glm5NextMoE(nn.Module):
204 def __init__(
205 self,
206 config: Glm5NextTextConfig,
207 parallel_config: ParallelConfig,
208 quant_config: QuantizationConfig | None = None,
209 prefix: str = "",
210 apply_routed_scale_to_output: bool = False,
211 ):
212 super().__init__()
213 self.tp_size = get_tensor_model_parallel_world_size()
214 self.tp_rank = get_tensor_model_parallel_rank()
215
216 self.routed_scaling_factor = config.routed_scaling_factor
217
218 self.ep_group = get_ep_group().device_group
219 self.ep_rank = get_ep_group().rank_in_group
220 self.ep_size = self.ep_group.size()
221 self.n_routed_experts: int = config.n_routed_experts
222 self.n_shared_experts: int = config.n_shared_experts
223
224 self.is_sequence_parallel = parallel_config.use_sequence_parallel_moe
225
226 if config.hidden_act != "silu":
227 raise ValueError(
228 f"Unsupported activation: {config.hidden_act}. "
229 "Only silu is supported for now."
230 )
231
232 self.router_dtype = _get_moe_router_dtype(config)
233 self.gate = GateLinear(
234 config.hidden_size,
235 config.n_routed_experts,
236 out_dtype=self.router_dtype,
237 prefix=f"{prefix}.gate",
238 )
239 if config.topk_method == "noaux_tc":
240 self.gate.e_score_correction_bias = nn.Parameter(
241 torch.empty(config.n_routed_experts, dtype=torch.float32)
242 )
243 else:
244 self.gate.e_score_correction_bias = None
245
246 # Load balancing settings.
247 eplb_config = parallel_config.eplb_config
248 self.enable_eplb = parallel_config.enable_eplb
249
250 self.n_redundant_experts = eplb_config.num_redundant_experts
251 self.n_logical_experts = self.n_routed_experts
252 self.n_physical_experts = self.n_logical_experts + self.n_redundant_experts
253 self.n_local_physical_experts = self.n_physical_experts // self.ep_size
254
255 self.physical_expert_start = self.ep_rank * self.n_local_physical_experts
256 self.physical_expert_end = (
257 self.physical_expert_start + self.n_local_physical_experts
258 )
259
260 swiglu_limit = config.swiglu_limit
261 if config.n_shared_experts is None:
262 self.shared_experts = None
263 else:
264 intermediate_size = config.moe_intermediate_size * config.n_shared_experts
265
266 self.shared_experts = Glm5NextMLP(
267 hidden_size=config.hidden_size,
268 intermediate_size=intermediate_size,
269 hidden_act=config.hidden_act,
270 quant_config=quant_config,
271 is_sequence_parallel=self.is_sequence_parallel,
272 reduce_results=False,
273 prefix=f"{prefix}.shared_experts",
274 swiglu_limit=swiglu_limit,
275 )
276
277 self.experts = FusedMoEFactory(
278 shared_experts=self.shared_experts,
279 gate=self.gate,
280 num_experts=config.n_routed_experts,
281 top_k=config.num_experts_per_tok,
282 hidden_size=config.hidden_size,
283 intermediate_size=config.moe_intermediate_size,
284 renormalize=config.norm_topk_prob,
285 quant_config=quant_config,
286 use_grouped_topk=True,
287 num_expert_group=config.n_group,
288 topk_group=config.topk_group,
289 prefix=f"{prefix}.experts",
290 scoring_func=config.scoring_func,
291 routed_scaling_factor=self.routed_scaling_factor,
292 apply_routed_scale_to_output=apply_routed_scale_to_output,
293 e_score_correction_bias=self.gate.e_score_correction_bias,
294 enable_eplb=self.enable_eplb,
295 num_redundant_experts=self.n_redundant_experts,
296 is_sequence_parallel=self.is_sequence_parallel,
297 n_shared_experts=None,
298 router_logits_dtype=self.gate.out_dtype,
299 swiglu_limit=swiglu_limit,
300 )
301
302 def forward(
303 self,
304 hidden_states: torch.Tensor,
305 already_sequence_parallel: bool = False,
306 ) -> torch.Tensor:
307 num_tokens, hidden_dim = hidden_states.shape
308
309 # Chunk the hidden states so they aren't replicated across TP ranks.
310 # This avoids duplicate computation in self.experts.
311 if self.is_sequence_parallel and not already_sequence_parallel:
312 hidden_states = sequence_parallel_chunk(hidden_states)
313
314 # MoERunner holds the gate (passed to FusedMoEFactory) and computes
315 # the router logits itself, so nothing is precomputed here (matches
316 # DeepseekV2MoE; `router_logits` is a placeholder).
317 final_hidden_states = self.experts(
318 hidden_states=hidden_states, router_logits=hidden_states
319 )
320
321 if self.is_sequence_parallel and not already_sequence_parallel:
322 final_hidden_states = tensor_model_parallel_all_gather(
323 final_hidden_states, 0
324 )
325 final_hidden_states = final_hidden_states[:num_tokens]
326
327 return final_hidden_states.view(num_tokens, hidden_dim)
328
329
330 class Glm5NextDecoderLayer(nn.Module):
331 def __init__(
332 self,
333 vllm_config: VllmConfig,
334 config: Glm5NextTextConfig,
335 layer_idx: int,
336 prefix: str = "",
337 topk_indices_buffer: torch.Tensor | None = None,
338 is_mtp_layer: bool = False,
339 **kwargs,
340 ) -> None:
341 super().__init__()
342
343 cache_config = vllm_config.cache_config
344 quant_config = vllm_config.quant_config
345 parallel_config = vllm_config.parallel_config
346
347 self.hidden_size = config.hidden_size
348 self.layer_idx = layer_idx
349 self.is_moe = _is_moe(config)
350 self.num_hidden_layers = config.num_hidden_layers
351 self.rms_norm_eps = config.rms_norm_eps
352 self.num_experts = config.n_routed_experts
353 self.is_mtp_layer = is_mtp_layer
354 self.mhc = config.mhc
355 is_kda_layer = not is_mtp_layer and _is_kda_layer(config, layer_idx)
356 self.layer_kind = "kda" if is_kda_layer else "mla"
357 self.is_sequence_parallel = parallel_config.use_sequence_parallel_moe
358
359 if is_kda_layer:
360 self.self_attn = Glm5NextLinearAttention(
361 config=config,
362 vllm_config=vllm_config,
363 prefix=f"{prefix}.self_attn",
364 )
365 else:
366 # MLA layers require the latent head dims, which are guaranteed set
367 # on MLA configs; narrow away the `int | None`.
368 assert config.v_head_dim is not None
369 assert config.kv_lora_rank is not None
370 self.self_attn = Glm5NextMLAAttention(
371 vllm_config=vllm_config,
372 config=config,
373 hidden_size=self.hidden_size,
374 num_heads=config.num_attention_heads,
375 qk_nope_head_dim=config.qk_nope_head_dim,
376 qk_rope_head_dim=config.qk_rope_head_dim,
377 v_head_dim=config.v_head_dim,
378 q_lora_rank=config.q_lora_rank,
379 kv_lora_rank=config.kv_lora_rank,
380 max_position_embeddings=config.max_position_embeddings,
381 cache_config=cache_config,
382 quant_config=None, # MLA projections are BF16 in checkpoint
383 prefix=f"{prefix}.self_attn",
384 topk_indices_buffer=topk_indices_buffer,
385 skip_rope=config.mla_use_nope,
386 )
387
388 # MTP layers sit past the base model's hidden layers (layer_idx >=
389 # num_hidden_layers), so they're outside mlp_layer_types; default them
390 # to the last base layer's MLP type (sparse/MoE for these checkpoints).
391 mlp_layer_types = config.mlp_layer_types
392 mlp_type = (
393 mlp_layer_types[layer_idx]
394 if layer_idx < len(mlp_layer_types)
395 else (mlp_layer_types[-1] if mlp_layer_types else "sparse")
396 )
397 if self.is_moe and self.num_experts is not None and mlp_type == "sparse":
398 self.mlp = Glm5NextMoE(
399 config=config,
400 parallel_config=parallel_config,
401 quant_config=quant_config,
402 prefix=f"{prefix}.mlp",
403 )
404 else:
405 self.mlp = Glm5NextMLP(
406 hidden_size=self.hidden_size,
407 intermediate_size=config.intermediate_size,
408 hidden_act=config.hidden_act,
409 quant_config=quant_config,
410 is_sequence_parallel=self.is_sequence_parallel,
411 prefix=f"{prefix}.mlp",
412 swiglu_limit=config.swiglu_limit,
413 )
414 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
415 # Cached for the hot forward path (isinstance per layer per step).
416 self._mlp_is_moe = isinstance(self.mlp, Glm5NextMoE)
417 # In SP, the attention output projection leaves a partial sum; the
418 # decoder-layer reduce_scatter after attention completes it (DSv4 pattern).
419 # MTP layers use the non-mHC path which has no sp_reduce_scatter, so
420 # their o_proj must still reduce normally.
421 if self.is_sequence_parallel and not is_mtp_layer:
422 self.self_attn.o_proj.reduce_results = False
423 self.post_attention_layernorm = RMSNorm(
424 config.hidden_size, eps=config.rms_norm_eps
425 )
426
427 if self.mhc and not is_mtp_layer:
428 # mhc config
429 self.mhc_num_residual_streams = config.hc_mult
430 self.mhc_tau = _MHC_TAU
431 self.hc_eps = config.hc_eps
432 self.mhc_sinkhorn_iterations = config.hc_sinkhorn_iters
433 self.mhc_post_mult_value = _MHC_POST_MULT_VALUE
434
435 n = config.hc_mult
436 d_model = n * self.hidden_size
437 mix_hc = (2 + n) * n
438
439 self.n = n
440
441 # attn hc
442 self.hc_attn_fn = nn.Parameter(
443 torch.empty(mix_hc, d_model, dtype=torch.float32)
444 )
445 self.hc_attn_base = nn.Parameter(torch.empty(mix_hc, dtype=torch.float32))
446 self.hc_attn_scale = nn.Parameter(torch.empty(3, dtype=torch.float32))
447
448 # ffn hc
449 self.hc_ffn_fn = nn.Parameter(
450 torch.empty(mix_hc, d_model, dtype=torch.float32)
451 )
452 self.hc_ffn_base = nn.Parameter(torch.empty(mix_hc, dtype=torch.float32))
453 self.hc_ffn_scale = nn.Parameter(torch.empty(3, dtype=torch.float32))
454
455 self.mhc_pre_op = MHCPreOp()
456 self.mhc_post_op = MHCPostOp()
457 self.mhc_fused_post_pre_op = MHCFusedPostPreOp()
458
459 if vllm_config.kernel_config.enable_jit_warmup:
460 from vllm.model_executor.kernels.mhc.tilelang_kernels import (
461 _HC_PRENORM_GEMM_TILELANG_KERNEL,
462 _MHC_FUSED_TILELANG_KERNEL,
463 _MHC_POST_TILELANG_KERNEL,
464 _MHC_PRE_BIG_FUSE_TILELANG_KERNEL,
465 )
466 from vllm.utils.deep_gemm import is_deep_gemm_supported
467
468 include_pre_gemm_splits = is_deep_gemm_supported()
469 _MHC_PRE_BIG_FUSE_TILELANG_KERNEL.register_warmup(
470 vllm_config,
471 hidden_size=self.hidden_size,
472 hc_mult=self.n,
473 use_norm_weight=True,
474 include_pre_gemm_splits=include_pre_gemm_splits,
475 include_broadcast_splits=False,
476 rms_eps=self.rms_norm_eps,
477 hc_pre_eps=self.hc_eps,
478 hc_sinkhorn_eps=self.hc_eps,
479 hc_post_mult_value=self.mhc_post_mult_value,
480 sinkhorn_repeat=self.mhc_sinkhorn_iterations,
481 norm_eps=(
482 self.input_layernorm.variance_epsilon,
483 self.post_attention_layernorm.variance_epsilon,
484 ),
485 )
486 if not include_pre_gemm_splits:
487 _HC_PRENORM_GEMM_TILELANG_KERNEL.register_warmup(
488 vllm_config,
489 hidden_size=self.hidden_size,
490 hc_mult=self.n,
491 n_out=self.n * (2 + self.n),
492 )
493 _MHC_POST_TILELANG_KERNEL.register_warmup(
494 hidden_size=self.hidden_size,
495 hc_mult=self.n,
496 )
497 _MHC_FUSED_TILELANG_KERNEL.register_warmup(
498 vllm_config,
499 hidden_size=self.hidden_size,
500 hc_mult=self.n,
501 )
502
503 def forward(
504 self,
505 positions: torch.Tensor,
506 hidden_states: torch.Tensor,
507 residual: torch.Tensor | None = None,
508 post: torch.Tensor | None = None,
509 comb: torch.Tensor | None = None,
510 ) -> tuple[
511 torch.Tensor,
512 torch.Tensor | None,
513 torch.Tensor | None,
514 torch.Tensor | None,
515 ]:
516 # 70B or MTP layers: KDA + MoE without HC.
517 if not self.mhc or self.is_mtp_layer:
518 residual = hidden_states
519 hidden_states = self.input_layernorm(hidden_states)
520
521 attn_output = self.self_attn(
522 hidden_states=hidden_states,
523 positions=positions,
524 )
525 hidden_states, residual = self.post_attention_layernorm(
526 attn_output, residual=residual
527 )
528 hidden_states = self.mlp(hidden_states)
529 if self.is_mtp_layer:
530 # Return the unsummed pair: the MTP caller feeds it straight
531 # into shared_head's fused_add_rms_norm (one kernel instead of
532 # a separate residual-add + norm). The sum itself is unchanged
533 # (fp32-accumulated inside the fused kernel).
534 return hidden_states, residual, None, None
535 hidden_states = residual + hidden_states
536 return hidden_states, residual, None, None
537
538 # mHC start. `post`/`comb` carry the previous layer's deferred
539 # hc_post inputs (its ffn-pre outputs); when present, fuse that
540 # hc_post with this layer's attn hc_pre into one kernel (inter-layer
541 # fusion). Layer 0 has no incoming state -> standalone hc_pre.
542 x = hidden_states
543 if post is None:
544 if self.layer_idx == 0:
545 x = hc_expand(x, self.n)
546 residual = x
547 post, comb, x = self.hc_pre(
548 x,
549 self.hc_attn_fn,
550 self.hc_attn_scale,
551 self.hc_attn_base,
552 norm_weight=self.input_layernorm.weight.data,
553 norm_eps=self.input_layernorm.variance_epsilon,
554 )
555 else:
556 residual, post, comb, x = self.hc_fused_post_pre(
557 x,
558 residual,
559 post,
560 comb,
561 self.hc_attn_fn,
562 self.hc_attn_scale,
563 self.hc_attn_base,
564 norm_weight=self.input_layernorm.weight.data,
565 norm_eps=self.input_layernorm.variance_epsilon,
566 )
567
568 # Attention needs the full token sequence; mHC above ran on the SP
569 # shard. Gather for attention, scatter back afterward (DSv4 pattern).
570 if self.is_sequence_parallel:
571 x = sp_all_gather(x)[: positions.shape[0]]
572
573 x = self.self_attn(
574 hidden_states=x,
575 positions=positions,
576 )
577
578 if self.is_sequence_parallel:
579 x = sp_reduce_scatter(x)
580
581 # Fuse post-attn hc_post + pre-FFN hc_pre (+ RMSNorm) into one kernel.
582 residual, post, comb, x = self.hc_fused_post_pre(
583 x,
584 residual,
585 post,
586 comb,
587 self.hc_ffn_fn,
588 self.hc_ffn_scale,
589 self.hc_ffn_base,
590 norm_weight=self.post_attention_layernorm.weight.data,
591 norm_eps=self.post_attention_layernorm.variance_epsilon,
592 )
593
594 # Fully Connected
595 if self._mlp_is_moe:
596 x = self.mlp(x, already_sequence_parallel=self.is_sequence_parallel)
597 else:
598 x = self.mlp(x)
599
600 # mHC end. The last mHC layer materializes its final hc_post (nothing
601 # to fuse with) then contracts; every other layer defers its hc_post to
602 # the next layer's fused pre, returning the state.
603 if self.layer_idx == self.num_hidden_layers - 1:
604 x = self.hc_post(x, residual, post, comb)
605 x = hc_contract(x, self.n)
606 return x, None, None, None
607
608 return x, residual, post, comb
609
610 def hc_pre(
611 self,
612 x: torch.Tensor,
613 hc_fn: torch.Tensor,
614 hc_scale: torch.Tensor,
615 hc_base: torch.Tensor,
616 norm_weight: torch.Tensor | None = None,
617 norm_eps: float = 0.0,
618 ):
619 post_mix, res_mix, layer_input = self.mhc_pre_op(
620 residual=x,
621 fn=hc_fn,
622 hc_scale=hc_scale,
623 hc_base=hc_base,
624 rms_eps=self.rms_norm_eps,
625 hc_pre_eps=self.hc_eps,
626 hc_sinkhorn_eps=self.hc_eps,
627 hc_post_mult_value=self.mhc_post_mult_value,
628 sinkhorn_repeat=self.mhc_sinkhorn_iterations,
629 norm_weight=norm_weight,
630 norm_eps=norm_eps,
631 )
632 return post_mix, res_mix, layer_input
633
634 def hc_post(
635 self,
636 x: torch.Tensor,
637 residual: torch.Tensor,
638 post: torch.Tensor,
639 comb: torch.Tensor,
640 ):
641 return self.mhc_post_op(x, residual, post, comb)
642
643 def hc_fused_post_pre(
644 self,
645 x: torch.Tensor,
646 residual: torch.Tensor,
647 post: torch.Tensor,
648 comb: torch.Tensor,
649 hc_fn: torch.Tensor,
650 hc_scale: torch.Tensor,
651 hc_base: torch.Tensor,
652 norm_weight: torch.Tensor | None = None,
653 norm_eps: float = 0.0,
654 ):
655 return self.mhc_fused_post_pre_op(
656 x=x,
657 residual=residual,
658 post_layer_mix=post,
659 comb_res_mix=comb,
660 fn=hc_fn,
661 hc_scale=hc_scale,
662 hc_base=hc_base,
663 rms_eps=self.rms_norm_eps,
664 hc_pre_eps=self.hc_eps,
665 hc_sinkhorn_eps=self.hc_eps,
666 hc_post_mult_value=self.mhc_post_mult_value,
667 sinkhorn_repeat=self.mhc_sinkhorn_iterations,
668 n_splits=1,
669 tile_n=1,
670 norm_weight=norm_weight,
671 norm_eps=norm_eps,
672 )
673
674
675 class Glm5NextModel(nn.Module):
676 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
677 super().__init__()
678
679 config = vllm_config.model_config.hf_text_config
680 _validate_supported_config(config)
681 self.config = config
682
683 self.vocab_size = config.vocab_size
684 self.device = current_platform.device_type
685
686 self.is_v32 = config.index_topk is not None
687 if self.is_v32:
688 topk_tokens = config.index_topk
689 assert topk_tokens is not None
690 # Reserve room for the incomplete pool tail.
691 kpool = config.index_kpool
692 assert kpool is not None
693 buffer_width = topk_tokens + (kpool - 1 if kpool > 1 else 0)
694 # Sparse MLA tiles top-k in 128 columns; padded slots remain masked.
695 sparse_topk_block_n = 128
696 buffer_width = (
697 (buffer_width + sparse_topk_block_n - 1) // sparse_topk_block_n
698 ) * sparse_topk_block_n
699 topk_indices_buffer = torch.empty(
700 vllm_config.scheduler_config.max_num_batched_tokens,
701 buffer_width,
702 dtype=torch.int32,
703 device=self.device,
704 )
705 else:
706 # Full-MLA config (no kpool sparse indexer): no topk buffer.
707 topk_indices_buffer = None
708
709 if get_pp_group().is_first_rank:
710 self.embed_tokens = VocabParallelEmbedding(
711 config.vocab_size,
712 config.hidden_size,
713 prefix=f"{prefix}.embed_tokens",
714 )
715 else:
716 self.embed_tokens = PPMissingLayer()
717
718 def get_layer(prefix: str):
719 layer_idx = int(prefix.rsplit(".", 1)[1])
720 return Glm5NextDecoderLayer(
721 vllm_config=vllm_config,
722 config=config,
723 layer_idx=layer_idx,
724 prefix=prefix,
725 topk_indices_buffer=topk_indices_buffer,
726 )
727
728 self.start_layer, self.end_layer, self.layers = make_layers(
729 config.num_hidden_layers,
730 get_layer,
731 prefix=f"{prefix}.layers",
732 )
733 # The active slice is fixed after construction; cache it so forward
734 # doesn't rebuild the slice (a fresh list) every step.
735 self._active_layers = self.layers[self.start_layer : self.end_layer]
736
737 if get_pp_group().is_last_rank:
738 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
739 else:
740 self.norm = PPMissingLayer()
741
742 self.is_sequence_parallel = (
743 vllm_config.parallel_config.use_sequence_parallel_moe
744 )
745
746 world_size = get_tensor_model_parallel_world_size()
747 assert config.num_attention_heads % world_size == 0, (
748 "num_attention_heads must be divisible by world_size"
749 )
750
751 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
752 return self.embed_tokens(input_ids)
753
754 def forward(
755 self,
756 input_ids: torch.Tensor | None,
757 positions: torch.Tensor,
758 intermediate_tensors: IntermediateTensors | None,
759 inputs_embeds: torch.Tensor | None = None,
760 **kwargs,
761 ) -> torch.Tensor:
762 if get_pp_group().is_first_rank:
763 if inputs_embeds is not None:
764 hidden_states = inputs_embeds
765 else:
766 hidden_states = self.embed_input_ids(input_ids)
767 residual = None
768 post = None
769 comb = None
770 else:
771 assert intermediate_tensors is not None
772 hidden_states = intermediate_tensors["hidden_states"]
773 residual = intermediate_tensors["residual"]
774 # post/comb (deferred mHC hc_post state) are not propagated across
775 # PP ranks; the receiving rank's first mHC layer uses standalone pre.
776 post = None
777 comb = None
778
779 full_num_tokens = positions.shape[0]
780 if self.is_sequence_parallel:
781 hidden_states = sp_shard(hidden_states)
782
783 for layer in self._active_layers:
784 hidden_states, residual, post, comb = layer(
785 positions, hidden_states, residual, post, comb
786 )
787
788 if not get_pp_group().is_last_rank:
789 # PP is gated off for GLM-5.3-Flash (no make_empty_intermediate_tensors),
790 # so this branch is not exercised. post/comb are the deferred
791 # hc_post state of this rank's last mHC layer; a future PP path
792 # would need to propagate them, but for now they are dropped (the
793 # receiving rank's first layer would fall back to standalone pre).
794 return IntermediateTensors(
795 {"hidden_states": hidden_states, "residual": residual}
796 )
797
798 if self.is_sequence_parallel:
799 hidden_states = sp_all_gather(hidden_states)[:full_num_tokens]
800
801 hidden_states = self.norm(hidden_states)
802 return hidden_states
803
804 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
805 stacked_params_mapping = [
806 # (param_name, shard_name, shard_id)
807 (".gate_up_proj", ".gate_proj", 0),
808 (".gate_up_proj", ".up_proj", 1),
809 # MLA: fuse q_a_proj and kv_a_proj_with_mqa
810 (".fused_qkv_a_proj", ".q_a_proj", 0),
811 (".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
812 # Indexer: fuse wk and weights_proj
813 (".wk_weights_proj", ".wk", 0),
814 (".wk_weights_proj", ".weights_proj", 1),
815 # KDA: merge q, k, v, b, f_a, g_a projections into one GEMM
816 (".in_proj_qkvbfg_a", ".q_proj", 0),
817 (".in_proj_qkvbfg_a", ".k_proj", 1),
818 (".in_proj_qkvbfg_a", ".v_proj", 2),
819 (".in_proj_qkvbfg_a", ".b_proj", 3),
820 (".in_proj_qkvbfg_a", ".f_a_proj", 4),
821 (".in_proj_qkvbfg_a", ".g_a_proj", 5),
822 ]
823 if _is_moe(self.config):
824 # Params for weights, fp8 weight scales, fp8 activation scales
825 # (param_name, weight_name, expert_id, shard_id)
826 # EPLB: the mapping enumerates physical experts, so it must cover
827 # the redundant replicas or their slots are never loaded.
828 num_redundant_experts = next(
829 (
830 layer.mlp.n_redundant_experts
831 for layer in self.layers
832 if isinstance(layer, Glm5NextDecoderLayer)
833 and isinstance(layer.mlp, Glm5NextMoE)
834 ),
835 0,
836 )
837 expert_params_mapping = fused_moe_make_expert_params_mapping(
838 self,
839 ckpt_gate_proj_name="gate_proj",
840 ckpt_down_proj_name="down_proj",
841 ckpt_up_proj_name="up_proj",
842 num_experts=self.config.n_routed_experts,
843 num_redundant_experts=num_redundant_experts,
844 )
845 else:
846 expert_params_mapping = []
847 params_dict = dict(self.named_parameters())
848 loaded_params: set[str] = set()
849
850 # GLM-5.3-Flash NoPE checkpoints omit the RoPE rows from
851 # ``kv_a_proj_with_mqa``; pad them with zeros for the model shape.
852 kv_a_pad_size = 0
853 if self.config.mla_use_nope and self.config.qk_rope_head_dim > 0:
854 kv_a_pad_size = self.config.qk_rope_head_dim
855
856 _pending_wk_fp8: dict = {}
857
858 for args in weights:
859 name, loaded_weight = args[:2]
860 kwargs: dict = args[2] if len(args) > 2 else {}
861 if "rotary_emb.inv_freq" in name:
862 continue
863
864 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
865 if spec_layer is not None:
866 continue # skip spec decode layers for main model
867 if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name:
868 # Models trained using ColossalAI may include these tensors in
869 # the checkpoint. Skip them.
870 continue
871
872 # Handle FP8 indexer WK: dequantize to BF16 for fusion with
873 # weights_proj into wk_weights_proj.
874 if _try_load_fp8_indexer_wk(
875 name,
876 loaded_weight,
877 _pending_wk_fp8,
878 params_dict,
879 loaded_params,
880 ):
881 continue
882
883 # FP8 checkpoint: dequantize BF16-kept MLA projections
884 # (q_a_proj / kv_a_proj_with_mqa / o_proj) to BF16.
885 if _try_load_fp8_attn_proj(
886 name,
887 loaded_weight,
888 _pending_wk_fp8,
889 params_dict,
890 loaded_params,
891 kv_a_pad_size,
892 ):
893 continue
894
895 # Pad kv_a_proj_with_mqa for NoPE models
896 if kv_a_pad_size > 0 and ".kv_a_proj_with_mqa." in name:
897 pad = torch.zeros(
898 kv_a_pad_size,
899 *loaded_weight.shape[1:],
900 dtype=loaded_weight.dtype,
901 device=loaded_weight.device,
902 )
903 loaded_weight = torch.cat([loaded_weight, pad], dim=0)
904
905 for param_name, weight_name, shard_id in stacked_params_mapping:
906 if weight_name not in name:
907 continue
908 # We have mlp.experts[0].gate_proj in the checkpoint.
909 # Since we handle the experts below in expert_params_mapping,
910 # we need to skip here BEFORE we update the name, otherwise
911 # name will be updated to mlp.experts[0].gate_up_proj, which
912 # will then be updated below in expert_params_mapping
913 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
914 if ("mlp.experts." in name) and name not in params_dict:
915 continue
916 name_mapped = name.replace(weight_name, param_name)
917 # QKV fusion: skip if fused module doesn't exist in model
918 if param_name == ".fused_qkv_a_proj" and name_mapped not in params_dict:
919 continue
920 name = name_mapped
921 # Skip loading extra bias for GPTQ models.
922 if name.endswith(".bias") and name not in params_dict:
923 continue
924 if is_pp_missing_parameter(name, self):
925 continue
926 param = params_dict[name]
927 weight_loader = param.weight_loader
928 weight_loader(param, loaded_weight, shard_id)
929 break
930 else:
931 is_expert_weight = False
932 for (
933 param_name,
934 weight_name,
935 expert_id,
936 expert_shard_id,
937 ) in expert_params_mapping:
938 if weight_name not in name:
939 continue
940 # A checkpoint expert may map to several physical replicas
941 # under EPLB; keep `name` intact and try the next entry
942 # when this physical expert is not local to the rank.
943 is_expert_weight = True
944 name_mapped = name.replace(weight_name, param_name)
945 if is_pp_missing_parameter(name_mapped, self):
946 continue
947 param = params_dict[name_mapped]
948 weight_loader = param.weight_loader
949 success = weight_loader(
950 param,
951 loaded_weight,
952 name_mapped,
953 expert_id=expert_id,
954 shard_id=expert_shard_id,
955 return_success=True,
956 )
957 if success:
958 name = name_mapped
959 break
960 else:
961 if is_expert_weight:
962 continue
963 # Skip loading extra bias for GPTQ models.
964 if (
965 name.endswith(".bias")
966 and name not in params_dict
967 and not _is_linear_attn(self.config)
968 ): # noqa: E501
969 continue
970 # Remapping the name of FP8 kv-scale.
971 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
972 if remapped_name is None:
973 continue
974 name = remapped_name
975 if is_pp_missing_parameter(name, self):
976 continue
977
978 param = params_dict[name]
979 weight_loader = getattr(
980 param, "weight_loader", default_weight_loader
981 )
982 weight_loader(param, loaded_weight, **kwargs)
983 loaded_params.add(name)
984 return loaded_params
985
986
987 class Glm5NextForCausalLM(
988 nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid
989 ):
990 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
991 super().__init__()
992 self.model_config = vllm_config.model_config
993 self.vllm_config = vllm_config
994 self.config = self.model_config.hf_text_config
995 quant_config = vllm_config.quant_config
996 self.quant_config = quant_config
997 self.model = Glm5NextModel(
998 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
999 )
1000 if get_pp_group().is_last_rank:
1001 self.lm_head = ParallelLMHead(
1002 self.config.vocab_size,
1003 self.config.hidden_size,
1004 quant_config=quant_config,
1005 prefix=maybe_prefix(prefix, "lm_head"),
1006 )
1007 else:
1008 self.lm_head = PPMissingLayer()
1009 self.logits_processor = LogitsProcessor(
1010 self.config.vocab_size, scale=_LOGIT_SCALE
1011 )
1012
1013 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1014 return self.model.embed_input_ids(input_ids)
1015
1016 def forward(
1017 self,
1018 input_ids: torch.Tensor | None,
1019 positions: torch.Tensor,
1020 intermediate_tensors: IntermediateTensors | None = None,
1021 inputs_embeds: torch.Tensor | None = None,
1022 **kwargs,
1023 ) -> torch.Tensor | IntermediateTensors:
1024 hidden_states = self.model(
1025 input_ids, positions, intermediate_tensors, inputs_embeds, **kwargs
1026 )
1027 return hidden_states
1028
1029 @classmethod
1030 def get_mamba_state_dtype_from_config(
1031 cls,
1032 vllm_config: "VllmConfig",
1033 ) -> tuple[torch.dtype, torch.dtype]:
1034 return MambaStateDtypeCalculator.kda_state_dtype(
1035 vllm_config.model_config.dtype, vllm_config.cache_config.mamba_cache_dtype
1036 )
1037
1038 @classmethod
1039 def get_mamba_state_shape_from_config(
1040 cls, vllm_config: "VllmConfig"
1041 ) -> tuple[tuple[int, int], tuple[int, int, int]]:
1042 parallel_config = vllm_config.parallel_config
1043 hf_config = vllm_config.model_config.hf_text_config
1044 tp_size = parallel_config.tensor_parallel_size
1045 num_spec = (
1046 vllm_config.speculative_config.num_speculative_tokens
1047 if vllm_config.speculative_config
1048 else 0
1049 )
1050 return MambaStateShapeCalculator.kda_state_shape(
1051 tp_size,
1052 hf_config.linear_num_heads,
1053 hf_config.linear_head_dim,
1054 conv_kernel_size=hf_config.linear_conv_kernel_dim,
1055 num_spec=num_spec,
1056 )
1057
1058 @classmethod
1059 def get_mamba_state_copy_func(
1060 cls,
1061 ) -> tuple[
1062 MambaStateCopyFunc, MambaStateCopyFunc, MambaStateCopyFunc, MambaStateCopyFunc
1063 ]:
1064 return MambaStateCopyFuncCalculator.kda_state_copy_func()
1065
1066 def compute_logits(
1067 self,
1068 hidden_states: torch.Tensor,
1069 ) -> torch.Tensor | None:
1070 logits = self.logits_processor(self.lm_head, hidden_states)
1071 return logits
1072
1073 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1074 loader = AutoWeightsLoader(self)
1075 return loader.load_weights(weights)
1076
1077
1078 @MULTIMODAL_REGISTRY.register_processor(
1079 Glm5NextMultiModalProcessor,
1080 info=Glm5NextProcessingInfo,
1081 dummy_inputs=Glm4vDummyInputsBuilder,
1082 )
1083 class Glm5NextForConditionalGeneration(
1084 Glm4vForConditionalGeneration, HasInnerState, IsHybrid, MixtureOfExperts
1085 ):
1086 # The text model (KDA + dense-MLA + MoE) is a hybrid mamba model. The
1087 # multimodal wrapper must declare the same interfaces so vLLM treats it as
1088 # hybrid (auto-aligns mamba/attention block sizes, sizes the mamba state
1089 # cache); the mamba-state classmethods delegate to the text model.
1090 has_inner_state: ClassVar[Literal[True]] = True
1091 is_hybrid: ClassVar[Literal[True]] = True
1092
1093 # GLM-5.3-Flash stores the dense-MLP gate/up as separate tensors (like
1094 # ``Glm4vMoeForConditionalGeneration``, ``glm4_moe`` and ``deepseek_v2``),
1095 # so the fused ``gate_up_proj`` must expand to its real shard names for
1096 # per-layer quant-scheme resolution. The identity ``gate_up_proj`` entry
1097 # inherited from ``Glm4vForConditionalGeneration`` (pre-fused gate_up_proj)
1098 # would otherwise route the module to ``global_quant_config`` and mismatch
1099 # at load for mixed-precision Quark checkpoints.
1100 packed_modules_mapping = {
1101 "gate_up_proj": ["gate_proj", "up_proj"],
1102 }
1103
1104 # NOTE: weight-prefix mapping is inherited from Glm4vForConditionalGeneration
1105 # (``model.visual.`` -> ``visual.``, ``model.language_model.`` ->
1106 # ``language_model.model.``, ``lm_head.`` -> ``language_model.lm_head.``),
1107 # matching the GLM-OCR / GLM-4V serialization convention. If the real
1108 # checkpoint's safetensors keys differ (e.g. ``language_model.model.`` with
1109 # no outer ``model.``), override ``hf_to_vllm_mapper`` accordingly.
1110
1111 @classmethod
1112 def get_mamba_state_dtype_from_config(cls, vllm_config: VllmConfig):
1113 from .model import Glm5NextForCausalLM
1114
1115 return Glm5NextForCausalLM.get_mamba_state_dtype_from_config(vllm_config)
1116
1117 @classmethod
1118 def get_mamba_state_shape_from_config(cls, vllm_config: VllmConfig):
1119 from .model import Glm5NextForCausalLM
1120
1121 return Glm5NextForCausalLM.get_mamba_state_shape_from_config(vllm_config)
1122
1123 @classmethod
1124 def get_mamba_state_copy_func(cls):
1125 from .model import Glm5NextForCausalLM
1126
1127 return Glm5NextForCausalLM.get_mamba_state_copy_func()
1128
1129 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1130 super(Glm4vForConditionalGeneration, self).__init__()
1131 config = vllm_config.model_config.hf_config
1132 multimodal_config = vllm_config.model_config.multimodal_config
1133 assert multimodal_config is not None
1134
1135 self.config = config
1136 self.model_config = vllm_config.model_config
1137 self.multimodal_config = multimodal_config
1138 self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1139 self.is_multimodal_pruning_enabled = (
1140 multimodal_config.is_multimodal_pruning_enabled()
1141 )
1142
1143 with self._mark_tower_model(vllm_config, {"image", "video"}):
1144 self.visual = Glm5NextVisionTransformer(
1145 config.text_config,
1146 config.vision_config,
1147 norm_eps=_VISION_RMS_NORM_EPS,
1148 # Vision tower ships BF16 weights in this fp8 checkpoint (no
1149 # weight_scale_inv for visual.*), so it must NOT inherit the
1150 # global fp8 quant_config -- doing so incorrectly quantizes
1151 # the tower
1152 # and yields NaN image features. Mirrors the MLA/KDA proj
1153 # pattern (quant_config=None for BF16 submodules).
1154 quant_config=None,
1155 input_norm=build_mm_input_norm(self.model_config),
1156 prefix=maybe_prefix(prefix, "visual"),
1157 )
1158
1159 with self._mark_language_model(vllm_config):
1160 self.language_model = init_vllm_registered_model(
1161 vllm_config=vllm_config,
1162 hf_config=config.text_config,
1163 prefix=maybe_prefix(prefix, "language_model"),
1164 architectures=["Glm5NextForCausalLM"],
1165 )
1166
1167 self.set_moe_parameters()
1168
1169 # Glm5NextForCausalLM does not implement make_empty_intermediate_tensors,
1170 # so pipeline parallelism is gated off (consistent with the text-only
1171 # model) and we intentionally do not alias it here.
1172
1173 def set_moe_parameters(self) -> None:
1174 self.moe_mlp_layers = [
1175 layer.mlp
1176 for layer in self.language_model.model.layers
1177 if isinstance(layer, Glm5NextDecoderLayer)
1178 and isinstance(layer.mlp, Glm5NextMoE)
1179 ]
1180 self.moe_layers = [moe.experts for moe in self.moe_mlp_layers]
1181 self.num_moe_layers = len(self.moe_layers)
1182 if not self.num_moe_layers:
1183 return
1184 example_moe = self.moe_mlp_layers[0]
1185 self.num_expert_groups = self.config.text_config.n_group
1186 self.num_logical_experts = example_moe.n_logical_experts
1187 self.num_physical_experts = example_moe.n_physical_experts
1188 self.num_local_physical_experts = example_moe.n_local_physical_experts
1189 self.num_routed_experts = example_moe.n_routed_experts
1190 self.num_shared_experts = example_moe.n_shared_experts
1191 self.num_redundant_experts = example_moe.n_redundant_experts
1192
1193 def update_physical_experts_metadata(
1194 self,
1195 num_physical_experts: int,
1196 num_local_physical_experts: int,
1197 ) -> None:
1198 if not self.num_moe_layers:
1199 return
1200 assert self.num_local_physical_experts == num_local_physical_experts
1201 self.num_physical_experts = num_physical_experts
1202 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
1203 for moe in self.moe_mlp_layers:
1204 moe.n_physical_experts = num_physical_experts
1205 moe.n_redundant_experts = self.num_redundant_experts
1206 moe.experts.update_expert_map()
1207
1208 def get_encoder_cudagraph_config(self):
1209 # This vision tower does not produce the absolute position embedding
1210 # buffer used by GLM4V.
1211 config = super().get_encoder_cudagraph_config()
1212 config.buffer_keys = [k for k in config.buffer_keys if k != "pos_embeds"]
1213 return config
1214
1215
1216 def get_spec_layer_idx_from_weight_name(
1217 config: Glm5NextTextConfig, weight_name: str
1218 ) -> int | None:
1219 if hasattr(config, "num_nextn_predict_layers") and (
1220 config.num_nextn_predict_layers > 0
1221 ):
1222 layer_idx = config.num_hidden_layers
1223 for i in range(config.num_nextn_predict_layers):
1224 if weight_name.startswith(
1225 f"model.layers.{layer_idx + i}."
1226 ) or weight_name.startswith(f"layers.{layer_idx + i}."):
1227 return layer_idx + i
1228 return None
1229
1230
1231 def _try_load_fp8_indexer_wk(name, tensor, buf, params_dict, loaded_params):
1232 if "indexer.wk." not in name or "wk_weights" in name:
1233 return False
1234 is_weight = name.endswith(".weight") and tensor.dtype == torch.float8_e4m3fn
1235 is_scale = "weight_scale_inv" in name
1236 if not is_weight and not is_scale:
1237 return False
1238 layer_prefix = name.rsplit(".wk.", 1)[0]
1239 entry = buf.setdefault(layer_prefix, {})
1240 entry["weight" if is_weight else "scale"] = tensor
1241 if "weight" not in entry or "scale" not in entry:
1242 return True
1243
1244 weight_fp8, scale_inv = entry["weight"], entry["scale"]
1245 del buf[layer_prefix]
1246 block_size = weight_fp8.shape[1] // scale_inv.shape[1]
1247 weight_bf16 = scaled_dequantize(
1248 weight_fp8,
1249 scale_inv,
1250 group_shape=GroupShape(block_size, block_size),
1251 out_dtype=torch.bfloat16,
1252 )
1253
1254 fused_name = f"{layer_prefix}.wk_weights_proj.weight"
1255 param = params_dict[fused_name]
1256 param.weight_loader(param, weight_bf16, 0)
1257 loaded_params.add(fused_name)
1258 return True
1259
1260
1261 def _dequant_fp8_block(
1262 weight_fp8: torch.Tensor,
1263 scale_inv: torch.Tensor,
1264 block_size: int = 128,
1265 ) -> torch.Tensor:
1266 """Dequantize a block-FP8 (e4m3) weight with per-block scale to BF16.
1267
1268 Unlike ``scaled_dequantize`` this tolerates a non-divisible (partial last
1269 block) shape by zero-padding to a multiple of ``block_size`` before the
1270 scale broadcast and trimming back afterwards (e.g. kv_a_proj_with_mqa is
1271 576 rows = 4*128 + 64).
1272 """
1273 out_dim, in_dim = weight_fp8.shape
1274 pad_out = (-out_dim) % block_size
1275 pad_in = (-in_dim) % block_size
1276 w = weight_fp8
1277 if pad_out or pad_in:
1278 w = torch.nn.functional.pad(w, (0, pad_in, 0, pad_out))
1279 # scale_inv is (ceil(out/block), ceil(in/block)); broadcast to (out, in).
1280 s = scale_inv.to(torch.float32)
1281 s_full = s.repeat_interleave(block_size, dim=0).repeat_interleave(block_size, dim=1)
1282 out = (w.to(torch.float32) * s_full).to(torch.bfloat16)
1283 return out[:out_dim, :in_dim].contiguous()
1284
1285
1286 # FP8 checkpoint projections that the MODEL keeps in BF16, so the block-FP8
1287 # (weight + weight_scale_inv) must be dequantized to BF16 on load.
1288 # Maps checkpoint proj-suffix -> (buffer key, model target base, fused shard id
1289 # or None for a direct projection, whether NoPE rope-padding applies).
1290 _FP8_ATTN_PROJS = {
1291 ".q_a_proj.": ("q_a", "fused_qkv_a_proj", 0, False),
1292 ".kv_a_proj_with_mqa.": ("kv_a", "fused_qkv_a_proj", 1, True),
1293 ".q_b_proj.": ("q_b", "q_b_proj", None, False),
1294 ".o_proj.": ("o_proj", "o_proj", None, False),
1295 }
1296
1297
1298 def _try_load_fp8_attn_proj(
1299 name,
1300 tensor,
1301 buf,
1302 params_dict,
1303 loaded_params,
1304 kv_a_pad_size: int,
1305 ) -> bool:
1306 """Dequantize FP8 q_a_proj / kv_a_proj_with_mqa / o_proj to BF16 on load.
1307
1308 The FP8 checkpoint stores these as block-FP8 (weight + weight_scale_inv),
1309 but the model holds them in BF16 (``fused_qkv_a_proj`` is always BF16 via
1310 DeepSeekV2FusedQkvAProjLinear; ``o_proj`` is excluded by
1311 modules_to_not_convert). When the model target is BF16 (no
1312 ``weight_scale_inv`` param) we dequantize; otherwise we return False so the
1313 normal stacked/direct path loads the FP8 tensor as-is.
1314 """
1315 matched = None
1316 for suffix, info in _FP8_ATTN_PROJS.items():
1317 if suffix in name:
1318 matched = (suffix, info)
1319 break
1320 if matched is None:
1321 return False
1322 suffix, (key, target_base, shard_id, is_kva) = matched
1323 is_weight = name.endswith(".weight") and tensor.dtype == torch.float8_e4m3fn
1324 # Need to accept both the DeepSeek-native ``weight_scale_inv`` and the Quark
1325 # ``weight_scale`` names before feeding the shared block dequant below.
1326 is_scale = "weight_scale_inv" in name or name.endswith(".weight_scale")
1327 if not is_weight and not is_scale:
1328 return False
1329
1330 layer_prefix = name.rsplit(suffix, 1)[0]
1331 target_w = f"{layer_prefix}.{target_base}.weight"
1332 target_s = f"{layer_prefix}.{target_base}.weight_scale_inv"
1333 # If the model actually kept this projection in FP8, let the normal path
1334 # handle it (it has a weight_scale_inv param).
1335 if target_s in params_dict:
1336 return False
1337
1338 entry = buf.setdefault(layer_prefix, {}).setdefault(key, {})
1339 entry["weight" if is_weight else "scale"] = tensor
1340 if "weight" not in entry or "scale" not in entry:
1341 return True
1342
1343 weight_fp8, scale_inv = entry["weight"], entry["scale"]
1344 buf[layer_prefix].pop(key, None)
1345 block_size = weight_fp8.shape[1] // scale_inv.shape[1]
1346 weight_bf16 = _dequant_fp8_block(weight_fp8, scale_inv, block_size)
1347 # NoPE: pad kv_a rope portion (kv_lora_rank -> kv_lora_rank + qk_rope_head_dim).
1348 if is_kva and kv_a_pad_size > 0:
1349 pad = torch.zeros(
1350 kv_a_pad_size,
1351 weight_bf16.shape[1],
1352 dtype=weight_bf16.dtype,
1353 device=weight_bf16.device,
1354 )
1355 weight_bf16 = torch.cat([weight_bf16, pad], dim=0)
1356
1357 param = params_dict[target_w]
1358 if shard_id is None:
1359 param.weight_loader(param, weight_bf16)
1360 else:
1361 param.weight_loader(param, weight_bf16, shard_id)
1362 loaded_params.add(target_w)
1363 return True