main
py 1,636 lines 66.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import typing
4 from collections.abc import Callable, Iterable
5 from functools import partial
6 from itertools import islice
7
8 import regex as re
9 import torch
10 import torch.nn as nn
11
12 import vllm.envs as envs
13 from vllm.config import VllmConfig
14 from vllm.config.kernel import MEGA_MOE_BACKENDS
15 from vllm.distributed import (
16 get_engram_dp_size,
17 get_pp_group,
18 get_tensor_model_parallel_rank,
19 get_tensor_model_parallel_world_size,
20 tensor_model_parallel_all_reduce,
21 )
22 from vllm.forward_context import (
23 get_forward_context,
24 in_piecewise_cudagraph,
25 is_forward_context_available,
26 )
27 from vllm.logger import init_logger
28 from vllm.model_executor.kernels.mhc.tilelang import (
29 mhc_post_tilelang,
30 mhc_pre_delayed_tilelang,
31 )
32 from vllm.model_executor.kernels.mhc.triton import hc_collapse_triton
33 from vllm.model_executor.layers.fused_moe import (
34 fused_moe_make_expert_params_mapping,
35 )
36 from vllm.model_executor.layers.fused_moe.experts.trtllm_mxfp4_moe import (
37 TrtLlmMxfp4ExpertsModular,
38 )
39 from vllm.model_executor.layers.fused_moe.moe_output import (
40 MoEOutput,
41 UnfinalizedMoEOutput,
42 )
43 from vllm.model_executor.layers.fused_moe.runner.moe_runner import _unpack
44 from vllm.model_executor.layers.layernorm import RMSNorm
45 from vllm.model_executor.layers.logits_processor import LogitsProcessor
46 from vllm.model_executor.layers.quantization import QuantizationConfig
47 from vllm.model_executor.layers.vocab_parallel_embedding import (
48 ParallelLMHead,
49 VocabParallelEmbedding,
50 )
51 from vllm.model_executor.model_loader.weight_utils import (
52 default_weight_loader,
53 drop_checkpoint_cache,
54 )
55 from vllm.model_executor.models.interfaces import (
56 EagleModelMixin,
57 MixtureOfExperts,
58 SupportsEagle3,
59 SupportsLoRA,
60 SupportsPP,
61 )
62 from vllm.model_executor.models.utils import (
63 AutoWeightsLoader,
64 PPMissingLayer,
65 WeightsMapper,
66 extract_layer_index,
67 is_pp_missing_parameter,
68 make_layers,
69 maybe_prefix,
70 )
71 from vllm.models.common.ops.sequence_parallel import (
72 sp_all_gather,
73 sp_padding_mask,
74 sp_reduce_scatter,
75 sp_shard,
76 )
77 from vllm.models.deepseek_v4.nvidia.model import (
78 DeepseekV4MoE as DeepseekV4MoEBase,
79 )
80 from vllm.models.deepseek_v4.nvidia.model import (
81 MegaGateRoutingMetadata,
82 make_deepseek_v4_expert_params_mapping,
83 prepare_mega_gate_routing_metadata,
84 )
85 from vllm.models.deepseek_v41.attention import DeepseekV4Attention
86 from vllm.models.deepseek_v41.decoder_replay_layers import DecoderReplayLayers
87 from vllm.models.deepseek_v41.nvidia.flash_mla_mega_attn import (
88 DeepseekV4MegaAttnAttention,
89 )
90 from vllm.models.deepseek_v41.nvidia.flashinfer_sparse import (
91 DeepseekV4FlashInferMLAAttention,
92 DeepseekV4FlashInferSM120Attention,
93 )
94 from vllm.models.deepseek_v41.nvidia.flashmla import DeepseekV4FlashMLAAttention
95 from vllm.platforms import current_platform
96 from vllm.sequence import IntermediateTensors
97 from vllm.utils.math_utils import cdiv
98 from vllm.v1.attention.backends.registry import AttentionBackendEnum
99 from vllm.v1.worker.ubatching import dbo_current_ubatch_id
100
101 from ..common.engram import (
102 Engram,
103 EngramLayout,
104 NgramHashState,
105 can_share_engram_tables,
106 gather_engram_hashes,
107 )
108 from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID, image_sentinel_mask
109 from .ops.mhc import (
110 MHC_OVERLAP_MAX_TOKENS,
111 init_mhc_all_reduce,
112 mhc_pre_delayed_overlap,
113 mhc_shifted_post_pre,
114 supports_mhc_all_reduce,
115 supports_mhc_overlap,
116 )
117
118 if typing.TYPE_CHECKING:
119 from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWAMetadata
120
121 logger = init_logger(__name__)
122
123
124 class DeepseekV4MoE(DeepseekV4MoEBase):
125 def __init__(
126 self,
127 vllm_config: VllmConfig,
128 prefix: str = "",
129 use_sequence_parallel: bool = False,
130 reduce_results: bool = True,
131 ):
132 config = vllm_config.model_config.hf_config
133 n_routed_experts = config.n_routed_experts
134 n_activated_experts = config.num_experts_per_tok
135 if extract_layer_index(prefix) >= config.num_hidden_layers:
136 n_routed_experts = (
137 getattr(config, "dspark_n_routed_experts", 0) or n_routed_experts
138 )
139 n_activated_experts = (
140 getattr(config, "dspark_num_experts_per_tok", 0) or n_activated_experts
141 )
142 super().__init__(
143 vllm_config,
144 prefix=prefix,
145 use_sequence_parallel=use_sequence_parallel,
146 n_routed_experts=n_routed_experts,
147 n_activated_experts=n_activated_experts,
148 num_hash_layers=0,
149 reduce_results=reduce_results,
150 image_sentinel_lo=IMAGE_SENTINEL_BASE_ID,
151 )
152
153 def defer_finalize(self) -> None:
154 """Leave the routed top-k reduction to the next fused all-reduce + mHC.
155
156 Only the modular TRTLLM MXFP4 experts can stop after GEMM2; the fused
157 kernel takes as many tokens as the mHC overlap path.
158 """
159 moe_config = self.experts.moe_config
160 experts_cls = getattr(
161 self.experts.routed_experts.quant_method, "experts_cls", None
162 )
163 if (
164 experts_cls is TrtLlmMxfp4ExpertsModular
165 and self.experts.routed_scaling_factor == 1.0
166 ):
167 moe_config.defer_moe_finalize(MHC_OVERLAP_MAX_TOKENS)
168 if moe_config.use_deferred_moe_finalize:
169 logger.info_once(
170 "DSV4.1 mHC: MoE top-k finalize fused into the all-reduce for "
171 "up to %d tokens.",
172 MHC_OVERLAP_MAX_TOKENS,
173 )
174
175 def defers_finalize(self, num_tokens: int) -> bool:
176 return (
177 not self.use_mega_moe
178 and self.experts.moe_config.should_defer_moe_finalize(num_tokens)
179 )
180
181 def forward_unfinalized(
182 self, hidden_states: torch.Tensor, input_ids: torch.Tensor | None
183 ) -> MoEOutput:
184 """``forward`` with the routed top-k reduction and all-reduce left open."""
185 # The runner's custom op returns tensors only, so run its body directly.
186 shared_output, routed = _unpack(
187 self.experts._forward_impl(
188 hidden_states, hidden_states, hidden_states, input_ids
189 )
190 )
191 assert shared_output is not None
192 assert isinstance(routed, UnfinalizedMoEOutput)
193 return MoEOutput(routed=routed, shared_output=shared_output)
194
195
196 def _select_dsv4_attn_cls(vllm_config: VllmConfig) -> type[DeepseekV4Attention]:
197 """Pick the CUDA sparse-MLA attention class for the configured backend.
198
199 The generic CUDA backend selector does not instantiate DSv4 layers directly,
200 so map generic sparse-MLA choices to the DSv4-specialized attention class.
201 Without an explicit backend: SM12 takes FlashInfer, SM100 takes mega
202 attention where the topology allows it, and everything else keeps the
203 FlashMLA path.
204 """
205 backend = vllm_config.attention_config.backend
206 device_capability = current_platform.get_device_capability()
207 if backend in (
208 AttentionBackendEnum.FLASHINFER_MLA_SPARSE,
209 AttentionBackendEnum.FLASHINFER_MLA_SPARSE_SM120,
210 ):
211 raise ValueError(
212 f"{backend.name} is not a DeepSeek V4.1 attention backend. "
213 "Use FLASHINFER_MLA_SPARSE_DSV41 for DeepSeek V4.1 FlashInfer "
214 "sparse MLA."
215 )
216 if backend in (
217 AttentionBackendEnum.FLASHINFER_MLA_SPARSE_DSV4,
218 AttentionBackendEnum.FLASHINFER_MLA_SPARSE_DSV41,
219 ):
220 if device_capability is not None and device_capability.major == 12:
221 return DeepseekV4FlashInferSM120Attention
222 return DeepseekV4FlashInferMLAAttention
223 if backend is AttentionBackendEnum.FLASHMLA_MEGA_ATTN_DSV41:
224 return DeepseekV4MegaAttnAttention
225 if backend in (
226 AttentionBackendEnum.FLASHMLA_SPARSE,
227 AttentionBackendEnum.FLASHMLA_SPARSE_DSV4,
228 AttentionBackendEnum.FLASHMLA_SPARSE_DSV41,
229 ):
230 return DeepseekV4FlashMLAAttention
231
232 if device_capability is not None and device_capability.major == 12:
233 return DeepseekV4FlashInferSM120Attention
234 # Mega attention is the SM100 default: it fuses Q RoPE, sparse attention,
235 # the output's inverse RoPE and its FP8 cast into one launch, and brings
236 # the 288 B NVFP4 compressed record -- the format the reference
237 # implementation itself stores. It declines topologies it cannot serve
238 # (non-SM100, TP that leaves fewer than WV_GROUP_SIZE heads per wo_a
239 # group, a build without the kernel), which then fall through to FlashMLA.
240 if DeepseekV4MegaAttnAttention.is_available_for(vllm_config):
241 return DeepseekV4MegaAttnAttention
242 return DeepseekV4FlashMLAAttention
243
244
245 def _use_sequence_parallel(vllm_config: VllmConfig) -> bool:
246 parallel_config = vllm_config.parallel_config
247 use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
248 return (
249 parallel_config.pipeline_parallel_size == 1
250 and parallel_config.enable_expert_parallel
251 and parallel_config.tensor_parallel_size > 1
252 and (use_mega_moe or parallel_config.data_parallel_size > 1)
253 )
254
255
256 def maybe_init_gemm_rs(vllm_config: VllmConfig, use_sequence_parallel: bool) -> bool:
257 """Set up the fused ``wo_b`` GEMM + reduce-scatter when opted in.
258
259 Sequence parallel is the only topology where ``wo_b`` ends in a
260 reduce-scatter, so the kernel is bound to RS mode. The workspace is a
261 process-wide singleton shared with any other model code in this worker
262 (the DSpark drafter reuses it), and its NVLink multicast rendezvous is
263 collective, so every TP rank must take the same decision here.
264 """
265 if not (use_sequence_parallel and envs.VLLM_ENABLE_GEMM_RS):
266 return False
267
268 # The kernel module pulls in cute_dsl, so import it only once opted in.
269 from vllm.model_executor.kernels.linear.cute_dsl.gemm_rs_ar import (
270 maybe_init_gemm_rs_ar,
271 )
272
273 hidden_size = vllm_config.model_config.hf_config.hidden_size
274 if not maybe_init_gemm_rs_ar(vllm_config, N=hidden_size, all_reduce=False):
275 return False
276 logger.info_once("To disable DeepSeek-V4.1 GEMM-RS, set VLLM_ENABLE_GEMM_RS=0.")
277 return True
278
279
280 class DeepseekV4DecoderLayer(nn.Module):
281 def __init__(
282 self,
283 vllm_config,
284 prefix,
285 topk_indices_buffer: torch.Tensor | None = None,
286 aux_stream_list: list[torch.cuda.Stream] | None = None,
287 candidate_block_buffer: torch.Tensor | None = None,
288 engram_layout: EngramLayout | None = None,
289 engram_prefetch_stream: torch.cuda.Stream | None = None,
290 run_gemm_rs: bool = False,
291 mhc_stream: torch.cuda.Stream | None = None,
292 fuse_mhc_all_reduce: bool = False,
293 ):
294 super().__init__()
295
296 config = vllm_config.model_config.hf_config
297 self.hidden_size = config.hidden_size
298 self.mhc_stream = mhc_stream
299 self.fuse_mhc_all_reduce = fuse_mhc_all_reduce
300 self.use_sequence_parallel = _use_sequence_parallel(vllm_config)
301
302 self.engram: Engram | None = None
303 if engram_layout is not None:
304 layer_id = extract_layer_index(prefix)
305 if layer_id in engram_layout.layer_ids:
306 self.engram = Engram(
307 config,
308 vllm_config.quant_config,
309 engram_layout,
310 engram_layout.layer_ids.index(layer_id),
311 use_sequence_parallel=self.use_sequence_parallel,
312 prefix=f"{prefix}.engram",
313 prefetch_stream=engram_prefetch_stream,
314 )
315
316 self.rms_norm_eps = config.rms_norm_eps
317 self.attn = _select_dsv4_attn_cls(vllm_config)(
318 vllm_config,
319 prefix=f"{prefix}.attn",
320 topk_indices_buffer=topk_indices_buffer,
321 aux_stream_list=aux_stream_list,
322 candidate_block_buffer=candidate_block_buffer,
323 )
324 if self.use_sequence_parallel or fuse_mhc_all_reduce:
325 self.attn.wo_b.reduce_results = False
326 if run_gemm_rs:
327 # Binds only when wo_b's kernel and shape qualify; otherwise
328 # forward keeps the separate reduce-scatter below.
329 self.attn.bind_gemm_rs()
330 self.ffn = DeepseekV4MoE(
331 vllm_config,
332 prefix=f"{prefix}.ffn",
333 use_sequence_parallel=self.use_sequence_parallel,
334 reduce_results=not fuse_mhc_all_reduce,
335 )
336
337 self.attn_norm = RMSNorm(self.hidden_size, self.rms_norm_eps)
338 self.ffn_norm = RMSNorm(self.hidden_size, self.rms_norm_eps)
339 self.hc_mult = config.hc_mult
340 self.hc_sinkhorn_iters = config.hc_sinkhorn_iters
341 self.hc_eps = config.hc_eps
342 self.hc_post_alpha = 2.0
343 if vllm_config.kernel_config.enable_jit_warmup and current_platform.is_cuda():
344 from vllm.model_executor.kernels.mhc.tilelang_kernels import (
345 mhc_fused_post_pre_splits,
346 )
347 from vllm.model_executor.kernels.mhc.warmup import MHC_PRE_NORM_KERNEL
348
349 max_tokens = vllm_config.scheduler_config.max_num_batched_tokens
350 if self.use_sequence_parallel:
351 tp_size = vllm_config.parallel_config.tensor_parallel_size
352 max_tokens = (max_tokens + tp_size - 1) // tp_size
353 # The epilogue compiles per projection width and per pre-mix mode.
354 # The first layer projects the broadcast embedding, so it reads one
355 # hidden_size-wide row and selects stream zero; every later sublayer
356 # projects the full hc stream, and collapses it with the pre-mix the
357 # previous sublayer carried in. Those later shapes also arrive
358 # through the fused post + pre-norm GEMM, which picks split-k
359 # factors the token sweep below never produces.
360 broadcast_embedding = {
361 "rms_numel": self.hidden_size,
362 "use_pre_mix_in": False,
363 "extra_splits": (),
364 }
365 hc_stream = {
366 "rms_numel": self.hc_mult * self.hidden_size,
367 "extra_splits": mhc_fused_post_pre_splits(
368 self.hidden_size, self.hc_mult
369 ),
370 }
371 variants = [
372 broadcast_embedding,
373 {**hc_stream, "use_pre_mix_in": False},
374 {**hc_stream, "use_pre_mix_in": True},
375 ]
376 if vllm_config.speculative_config is not None:
377 # Draft setups read aux hidden states out of the same collapse,
378 # which is its own specialization of the kernel.
379 variants.append(
380 {**hc_stream, "use_pre_mix_in": True, "write_aux": True}
381 )
382 # Shifted mHC splits the epilogue in two: the input collapse on the
383 # caller stream and coefficient generation on a side stream.
384 split_modes = (
385 ("fused", "stats", "input") if mhc_stream is not None else ("fused",)
386 )
387 for variant in variants:
388 for split_mode in split_modes:
389 MHC_PRE_NORM_KERNEL.register_warmup(
390 split_mode=split_mode,
391 max_tokens=(
392 max_tokens
393 if split_mode == "fused"
394 else min(max_tokens, MHC_OVERLAP_MAX_TOKENS)
395 ),
396 hidden_size=self.hidden_size,
397 rms_eps=self.rms_norm_eps,
398 hc_pre_eps=self.hc_eps,
399 hc_sinkhorn_eps=self.hc_eps,
400 hc_post_mult_value=self.hc_post_alpha,
401 sinkhorn_repeat=self.hc_sinkhorn_iters,
402 norm_eps=self.rms_norm_eps,
403 hc_mult=self.hc_mult,
404 **variant,
405 )
406 mix_hc = (2 + self.hc_mult) * self.hc_mult
407 hc_dim = self.hc_mult * self.hidden_size
408 self.hc_attn_fn = nn.Parameter(
409 torch.empty(
410 (mix_hc, hc_dim),
411 dtype=torch.float32,
412 ),
413 requires_grad=False,
414 )
415 self.hc_attn_fn_broadcast: torch.Tensor | None = None
416 self.hc_ffn_fn = nn.Parameter(
417 torch.empty(
418 (mix_hc, hc_dim),
419 dtype=torch.float32,
420 ),
421 requires_grad=False,
422 )
423 self.hc_attn_base = nn.Parameter(
424 torch.empty(
425 mix_hc,
426 dtype=torch.float32,
427 ),
428 requires_grad=False,
429 )
430 self.hc_ffn_base = nn.Parameter(
431 torch.empty(
432 mix_hc,
433 dtype=torch.float32,
434 ),
435 requires_grad=False,
436 )
437 self.hc_attn_scale = nn.Parameter(
438 torch.empty(
439 3,
440 dtype=torch.float32,
441 ),
442 requires_grad=False,
443 )
444 self.hc_ffn_scale = nn.Parameter(
445 torch.empty(
446 3,
447 dtype=torch.float32,
448 ),
449 requires_grad=False,
450 )
451
452 def forward(
453 self,
454 x: torch.Tensor | MoEOutput,
455 positions: torch.Tensor,
456 input_ids: torch.Tensor | None,
457 pre_mix: torch.Tensor | None = None,
458 post_mix: torch.Tensor | None = None,
459 res_mix: torch.Tensor | None = None,
460 residual: torch.Tensor | None = None,
461 engram_hashes: torch.Tensor | None = None,
462 engram_mask: torch.Tensor | None = None,
463 *,
464 capture_previous_aux: bool = False,
465 mega_gate_metadata: MegaGateRoutingMetadata | None = None,
466 ) -> tuple[
467 torch.Tensor | MoEOutput,
468 torch.Tensor,
469 torch.Tensor,
470 torch.Tensor,
471 torch.Tensor,
472 torch.Tensor | None,
473 ]:
474 previous_aux: torch.Tensor | None = None
475 mhc_stream = self.mhc_stream
476 if mhc_stream is not None and (
477 in_piecewise_cudagraph()
478 or not 0 < positions.shape[0] <= MHC_OVERLAP_MAX_TOKENS
479 or not torch.cuda.is_current_stream_capturing()
480 ):
481 # Use overlap only in FULL graphs; eager warmup initializes Mega mHC.
482 mhc_stream = None
483 mhc_pre = (
484 partial(mhc_pre_delayed_overlap, stream=mhc_stream)
485 if mhc_stream is not None
486 else mhc_pre_delayed_tilelang
487 )
488 # The reference collapses each sublayer's input with the *previous*
489 # sublayer's pre-mix: attention uses the pre-mix carried in (identity
490 # for the first layer), the FFN uses this layer's attention pre-mix.
491 if residual is None:
492 assert isinstance(x, torch.Tensor)
493 if x.dim() == 2:
494 # First layer: the stream is the embedding broadcast to hc
495 # copies and the identity pre-mix selects copy 0.
496 assert self.hc_attn_fn_broadcast is not None
497 residual = x.unsqueeze(1).expand(-1, self.hc_mult, -1).contiguous()
498 post_mix, res_mix, x, attn_pre = mhc_pre(
499 residual,
500 self.hc_attn_fn_broadcast,
501 self.hc_attn_scale,
502 self.hc_attn_base,
503 self.rms_norm_eps,
504 self.hc_eps,
505 self.hc_eps,
506 self.hc_post_alpha,
507 self.hc_sinkhorn_iters,
508 x=x,
509 norm_weight=self.attn_norm.weight,
510 norm_eps=self.attn_norm.variance_epsilon,
511 )
512 else:
513 residual = x
514 post_mix, res_mix, x, attn_pre = mhc_pre(
515 residual,
516 self.hc_attn_fn,
517 self.hc_attn_scale,
518 self.hc_attn_base,
519 self.rms_norm_eps,
520 self.hc_eps,
521 self.hc_eps,
522 self.hc_post_alpha,
523 self.hc_sinkhorn_iters,
524 pre_mix=pre_mix,
525 norm_weight=self.attn_norm.weight,
526 norm_eps=self.attn_norm.variance_epsilon,
527 )
528 elif self.engram is not None and engram_hashes is not None:
529 # Engram injection happens between the previous sublayer's post
530 # and this block's pre, on the full hc stream, so the mix
531 # coefficients see the injected stream. The injection also keeps
532 # the post out of the pre-norm GEMM's fused prologue. The layer
533 # before an engram layer finalizes its own MoE.
534 assert isinstance(x, torch.Tensor)
535 if self.fuse_mhc_all_reduce:
536 x = tensor_model_parallel_all_reduce(x)
537 previous_post = mhc_post_tilelang(x, residual, post_mix, res_mix)
538 if capture_previous_aux:
539 previous_aux = previous_post.mean(dim=1)
540 residual = self.engram(
541 previous_post,
542 engram_hashes[:, self.engram.layer_hash_index],
543 engram_mask,
544 )
545 post_mix, res_mix, x, attn_pre = mhc_pre(
546 residual,
547 self.hc_attn_fn,
548 self.hc_attn_scale,
549 self.hc_attn_base,
550 self.rms_norm_eps,
551 self.hc_eps,
552 self.hc_eps,
553 self.hc_post_alpha,
554 self.hc_sinkhorn_iters,
555 pre_mix=pre_mix,
556 norm_weight=self.attn_norm.weight,
557 norm_eps=self.attn_norm.variance_epsilon,
558 )
559 else:
560 # The collapse already reads the post-mapped streams, so the mean
561 # aux consumers want comes out of the same kernel.
562 residual, post_mix, res_mix, x, attn_pre, aux = mhc_shifted_post_pre(
563 x,
564 residual,
565 post_mix,
566 res_mix,
567 self.hc_attn_fn,
568 self.hc_attn_scale,
569 self.hc_attn_base,
570 self.rms_norm_eps,
571 self.hc_eps,
572 self.hc_eps,
573 self.hc_post_alpha,
574 self.hc_sinkhorn_iters,
575 pre_mix=pre_mix,
576 norm_weight=self.attn_norm.weight,
577 norm_eps=self.attn_norm.variance_epsilon,
578 capture_aux=capture_previous_aux,
579 stream=mhc_stream,
580 reduce_results=self.fuse_mhc_all_reduce,
581 )
582 if capture_previous_aux:
583 previous_aux = aux
584
585 if self.use_sequence_parallel:
586 x = sp_all_gather(x)[: positions.shape[0]]
587
588 x = self.attn(positions, x, None)
589 # With GEMM-RS bound, the attention output is already this rank's
590 # sequence-parallel shard (see DeepseekV4Attention._wo_b_proj).
591 if self.use_sequence_parallel and self.attn.gemm_rs is None:
592 x = sp_reduce_scatter(x)
593
594 if mhc_stream is not None:
595 torch.cuda.current_stream().wait_stream(mhc_stream)
596 residual, post_mix, res_mix, x, ffn_pre, _ = mhc_shifted_post_pre(
597 x,
598 residual,
599 post_mix,
600 res_mix,
601 self.hc_ffn_fn,
602 self.hc_ffn_scale,
603 self.hc_ffn_base,
604 self.rms_norm_eps,
605 self.hc_eps,
606 self.hc_eps,
607 self.hc_post_alpha,
608 self.hc_sinkhorn_iters,
609 pre_mix=attn_pre,
610 norm_weight=self.ffn_norm.weight,
611 norm_eps=self.ffn_norm.variance_epsilon,
612 stream=mhc_stream,
613 reduce_results=self.fuse_mhc_all_reduce,
614 )
615 if self.fuse_mhc_all_reduce and self.ffn.defers_finalize(x.shape[0]):
616 # The next layer's first boundary finalizes it with the all-reduce.
617 x = self.ffn.forward_unfinalized(x, input_ids)
618 else:
619 x = self.ffn(x, input_ids, mega_gate_metadata)
620 if mhc_stream is not None:
621 torch.cuda.current_stream().wait_stream(mhc_stream)
622 return x, residual, post_mix, res_mix, ffn_pre, previous_aux
623
624
625 class DeepseekV4Model(nn.Module, EagleModelMixin):
626 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
627 super().__init__()
628
629 config = vllm_config.model_config.hf_config
630 quant_config = vllm_config.quant_config
631 self.config = config
632 self.quant_config = quant_config
633 self.parallel_config = vllm_config.parallel_config
634 self.use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
635 self.use_sequence_parallel = _use_sequence_parallel(vllm_config)
636 if self.use_mega_moe and not vllm_config.parallel_config.enable_expert_parallel:
637 raise NotImplementedError(
638 "DeepSeek V4 MegaMoE currently requires expert parallel. "
639 "Enable it with --enable-expert-parallel, or pick a different "
640 "moe backend."
641 )
642 self.vocab_size = config.vocab_size
643 self.hc_eps = config.hc_eps
644 self.hc_mult = config.hc_mult
645 self.hc_dim = self.hc_mult * config.hidden_size
646 self.rms_norm_eps = config.rms_norm_eps
647
648 # Three aux streams: one per non-default input GEMM in
649 # DeepseekV4Attention._run_parallel_input_projections
650 # (compressor kv_score, indexer.weights_proj). fused_wqa_wkv stays on
651 # the default stream.
652 aux_stream_list = [torch.cuda.Stream() for _ in range(3)]
653 # Keep mHC independent of the streams used inside attention.
654 mhc_stream = torch.cuda.Stream() if supports_mhc_overlap(vllm_config) else None
655 self.fuse_mhc_all_reduce = mhc_stream is not None and supports_mhc_all_reduce(
656 vllm_config
657 )
658 if self.fuse_mhc_all_reduce:
659 init_mhc_all_reduce(vllm_config)
660
661 # Reserved topk indices buffer for all Indexer layers to reuse.
662 self.topk_indices_buffer = torch.empty(
663 vllm_config.scheduler_config.max_num_batched_tokens,
664 config.index_topk,
665 dtype=torch.int32,
666 )
667
668 # Two-level candidate filtering: the indexer at
669 # candidate_source_layer_id publishes the top candidate blocks of
670 # compressed positions here; later ratio-1 indexers (24/28/32/36)
671 # mask their scores with it.
672 candidate_source_layer = getattr(config, "candidate_source_layer_id", -1)
673 candidate_topk_blocks = getattr(config, "candidate_topk_blocks", 0)
674 if candidate_source_layer >= 0 and candidate_topk_blocks > 0:
675 self.candidate_block_buffer = torch.empty(
676 vllm_config.scheduler_config.max_num_batched_tokens,
677 candidate_topk_blocks,
678 dtype=torch.int32,
679 )
680 else:
681 self.candidate_block_buffer = None
682
683 if get_pp_group().is_first_rank:
684 self.embed_tokens = VocabParallelEmbedding(
685 config.vocab_size,
686 config.hidden_size,
687 quant_config=quant_config,
688 prefix=f"{prefix}.embed_tokens",
689 )
690 else:
691 self.embed_tokens = PPMissingLayer()
692
693 self.engram_layout = EngramLayout.from_config(config)
694 engram_config = vllm_config.engram_config
695 self.vllm_config = vllm_config
696 engram_prefetch_stream = (
697 torch.cuda.Stream()
698 if self.engram_layout is not None
699 and engram_config is not None
700 and engram_config.cpu_offload
701 else None
702 )
703 if (
704 self.engram_layout is not None
705 and engram_config is not None
706 and engram_config.dp_shared_memory
707 ):
708 engram_config.dp_shared_memory = can_share_engram_tables(self.engram_layout)
709
710 if self.engram_layout is not None and engram_config and engram_config.use_thp:
711 # Release old checkpoint cache before allocating the Engram host tables.
712 model_config = vllm_config.model_config
713 drop_checkpoint_cache(
714 model_config.model_weights or model_config.model,
715 revision=model_config.revision,
716 cache_dir=vllm_config.load_config.download_dir,
717 )
718
719 # GEMM-RS uses NCCL symmetric-memory multicast, which requires all TP
720 # ranks to belong to one NVLink domain. Collective: run before layers.
721 self.run_gemm_rs = maybe_init_gemm_rs(vllm_config, self.use_sequence_parallel)
722
723 self.start_layer, self.end_layer, self.layers = make_layers(
724 config.num_hidden_layers,
725 lambda prefix: DeepseekV4DecoderLayer(
726 vllm_config,
727 prefix=prefix,
728 topk_indices_buffer=self.topk_indices_buffer,
729 aux_stream_list=aux_stream_list,
730 candidate_block_buffer=self.candidate_block_buffer,
731 engram_layout=self.engram_layout,
732 engram_prefetch_stream=engram_prefetch_stream,
733 run_gemm_rs=self.run_gemm_rs,
734 mhc_stream=mhc_stream,
735 fuse_mhc_all_reduce=self.fuse_mhc_all_reduce,
736 ),
737 prefix=f"{prefix}.layers",
738 )
739 if self.fuse_mhc_all_reduce:
740 # A MoE's top-k finalize folds into the next layer's first mHC
741 # boundary, which the last local layer lacks and an engram layer
742 # replaces with its own all-reduce.
743 local_layers = list(islice(self.layers, self.start_layer, self.end_layer))
744 for layer, successor in zip(local_layers, local_layers[1:]):
745 assert isinstance(layer, DeepseekV4DecoderLayer)
746 assert isinstance(successor, DeepseekV4DecoderLayer)
747 if successor.engram is None:
748 layer.ffn.defer_finalize()
749
750 # Decoder-side SWA bounded replay: in eager prefill steps the layers past
751 # the last KV source run on each request's trailing window only
752 # (decoder_replay_layers.py).
753 self.decoder_replay_layers: DecoderReplayLayers | None = None
754 self.decoder_replay_start = self.end_layer
755 cut = max(config.kv_source_layer_ids)
756 if (
757 cut < self.end_layer - 1
758 and self._decoder_replay_supported(vllm_config, cut)
759 and self.layers[cut].attn.swa_cache_layer.bounded_replay
760 ):
761 self.decoder_replay_start = cut + 1
762 self.decoder_replay_layers = DecoderReplayLayers(
763 config.sliding_window,
764 self._run_replay_layers,
765 [
766 buf
767 for buf in (self.topk_indices_buffer, self.candidate_block_buffer)
768 if buf is not None
769 ],
770 )
771 logger.info_once(
772 "Decoder SWA bounded replay: in eager prefill steps, layers "
773 "%d-%d run on each request's last %d tokens only.",
774 cut + 1,
775 self.end_layer - 1,
776 config.sliding_window,
777 )
778
779 # The n-gram hash needs a slot-keyed rolling store of compressed ids
780 # (chunked prefill / decode lookback); key it off the first local
781 # layer's sliding-window KV cache. Only PP ranks owning an engram
782 # layer need it.
783 self.engram_hash: NgramHashState | None = None
784 self.engram_dp_shared_memory = bool(
785 vllm_config.engram_config and vllm_config.engram_config.dp_shared_memory
786 )
787 self.engram_swa_prefix: str | None = None
788 if self.engram_layout is not None:
789 local_engram = any(
790 isinstance(layer, DeepseekV4DecoderLayer) and layer.engram is not None
791 for layer in islice(self.layers, self.start_layer, self.end_layer)
792 )
793 if local_engram:
794 first_layer = next(
795 iter(islice(self.layers, self.start_layer, self.end_layer))
796 )
797 swa_cache_module = first_layer.attn.swa_cache_layer
798 self.engram_hash = NgramHashState(
799 vllm_config, self.engram_layout, swa_cache_module
800 )
801 self.engram_swa_prefix = swa_cache_module.prefix
802
803 if get_pp_group().is_last_rank:
804 self.norm = RMSNorm(config.hidden_size, self.rms_norm_eps)
805 else:
806 self.norm = PPMissingLayer()
807
808 spec_config = vllm_config.speculative_config
809 needs_mtp_hidden_states = spec_config is not None and (
810 spec_config.use_eagle() or spec_config.uses_draft_model()
811 )
812 if get_pp_group().is_last_rank and needs_mtp_hidden_states:
813 self._mtp_hidden_buffer = torch.empty(
814 vllm_config.scheduler_config.max_num_batched_tokens,
815 self.hc_dim,
816 dtype=vllm_config.model_config.dtype,
817 )
818 else:
819 self._mtp_hidden_buffer = None
820
821 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
822 return self.embed_tokens(input_ids)
823
824 def make_empty_intermediate_tensors(
825 self,
826 batch_size: int,
827 dtype: torch.dtype,
828 device: torch.device,
829 ) -> IntermediateTensors:
830 # PP intermediate tensors carry the multi-stream hidden_states
831 # of shape (num_tokens, hc_mult, hidden_size) — V4 expands the
832 # token embedding to hc_mult streams before the first decoder
833 # layer and keeps that shape until the final hc collapse — plus the
834 # (num_tokens, hc_mult) pre-mix the next rank's first layer needs
835 # for its attention collapse.
836 return IntermediateTensors(
837 {
838 "hidden_states": torch.zeros(
839 (batch_size, self.hc_mult, self.config.hidden_size),
840 dtype=dtype,
841 device=device,
842 ),
843 "pre_mix": torch.zeros(
844 (batch_size, self.hc_mult),
845 dtype=torch.float32,
846 device=device,
847 ),
848 }
849 )
850
851 def forward(
852 self,
853 input_ids: torch.Tensor,
854 positions: torch.Tensor,
855 intermediate_tensors: IntermediateTensors | None,
856 inputs_embeds: torch.Tensor | None = None,
857 lookback_token_ids: torch.Tensor | None = None,
858 ) -> torch.Tensor | IntermediateTensors:
859 if get_pp_group().is_first_rank:
860 if inputs_embeds is not None:
861 hidden_states = inputs_embeds
862 else:
863 hidden_states = self.embed_input_ids(input_ids)
864 else:
865 assert intermediate_tensors is not None
866 hidden_states = intermediate_tensors["hidden_states"]
867
868 if self.use_mega_moe:
869 input_ids = input_ids.to(torch.int64)
870
871 # Engram n-gram hashes for the whole (flattened) batch, computed once
872 # on the full token stream — before any sequence-parallel sharding —
873 # and consumed by the engram layers (1 and 14) below. Skipped on
874 # profile runs (KV cache unbound).
875 engram_hashes: torch.Tensor | None = None
876 engram_mask: torch.Tensor | None = None
877 if (
878 self.engram_hash is not None
879 and input_ids is not None
880 and is_forward_context_available()
881 ):
882 attn_metadata = get_forward_context().attn_metadata
883 if isinstance(attn_metadata, list):
884 attn_metadata = attn_metadata[dbo_current_ubatch_id()]
885 if isinstance(attn_metadata, dict) and self.engram_hash.ensure_cache():
886 assert self.engram_swa_prefix is not None
887 swa_metadata = typing.cast(
888 "DeepseekSparseSWAMetadata", attn_metadata[self.engram_swa_prefix]
889 )
890 # Image-span tokens are dead: they break n-grams (hash op
891 # takes True=dead) and their gate is zeroed (Engram.forward
892 # takes True=keep).
893 image_mask = image_sentinel_mask(input_ids)
894 engram_mask = ~image_mask
895 if lookback_token_ids is None:
896 if not self.engram_hash.use_slot_cache:
897 raise NotImplementedError(
898 "engram needs `lookback_token_ids` from the model "
899 "runner (the DBO/ubatch wrapper drops model kwargs)"
900 )
901 num_reqs = swa_metadata.num_decodes + swa_metadata.num_prefills
902 lookback_token_ids = input_ids.new_full(
903 (num_reqs, self.engram_hash.lookback_depth), -1
904 )
905 engram_hashes = self.engram_hash(
906 input_ids,
907 positions,
908 swa_metadata.query_start_loc,
909 image_mask,
910 lookback_token_ids,
911 image_sentinel_mask(lookback_token_ids),
912 swa_metadata.slot_mapping,
913 swa_metadata.block_table,
914 )
915 elif not self.engram_dp_shared_memory and get_engram_dp_size() > 1:
916 # DP-sharded lookups are collective, so a replica skipping the
917 # hash still has to reach them.
918 engram_hashes, engram_mask = self.engram_hash.dummy_hashes(input_ids)
919 if engram_hashes is not None:
920 # Gather all Engram rows before entering the decoder layers.
921 # One gather feeds every layer sharing the DP-split table.
922 gathered_hashes = gather_engram_hashes(
923 engram_hashes, dp_shared_memory=self.engram_dp_shared_memory
924 )
925 for layer in islice(self.layers, self.start_layer, self.end_layer):
926 engram = getattr(layer, "engram", None)
927 if engram is not None:
928 engram.prepare_embeddings(
929 gathered_hashes[:, engram.layer_hash_index]
930 )
931
932 full_num_tokens = positions.shape[0]
933 if self.use_sequence_parallel:
934 if envs.VLLM_MOE_SKIP_PADDING and is_forward_context_available():
935 forward_context = get_forward_context()
936 forward_context.is_padding = sp_padding_mask(
937 forward_context.is_padding, hidden_states
938 )
939 hidden_states = sp_shard(hidden_states)
940 input_ids = sp_shard(input_ids)
941
942 residual, post_mix, res_mix = None, None, None
943 pre_mix: torch.Tensor | None = None
944 if not get_pp_group().is_first_rank:
945 assert intermediate_tensors is not None
946 pre_mix = intermediate_tensors["pre_mix"]
947 aux_hidden_by_layer: dict[int, torch.Tensor] = {}
948 hidden_states, residual, post_mix, res_mix, pre_mix = self._run_layers(
949 range(self.start_layer, self.decoder_replay_start),
950 hidden_states,
951 positions,
952 input_ids,
953 pre_mix,
954 post_mix,
955 res_mix,
956 residual,
957 aux_hidden_by_layer,
958 engram_hashes,
959 engram_mask,
960 )
961 late_aux: list[torch.Tensor] = []
962 if self.decoder_replay_layers is not None:
963 hidden_states, pre_mix, *late_aux = self.decoder_replay_layers(
964 hidden_states,
965 positions,
966 input_ids,
967 pre_mix,
968 post_mix,
969 res_mix,
970 residual,
971 )
972 else:
973 hidden_states = self._collapse(
974 hidden_states,
975 residual,
976 post_mix,
977 res_mix,
978 aux_hidden_by_layer,
979 full_num_tokens,
980 )
981
982 aux_hidden_states = [
983 aux_hidden_by_layer[layer_id]
984 for layer_id in self.aux_hidden_state_layers
985 if layer_id in aux_hidden_by_layer
986 ] + late_aux
987
988 if not get_pp_group().is_last_rank:
989 return IntermediateTensors(
990 {"hidden_states": hidden_states, "pre_mix": pre_mix}
991 )
992
993 # MTP needs full HC states; otherwise collapse and normalize locally
994 # before gathering to reduce communication.
995 if self._mtp_hidden_buffer is not None:
996 if self.use_sequence_parallel:
997 hidden_states = sp_all_gather(hidden_states)[:full_num_tokens]
998 pre_mix = sp_all_gather(pre_mix)[:full_num_tokens]
999 num_tokens = hidden_states.shape[0]
1000 self._mtp_hidden_buffer[:num_tokens].copy_(hidden_states.flatten(1))
1001
1002 # Collapse the hc copies with the pre-mix from the last layer's FFN
1003 # mixes — the mix the reference applies via
1004 # ``last_layer.hc_pre(h, pre_mix)`` (v4.1 has no learned hc_head).
1005 assert pre_mix is not None
1006 hidden_states = hc_collapse_triton(hidden_states, pre_mix)
1007 hidden_states = self.norm(hidden_states)
1008 if self.use_sequence_parallel and self._mtp_hidden_buffer is None:
1009 # Without MTP, gather only the collapsed and normalized hidden states.
1010 hidden_states = sp_all_gather(hidden_states)[:full_num_tokens]
1011 if len(aux_hidden_states) > 0:
1012 return hidden_states, aux_hidden_states
1013 return hidden_states
1014
1015 def _mega_gate_metadata(
1016 self, input_ids: torch.Tensor | None
1017 ) -> MegaGateRoutingMetadata | None:
1018 if not self.use_mega_moe:
1019 return None
1020 assert input_ids is not None
1021 return prepare_mega_gate_routing_metadata(
1022 input_ids,
1023 has_hash_routing=False,
1024 image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
1025 if getattr(self.config, "vision_n_layers", 0) > 0
1026 else None,
1027 )
1028
1029 def _run_layers(
1030 self,
1031 layer_ids: range,
1032 hidden_states: torch.Tensor | MoEOutput,
1033 positions: torch.Tensor,
1034 input_ids: torch.Tensor | None,
1035 pre_mix: torch.Tensor | None,
1036 post_mix: torch.Tensor | None,
1037 res_mix: torch.Tensor | None,
1038 residual: torch.Tensor | None,
1039 aux_hidden_by_layer: dict[int, torch.Tensor],
1040 engram_hashes: torch.Tensor | None = None,
1041 engram_mask: torch.Tensor | None = None,
1042 ) -> tuple[
1043 torch.Tensor | MoEOutput, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor
1044 ]:
1045 # Every layer's post runs inside the next layer's fused pre, so aux
1046 # hidden states are read back from there instead of recomputed.
1047 full_num_tokens = positions.shape[0]
1048 mega_gate_metadata = self._mega_gate_metadata(input_ids)
1049 for idx in layer_ids:
1050 hidden_states, residual, post_mix, res_mix, pre_mix, previous_aux = (
1051 self.layers[idx](
1052 hidden_states,
1053 positions,
1054 input_ids,
1055 pre_mix,
1056 post_mix,
1057 res_mix,
1058 residual,
1059 engram_hashes,
1060 engram_mask,
1061 capture_previous_aux=idx in self.aux_hidden_state_layers,
1062 mega_gate_metadata=mega_gate_metadata,
1063 )
1064 )
1065 if previous_aux is not None:
1066 # idx is the one-based id of the layer whose post this is.
1067 if self.use_sequence_parallel:
1068 previous_aux = sp_all_gather(previous_aux)[:full_num_tokens]
1069 aux_hidden_by_layer[idx] = previous_aux
1070 assert residual is not None and post_mix is not None
1071 assert res_mix is not None and pre_mix is not None
1072 return hidden_states, residual, post_mix, res_mix, pre_mix
1073
1074 def _collapse(
1075 self,
1076 hidden_states: torch.Tensor | MoEOutput,
1077 residual: torch.Tensor,
1078 post_mix: torch.Tensor,
1079 res_mix: torch.Tensor,
1080 aux_hidden_by_layer: dict[int, torch.Tensor],
1081 full_num_tokens: int,
1082 ) -> torch.Tensor:
1083 # Without a successor boundary, the last layer finalized its own MoE.
1084 assert isinstance(hidden_states, torch.Tensor)
1085 # The last layer has no successor to fold its post into.
1086 if self.fuse_mhc_all_reduce:
1087 hidden_states = tensor_model_parallel_all_reduce(hidden_states)
1088 hidden_states = mhc_post_tilelang(hidden_states, residual, post_mix, res_mix)
1089 if self.end_layer in self.aux_hidden_state_layers:
1090 final_aux = hidden_states.mean(dim=1)
1091 if self.use_sequence_parallel:
1092 final_aux = sp_all_gather(final_aux)[:full_num_tokens]
1093 aux_hidden_by_layer[self.end_layer] = final_aux
1094 return hidden_states
1095
1096 def _run_replay_layers(
1097 self,
1098 hidden_states: torch.Tensor | MoEOutput,
1099 positions: torch.Tensor,
1100 input_ids: torch.Tensor | None,
1101 pre_mix: torch.Tensor,
1102 post_mix: torch.Tensor,
1103 res_mix: torch.Tensor,
1104 residual: torch.Tensor,
1105 ) -> tuple[torch.Tensor, ...]:
1106 """The layers past the last KV source, on whatever rows they are given;
1107 returns their output, the last FFN's pre-mix and the aux hidden states
1108 they capture."""
1109 aux_hidden_by_layer: dict[int, torch.Tensor] = {}
1110 hidden_states, residual, post_mix, res_mix, pre_mix = self._run_layers(
1111 range(self.decoder_replay_start, self.end_layer),
1112 hidden_states,
1113 positions,
1114 input_ids,
1115 pre_mix,
1116 post_mix,
1117 res_mix,
1118 residual,
1119 aux_hidden_by_layer,
1120 )
1121 hidden_states = self._collapse(
1122 hidden_states,
1123 residual,
1124 post_mix,
1125 res_mix,
1126 aux_hidden_by_layer,
1127 positions.shape[0],
1128 )
1129 return (
1130 hidden_states,
1131 pre_mix,
1132 *(
1133 aux_hidden_by_layer[layer_id]
1134 for layer_id in self.aux_hidden_state_layers
1135 if layer_id in aux_hidden_by_layer
1136 ),
1137 )
1138
1139 def _decoder_replay_supported(self, vllm_config: VllmConfig, cut: int) -> bool:
1140 """Whether this rank may trim the layers after ``cut``; warns when not."""
1141 parallel_config = vllm_config.parallel_config
1142 spec_config = vllm_config.speculative_config
1143 draft_config = spec_config.draft_model_config if spec_config else None
1144 draft_hf_config = getattr(draft_config, "hf_config", None)
1145 draft_window = getattr(draft_hf_config, "sliding_window", None)
1146 draft_layer_types = getattr(draft_hf_config, "layer_types", None) or ()
1147 window = self.config.sliding_window
1148 if self.start_layer > cut or self.end_layer < self.config.num_hidden_layers:
1149 reason = (
1150 "the pipeline stage holding the last KV source layer must also "
1151 "hold every layer after it"
1152 )
1153 elif (
1154 self.use_sequence_parallel
1155 or parallel_config.prefill_context_parallel_size > 1
1156 or parallel_config.use_ubatching
1157 ):
1158 reason = (
1159 "the replay-layer batch shrinks per rank, which sequence and "
1160 "prefill-context parallelism and microbatching cannot follow"
1161 )
1162 elif any(i > cut for i in getattr(self.config, "engram_layer_ids", ())):
1163 reason = "an Engram layer sits after the last KV source layer"
1164 elif draft_config is not None and (
1165 draft_window is None
1166 or draft_window > window
1167 or any(t != "sliding_attention" for t in draft_layer_types)
1168 ):
1169 reason = (
1170 f"the drafter (sliding window {draft_window}) reads hidden states "
1171 f"outside the target's {window}-token window"
1172 )
1173 else:
1174 return True
1175 logger.warning_once("Decoder SWA bounded replay is off: %s.", reason)
1176 return False
1177
1178 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1179 stacked_params_mapping = [
1180 # (param_name, shard_name, shard_id)
1181 ("gate_up_proj", "w1", 0),
1182 ("gate_up_proj", "w3", 1),
1183 ("attn.fused_wqa_wkv", "attn.wq_a", 0),
1184 ("attn.fused_wqa_wkv", "attn.wkv", 1),
1185 ("compressor.fused_wkv_wgate", "compressor.wkv", 0),
1186 ("compressor.fused_wkv_wgate", "compressor.wgate", 1),
1187 ]
1188 params_dict = dict(self.named_parameters())
1189 loaded_params: set[str] = set()
1190
1191 # TP for attention
1192 tp_size = get_tensor_model_parallel_world_size()
1193 tp_rank = get_tensor_model_parallel_rank()
1194 n_head = self.config.num_attention_heads
1195 n_local_head = n_head // tp_size
1196 head_rank_start = n_local_head * tp_rank
1197 head_rank_end = n_local_head * (tp_rank + 1)
1198
1199 # Pre-compute expert mapping ONCE.
1200 expert_mapping = self.get_expert_mapping()
1201
1202 # Block-FP8 shared experts: pad the intermediate up to the TP-uniform
1203 # block count so the standard loaders below slice it evenly (trailing
1204 # ranks land on the zero pad). SP / unquantized ones need no padding.
1205 pad_shared_expert = (
1206 getattr(self.quant_config, "weight_block_size", None) is not None
1207 and not self.use_sequence_parallel
1208 )
1209
1210 for name, loaded_weight in weights:
1211 if name.startswith(("vision.", "aligner.", "image_")):
1212 # Vision weights are loaded by the outer multimodal wrapper.
1213 logger.warning_once("Skipping non-text weight: %s", name)
1214 continue
1215 if pad_shared_expert and ".shared_experts." in name:
1216 loaded_weight = self._pad_shared_expert_weight(
1217 self.quant_config, name, loaded_weight
1218 )
1219 for param_name, weight_name, shard_id in stacked_params_mapping:
1220 # Skip non-stacked layers and experts (experts handled below).
1221 if ".experts." in name:
1222 continue
1223 if weight_name not in name:
1224 continue
1225 name = name.replace(weight_name, param_name)
1226
1227 if is_pp_missing_parameter(name, self):
1228 break
1229 if name not in params_dict:
1230 head, _, leaf = name.rpartition(".")
1231 suffixed = f"{head}.base_layer.{leaf}"
1232 if suffixed in params_dict:
1233 name = suffixed
1234 param = params_dict[name]
1235 weight_loader = param.weight_loader
1236 weight_loader(param, loaded_weight, shard_id)
1237 loaded_params.add(name)
1238 break
1239 else:
1240 if ".experts." in name:
1241 # E8M0 scales are stored as float8_e8m0fnu in
1242 # checkpoints but the MoE param is uint8. copy_()
1243 # would do a numeric conversion (e.g. 2^-7 → 0),
1244 # destroying the raw exponent bytes.
1245 if (
1246 "weight_scale" in name
1247 and loaded_weight.dtype == torch.float8_e8m0fnu
1248 ):
1249 loaded_weight = loaded_weight.view(torch.uint8)
1250 for mapping in expert_mapping:
1251 param_name, weight_name, expert_id, expert_shard_id = mapping
1252 if weight_name not in name:
1253 continue
1254 name_mapped = name.replace(weight_name, param_name)
1255 if is_pp_missing_parameter(name_mapped, self):
1256 continue
1257 param = params_dict[name_mapped]
1258 # We should ask the weight loader to return success or not
1259 # here since otherwise we may skip experts with other
1260 # available replicas.
1261 weight_loader = typing.cast(
1262 Callable[..., bool], param.weight_loader
1263 )
1264 success = weight_loader(
1265 param,
1266 loaded_weight,
1267 name_mapped,
1268 shard_id=expert_shard_id,
1269 expert_id=expert_id,
1270 return_success=True,
1271 )
1272 if success:
1273 name = name_mapped
1274 break
1275 loaded_params.add(name_mapped)
1276 continue
1277 elif "attn_sink" in name:
1278 if is_pp_missing_parameter(name, self):
1279 continue
1280 narrow_weight = loaded_weight[head_rank_start:head_rank_end]
1281 n = narrow_weight.shape[0]
1282 params_dict[name][:n].copy_(narrow_weight)
1283 loaded_params.add(name)
1284 continue
1285 else:
1286 if is_pp_missing_parameter(name, self):
1287 continue
1288 # Non-LoRA params on a LoRA-wrapped module live at
1289 # ``<head>.base_layer.<leaf>``; the checkpoint is plain.
1290 if name not in params_dict:
1291 head, _, leaf = name.rpartition(".")
1292 suffixed = f"{head}.base_layer.{leaf}"
1293 if suffixed in params_dict:
1294 name = suffixed
1295 param = params_dict[name]
1296 weight_loader = getattr(
1297 param, "weight_loader", default_weight_loader
1298 )
1299 weight_loader(param, loaded_weight)
1300 loaded_params.add(name)
1301 continue
1302
1303 return loaded_params
1304
1305 @staticmethod
1306 def _pad_shared_expert_weight(
1307 quant_config: QuantizationConfig | None,
1308 name: str,
1309 loaded_weight: torch.Tensor,
1310 ) -> torch.Tensor:
1311 """Zero-pad a block-FP8 shared-expert weight/scale on its intermediate
1312 axis so the standard TP loaders split it into even, block-aligned shards
1313 (trailing ranks get the zero pad). gate (w1)/up (w3) [I, H] pad dim 0;
1314 down (w2 -> down_proj) [H, I] pads dim 1.
1315 """
1316 block_size = getattr(quant_config, "weight_block_size", None)
1317 assert block_size is not None
1318 # Round the intermediate axis up to a whole number of TP shards. The axis
1319 # is in elements for weights (step = block) and in blocks for scales.
1320 step = (
1321 1 if name.endswith(("weight_scale_inv", "weight_scale")) else block_size[0]
1322 )
1323 dim = 1 if ".down_proj." in name else 0
1324 mult = get_tensor_model_parallel_world_size() * step
1325 pad = cdiv(loaded_weight.shape[dim], mult) * mult - loaded_weight.shape[dim]
1326 if pad == 0:
1327 return loaded_weight
1328 pad_shape = list(loaded_weight.shape)
1329 pad_shape[dim] = pad
1330 return torch.cat([loaded_weight, loaded_weight.new_zeros(pad_shape)], dim=dim)
1331
1332 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1333 first_layer = next(iter(islice(self.layers, self.start_layer, self.end_layer)))
1334 if first_layer.ffn.use_mega_moe:
1335 return make_deepseek_v4_expert_params_mapping(self.config.n_routed_experts)
1336 # Params for weights, fp8 weight scales, fp8 activation scales
1337 # (param_name, weight_name, expert_id, shard_id)
1338 return fused_moe_make_expert_params_mapping(
1339 self,
1340 ckpt_gate_proj_name="w1",
1341 ckpt_down_proj_name="w2",
1342 ckpt_up_proj_name="w3",
1343 num_experts=self.config.n_routed_experts,
1344 )
1345
1346 def finalize_mega_moe_weights(self) -> None:
1347 for layer in islice(self.layers, self.start_layer, self.end_layer):
1348 layer.ffn.finalize_mega_moe_weights()
1349
1350 def finalize_mega_attn_weights(self) -> None:
1351 """Permute wq_b / wo_a into FlashMLA's mega-attention layouts.
1352
1353 A no-op for every other attention layer, and idempotent, so a second
1354 post-load pass cannot permute twice.
1355 """
1356 for layer in islice(self.layers, self.start_layer, self.end_layer):
1357 finalize = getattr(layer.attn, "finalize_loaded_weights", None)
1358 if finalize is not None:
1359 finalize()
1360
1361 def finalize_mhc_broadcast_weights(self) -> None:
1362 if not get_pp_group().is_first_rank or self.start_layer >= self.end_layer:
1363 return
1364 layer = self.layers[self.start_layer]
1365 if isinstance(layer, DeepseekV4DecoderLayer):
1366 broadcast = (
1367 layer.hc_attn_fn.detach()
1368 .view(-1, layer.hc_mult, layer.hidden_size)
1369 .sum(dim=1)
1370 )
1371 if layer.hc_attn_fn_broadcast is None:
1372 layer.hc_attn_fn_broadcast = broadcast
1373 else:
1374 layer.hc_attn_fn_broadcast.copy_(broadcast)
1375
1376
1377 def _linear_scale_param_name(vllm_config: VllmConfig, expert_dtype: str) -> str:
1378 """Parameter name the linear quant method registers for weight scales.
1379
1380 Native MXFP8 checkpoints ([32, 32] blocks with MXFP4 experts) route linear
1381 layers through ModelOptLinearMethod, which registers ``weight_scale``;
1382 block-FP8 linear layers register ``weight_scale_inv``.
1383 """
1384 use_mxfp8 = (
1385 getattr(vllm_config.quant_config, "weight_block_size", None) == [32, 32]
1386 and expert_dtype == "fp4"
1387 )
1388 return "weight_scale" if use_mxfp8 else "weight_scale_inv"
1389
1390
1391 def _make_deepseek_v4_weights_mapper(
1392 expert_dtype: str, linear_scale_name: str = "weight_scale_inv"
1393 ) -> WeightsMapper:
1394 if expert_dtype == "fp4":
1395 # MXFP4 experts use Mxfp4MoEMethod, which registers scales as
1396 # ``w{1,2,3}_weight_scale`` (no _inv suffix). Linear scales use the
1397 # parameter name registered by their quantization method.
1398 scale_regex = {
1399 # ``.base_layer.``-namespace variant (LoRA-wrapped experts).
1400 re.compile(
1401 r"(\.experts\.\d+\.w[123]\.base_layer)\.scale$"
1402 ): r"\1.weight_scale",
1403 re.compile(r"(\.experts\.\d+\.w[123])\.scale$"): r"\1.weight_scale",
1404 # The ``embed.weight`` -> ``embed_tokens.weight`` suffix rule
1405 # renames the engram fp8 table but not its scale; route the
1406 # scale explicitly to the same module.
1407 re.compile(r"(engram\.embed)\.scale$"): r"\1_tokens.weight_scale_inv",
1408 re.compile(r"\.scale$"): f".{linear_scale_name}",
1409 }
1410 else:
1411 # FP8 experts use Fp8MoEMethod (block_quant=True), which registers
1412 # scales as ``w{13,2}_weight_scale_inv``. Map all ``.scale`` keys
1413 # there.
1414 scale_regex = {
1415 # ``.base_layer.``-namespace variant of the above.
1416 re.compile(
1417 r"(\.experts\.\d+\.w[123]\.base_layer)\.scale$"
1418 ): r"\1.weight_scale_inv",
1419 # Same engram reroute as the fp4 branch above.
1420 re.compile(r"(engram\.embed)\.scale$"): r"\1_tokens.weight_scale_inv",
1421 re.compile(r"\.scale$"): f".{linear_scale_name}",
1422 }
1423 return WeightsMapper(
1424 orig_to_new_prefix={
1425 "layers.": "model.layers.",
1426 "embed.": "model.embed.",
1427 "norm.": "model.norm.",
1428 "mtp.": "model.mtp.",
1429 },
1430 orig_to_new_regex=scale_regex,
1431 orig_to_new_suffix={
1432 "head.weight": "lm_head.weight",
1433 "embed.weight": "embed_tokens.weight",
1434 ".ffn.gate.bias": ".ffn.gate.e_score_correction_bias",
1435 },
1436 orig_to_new_substr={
1437 ".shared_experts.w2": ".shared_experts.down_proj",
1438 "mtp.": None,
1439 # The v4.1 checkpoint declares the VL arch even for text-only
1440 # use; the text backbone drops the vision-tower weights.
1441 "vision.": None,
1442 "aligner.": None,
1443 "image_": None,
1444 },
1445 )
1446
1447
1448 class DeepseekV4MixtureOfExperts(MixtureOfExperts):
1449 moe_mlp_layers: list["DeepseekV4MoE"]
1450
1451 def extract_moe_parameters(self, example_moe: "DeepseekV4MoE | None") -> None:
1452 if example_moe is None:
1453 self.num_moe_layers = 0
1454 self.num_expert_groups = 0
1455 self.num_logical_experts = 0
1456 self.num_physical_experts = 0
1457 self.num_local_physical_experts = 0
1458 self.num_routed_experts = 0
1459 self.num_shared_experts = 0
1460 self.num_redundant_experts = 0
1461 return
1462 self.num_logical_experts = example_moe.n_logical_experts
1463 self.num_physical_experts = example_moe.n_physical_experts
1464 self.num_local_physical_experts = example_moe.n_local_physical_experts
1465 self.num_routed_experts = example_moe.n_routed_experts
1466 self.num_shared_experts = example_moe.n_shared_experts
1467 self.num_redundant_experts = example_moe.n_redundant_experts
1468
1469 def update_physical_experts_metadata(
1470 self,
1471 num_physical_experts: int,
1472 num_local_physical_experts: int,
1473 ) -> None:
1474 assert self.num_local_physical_experts == num_local_physical_experts
1475 self.num_physical_experts = num_physical_experts
1476 self.num_local_physical_experts = num_local_physical_experts
1477 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
1478 for moe in self.moe_mlp_layers:
1479 moe.n_local_physical_experts = num_local_physical_experts
1480 moe.n_physical_experts = num_physical_experts
1481 moe.n_redundant_experts = self.num_redundant_experts
1482 moe.experts.update_expert_map()
1483
1484
1485 class DeepseekV41LLMForCausalLM(
1486 nn.Module,
1487 SupportsPP,
1488 SupportsEagle3,
1489 SupportsLoRA,
1490 DeepseekV4MixtureOfExperts,
1491 ):
1492 model_cls = DeepseekV4Model
1493
1494 # Default mapper assumes the original FP4-expert checkpoint layout.
1495 # Overridden per-instance in __init__ when expert_dtype != "fp4".
1496 hf_to_vllm_mapper = _make_deepseek_v4_weights_mapper("fp4")
1497
1498 packed_modules_mapping = {
1499 "gate_up_proj": ["w1", "w3"],
1500 "fused_wqa_wkv": ["wq_a", "wkv"],
1501 "fused_wkv_wgate": ["wkv", "wgate"],
1502 }
1503
1504 # The MTP draft head is not LoRA-adapted.
1505 lora_skip_prefixes = ["mtp."]
1506
1507 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1508 super().__init__()
1509
1510 config = vllm_config.model_config.hf_config
1511 self.config = config
1512 expert_dtype = getattr(config, "expert_dtype", "fp4")
1513 self.hf_to_vllm_mapper = _make_deepseek_v4_weights_mapper(
1514 expert_dtype, _linear_scale_param_name(vllm_config, expert_dtype)
1515 )
1516
1517 self.model = self.model_cls(
1518 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1519 )
1520 if get_pp_group().is_last_rank:
1521 self.lm_head = ParallelLMHead(
1522 config.vocab_size,
1523 config.hidden_size,
1524 prefix=maybe_prefix(prefix, "lm_head"),
1525 )
1526 else:
1527 self.lm_head = PPMissingLayer()
1528 self.logits_processor = LogitsProcessor(config.vocab_size)
1529 self.make_empty_intermediate_tensors = ( # type: ignore[method-assign]
1530 self.model.make_empty_intermediate_tensors
1531 )
1532
1533 self.set_moe_parameters()
1534
1535 def set_moe_parameters(self) -> None:
1536 self.num_expert_groups = getattr(self.config, "n_group", 1)
1537 self.num_moe_layers = self.config.num_hidden_layers
1538 self.moe_layers: list[nn.Module] = []
1539 self.moe_mlp_layers: list[DeepseekV4MoE] = []
1540 example_moe: DeepseekV4MoE | None = None
1541 for layer in self.model.layers:
1542 if isinstance(layer, PPMissingLayer):
1543 continue
1544 if not isinstance(layer, DeepseekV4DecoderLayer):
1545 continue
1546 if isinstance(layer.ffn, DeepseekV4MoE):
1547 example_moe = layer.ffn
1548 self.moe_mlp_layers.append(layer.ffn)
1549 self.moe_layers.append(layer.ffn.experts)
1550
1551 self.num_moe_layers = len(self.moe_layers)
1552 self.extract_moe_parameters(example_moe)
1553
1554 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1555 return self.model.embed_input_ids(input_ids)
1556
1557 def compute_logits(
1558 self,
1559 hidden_states: torch.Tensor,
1560 ) -> torch.Tensor | None:
1561 logits = self.logits_processor(self.lm_head, hidden_states)
1562 return logits
1563
1564 def compute_logits_local(
1565 self,
1566 hidden_states: torch.Tensor,
1567 ) -> torch.Tensor:
1568 return self.logits_processor(self.lm_head, hidden_states, skip_gather=True)
1569
1570 @staticmethod
1571 def get_model_state_cls():
1572 from .model_state import DeepseekV41ModelState
1573
1574 return DeepseekV41ModelState
1575
1576 @property
1577 def token_lookback_depth(self) -> int:
1578 """Tokens before a chunk start the engram hash needs; the model runner
1579 passes them as `lookback_token_ids`."""
1580 engram_hash = self.model.engram_hash
1581 return engram_hash.lookback_depth if engram_hash is not None else 0
1582
1583 @property
1584 def decoder_replay_layers(self) -> DecoderReplayLayers | None:
1585 return self.model.decoder_replay_layers
1586
1587 def forward(
1588 self,
1589 input_ids: torch.Tensor,
1590 positions: torch.Tensor,
1591 intermediate_tensors: IntermediateTensors | None = None,
1592 inputs_embeds: torch.Tensor | None = None,
1593 lookback_token_ids: torch.Tensor | None = None,
1594 ) -> torch.Tensor | IntermediateTensors:
1595 hidden_states = self.model(
1596 input_ids,
1597 positions,
1598 intermediate_tensors,
1599 inputs_embeds,
1600 lookback_token_ids=lookback_token_ids,
1601 )
1602 return hidden_states
1603
1604 def get_mtp_target_hidden_states(self) -> torch.Tensor | None:
1605 """Pre-collapse residual stream buffer (max_num_batched_tokens,
1606 hc_mult * hidden_size) for the MTP draft model. Populated by
1607 forward(); valid after each target step."""
1608 return getattr(self.model, "_mtp_hidden_buffer", None)
1609
1610 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1611 loader = AutoWeightsLoader(self)
1612 loaded_params = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1613 self.process_weights_after_loading()
1614 config = self.model.vllm_config
1615 if config.engram_config and config.engram_config.use_thp:
1616 # Loading weights refills the file cache; release it before MADV_COLLAPSE.
1617 model_config = config.model_config
1618 drop_checkpoint_cache(
1619 model_config.model_weights or model_config.model,
1620 revision=model_config.revision,
1621 cache_dir=config.load_config.download_dir,
1622 )
1623 for layer in islice(
1624 self.model.layers, self.model.start_layer, self.model.end_layer
1625 ):
1626 if layer.engram is not None:
1627 layer.engram.embed_tokens.collapse_huge_pages()
1628 return loaded_params
1629
1630 def process_weights_after_loading(self) -> None:
1631 self.model.finalize_mega_moe_weights()
1632 self.model.finalize_mhc_broadcast_weights()
1633 self.model.finalize_mega_attn_weights()
1634
1635 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1636 return self.model.get_expert_mapping()