Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

2199 lines
83 KiB
Python

# Copyright (c) 2026 LightSeek Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Inference-only DeepseekV3 model."""
# ruff: noqa: E402
from __future__ import annotations
import re
from collections.abc import Iterable
from dataclasses import replace
from typing import Any
import torch
import torch.nn.functional as F
from tokenspeed_kernel.ops.attention import attn_merge_state
from tokenspeed_kernel.ops.attention.tokenspeed_mla import mla_kv_pack_quantize_fp8
from tokenspeed_kernel.ops.embedding import apply_rope_mla
from tokenspeed_kernel.ops.gemm.cute_dsl import (
nvfp4_gemm_swiglu_nvfp4_quant,
)
from tokenspeed_kernel.ops.gemm.trtllm import dsv3_fused_a_gemm
from tokenspeed_kernel.ops.quantization.flashinfer import fp4_quantize
from tokenspeed_kernel.ops.quantization.triton import fp8_quantize
from tokenspeed_kernel.platform import current_platform
from tokenspeed_kernel.thirdparty.cuda import dsv3_router_gemm, moe_finalize_fuse_shared
from torch import nn
from transformers import PretrainedConfig
from tokenspeed.runtime.configs.utils import get_rope_theta
from tokenspeed.runtime.layers.moe import (
ExpertCheckpointSchema,
build_moe_checkpoint_loader,
)
from tokenspeed.runtime.layers.utils import (
CP_METADATA,
ENABLE_CP,
cp_all_gather_rerange_output,
cp_split_and_rebuild_data,
get_layer_id,
)
_platform = current_platform()
_is_amd = _platform.is_amd
_is_blackwell = _platform.is_blackwell
_is_hopper_plus = _platform.is_hopper_plus
_device_sm = _platform.arch_version.major * 10 + _platform.arch_version.minor
from tokenspeed.runtime.distributed import Mapping
from tokenspeed.runtime.distributed.comm_manager import CommManager
from tokenspeed.runtime.execution.breakable_cuda_graph import (
break_point,
scrub_padding_tail,
)
from tokenspeed.runtime.execution.context import (
ForwardContext,
report_collective_sizing,
)
from tokenspeed.runtime.execution.cuda_graph_wrapper import get_is_capture_mode
from tokenspeed.runtime.execution.forward_batch_info import ForwardMode
from tokenspeed.runtime.layers.activation import SiluAndMul
from tokenspeed.runtime.layers.dense.nvfp4 import Nvfp4LinearMethod
from tokenspeed.runtime.layers.layernorm import FusedRMSNorm, RMSNorm
from tokenspeed.runtime.layers.linear import (
ColumnParallelLinear,
MergedColumnParallelLinear,
ReplicatedLinear,
RowParallelLinear,
)
from tokenspeed.runtime.layers.logits_processor import LogitsProcessor
from tokenspeed.runtime.layers.moe.expert import MoELayer
from tokenspeed.runtime.layers.moe.topk import TopK
from tokenspeed.runtime.layers.moe.utils import RoutingMethodType
from tokenspeed.runtime.layers.paged_attention import PagedAttention
from tokenspeed.runtime.layers.quantization.base_config import QuantizationConfig
from tokenspeed.runtime.layers.quantization.nvfp4 import Nvfp4Config
from tokenspeed.runtime.layers.quantization.utils import (
block_dequant,
should_exclude_quant_module,
)
from tokenspeed.runtime.layers.rotary_embedding import get_rope
from tokenspeed.runtime.layers.vocab_parallel_embedding import (
ParallelLMHead,
VocabParallelEmbedding,
)
from tokenspeed.runtime.model_loader.weight_utils import (
default_weight_loader,
kv_cache_scales_loader,
)
from tokenspeed.runtime.models.base import BaseCausalLM
from tokenspeed.runtime.models.utils import (
create_fused_set_kv_buffer_arg,
)
from tokenspeed.runtime.moe.distribution_recorder import (
get_global_expert_distribution_recorder,
)
from tokenspeed.runtime.moe.expert_location import ModelConfigForExpertLocation
from tokenspeed.runtime.utils import LazyValue, add_prefix, get_colorful_logger
from tokenspeed.runtime.utils.cuda_stream import StreamFork
from tokenspeed.runtime.utils.env import envs, global_server_args_dict
from tokenspeed.runtime.utils.pdl import pdl_enabled
logger = get_colorful_logger(__name__)
_OPTIONAL_MISSING_WEIGHT_SUFFIXES = (
".k_scale",
".v_scale",
)
def _prepare_mla_kv_b_proj_weights(
w: torch.Tensor, self_attn
) -> tuple[torch.Tensor, torch.Tensor]:
w_kc, w_vc = w.unflatten(
0, (-1, self_attn.qk_nope_head_dim + self_attn.v_head_dim)
).split([self_attn.qk_nope_head_dim, self_attn.v_head_dim], dim=1)
if _is_amd:
return w_kc.contiguous(), w_vc.transpose(1, 2).contiguous()
return (
w_kc.transpose(1, 2).contiguous().transpose(1, 2),
w_vc.contiguous().transpose(1, 2),
)
class DeepseekV3MLP(nn.Module):
def __init__(
self,
hidden_size: int,
intermediate_size: int,
hidden_act: str,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
is_shared_expert: bool = False,
) -> None:
super().__init__()
self.mapping = mapping
if is_shared_expert:
tp_rank = self.mapping.moe.tp_ep_rank
tp_size = self.mapping.moe.tp_ep_size
tp_group = self.mapping.moe.tp_ep_group
else:
tp_rank = self.mapping.dense.tp_rank
tp_size = self.mapping.dense.tp_size
tp_group = self.mapping.dense.tp_group
self.gate_up_proj = MergedColumnParallelLinear(
hidden_size,
[intermediate_size] * 2,
bias=False,
tp_size=tp_size,
tp_rank=tp_rank,
tp_group=tp_group,
quant_config=quant_config,
prefix=add_prefix("gate_up_proj", prefix),
)
self.down_proj = RowParallelLinear(
intermediate_size,
hidden_size,
bias=False,
reduce_results=False, # Communication is handled externally and manually controlled
tp_size=tp_size,
tp_rank=tp_rank,
tp_group=tp_group,
quant_config=quant_config,
prefix=add_prefix("down_proj", prefix),
)
if hidden_act != "silu":
raise ValueError(
f"Unsupported activation: {hidden_act}. Only silu is supported for now."
)
self.act_fn = SiluAndMul()
self._use_nvfp4_gemm_swiglu_nvfp4_quant = (
envs.TOKENSPEED_NVFP4_GEMM_SWIGLU_NVFP4_QUANT.get()
and _is_blackwell
and isinstance(self.gate_up_proj.quant_method, Nvfp4LinearMethod)
and isinstance(self.down_proj.quant_method, Nvfp4LinearMethod)
)
self.gate_up_proj.interleave_linear_and_gate = (
self._use_nvfp4_gemm_swiglu_nvfp4_quant
)
def forward(self, x):
if x.size(0) == 0:
return x
if self._use_nvfp4_gemm_swiglu_nvfp4_quant:
x_fc1_fp4, x_fc1_scale = fp4_quantize(
x, self.gate_up_proj.input_scale_inv, enable_pdl=pdl_enabled()
)
x_fp4, x_scale = nvfp4_gemm_swiglu_nvfp4_quant(
x_fc1_fp4,
x_fc1_scale,
self.gate_up_proj.weight_swiglu_interleaved,
self.gate_up_proj.weight_scale_swiglu_interleaved,
self.gate_up_proj.alpha,
self.down_proj.input_scale_inv,
enable_pdl=pdl_enabled(),
)
x, _ = self.down_proj((x_fp4, x_scale))
return x
gate_up, _ = self.gate_up_proj(x)
x = self.act_fn(gate_up)
x, _ = self.down_proj(x)
return x
class MoEGate(nn.Module):
_DSV3_ROUTER_GEMM_EXPERTS = (256, 384, 768)
_DSV3_ROUTER_GEMM_HIDDEN = (3072, 6144, 7168)
def __init__(self, config, prefix: str = ""):
super().__init__()
self.weight = nn.Parameter(
torch.empty((config.n_routed_experts, config.hidden_size))
)
if config.topk_method == "noaux_tc":
self.e_score_correction_bias = nn.Parameter(
torch.empty((config.n_routed_experts), dtype=torch.float32)
)
else:
self.e_score_correction_bias = None
self.use_dsv3_router_gemm = (
_is_hopper_plus
and self.weight.dtype in (torch.bfloat16, torch.float32)
and config.n_routed_experts in self._DSV3_ROUTER_GEMM_EXPERTS
and config.hidden_size in self._DSV3_ROUTER_GEMM_HIDDEN
)
def forward(self, hidden_states, comm_manager=None):
if self.use_dsv3_router_gemm and hidden_states.size(0) > 0:
logits = dsv3_router_gemm(
hidden_states,
self.weight,
out_dtype=torch.float32,
enable_pdl=pdl_enabled(),
)
else:
logits = F.linear(hidden_states, self.weight, None)
return logits
class DeepseekV3MoE(nn.Module):
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
layer_index: int = -1,
prefix: str = "",
alt_stream: torch.cuda.Stream | None = None,
):
super().__init__()
self.mapping = mapping
self.layer_index = layer_index
self.n_shared_experts = config.n_shared_experts
self.routed_scaling_factor = config.routed_scaling_factor
self.stream_fork = StreamFork(alt_stream)
if self.mapping.moe.ep_size > config.n_routed_experts:
raise ValueError(
f"EP size {self.mapping.moe.ep_size} is greater than the number of experts {config.n_routed_experts}."
)
if config.hidden_act != "silu":
raise ValueError(
f"Unsupported activation: {config.hidden_act}. Only silu is supported for now."
)
self.gate = MoEGate(config=config, prefix=add_prefix("gate", prefix))
if config.n_shared_experts is not None:
intermediate_size = config.moe_intermediate_size * config.n_shared_experts
self.shared_experts = DeepseekV3MLP(
hidden_size=config.hidden_size,
intermediate_size=intermediate_size,
hidden_act=config.hidden_act,
mapping=self.mapping,
quant_config=quant_config,
prefix=add_prefix("shared_experts", prefix),
is_shared_expert=True,
)
self.experts = MoELayer(
top_k=config.num_experts_per_tok,
num_experts=config.n_routed_experts
+ global_server_args_dict["ep_num_redundant_experts"],
hidden_size=config.hidden_size,
intermediate_size=config.moe_intermediate_size,
quant_config=quant_config,
layer_index=layer_index,
prefix=prefix,
tp_rank=self.mapping.moe.tp_rank,
tp_size=self.mapping.moe.tp_size,
ep_rank=self.mapping.moe.ep_rank,
ep_size=self.mapping.moe.ep_size,
routing_config={
"n_group": getattr(config, "n_group", 0),
"topk_group": getattr(config, "topk_group", 0),
"routed_scaling_factor": getattr(config, "routed_scaling_factor", 1.0),
"normalize_topk_weights": config.norm_topk_prob,
"correction_bias": self.gate.e_score_correction_bias,
"routing_method_type": RoutingMethodType.DeepSeekV3,
},
)
self.topk = TopK(
top_k=config.num_experts_per_tok,
renormalize=config.norm_topk_prob,
use_grouped_topk=True,
num_expert_group=config.n_group,
num_fused_shared_experts=0,
topk_group=config.topk_group,
correction_bias=self.gate.e_score_correction_bias,
routed_scaling_factor=self.routed_scaling_factor,
output_format=self.experts.topk_output_format,
)
def get_moe_routed_weights(self):
return [
x.data
for name, x in self.experts.named_parameters()
if name not in ["correction_bias"] and "shared_experts" not in name
]
def forward(
self,
hidden_states: torch.Tensor,
num_global_tokens: int,
max_num_tokens_per_gpu: int,
) -> torch.Tensor:
num_tokens = hidden_states.size(0)
with self.stream_fork.scope(enable=get_is_capture_mode()) as fork:
# router_logits: (num_tokens, n_experts)
router_logits = self.gate(hidden_states)
if num_tokens > 0:
topk_output = self.topk(hidden_states, router_logits)
else:
topk_output = self.topk.empty_topk_output(
hidden_states.device,
hidden_states=hidden_states,
router_logits=router_logits,
)
deferred_finalize = self.experts.supports_deferred_finalize
routed_expert_output = self.experts(
hidden_states=hidden_states,
topk_output=topk_output,
num_global_tokens=num_global_tokens,
max_num_tokens_per_gpu=max_num_tokens_per_gpu,
do_finalize=not deferred_finalize,
)
shared_output = None
with fork.branch():
if self.n_shared_experts is not None and num_tokens > 0:
shared_output = self.shared_experts(hidden_states)
if deferred_finalize:
gemm2_out, expert_weights, expanded_idx = routed_expert_output
final_hidden_states = moe_finalize_fuse_shared(
gemm2_out,
expanded_idx,
expert_weights,
shared_output,
top_k=self.topk.topk_config.top_k,
enable_pdl=pdl_enabled(),
)
else:
final_hidden_states = (
routed_expert_output + shared_output
if shared_output is not None
else routed_expert_output
)
return final_hidden_states
def yarn_get_mscale(scale: float = 1, mscale: float = 1) -> float:
import math
if scale <= 1:
return 1.0
return 0.1 * mscale * math.log(scale) + 1.0
class DeepseekV3FusedQkvAProjWithMqa(ReplicatedLinear):
def __init__(
self,
input_size: int,
output_size: int,
bias: bool = True,
skip_bias_add: bool = False,
params_dtype: torch.dtype | None = None,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
):
# ModelOpt NVFP4 checkpoints (e.g. DeepSeek-R1-0528-NVFP4-v2) keep the
# q_a_proj / kv_a_proj_with_mqa weights as bf16 via exclude_modules.
# exclude_modules matches by component name, not by the fused parent
# prefix, so the fused layer would otherwise allocate an NVFP4-packed
# buffer and crash when bf16 weights are copied in.
if isinstance(quant_config, Nvfp4Config) and prefix:
q_a_prefix = prefix.replace("fused_qkv_a_proj_with_mqa", "q_a_proj")
kv_a_prefix = prefix.replace(
"fused_qkv_a_proj_with_mqa", "kv_a_proj_with_mqa"
)
if should_exclude_quant_module(
q_a_prefix, quant_config.exclude_modules
) or should_exclude_quant_module(kv_a_prefix, quant_config.exclude_modules):
quant_config = None
super().__init__(
input_size,
output_size,
bias=bias,
skip_bias_add=skip_bias_add,
params_dtype=params_dtype,
quant_config=quant_config,
prefix=prefix,
)
self.use_min_latency = (
self.bias is None
and self.weight.dtype == torch.bfloat16
and self.weight.size() == (2112, 7168)
and current_platform().is_nvidia
and _device_sm >= 90
and _device_sm not in (120, 121)
)
def forward(
self, x: torch.Tensor, block_scale=None, output_dtype=None
) -> torch.Tensor:
if (
self.use_min_latency
and x.size(0) > 0
and block_scale is None
and (output_dtype is None or output_dtype == torch.bfloat16)
):
return dsv3_fused_a_gemm(x, self.weight.T)
return super().forward(x, block_scale=block_scale, output_dtype=output_dtype)[0]
class DeepseekV3AttentionMLA(nn.Module):
# Backends that use non-absorbed MLA kernels (ragged prefill, paged KV decode).
_MLA_KERNEL_BACKENDS = ("mla", "trtllm_mla", "tokenspeed_mla")
# Backends that support chunked ragged prefill with prefix replay.
_RAGGED_PREFILL_BACKENDS = ("mla", "trtllm_mla", "tokenspeed_mla")
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
hidden_size: int,
num_heads: int,
qk_nope_head_dim: int,
qk_rope_head_dim: int,
v_head_dim: int,
q_lora_rank: int,
kv_lora_rank: int,
rope_theta: float = 10000,
rope_scaling: dict[str, Any] | None = None,
max_position_embeddings: int = 8192,
quant_config: QuantizationConfig | None = None,
layer_id=None,
prefix: str = "",
reduce_attn_results=True,
alt_stream: torch.cuda.Stream | None = None,
skip_rope: bool = False,
) -> None:
super().__init__()
self.mapping = mapping
self.layer_id = layer_id
self.hidden_size = hidden_size
self.qk_nope_head_dim = qk_nope_head_dim
self.qk_rope_head_dim = qk_rope_head_dim
self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim
self.v_head_dim = v_head_dim
self.q_lora_rank = q_lora_rank
self.kv_lora_rank = kv_lora_rank
self.num_heads = num_heads
if num_heads % self.mapping.attn.tp_size != 0:
raise ValueError(
f"num_heads={num_heads} must be divisible by attn_tp_size={self.mapping.attn.tp_size}."
)
self.num_local_heads = num_heads // self.mapping.attn.tp_size
self.scaling = self.qk_head_dim**-0.5
self.rope_theta = rope_theta
self.max_position_embeddings = max_position_embeddings
self.config = config
self.alt_stream = alt_stream
self.attention_backend = global_server_args_dict["attention_backend"]
self.cli_factor = getattr(config, "cli_factor", 1)
self.prefix = prefix
# modification to rope_scaling must be done early enough, b/c e.g. Indexer needs it
if rope_scaling:
rope_scaling["rope_type"] = "deepseek_yarn"
if self.q_lora_rank is not None:
self.fused_qkv_a_proj_with_mqa = DeepseekV3FusedQkvAProjWithMqa(
self.hidden_size,
self.q_lora_rank + self.kv_lora_rank + self.qk_rope_head_dim,
bias=False,
quant_config=quant_config,
prefix=add_prefix("fused_qkv_a_proj_with_mqa", prefix),
)
self.q_a_layernorm = RMSNorm(self.q_lora_rank, eps=config.rms_norm_eps)
self.q_b_proj = ColumnParallelLinear(
q_lora_rank,
self.num_heads * self.qk_head_dim,
bias=False,
quant_config=quant_config,
prefix=add_prefix("q_b_proj", prefix),
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
else:
self.q_proj = ColumnParallelLinear(
self.hidden_size,
self.num_heads * self.qk_head_dim,
bias=False,
quant_config=quant_config,
prefix=add_prefix("q_proj", prefix),
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
self.kv_a_proj_with_mqa = ReplicatedLinear(
self.hidden_size,
self.kv_lora_rank + self.qk_rope_head_dim,
bias=False,
quant_config=quant_config,
prefix=add_prefix("kv_a_proj_with_mqa", prefix),
)
self.kv_b_proj = ColumnParallelLinear(
self.kv_lora_rank,
self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
bias=False,
quant_config=quant_config,
prefix=add_prefix("kv_b_proj", prefix),
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
# O projection.
self.o_proj = RowParallelLinear(
self.num_heads * self.v_head_dim,
self.hidden_size,
bias=False,
reduce_results=reduce_attn_results,
quant_config=quant_config,
prefix=add_prefix("o_proj", prefix),
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
self.kv_a_layernorm = RMSNorm(self.kv_lora_rank, eps=config.rms_norm_eps)
# Fusion layer
if self.q_lora_rank is not None:
self.fused_qk_layernorm = FusedRMSNorm(
self.q_a_layernorm,
self.kv_a_layernorm,
)
if not skip_rope:
self.rotary_emb = get_rope(
qk_rope_head_dim,
rotary_dim=qk_rope_head_dim,
max_position=max_position_embeddings,
base=rope_theta,
rope_scaling=rope_scaling,
is_neox_style=False,
)
if rope_scaling:
mscale_all_dim = rope_scaling.get("mscale_all_dim", False)
scaling_factor = rope_scaling["factor"]
mscale = yarn_get_mscale(scaling_factor, float(mscale_all_dim))
self.scaling = self.scaling * mscale * mscale
else:
self.rotary_emb = None
# Fused RoPE+KV write kernel is incompatible with MLA: it assumes
# K and V have the same head_dim, but MLA's KV cache is a single
# [latent(512)|rope(64)] buffer where the two dimensions differ.
# Passing this to the kernel causes thread overflow and silent
# corruption of the latent cache. All DeepSeek V2/V3 models use
# MLA (kv_lora_rank > 0), so we unconditionally disable it here.
self.use_fused_set_kv_buffer = False
self.attn_mqa = PagedAttention(
self.num_local_heads,
self.kv_lora_rank + self.qk_rope_head_dim,
self.scaling,
num_kv_heads=1,
layer_id=layer_id,
v_head_dim=self.kv_lora_rank,
)
self.attn_mha = PagedAttention(
self.num_local_heads,
self.qk_nope_head_dim + self.qk_rope_head_dim,
self.scaling,
num_kv_heads=self.num_local_heads,
layer_id=layer_id,
v_head_dim=self.v_head_dim,
)
self.w_kc = None
self.w_vc = None
def forward(
self,
positions: torch.Tensor,
hidden_states: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
comm_manager: CommManager,
block_scale: torch.Tensor | None = None,
) -> torch.Tensor:
"""MLA attention with a NARROW prefill-graph break.
The token-shaped input/output projections (q/kv-down, layernorm,
q_b_proj, o_proj) stay in the captured prefill graph; only the
data-dependent attention -- KV write + varlen prefill / absorb decode
kernels + the live prefill/decode split -- runs as the eager break
(``_attn``). This keeps the big projection GEMMs graphed instead of
dispatch-bound eager, collapsing the inter-segment bubbles a coarse
whole-attention break leaves. Outside capture the ``@break_point`` is
a direct call, so the eager path is unchanged.
"""
if hidden_states.shape[0] == 0:
return hidden_states
q, latent_cache = self._project_q_latent(
hidden_states, ctx, comm_manager, block_scale
)
attn_output = self._attn(positions, q, latent_cache, ctx, out_cache_loc)
output, _ = self.o_proj(attn_output)
return output
def _project_q_latent(
self,
hidden_states: torch.Tensor,
ctx: ForwardContext,
comm_manager: CommManager,
block_scale: torch.Tensor | None,
) -> tuple[torch.Tensor, torch.Tensor]:
"""QKV projection producing absorbed ``q`` and raw ``latent_cache``."""
if self.q_lora_rank is not None:
qkv = self.fused_qkv_a_proj_with_mqa(
hidden_states, block_scale, torch.bfloat16
)
qkv = comm_manager.pre_attn_comm(qkv, ctx)
q_a, latent_cache = qkv.split(
[self.q_lora_rank, self.kv_lora_rank + self.qk_rope_head_dim],
dim=-1,
)
kv_a = latent_cache[..., : self.kv_lora_rank]
q_norm = torch.empty_like(q_a)
if q_a.size(0) > 0:
self.fused_qk_layernorm(
input_q_a=q_a, input_kv_a=kv_a, output_q_a=q_norm
)
q = self.q_b_proj(q_norm)[0]
else:
hidden_states = comm_manager.pre_attn_comm(hidden_states, ctx)
q = self.q_proj(hidden_states)[0]
latent_cache = self.kv_a_proj_with_mqa(hidden_states)[0]
kv_a = latent_cache[..., : self.kv_lora_rank]
self.kv_a_layernorm(kv_a, inplace=True)
return q, latent_cache
@break_point
def _attn(
self,
positions: torch.Tensor,
q: torch.Tensor,
latent_cache: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
) -> torch.Tensor:
"""The eager break: KV write + varlen prefill / absorb decode attention.
Prefill/decode dispatch over the full rows; subclasses override (the
draft variant narrows to live rows, see ``DeepseekV3DraftAttentionMLA``).
The split is recovered from LIVE state -- correct both in eager and
under a prefill-graph replay, where ``ctx`` is the live ambient context
but ``q`` is padded to the graph bucket (``q.size(0)`` is NOT the real
token count). The decode token count comes from the live ctx; the real
prefill token count from the live attention metadata (the same source
the padding scrub uses). Padded tail rows produce discarded garbage.
"""
spec = ctx.attn_backend.spec_num_tokens or 1
num_decodes = max(ctx.bs - ctx.num_extends, 0)
num_decode_tokens = num_decodes * spec
if ctx.num_extends > 0:
cmeta = ctx.attn_backend.chunked_prefill_metadata
num_prefill_tokens = int(sum(cmeta.extend_seq_lens_cpu))
else:
num_prefill_tokens = 0
real_total = num_prefill_tokens + num_decode_tokens
attn_output = torch.empty(
q.size(0),
self.num_local_heads * self.v_head_dim,
dtype=q.dtype,
device=q.device,
)
if num_prefill_tokens > 0:
prefill_ctx = replace(
ctx,
bs=max(ctx.bs - num_decodes, 1),
num_extends=max(ctx.bs - num_decodes, 1),
input_num_tokens=num_prefill_tokens,
forward_mode=ForwardMode.EXTEND,
)
self.forward_normal_chunked(
positions[:num_prefill_tokens],
q[:num_prefill_tokens],
latent_cache[:num_prefill_tokens],
prefill_ctx,
out_cache_loc[:num_prefill_tokens],
attn_output[:num_prefill_tokens],
)
if num_decode_tokens > 0:
decode_ctx = replace(
ctx,
bs=num_decodes,
num_extends=0,
input_num_tokens=num_decode_tokens,
forward_mode=ForwardMode.DECODE,
)
self.forward_absorb(
positions[num_prefill_tokens:real_total],
q[num_prefill_tokens:real_total],
latent_cache[num_prefill_tokens:real_total],
decode_ctx,
out_cache_loc[num_prefill_tokens:real_total],
attn_output[num_prefill_tokens:real_total],
)
return attn_output
def forward_absorb(
self,
positions: torch.Tensor,
q: torch.Tensor,
latent_cache: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
output: torch.Tensor,
) -> torch.Tensor:
Q, K = self.forward_absorb_qkv_proj(
q,
latent_cache,
positions,
ctx,
out_cache_loc,
)
return self.forward_absorb_attn_v_proj(Q, K, ctx, out_cache_loc, output)
def forward_absorb_qkv_proj(
self,
q: torch.Tensor,
latent_cache: torch.Tensor,
positions,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
q = q.view(-1, self.num_local_heads, self.qk_head_dim)
q_nope, q_pe = q.split([self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
Q = torch.empty(
q_nope.size(0),
self.num_local_heads,
self.kv_lora_rank + self.qk_rope_head_dim,
dtype=q_nope.dtype,
device=q_nope.device,
)
# latent_cache contains normalized kv_a and k_pe before rotate.
K = latent_cache.unsqueeze(1)
q_nope_out_view = Q[..., : self.kv_lora_rank]
if _is_amd:
q_nope_projected = torch.bmm(
q_nope.transpose(0, 1).contiguous(),
self.w_kc.contiguous(),
)
q_nope_out_view.copy_(q_nope_projected.transpose(0, 1))
else:
torch.bmm(
q_nope.transpose(0, 1),
self.w_kc,
out=q_nope_out_view.transpose(0, 1),
)
# Model-owned fused FP8 decode: RoPE + quantize + KV cache write
# all done here, so backend only needs to do attention.
k_scale = getattr(self.attn_mqa, "k_scale_float", 1.0)
use_fused_fp8_decode = (
self.attention_backend in self._MLA_KERNEL_BACKENDS
and getattr(ctx.attn_backend, "data_type", None) == torch.float8_e4m3fn
and self.rotary_emb is not None
and k_scale == 1.0
)
if use_fused_fp8_decode:
q_nope_absorbed = Q[..., : self.kv_lora_rank]
k_nope_raw = K[..., : self.kv_lora_rank]
k_pe_raw = K[..., self.kv_lora_rank :]
query_fp8, key_fp8 = apply_rope_mla(
positions=positions,
q_rope=q_pe,
k_rope=k_pe_raw,
q_nope=q_nope_absorbed,
k_nope=k_nope_raw,
cos_sin_cache=self.rotary_emb.cos_sin_cache,
is_neox=getattr(self.rotary_emb, "is_neox_style", True),
quant_scale_q=1.0,
quant_scale_kv=k_scale,
enable_pdl=pdl_enabled(),
)
# Write FP8 KV cache (single write, no double-write)
ctx.token_to_kv_pool.set_mla_kv_buffer(
self.attn_mqa,
out_cache_loc,
cache_k_nope=key_fp8[..., : self.kv_lora_rank],
cache_k_rope=key_fp8[..., self.kv_lora_rank :],
)
return query_fp8, key_fp8
elif self.rotary_emb is not None and q_nope.size(0) > 0:
# Apply RoPE directly on Q and K slices
q_pe, k_pe = self.rotary_emb(
positions,
q_pe,
K[..., self.kv_lora_rank :],
fused_set_kv_buffer_arg=(
create_fused_set_kv_buffer_arg(
value=K[..., : self.kv_lora_rank],
layer=self.attn_mqa,
out_cache_loc=out_cache_loc,
token_to_kv_pool=ctx.token_to_kv_pool,
)
if self.use_fused_set_kv_buffer
else None
),
)
Q[..., self.kv_lora_rank :].copy_(q_pe)
K[..., self.kv_lora_rank :].copy_(k_pe)
else:
Q[..., self.kv_lora_rank :] = q_pe
# For MLA kernel backends, write KV cache here (model-owned) so the
# backend never has to. This unifies the FP8 fused path (written above)
# and the BF16 path into a single ownership model.
if (
self.attention_backend in self._MLA_KERNEL_BACKENDS
and not self.use_fused_set_kv_buffer
):
ctx.token_to_kv_pool.set_mla_kv_buffer(
self.attn_mqa,
out_cache_loc,
cache_k_nope=K[..., : self.kv_lora_rank],
cache_k_rope=K[..., self.kv_lora_rank :],
)
return Q, K
def forward_absorb_attn_v_proj(
self,
Q,
K,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
output: torch.Tensor,
record_kv_cache: bool | None = None,
) -> torch.Tensor:
# MLA kernel backends: KV cache already written in forward_absorb_qkv_proj.
# Other backends: write via fused_set_kv_buffer or let backend handle it.
if self.attention_backend in self._MLA_KERNEL_BACKENDS:
need_save_kv = False
else:
need_save_kv = not self.use_fused_set_kv_buffer
attn_output = self.attn_mqa(
Q,
K,
K[..., : self.kv_lora_rank],
ctx,
out_cache_loc,
save_kv_cache=need_save_kv,
record_kv_cache=record_kv_cache,
)
attn_output = attn_output.view(-1, self.num_local_heads, self.kv_lora_rank)
if _is_amd:
projected = torch.bmm(
attn_output.transpose(0, 1).contiguous(),
self.w_vc.contiguous(),
)
output.copy_(projected.transpose(0, 1).reshape_as(output))
else:
output_view = output.view(-1, self.num_local_heads, self.v_head_dim)
torch.bmm(
attn_output.transpose(0, 1),
self.w_vc,
out=output_view.transpose(0, 1),
)
return output
def forward_normal_chunked(
self,
positions: torch.Tensor,
q: torch.Tensor,
latent_cache: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
output: torch.Tensor,
) -> torch.Tensor:
# Prefill-graph padding contract: zero garbage rows the per-row projections
# + FP8 quantize would otherwise touch (see scrub_padding_tail).
ntok = sum(ctx.attn_backend.chunked_prefill_metadata.extend_seq_lens_cpu)
scrub_padding_tail(ntok, q, latent_cache)
q, k, v = self.forward_normal_chunked_kv_prepare(
positions, q, latent_cache, ctx, out_cache_loc
)
return self.forward_normal_chunked_kv_core(q, k, v, ctx, out_cache_loc, output)
def forward_normal_chunked_kv_prepare(
self,
positions: torch.Tensor,
q: torch.Tensor,
latent_cache: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
kv_a, k_pe = latent_cache.split(
[self.kv_lora_rank, self.qk_rope_head_dim], dim=-1
)
k_pe = k_pe.unsqueeze(1)
q = q.view(-1, self.num_local_heads, self.qk_head_dim)
q_nope, q_pe = q.split([self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
kv = self.kv_b_proj(kv_a)[0]
kv = kv.view(-1, self.num_local_heads, self.qk_nope_head_dim + self.v_head_dim)
k_nope = kv[..., : self.qk_nope_head_dim]
v = kv[..., self.qk_nope_head_dim :]
# FP8 prefill: fused RoPE + FP8 quantize, direct FP8 KV cache write.
# Disabled when k_scale != 1.0; mla_fp8_utils.py documents the current limitation.
k_scale = getattr(self.attn_mha, "k_scale_float", 1.0)
use_fp8_prefill = (
self.attention_backend in self._MLA_KERNEL_BACKENDS
and getattr(ctx.attn_backend, "data_type", None) == torch.float8_e4m3fn
and self.rotary_emb is not None
and k_scale == 1.0
)
if use_fp8_prefill:
# Expand k_pe from [tokens,1,rope] to [tokens,heads,rope] for GQA
k_pe_expanded = k_pe.expand(-1, self.num_local_heads, -1)
q_fp8, k_fp8 = apply_rope_mla(
positions=positions,
q_rope=q_pe,
k_rope=k_pe_expanded,
q_nope=q_nope,
k_nope=k_nope,
cos_sin_cache=self.rotary_emb.cos_sin_cache,
is_neox=getattr(self.rotary_emb, "is_neox_style", True),
quant_scale_q=1.0,
quant_scale_kv=k_scale,
enable_pdl=pdl_enabled(),
)
v_fp8 = fp8_quantize(v, enable_pdl=pdl_enabled())
# Write FP8 KV cache directly (skip BF16→FP8 conversion in pool)
k_pe_for_cache = k_fp8[:, 0:1, self.qk_nope_head_dim :]
kv_a_fp8 = fp8_quantize(kv_a, enable_pdl=pdl_enabled())
ctx.token_to_kv_pool.set_mla_kv_buffer(
self.attn_mha,
out_cache_loc,
cache_k_nope=kv_a_fp8.unsqueeze(1),
cache_k_rope=k_pe_for_cache,
)
return q_fp8, k_fp8, v_fp8
# BF16 path: apply RoPE, assemble Q/K, write cache
if self.rotary_emb is not None:
q_pe, k_pe = self.rotary_emb(
positions,
q_pe,
k_pe,
fused_set_kv_buffer_arg=(
create_fused_set_kv_buffer_arg(
value=kv_a.unsqueeze(1),
layer=self.attn_mha,
out_cache_loc=out_cache_loc,
token_to_kv_pool=ctx.token_to_kv_pool,
)
if self.use_fused_set_kv_buffer
else None
),
)
q[..., self.qk_nope_head_dim :] = q_pe
k = torch.empty_like(q)
k[..., : self.qk_nope_head_dim] = k_nope
k[..., self.qk_nope_head_dim :] = k_pe
if not self.use_fused_set_kv_buffer:
ctx.token_to_kv_pool.set_mla_kv_buffer(
self.attn_mha,
out_cache_loc,
cache_k_nope=kv_a.unsqueeze(1),
cache_k_rope=k_pe,
)
return q, k, v
def forward_normal_chunked_kv_core(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
output: torch.Tensor,
) -> torch.Tensor:
attn_backend = ctx.attn_backend
chunk_meta = attn_backend.chunked_prefill_metadata
token_to_kv_pool = ctx.token_to_kv_pool
# Scale compensation for FP8 prefill: bmm1_scale = k_scale * softmax_scale
scaling = self.attn_mha.scaling
k_scale = getattr(self.attn_mha, "k_scale_float", 1.0)
if q.dtype == torch.float8_e4m3fn:
scaling = k_scale * scaling
# Causal self-attention over the new chunk tokens. q_lens == kv_lens ==
# extend_seq_lens, so cum_seq_lens_q and cum_seq_lens_kv alias the same
# cum_extend_seq_lens. Causal pass writes directly into output; each
# chunk's merge accumulates in place via attn_merge_state(inplace=True).
num_extends = chunk_meta.extend_seq_lens.size(0)
output_view = output.view(-1, self.num_local_heads, self.v_head_dim)
_, accum_lse = attn_backend.forward_extend_chunked(
q,
k,
v,
scaling,
self.attn_mha.logit_cap,
cum_seq_lens_q=chunk_meta.cum_extend_seq_lens,
cum_seq_lens_kv=chunk_meta.cum_extend_seq_lens,
max_q_len=chunk_meta.max_extend_seq_len,
max_kv_len=chunk_meta.max_extend_seq_len,
seq_lens=chunk_meta.extend_seq_lens,
batch_size=num_extends,
causal=True,
out=output_view,
)
# Always read KV cache as BF16 for kv_b_proj (weight is BF16), even if Q is FP8.
read_dtype = (
q.dtype
if q.dtype not in (torch.float8_e4m3fn, torch.float8_e5m2)
else torch.bfloat16
)
for loop_idx in range(chunk_meta.chunked_loop_num):
chunk_kv_indices = chunk_meta.chunk_kv_indices_list[loop_idx]
kv_a_normed, k_pe = token_to_kv_pool.get_mla_kv_buffer(
self.attn_mha, chunk_kv_indices, read_dtype
)
kv_a_normed = kv_a_normed.squeeze(1)
kv = self.kv_b_proj(kv_a_normed)[0]
kv = kv.view(
-1, self.num_local_heads, self.qk_nope_head_dim + self.v_head_dim
)
v = kv[..., self.qk_nope_head_dim :]
k_nope = kv[..., : self.qk_nope_head_dim]
if q.dtype == torch.float8_e4m3fn:
# FP8 Attention
k, v = mla_kv_pack_quantize_fp8(
k_nope, k_pe, v, k_scale_inv=1.0 / k_scale, enable_pdl=pdl_enabled()
)
else:
# BF16 Attention
k = torch.cat(
[k_nope, k_pe.expand(-1, self.num_local_heads, -1)], dim=-1
)
chunk_output, lse = attn_backend.forward_extend_chunked(
q,
k,
v,
scaling,
self.attn_mha.logit_cap,
cum_seq_lens_q=chunk_meta.cum_extend_seq_lens,
cum_seq_lens_kv=chunk_meta.cu_chunked_seq_len[loop_idx],
max_q_len=chunk_meta.max_extend_seq_len,
max_kv_len=chunk_meta.max_chunk_len_per_loop[loop_idx],
seq_lens=chunk_meta.chunked_seq_len[loop_idx],
batch_size=num_extends,
causal=False,
)
attn_merge_state(
output_view,
accum_lse,
chunk_output,
lse,
inplace=True,
enable_pdl=pdl_enabled(),
)
return output
class DeepseekV3DraftAttentionMLA(DeepseekV3AttentionMLA):
"""Draft variant of MLA shared by the NextN and Eagle3 MLA drafters.
On the active first draft step the full ``latent_cache`` (N rows) is
projected so every KV cache entry is written, but only the live query rows
(``ctx.gather_ids``) run the absorbed decode attention, narrowing the output
to ``[bs, H]``. Multi-step decode and target paths delegate to the base.
Single-layer only, so dropping the dead rows has no downstream consumer.
"""
def _attn(
self,
positions: torch.Tensor,
q: torch.Tensor,
latent_cache: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
) -> torch.Tensor:
if ctx.accept_lengths is None:
return super()._attn(positions, q, latent_cache, ctx, out_cache_loc)
self._apply_correction(ctx)
# Full q/latent_cache write all KV cache rows; only the live rows
# (ctx.gather_ids) run the absorbed decode attention, so the output is
# narrowed to [bs, H] for o_proj / MLP / post-norms.
decode_ctx = replace(ctx, forward_mode=ForwardMode.DECODE)
Q, K = self.forward_absorb_qkv_proj(
q,
latent_cache,
positions,
decode_ctx,
out_cache_loc,
)
Q = Q.index_select(0, ctx.gather_ids)
attn_output = q.new_empty(ctx.bs, self.num_local_heads * self.v_head_dim)
# gather_ids keeps one live row per request, so the decode runs on the
# full bs -- the page table and seq lens must span the same rows. Drop
# the [num_extends:] slice a MIXED target's first-step metadata sets up
# (mirrors the multi-step drafter loop's override_num_extends(0)).
with ctx.attn_backend.override_num_extends(0):
self.forward_absorb_attn_v_proj(
Q,
K,
decode_ctx,
out_cache_loc,
attn_output,
# Real-mode record: decode_ctx would skip the PD cache-step here.
record_kv_cache=not ctx.forward_mode.is_decode_or_idle(),
)
return attn_output
def _apply_correction(self, ctx: ForwardContext) -> None:
"""Trim decode rows' cache_seqlens by ``spec_num_tokens - accept_lengths``."""
seq_lens_buf = ctx.draft_seq_lens_buf
if seq_lens_buf is None or ctx.accept_lengths is None:
return
num_extends = ctx.num_extends
if num_extends >= ctx.bs:
return
correction = (
ctx.attn_backend.spec_num_tokens - ctx.accept_lengths[num_extends:]
).to(seq_lens_buf.dtype)
seq_lens_buf[num_extends : ctx.bs].sub_(correction).clamp_(min=1)
class DeepseekV3DecoderLayer(nn.Module):
@property
def attention_cls(self) -> type[nn.Module]:
return DeepseekV3AttentionMLA
def __init__(
self,
config: PretrainedConfig,
layer_id: int,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
is_nextn: bool = False,
prefix: str = "",
alt_stream: torch.cuda.Stream | None = None,
) -> None:
super().__init__()
self.mapping = mapping
self.hidden_size = config.hidden_size
rope_theta = get_rope_theta(config)
rope_scaling = getattr(config, "rope_scaling", None)
max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
self.self_attn = self.attention_cls(
config=config,
hidden_size=self.hidden_size,
num_heads=config.num_attention_heads,
qk_nope_head_dim=config.qk_nope_head_dim,
qk_rope_head_dim=config.qk_rope_head_dim,
v_head_dim=config.v_head_dim,
q_lora_rank=(
config.q_lora_rank if hasattr(config, "q_lora_rank") else None
),
kv_lora_rank=config.kv_lora_rank,
rope_theta=rope_theta,
rope_scaling=rope_scaling,
max_position_embeddings=max_position_embeddings,
quant_config=(
None
if "self_attn" in getattr(config, "disable_quant_module", [])
else quant_config
),
layer_id=layer_id,
prefix=add_prefix("self_attn", prefix),
reduce_attn_results=False,
alt_stream=alt_stream,
mapping=self.mapping,
)
self.layer_id = layer_id
self.is_moe_layer = self._is_moe_layer(layer_id, is_nextn, config)
if self.is_moe_layer:
self.mlp = DeepseekV3MoE(
config=config,
mapping=self.mapping,
quant_config=quant_config,
layer_index=layer_id,
prefix=add_prefix("mlp", prefix),
alt_stream=alt_stream,
)
else:
self.mlp = DeepseekV3MLP(
hidden_size=config.hidden_size,
intermediate_size=(
config.ffn_hidden_size
if hasattr(config, "ffn_hidden_size")
else config.intermediate_size
),
hidden_act=config.hidden_act,
mapping=self.mapping,
quant_config=(
None
if "dense_mlp" in getattr(config, "disable_quant_module", [])
else quant_config
),
prefix=add_prefix("mlp", prefix),
is_shared_expert=False,
)
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.post_attention_layernorm = RMSNorm(
config.hidden_size, eps=config.rms_norm_eps
)
self.comm_manager = CommManager(
mapping=self.mapping,
layer_id=self.layer_id,
is_moe=self.is_moe_layer,
prev_is_moe=self._is_moe_layer(layer_id - 1, is_nextn, config),
input_layernorm=self.input_layernorm,
post_attn_layernorm=self.post_attention_layernorm,
)
@staticmethod
def _is_moe_layer(layer_id: int, is_nextn: bool, config):
if is_nextn:
return True
if (
config.n_routed_experts is not None
and layer_id >= config.first_k_dense_replace
and layer_id % config.moe_layer_freq == 0
):
return True
return False
def forward(
self,
positions: torch.Tensor,
hidden_states: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
residual: torch.Tensor | None,
) -> torch.Tensor:
num_global_tokens, max_num_tokens_per_gpu = self.comm_manager.get_num_tokens(
ctx
)
if not ctx.forward_mode.is_idle():
hidden_states, residual = self.comm_manager.input_reduce_norm(
hidden_states, residual
)
hidden_states = self.self_attn(
positions=positions,
hidden_states=hidden_states,
ctx=ctx,
out_cache_loc=out_cache_loc,
comm_manager=self.comm_manager,
)
hidden_states, residual = self.comm_manager.post_attn_reduce_norm(
hidden_states, residual, ctx
)
hidden_states = self.forward_mlp(
hidden_states,
residual,
ctx,
num_global_tokens,
max_num_tokens_per_gpu,
)
else:
hidden_states = self.forward_mlp(
hidden_states,
residual,
ctx,
num_global_tokens,
max_num_tokens_per_gpu,
)
return hidden_states, residual
def input_layer_norm_fn(self, hidden_states, residual):
if residual is None:
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
else:
hidden_states, residual = self.input_layernorm(hidden_states, residual)
return hidden_states, residual
def forward_mlp(
self,
hidden_states,
residual,
ctx: ForwardContext,
num_global_tokens,
max_num_tokens_per_gpu,
):
hidden_states = self.comm_manager.pre_mlp_comm(hidden_states, ctx)
if self.is_moe_layer:
hidden_states = self.mlp(
hidden_states, num_global_tokens, max_num_tokens_per_gpu
)
else:
hidden_states = self.mlp(hidden_states)
hidden_states, residual = self.comm_manager.post_mlp_fused(
hidden_states, residual, ctx
)
return hidden_states
class DeepseekV3Model(nn.Module):
fall_back_to_pt_during_load = False
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
super().__init__()
self.mapping = mapping
self.padding_id = config.pad_token_id
self.vocab_size = config.vocab_size
self.embed_tokens = VocabParallelEmbedding(
config.vocab_size,
config.hidden_size,
)
self.alt_stream = torch.cuda.Stream()
# config.num_hidden_layers = 5; self.start_layer,self.end_layer = 0, 5
self.layers = nn.ModuleList(
[
DeepseekV3DecoderLayer(
config,
layer_id,
mapping=self.mapping,
quant_config=quant_config,
prefix=add_prefix(f"layers.{layer_id}", prefix),
alt_stream=self.alt_stream,
)
for layer_id in range(config.num_hidden_layers)
]
)
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
# For EAGLE3 support: set of layer indices whose *input* hidden states
# are captured. Populated by set_eagle3_layers_to_capture().
self.layers_to_capture: set = set()
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
input_embeds: torch.Tensor | None = None,
) -> tuple[torch.Tensor, list[torch.Tensor] | None]:
if input_embeds is not None:
hidden_states = input_embeds
else:
hidden_states = self.embed_tokens(input_ids)
if CP_METADATA:
hidden_states = cp_split_and_rebuild_data(
hidden_states,
CP_METADATA.value.split_list,
CP_METADATA.value.zigzag_index,
)
positions = cp_split_and_rebuild_data(
positions, CP_METADATA.value.split_list, CP_METADATA.value.zigzag_index
)
residual = None
aux_hidden_states = [] if self.layers_to_capture else None
for i in range(len(self.layers)):
if aux_hidden_states is not None and i in self.layers_to_capture:
# Under RSAG the inter-layer hidden/residual are reduce-
# scattered across the attn TP group; aux consumers (e.g. the
# EAGLE3 drafter) expect full rows, so gather before capturing.
aux = (
hidden_states + residual if residual is not None else hidden_states
)
gathered = self.layers[i].comm_manager.gather_residual(aux, ctx)
aux_hidden_states.append(
gathered if gathered is aux else gathered.clone()
)
with get_global_expert_distribution_recorder().with_current_layer(i):
layer = self.layers[i]
hidden_states, residual = layer(
positions,
hidden_states,
ctx,
out_cache_loc,
residual,
)
if not ctx.forward_mode.is_idle():
if not ENABLE_CP:
hidden_states, _ = layer.comm_manager.final_norm(
hidden_states, residual, ctx, self.norm
)
else:
hidden_states, _ = self.norm(hidden_states, residual)
if CP_METADATA:
hidden_states = cp_all_gather_rerange_output(
hidden_states,
CP_METADATA.value,
self.mapping.attn.tp_rank,
self.mapping.attn.tp_group,
)
return hidden_states, aux_hidden_states
class DeepseekV3ForCausalLM(BaseCausalLM):
model_cls = DeepseekV3Model
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
model: DeepseekV3Model | None = None,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
self._model_override = model
super().__init__(
config=config,
mapping=mapping,
quant_config=quant_config,
prefix=prefix,
)
def resolve_model(
self,
config: PretrainedConfig,
mapping: Mapping,
quant_config: QuantizationConfig | None,
prefix: str,
) -> DeepseekV3Model:
if self._model_override is not None:
return self._model_override
return self.model_cls(
config,
mapping=mapping,
quant_config=quant_config,
prefix=add_prefix("model", prefix),
)
def post_init(self) -> None:
self._routed_experts_weights_of_layer = LazyValue(
lambda: {
layer_id: layer.mlp.get_moe_routed_weights()
for layer_id, layer in enumerate(self.model.layers)
if isinstance(layer.mlp, DeepseekV3MoE)
}
)
@property
def routed_experts_weights_of_layer(self):
return self._routed_experts_weights_of_layer.value
def set_eagle3_layers_to_capture(self, layer_ids: list[int] | None = None):
# layer_ids are 0-indexed from the external API; +1 because the capture
# check runs *before* the layer forward, so index i captures layer i-1's output.
if layer_ids is None:
num_layers = self.config.num_hidden_layers
self.model.layers_to_capture = {2, num_layers // 2, num_layers - 3}
else:
self.model.layers_to_capture = {val + 1 for val in layer_ids}
def set_dflash_layers_to_capture(self, layer_ids: list[int]):
# DFlash checkpoints name 0-indexed target layer outputs. The capture
# check runs before layer i, so capture at i + 1 for layer i's output.
num_layers = len(self.model.layers)
if len(set(layer_ids)) != len(layer_ids):
raise ValueError("DFLASH target_layer_ids must be unique.")
invalid = [val for val in layer_ids if val < 0 or val + 1 >= num_layers]
if invalid:
raise ValueError(
"DFLASH target_layer_ids must map to capturable target layer "
f"outputs. Got invalid ids {invalid}; valid range is "
f"[0, {num_layers - 2}] for {num_layers} target layers."
)
self.model.layers_to_capture = {val + 1 for val in layer_ids}
def get_param(self, params_dict, name):
if name in params_dict:
return params_dict[name]
if "language_model." in name:
name = name.replace("language_model.", "")
if name in params_dict:
return params_dict[name]
if name.endswith(_OPTIONAL_MISSING_WEIGHT_SUFFIXES):
return None
logger.warning("The %s is not in the model.", name)
return None
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
stacked_params_mapping = [
# (param_name, shard_name, shard_id)
("gate_up_proj", "gate_proj", 0),
("gate_up_proj", "up_proj", 1),
]
# Fuse q_a_proj and kv_a_proj_with_mqa along output dimension when q_lora_rank is not None
fuse_qkv_a_proj = getattr(self.config, "q_lora_rank", None) is not None
params_dict = dict(self.named_parameters())
moe_params_dict = dict(params_dict)
for param_name, param in params_dict.items():
if param_name.startswith("model."):
moe_params_dict.setdefault(
param_name.replace("model.", "model.language_model.", 1),
param,
)
moe_params_dict.setdefault(
param_name.replace("model.", "language_model.model.", 1),
param,
)
# MoE expert weights, scales, and activation scales are handled
# by the checkpoint loader.
moe_loader = build_moe_checkpoint_loader(
params_dict=moe_params_dict,
expert_schema=ExpertCheckpointSchema(
gate_proj_name="gate_proj",
down_proj_name="down_proj",
up_proj_name="up_proj",
),
num_experts=self.config.n_routed_experts,
ep_rank=self.mapping.moe.ep_rank,
ep_size=self.mapping.moe.ep_size,
)
for name, loaded_weight in weights:
layer_id = get_layer_id(name)
if (
layer_id is not None
and hasattr(self.model, "start_layer")
and (
layer_id < self.model.start_layer
or layer_id >= self.model.end_layer
)
):
continue
if hasattr(self.config, "num_nextn_predict_layers"):
num_nextn_layers = self.config.num_nextn_predict_layers
if num_nextn_layers > 0 and name.startswith("model.layers"):
name_list = name.split(".")
if (
len(name_list) >= 3
and int(name_list[2]) >= self.config.num_hidden_layers
):
continue
if "rotary_emb.inv_freq" in name:
continue
if ".indexer." in name:
continue
for param_name, weight_name, shard_id in stacked_params_mapping:
# Skip non-stacked layers and experts (experts handled below).
if weight_name not in name:
continue
# We have mlp.experts[0].gate_proj in the checkpoint.
# Since moe_loader handles the experts below,
# we need to skip here BEFORE we update the name, otherwise
# name will be updated to mlp.experts[0].gate_up_proj, which
# will then be updated below by moe_loader
# for mlp.experts[0].gate_gate_up_proj, which breaks load.
if ("mlp.experts." in name) and name not in params_dict:
continue
name = name.replace(weight_name, param_name)
# Skip loading extra bias for GPTQ models.
if name.endswith(".bias") and name not in params_dict:
continue
param = self.get_param(params_dict, name)
if param is None:
continue
weight_loader = param.weight_loader
weight_loader(param, loaded_weight, shard_id)
break
else:
# Skip loading extra bias for GPTQ models.
if name.endswith(".bias") and name not in params_dict:
continue
if moe_loader.matches(name):
moe_loader.load(name, loaded_weight)
continue
if fuse_qkv_a_proj and (
"q_a_proj" in name or "kv_a_proj_with_mqa" in name
):
quant_block_size = 1
# ``weight_block_size`` exists only on block-FP8 configs;
# elsewhere (e.g. compressed-tensors INT4) q/kv_a_proj is unquantized.
weight_block_size = getattr(
self.quant_config, "weight_block_size", None
)
if weight_block_size is not None:
quant_block_size = weight_block_size[0]
begin_size_mp = {
"q_a_proj": 0,
"kv_a_proj_with_mqa": self.config.q_lora_rank,
}
if "q_a_proj" in name:
param = self.get_param(
params_dict,
name.replace("q_a_proj", "fused_qkv_a_proj_with_mqa"),
)
weight_loader = param.weight_loader
begin_size = begin_size_mp["q_a_proj"]
elif "kv_a_proj_with_mqa" in name:
param = self.get_param(
params_dict,
name.replace(
"kv_a_proj_with_mqa", "fused_qkv_a_proj_with_mqa"
),
)
weight_loader = param.weight_loader
begin_size = begin_size_mp["kv_a_proj_with_mqa"]
if "scale_inv" in name:
begin_size //= quant_block_size
weight_loader(param, loaded_weight, begin_size=begin_size)
else:
# Owned-expert weights were already consumed by ``moe_loader.load(...)`` above (matches() == True branch).
# Anything reaching here that still looks like an expert weight is for an expert this rank does ot own under ep_size > 1.
if ".mlp.experts." in name:
continue
if "q_a_proj" in name and name not in params_dict:
name = name.replace("q_a_proj", "q_proj")
param = self.get_param(params_dict, name)
if param is None:
continue
weight_loader = getattr(
param, "weight_loader", default_weight_loader
)
weight_loader(param, loaded_weight)
self.post_load_weights()
def post_load_weights(self):
for layer_id in range(self.config.num_hidden_layers):
self_attn = self.model.layers[layer_id].self_attn
if hasattr(
self.quant_config, "weight_block_size"
) and self_attn.kv_b_proj.weight.dtype in (
torch.float8_e4m3fn,
torch.float8_e4m3fnuz,
):
weight_block_size = self.quant_config.weight_block_size
if weight_block_size is not None:
if not hasattr(self_attn.kv_b_proj, "weight_scale_inv"):
raise RuntimeError(
"kv_b_proj.weight_scale_inv is required for block FP8 dequant."
)
dtype = torch.get_default_dtype()
w = block_dequant(
self_attn.kv_b_proj.weight,
self_attn.kv_b_proj.weight_scale_inv,
weight_block_size,
).to(dtype)
else:
w = self_attn.kv_b_proj.weight
self_attn.w_kc, self_attn.w_vc = _prepare_mla_kv_b_proj_weights(
w, self_attn
)
def load_kv_cache_scales(self, quantization_param_path: str) -> None:
tp_size = self.mapping.attn.tp_size
tp_rank = self.mapping.attn.tp_rank
for layer_idx, scaling_factor in kv_cache_scales_loader(
quantization_param_path,
tp_rank,
tp_size,
self.config.num_hidden_layers,
self.config.__class__.model_type,
):
if not isinstance(self.model.layers[layer_idx], nn.Identity):
self_attn = self.model.layers[layer_idx].self_attn
# Set on both attn_mha (non-absorbed prefill) and attn_mqa (absorbed decode).
for attn in (self_attn.attn_mha, self_attn.attn_mqa):
if attn is not None and hasattr(attn, "k_scale"):
attn.k_scale = scaling_factor
attn.k_scale_float = scaling_factor
def get_embed_and_head(self):
return self.model.embed_tokens.weight, self.lm_head.weight
def set_embed_and_head(self, embed, head):
del self.model.embed_tokens.weight
del self.lm_head.weight
self.model.embed_tokens.weight = embed
self.lm_head.weight = head
torch.cuda.empty_cache()
torch.cuda.synchronize()
@classmethod
def get_model_config_for_expert_location(cls, config):
return ModelConfigForExpertLocation(
num_layers=config.num_hidden_layers,
num_logical_experts=config.n_routed_experts,
num_groups=config.n_group,
)
# ---------------------------------------------------------------------------
# Eagle3 MLA draft model
# ---------------------------------------------------------------------------
class Eagle3MlaDecoderLayer(nn.Module):
"""Single decoder layer for Eagle3 MLA draft model.
The fused_qkv_a_proj_with_mqa is overridden to accept 2x hidden_size
input (concatenated [embeds, hidden_states]) while keeping o_proj at
the standard hidden_size output.
"""
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
layer_id: int = 0,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
super().__init__()
self.mapping = mapping
self.hidden_size = config.hidden_size
self.layer_id = layer_id
rope_theta = get_rope_theta(config)
rope_scaling = getattr(config, "rope_scaling", None)
max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
self.self_attn = DeepseekV3DraftAttentionMLA(
config=config,
mapping=self.mapping,
hidden_size=self.hidden_size,
num_heads=config.num_attention_heads,
qk_nope_head_dim=getattr(config, "qk_nope_head_dim", 128),
qk_rope_head_dim=getattr(config, "qk_rope_head_dim", 64),
v_head_dim=getattr(config, "v_head_dim", 128),
q_lora_rank=getattr(config, "q_lora_rank", None),
kv_lora_rank=getattr(config, "kv_lora_rank", 512),
rope_theta=rope_theta,
rope_scaling=rope_scaling,
max_position_embeddings=max_position_embeddings,
quant_config=quant_config,
layer_id=layer_id,
prefix=add_prefix("self_attn", prefix),
reduce_attn_results=False,
)
if hasattr(self.self_attn, "fused_qkv_a_proj_with_mqa"):
q_lora_rank = getattr(config, "q_lora_rank", 0) or 0
kv_lora_rank = getattr(config, "kv_lora_rank", 512)
qk_rope_head_dim = getattr(config, "qk_rope_head_dim", 64)
self.self_attn.fused_qkv_a_proj_with_mqa = DeepseekV3FusedQkvAProjWithMqa(
2 * self.hidden_size,
q_lora_rank + kv_lora_rank + qk_rope_head_dim,
bias=False,
quant_config=quant_config,
prefix=add_prefix(
"fused_qkv_a_proj_with_mqa",
add_prefix("self_attn", prefix),
),
)
self.mlp = DeepseekV3MLP(
hidden_size=config.hidden_size,
intermediate_size=getattr(
config, "intermediate_size", config.hidden_size * 4
),
hidden_act=getattr(config, "hidden_act", "silu"),
mapping=self.mapping,
quant_config=quant_config,
prefix=add_prefix("mlp", prefix),
)
self.hidden_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.fused_input_hidden_norm = FusedRMSNorm(
self.input_layernorm,
self.hidden_norm,
)
self.post_attention_layernorm = RMSNorm(
config.hidden_size, eps=config.rms_norm_eps
)
self.comm_manager = CommManager(
mapping=self.mapping,
layer_id=self.layer_id,
is_moe=False,
prev_is_moe=False,
post_attn_layernorm=self.post_attention_layernorm,
)
def forward(
self,
positions: torch.Tensor,
embeds: torch.Tensor,
hidden_states: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
residual: torch.Tensor | None,
) -> tuple[torch.Tensor, torch.Tensor]:
residual = hidden_states
if not ctx.forward_mode.is_idle():
fused_norm_out = torch.empty(
embeds.size(0),
self.hidden_size * 2,
dtype=embeds.dtype,
device=embeds.device,
)
# FusedRMSNorm's q_a/kv_a kwargs are MLA-specific names.
# Here embeds and hidden_states corresponds to q_a and kv_a, separately.
self.fused_input_hidden_norm(
input_q_a=embeds,
input_kv_a=hidden_states,
output_q_a=fused_norm_out[..., : self.hidden_size],
output_kv_a=fused_norm_out[..., self.hidden_size :],
)
hidden_states = self.self_attn(
positions=positions,
hidden_states=fused_norm_out,
ctx=ctx,
out_cache_loc=out_cache_loc,
comm_manager=self.comm_manager,
)
# Active first draft step narrows attn output to [bs, H]; align the
# residual to the same live rows before the post-attn reduce-norm.
if ctx.accept_lengths is not None:
residual = residual.index_select(0, ctx.gather_ids)
hidden_states, residual = self.comm_manager.post_attn_reduce_norm(
hidden_states, residual, ctx
)
hidden_states = self.comm_manager.pre_mlp_comm(hidden_states, ctx)
hidden_states = self.mlp(hidden_states)
hidden_states, residual = self.comm_manager.post_mlp_fused(
hidden_states, residual, ctx
)
return hidden_states, residual
class Eagle3MlaModel(nn.Module):
@staticmethod
def _get_eagle_layer_ids(config: PretrainedConfig):
"""Extract eagle aux hidden state layer IDs from config, or None if absent."""
eagle_config = getattr(config, "eagle_config", None)
if eagle_config is None:
return getattr(config, "eagle_aux_hidden_state_layer_ids", None)
if isinstance(eagle_config, dict):
return eagle_config.get("eagle_aux_hidden_state_layer_ids", None)
return getattr(eagle_config, "eagle_aux_hidden_state_layer_ids", None)
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
super().__init__()
self.mapping = mapping
self.config = config
self.vocab_size = config.vocab_size
self.embed_tokens = VocabParallelEmbedding(
config.vocab_size,
config.hidden_size,
prefix=add_prefix("embed_tokens", prefix),
)
layer_ids = self._get_eagle_layer_ids(config)
self.num_fc_input_dim = len(layer_ids) if layer_ids is not None else 3
target_hidden_size = getattr(config, "target_hidden_size", config.hidden_size)
fc_input_size = target_hidden_size * self.num_fc_input_dim
self.fc = ColumnParallelLinear(
fc_input_size,
config.hidden_size,
bias=False,
gather_output=True,
quant_config=quant_config,
prefix=add_prefix("fc", prefix),
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
self.midlayer = Eagle3MlaDecoderLayer(
config,
mapping=self.mapping,
layer_id=0,
quant_config=quant_config,
prefix=prefix,
)
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
ctx: ForwardContext,
out_cache_loc: torch.Tensor,
input_embeds: torch.Tensor | None = None,
captured_hidden_states: torch.Tensor | None = None,
) -> tuple[torch.Tensor, list[torch.Tensor]]:
if captured_hidden_states is None:
raise ValueError("Eagle3 MLA forward requires captured_hidden_states.")
if input_embeds is None:
embeds = self.embed_tokens(input_ids)
else:
embeds = input_embeds
hidden_states = captured_hidden_states
if hidden_states.size(-1) != embeds.size(-1):
hidden_states, _ = self.fc(hidden_states)
residual = None
hidden_states, residual = self.midlayer(
positions,
embeds,
hidden_states,
ctx,
out_cache_loc,
residual,
)
comm_manager = self.midlayer.comm_manager
if comm_manager.should_fuse(hidden_states.size(0)):
hidden_states_to_logits, hidden_states_to_aux, *_ = (
self.norm.forward_with_allreduce_fusion(
self.mapping.dense.tp_rank,
self.mapping.dense.tp_group,
hidden_states,
residual,
)
)
else:
hidden_states_to_logits, hidden_states_to_aux = self.norm(
hidden_states, residual
)
hidden_states_to_logits, _ = comm_manager.post_final_norm_comm(
hidden_states_to_logits, None, ctx
)
hidden_states_to_aux, _ = comm_manager.post_final_norm_comm(
hidden_states_to_aux, None, ctx
)
return hidden_states_to_logits, [hidden_states_to_aux]
class Eagle3DeepseekV2ForCausalLM(DeepseekV3ForCausalLM):
"""Eagle3 MLA draft model for DeepSeek-V2/V3 / Kimi-K2 style architectures.
Inherits weight-loading fusion logic from DeepseekV3ForCausalLM but uses
Eagle3MlaModel internally with a single MLA decoder layer that accepts
concatenated [embeds || hidden_states] as input.
"""
def __init__(
self,
config: PretrainedConfig,
mapping: Mapping,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
nn.Module.__init__(self)
self.config = config
self.mapping = mapping
self.quant_config = quant_config
if self.config.num_hidden_layers != 1:
raise ValueError("Eagle3 MLA drafter currently only supports 1 layer")
self.model = Eagle3MlaModel(
config,
mapping=self.mapping,
quant_config=quant_config,
prefix=add_prefix("model", prefix),
)
self.load_lm_head_from_target = False
if self.config.tie_word_embeddings:
self.lm_head = self.model.embed_tokens
else:
draft_vocab_size = (
getattr(config, "draft_vocab_size", None) or config.vocab_size
)
if not hasattr(config, "draft_vocab_size"):
self.load_lm_head_from_target = True
self.lm_head = ParallelLMHead(
draft_vocab_size,
config.hidden_size,
quant_config=quant_config,
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
prefix=add_prefix("lm_head", prefix),
)
self.logits_processor = LogitsProcessor(
config,
skip_all_gather=self.mapping.attn.has_dp,
do_argmax=True,
tp_rank=self.mapping.attn.tp_rank,
tp_size=self.mapping.attn.tp_size,
tp_group=self.mapping.attn.tp_group,
)
self.capture_aux_hidden_states = True
self.hot_token_id = None
def forward(
self,
ctx: ForwardContext,
input_ids: torch.Tensor,
positions: torch.Tensor,
out_cache_loc: torch.Tensor,
**kwargs,
) -> torch.Tensor:
with report_collective_sizing(ctx, ctx.bs, ctx.global_bs):
return super().forward(ctx, input_ids, positions, out_cache_loc, **kwargs)
def prepare_model_kwargs(
self, ctx: ForwardContext, input_ids: torch.Tensor, kwargs: dict
) -> dict:
model_kwargs = super().prepare_model_kwargs(ctx, input_ids, kwargs)
captured_hidden_states = kwargs.get("captured_hidden_states")
if captured_hidden_states is not None:
model_kwargs["captured_hidden_states"] = captured_hidden_states
else:
# During CUDA graph capture warmup, provide dummy hidden states.
target_hidden_size = getattr(
self.config, "target_hidden_size", self.config.hidden_size
)
num_fc = self.model.num_fc_input_dim
model_kwargs["captured_hidden_states"] = torch.zeros(
input_ids.size(0),
target_hidden_size * num_fc,
dtype=torch.bfloat16,
device=input_ids.device,
)
return model_kwargs
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
remapped = []
for name, loaded_weight in weights:
if "d2t" in name:
self.hot_token_id = loaded_weight + torch.arange(loaded_weight.size(0))
continue
if "t2d" in name:
continue
new_name = re.sub(r"^layers\.0\.", "midlayer.", name)
if "lm_head" not in new_name:
new_name = f"model.{new_name}"
else:
self.load_lm_head_from_target = False
remapped.append((new_name, loaded_weight))
super().load_weights(remapped)
def post_load_weights(self):
self_attn = self.model.midlayer.self_attn
if (
self.quant_config is not None
and hasattr(self.quant_config, "weight_block_size")
and self_attn.kv_b_proj.weight.dtype
in (torch.float8_e4m3fn, torch.float8_e4m3fnuz)
):
weight_block_size = self.quant_config.weight_block_size
if weight_block_size is not None:
if not hasattr(self_attn.kv_b_proj, "weight_scale_inv"):
raise RuntimeError(
"kv_b_proj.weight_scale_inv is required for block FP8 dequant."
)
dtype = torch.get_default_dtype()
w = block_dequant(
self_attn.kv_b_proj.weight,
self_attn.kv_b_proj.weight_scale_inv,
weight_block_size,
).to(dtype)
else:
w = self_attn.kv_b_proj.weight
else:
w = self_attn.kv_b_proj.weight
self_attn.w_kc, self_attn.w_vc = _prepare_mla_kv_b_proj_weights(w, self_attn)
def get_hot_token_id(self):
return self.hot_token_id
def set_embed_and_head(self, embed, head):
if (
hasattr(self.config, "target_hidden_size")
and self.config.target_hidden_size != self.config.hidden_size
):
return
del self.model.embed_tokens.weight
self.model.embed_tokens.weight = embed
if head is not None and self.load_lm_head_from_target:
del self.lm_head.weight
self.lm_head.weight = head
torch.cuda.empty_cache()
torch.cuda.synchronize()
EntryClass = [
DeepseekV3ForCausalLM,
Eagle3DeepseekV2ForCausalLM,
]