from __future__ import annotations import concurrent.futures import functools import logging import time from array import array from contextlib import contextmanager, nullcontext from types import SimpleNamespace from typing import ( TYPE_CHECKING, Any, Callable, Iterable, List, NamedTuple, Optional, Set, Tuple, Union, ) import torch import torch.nn as nn import torch.nn.functional as F import sglang.srt.models.deepseek_v2 as deepseek_v2 from sglang.kernels.ops.attention.dsv4 import ( fused_norm_rope_inplace, fused_q_norm_rope, fused_rope_inplace, ) from sglang.kernels.ops.attention.dsv4.wo_a import MAX_M as _FUSED_WO_A_MAX_TOKENS from sglang.kernels.ops.attention.dsv4.wo_a import ( can_use_fused_wo_a, fused_rope_wo_a_bf16, ) from sglang.kernels.ops.attention.flash_mla_sm120 import SM120_DECODE_MAX_TOKENS from sglang.kernels.ops.gemm.dsv4_wo_a import ( wo_a_bf16_gemv, wo_a_bf16_small_batch, wo_a_bf16_small_batch_mxfp8, ) from sglang.kernels.ops.quantization.fp8_group_major_quant_ue8m0 import ( sglang_per_token_group_quant_fp8_dsv4_wo_a, ) from sglang.kernels.ops.quantization.fp8_kernel import ( sglang_per_token_group_quant_fp8, ) from sglang.srt.compilation.compilation_config import register_split_op from sglang.srt.configs.deepseek_v4 import DeepSeekV4Config from sglang.srt.distributed.device_communicators.pynccl_allocator import ( use_symmetric_memory, ) from sglang.srt.environ import envs from sglang.srt.eplb.expert_distribution import get_global_expert_distribution_recorder from sglang.srt.eplb.expert_location import ModelConfigForExpertLocation from sglang.srt.hardware_backend.npu.dsv4.dsv4_rope import ( Dsv4NpuRoPE, prime_rope_cos_sin, rope_cos_sin, ) from sglang.srt.hardware_backend.npu.utils import ( use_npu_arch35_mxfp8_wo_a, ) from sglang.srt.layers.attention.dsa.utils import ( is_dsa_enable_prefill_cp, ) from sglang.srt.layers.attention.dsv4.compressor import Compressor from sglang.srt.layers.attention.dsv4.dsv41_sparse import ( DeepseekV41Compressor, DeepseekV41Indexer, ) from sglang.srt.layers.attention.dsv4.indexer import C4Indexer from sglang.srt.layers.cp.cp_decode_attn_tp import get_cp_decode_attn_tp_ctx from sglang.srt.layers.cp.utils import ( cp_gather_full_sequence_states, cp_materialize_global_token_order, is_cp_active, ) from sglang.srt.layers.deep_gemm_wrapper.configurer import DEEPGEMM_SCALE_UE8M0 from sglang.srt.layers.dp_attention import ( attn_tp_all_reduce, dp_gather_replicate, get_global_dp_buffer_len, is_allocation_symmetric, is_dp_attention_enabled, ) from sglang.srt.layers.engram import Engram, EngramHasher, EngramLayout from sglang.srt.layers.layer_boundary import append_stages, get_attn_tp_context from sglang.srt.layers.layer_boundary.residual import batch as residual_batch from sglang.srt.layers.layernorm import RMSNorm from sglang.srt.layers.linear import ColumnParallelLinear, RowParallelLinear from sglang.srt.layers.logits_processor import LogitsMetadata, LogitsProcessor from sglang.srt.layers.moe import get_moe_a2a_backend from sglang.srt.layers.moe.fused_moe_triton import FusedMoE from sglang.srt.layers.moe.utils import ( get_num_fused_shared_experts, should_skip_post_experts_all_reduce, uses_per_rank_fused_shared_slots, ) from sglang.srt.layers.quantization.fp8 import Fp8Config, Fp8LinearMethod from sglang.srt.layers.quantization.fp8_utils import ( Mxfp8DenseGemmBackend, view_aiter_fused_rms_transposed_fp8_scale, ) from sglang.srt.layers.quantization.mxfp8_input import Mxfp8SwizzledInput from sglang.srt.layers.rotary_embedding import get_rope_wrapper from sglang.srt.layers.utils import PPMissingLayer, get_layer_id from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding from sglang.srt.managers.mm_utils import ( embed_mm_inputs, pad_mm_tokens, ) from sglang.srt.managers.schedule_batch import MM_PAD_SHIFT_VALUE, MultimodalInputs from sglang.srt.mem_cache.memory_pool import RadixAttention from sglang.srt.model_executor.cuda_graph_config import ( Backend, Phase, check_cuda_graph_backend, ) from sglang.srt.model_executor.forward_batch_info import ( CaptureHiddenMode, PPProxyTensors, ) from sglang.srt.model_executor.forward_context import ( get_attn_backend, get_token_to_kv_pool, ) from sglang.srt.model_executor.runner import ( compile_in_capture_mode, get_is_capture_mode, ) from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph.breakable_cuda_graph import ( eager_on_graph, ) from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph.context import ( is_in_breakable_cuda_graph, ) from sglang.srt.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import ( get_tc_piecewise_forward_context, ) from sglang.srt.model_loader.utils import maybe_executor_submit, should_async_load from sglang.srt.model_loader.weight_utils import ( RUNAI_STREAMER_TENSOR_ATTR, default_weight_loader, ) from sglang.srt.models import deepseek_v4_boundary as hc_boundary from sglang.srt.models import deepseek_v4_mhc as mhc from sglang.srt.models.dbrx import ReplicatedLinear from sglang.srt.models.deepseek_common.amd.deepseek_v4_fused_mhc import ( is_cross_layer_mhc_fusion_enabled, ) from sglang.srt.models.deepseek_common.decoder_swa_bounded_replay import ( enter_late_layer_dp, exit_late_layer_dp, late_layer_dp_counts, ) from sglang.srt.models.deepseek_common.moe_transport import ( OverlappedMoeTransport, _tbo_run_moe, ) from sglang.srt.models.deepseek_common.utils import ( _use_aiter_bpreshuffle_gfx95, is_wint4afp8_or_wint4a16_config, quant_blocks_shared_experts_fusion, ) from sglang.srt.models.deepseek_v2 import ( ParallelLMHead, _is_cuda, _is_hip, _is_npu, _is_xpu, ) from sglang.srt.models.deepseek_v4_mhc import MHC_POST_MULT_VALUE from sglang.srt.models.deepseek_v4_platform_stage import ( NonCudaDecoderMixin, forward_layer_fused_boundary, fused_boundary_attn_input, ) from sglang.srt.models.deepseek_v4_replay_tail import ( DecoderReplayTail, bcg_tail_kv_store, in_replay_tail_graph, ) from sglang.srt.models.deepseek_v41_vit import Aligner, ViT from sglang.srt.models.utils import WeightsMapper from sglang.srt.multimodal.deepseek_v41_image_processing import ( GPU_PLAN_KEY, image_token_types, materialize_image_gpu, ) from sglang.srt.runtime_context import ( get_device, get_exec, get_forward, get_parallel, get_platform, ) from sglang.srt.utils import ( LazyValue, add_prefix, get_bool_env_var, is_gfx95_supported, is_gfx942_supported, is_gfx1250_supported, log_info_on_rank0, make_pp_layers, ) from sglang.srt.utils.custom_op import register_custom_op from sglang.srt.utils.hf_transformers_utils import get_rope_config # NPU-only: bind torch_npu here so _compute_q_b / _forward_prepare can call # torch_npu.npu_rms_norm directly (imports elsewhere aren't visible in this module). if _is_npu: import torch_npu class MhcOps(NamedTuple): hc_split_sinkhorn: Callable[..., Any] mhc_fused_post_pre: Optional[Callable[..., Any]] npu_hc_pre: Optional[Callable[..., Any]] mhc_pre: Optional[Callable[..., Any]] mhc_post: Optional[Callable[..., Any]] fused_hc_head: Optional[Callable[..., Any]] @functools.cache def get_mhc_ops() -> MhcOps: """Load MHC kernels only when a DeepSeek-V4 layer needs them. Model modules are imported eagerly by the registry. Importing ``sglang.kernels.ops.layernorm.mhc`` owns TileLang-backed MHC kernels. Import it only when a DeepSeek-V4 layer executes so registry discovery cannot initialize an optional CUDA runtime before unrelated models set up their communication workspaces. DeepSeek-V4 is the sole consumer here. """ if _is_xpu: from sgl_kernel import ( fused_hc_head, hc_post, hc_split_sinkhorn, mhc_fused_post_pre, mhc_pre, ) return MhcOps( hc_split_sinkhorn=hc_split_sinkhorn, mhc_fused_post_pre=mhc_fused_post_pre, npu_hc_pre=None, mhc_pre=mhc_pre, mhc_post=hc_post, fused_hc_head=fused_hc_head, ) from sglang.kernels.ops.layernorm.mhc import ( hc_split_sinkhorn, mhc_fused_post_pre, npu_hc_pre, ) return MhcOps( hc_split_sinkhorn=hc_split_sinkhorn, mhc_fused_post_pre=mhc_fused_post_pre, npu_hc_pre=npu_hc_pre, mhc_pre=None, mhc_post=None, fused_hc_head=None, ) logger = logging.getLogger(__name__) _FP8_WO_A_GEMM = envs.SGLANG_OPT_FP8_WO_A_GEMM.get() _FP8_WO_A_UE8M0 = _FP8_WO_A_GEMM and DEEPGEMM_SCALE_UE8M0 def wo_a_fp8_gemm_enabled(quant_config: Optional[QuantizationConfig]) -> bool: """The fp8 wo_a absorb GEMM (DeepGEMM fp8_einsum, aiter mxscale) takes 128x128 block scales only; any other layout dequantizes wo_a to bf16 at load.""" return ( _FP8_WO_A_GEMM and isinstance(quant_config, Fp8Config) and quant_config.weight_block_size == [128, 128] ) _NPU_BF16_WO_A_GEMM = _is_npu and envs.SGLANG_OPT_NPU_BF16_WO_A_GEMM.get() _HC_PRENORM_DEEPGEMM_MIN_TOKENS = 1024 DEEPSEEK_V4_STACKED_PARAMS_MAPPING: List[Tuple[str, str, int]] = [ ("gate_up_proj", "gate_proj", 0), ("gate_up_proj", "up_proj", 1), ] def _is_fused_mhc_post_pre_enabled_xpu() -> bool: if _is_xpu: return envs.SGLANG_OPT_FUSE_MHC_POST_PRE.get() return False # FlashInfer's mhc_pre_big_fuse only accepts these split-K counts. _FLASHINFER_MHC_PRE_SPLITS = (1, 2, 4, 8, 16) @functools.cache def _cuda_sm_count() -> int: return torch.cuda.get_device_properties(0).multi_processor_count def _flashinfer_mhc_pre_num_splits(num_tokens: int, hc_hidden_size: int) -> int: block_m = block_k = 64 grid_m = (num_tokens + block_m - 1) // block_m num_block_k = (hc_hidden_size + block_k - 1) // block_k raw = max(1, min(_cuda_sm_count() // max(grid_m, 1), num_block_k // 4)) best = 1 for split in _FLASHINFER_MHC_PRE_SPLITS: if split <= raw: best = split return best def _flashinfer_hc_pre( x: torch.Tensor, hc_fn: torch.Tensor, hc_scale: torch.Tensor, hc_base: torch.Tensor, *, rms_eps: float, hc_eps: float, sinkhorn_iters: int, ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: from flashinfer.mhc import mhc_pre_big_fuse from sglang.srt.layers.deep_gemm_wrapper.entrypoint import tf32_hc_prenorm_gemm num_tokens, hc_mult, hidden_size = x.shape hc_hidden_size = hc_mult * hidden_size mix_dim = hc_fn.shape[0] # hc_mult * (2 + hc_mult) == 24 n_splits = _flashinfer_mhc_pre_num_splits(num_tokens, hc_hidden_size) dot_mix = torch.empty( (n_splits, num_tokens, mix_dim), dtype=torch.float32, device=x.device ) sqrsum = torch.empty((n_splits, num_tokens), dtype=torch.float32, device=x.device) tf32_hc_prenorm_gemm( x.reshape(num_tokens, hc_hidden_size), hc_fn, dot_mix, sqrsum, n_splits ) if n_splits == 1: dot_mix = dot_mix.squeeze(0) sqrsum = sqrsum.squeeze(0) post, comb, layer_input = mhc_pre_big_fuse( dot_mix, sqrsum, x, hc_scale, hc_base, hc_hidden_size, rms_eps=rms_eps, mhc_pre_eps=hc_eps, mhc_sinkhorn_eps=hc_eps, mhc_post_mult_value=MHC_POST_MULT_VALUE, sinkhorn_repeat=sinkhorn_iters, num_splits=n_splits, ) return layer_input, post.squeeze(-1), comb _use_aiter = get_bool_env_var("SGLANG_USE_AITER") and _is_hip _is_gfx95_supported = is_gfx95_supported() _is_gfx942_supported = is_gfx942_supported() _is_gfx1250_supported = is_gfx1250_supported() if _use_aiter: if _is_gfx95_supported or _is_gfx1250_supported: from aiter.ops.triton.fused_fp8_quant import fused_rms_fp8_group_quant def _wo_a_aiter_gemm_eligible( flag: bool, use_aiter: bool, is_hip: bool, is_gfx95: bool ) -> bool: """Static eligibility for the aiter ``wo_a`` reroute. Folds the opt-in flag, the global ``SGLANG_USE_AITER`` switch, and the HIP/gfx95 platform gates into one predicate. Evaluated once at import (see ``_wo_a_aiter_batched_gemm_enabled``) so none of it runs on the per-token decode critical path. """ return bool(flag and use_aiter and is_hip and is_gfx95) # Read the opt-in flag and import the aiter kernel ONCE at module import: the # decode ``wo_a`` matmul runs per layer/token on the critical path, so it must # not pay an ``EnvBool.get()`` plus a function-local import on every call. If the # path is eligible but the kernel import fails, disable it here and fall back to # the einsum for the process (logged once) instead of retrying every step. _wo_a_aiter_batched_gemm_enabled = _wo_a_aiter_gemm_eligible( envs.SGLANG_OPT_USE_AITER_BATCHED_GEMM.get(), _use_aiter, _is_hip, _is_gfx95_supported, ) _wo_a_batched_gemm_bf16 = None if _wo_a_aiter_batched_gemm_enabled: try: from aiter.ops.triton.gemm.batched.batched_gemm_bf16 import ( batched_gemm_bf16 as _wo_a_batched_gemm_bf16, ) except Exception as err: # pragma: no cover - env-dependent _wo_a_aiter_batched_gemm_enabled = False logger.warning( "aiter wo_a batched_gemm_bf16 import failed; using einsum for wo_a " "for the rest of this process: %s", err, ) # Flipped once if the (already-imported) aiter kernel raises at runtime, so a # per-call kernel failure falls back to the einsum for the rest of the process # instead of re-raising (and re-logging) on every layer/token. _wo_a_aiter_batched_gemm_disabled = False # ROCm fp8 wo_a. The CUDA fp8 path below is built on DeepGEMM's fp8_einsum, so # gfx950 runs the equivalent aiter e8m0 block-scale batched GEMM instead. Both # the kernel availability and the weight-scale converter resolve once at import; # ``None`` here means the platform keeps the bf16 absorb GEMM. _wo_a_fp8_mxscale = None _wo_a_fp8_mxscale_fused_invrope = None _wo_a_fp8_mxscale_prequant = None _wo_a_weight_scale_to_e8m0 = None _hip = None if _is_hip: from sglang.srt.models.deepseek_common.amd import deepseek_v4_hip as _hip from sglang.srt.models.deepseek_common.amd.deepseek_v4_wo_a_fp8 import ( apply_wo_a_fp8_mxscale, apply_wo_a_fp8_mxscale_fused_invrope, apply_wo_a_fp8_mxscale_prequant, is_wo_a_fp8_fused_invrope_supported, is_wo_a_fp8_mxscale_supported, wo_a_weight_scale_to_e8m0, ) if is_wo_a_fp8_mxscale_supported(): _wo_a_fp8_mxscale = apply_wo_a_fp8_mxscale _wo_a_weight_scale_to_e8m0 = wo_a_weight_scale_to_e8m0 # Opt-in fused inverse-RoPE + quant front end (env-gated for A/B). Only # bind it when both the flatmm and the fused aiter op are available. if ( envs.SGLANG_OPT_FP8_WO_A_FUSED_INVROPE.get() and is_wo_a_fp8_fused_invrope_supported() ): _wo_a_fp8_mxscale_fused_invrope = apply_wo_a_fp8_mxscale_fused_invrope _wo_a_fp8_mxscale_prequant = apply_wo_a_fp8_mxscale_prequant @functools.lru_cache(maxsize=1) def _fused_wo_a_arch_supported() -> bool: return _is_cuda and torch.cuda.get_device_capability()[0] == 10 def _apply_wo_a_bf16_matmul( o: torch.Tensor, wo_a: torch.Tensor, is_decode: bool, is_target_verify: bool = False, fuse_mxfp8_quant: bool = False, is_prefill: bool = False, fast_path: bool = False, fp8_grid: bool = False, emit_fp8: bool = False, ) -> torch.Tensor | Mxfp8SwizzledInput | Fp8GridActivation | Mxfp8Activation: # o [T, G, D] @ wo_a [G, R, D] -> [T, G, R]; the fast paths below are gated # on the exact validated TP4 shapes and write token-major output directly. global _wo_a_aiter_batched_gemm_disabled # the gfx950 routes, like the CUDA ones, are V4.1's (fast_path); DSv4 keeps the # aiter batched GEMM / einsum below hip_fast_path = fast_path and _is_hip and _is_gfx95_supported hip_decode_verify = ( hip_fast_path and ( (is_decode and o.shape[0] == 1 and o.is_contiguous()) or (is_target_verify and 2 <= o.shape[0] <= 384) ) and _hip.wo_a_split_k_allowed() ) if ( ( fast_path and _is_cuda and ( ( is_decode and o.shape[0] == 1 and (get_platform().is_blackwell or get_platform().is_sm90) ) or ( is_target_verify and 0 < o.shape[0] <= 384 and get_platform().is_blackwell ) or ( is_prefill and 4096 <= o.shape[0] <= 65536 and get_platform().is_blackwell ) ) ) or hip_decode_verify or (hip_fast_path and is_prefill and 4096 <= o.shape[0] <= 65536) ) and ( o.shape[1:] == (2, 4096) and wo_a.shape == (2, 1024, 4096) and o.dtype == wo_a.dtype == torch.bfloat16 and o.stride(2) == 1 and o.stride(1) == 4096 and o.stride(0) >= 8192 and wo_a.is_contiguous() ): if is_decode and o.shape[0] == 1: return wo_a_bf16_gemv(o, wo_a) if 2 <= o.shape[0] <= 8: if fuse_mxfp8_quant: return Mxfp8SwizzledInput(*wo_a_bf16_small_batch_mxfp8(o, wo_a)) return wo_a_bf16_small_batch(o, wo_a) result = torch.empty( (o.shape[0], wo_a.shape[0], wo_a.shape[1]), dtype=o.dtype, device=o.device ) # The strided destination keeps the einsum reduction while producing the # layout wo_b consumes; draft warmup/capture can enter with grad enabled. with torch.no_grad(): torch.bmm( o.transpose(0, 1), wo_a.transpose(1, 2), out=result.transpose(0, 1) ) return result # the 16-row decode kernels also serve V4.1's target-verify rows if (is_decode or is_target_verify) and hip_fast_path: y = _hip.wo_a_fp8_grid_matmul(o, wo_a, fp8_grid, emit_fp8=emit_fp8) if y is not None: return y if ( is_decode and _wo_a_aiter_batched_gemm_enabled and not _wo_a_aiter_batched_gemm_disabled ): try: # aiter batched_gemm_bf16: XQ[B,M,K] @ WQ[B,N,K]^T -> [B,M,N]. # Here batch = group G: XQ = o.transpose(0,1) [G,T,D], WQ = wo_a # [G,R,D] -> [G,T,R] -> transpose back to [T,G,R]. xq = o.transpose(0, 1).contiguous() y = _wo_a_batched_gemm_bf16(xq, wo_a, dtype=torch.bfloat16) return y.transpose(0, 1).contiguous() except Exception as err: _wo_a_aiter_batched_gemm_disabled = True logger.warning( "aiter wo_a batched_gemm_bf16 failed; disabling the reroute and " "falling back to einsum for the rest of this process: %s", err, ) return torch.einsum("tgd,grd->tgr", o, wo_a) def _fused_rmsnorm_fp8_quant(hidden_states, weight, eps, e8m0: bool = False): if e8m0: # Consumers run the e8m0 mxscale GEMM (SGLANG_DSV4_DENSE_MXSCALE). from sglang.kernels.ops.layernorm.rmsnorm_fp8_e8m0 import ( rmsnorm_fp8_e8m0_quant, ) return rmsnorm_fp8_e8m0_quant(hidden_states, weight, eps) x_quant, x_bf16, _, _ = fused_rms_fp8_group_quant( hidden_states, weight, eps, inp2=None, inp2_weight=None, inp2_epsilon=None, group_size=128, dtype_quant=torch.float8_e4m3fn, res1=None, output_unquantized_inp1=True, transpose_scale=_use_aiter_bpreshuffle_gfx95, ) if _use_aiter_bpreshuffle_gfx95: x_quant = ( x_quant[0], view_aiter_fused_rms_transposed_fp8_scale(x_quant[1]), ) return x_quant, x_bf16 def make_hc_mixing_params( hc_mult: int, hidden_size: int ) -> Tuple[ nn.Parameter, nn.Parameter, nn.Parameter, nn.Parameter, nn.Parameter, nn.Parameter ]: mix_hc = (2 + hc_mult) * hc_mult hc_dim = hc_mult * hidden_size return ( nn.Parameter(torch.empty(mix_hc, hc_dim, dtype=torch.float32)), nn.Parameter(torch.empty(mix_hc, hc_dim, dtype=torch.float32)), nn.Parameter(torch.empty(mix_hc, dtype=torch.float32)), nn.Parameter(torch.empty(mix_hc, dtype=torch.float32)), nn.Parameter(torch.empty(3, dtype=torch.float32)), nn.Parameter(torch.empty(3, dtype=torch.float32)), ) def make_hc_head_params( hc_mult: int, hidden_size: int ) -> Tuple[nn.Parameter, nn.Parameter, nn.Parameter]: hc_dim = hc_mult * hidden_size return ( nn.Parameter(torch.empty(hc_mult, hc_dim, dtype=torch.float32)), nn.Parameter(torch.empty(hc_mult, dtype=torch.float32)), nn.Parameter(torch.empty(1, dtype=torch.float32)), ) def hc_head_torch( x: torch.Tensor, hc_fn: torch.Tensor, hc_scale: torch.Tensor, hc_base: torch.Tensor, *, norm_eps: float, hc_eps: float, ) -> torch.Tensor: shape, dtype = x.size(), x.dtype x = x.flatten(-2).float() rsqrt = torch.rsqrt(x.square().mean(-1, keepdim=True) + norm_eps) mixes = F.linear(x, hc_fn) * rsqrt pre = torch.sigmoid(mixes * hc_scale + hc_base) + hc_eps y = torch.sum(pre.unsqueeze(-1) * x.view(shape), dim=-2) return y.to(dtype) _FREQS_CIS_TO_COS_SIN: dict[ Tuple[int, torch.dtype, torch.device], Tuple[torch.Tensor, torch.Tensor] ] = {} def _freqs_cis_to_cos_sin( freqs_cis: torch.Tensor, dtype: torch.dtype, device: torch.device ) -> Tuple[torch.Tensor, torch.Tensor]: """Derive (cos, sin) bf16 contiguous tables from a complex64 `freqs_cis`, cached by `(id(freqs_cis), dtype, device)` so that all layers sharing the same `freqs_cis` (via `precompute_freqs_cis`'s lru_cache) reuse one pair.""" key = (id(freqs_cis), dtype, device) cached = _FREQS_CIS_TO_COS_SIN.get(key) if cached is not None: return cached fr = torch.view_as_real(freqs_cis) cos = fr[..., 0].to(device=device, dtype=dtype).contiguous() sin = fr[..., 1].to(device=device, dtype=dtype).contiguous() _FREQS_CIS_TO_COS_SIN[key] = (cos, sin) return cos, sin def _fp8_wo_a_einsum( o_fp8: torch.Tensor, o_s: torch.Tensor, weight: torch.Tensor, weight_scale: torch.Tensor, recipe: Tuple[int, int, int], ) -> torch.Tensor: import deep_gemm groups, rank = weight.shape[:2] output = torch.empty( o_fp8.shape[0], groups, rank, device=o_fp8.device, dtype=torch.bfloat16 ) deep_gemm.fp8_einsum( "bhr,hdr->bhd", (o_fp8, o_s), (weight, weight_scale), output, recipe=recipe ) return output def _apply_gguf_grouped_wo_a( o: torch.Tensor, qweight: torch.Tensor, qweight_type: int, o_lora_rank: int, matmul_fn: Optional[Callable] = None, ) -> torch.Tensor: if matmul_fn is None: from sglang.srt.layers.quantization.gguf import fused_mul_mat_gguf matmul_fn = fused_mul_mat_gguf group_outputs = [] for group_id in range(o.shape[1]): start = group_id * o_lora_rank group_outputs.append( matmul_fn( o[:, group_id, :].contiguous(), qweight[start : start + o_lora_rank], qweight_type, ) ) return torch.stack(group_outputs, dim=1) def _materialize_cp_unified_fp8_kv( k_nope: torch.Tensor, k_rope: torch.Tensor, forward_batch: ForwardBatch, stream: Optional[Any], ) -> Tuple[torch.Tensor, torch.Tensor]: """Gather a two-pool unified-KV pair in global logical-token order. E8M0 scales live inline in the noPE bytes, so both halves travel as one raw byte row and the CP permutation cannot separate scales, noPE and RoPE. """ nope_bytes = k_nope.view(torch.uint8) rope_bytes = k_rope.view(torch.uint8) packed = torch.cat((nope_bytes, rope_bytes), dim=-1) packed = cp_materialize_global_token_order(packed, forward_batch, stream) nope, rope = packed.split((nope_bytes.shape[-1], rope_bytes.shape[-1]), dim=-1) return nope.contiguous().view(k_nope.dtype), rope.contiguous().view(k_rope.dtype) if TYPE_CHECKING: from sglang.kernels.ops.quantization.mxfp8_amd_gfx95 import ( Fp8GridActivation, Mxfp8Activation, ) from sglang.srt.layers.attention.deepseek_v4_backend import ( DeepseekV4AttnBackend, LateLayerTail, ) from sglang.srt.layers.attention.deepseek_v4_backend_hip_radix import ( DeepseekV4HipRadixBackend, ) from sglang.srt.layers.quantization import QuantizationConfig from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool from sglang.srt.model_executor.forward_batch_info import ForwardBatch @register_custom_op(mutates_args=["output"]) @register_split_op() def deepseek_v4_attention_with_output( query: torch.Tensor, key_value: torch.Tensor, output: torch.Tensor, layer_id: int, compress_ratio: int, attn_sink: torch.Tensor, save_kv_cache: bool, ) -> None: context = get_tc_piecewise_forward_context() forward_batch = context.forward_batch attention_layers = context.attention_layers attention_layer = attention_layers[layer_id] real_num_tokens = forward_batch.global_num_token_non_padded_cpu if real_num_tokens == 0: output.zero_() return query = query[:real_num_tokens] key_value = key_value[:real_num_tokens] original_out_cache_loc = forward_batch.out_cache_loc forward_batch.out_cache_loc = original_out_cache_loc[:real_num_tokens] attn_backend = get_attn_backend() try: ret = attn_backend.forward( q=query, k=key_value, v=key_value, layer=attention_layer, forward_batch=forward_batch, compress_ratio=compress_ratio, attn_sink=attn_sink, save_kv_cache=save_kv_cache, ) finally: forward_batch.out_cache_loc = original_out_cache_loc assert output[:real_num_tokens].numel() == ret.numel(), ( f"Output tensor element mismatch: {output[:real_num_tokens].numel()} != {ret.numel()}" ) output[:real_num_tokens].view(ret.shape).copy_(ret) output[real_num_tokens:].zero_() return bcg_deepseek_v4_attention_with_output = eager_on_graph(True)( deepseek_v4_attention_with_output ) def deepseek_v4_mega_attention_with_output(layer, query, output, attn_sink) -> None: forward_batch = get_tc_piecewise_forward_context().forward_batch real_num_tokens = forward_batch.global_num_token_non_padded_cpu if real_num_tokens: output[:real_num_tokens].copy_( layer._mega_core( query[:real_num_tokens], attn_sink, forward_batch, get_attn_backend() ) ) output[real_num_tokens:].zero_() bcg_deepseek_v4_mega_attention_with_output = eager_on_graph(True)( deepseek_v4_mega_attention_with_output ) def deepseek_v4_low_ratio_sources(layer, x, q_lora, positions) -> None: # The compressor and prefill indexer sync with the host, like the attention. forward_batch = get_tc_piecewise_forward_context().forward_batch real_num_tokens = forward_batch.global_num_token_non_padded_cpu if real_num_tokens == 0: return get_attn_backend().forward_low_ratio_sources( layer=layer, x=x[:real_num_tokens], q_lora=( _hip.live_rows(q_lora, real_num_tokens) if _is_hip else q_lora[:real_num_tokens] ), positions=positions[:real_num_tokens], forward_batch=forward_batch, ) def _low_ratio_sources_capture_stub(layer, x, q_lora, positions) -> None: # A decoder tail graph's capture pass hands the break activations its # segments never computed; the indexer would select candidate blocks from # them and index pages past the live context. Its replay runs the body. if not in_replay_tail_graph(): deepseek_v4_low_ratio_sources(layer, x, q_lora, positions) bcg_deepseek_v4_low_ratio_sources = eager_on_graph( deepseek_v4_low_ratio_sources, capture_stub=_low_ratio_sources_capture_stub ) def deepseek_v4_engram_hash_ids(hasher, input_ids: torch.Tensor) -> torch.Tensor: # The hasher reads per-request rows, so it cannot run inside the CUDA graph. forward_batch = get_tc_piecewise_forward_context().forward_batch return hasher(input_ids, forward_batch) bcg_deepseek_v4_engram_hash_ids = eager_on_graph(True)(deepseek_v4_engram_hash_ids) def deepseek_v4_enter_late_layer_tail(sentinel: torch.Tensor) -> torch.Tensor: # Decoder SWA bounded replay under the prefill CUDA graph: swap the attention # backend onto the tail metadata of the *live* batch (the captured args hold # the capture-time batch). The sentinel keeps the break in the replay sequence. forward_batch = get_tc_piecewise_forward_context().forward_batch get_attn_backend().prefill_graph_enter_late_layer_tail(forward_batch) return sentinel def deepseek_v4_exit_late_layer_tail(sentinel: torch.Tensor) -> torch.Tensor: forward_batch = get_tc_piecewise_forward_context().forward_batch get_attn_backend().prefill_graph_exit_late_layer_tail(forward_batch) return sentinel bcg_deepseek_v4_enter_late_layer_tail = eager_on_graph(True)( deepseek_v4_enter_late_layer_tail ) bcg_deepseek_v4_exit_late_layer_tail = eager_on_graph(True)( deepseek_v4_exit_late_layer_tail ) class MqaAttentionBase(nn.Module): # Class-level default for subclasses that read it without running __init__. wo_a_fp8: bool = False def __init__( self, config: DeepSeekV4Config, layer_id: int, quant_config: Optional[QuantizationConfig], prefix: str, *, compress_ratio: Optional[int] = None, fuse_wqa_wkv: Optional[bool] = None, wo_a_fp8: Optional[bool] = None, wo_a_keeps_quant_config: Optional[bool] = None, wo_b_reduce_results: Optional[bool] = None, rope_original_seq_len: Optional[int] = None, ) -> None: super().__init__() self.is_dsv41 = getattr(config, "model_type", None) == "deepseek_v41" self.attn_tp_rank: int = get_parallel().attn_tp_rank self.attn_tp_size: int = get_parallel().attn_tp_size self.layer_id = layer_id self.dim = config.hidden_size self.hidden_size = config.hidden_size self.qk_rope_head_dim = config.qk_rope_head_dim self.qk_nope_head_dim = config.head_dim - config.qk_rope_head_dim self.head_dim = self.qk_rope_head_dim + self.qk_nope_head_dim self.rope_head_dim = config.qk_rope_head_dim self.n_heads = config.num_attention_heads self.n_local_heads = self.n_heads // self.attn_tp_size self.n_groups = config.o_groups self.n_local_groups = self.n_groups // self.attn_tp_size self.q_lora_rank = config.q_lora_rank self.o_lora_rank = config.o_lora_rank self.eps = config.rms_norm_eps self.softmax_scale = self.head_dim**-0.5 self.q_head_norm = config.q_head_norm self.compress_ratio: int = ( compress_ratio if compress_ratio is not None else config.compress_ratios[layer_id] ) assert self.compress_ratio in ( 0, 1, 2, 4, 128, ), ( f"compress_ratio: expected one of (0, 1, 2, 4, 128), got {self.compress_ratio}" ) assert self.head_dim == config.head_dim assert config.num_key_value_heads == 1 fuse: bool = ( envs.SGLANG_OPT_FUSE_WQA_WKV.get() if fuse_wqa_wkv is None else fuse_wqa_wkv ) fp8: bool = ( wo_a_fp8_gemm_enabled(quant_config) if wo_a_fp8 is None else wo_a_fp8 ) reduce_results: bool = ( (self.attn_tp_size == get_parallel().tp_size and self.attn_tp_size > 1) if wo_b_reduce_results is None else wo_b_reduce_results ) # NPU arch35 runs wo_a as a batched MXFP8 GEMM instead of deep_gemm's FP8 one, # but it needs the same quantized weights. self.use_npu_arch35_mxfp8_wo_a = use_npu_arch35_mxfp8_wo_a(quant_config) quantize_wo_a = fp8 or self.use_npu_arch35_mxfp8_wo_a if wo_a_keeps_quant_config is None: keep_source_quant = ( quant_config is not None and quant_config.get_name() == "expert_pack" ) wo_a_quant_config: Optional[QuantizationConfig] = ( quant_config if quantize_wo_a or keep_source_quant else None ) elif wo_a_keeps_quant_config: wo_a_quant_config = quant_config else: wo_a_quant_config = None self.fuse_wqa_wkv = fuse self.wo_a_fp8 = fp8 self.attn_sink = nn.Parameter(torch.empty(self.n_heads, dtype=torch.float32)) self._attn_sink_local: Optional[torch.Tensor] = None if fuse: self.wqkv_a = ReplicatedLinear( self.hidden_size, self.q_lora_rank + self.head_dim, bias=False, quant_config=quant_config, prefix=add_prefix("wqkv_a", prefix), ) else: self.wq_a = ReplicatedLinear( self.hidden_size, self.q_lora_rank, bias=False, quant_config=quant_config, prefix=add_prefix("wq_a", prefix), ) self.wkv = ReplicatedLinear( self.hidden_size, self.head_dim, bias=False, quant_config=quant_config, prefix=add_prefix("wkv", prefix), ) self.q_norm = RMSNorm(self.q_lora_rank, eps=self.eps) self.wq_b = ColumnParallelLinear( self.q_lora_rank, self.n_heads * self.head_dim, bias=False, quant_config=quant_config, prefix=add_prefix("wq_b", prefix), parallel_group="attn_tp", ) self.kv_norm = RMSNorm(self.head_dim, eps=self.eps) self.wo_a = ColumnParallelLinear( self.n_heads * self.head_dim // self.n_groups, self.n_groups * self.o_lora_rank, bias=False, quant_config=wo_a_quant_config, prefix=add_prefix("wo_a", prefix), parallel_group="attn_tp", **({} if quantize_wo_a else {"params_dtype": torch.bfloat16}), ) if quantize_wo_a: assert hasattr(self.wo_a, "weight_scale_inv"), ( "FP8 quant_config must create weight_scale_inv" ) if self.use_npu_arch35_mxfp8_wo_a: # Read by the NPU arch35 MXFP8 weight processor to batch the # weight/scale per attention group for npu_transpose_quant_batchmatmul. self.wo_a._dsv4_npu_arch35_mxfp8_wo_a = True self.wo_a._dsv4_num_groups = self.n_local_groups self.wo_a._dsv4_o_lora_rank = self.o_lora_rank elif fp8: self.wo_a.weight_scale_inv.format_ue8m0 = _FP8_WO_A_UE8M0 # wo_a is quantized but never *applied* through its quant method: # the absorb GEMM in forward() reads .weight / .weight_scale_inv and # runs its own batched kernel (DeepGEMM fp8_einsum on CUDA, aiter # mxscale BMM on gfx950), both of which want the plain row-major # [G, R, D] weight. Opt out of any backend-private weight layout the # linear method would otherwise install for its own GEMM -- on ROCm # that is aiter's B-preshuffle, which silently permutes the weight # in place (same shape, dtype and strides) and makes this GEMM # return noise. self.wo_a.keep_plain_weight_layout = True self.wo_b = RowParallelLinear( self.n_groups * self.o_lora_rank, self.hidden_size, bias=False, quant_config=quant_config, reduce_results=reduce_results, use_dp_attention_reduce=is_dp_attention_enabled(), prefix=add_prefix("wo_b", prefix), parallel_group="attn_tp", ) from sglang.kernels.ops.attention.dsv4.rope import precompute_freqs_cis rope_theta, rope_scaling = get_rope_config(config) self.rope_scaling = dict(rope_scaling) if rope_scaling else None scaling = self.rope_scaling or {} # RoPE is selected at layer granularity in the reference model. Pure # SWA layers use the main unscaled RoPE, while C4/C128 layers use the # compressed YaRN RoPE for Q, their SWA branch, and compressed KV. self.rope_base = ( config.compress_rope_theta if self.compress_ratio else rope_theta ) original_seq_len: int = ( rope_original_seq_len if rope_original_seq_len is not None else ( scaling["original_max_position_embeddings"] if self.compress_ratio else 0 ) ) self.rope_kwargs = dict( dim=self.qk_rope_head_dim, seqlen=config.max_position_embeddings, original_seq_len=original_seq_len, base=self.rope_base, factor=scaling.get("factor", 1.0), beta_fast=scaling.get("beta_fast", 32), beta_slow=scaling.get("beta_slow", 1), ) freqs_cis = precompute_freqs_cis(**self.rope_kwargs) self.register_buffer("freqs_cis", freqs_cis, persistent=False) self.freqs_cis: torch.Tensor @functools.cached_property def use_flashinfer_mxfp8_wo_b(self) -> bool: """Whether wo_b consumes FlashInfer-swizzled MXFP8, so wo_a can fuse the quantization into its epilogue. Not known until wo_b's weights load.""" quant_method = getattr(self.wo_b, "quant_method", None) return getattr( quant_method, "mxfp8_dense_backend", None ) == Mxfp8DenseGemmBackend.FLASHINFER_CUTEDSL and ( getattr(quant_method, "use_mxfp8", False) or getattr(self.wo_b, "block_fp8_mxfp8_ready", False) ) def _kernel_num_heads(self, num_tokens: int) -> int: if self.attn_tp_size == 1: return self.n_local_heads if get_platform().is_sm120: # Prefill already accepts the native per-rank query width. if num_tokens > SM120_DECODE_MAX_TOKENS: return self.n_local_heads if envs.SGLANG_SM120_FLASHMLA_BACKEND.get() == "flashinfer": from sglang.kernels.ops.attention.flash_mla_sm120 import ( flashinfer_dsv4_decode_supports_num_heads, ) if flashinfer_dsv4_decode_supports_num_heads( self.n_local_heads, num_tokens ): return self.n_local_heads # Other FlashMLA implementations retain their existing padded shape. return 64 if self.n_local_heads <= 64 else self.n_heads def _zero_padded_q(self, x, kernel_num_heads, attn_backend) -> torch.Tensor: # One zeroed buffer per metadata, grown to the most rows it has served; each # call takes a row slice. A graph that keeps the metadata keeps the buffer, # so the buffer must not grow once a graph has captured it. meta = getattr(attn_backend, "forward_metadata", None) buf = getattr(meta, "q_pad_buffer", None) if ( buf is None or buf.shape[0] < x.shape[0] or buf.shape[1:] != (kernel_num_heads, self.head_dim) or buf.dtype != x.dtype ): assert not getattr(meta, "q_pad_captured", False), ( "the padded query buffer grew after a CUDA graph captured it" ) buf = x.new_zeros(x.shape[0], kernel_num_heads, self.head_dim) if meta is not None: try: meta.q_pad_buffer = buf except (AttributeError, TypeError): pass if meta is not None and torch.cuda.is_current_stream_capturing(): try: meta.q_pad_captured = True except (AttributeError, TypeError): pass return buf[: x.shape[0]] def _wo_a_quant_freqs( self, forward_batch: ForwardBatch, q_rope: Optional[torch.Tensor] ) -> Optional[torch.Tensor]: """Rope table when the fp8 unified_kv decode (aiter FlyDSL) should also do the inverse RoPE and the wo_a mxfp8 quant; else None.""" mode = forward_batch.forward_mode if q_rope is None or not (mode.is_decode() or mode.is_target_verify()): return None if not hasattr(self, "_wo_a_freqs"): from sglang.kernels.ops.attention.dsv4.unified_kv_kernels import runtime fused = ( _wo_a_fp8_mxscale_prequant is not None and self.wo_a_fp8 and runtime.flydsl_decode_supported(q_rope.shape[1]) ) self._wo_a_freqs = ( torch.view_as_real(self.freqs_cis).flatten(1) if fused else None ) return self._wo_a_freqs def _local_attn_sink(self, kernel_num_heads: Optional[int] = None) -> torch.Tensor: if self.attn_tp_size == 1: return self.attn_sink rank = self.attn_tp_rank num_heads = self.n_local_heads padded_num_heads = 64 if num_heads <= 64 else self.n_heads if kernel_num_heads is None: # Preserve the legacy contract for subclasses such as DSpark that # always pad their attention query independently of this helper. kernel_num_heads = padded_num_heads assert kernel_num_heads >= num_heads # Keep one fallback-width allocation and return a view matching Q. # Prefill and decode can alternate, and CUDA graphs can retain the # view, so replacing this tensor when the path changes would # both reallocate every transition and risk invalidating a captured # pointer. sink_num_heads = max(kernel_num_heads, padded_num_heads) if self._attn_sink_local is None: sink = self.attn_sink.new_zeros(sink_num_heads) self._attn_sink_local_slice = slice( rank * num_heads, (rank + 1) * num_heads ) sink[:num_heads] = self.attn_sink[self._attn_sink_local_slice] self._attn_sink_local = sink return self._attn_sink_local[:kernel_num_heads] def refresh_attn_sink_cache(self) -> None: if self._attn_sink_local is None: return src = self.attn_sink.data[self._attn_sink_local_slice] self._attn_sink_local[: src.numel()].copy_(src) def _q_lora_quant_e8m0(self, with_indexer: bool) -> bool: """Whether the fused q_norm quant feeds wq_b (and the indexer's wq_b, which reuses it) with ue8m0 scales: only when each consumer runs the e8m0 mxscale GEMM. Resolved on first use, after the weights are processed.""" key = "_q_lora_e8m0_idx" if with_indexer else "_q_lora_e8m0" cached = self.__dict__.get(key) if cached is None: from sglang.srt.layers.quantization.fp8_utils import dense_mxscale_ready indexer = getattr(self, "indexer", None) if with_indexer else None cached = dense_mxscale_ready( self.wq_b, getattr(indexer, "wq_b", None) if indexer else None ) self.__dict__[key] = cached return cached @contextmanager def maybe_use_decode_attn_tp(self, forward_batch: ForwardBatch): ctx = get_cp_decode_attn_tp_ctx() attn = self.attn_mqa if isinstance(self, MQALayer) else self.attn with ctx.maybe_use_decode_attn_tp( forward_batch, [self.wq_b, self.wo_a, self.wo_b], radix_attn=attn, ): if ctx.use_decode_attn_tp: orig = ( self.n_local_heads, self.n_local_groups, self.attn_tp_rank, self.attn_tp_size, ) decode_tp_size = ctx.decode_tp_size self.n_local_heads = self.n_heads // decode_tp_size self.n_local_groups = self.n_groups // decode_tp_size self.attn_tp_rank = ctx.decode_tp_rank self.attn_tp_size = decode_tp_size try: yield finally: ( self.n_local_heads, self.n_local_groups, self.attn_tp_rank, self.attn_tp_size, ) = orig else: yield class MQALayer(MqaAttentionBase): is_dsv41: bool = False use_mega_attention: bool = False def __init__( self, config: DeepSeekV4Config, layer_id: int, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", alt_streams: Optional[List[torch.cuda.Stream]] = None, compress_ratio_override: Optional[int] = None, ) -> None: super().__init__( config, layer_id, quant_config, prefix, compress_ratio=compress_ratio_override, ) active_rope_scaling = None if self.compress_ratio: active_rope_scaling = dict(self.rope_scaling or {}) active_rope_scaling["rope_type"] = "deepseek_yarn" self.rotary_emb = get_rope_wrapper( head_size=self.rope_head_dim, rotary_dim=self.rope_head_dim, max_position=config.max_position_embeddings, base=self.rope_base, rope_scaling=active_rope_scaling, is_neox_style=False, device=get_device().device, ) if _is_npu: rope = Dsv4NpuRoPE.for_freqs( self.freqs_cis, getattr(self, "rotary_emb", None) ) # fp32 tables feed the compressor gather; bf16 tables make the # activation-dtype gathers cast-free. Bit-identical values: # rounding the table once equals rounding each gathered element. rope.ensure_tables(torch.float32) rope.ensure_tables(torch.bfloat16) # npu_rms_norm has no weight-free overload; the per-head q norm # reads this cached ones vector instead of paying a per-call # alloc + fill. self.register_buffer( "q_rms_norm_ones", torch.ones(self.head_dim, dtype=torch.bfloat16), persistent=False, ) if _is_hip: cos_cache = ( self.freqs_cis.real.to(torch.bfloat16).unsqueeze(-2).unsqueeze(-2) ) sin_cache = ( self.freqs_cis.imag.to(torch.bfloat16).unsqueeze(-2).unsqueeze(-2) ) self.register_buffer("cos_cache", cos_cache, persistent=False) self.register_buffer("sin_cache", sin_cache, persistent=False) if alt_streams is not None and ( ( # the gfx950 overlap is validated on V4.1 only; DSv4 on ROCm keeps one stream (_is_cuda or (_is_gfx95_supported and self.is_dsv41)) and envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.get() ) or (_is_npu and envs.SGLANG_NPU_USE_MULTI_STREAM.get()) ): self.alt_streams = alt_streams[:3] self.alt_streams_indexer = alt_streams[-2:] else: self.alt_streams = None self.alt_streams_indexer = None self._multi_stream_bs_limit = 128 if get_platform().is_blackwell else 64 self.compressor = None self.indexer = None if self.compress_ratio in (4, 128): expert_pack_quant_config = ( quant_config if quant_config is not None and quant_config.get_name() == "expert_pack" else None ) self.compressor = Compressor( config, layer_id=self.layer_id, is_in_indexer=False, freqs_cis=self.freqs_cis, compress_ratio=self.compress_ratio, head_dim=self.head_dim, rotate=False, prefix=add_prefix("compressor", prefix), quant_config=expert_pack_quant_config, rotary_emb=self.rotary_emb, ) if self.compress_ratio == 4: self.indexer = C4Indexer( config, freqs_cis=self.freqs_cis, layer_id=layer_id, quant_config=quant_config, prefix=add_prefix("indexer", prefix), alt_streams=self.alt_streams_indexer, rotary_emb=self.rotary_emb, fp4_cos=(self.cos_cache[:, 0, 0, :] if _is_hip else None), fp4_sin=(self.sin_cache[:, 0, 0, :] if _is_hip else None), ) elif self.compress_ratio in (1, 2): # The layers in between read both through the attention backend. if self.layer_id in config.kv_source_layer_ids: self.compressor = DeepseekV41Compressor( hidden_size=config.hidden_size, head_dim=self.head_dim, compress_ratio=self.compress_ratio, eps=config.rms_norm_eps, ) if self.layer_id in config.index_source_layer_ids: self.indexer = DeepseekV41Indexer( config, layer_id=self.layer_id, head_dim=self.head_dim, quant_config=quant_config, prefix=add_prefix("indexer", prefix), ) self.attn_mqa = RadixAttention( self.n_local_heads, self.head_dim, self.softmax_scale, num_kv_heads=1, layer_id=layer_id, quant_config=quant_config, prefix=add_prefix("attn_mqa", prefix), ) self.use_mega_attention = ( self.is_dsv41 and _is_cuda and get_exec().kernel.dsv4_attn_backend == "flashmla_mega" ) self.mega_cos_sin_cache = None if self.use_mega_attention: from sglang.kernels.ops.attention.dsv4.rope import ( precompute_cos_sin_table, ) self.mega_cos_sin_cache = precompute_cos_sin_table(**self.rope_kwargs) self.mega_wo_a = None self.mega_wq_b_permuted = False self.use_fused_qk_norm_rope = _is_hip and _hip.use_fused_qk_norm_rope(self) # Static eligibility; token count and wo_b's output format are checked # in forward, after weights have loaded. self.use_fused_wo_a = ( self.is_dsv41 and envs.SGLANG_DSV41_FUSED_WO_A.get() and _fused_wo_a_arch_supported() and not self.wo_a_fp8 and not self.use_npu_arch35_mxfp8_wo_a and self.wo_a.weight.dtype == torch.bfloat16 and self.wo_a.weight.shape == (self.n_local_groups * self.o_lora_rank, 4096) and can_use_fused_wo_a(self.n_local_groups, self.o_lora_rank) ) if _is_hip: _hip.init_mqa_layer(self, quant_config) # KV cache write is always fused into the K kernel # (`_compute_kv_to_cache`), so the legacy "overlap store cache" flag # has no effect here -- the fused path is on by default. def _apply(self, fn, recurse=True): result = super()._apply(fn, recurse=recurse) if self.indexer is not None and hasattr(self.indexer.compressor, "fp4_cos"): self.indexer.compressor.fp4_cos = self.cos_cache[:, 0, 0, :] self.indexer.compressor.fp4_sin = self.sin_cache[:, 0, 0, :] return result def setup_mega_attention(self) -> None: if not self.use_mega_attention: return from sglang.srt.layers.attention.deepseek_v4_mega_backend import ( interleave_output_columns, interleave_query_rows, quantize_wo_a, ) self.mega_wq_b_permuted = ( self.n_local_heads in (64, 128) and self._wq_b_uses_flashinfer_mxfp8() ) if self.mega_wq_b_permuted: method = self.wq_b.quant_method if method.use_mxfp8: for param in (self.wq_b.weight, self.wq_b.weight_scale_inv): param.data.copy_( interleave_query_rows(param.data, self.n_local_heads) ) method._process_mxfp8_linear_weight_scale(self.wq_b) else: block_n, block_k = method.weight_block_size sf = self.wq_b.weight_scale_inv.data.repeat_interleave( block_n // 16, dim=0 ) sf = ( sf.view(self.n_local_heads, 32, -1) .transpose(0, 1) .reshape(sf.shape) ) self.wq_b.weight.data.copy_( interleave_query_rows(self.wq_b.weight.data, self.n_local_heads) ) self.wq_b.weight_scale_inv.data = sf.contiguous() method.weight_block_size = [16, block_k] wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) self.mega_wo_a = quantize_wo_a(interleave_output_columns(wo_a)) def _wq_b_uses_flashinfer_mxfp8(self) -> bool: method = self.wq_b.quant_method backend = getattr(method, "mxfp8_dense_backend", None) return bool( ( getattr(method, "use_mxfp8", False) or getattr(method, "block_fp8_as_mxfp8", False) ) and backend is not None and (backend.is_flashinfer_cutlass() or backend.is_flashinfer_cutedsl()) ) def _forward_mega( self, q: torch.Tensor, kernel_num_heads: int, attn_sink: torch.Tensor, forward_batch: ForwardBatch, attn_backend, ) -> torch.Tensor: from sglang.srt.layers.attention.deepseek_v4_mega_backend import pack_query if forward_batch.forward_mode.is_idle(): return q.new_zeros(q.shape[0], self.n_local_groups, self.o_lora_rank) if not self.mega_wq_b_permuted: q = pack_query(q, kernel_num_heads) if forward_batch.forward_mode.is_extend() and is_in_breakable_cuda_graph(): o = q.new_empty((q.shape[0], self.n_local_groups, self.o_lora_rank)) bcg_deepseek_v4_mega_attention_with_output(self, q, o, attn_sink) return o return self._mega_core(q, attn_sink, forward_batch, attn_backend) def _mega_core( self, q: torch.Tensor, attn_sink: torch.Tensor, forward_batch: ForwardBatch, attn_backend, ) -> torch.Tensor: data, scale = attn_backend.forward( q=q, k=q, v=q, layer=self.attn_mqa, forward_batch=forward_batch, compress_ratio=self.compress_ratio, attn_sink=attn_sink, save_kv_cache=False, cos_sin_cache=self.mega_cos_sin_cache, ) groups = self.n_local_groups return _fp8_wo_a_einsum( data[:, :groups], scale[:, :groups], *self.mega_wo_a, recipe=(1, 1, 32) ) def _get_npu_rope_position_cache( self, forward_batch: ForwardBatch, positions: torch.Tensor, dtype: torch.dtype, inverse: bool = False, ) -> Tuple[torch.Tensor, torch.Tensor]: # ``rotary_emb`` is shared by layers with the same RoPE configuration and # can also be shared by the target and NextN models. Only the immutable # full table is cached on it; position-gathered tensors are memoized per # forward (prime_rope_cos_sin / rope_cos_sin), never across forwards -- # reusing them based on shape alone gives MTP decode the previous step's # RoPE values when positions change but batch size does not. return rope_cos_sin( self.freqs_cis, getattr(self, "rotary_emb", None), forward_batch, positions, dtype, inverse=inverse, ) def accepts_mxfp8_swizzled_input(self) -> bool: """Whether the first projection consumes a 128x4 MXFP8 activation tuple.""" cached = getattr(self, "_accepts_mxfp8_swizzled_input", None) if cached is not None: return cached if self.fuse_wqa_wkv: linears = [getattr(self, "wqkv_a", None)] else: # Both projections read the same activation on this path. linears = [getattr(self, "wq_a", None), getattr(self, "wkv", None)] def _takes_swizzled(linear) -> bool: method = getattr(linear, "quant_method", None) return bool( linear is not None and getattr(method, "mxfp8_dense_backend", None) in ( Mxfp8DenseGemmBackend.FLASHINFER_CUTEDSL, Mxfp8DenseGemmBackend.FLASHINFER_CUTLASS, ) and ( getattr(method, "use_mxfp8", False) or getattr(linear, "block_fp8_mxfp8_ready", False) ) ) ok = all(_takes_swizzled(linear) for linear in linears) self._accepts_mxfp8_swizzled_input = ok return ok def _normalize_q_lora( self, q: torch.Tensor ) -> Tuple[torch.Tensor, torch.Tensor | Mxfp8SwizzledInput]: # The indexer needs the BF16 normalized row; wq_b needs the quantized one. if _is_hip: return _hip.q_norm_for_wq_b(self, q) method = self.wq_b.quant_method if ( _is_cuda and self.is_dsv41 and get_platform().is_blackwell and q.dtype == self.q_norm.weight.dtype == torch.bfloat16 and q.ndim == 2 and 0 < q.shape[0] <= 8 and q.shape[1] == 1280 and q.stride(1) == 1 and getattr(method, "mxfp8_dense_backend", None) == Mxfp8DenseGemmBackend.FLASHINFER_CUTEDSL and ( getattr(method, "use_mxfp8", False) or getattr(self.wq_b, "block_fp8_mxfp8_ready", False) ) ): from sglang.srt.batch_invariant_ops import is_batch_invariant_mode_enabled from sglang.srt.runtime_context import get_exec if not ( is_batch_invariant_mode_enabled() or get_exec().deterministic.enable_deterministic_inference ): from sglang.kernels.ops.layernorm.rmsnorm_mxfp8 import rmsnorm_mxfp8 y, quant, scale = rmsnorm_mxfp8( q, self.q_norm.weight, self.q_norm.variance_epsilon ) return y, Mxfp8SwizzledInput(quant, scale) q = self.q_norm(q) return q, q def _compute_q_a( self, x: torch.Tensor, qkv_a: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor, torch.Tensor | Mxfp8SwizzledInput]: if qkv_a is not None: q = qkv_a[..., : self.q_lora_rank] else: q, _ = self.wq_a(x) return self._normalize_q_lora(q) def _compute_q_b( self, q: torch.Tensor, positions: torch.Tensor, q_out: Optional[torch.Tensor] = None, ) -> torch.Tensor: q, _ = self.wq_b(q) q = q.view(-1, self.n_local_heads, self.head_dim) if self.use_mega_attention: return q if not self.q_head_norm: if q_out is not None and q_out.dtype == torch.float8_e4m3fn: fused_q_norm_rope(q, q_out, None, self.freqs_cis, positions) return q_out # TODO: enable zero-copy path to skip the extra no-rope copy overhead if (_is_cuda or _is_gfx95_supported) and q_out is not None: fused_q_norm_rope( q, q_out, None, # eps = None means no norm self.freqs_cis, positions, ) return q_out fused_rope_inplace( q[..., -self.qk_rope_head_dim :], None, self.freqs_cis, positions=positions, ) if q_out is None: return q q_out.copy_(q) return q_out if q_out is None: q_out = torch.empty_like(q) # Fused warp-per-(token, head) rmsnorm-self + RoPE + write to q_out. fused_q_norm_rope(q, q_out, self.eps, self.freqs_cis, positions) return q_out def _compute_kv_to_cache( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, qkv_a: Optional[torch.Tensor] = None, q_rope: Optional[torch.Tensor] = None, ) -> None: """Fused: rmsnorm + RoPE + write directly to FlashMLA paged cache. Replaces the bf16-kv-intermediate path. Used everywhere except the DSA prefill-CP case (which needs bf16 kv for the cross-rank all-gather). q_rope ([T, H, head_dim]) has its query heads roped by the same launch. """ if envs.SGLANG_DSV4_USE_BF16_KV_QUANT_SOURCE.get(): assert q_rope is None # Quantize the nope payload from bf16-rounded values (the fused # kernel quantizes from fp32 registers; the bf16 rounding moves # values across fp8 bins relative to bf16-sourced consumers). kv = self._compute_kv_bf16(x, positions, qkv_a=qkv_a) attn_backend.store_cache( layer_id=self.layer_id, swa_k=kv, forward_batch=forward_batch ) return if qkv_a is not None: kv = qkv_a[..., self.q_lora_rank :] else: kv, _ = self.wkv(x) token_to_kv_pool = get_token_to_kv_pool() if TYPE_CHECKING: assert isinstance(token_to_kv_pool, DeepSeekV4TokenToKVPool) token_to_kv_pool.set_swa_key_buffer_radix_fused_norm_rope( layer_id=self.layer_id, swa_loc=attn_backend.get_swa_out_cache_loc(forward_batch), kv=kv, kv_weight=self.kv_norm.weight.data, eps=self.eps, freqs_cis=self.freqs_cis, positions=positions, # the query RoPE fused into this store is HIP only **({} if q_rope is None else {"q": q_rope}), ) def _compute_kv_bf16( self, x: torch.Tensor, positions: torch.Tensor, qkv_a: Optional[torch.Tensor] = None, ) -> torch.Tensor: """Bf16-kv path used by the DSA prefill-CP case (needs all-gather).""" if qkv_a is not None: kv = qkv_a[..., self.q_lora_rank :] else: kv, _ = self.wkv(x) kv = kv.contiguous() fused_norm_rope_inplace( kv, self.kv_norm.weight.data, self.eps, self.freqs_cis, positions, ) return kv def _forward_prepare_multi_stream( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, q_out: Optional[torch.Tensor] = None, x_quant=None, ) -> torch.Tensor: assert self.alt_streams is not None assert len(self.alt_streams) >= 3 current_stream = torch.cuda.current_stream() stream_kv = self.alt_streams[0] stream_compressor = self.alt_streams[1] stream_indexer = self.alt_streams[2] stream_kv.wait_stream(current_stream) stream_compressor.wait_stream(current_stream) stream_indexer.wait_stream(current_stream) x_linear = x_quant if x_quant is not None else x qkv_a: Optional[torch.Tensor] = None qkv_a_ready: Optional[torch.cuda.Event] = None if self.fuse_wqa_wkv: qkv_a, _ = self.wqkv_a(x_linear) qkv_a_ready = current_stream.record_event() q_lora, q_for_wqb = self._compute_q_a(x_linear, qkv_a=qkv_a) q_lora_ready = current_stream.record_event() if self.indexer is not None: with torch.cuda.stream(stream_indexer): self.indexer( x=x, q_lora=q_lora, forward_batch=forward_batch, attn_backend=attn_backend, enable_multi_stream=True, q_lora_ready=q_lora_ready, ) with torch.cuda.stream(stream_kv): if qkv_a_ready is not None: stream_kv.wait_event(qkv_a_ready) # Fused norm + rope + cache write -- no bf16 KV intermediate. self._compute_kv_to_cache( x_linear, positions, forward_batch, attn_backend, qkv_a=qkv_a ) if self.compressor is not None: with torch.cuda.stream(stream_compressor): attn_backend.forward_core_compressor( x, forward_batch, self.layer_id, self.compressor ) q = self._compute_q_b(q_for_wqb, positions, q_out) current_stream.wait_stream(stream_kv) current_stream.wait_stream(stream_compressor) current_stream.wait_stream(stream_indexer) del qkv_a return q def _forward_prepare_low_ratio_multi_stream( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, q_out: Optional[torch.Tensor] = None, x_quant=None, ) -> torch.Tensor: # Both side streams are joined before returning, and nothing they read is # released before the join. assert self.alt_streams is not None current_stream = torch.cuda.current_stream() stream_kv = self.alt_streams[0] stream_sources = self.alt_streams[-1] x_linear = x_quant if x_quant is not None else x # NOTE: wait for x ready if self.compressor is not None: stream_sources.wait_stream(current_stream) qkv_a: Optional[torch.Tensor] = None if self.fuse_wqa_wkv: qkv_a, _ = self.wqkv_a(x_linear) if self.compressor is not None: with torch.cuda.stream(stream_sources): attn_backend.forward_low_ratio_sources( layer=self, x=x, q_lora=None, positions=positions, forward_batch=forward_batch, run_indexer=False, ) stream_kv.wait_stream(current_stream) q_lora, q_for_wqb = self._compute_q_a(x_linear, qkv_a=qkv_a) # NOTE: wait for the q_lora ready if self.indexer is not None: stream_sources.wait_stream(current_stream) q = self._compute_q_b(q_for_wqb, positions, q_out) if self.indexer is not None: # Forked above, right after q_lora; recorded here, after the Q chain. with torch.cuda.stream(stream_sources): attn_backend.forward_low_ratio_sources( layer=self, x=x, q_lora=q_lora, positions=positions, forward_batch=forward_batch, run_compressor=False, ) with torch.cuda.stream(stream_kv): self._compute_kv_to_cache( x_linear, positions, forward_batch, attn_backend, qkv_a=qkv_a ) current_stream.wait_stream(stream_kv) if self.compressor is not None or self.indexer is not None: current_stream.wait_stream(stream_sources) return q def _forward_prepare_multi_stream_npu( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, q_out: Optional[torch.Tensor] = None, x_quant=None, ) -> torch.Tensor: # NPU multi-stream: KV on stream_kv, Q on stream_q, overlapped with # indexer/compressor on current. rope is split; the kv-only call passes # kv.unsqueeze(1) as q_rope so the op sees [T,1,1,head_dim] like the # fused path. assert self.alt_streams is not None current_stream = torch.npu.current_stream() stream_kv = self.alt_streams[0] stream_q = self.alt_streams[1] stream_kv.wait_stream(current_stream) stream_q.wait_stream(current_stream) x_linear = x_quant if x_quant is not None else x qkv_a: Optional[torch.Tensor] = None qkv_a_ready = None if self.fuse_wqa_wkv: qkv_a, _ = self.wqkv_a(x_linear) qkv_a_ready = current_stream.record_event() if qkv_a is not None: q_lora = qkv_a[..., : self.q_lora_rank] else: q_lora, _ = self.wq_a(x_linear) q_lora = self.q_norm(q_lora) q_lora_ready = current_stream.record_event() # KV block on stream_kv. with torch.npu.stream(stream_kv): if qkv_a_ready is not None: stream_kv.wait_event(qkv_a_ready) if qkv_a is not None: kv = qkv_a[..., self.q_lora_rank :] else: kv, _ = self.wkv(x) kv = self.kv_norm(kv) cos4_k, sin4_k = self._get_npu_rope_position_cache( forward_batch, positions, kv.dtype, inverse=False ) Dsv4NpuRoPE.apply_rotary_mul_inplace( kv.unsqueeze(1), None, cos4_k, sin4_k, qk_nope_dim=self.qk_nope_head_dim, ) attn_backend.store_cache( layer_id=self.layer_id, swa_k=kv, forward_batch=forward_batch, ) # Q block on stream_q (needs only q_lora). with torch.npu.stream(stream_q): stream_q.wait_event(q_lora_ready) q, _ = self.wq_b(q_lora) q = q.view(-1, self.n_local_heads, self.head_dim) q = torch_npu.npu_rms_norm(q, self.q_rms_norm_ones, self.eps)[0] cos4_q, sin4_q = self._get_npu_rope_position_cache( forward_batch, positions, q.dtype, inverse=False ) Dsv4NpuRoPE.apply_rotary_mul_inplace( q, None, cos4_q, sin4_q, qk_nope_dim=self.qk_nope_head_dim, ) if q_out is not None: q_out.copy_(q) q.record_stream(stream_q) # Indexer + compressor: serial on current. if self.indexer is not None: self.indexer( x=x, q_lora=q_lora, forward_batch=forward_batch, attn_backend=attn_backend, ) if self.compressor is not None: attn_backend.forward_core_compressor( x, forward_batch, self.layer_id, self.compressor, ) # Join stream_kv + stream_q before downstream attention. current_stream.wait_stream(stream_kv) current_stream.wait_stream(stream_q) del qkv_a return q def _forward_prepare_multi_stream_hip( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, q_out: Optional[torch.Tensor] = None, x_quant=None, ) -> torch.Tensor: """ATOM-style ROCm path: overlap compressors, keep Q/KV on main stream.""" assert self.alt_streams is not None assert len(self.alt_streams) >= 1 current_stream = torch.cuda.current_stream() stream_compressor = self.alt_streams[0] stream_indexer_compressor = ( self.alt_streams[1] if len(self.alt_streams) > 1 else None ) if self.compressor is not None: stream_compressor.wait_stream(current_stream) with torch.cuda.stream(stream_compressor): attn_backend.forward_core_compressor( x, forward_batch, self.layer_id, self.compressor ) if self.indexer is not None and stream_indexer_compressor is not None: stream_indexer_compressor.wait_stream(current_stream) with torch.cuda.stream(stream_indexer_compressor): attn_backend.forward_indexer_compressor( x=x, forward_batch=forward_batch, layer_id=self.indexer.layer_id, compressor=self.indexer.compressor, ) x_linear = x_quant if x_quant is not None else x if self.fuse_wqa_wkv: qkv_a, _ = self.wqkv_a(x_linear) q_lora = qkv_a[..., : self.q_lora_rank] else: q_lora, _ = self.wq_a(x_linear) qkv_a = None if self.use_fused_qk_norm_rope: if _is_gfx95_supported or _is_gfx1250_supported: q_for_wqb, q_lora = _fused_rmsnorm_fp8_quant( q_lora, self.q_norm.weight, self.q_norm.variance_epsilon, e8m0=self._q_lora_quant_e8m0(with_indexer=False), ) q, _ = self.wq_b(q_for_wqb) else: q_lora = self.q_norm(q_lora) q, _ = self.wq_b(q_lora) kv = ( qkv_a[..., self.q_lora_rank :] if qkv_a is not None else self.wkv(x_linear)[0] ) from sglang.kernels.ops.attention.fused_qk_norm_rope_store import ( fused_qk_norm_rope_swa_store, ) token_to_kv_pool = get_token_to_kv_pool() swa_loc = attn_backend.get_swa_out_cache_loc(forward_batch) swa_cache = token_to_kv_pool.get_swa_raw_buffer(self.layer_id) swa_page_size = token_to_kv_pool.swa_page_size q = fused_qk_norm_rope_swa_store( q=q, kv=kv, q_norm_weight=None, kv_norm_weight=self.kv_norm.weight, q_rms_eps=self.eps, kv_rms_eps=self.eps, rope_head_dim=self.qk_rope_head_dim, cos_cache=self.cos_cache, sin_cache=self.sin_cache, positions=positions, swa_cache=swa_cache, swa_loc=swa_loc, swa_page_size=swa_page_size, q_out=q_out, dtype=x.dtype, ) else: q_lora, q_for_wqb = self._normalize_q_lora(q_lora) q = self._compute_q_b(q_for_wqb, positions, q_out) self._compute_kv_to_cache( x_linear, positions, forward_batch, attn_backend, qkv_a=qkv_a ) del qkv_a if self.indexer is not None: current_stream.wait_stream(stream_compressor) if stream_indexer_compressor is not None: current_stream.wait_stream(stream_indexer_compressor) self.indexer( x=x, q_lora=q_lora, forward_batch=forward_batch, attn_backend=attn_backend, skip_compressor=True, ) elif self.compressor is not None: current_stream.wait_stream(stream_compressor) return q def _forward_prepare( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, attn_backend, q_out: Optional[torch.Tensor] = None, x_quant=None, q_rope_out: Optional[torch.Tensor] = None, k_nope_out: Optional[torch.Tensor] = None, k_rope_out: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: x_linear = x_quant if x_quant is not None else x if self.fuse_wqa_wkv: qkv_a, _ = self.wqkv_a(x_linear) q_lora = qkv_a[..., : self.q_lora_rank] else: q_lora, _ = self.wq_a(x_linear) qkv_a = None use_cp = is_cp_active(forward_batch) kv: Optional[torch.Tensor] from sglang.srt.layers.attention.dsv4.unified_kv_gate import ( is_unified_kv_fp8, is_unified_kv_triton, ) unified = is_unified_kv_triton() fp8_2buff = is_unified_kv_fp8() is_decode = forward_batch.forward_mode.is_decode_or_idle() # The kernel is token-indexed (q, kv and positions are all length M), so # a verify batch carrying several draft tokens per request is a shape it # already handles. Only the cache store differs between decode and # verify, and under fp8 that store takes the packed pair instead of bf16. fuse_verify = ( envs.SGLANG_OPT_FUSED_QK_NORM_ROPE_VERIFY.get() and forward_batch.forward_mode.is_target_verify() ) # fp8 verify stores like decode: the kernel writes each token's ring row # (swa_loc) before attention, so no packed pair, kv copy or backend # scatter. Keyed off the caller's buffers the same way fuse_prefill is. fuse_verify_fp8 = ( fuse_verify and unified and fp8_2buff and k_nope_out is not None and k_rope_out is not None ) # Prefill under fp8 goes through the same fused store: the 2-source # kernel reads this chunk as its extend region in the pool's packed form, # and the ring write after attention reuses those same rows, so they are # materialised once here rather than quantized on both sides. Keyed off # the caller's buffers the way q_rope_out keys the packed Q, so the two # cannot disagree about the layout; both halves are required because the # nope one leaves on the kv slot and a missing one would read as "the # fused store did not run". Verify packs the same way but is its own arm # above: it stores before attention, not after. fuse_prefill = ( unified and fp8_2buff and k_nope_out is not None and k_rope_out is not None and not is_decode and not forward_batch.forward_mode.is_target_verify() ) do_fused_qk_norm_rope = ( unified and (is_decode or fuse_verify or fuse_prefill) ) or (not unified and self.use_fused_qk_norm_rope) # The indexer's wq_b reuses the fp8 quant of the normed q_lora made here. q_lora_quant = None if do_fused_qk_norm_rope: if _is_gfx95_supported or _is_gfx1250_supported: q_for_wqb, q_lora = _fused_rmsnorm_fp8_quant( q_lora, self.q_norm.weight, self.q_norm.variance_epsilon, e8m0=self._q_lora_quant_e8m0(with_indexer=True), ) q, _ = self.wq_b(q_for_wqb) q_lora_quant = q_for_wqb else: q_lora, q_for_wqb = self._normalize_q_lora(q_lora) q, _ = self.wq_b(q_for_wqb) kv = ( qkv_a[..., self.q_lora_rank :] if qkv_a is not None else self.wkv(x_linear)[0] ) token_to_kv_pool = get_token_to_kv_pool() swa_rope_cache = None if unified and fuse_verify and not fuse_verify_fp8: # Target-verify runs through the unified_kv decode path. The # backend writes the current chunk's KV into the ring *before* # attention (save_kv_cache=True -> store_swa_into_unified ahead # of runtime.decode), and per-token causal index streams -- built # once per step in the backend metadata -- keep each draft query # attending only to positions up to itself. Causal masking among # the draft tokens comes from those index streams, not from store # timing. So this path skips only the fused kernel's *own* store # and returns kv, letting that existing causally-indexed backend # store run unchanged; we fuse just the norm+RoPE. swa_loc is not # computed -- it only addresses the kernel store this path drops. # # kv is a strided slice of qkv_a and the ring store requires a # contiguous buffer, so materialise it before the kernel norms # it in place. The unfused path pays the same copy inside # _compute_kv_bf16. # # Under fp8, verify takes the decode arm below instead: the ring # rows are per token (swa_loc is built for verify too) and are # written before attention either way, so the kernel stores them # itself, off the strided kv. kv = kv.contiguous() swa_cache, swa_loc = None, None swa_page_size, bf16_store = 1, True elif unified and fuse_prefill: # No pools, so the kernel norms + RoPEs + packs and writes no # ring row. It must not: those rows are this fwd's extend region # and the prefix pool has to stay as attention expects to find # it. The backend stores them after attention from the pair. swa_cache, swa_loc = None, None swa_page_size, bf16_store = 1, False # kv stays the strided slice of qkv_a. Under fp8 the kernel only # reads it -- the packed pair goes to k_nope_out/k_rope_out, it # does not norm in place -- and it takes the row stride as an # argument, so materialising it was a copy on every fp8 layer. elif unified: swa_cache = token_to_kv_pool.get_unified_kv(self.layer_id) # swa_loc is layer-independent; computed once per forward by the # backend and cached on the metadata (read here by every layer). swa_loc = attn_backend.get_unified_swa_loc(forward_batch) swa_page_size, bf16_store = 1, not fp8_2buff if fp8_2buff: swa_rope_cache = token_to_kv_pool.get_unified_kv_rope(self.layer_id) # kv stays the strided slice of qkv_a -- the group-quant # kernel takes the row stride as an argument. else: swa_loc = attn_backend.get_swa_out_cache_loc(forward_batch) swa_cache = token_to_kv_pool.get_swa_raw_buffer(self.layer_id) swa_page_size, bf16_store = ( token_to_kv_pool.swa_page_size, False, ) if use_cp: # Store globally ordered KV after the CP gather. swa_cache, swa_loc = None, None from sglang.kernels.ops.attention.fused_qk_norm_rope_store import ( fused_qk_norm_rope_swa_store, ) q = fused_qk_norm_rope_swa_store( q=q, kv=kv, q_norm_weight=None, kv_norm_weight=self.kv_norm.weight, q_rms_eps=self.eps, kv_rms_eps=self.eps, rope_head_dim=self.qk_rope_head_dim, cos_cache=self.cos_cache, sin_cache=self.sin_cache, positions=positions, swa_cache=swa_cache, swa_loc=swa_loc, swa_page_size=swa_page_size, q_out=q_out, dtype=x.dtype, bf16_store=bf16_store, fp8_2buff=fp8_2buff, swa_rope_cache=swa_rope_cache, k_nope_out=k_nope_out if fuse_prefill else None, k_rope_out=k_rope_out if fuse_prefill else None, q_rope_out=q_rope_out, ) # On the verify path the kernel normed + RoPE'd kv in place and wrote # nothing, so hand it back: the caller feeds it to attention as the # current chunk (attn_k = kv) and save_kv_cache = kv is not None lets # the backend do its normal causally-indexed store into the ring # before the decode kernel runs -- exactly as the unfused path did. if unified and fuse_prefill: # The packed nope half rides out on the kv slot -- attention # takes it as attn_k and save_kv_cache stays on so the backend # does the ring write. Its rope half went to the caller's buffer, # which has no second return slot here. Prefill's write lands # after attention, verify's before it; both read this pair. kv = k_nope_out elif not unified and use_cp: kv = cp_materialize_global_token_order( kv.contiguous(), forward_batch, torch.cuda.current_stream(), ) attn_backend.store_cache( layer_id=self.layer_id, swa_k=kv, forward_batch=forward_batch, ) elif not (unified and fuse_verify and not fuse_verify_fp8): kv = None elif _is_npu: q_lora = self.q_norm(q_lora) q, _ = self.wq_b(q_lora) q = q.view(-1, self.n_local_heads, self.head_dim) q = torch_npu.npu_rms_norm(q, self.q_rms_norm_ones, self.eps)[0] if qkv_a is not None: kv = qkv_a[..., self.q_lora_rank :] else: kv, _ = self.wkv(x) kv = self.kv_norm(kv) cos4, sin4 = self._get_npu_rope_position_cache( forward_batch, positions, q.dtype, inverse=False ) Dsv4NpuRoPE.apply_rotary_mul_inplace( q, kv.unsqueeze(1), cos4, sin4, qk_nope_dim=self.qk_nope_head_dim, ) kv_for_cache = kv if use_cp: kv_for_cache = cp_gather_full_sequence_states( kv.contiguous(), forward_batch, torch.cuda.current_stream(), ) attn_backend.store_cache( layer_id=self.layer_id, swa_k=kv_for_cache, forward_batch=forward_batch, ) kv = None if q_out is not None: q_out.copy_(q) else: q_lora, q_for_wqb = self._normalize_q_lora(q_lora) if _is_hip: q, q_lora, fuse_q_rope = _hip.compute_q_b( self, q_lora, q_for_wqb, positions, q_out, unified, use_cp ) else: q = self._compute_q_b(q_for_wqb, positions, q_out) fuse_q_rope = False if unified: # unified_kv prefill: keep bf16 kv; the backend writes # the ring AFTER attention (2-source path). kv = self._compute_kv_bf16(x_linear, positions, qkv_a=qkv_a) if use_cp: # 2-source attention reads the whole logical chunk. kv = cp_materialize_global_token_order( kv.contiguous(), forward_batch, torch.cuda.current_stream(), ) elif use_cp and not self.is_dsv41: kv = self._compute_kv_bf16(x_linear, positions, qkv_a=qkv_a) kv = cp_materialize_global_token_order( kv.contiguous(), forward_batch, torch.cuda.current_stream(), ) attn_backend.store_cache( layer_id=self.layer_id, swa_k=kv, forward_batch=forward_batch, ) elif use_cp: # every rank writes the whole chunk's window KV with the fused fp32 store if qkv_a is not None: kv = qkv_a[..., self.q_lora_rank :] else: kv, _ = self.wkv(x_linear) kv = cp_materialize_global_token_order( kv.contiguous(), forward_batch, torch.cuda.current_stream(), ) tail = attn_backend.forward_metadata.late_layer_tail global_positions = ( tail.pos_global if tail is not None else forward_batch.positions[: kv.shape[0]] ) get_token_to_kv_pool().set_swa_key_buffer_radix_fused_norm_rope( layer_id=self.layer_id, swa_loc=attn_backend.get_swa_out_cache_loc(forward_batch), kv=kv, kv_weight=self.kv_norm.weight.data, eps=self.eps, freqs_cis=self.freqs_cis, positions=global_positions, ) kv = None elif in_replay_tail_graph(): assert not fuse_q_rope bcg_tail_kv_store(self, x_linear, positions, qkv_a) kv = None else: self._compute_kv_to_cache( x_linear, positions, forward_batch, attn_backend, qkv_a=qkv_a, q_rope=q if fuse_q_rope else None, ) kv = None del qkv_a if self.compress_ratio in (1, 2) and ( self.compressor is not None or self.indexer is not None ): if ( forward_batch.forward_mode.is_extend() and is_in_breakable_cuda_graph() and ( not getattr(attn_backend, "low_ratio_prefill_graph", False) or in_replay_tail_graph() ) ): bcg_deepseek_v4_low_ratio_sources(self, x, q_lora, positions) else: attn_backend.forward_low_ratio_sources( layer=self, x=x, q_lora=q_lora, positions=positions, forward_batch=forward_batch, ) else: use_npu_cp_full_metadata = use_cp and _is_npu if self.indexer is not None: if use_npu_cp_full_metadata: with attn_backend.use_dsv4_cp_full_metadata(forward_batch): attn_backend.forward_indexer_compressor( x, forward_batch, self.indexer.layer_id, self.indexer.compressor, ) self.indexer( x=x, q_lora=q_lora, forward_batch=forward_batch, attn_backend=attn_backend, skip_compressor=True, ) else: self.indexer( x=x, q_lora=q_lora, forward_batch=forward_batch, attn_backend=attn_backend, q_lora_quant=q_lora_quant, ) if self.compressor is not None: if use_npu_cp_full_metadata: with attn_backend.use_dsv4_cp_full_metadata(forward_batch): attn_backend.forward_core_compressor( x, forward_batch, self.layer_id, self.compressor, ) else: attn_backend.forward_core_compressor( x, forward_batch, self.layer_id, self.compressor, ) return q, kv def forward( self, x: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, x_quant: Optional[torch.Tensor] = None, defer_all_reduce: bool = False, ) -> Union[torch.Tensor, mhc.AttnOutput]: if not get_attn_tp_context().input_scattered and x.shape[0] == 0: return x attn_backend = get_attn_backend() if TYPE_CHECKING: assert isinstance( attn_backend, (DeepseekV4AttnBackend, DeepseekV4HipRadixBackend), ) enable_multi_stream = ( envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.get() and self.alt_streams is not None and get_is_capture_mode() and ( is_in_breakable_cuda_graph() or x.shape[0] <= self._multi_stream_bs_limit ) and not is_cp_active(forward_batch) and not (_is_hip and self.compressor is None) and self.compress_ratio not in (1, 2) ) or ( _is_npu and envs.SGLANG_NPU_USE_MULTI_STREAM.get() and self.alt_streams is not None and x.shape[0] <= self._multi_stream_bs_limit and not forward_batch.forward_mode.is_extend_or_draft_extend_or_mixed() ) low_ratio_multi_stream = ( ((_is_cuda and get_platform().is_blackwell) or _is_gfx95_supported) and self.compress_ratio in (1, 2) and self.alt_streams is not None and ( forward_batch.forward_mode.is_decode() or ( forward_batch.forward_mode.is_target_verify() # Other MXFP8 backends may share mutable GEMM workspace. and ( _is_gfx95_supported or getattr(self.wq_b.quant_method, "mxfp8_dense_backend", None) == Mxfp8DenseGemmBackend.FLASHINFER_CUTEDSL ) ) ) ) from sglang.srt.layers.attention.dsv4.unified_kv_gate import ( is_unified_kv_fp8, is_unified_kv_triton, ) unified = is_unified_kv_triton() unified_fp8_verify = ( unified and is_unified_kv_fp8() and forward_batch.forward_mode.is_target_verify() ) # The v4 nm asm reader takes Q in the pool's own packed form, so fp8 # decode wants a contiguous fp8 buffer of exactly the local heads -- # q_padded below is a FlashMLA layout and buys nothing here. Verify runs # that same reader over the ring, so it takes the same Q. unified_fp8_decode = ( unified and is_unified_kv_fp8() and (forward_batch.forward_mode.is_decode_or_idle() or unified_fp8_verify) ) # The 2-source prefill kernel wants the same packed Q plus this chunk's # K in the pool's layout. Verify is not prefill here even though it takes # the same branch below -- it reads rows the ring already holds, so it # goes with decode above. Multi-stream picks a different prepare that has # no unified arm at all, so it keeps the bf16 buffers it always had. unified_fp8_prefill = ( unified and is_unified_kv_fp8() and not enable_multi_stream and not forward_batch.forward_mode.is_decode_or_idle() and not forward_batch.forward_mode.is_target_verify() ) if unified_fp8_verify and not envs.SGLANG_OPT_FUSED_QK_NORM_ROPE_VERIFY.get(): # The packed pair is produced by the fused norm+RoPE store; with that # off the unfused arm hands the backend bf16 kv and the ring scatter # dies on a dtype assert that says nothing about MTP. raise NotImplementedError( "fp8 two-pool unified_kv needs the fused verify store for " "speculative decoding: set " "SGLANG_OPT_FUSED_QK_NORM_ROPE_VERIFY=1, or run with " "SGLANG_DSV4_UNIFIED_KV_FP8=0." ) tp_slice, q_padded, q_out, q_rope = slice(None), None, None, None k_nope, k_rope = None, None if self.use_mega_attention: kernel_num_heads = 64 if self.n_local_heads <= 64 else 128 elif ( self.is_dsv41 and not self.q_head_norm and get_token_to_kv_pool().uniform_fp8 and self.n_local_heads in (8, 16, 32, 64, 128) ): kernel_num_heads = self.n_local_heads q_out = torch.empty( (x.shape[0], self.n_local_heads, self.head_dim), dtype=torch.float8_e4m3fn, device=x.device, ) elif unified_fp8_decode or unified_fp8_prefill: # width and dtype come off the pools themselves; the kernel reads Q # with the kv row stride, so the two must not drift kv_pool = get_token_to_kv_pool() nope_pool = kv_pool.get_unified_kv(self.layer_id) rope_pool = kv_pool.get_unified_kv_rope(self.layer_id) q_out = nope_pool.new_empty( (x.shape[0], self.n_local_heads, nope_pool.shape[-1]) ) q_rope = rope_pool.new_empty( (x.shape[0], self.n_local_heads, rope_pool.shape[-1]) ) if unified_fp8_prefill or unified_fp8_verify: k_nope = nope_pool.new_empty((x.shape[0], nope_pool.shape[-1])) k_rope = rope_pool.new_empty((x.shape[0], rope_pool.shape[-1])) kernel_num_heads = self.n_local_heads else: kernel_num_heads = ( self.n_local_heads if _is_hip and not unified and _hip.skip_head_pad(self) else self._kernel_num_heads(x.shape[0]) ) if kernel_num_heads != self.n_local_heads: # Backends without an exact-head specialization retain the existing # padded shape. attn_sink is sliced to this rank and padded to match. if self.is_dsv41: # V4.1 kernels read all padded heads, so the padding must be zero. q_padded = self._zero_padded_q(x, kernel_num_heads, attn_backend) elif _is_gfx942_supported: # Uninitialized padded TP heads inject NaN into attention on gfx942 # (fnuz), so zero-init there; other archs tolerate new_empty and skip # the per-forward memset. q_padded = x.new_zeros(x.shape[0], kernel_num_heads, self.head_dim) else: q_padded = x.new_empty(x.shape[0], kernel_num_heads, self.head_dim) tp_slice = slice(0, self.n_local_heads) q_out = q_padded[:, tp_slice, :] attn_sink = self._local_attn_sink(kernel_num_heads) if enable_multi_stream: # Multi-stream path always fuses cache write into the K kernel, # so the bf16 KV intermediate is gone. if _is_hip: q = self._forward_prepare_multi_stream_hip( x, positions, forward_batch, attn_backend, q_out, x_quant=x_quant, ) elif _is_npu: q = self._forward_prepare_multi_stream_npu( x, positions, forward_batch, attn_backend, q_out, x_quant=x_quant, ) else: q = self._forward_prepare_multi_stream( x, positions, forward_batch, attn_backend, q_out, x_quant=x_quant, ) kv = None elif low_ratio_multi_stream: q = self._forward_prepare_low_ratio_multi_stream( x, positions, forward_batch, attn_backend, q_out, x_quant=x_quant, ) kv = None else: q, kv = self._forward_prepare( x, positions, forward_batch, attn_backend, q_out, x_quant=x_quant, q_rope_out=q_rope, k_nope_out=k_nope, k_rope_out=k_rope, ) if unified_fp8_prefill and is_cp_active(forward_batch): # kv is the packed noPE half the fused prefill store left on the kv slot. kv, k_rope = _materialize_cp_unified_fp8_kv( kv, k_rope, forward_batch, torch.cuda.current_stream() ) if self.use_mega_attention: o = self._forward_mega( q, kernel_num_heads, attn_sink, forward_batch, attn_backend ) return self._project_wo_b(o, defer_all_reduce) # save_kv_cache = kv is not None selects who writes the ring. When kv is # None the store was already fused into _forward_prepare* (decode) or # done inline, so the backend skips its own store_cache; pass `q` as a # sentinel for the `k is v` assert (attention won't read it once # save_kv_cache=False). When kv is not None (target-verify, or DSA-CP), # _forward_prepare* deliberately left the store off and the backend does # its normal causally-indexed store from attn_k = kv. attn_k = kv if kv is not None else q # HIP: the attention kernel may apply the inverse RoPE itself; the else-branch below keeps it inv_rope = ( _hip.attention_inv_rope( self, positions, forward_batch, unified, wo_a_applies_inv_rope=self.wo_a_fp8 and _wo_a_fp8_mxscale_fused_invrope is not None, ) if _is_hip else None ) wo_a_quant_freqs = None if unified: # only the HIP radix backend takes these; passing them always would # leave non-ROCm depending on the **_ in its forward() to drop them, and # no test on that side would notice if the **_ went away rope_kwargs = {} if q_rope is not None: rope_kwargs["q_rope"] = q_rope if k_rope is not None: rope_kwargs["k_rope"] = k_rope if inv_rope is not None: rope_kwargs["inv_rope"] = inv_rope wo_a_quant_freqs = self._wo_a_quant_freqs(forward_batch, q_rope) if wo_a_quant_freqs is not None: rope_kwargs["wo_a_quant_freqs"] = wo_a_quant_freqs o = attn_backend.forward( q=q_out if q_out is not None else q, k=attn_k, v=attn_k, layer=self.attn_mqa, forward_batch=forward_batch, compress_ratio=self.compress_ratio, attn_sink=attn_sink[: self.n_local_heads], save_kv_cache=kv is not None, **rope_kwargs, ) else: attn_q = q_padded if q_padded is not None else q save_kv_cache = False if forward_batch.forward_mode.is_extend() and is_in_breakable_cuda_graph(): o = attn_q.new_empty( (*attn_q.shape[:-1], self.attn_mqa.v_head_dim), # TRT-LLM returns BF16 even with FP8 queries. dtype=x.dtype, ) bcg_deepseek_v4_attention_with_output( attn_q, attn_k, o, self.attn_mqa.layer_id, self.compress_ratio, attn_sink, save_kv_cache, ) else: o = attn_backend.forward( q=attn_q, k=attn_k, v=attn_k, layer=self.attn_mqa, forward_batch=forward_batch, compress_ratio=self.compress_ratio, attn_sink=attn_sink, save_kv_cache=save_kv_cache, # HIP only, for the same reason as rope_kwargs above **({} if inv_rope is None else {"inv_rope": inv_rope}), ) o = o[:, tp_slice, :] if wo_a_quant_freqs is not None: # the decode already returned the wo_a mxfp8 (codes, scales) G = self.n_local_groups o = _wo_a_fp8_mxscale_prequant( *o, self.wo_a.weight.view(G, self.o_lora_rank, -1), self.wo_a.weight_scale_inv.data, ) elif ( self.wo_a_fp8 and _wo_a_fp8_mxscale_fused_invrope is not None and not _is_npu ): # ROCm gfx950 fused path: inverse-RoPE + per-token-group mxfp8 quant # in one aiter kernel on the pre-view [T,H,Dh] output, then the a8w8 # mxscale absorb GEMM. Replaces the standalone inverse RoPE, the # [T,G,D] view, and the quant inside the two-kernel fp8 path below. G = self.n_local_groups cos_c, sin_c = _freqs_cis_to_cos_sin(self.freqs_cis, o.dtype, o.device) o = _wo_a_fp8_mxscale_fused_invrope( o, positions, cos_c, sin_c, G, self.wo_a.weight.view(G, self.o_lora_rank, -1), self.wo_a.weight_scale_inv.data, ) else: fuse_mxfp8_quant = ( self.use_flashinfer_mxfp8_wo_b and not get_forward().sp_active ) fuse_rope_wo_a = ( self.use_fused_wo_a and 0 < o.shape[0] <= _FUSED_WO_A_MAX_TOKENS ) if _is_npu: cos4, sin4 = self._get_npu_rope_position_cache( forward_batch, positions, o.dtype, inverse=True ) Dsv4NpuRoPE.apply_rotary_mul_inplace( o, None, cos4, sin4, qk_nope_dim=self.qk_nope_head_dim, ) elif inv_rope is None and not fuse_rope_wo_a: # The fused path folds this in; it must not run twice. fused_rope_inplace( o[..., -self.qk_rope_head_dim :], None, self.freqs_cis, positions=positions, inverse=True, ) o = o.view(o.shape[0], self.n_local_groups, -1) if fuse_rope_wo_a: wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) out = fused_rope_wo_a_bf16( o, wo_a, torch.view_as_real(self.freqs_cis).flatten(1), positions, out_mxfp8=fuse_mxfp8_quant, ) o = Mxfp8SwizzledInput(*out) if fuse_mxfp8_quant else out[0] elif self.use_npu_arch35_mxfp8_wo_a: o, o_scale = torch_npu.npu_dynamic_mx_quant( o, dst_type=torch.float8_e4m3fn ) o = torch_npu.npu_transpose_quant_batchmatmul( o, self.wo_a.weight, dtype=torch.bfloat16, bias=None, group_sizes=(0, 0, 32), x1_scale=o_scale.view(torch.float8_e8m0fnu), x2_scale=self.wo_a.weight_scale_inv.view(torch.float8_e8m0fnu), perm_x1=(1, 0, 2), perm_x2=(0, 1, 2), perm_y=(1, 0, 2), ) elif self.wo_a_fp8 and _wo_a_fp8_mxscale is not None: # ROCm gfx950: same fp8 absorb GEMM as the DeepGEMM path below, # but through aiter's e8m0 block-scale batched GEMM. The # activation is quantized per token-group inside the helper. T, G, D = o.shape o = _wo_a_fp8_mxscale( o, self.wo_a.weight.view(G, self.o_lora_rank, D), self.wo_a.weight_scale_inv.data, ) elif self.wo_a_fp8: T, G, D = o.shape R = self.o_lora_rank if _FP8_WO_A_UE8M0: # Blackwell (including SM120): UE8M0 scales via the dedicated # JIT kernel. o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o) recipe = (1, 1, 128) else: # sm90 (Hopper): fp32 scales. o_fp8, o_s = sglang_per_token_group_quant_fp8( o.reshape(T * G, D).contiguous(), group_size=128, scale_ue8m0=False, ) o_fp8 = o_fp8.view(T, G, D) o_s = o_s.view(T, G, -1) recipe = (1, 128, 128) o = _fp8_wo_a_einsum( o_fp8, o_s, self.wo_a.weight.view(G, R, D), self.wo_a.weight_scale_inv.data, recipe=recipe, ) else: wo_a_weight = getattr(self.wo_a, "weight", None) if wo_a_weight is not None: if ( _NPU_BF16_WO_A_GEMM and forward_batch.forward_mode.is_decode() and self.n_local_groups == 1 and o.dtype == wo_a_weight.dtype == torch.bfloat16 and wo_a_weight.is_contiguous() ): # One local group needs no grouped contraction; linear # avoids materializing a transpose of the BF16 weight. o = F.linear(o, wo_a_weight) else: wo_a = wo_a_weight.view( self.n_local_groups, self.o_lora_rank, -1 ) o = _apply_wo_a_bf16_matmul( o, wo_a, is_decode=forward_batch.forward_mode.is_decode(), is_target_verify=forward_batch.forward_mode.is_target_verify(), is_prefill=forward_batch.forward_mode.is_extend_without_speculative(), fast_path=self.is_dsv41, fuse_mxfp8_quant=fuse_mxfp8_quant, fp8_grid=_is_hip and _hip.wo_a_emits_fp8_grid(self), emit_fp8=_is_hip and _hip.wo_b_emits_mxfp8(self, o.shape[0]), ) else: o = _apply_gguf_grouped_wo_a( o, self.wo_a.qweight, self.wo_a.qweight_type.weight_type, self.o_lora_rank, ) return self._project_wo_b(o, defer_all_reduce) def _project_wo_b(self, o, defer_all_reduce: bool = False): from sglang.srt.layers.moe.utils import should_skip_mlp_all_reduce defer_all_reduce = defer_all_reduce and ( self.attn_tp_size > 1 and ( _is_cuda or ( self.attn_tp_size == get_parallel().tp_size and self.wo_b.reduce_results and not self.wo_b.use_decode_attn_tp ) ) and not should_skip_mlp_all_reduce() ) o, _ = self.wo_b( o.flatten(1) if isinstance(o, torch.Tensor) else o, skip_all_reduce=defer_all_reduce, ) if defer_all_reduce: return mhc.AttnOutput(o) if self.attn_tp_size > 1 and self.attn_tp_size < get_parallel().tp_size: o = attn_tp_all_reduce(o) return o # ---- TBO op decomposition (prefill two-batch-overlap) ---- def op_attn(self, state): """Run the attention forward as a single TBO op. Consumes the post-input-norm hidden states produced by ``DeepseekV4DecoderLayer.op_mhc_prepare_attn`` and stores the attention output for ``op_mhc_post_attn_pre_mlp``. """ state.hidden_states_after_attn = self.forward( x=state.pop("hidden_states_after_input_norm"), positions=state.positions, forward_batch=state.forward_batch, x_quant=state.pop("attn_x_quant"), defer_all_reduce=_is_cuda, ) class DeepseekV4DecoderLayerBase(nn.Module): def __init__( self, config: DeepSeekV4Config, layer_id: int, quant_config: Optional[QuantizationConfig] = None, moe_quant_config_override: Optional[QuantizationConfig] = None, is_nextn: bool = False, prefix: str = "", alt_streams: Optional[List[torch.cuda.Stream]] = None, compress_ratio_override: Optional[int] = None, engram_layout: Optional[EngramLayout] = None, hc_stats_stream: Optional[torch.cuda.Stream] = None, moe_routed_quant_stream: Optional[torch.cuda.Stream] = None, *, boundary_pre_from_prev: Optional[bool] = None, boundary_mhc_fusions: bool = True, ) -> None: super().__init__() self.hc_stats_stream = hc_stats_stream self.config = config self.hidden_size = config.hidden_size self.layer_id = layer_id self.boundary_pre_from_prev = ( config.hc_pre_from_prev_sublayer and not is_nextn if boundary_pre_from_prev is None else boundary_pre_from_prev ) self.boundary_mhc_fusions = boundary_mhc_fusions self.self_attn = self._build_self_attn( config=config, layer_id=layer_id, quant_config=quant_config, prefix=add_prefix("self_attn", prefix), alt_streams=alt_streams, compress_ratio_override=compress_ratio_override, ) moe_alt_stream = ( alt_streams[0] if ( alt_streams is not None and ( _is_cuda or envs.SGLANG_ROCM_USE_MULTI_STREAM.get() or envs.SGLANG_NPU_USE_MULTI_STREAM.get() ) ) else None ) self.mlp = deepseek_v2.DeepseekV2MoE( config=config, quant_config=moe_quant_config_override or quant_config, prefix=add_prefix("mlp", prefix), layer_id=self.layer_id, alt_stream=moe_alt_stream, routed_quant_stream=moe_routed_quant_stream, is_nextn=is_nextn, is_deepseek_v4=True, vl_correction_bias=config.model_type == "deepseek_v41" and config.vision_n_layers > 0 and not getattr(config, "language_model_only", 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.hc_mult = hc_mult = config.hc_mult self.hc_sinkhorn_iters = config.hc_sinkhorn_iters self.hc_eps = config.hc_eps ( self.hc_attn_fn, self.hc_ffn_fn, self.hc_attn_base, self.hc_ffn_base, self.hc_attn_scale, self.hc_ffn_scale, ) = make_hc_mixing_params(hc_mult, config.hidden_size) self.rms_norm_eps = config.rms_norm_eps self.dsa_enable_prefill_cp = is_dsa_enable_prefill_cp() self.use_fused_mhc_post_pre = ( is_cross_layer_mhc_fusion_enabled() or _is_fused_mhc_post_pre_enabled_xpu() ) # The fused post+pre boundary bakes in the same-sublayer pre-mix. self.hc_pre_from_prev_sublayer = config.hc_pre_from_prev_sublayer if self.hc_pre_from_prev_sublayer: self.use_fused_mhc_post_pre = False if _is_hip: _hip.init_decoder_layer(self, quant_config) self.hc_cfg = mhc.HcConfig( mult=hc_mult, sinkhorn_iters=config.hc_sinkhorn_iters, eps=config.hc_eps, rms_eps=config.rms_norm_eps, hidden=config.hidden_size, pre_from_prev=self.hc_pre_from_prev_sublayer, cp_prefill=self.dsa_enable_prefill_cp, ) self.attn_hc: Optional[mhc.HcSubLayer] = None self.ffn_hc: Optional[mhc.HcSubLayer] = None self._hc_attn_tf32_parts = self._hc_ffn_tf32_parts = None self._hc_attn_bf16_parts = self._hc_ffn_bf16_parts = None self._init_hyper_connections() self.engram = None if engram_layout is not None and layer_id in engram_layout.layer_ids: self.engram = Engram( config, layer_id, engram_layout, quant_config=quant_config, prefix=add_prefix("engram", prefix), ) self._input_layernorm_weight_bf16 = None self._post_attention_layernorm_weight_bf16 = None def _build_self_attn( self, *, config: DeepSeekV4Config, layer_id: int, quant_config: Optional[QuantizationConfig], prefix: str, alt_streams: Optional[List[torch.cuda.Stream]], compress_ratio_override: Optional[int], ) -> MQALayer: return MQALayer( config=config, layer_id=layer_id, quant_config=quant_config, prefix=prefix, alt_streams=alt_streams, compress_ratio_override=compress_ratio_override, ) def _init_hyper_connections(self) -> None: """(Re)bind the two hyper-connections to the current mixing weights.""" if not self.hc_pre_from_prev_sublayer: return self.attn_hc = mhc.HcSubLayer( self.hc_cfg, self.hc_attn_fn, self.hc_attn_scale, self.hc_attn_base, self.input_layernorm, self._hc_attn_tf32_parts, self._hc_attn_bf16_parts, ) self.ffn_hc = mhc.HcSubLayer( self.hc_cfg, self.hc_ffn_fn, self.hc_ffn_scale, self.hc_ffn_base, self.post_attention_layernorm, self._hc_ffn_tf32_parts, self._hc_ffn_bf16_parts, ) def refresh_mhc_norm_weight_cache(self): # Cache bf16 norm weights so the fused path does not allocate/cast per forward. self._input_layernorm_weight_bf16 = ( self.input_layernorm.weight.data.bfloat16().contiguous() ) self._post_attention_layernorm_weight_bf16 = ( self.post_attention_layernorm.weight.data.bfloat16().contiguous() ) from sglang.srt.batch_invariant_ops import is_batch_invariant_mode_enabled # The original FP32 parameters stay intact for small rows and invariant mode. self._hc_attn_tf32_parts = self._hc_ffn_tf32_parts = None self._hc_attn_bf16_parts = self._hc_ffn_bf16_parts = None if ( self.hc_pre_from_prev_sublayer and (get_platform().is_sm100 or get_platform().is_sm90) and self.hc_attn_fn.shape == (24, 20480) and envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get() and getattr(self.config, "model_type", None) == "deepseek_v41" and not is_batch_invariant_mode_enabled() ): from sglang.kernels.ops.layernorm.mhc import split_bf16_hc_weight if get_platform().is_sm90: # Hopper's compensated projection does not require DeepGEMM. self._hc_attn_bf16_parts = split_bf16_hc_weight(self.hc_attn_fn.data) self._hc_ffn_bf16_parts = split_bf16_hc_weight(self.hc_ffn_fn.data) else: from sglang.kernels.ops.layernorm.mhc import split_tf32_hc_weight from sglang.srt.layers.deep_gemm_wrapper.configurer import ( ENABLE_JIT_DEEPGEMM, ) if ENABLE_JIT_DEEPGEMM: import deep_gemm if callable(getattr(deep_gemm, "tf32_hc_prenorm_gemm", None)): self._hc_attn_tf32_parts = split_tf32_hc_weight( self.hc_attn_fn.data ) self._hc_ffn_tf32_parts = split_tf32_hc_weight( self.hc_ffn_fn.data ) self._hc_attn_bf16_parts = split_bf16_hc_weight( self.hc_attn_fn.data ) self._hc_ffn_bf16_parts = split_bf16_hc_weight( self.hc_ffn_fn.data ) if self.hc_pre_from_prev_sublayer: self._init_hyper_connections() # The fuse gates and boundaries snapshot load-time facts; weight updates # re-run this, so drop them and re-resolve on the next forward. self.__dict__.pop("_can_fuse_attn_mhc", None) self.__dict__.pop("_can_fuse_ffn_mhc", None) def _input_norm( self, hidden_states: torch.Tensor, allow_aiter_quant: bool = True, coefficients=None, ) -> Tuple[torch.Tensor, Optional[Tuple]]: """input_layernorm(hidden_states) as (the bf16 norm attention reads, the pre-quantized operand of its dense projections or None); coefficients is a ROCm boundary's pending reduce + sinkhorn.""" if _is_hip: return _hip.input_norm( self, hidden_states, allow_aiter_quant, coefficients, _fused_rmsnorm_fp8_quant, ) return self.input_layernorm(hidden_states), None def hc_pre( self, x: torch.Tensor, hc_fn: torch.Tensor, hc_scale: torch.Tensor, hc_base: torch.Tensor, norm: Optional[nn.Module] = None, forward_batch: Optional[ForwardBatch] = None, ): """If *norm* is given and the TileLang path is active, the returned hidden_states are already post-norm (the norm is fused into the kernel).""" @compile_in_capture_mode def hc_pre_torch_impl(x, hc_fn): x_flat = x.flatten(1).float() rsqrt = torch.rsqrt( x_flat.square().mean(-1, keepdim=True) + self.rms_norm_eps ) mixes = (F.linear(x_flat, hc_fn) * rsqrt).unsqueeze(1) return x_flat, mixes shape, dtype = x.size(), x.dtype if _is_npu: return get_mhc_ops().npu_hc_pre( x, hc_fn, hc_scale, hc_base, hc_mult=self.hc_mult, hc_sinkhorn_iters=self.hc_sinkhorn_iters, rms_norm_eps=self.rms_norm_eps, hc_eps=self.hc_eps, forward_batch=forward_batch, ) if x.shape[0] == 0: y = torch.empty((0, shape[-1]), dtype=dtype, device=x.device) post = torch.empty((0, self.hc_mult), dtype=torch.float32, device=x.device) comb = torch.empty( (0, self.hc_mult, self.hc_mult), dtype=torch.float32, device=x.device ) return y, post, comb, False if _is_xpu: norm_kwargs = {} if norm is not None: norm_kwargs["norm_weight"] = norm.weight.data norm_kwargs["norm_eps"] = norm.variance_epsilon post, comb, y = get_mhc_ops().mhc_pre( residual=x, fn=hc_fn, hc_scale=hc_scale, hc_base=hc_base, rms_eps=self.rms_norm_eps, hc_pre_eps=self.hc_eps, hc_sinkhorn_eps=self.hc_eps, hc_post_mult_value=MHC_POST_MULT_VALUE, sinkhorn_repeat=self.hc_sinkhorn_iters, **norm_kwargs, ) return y, post, comb, norm is not None if envs.SGLANG_OPT_USE_FLASHINFER_MHC.get(): y, post, comb = _flashinfer_hc_pre( x, hc_fn, hc_scale, hc_base, rms_eps=self.rms_norm_eps, hc_eps=self.hc_eps, sinkhorn_iters=self.hc_sinkhorn_iters, ) return y, post, comb, False if envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get(): from sglang.kernels.ops.layernorm.mhc import mhc_pre norm_kwargs = {} if norm is not None: norm_kwargs["norm_weight"] = norm.weight.data norm_kwargs["norm_eps"] = norm.variance_epsilon post, comb, y = mhc_pre( residual=x, fn=hc_fn, hc_scale=hc_scale, hc_base=hc_base, rms_eps=self.rms_norm_eps, hc_pre_eps=self.hc_eps, hc_sinkhorn_eps=self.hc_eps, hc_post_mult_value=MHC_POST_MULT_VALUE, sinkhorn_repeat=self.hc_sinkhorn_iters, **norm_kwargs, ) return y, post.squeeze(-1), comb, norm is not None if _is_hip: from aiter.ops.mhc import mhc_pre post, comb, y = mhc_pre( residual=x, fn=hc_fn, hc_scale=hc_scale, hc_base=hc_base, rms_eps=self.rms_norm_eps, hc_pre_eps=self.hc_eps, hc_sinkhorn_eps=self.hc_eps, hc_post_mult_value=MHC_POST_MULT_VALUE, sinkhorn_repeat=self.hc_sinkhorn_iters, ) return y, post.squeeze(-1), comb, False # The deepgemm tf32 gemm wins at large M (prefill) but its fixed # dispatch cost dominates at small M (decode): dispatch by token count. if ( envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get() and x.shape[0] >= _HC_PRENORM_DEEPGEMM_MIN_TOKENS ): from sglang.srt.layers.deep_gemm_wrapper.entrypoint import ( tf32_hc_prenorm_gemm, ) x_flat = x.flatten(1).bfloat16() m, k = x_flat.shape mix_hc = hc_fn.size(0) d_out = torch.empty((m, mix_hc), dtype=torch.float, device=x.device) s_out = torch.empty((m,), dtype=torch.float, device=x.device) tf32_hc_prenorm_gemm( x_flat, hc_fn.float().contiguous(), d_out, s_out, num_splits=None ) rsqrt = torch.rsqrt(s_out / k + self.rms_norm_eps) mixes = (d_out * rsqrt.unsqueeze(1)).unsqueeze(1) else: x_flat, mixes = hc_pre_torch_impl(x, hc_fn) pre, post, comb = get_mhc_ops().hc_split_sinkhorn( mixes, hc_scale, hc_base, self.hc_mult, self.hc_sinkhorn_iters, self.hc_eps, ) from sglang.kernels.ops.layernorm.mhc import mhc_combine # y is the post-norm activation fed into the MoE. Allocate it in the # symmetric memory pool so the downstream all-reduce uses the low-latency # NCCL symmetric path: the Triton inplace MoE runner writes the expert # output back into this buffer, so a symmetric input yields a symmetric # all-reduce input. Gated by is_allocation_symmetric() (mirrors the # TileLang path in _mhc_pre_impl / mhc_fused_post_pre). with use_symmetric_memory( get_parallel().tp_group, disabled=not is_allocation_symmetric() ): y = mhc_combine(x_flat, pre.squeeze(1), self.hc_mult, dtype) return y, post.squeeze(1), comb.squeeze(1), False def hc_post( self, x: torch.Tensor, residual: torch.Tensor, post: torch.Tensor, comb: torch.Tensor, ) -> torch.Tensor: return mhc.post(self.hc_cfg, x, residual, post, comb) @functools.cached_property def _can_fuse_ffn_mhc(self) -> bool: """The MoE counterpart of `_can_fuse_attn_mhc`.""" return ( mhc.fused_mhc_tp_ok(self.mlp.tp_size) and get_moe_a2a_backend().is_none() and not self.mlp._shared_expert_tp1 and not should_skip_post_experts_all_reduce(is_tp_path=True) and mhc.can_fuse_post(self.hc_cfg) ) def _gather_late_layer_ids( input_ids: torch.Tensor, counts: List[int], forward_batch: ForwardBatch ) -> torch.Tensor: # Re-gather the compact tail IDs in the late-layer MoE layout. gathered = input_ids.new_empty((sum(counts), 1)) dp_gather_replicate(gathered, input_ids[:, None].clone(), forward_batch) return gathered.squeeze(-1) def _scatter_tail_rows( tail: LateLayerTail, rows: torch.Tensor, num_tokens: int ) -> torch.Tensor: # Rows outside the tail are never read (see _check_late_layer_tail_readers). if tail.scatter_indices is not None: # Prefill CUDA graph: fixed-capacity tail block; padding rows land on the # dummy row past the extend and are dropped by the slice. full = rows.new_zeros((num_tokens + 1, rows.shape[1])) full.index_copy_(0, tail.scatter_indices, rows) return full[:num_tokens] full = rows.new_empty((num_tokens, rows.shape[1])) if tail.contiguous_start is not None: full[tail.contiguous_start :].copy_(rows) else: full[tail.token_indices] = rows[: tail.token_indices.shape[0]] return full class DeepseekV4CudaDecoderLayer(DeepseekV4DecoderLayerBase): """CUDA execution with declared residual reads and writes.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.self_attn.wo_b.reduce_results = False self.mlp.reduce_results = False declarations = hc_boundary.stages(self.config, self.layer_id, layer=self) self.attn_boundary, self.ffn_boundary = append_stages( (declarations[0], self.input_layernorm), (declarations[1], self.post_attention_layernorm), ) def _run_boundary_attention( self, hidden_states, positions, forward_batch, quantized ): return self.self_attn( x=hidden_states, positions=positions, forward_batch=forward_batch, x_quant=quantized, defer_all_reduce=True, ) def _bind_attention_transport( self, hidden_states, batch, call, rows, overlapped_rows ): return hc_boundary.AttentionTransport(self, hidden_states, batch, call, rows) def _bind_moe_transport(self, hidden_states, batch, call, rows, overlapped_rows): if call.tbo: if overlapped_rows is None: raise NotImplementedError( "this operand transport has no overlapped path" ) return OverlappedMoeTransport( self.mlp, hidden_states, batch, call.tbo_subbatch_index, overlapped_rows ) return hc_boundary.MoeTransport(self, hidden_states, batch, call, rows) def forward( self, positions, hidden_states, input_ids, forward_batch, input_ids_global, *, allow_cross_layer_fusion=True, ): call = hc_boundary.LayerCall( input_ids, input_ids_global, allow_cross_layer_fusion ) hidden_states = self.attn_boundary.prepare( hidden_states, forward_batch, read_context=call ) quantized = residual_batch.written_residual(forward_batch).quantized with self.self_attn.maybe_use_decode_attn_tp(forward_batch): hidden_states = self._run_boundary_attention( hidden_states, positions, forward_batch, quantized ) hidden_states = self.attn_boundary.finish(hidden_states, forward_batch) hidden_states = self.ffn_boundary.prepare( hidden_states, forward_batch, read_context=call ) transport = self.ffn_boundary.transport_context(forward_batch) with self.ffn_boundary.exit(forward_batch) as output: hidden_states = self.mlp( hidden_states, forward_batch, input_ids=transport.input_ids, input_ids_global=transport.input_ids_global, skip_shared_experts=transport.do_shared_local, return_moe_output=transport.return_moe_output, leave_output_sum=True, ) return output.finish(hidden_states) @functools.cached_property def _can_fuse_attn_mhc(self) -> bool: """The static half of the attention fuse gate (dsv4.1 only); load-time facts, dropped by `refresh_mhc_norm_weight_cache`. Row capacity is the dynamic half, checked per forward via `can_fuse_all_reduce`.""" boundary = mhc.make_boundary( self.post_attention_layernorm, accepts_mxfp8=False, hc=self.ffn_hc ) return ( get_parallel().tp_size == self.self_attn.attn_tp_size and mhc.fused_mhc_tp_ok(self.self_attn.attn_tp_size) and boundary.norm_fusable and mhc.can_fuse_post(self.hc_cfg) ) def op_mhc_prepare_attn( self, state, positions, hidden_states, forward_batch, residual=None, tbo_subbatch_index=None, **kwargs, ): residual_batch.start(forward_batch) call = hc_boundary.LayerCall( forward_batch.input_ids, getattr(forward_batch, "_tbo_global_input_ids", forward_batch.input_ids), allow_cross_layer_fusion=False, tbo=True, tbo_subbatch_index=tbo_subbatch_index, ) hidden_states = self.attn_boundary.prepare( hidden_states, forward_batch, read_context=call ) state.hidden_states_after_input_norm = hidden_states state.attn_x_quant = residual_batch.written_residual(forward_batch).quantized if get_moe_a2a_backend().is_mori(): state.num_tokens = hidden_states.shape[0] state.update( dict( forward_batch=forward_batch, positions=positions, tbo_subbatch_index=tbo_subbatch_index, ) ) def op_mhc_post_attn_pre_mlp(self, state): batch = state.forward_batch hidden_states = self.attn_boundary.finish( state.pop("hidden_states_after_attn"), batch ) call = hc_boundary.LayerCall( batch.input_ids, getattr(batch, "_tbo_global_input_ids", batch.input_ids), allow_cross_layer_fusion=False, tbo=True, tbo_subbatch_index=state.tbo_subbatch_index, ) state.hidden_states_mlp_input = self.ffn_boundary.prepare( hidden_states, batch, read_context=call ) def op_mhc_postprocess(self, state): batch = state.forward_batch hidden_states = self.ffn_boundary.finish( state.pop("hidden_states_mlp_output"), batch ) hidden_states = residual_batch.read_output( hidden_states, batch, hc_boundary.FinalRead(), None ) output = dict( positions=state.positions, hidden_states=hidden_states, residual=None, forward_batch=batch, tbo_subbatch_index=state.tbo_subbatch_index, ) state.clear(expect_keys={"positions", "forward_batch", "tbo_subbatch_index"}) return output def op_gather_a(self, state): state.pop("hidden_states_mlp_input") self.ffn_boundary.transport_context(state.forward_batch).issue_gather() def op_gather_b(self, state): self.ffn_boundary.transport_context(state.forward_batch).wait_gather() def op_moe(self, state): transport = self.ffn_boundary.transport_context(state.forward_batch) state.global_expert_out = _tbo_run_moe( self, transport.global_hidden, state.forward_batch, transport.do_shared_local, ) def op_combine_a(self, state): self.ffn_boundary.transport_context(state.forward_batch).issue_combine( state.pop("global_expert_out") ) def op_combine_b(self, state): state.hidden_states_mlp_output = self.ffn_boundary.transport_context( state.forward_batch ).wait_combine() class DeepseekV4PlatformDecoderLayer(NonCudaDecoderMixin, DeepseekV4DecoderLayerBase): """Non-CUDA execution with platform mHC state and fusion.""" DeepseekV4DecoderLayer = ( DeepseekV4CudaDecoderLayer if _is_cuda else DeepseekV4PlatformDecoderLayer ) class DeepseekV4Model(nn.Module): fall_back_to_pt_during_load = False # Class-level default for instances built without running __init__. decoder_replay_tail: Optional[DecoderReplayTail] = None def __init__( self, config: DeepSeekV4Config, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ) -> None: super().__init__() self.config = config self.pp_group = get_parallel().pp_group self.hidden_size = config.hidden_size if self.pp_group.is_first_rank: embedding_quant_config = ( quant_config if quant_config is not None and quant_config.get_name() == "expert_pack" else None ) self.embed_tokens = VocabParallelEmbedding( config.vocab_size, config.hidden_size, enable_tp=not is_dp_attention_enabled(), quant_config=embedding_quant_config, prefix=add_prefix("embed_tokens", prefix), ) else: self.embed_tokens = PPMissingLayer() self.rms_norm_eps = config.rms_norm_eps use_stream_pool = ( _is_cuda or ( _is_hip and ( envs.SGLANG_ROCM_USE_MULTI_STREAM.get() or envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.get() ) ) or (_is_npu and envs.SGLANG_NPU_USE_MULTI_STREAM.get()) ) device_module = torch.get_device_module() num_alt_streams = 5 if (_is_cuda or _is_npu) else 2 self.alt_streams = ( [device_module.Stream() for _ in range(num_alt_streams)] if use_stream_pool else None ) # Routed-MoE input pre-quant, separate from the attention/indexer and # shared-expert streams. self.moe_routed_quant_stream = ( device_module.Stream() if _is_cuda and config.hc_pre_from_prev_sublayer and envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.get() else None ) # One shared stream for all layers; every sublayer joins it before # reusing its residual. self.hc_stats_stream = ( device_module.Stream() if _is_cuda and (get_platform().is_blackwell or get_platform().is_sm90) and envs.SGLANG_OPT_USE_MULTI_STREAM_OVERLAP.get() and config.hc_pre_from_prev_sublayer else None ) self.engram_layout = EngramLayout.from_config(config) self.layers, self.start_layer, self.end_layer = make_pp_layers( config.num_hidden_layers, lambda idx, prefix: DeepseekV4DecoderLayer( config=config, layer_id=idx, quant_config=quant_config, prefix=prefix, alt_streams=self.alt_streams, engram_layout=self.engram_layout, hc_stats_stream=self.hc_stats_stream, moe_routed_quant_stream=self.moe_routed_quant_stream, ), prefix=add_prefix("layers", prefix), final_read=hc_boundary.FinalRead() if _is_cuda else None, stage_facts=functools.partial(hc_boundary.stages, config) if _is_cuda else None, ) if self.pp_group.is_last_rank: self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) else: self.norm = PPMissingLayer() self.gemm_output_zero_allocator_size = 0 self.hc_eps = config.hc_eps self.hc_mult = hc_mult = config.hc_mult self.norm_eps = config.rms_norm_eps self.hc_pre_from_prev_sublayer = config.hc_pre_from_prev_sublayer self.hc_head_fn = self.hc_head_base = self.hc_head_scale = None if self.pp_group.is_last_rank and not self.hc_pre_from_prev_sublayer: ( self.hc_head_fn, self.hc_head_base, self.hc_head_scale, ) = make_hc_head_params(hc_mult, config.hidden_size) self.engram_hasher = None if self.engram_layout is not None: self.engram_hasher = EngramHasher.from_config( config, self.engram_layout, image_token_id=( config.image_token_id if config.model_type == "deepseek_v41" and config.vision_n_layers > 0 else None ), ) if not _is_cuda: self.use_fused_mhc_post_pre = ( is_cross_layer_mhc_fusion_enabled() or _is_fused_mhc_post_pre_enabled_xpu() ) self.dspark_layers_to_capture: Optional[List[int]] = None # Decoder SWA bounded replay: layers from late_layer_start on run over each # request's last SWA_WINDOW extend tokens only. self.late_layer_start: Optional[int] = None # The tail starts at the last kv_source layer, whose compressed KV is first # written for every row (DeepseekV4AttnBackend.write_tail_source_kv). self.tail_writes_source_kv = False if get_exec().features.enable_decoder_swa_bounded_replay: assert config.kv_source_layer_ids, ( "decoder SWA bounded replay needs kv_source_layer_ids" ) last_source = max(config.kv_source_layer_ids) self.tail_writes_source_kv = True self.late_layer_start = ( last_source if self.tail_writes_source_kv else last_source + 1 ) if self.tail_writes_source_kv: assert self.layers[last_source].engram is None, ( "the last kv_source layer must not carry an Engram" ) # The tail layers read the top-k and candidates the source layer # publishes inside the tail; no earlier layer carries them over. assert ( last_source in config.index_source_layer_ids and config.candidate_source_layer_id in (-1, last_source) ), ( "the last kv_source layer must also be the index and candidate source" ) late_ratios = set( config.compress_ratios[last_source + 1 : config.num_hidden_layers] ) assert late_ratios <= { 0, 1, }, f"late layers must not compress on their own, got ratios {late_ratios}" assert getattr(config, "num_hash_layers", 0) <= self.late_layer_start, ( "decoder SWA bounded replay needs hash routing before the late layers" ) # Only vision routing reads token IDs in the late layers. self.late_layer_vl_routing = ( config.model_type == "deepseek_v41" and config.vision_n_layers > 0 and not getattr(config, "language_model_only", False) ) self.decoder_replay_tail = DecoderReplayTail.maybe_create(self) def get_input_embeddings(self) -> nn.Module: return self.embed_tokens def hc_head( self, x: torch.Tensor, hc_fn: torch.Tensor, hc_scale: torch.Tensor, hc_base: torch.Tensor, ): if x.numel() > 0: if _is_xpu: return get_mhc_ops().fused_hc_head( x.contiguous(), hc_fn, hc_scale, hc_base, norm_eps=self.norm_eps, hc_eps=self.hc_eps, ) from sglang.kernels.ops.layernorm.mhc_head import fused_hc_head return fused_hc_head( x.contiguous(), hc_fn, hc_scale, hc_base, norm_eps=self.norm_eps, hc_eps=self.hc_eps, ) return hc_head_torch( x, hc_fn, hc_scale, hc_base, norm_eps=self.norm_eps, hc_eps=self.hc_eps, ) def _check_late_layer_tail_readers( self, forward_batch: ForwardBatch, tail: LateLayerTail ) -> None: # Rows outside the tail are never computed from late_layer_start on; # late_layer_whole_extend gives the requests that read them a whole tail. extend_lens = forward_batch.extend_seq_lens_cpu if ( forward_batch.capture_hidden_mode == CaptureHiddenMode.FULL and self.dspark_layers_to_capture is None and list(tail.extend_seq_lens_cpu) != list(extend_lens) ): raise ValueError( "decoder SWA bounded replay tail misses captured hidden-state rows" ) if forward_batch.return_logprob and any( start < n - t for start, n, t in zip( forward_batch.extend_logprob_start_lens_cpu, extend_lens, tail.extend_seq_lens_cpu, ) ): raise ValueError( "decoder SWA bounded replay tail misses prompt-logprob rows" ) def _forward_layers_hc_pre_from_prev( self, positions: torch.Tensor, hidden_states: torch.Tensor, forward_batch: ForwardBatch, input_ids: torch.Tensor, input_ids_global: torch.Tensor, capture_dspark: bool, dspark_aux_hidden_states: List[torch.Tensor], ) -> Tuple[torch.Tensor, torch.Tensor, Optional[LateLayerTail]]: assert self.pp_group.world_size == 1, "pre-mix hand-off across PP is not wired" hash_ids = None cp_extend = ( is_cp_active(forward_batch) and forward_batch.forward_mode.is_extend() ) if self.engram_hasher is not None: if cp_extend: # n-gram hashing needs each token's predecessors: hash the whole prompt total = int(forward_batch.attn_cp_metadata.total_seq_lens) hash_ids = self.engram_hasher( forward_batch.input_ids[:total], forward_batch ) parallel = get_parallel() hash_ids = hash_ids[parallel.attn_cp_rank :: parallel.attn_cp_size] pad_rows = hidden_states.shape[0] - hash_ids.shape[0] if pad_rows > 0: hash_ids = torch.cat( [hash_ids, hash_ids.new_zeros(pad_rows, *hash_ids.shape[1:])] ) elif ( forward_batch.forward_mode.is_extend() and is_in_breakable_cuda_graph() ): hash_ids = bcg_deepseek_v4_engram_hash_ids( self.engram_hasher, input_ids ) else: hash_ids = self.engram_hasher(input_ids, forward_batch) tail = None if ( self.late_layer_start is not None and forward_batch.forward_mode.is_extend_without_speculative() ): attn_backend = get_attn_backend() tail = attn_backend.tail_forward_metadata.late_layer_tail self._check_late_layer_tail_readers(forward_batch, tail) tail_in_graph = ( tail is not None and tail.scatter_indices is not None and is_in_breakable_cuda_graph() ) late_dp_counts = None saved_dp = None if ( self.late_layer_start is not None and get_parallel().attn_dp_size > 1 # The prefill graph's capture batch leaves is_extend_in_batch unset. and (forward_batch.is_extend_in_batch or tail_in_graph or _is_hip) ): late_dp_counts = late_layer_dp_counts( forward_batch, ( tail.token_indices.shape[0] if tail is not None else hidden_states.shape[0] ), in_graph=tail_in_graph, ) use_tail_graph, tail_graph_dp_rows = False, None if self.decoder_replay_tail is not None: use_tail_graph, tail_graph_dp_rows = self.decoder_replay_tail.plan( tail, forward_batch=forward_batch, num_tokens=input_ids.shape[0], tail_in_graph=tail_in_graph, late_dp_counts=late_dp_counts, ) tail_sentinel = positions.new_zeros((1,)) if tail_in_graph else None saved_full = None # The HIP defer_post gate excludes residual readers. CUDA materializes # its producer update explicitly before these readers below. # mHC assumes full token rows per rank; LayerNorm SP needs its own path. assert not get_forward().sp_active state = mhc.HcState(hidden_states) if _is_cuda: residual_batch.start(forward_batch) hidden_states = residual_batch.set_written(state, forward_batch) for i in range(self.start_layer, self.end_layer): if _is_cuda: layer = self.layers[i] if ( ( (tail is not None or use_tail_graph) and i == self.late_layer_start ) or layer.engram is not None or (capture_dspark and i in self.dspark_layers_to_capture) ): call = hc_boundary.LayerCall( input_ids, input_ids_global, ) next_boundary = ( hc_boundary.HcRead(layer, True) .bind(forward_batch, call) .next_boundary ) hidden_states = state = hc_boundary.materialize( hidden_states, forward_batch, next=next_boundary ) if tail is not None and i == self.late_layer_start: if self.tail_writes_source_kv: source = self.layers[i] if _is_hip and source.hc_boundary_fused: # The previous FFN hc_post has not run yet, so run it here too. source_x = fused_boundary_attn_input(source, state) else: source_x = mhc.combine(source.attn_hc, state) attn_backend.write_tail_source_kv( layer=source.self_attn, x=source_x, positions=positions, forward_batch=forward_batch, ) # Decode reaches back at most SWA_WINDOW positions. if tail_in_graph: # Eager break: re-run at replay so the backend swaps onto the # live batch's tail metadata; the gathers below are captured # against the persistent index buffers. tail_sentinel = bcg_deepseek_v4_enter_late_layer_tail(tail_sentinel) saved_full = attn_backend.prefill_graph_late_layer_saved if ( self.decoder_replay_tail is not None and self.decoder_replay_tail.breaks_out_of_prefill_graph( tail, num_tokens=input_ids.shape[0] ) ): residual, pre, aux = ( self.decoder_replay_tail.run_in_prefill_graph( state=state, positions=positions, input_ids=input_ids, input_ids_global=input_ids_global, capture_dspark=capture_dspark, block_rows=tail.token_indices.shape[0], source_kv_layer_id=( self.late_layer_start if self.tail_writes_source_kv else None ), ) ) dspark_aux_hidden_states.extend(aux) residual_batch.clear(forward_batch) bcg_deepseek_v4_exit_late_layer_tail(tail_sentinel) return residual, pre, tail else: saved_full = attn_backend.enter_late_layer_tail(forward_batch) state = state.take_rows(tail.rows) if _is_cuda: hidden_states = residual_batch.set_written(state, forward_batch) input_ids = tail.rows(input_ids) if not ( get_parallel().attn_dp_size > 1 and get_moe_a2a_backend().is_none() ): input_ids_global = tail.rows(input_ids_global) positions = tail.positions if hash_ids is not None: hash_ids = tail.rows(hash_ids) if use_tail_graph: residual, pre, aux = self.decoder_replay_tail.run( state=state, positions=positions, input_ids=input_ids, input_ids_global=input_ids_global, forward_batch=forward_batch, capture_dspark=capture_dspark, dp_rows=tail_graph_dp_rows, ) dspark_aux_hidden_states.extend(aux) residual_batch.clear(forward_batch) attn_backend.exit_late_layer_tail(saved_full, forward_batch) return residual, pre, tail if use_tail_graph and tail is None and i == self.late_layer_start: residual, pre, aux = self.decoder_replay_tail.run_idle( state=state, positions=positions, input_ids=input_ids, input_ids_global=input_ids_global, forward_batch=forward_batch, capture_dspark=capture_dspark, dp_rows=tail_graph_dp_rows, ) dspark_aux_hidden_states.extend(aux) residual_batch.clear(forward_batch) return residual, pre, None if late_dp_counts is not None and i == self.late_layer_start: saved_dp = enter_late_layer_dp(forward_batch, late_dp_counts) if get_moe_a2a_backend().is_none(): # Full-prefill IDs are not indexed by this rank's tail rows. input_ids_global = ( _gather_late_layer_ids(input_ids, late_dp_counts, forward_batch) if self.late_layer_vl_routing else None ) engram = self.layers[i].engram if engram is not None: before_engram = state.residual hidden_states = engram( state.residual, hash_ids[:, engram.layer_hash_index], forward_batch, cp_all_tokens=cp_extend, ) if ( self.config.model_type == "deepseek_v41" and self.config.vision_n_layers > 0 ): hidden_states = torch.where( (input_ids == self.config.image_token_id)[:, None, None], before_engram, hidden_states, ) state = state.with_residual(hidden_states) if _is_cuda: hidden_states = residual_batch.set_written(state, forward_batch) if capture_dspark and i in self.dspark_layers_to_capture: # The draft head reads the attention input of its target layers. aux = state.residual if tail is not None and i < self.late_layer_start: aux = tail.rows(aux) dspark_aux_hidden_states.append(aux.mean(dim=1)) ctx = ( nullcontext() if check_cuda_graph_backend(Phase.PREFILL, Backend.TC_PIECEWISE) else get_global_expert_distribution_recorder().with_current_layer(i) ) with ctx: if _is_cuda: hidden_states = self.layers[i]( positions=positions, hidden_states=hidden_states, input_ids=input_ids, forward_batch=forward_batch, input_ids_global=input_ids_global, ) else: if _is_hip and self.layers[i].hc_boundary_fused: state = forward_layer_fused_boundary( self, i, state, positions=positions, input_ids=input_ids, forward_batch=forward_batch, input_ids_global=input_ids_global, capture_dspark=capture_dspark, ) continue state = self.layers[i].forward_hc_pre_from_prev( positions=positions, state=state, input_ids=input_ids, forward_batch=forward_batch, input_ids_global=input_ids_global, ) if _is_cuda: state = residual_batch.read_output( hidden_states, forward_batch, hc_boundary.FinalRead(), None ) residual_batch.clear(forward_batch) state = state.materialized(self.layers[self.end_layer - 1].hc_cfg) if saved_dp is not None: exit_late_layer_dp(forward_batch, saved_dp) if saved_full is not None: if tail_in_graph: tail_sentinel = bcg_deepseek_v4_exit_late_layer_tail(tail_sentinel) else: attn_backend.exit_late_layer_tail(saved_full, forward_batch) return state.residual, state.pre, tail return state.residual, state.pre, None def _can_run_tbo(self, forward_batch: ForwardBatch) -> bool: """DSV4 prefill-only two-batch-overlap gate. TBO batch prep (tbo_split_seq_index / tbo_children) is populated model-agnostically when --enable-two-batch-overlap is set and the DP-attention preparer allows it (mori `normal` mode permits prefill TBO). We additionally restrict to prefill (EXTEND), single PP, and non-CP paths supported by the DSV4 op strategy. """ from sglang.srt.layers.moe import is_tbo_enabled path_ok = not is_cp_active(forward_batch) and ( not _is_hip or not get_moe_a2a_backend().is_none() or get_parallel().attn_dp_size > 1 ) return ( is_tbo_enabled() and forward_batch.can_run_tbo and forward_batch.tbo_children is not None and forward_batch.global_forward_mode is not None # MTP target-verify also reports is_extend(); only real prefill # should enter the prefill TBO strategy. and forward_batch.global_forward_mode.is_extend_without_speculative() and path_ok and self.pp_group.world_size == 1 ) def _forward_layers_tbo( self, positions: torch.Tensor, hidden_states: torch.Tensor, forward_batch: ForwardBatch, ) -> torch.Tensor: from sglang.srt.batch_overlap.operations import execute_overlapped_operations from sglang.srt.batch_overlap.operations_strategy import OperationsStrategy from sglang.srt.batch_overlap.two_batch_overlap import ( _model_forward_filter_inputs, _model_forward_tbo_merge_key, ) layers = [self.layers[i] for i in range(self.start_layer, self.end_layer)] operations_strategy = OperationsStrategy.init_new_tbo( layers, forward_batch.global_forward_mode ) # Split the per-rank batch into the 2 ubatches (token-range slice + pad # to tbo_padded_len). residual is unused by the DSV4 non-fused layer ops. inputs_arr = [ _model_forward_filter_inputs( hidden_states=hidden_states, residual=None, positions=positions, output_forward_batch=child, tbo_subbatch_index=idx, ) for idx, child in enumerate(forward_batch.tbo_children) ] # Non-EP DP TP-MoE: the per-ubatch DP gather/combine (op_gather/op_combine) # needs each ubatch's per-rank token counts, but tbo_padded_len is computed # per-rank locally (not synced). All-gather both ubatches' padded lengths # once across DP ranks, then populate each child's global_num_tokens + # global_dp_buffer_len so the gatherv/reduce_scatterv buffers size correctly. if get_moe_a2a_backend().is_none() and get_parallel().attn_dp_size > 1: tp_group = get_parallel().tp_group world = tp_group.world_size children = forward_batch.tbo_children local_lens = torch.tensor( [int(c.tbo_padded_len) for c in children], dtype=torch.int64, device=hidden_states.device, ) gathered = torch.empty( (world, local_lens.shape[0]), dtype=torch.int64, device=hidden_states.device, ) tp_group.all_gather_into_tensor(gathered, local_lens) gathered_cpu = gathered.tolist() rank = tp_group.rank_in_group for idx, child in enumerate(children): sizes = [gathered_cpu[r][idx] for r in range(world)] child.global_num_tokens_cpu = sizes child.global_num_tokens_gpu = gathered[:, idx].contiguous() child.global_dp_buffer_len = sum(sizes) # Gather the ubatch's input_ids -> global ONCE here (cached on the # child) instead of per-layer in op_gather_a. The hash MoE reads # the SAME global ids every layer, so 61x2 per-layer all_gatherv of # VARYING size (-> RCCL registers a new internal buffer per size -> # HSA_STATUS_ERROR_OUT_OF_RESOURCES) collapses to 1 per ubatch. local_ids = child.input_ids rows = sizes[rank] if local_ids.shape[0] < rows: padded_ids = local_ids.new_zeros((rows,)) padded_ids[: local_ids.shape[0]] = local_ids elif local_ids.shape[0] > rows: padded_ids = local_ids[:rows] else: padded_ids = local_ids gids = torch.empty( (sum(sizes),), dtype=local_ids.dtype, device=local_ids.device ) tp_group.all_gatherv(padded_ids, sizes=sizes, output=gids) child._tbo_global_input_ids = gids try: outputs_arr = execute_overlapped_operations( inputs_arr=inputs_arr, operations_arr=[operations_strategy.operations] * 2, delta_stages=[0, operations_strategy.tbo_delta_stages], ) return _model_forward_tbo_merge_key( outputs_arr[0], outputs_arr[1], "hidden_states", hidden_states.shape[0] ) finally: if _is_cuda: for child in forward_batch.tbo_children: residual_batch.clear(child) @torch.no_grad() def forward( self, input_ids: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, input_embeds: Optional[torch.Tensor], pp_proxy_tensors: Optional[PPProxyTensors] = None, ) -> Union[torch.Tensor, PPProxyTensors]: if not _is_cuda: return self._forward_impl( input_ids, positions, forward_batch, input_embeds, pp_proxy_tensors ) try: return self._forward_impl( input_ids, positions, forward_batch, input_embeds, pp_proxy_tensors ) finally: residual_batch.clear(forward_batch) @torch.no_grad() def _forward_impl( self, input_ids: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, input_embeds: Optional[torch.Tensor], pp_proxy_tensors: Optional[PPProxyTensors] = None, ) -> Union[torch.Tensor, PPProxyTensors]: if self.pp_group.is_first_rank: if input_embeds is None: hidden_states = self.embed_tokens(input_ids) else: hidden_states = input_embeds from sglang.kernels.ops.layernorm.mhc import hc_broadcast hidden_states = hc_broadcast(hidden_states, self.hc_mult) else: assert pp_proxy_tensors is not None hidden_states = pp_proxy_tensors["hidden_states"] # Unflatten 2D PP IPC tensor back to 3D mHC shape. if hidden_states.ndim == 2: hidden_states = hidden_states.view( hidden_states.shape[0], self.hc_mult, self.hidden_size ) if get_parallel().attn_dp_size > 1 and get_moe_a2a_backend().is_none(): input_ids_global = torch.empty( (get_global_dp_buffer_len(), 1), dtype=input_ids.dtype, device=input_ids.device, ) # Token ids are replicated within an attention-TP group. Use replicate # gather here to avoid summing duplicated ids when attention_tp_size > 1. # Clone because the MAX_LEN gather may zero its local input in place. dp_gather_replicate( input_ids_global, input_ids[:, None].clone(), forward_batch ) input_ids_global = input_ids_global.squeeze(-1) else: input_ids_global = getattr(forward_batch, "input_ids_global", input_ids) capture_dspark = self.dspark_layers_to_capture is not None dspark_aux_hidden_states: List[torch.Tensor] = [] attn_backend = get_attn_backend() if _is_npu and forward_batch.attn_cp_metadata is not None: attn_backend.prepare_dsv4_cp_metadata(forward_batch) local_positions = getattr(forward_batch, "dsv4_cp_local_positions", None) if ( local_positions is not None and positions.shape[0] == local_positions.shape[0] ): forward_batch.positions = positions # Reset Compressor's per-step freqs_cis cache from any previous step. for _attr in ("freqs_cis_c4", "freqs_cis_c128"): if hasattr(forward_batch, _attr): delattr(forward_batch, _attr) run_tbo = self._can_run_tbo(forward_batch) and not capture_dspark if _is_npu and not run_tbo: # Rope cos/sin for the whole forward: one bf16 gather per rope # config on the current stream, before the layer loop forks the # KV/Q side streams. TBO children carry their own positions and # recompute per layer. prime_rope_cos_sin( ( self.layers[i].self_attn for i in range(self.start_layer, self.end_layer) ), forward_batch, positions, ) last_pre = None tail = None if self.hc_pre_from_prev_sublayer: assert not run_tbo, "two-batch overlap is not wired for this hc scheme" hidden_states, last_pre, tail = self._forward_layers_hc_pre_from_prev( positions, hidden_states, forward_batch, input_ids, input_ids_global, capture_dspark, dspark_aux_hidden_states, ) elif run_tbo: # Two-batch-overlap prefill (EP / mori). Cross-layer mHC fusion is # disabled here (each layer self-contained), so no trailing hc_post. hidden_states = self._forward_layers_tbo( positions=positions, hidden_states=hidden_states, forward_batch=forward_batch, ) else: if _is_cuda: residual_batch.start(forward_batch) use_fused = not _is_cuda and self.use_fused_mhc_post_pre prev_residual, prev_post, prev_comb = None, None, None last_layer = None for i in range(self.start_layer, self.end_layer): layer = self.layers[i] last_layer = layer ctx = ( nullcontext() if check_cuda_graph_backend(Phase.PREFILL, Backend.TC_PIECEWISE) else get_global_expert_distribution_recorder().with_current_layer(i) ) with ctx: if _is_cuda: hidden_states = layer( positions=positions, hidden_states=hidden_states, forward_batch=forward_batch, input_ids=input_ids, input_ids_global=input_ids_global, ) else: hidden_states, prev_residual, prev_post, prev_comb = layer( positions=positions, hidden_states=hidden_states, forward_batch=forward_batch, input_ids=input_ids, input_ids_global=input_ids_global, prev_residual=prev_residual, prev_post=prev_post, prev_comb=prev_comb, ) if capture_dspark and i in self.dspark_layers_to_capture: if _is_cuda: completed = hc_boundary.materialize( hidden_states, forward_batch, consume=False ) elif use_fused: completed = layer.hc_post( hidden_states, prev_residual, prev_post, prev_comb ) else: completed = hidden_states dspark_aux_hidden_states.append(completed.mean(dim=1)) if _is_cuda: hidden_states = residual_batch.read_output( hidden_states, forward_batch, hc_boundary.FinalRead(), None ) elif use_fused and last_layer is not None: hidden_states = last_layer.hc_post( hidden_states, prev_residual, prev_post, prev_comb ) if not self.pp_group.is_last_rank: # Flatten 3D mHC tensor for PP IPC. return PPProxyTensors({"hidden_states": hidden_states.flatten(1)}) pre_hc_head = hidden_states.flatten(1) if self.hc_pre_from_prev_sublayer: from sglang.kernels.ops.layernorm.mhc import mhc_combine hidden_states = mhc_combine( pre_hc_head.float(), last_pre, self.hc_mult, hidden_states.dtype ) else: hidden_states = self.hc_head( hidden_states, self.hc_head_fn, self.hc_head_scale, self.hc_head_base ) hidden_states = self.norm(hidden_states) if tail is not None and not capture_dspark: # The logits processor indexes rows by the full extend layout. num_tokens = input_ids.shape[0] hidden_states = _scatter_tail_rows( tail=tail, rows=hidden_states, num_tokens=num_tokens ) pre_hc_head = _scatter_tail_rows( tail=tail, rows=pre_hc_head, num_tokens=num_tokens ) if capture_dspark: return (hidden_states, pre_hc_head), dspark_aux_hidden_states return hidden_states, pre_hc_head # Quark keys its per-layer specs by checkpoint module names (layers.N.attn.wq_a, # mtp.0.ffn.experts.N.w1, ...); this maps them onto the module prefixes built here. _QUARK_CKPT_TO_SGLANG_MAPPER = WeightsMapper( orig_to_new_prefix={"layers.": "model.layers.", "mtp.": "model.mtp."}, orig_to_new_substr={".attn.": ".self_attn.", ".ffn.": ".mlp."}, orig_to_new_suffix={".w1": ".gate_proj", ".w2": ".down_proj", ".w3": ".up_proj"}, ) def _is_quark_checkpoint(config: DeepSeekV4Config) -> bool: quantization_config = getattr(config, "quantization_config", None) or {} return quantization_config.get("quant_method") == "quark" class DeepseekV4ForCausalLM(nn.Module): supports_cuda_vmm_feature_transport = True @classmethod def get_hf_to_sglang_mapper(cls, config) -> Optional[WeightsMapper]: # The loader hands this mapper to every quant config; other formats were # written against different names, so only Quark checkpoints get it. return _QUARK_CKPT_TO_SGLANG_MAPPER if _is_quark_checkpoint(config) else None def __init__( self, config: DeepSeekV4Config, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ) -> None: super().__init__() # DeepseekV4 enables, by default, the CK w8a8-block GEMM (MLA proj) and the # batched/contiguous-load rope kernels (faster on gfx95; . # Module-level toggles default OFF; flipped True here for DSV4 if _is_hip: from sglang.kernels.ops.attention.dsv4.rope import set_batched_rope from sglang.srt.layers.quantization.fp8_utils import ( set_dense_mxscale, set_force_ck_w8a8, ) set_force_ck_w8a8(True) set_batched_rope(True) # TODO(aiter#6004): remove once ROCm/aiter#6004 is in the aiter pin # TODO(aiter#6029): remove once ROCm/aiter#6029 is in the aiter pin # TODO(aiter#6030): remove once ROCm/aiter#6030 is in the aiter pin from sglang.srt.models.deepseek_common.amd.aiter_intree_tune import ( install_aiter_intree_tune, ) install_aiter_intree_tune() if envs.SGLANG_DSV4_DENSE_MXSCALE.get(): set_dense_mxscale(True) self.config = config self.tp_size = get_parallel().tp_size self.quant_config = quant_config self.wo_a_fp8 = wo_a_fp8_gemm_enabled(quant_config) # The weight loader reads this after the build. self.num_fused_shared_experts = get_num_fused_shared_experts() self.vision = None if ( config.model_type == "deepseek_v41" and config.vision_n_layers > 0 and not getattr(config, "language_model_only", False) ): if ( get_parallel().attn_cp_size != 1 or get_parallel().pp_group.world_size != 1 or not ( get_moe_a2a_backend().is_none() or (_is_cuda and get_moe_a2a_backend().is_megamoe()) ) ): raise ValueError( "V4.1 vision supports TP/EP/DP without CP or PP; " "MoE A2A requires CUDA MegaMoE" ) args = SimpleNamespace(**vars(config), dim=config.hidden_size) self.vision = ViT(args) self.aligner = Aligner(args) self.image_start = nn.Parameter(torch.empty(config.hidden_size)) self.image_end = nn.Parameter(torch.empty(config.hidden_size)) self.image_newline = nn.Parameter(torch.empty(config.hidden_size)) self.model = DeepseekV4Model( config, quant_config, prefix=add_prefix("model", prefix) ) self.pp_group = get_parallel().pp_group if self.pp_group.is_last_rank: if self.pp_group.world_size == 1 and config.tie_word_embeddings: self.lm_head = self.model.embed_tokens else: self.lm_head = ParallelLMHead( config.vocab_size, config.hidden_size, quant_config=quant_config, prefix=add_prefix("lm_head", prefix), use_attn_tp_group=get_parallel().enable_dp_lm_head, ) else: self.lm_head = PPMissingLayer() self.logits_processor = LogitsProcessor(config) self.capture_aux_hidden_states = False get_attn_tp_context().init_context(config.q_lora_rank, is_dsa=True) self._routed_experts_weights_of_layer = LazyValue( lambda: { layer_id: self.model.layers[layer_id].mlp.get_moe_weights() for layer_id in range(self.model.start_layer, self.model.end_layer) if isinstance( self.model.layers[layer_id].mlp, deepseek_v2.DeepseekV2MoE ) } ) # Expose start_layer/end_layer for model_runner PP support self.start_layer = self.model.start_layer self.end_layer = self.model.end_layer # update_weights_from_disk/_tensor/_distributed re-enter load_weights # mid-serving (RL refit sends many partial batches); the prewarm and # its barrier must only run on the first (startup) load. self._mhc_prewarmed_at_load = False @torch.inference_mode() def wants_prefill_autotune(self) -> bool: return getattr(self.config, "model_type", None) == "deepseek_v41" def autotune_prefill_kernels(self, num_tokens: int, *, dtype: torch.dtype) -> int: """Tune resident MXFP8 linears for every M bucket up to ``num_tokens``. The quant method is called directly, so no TP collectives run and no request/KV/draft state is touched; the runner owns the autotune context.""" if getattr(self.config, "model_type", None) != "deepseek_v41": return 0 seen = set() # The backbone excludes vision and lm_head, whose prefill shapes differ. for layer in self.model.modules(): method = getattr(layer, "quant_method", None) if not isinstance(method, Fp8LinearMethod): continue if not (method.use_mxfp8 or method.block_fp8_as_mxfp8): continue if method.block_fp8_as_mxfp8 and not getattr( layer, "block_fp8_mxfp8_ready", False ): # No swizzled MXFP8 scale buffer: these kept the block-FP8 fallback. continue backend = method.mxfp8_dense_backend if backend is None or not backend.is_flashinfer_cutedsl(): continue if method.block_fp8_as_mxfp8: # Small shapes and deterministic execution keep their pinned tactic. method.mxfp8_prefill_autotune_min_tokens = 4096 weight = layer.weight scale = layer.weight_scale_inv_swizzled key = ( weight.shape, weight.stride(), weight.dtype, scale.shape, scale.stride(), scale.dtype, ) if key in seen: continue seen.add(key) x = torch.zeros( (num_tokens, weight.shape[1]), dtype=dtype, device=weight.device, ) method.apply(layer, x) del x if seen: logger.info( "FlashInfer prefill autotune: %d MXFP8 weight layouts at M=%d.", len(seen), num_tokens, ) return len(seen) @property def routed_experts_weights_of_layer(self): return self._routed_experts_weights_of_layer.value def pad_input_ids(self, input_ids: array, mm_inputs) -> array: return pad_mm_tokens(input_ids, mm_inputs) def get_image_feature(self, items): """Return complete spans for the shared MM cache and chunk scheduler.""" spans = [] device, dtype = self.image_start.device, self.image_start.dtype for item in items: item.reconstruct(device.index) h, w = int(item.n_vit_h), int(item.n_vit_w) pixels = torch.as_tensor(item.feature, device=device) plan = item.model_specific_data.get(GPU_PLAN_KEY) patches = ( materialize_image_gpu(pixels, plan).to(dtype) if plan is not None else pixels.to(dtype) ) features = self.aligner(self.vision(patches, h, w), h, w) r = self.config.vision_downsample_ratio types = image_token_types((h + r - 1) // r, (w + r - 1) // r).to(device) span = torch.empty( (len(types), self.config.hidden_size), device=device, dtype=dtype ) span[types == 0] = self.image_start span[types == 1] = features.to(dtype) span[types == 2] = self.image_newline span[types == 3] = self.image_end spans.append(span) return spans def _prepare_mm_embeddings(self, input_ids, forward_batch): # Keep scheduler hash IDs intact: the shared embedder clamps its input in place. input_embeds, _ = embed_mm_inputs( mm_inputs_list=[ item if item is not None else MultimodalInputs(mm_items=[]) for item in forward_batch.mm_inputs ], extend_prefix_lens=forward_batch.extend_prefix_lens_cpu, extend_seq_lens=forward_batch.extend_seq_lens_cpu, input_ids=input_ids.clone(), input_embedding=self.get_input_embeddings(), multimodal_model=self, ) forward_batch.mm_input_embeds = input_embeds return input_embeds def get_input_embeddings(self) -> nn.Module: return self.model.get_input_embeddings() def set_dspark_layers_to_capture(self, layer_ids: List[int]) -> None: if not self.pp_group.is_last_rank: return if layer_ids is None: raise ValueError( "DSPARK requires explicit layer_ids for aux hidden capture." ) self.capture_aux_hidden_states = True self.model.dspark_layers_to_capture = list(layer_ids) @classmethod def shared_experts_fusion_disable_reason(cls, hf_config, quant_config): """V4 only fuses when explicitly asked to, and then the checkpoint must carry exactly one shared expert. Asked by the loader before any layer is built.""" # Need to disable if quant precision mismatch, even if # --enforce-shared-experts-fusion is specified if quant_blocks_shared_experts_fusion(quant_config): return ( "Quantization keeps shared experts at a higher precision than the " "routed experts, so they cannot be fused into the quantized " "routed-expert path." ) if get_parallel().moe_ep_size > 1 and not uses_per_rank_fused_shared_slots(): return ( "Expert parallelism keeps only a slice of the routed experts on " "each rank, so the fused shared expert cannot be appended to the " "routed weight tensor (only DeepEP/MegaMOE per-rank shared slots " "support fusion under EP)." ) if not get_exec().moe.enforce_shared_experts_fusion: return "Config does not support fused shared expert(s)." if hf_config.n_shared_experts != 1: raise ValueError( "DeepSeek V4 shared-experts fusion expects exactly one shared " f"expert, but got n_shared_experts={hf_config.n_shared_experts}." ) return None @classmethod def num_fusable_shared_experts(cls, hf_config) -> int: """Shared experts fused as routed slots when fusion is on.""" return hf_config.n_shared_experts or 0 def prepare_language_model_inputs( self, input_ids: torch.Tensor, forward_batch: ForwardBatch, input_embeds: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: """Prepare full-sequence image embeddings and model IDs before CP splits. Scheduler hash IDs stay intact for multimodal cache keys; the language model uses image_token_id for Engram masking and visual MoE routing. """ if ( getattr(self, "vision", None) is not None and not forward_batch.forward_mode.is_decode() and not forward_batch.forward_mode.is_target_verify() and forward_batch.mm_inputs is not None and any(x is not None for x in forward_batch.mm_inputs) ): if input_embeds is not None: raise ValueError("Cannot combine input_embeds and image inputs") input_embeds = self._prepare_mm_embeddings(input_ids, forward_batch) if getattr(self, "vision", None) is not None and not ( forward_batch.forward_mode.is_decode_or_idle() or forward_batch.forward_mode.is_target_verify() ): # Decode/verify IDs are already vocabulary IDs; remap prompt image # hashes for Engram and routing. input_ids = input_ids.masked_fill( input_ids >= MM_PAD_SHIFT_VALUE, self.config.image_token_id ) return input_ids, input_embeds def forward( self, input_ids: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, input_embeds: Optional[torch.Tensor] = None, pp_proxy_tensors: Optional[PPProxyTensors] = None, ) -> torch.Tensor: input_ids, input_embeds = self.prepare_language_model_inputs( input_ids, forward_batch, input_embeds ) with get_attn_tp_context().maybe_input_scattered(forward_batch): hidden_states = self.model.forward( input_ids, positions, forward_batch, input_embeds, pp_proxy_tensors ) if not self.pp_group.is_last_rank: return hidden_states aux_hidden_states = None if self.capture_aux_hidden_states: hidden_states, aux_hidden_states = hidden_states hidden_states, pre_hc_head = hidden_states logits_metadata = forward_batch tail = None if ( self.capture_aux_hidden_states and self.model.late_layer_start is not None and forward_batch.forward_mode.is_extend_without_speculative() ): tail = get_attn_backend().tail_forward_metadata.late_layer_tail input_ids = tail.rows(input_ids) if tail.real_rows_count is not None: # Fixed-capacity tail block from the prefill CUDA graph: drop padding. n = tail.real_rows_count input_ids = input_ids[:n] hidden_states = hidden_states[:n] pre_hc_head = pre_hc_head[:n] if aux_hidden_states is not None: aux_hidden_states = [a[:n] for a in aux_hidden_states] logits_metadata = LogitsMetadata.from_forward_batch(forward_batch) logits_metadata.extend_seq_lens = tail.extend_seq_lens logits_metadata.extend_seq_lens_cpu = tail.extend_seq_lens_cpu logits_metadata.extend_logprob_start_lens_cpu = ( # A prompt-logprob reader's tail is its whole extend, so its start # stands; every other start is the extend's end. [ min(start, t) for start, t in zip( forward_batch.extend_logprob_start_lens_cpu, tail.extend_seq_lens_cpu, ) ] if forward_batch.return_logprob else tail.extend_seq_lens_cpu ) output = self.logits_processor( input_ids, hidden_states, self.lm_head, logits_metadata, aux_hidden_states, hidden_states_before_norm=( None if aux_hidden_states is not None else pre_hc_head ), ) if tail is not None: output.hidden_states_token_indices = ( tail.token_indices[: tail.real_rows_count] if tail.real_rows_count is not None else tail.token_indices ) return output def _setup_fp8_wo_a_scales(self, is_nextn: bool) -> None: if _FP8_WO_A_UE8M0: from deep_gemm import transform_sf_into_required_layout if is_nextn: layers = [self.model.decoder] else: layers = [ self.model.layers[layer_id] for layer_id in range(self.model.start_layer, self.model.end_layer) ] for layer in layers: attn = layer.self_attn G = attn.n_local_groups R = attn.o_lora_rank D = attn.wo_a.weight.shape[1] if _wo_a_weight_scale_to_e8m0 is not None: # ROCm: aiter's mxscale GEMM reads uint8 e8m0 block scales, and # requantizes the weight when the checkpoint's scales are not # already powers of two. It also needs the weight row-major, so # check the linear method honoured keep_plain_weight_layout: a # preshuffled weight has the same shape, dtype and strides and # would only show up as garbage output. assert not getattr(attn.wo_a, "aiter_bpreshuffled", False), ( "DSV4 wo_a was B-preshuffled by the fp8 linear method; the " "aiter mxscale absorb GEMM needs the row-major weight" ) weight, scale = _wo_a_weight_scale_to_e8m0( attn.wo_a.weight.data, attn.wo_a.weight_scale_inv.data, G, R, ) attn.wo_a.weight.data = weight.view(G * R, D) attn.wo_a.weight_scale_inv.data = scale attn.wo_a.weight_scale_inv.format_ue8m0 = True continue raw_scale = attn.wo_a.weight_scale_inv.data.view(G, R // 128, D // 128) if _FP8_WO_A_UE8M0: attn.wo_a.weight_scale_inv.data = transform_sf_into_required_layout( raw_scale, mn=R, k=D, recipe=(1, 128, 128), num_groups=G, is_sfa=False, ) attn.wo_a.weight_scale_inv.format_ue8m0 = True else: attn.wo_a.weight_scale_inv.data = raw_scale.contiguous() attn.wo_a.weight_scale_inv.format_ue8m0 = False def post_load_weights(self, is_nextn=False, weight_names=None): if self.wo_a_fp8: self._setup_fp8_wo_a_scales(is_nextn) for module in self.modules(): if isinstance(module, MQALayer): module.setup_mega_attention() if is_nextn: return for layer_id in range(self.model.start_layer, self.model.end_layer): layer = self.model.layers[layer_id] self_attn = layer.self_attn if ( self_attn.compress_ratio in (4, 128) and not self_attn.compressor.ape_converted ): self_attn.compressor.apply_ape_hotfix() if ( self_attn.compress_ratio == 4 and not self_attn.indexer.compressor.ape_converted ): self_attn.indexer.compressor.apply_ape_hotfix() self_attn.refresh_attn_sink_cache() layer.refresh_mhc_norm_weight_cache() layers = self.model.layers for i, layer in enumerate(layers): if isinstance(layer, DeepseekV4PlatformDecoderLayer): layer.bind_next(layers[i + 1] if i + 1 < len(layers) else None) def precompile_kernels_after_loading(self) -> None: from sglang.srt.layers.moe.mega_moe import ( build_mega_moe_shared_weights, should_fuse_mega_moe_shared_experts, ) # The only deployments whose fused-collective gates can fire; registration # must happen eagerly here, before graph capture. if get_platform().is_blackwell and mhc.fused_mhc_tp_ok(get_parallel().tp_size): from sglang.srt.layers.quantization.mxfp4_flashinfer_trtllm_moe import ( register_fused_all_reduce_comm, ) register_fused_all_reduce_comm() for module in self.modules(): if isinstance( module, deepseek_v2.DeepseekV2MoE ) and should_fuse_mega_moe_shared_experts(module): module.mega_shared_l1_weights, module.mega_shared_l2_weights = ( build_mega_moe_shared_weights(module.shared_experts) ) @staticmethod def remap_weight_name_to_dpsk_hf_format( name: str, is_nextn: bool = False, num_hidden_layers: Optional[int] = None, ) -> str: if name.startswith("vision."): return name.replace(".attn.wqkv.", ".attn.qkv_proj.").replace( ".attn.wo.", ".attn.proj." ) if name.startswith(("aligner.", "image_")): return name if name.startswith("embed."): return "model.embed_tokens." + name.removeprefix("embed.") if name.startswith("head."): return "lm_head." + name.removeprefix("head.") if name == "norm.weight": return "model.norm.weight" if name.startswith("hc_head_"): return "model." + name if is_nextn and name.startswith("mtp."): parts = name.split(".", 2) if len(parts) >= 3: rest = parts[2] nextn_spec_prefixes = [ "e_proj", "h_proj", "emb", "enorm", "hnorm", "norm", "head", "hc_head", ] is_nextn_spec = any(rest.startswith(p) for p in nextn_spec_prefixes) if is_nextn_spec: if rest.startswith("emb.tok_emb"): rest = rest.replace("emb.tok_emb", "embed_tokens") elif rest == "norm.weight": rest = "shared_head.norm.weight" elif rest.startswith("head."): rest = "shared_head.head.weight" elif rest == "e_proj.scale": rest = "e_proj.weight_scale_inv" elif rest == "h_proj.scale": rest = "h_proj.weight_scale_inv" name = f"model.layers.{num_hidden_layers}." + rest if name.startswith("layers."): name = "model." + name name = name.replace(".attn.", ".self_attn.") name = name.replace(".ffn.", ".mlp.") name = name.replace(".attn_norm.", ".input_layernorm.") name = name.replace(".ffn_norm.", ".post_attention_layernorm.") if "self_attn" in name and name.endswith(".scale"): name = name.removesuffix(".scale") + ".weight_scale_inv" if ".engram.wkv." in name and name.endswith(".scale"): name = name.removesuffix(".scale") + ".weight_scale_inv" name = name.replace(".gate.tid2eid", ".topk.tid2eid") name = name.replace(".gate.bias", ".gate.e_score_correction_bias") name = name.replace(".w1.", ".gate_proj.") name = name.replace(".w2.", ".down_proj.") name = name.replace(".w3.", ".up_proj.") if "mlp" in name and name.endswith(".scale"): name = name.removesuffix(".scale") + ".weight_scale_inv" return name def _prewarm_mhc_kernels(self) -> None: """One-shot MHC JIT prewarm at load time, synced across ranks. Runs before any forward so the compile burst stays off the serving path; the barrier keeps ranks from proceeding while a peer is still compiling. The early returns below must stay rank-uniform. """ if self._mhc_prewarmed_at_load: return self._mhc_prewarmed_at_load = True if _is_npu or _is_xpu or not envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get(): return layer = next( (m for m in self.model.layers if isinstance(m, DeepseekV4DecoderLayer)), None, ) if layer is None: return from sglang.kernels.ops.layernorm.mhc import mhc_post, prewarm_mhc_pre tic = time.perf_counter() residual = torch.zeros( (1, layer.hc_mult, layer.hidden_size), dtype=torch.bfloat16, device=layer.hc_attn_fn.device, ) prewarm_mhc_pre( # Template carrying dtype/device; buckets allocate their own sizes. residual=residual, fn=layer.hc_attn_fn, hc_scale=layer.hc_attn_scale, hc_base=layer.hc_attn_base, rms_eps=layer.rms_norm_eps, hc_pre_eps=layer.hc_eps, hc_sinkhorn_eps=layer.hc_eps, hc_post_mult_value=MHC_POST_MULT_VALUE, sinkhorn_repeat=layer.hc_sinkhorn_iters, n_splits=1, n_splits_pre=32, norm_weight=layer.input_layernorm.weight.data, norm_eps=layer.input_layernorm.variance_epsilon, ) mhc_post( x=residual.new_zeros((1, layer.hidden_size)), residual=residual, post_layer_mix=torch.zeros( (1, layer.hc_mult, 1), dtype=torch.float32, device=residual.device, ), comb_res_mix=torch.zeros( (1, layer.hc_mult, layer.hc_mult), dtype=torch.float32, device=residual.device, ), ) torch.cuda.synchronize() compile_secs = time.perf_counter() - tic # Runs before init_memory_pool(); don't let transients skew pool sizing. torch.cuda.empty_cache() get_parallel().tp_group.barrier() logger.info( "DeepSeek V4 MHC prewarm at load: compile %.1fs, rank sync +%.1fs", compile_secs, time.perf_counter() - tic - compile_secs, ) def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]], is_nextn=False): params_dict = dict(self.named_parameters()) loaded_params: Set[str] = set() if is_nextn: if hasattr(self.config, "num_nextn_predict_layers"): num_nextn_layers = self.config.num_nextn_predict_layers assert num_nextn_layers == 1, "Only 1 nextn layer is supported" nextn_layer_id = ( 0 if self.config.num_hidden_layers == 1 else self.config.num_hidden_layers ) else: raise ValueError("num_nextn_predict_layers is not in the config") # Must mirror MQALayer.__init__'s `quantize_wo_a`: dequantizing wo_a here # while the layer allocated an FP8 parameter (or vice versa) fails the # weight loader's dtype check. if not (self.wo_a_fp8 or use_npu_arch35_mxfp8_wo_a(self.quant_config)): weights = _prepare_deepseek_v4_weights(weights, self.quant_config) stacked_params_mapping = DEEPSEEK_V4_STACKED_PARAMS_MAPPING expert_params_mapping = FusedMoE.make_expert_params_mapping( ckpt_gate_proj_name="gate_proj", ckpt_down_proj_name="down_proj", ckpt_up_proj_name="up_proj", num_experts=self.config.n_routed_experts + self.num_fused_shared_experts, ) if is_wint4afp8_or_wint4a16_config(self.quant_config): expert_params_mapping += FusedMoE.make_expert_input_scale_params_mapping( num_experts=self.config.n_routed_experts ) cache_compressor_weight = {} COMPRESSOR_PART = ".compressor.w" fuse_wqa_wkv = envs.SGLANG_OPT_FUSE_WQA_WKV.get() cache_wqkv_a_weight: dict[str, dict[str, torch.Tensor]] = {} skipped_by_group: dict[str, int] = {} # V4 checkpoints must load every compressor / indexer tensor. is_dsv41 = getattr(self.config, "model_type", None) == "deepseek_v41" def auto_weight_loader(module): return getattr(module, "weight_loader", default_weight_loader) if is_nextn: nextn_layer_prefix = f"model.layers.{nextn_layer_id}" nextn_spec_weight_names_out_of_layer = [ "shared_head.norm", "shared_head.head", "embed_tokens", ".e_proj", "h_proj", "enorm", "hnorm", "hc_head_base", "hc_head_fn", "hc_head_scale", ] if self.num_fused_shared_experts > 0: assert self.num_fused_shared_experts == 1 log_info_on_rank0(logger, "Shared experts fusion optimization enabled.") with concurrent.futures.ThreadPoolExecutor() as executor: futures = [] weight_names = [] for name, loaded_weight in weights: if ( self.wo_a_fp8 and name.endswith(".wo_a.weight") and loaded_weight.dtype != torch.float8_e4m3fn ): raise ValueError( f"SGLANG_OPT_FP8_WO_A_GEMM is enabled but {name} has " f"dtype {loaded_weight.dtype}, expected " "torch.float8_e4m3fn. This checkpoint does not provide " "a supported fp8-quantized wo_a; rerun with " "SGLANG_OPT_FP8_WO_A_GEMM=0." ) try: use_async_loading = should_async_load(loaded_weight) name = self.remap_weight_name_to_dpsk_hf_format( name, is_nextn=is_nextn, num_hidden_layers=self.config.num_hidden_layers, ) # V4.1 checkpoint tensors with no module in the text model yet. skip_group = None if not is_dsv41: pass elif self.vision is None and name.startswith( ("vision.", "aligner.", "image_") ): skip_group = "vision" elif self.vision is None and name.endswith( ".gate.e_score_correction_bias_vl" ): skip_group = "gate.bias_vl" if skip_group is not None: skipped_by_group[skip_group] = ( skipped_by_group.get(skip_group, 0) + 1 ) continue 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 ( self.num_fused_shared_experts > 0 and "mlp.shared_experts" in name ): name = name.replace( "mlp.shared_experts", f"mlp.experts.{self.config.n_routed_experts}", ) weight_names.append(name) if not is_nextn: 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 name.startswith("mtp"): continue else: if "shared_head.head" in name or "embed_tokens" in name: continue if not name.startswith(nextn_layer_prefix): continue in_decoder = True for weight_name in nextn_spec_weight_names_out_of_layer: if weight_name in name: in_decoder = False name = name.replace(nextn_layer_prefix, "model") break if in_decoder: name = name.replace(nextn_layer_prefix, "model.decoder") if "rotary_emb.inv_freq" in name: continue for param_name, weight_name, shard_id in stacked_params_mapping: if weight_name not in name: continue if _is_npu: name = name.replace("weight_packed", "weight") if ("mlp.experts." in name) and name not in params_dict: continue name = name.replace(weight_name, param_name) if name.endswith(".bias") and name not in params_dict: continue if name not in params_dict and name.startswith("mtp"): break param = params_dict[name] weight_loader = param.weight_loader maybe_executor_submit( executor=executor, futures=futures, use_async=use_async_loading, func=weight_loader, func_args=(param, loaded_weight, shard_id), ) loaded_params.add(name) break else: skip_unmaterialized_expert_param = False for mapping in expert_params_mapping: param_name, weight_name, expert_id, shard_id = mapping if weight_name not in name: continue if _is_npu: name = name.replace("weight_packed", "weight") resolved_name = name.replace(weight_name, param_name) if resolved_name not in params_dict: skip_unmaterialized_expert_param = True continue param = params_dict[resolved_name] weight_loader = param.weight_loader maybe_executor_submit( executor=executor, futures=futures, use_async=use_async_loading, func=weight_loader, func_args=( param, loaded_weight, resolved_name, ), func_kwargs={ "shard_id": shard_id, "expert_id": expert_id, }, ) loaded_params.add(resolved_name) break else: if skip_unmaterialized_expert_param: continue if name.endswith(".bias") and name not in params_dict: continue if ( ".embed_tokens." in name and not self.pp_group.is_first_rank ): continue if ( name == "model.norm.weight" and not self.pp_group.is_last_rank ): continue if ( name.startswith("model.hc_head_") or name == "lm_head.weight" ) and not self.pp_group.is_last_rank: continue elif ( COMPRESSOR_PART in name and ".wkv_gate." not in name and (name.rsplit(".", 2)[0] + ".wkv_gate.weight") in params_dict ): # Split-projection modules load per parameter instead. is_kv = name.endswith(".wkv.weight") is_wgate = name.endswith(".wgate.weight") assert is_kv != is_wgate key = name.rsplit(".", 2)[0] assert key.endswith(".compressor") if key not in cache_compressor_weight: cache_compressor_weight[key] = ( is_kv, _clone_if_runai_streamed_tensor(loaded_weight), ) else: assert key in cache_compressor_weight cached_is_kv, cached_weight = ( cache_compressor_weight[key] ) assert cached_is_kv != is_kv kv = loaded_weight if is_kv else cached_weight wgate = loaded_weight if is_wgate else cached_weight fused_weight = torch.cat([kv, wgate], dim=0) param_name = key + ".wkv_gate.weight" param = params_dict[param_name] weight_loader = auto_weight_loader(param) maybe_executor_submit( executor=executor, futures=futures, use_async=use_async_loading, func=weight_loader, func_args=(param, fused_weight), ) loaded_params.add(param_name) cache_compressor_weight.pop(key) elif ( fuse_wqa_wkv and ".compressor." not in name and ".engram." not in name and ( name.endswith(".wq_a.weight") or name.endswith(".wq_a.weight_scale_inv") or name.endswith(".wkv.weight") or name.endswith(".wkv.weight_scale_inv") or name.endswith(".wq_a.qweight") or name.endswith(".wkv.qweight") or name.endswith(".wq_a.qweight_type") or name.endswith(".wkv.qweight_type") ) ): is_q = ".wq_a." in name param_name = name.replace( ".wq_a." if is_q else ".wkv.", ".wqkv_a." ) bucket = cache_wqkv_a_weight.setdefault(param_name, {}) shard_key = "q" if is_q else "kv" assert shard_key not in bucket, ( f"duplicate shard {shard_key} for {param_name}" ) bucket[shard_key] = _clone_if_runai_streamed_tensor( loaded_weight ) if len(bucket) == 2: fused_weight = _fuse_deepseek_v4_wqkv_a_pair( param_name, bucket ) param = params_dict[param_name] weight_loader = auto_weight_loader(param) maybe_executor_submit( executor=executor, futures=futures, use_async=use_async_loading, func=weight_loader, func_args=(param, fused_weight), ) loaded_params.add(param_name) cache_wqkv_a_weight.pop(param_name) else: if ( "k_scale" in name or "v_scale" in name ) and name not in params_dict: for scale in ["k_scale", "v_scale"]: if scale in name: name = name.replace( f"{scale[0]}_proj", "attn_mqa" ) break if name not in params_dict: if not name.startswith("mtp"): logger.warning( f"{name} not found in params_dict." ) continue param = params_dict[name] weight_loader = auto_weight_loader(param) maybe_executor_submit( executor=executor, futures=futures, use_async=use_async_loading, func=weight_loader, func_args=(param, loaded_weight), ) loaded_params.add(name) except Exception as e: e.add_note(f"{name=} {loaded_weight.shape=}") raise for future in concurrent.futures.as_completed(futures): future.result() assert len(cache_compressor_weight) == 0 assert len(cache_wqkv_a_weight) == 0, cache_wqkv_a_weight.keys() if skipped_by_group: log_info_on_rank0( logger, "Skipped checkpoint tensors not wired yet: " + ", ".join(f"{k}={v}" for k, v in sorted(skipped_by_group.items())), ) unloaded_params = params_dict.keys() - loaded_params skipped_checking_patterns = [ "attn_mqa.k_scale", "attn_mqa.v_scale", "blockscale_swizzled", ] if not self.pp_group.is_first_rank: skipped_checking_patterns.append("embed_tokens") if not self.pp_group.is_last_rank: skipped_checking_patterns.append("model.norm.") skipped_checking_patterns.extend(["lm_head", "hc_head_"]) if is_nextn: skipped_checking_patterns.extend(["lm_head", "embed_tokens"]) unloaded_params = { p for p in unloaded_params if all( skipped_checking_pattern not in p for skipped_checking_pattern in skipped_checking_patterns ) } if unloaded_params: logger.warning( f"Some weights are not initialized from checkpoints: {unloaded_params}" ) self.post_load_weights(is_nextn=is_nextn, weight_names=weight_names) if not is_nextn: for i, layer in enumerate(self.model.layers): if getattr(layer, "engram", None) is not None: layer.engram.embed.finish_load(label=f"layer {i}") self._prewarm_mhc_kernels() 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 # Hot weight reload (RL workflows). Use the device-agnostic module # accessor so this works on both CUDA/HIP and NPU. torch.get_device_module().empty_cache() torch.get_device_module().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=None, ) EntryClass = [DeepseekV4ForCausalLM] def _dequant_fp8(weight: torch.Tensor, scale: torch.Tensor) -> torch.Tensor: from einops import rearrange assert weight.dtype == torch.float8_e4m3fn, ( f"expected fp8_e4m3fn, got {weight.dtype}" ) assert scale.dtype in ( torch.float8_e8m0fnu, torch.float32, ), f"expected fp8_e8m0fnu or float32, got {scale.dtype}" # Block size is per-checkpoint: V4 128x128, V4.1 32x32. bn = weight.shape[0] // scale.shape[0] bk = weight.shape[1] // scale.shape[1] weight_f32 = rearrange( weight.float(), "(sn bn) (sk bk) -> sn bn sk bk", bn=bn, bk=bk ) result = rearrange( weight_f32 * scale.float()[:, None, :, None], "sn bn sk bk -> (sn bn) (sk bk)" ) return result.to(torch.bfloat16) def _clone_if_runai_streamed_tensor(tensor: torch.Tensor) -> torch.Tensor: if getattr(tensor, RUNAI_STREAMER_TENSOR_ATTR, False): return tensor.clone().detach() return tensor def _dequant_fp8_wo_a_streaming( weights: Iterable[Tuple[str, torch.Tensor]], ) -> Iterable[Tuple[str, torch.Tensor]]: pending: dict[str, dict[str, torch.Tensor]] = {} saw_wo_a_scale = False emitted = False for name, tensor in weights: if name.endswith(".wo_a.weight"): prefix = name[: -len(".weight")] bucket = pending.setdefault(prefix, {}) scale = bucket.pop("scale", None) if scale is not None: pending.pop(prefix, None) emitted = True yield name, _dequant_fp8(tensor, scale) else: bucket["weight"] = _clone_if_runai_streamed_tensor(tensor) continue if name.endswith(".wo_a.scale"): saw_wo_a_scale = True prefix = name[: -len(".scale")] bucket = pending.setdefault(prefix, {}) weight = bucket.pop("weight", None) if weight is not None: pending.pop(prefix, None) emitted = True yield prefix + ".weight", _dequant_fp8(weight, tensor) else: bucket["scale"] = _clone_if_runai_streamed_tensor(tensor) continue yield name, tensor if emitted: logger.info("Finished streaming dequant fp8 wo_a") for prefix, bucket in pending.items(): if "weight" in bucket: assert not saw_wo_a_scale, f"{prefix}.scale is missing" yield prefix + ".weight", bucket["weight"] if "scale" in bucket: yield prefix + ".scale", bucket["scale"] def _dequant_fp8_wo_a( weights: Iterable[Tuple[str, torch.Tensor]], ) -> Iterable[Tuple[str, torch.Tensor]]: weights_dict = dict(weights) for name in list(weights_dict.keys()): if name not in weights_dict: continue if not name.endswith(".wo_a.weight"): continue scale_name = name.replace(".wo_a.weight", ".wo_a.scale") assert scale_name in weights_dict weight = weights_dict.pop(name) scale = weights_dict.pop(scale_name) yield name, _dequant_fp8(weight, scale) yield from weights_dict.items() def _prepare_deepseek_v4_weights( weights: Iterable[Tuple[str, torch.Tensor]], quant_config: Optional[QuantizationConfig], ) -> Iterable[Tuple[str, torch.Tensor]]: """Keep Expert Pack GGUF weights on the streaming load path.""" if quant_config is not None and quant_config.get_name() == "expert_pack": logger.info("Keep Expert Pack GGUF weights on the streaming load path") return weights if quant_config is not None and quant_config.get_name() == "quark": weights = _rename_quark_fp8_block_scales(weights) return _dequant_fp8_wo_a_streaming(weights) def _rename_quark_fp8_block_scales( weights: Iterable[Tuple[str, torch.Tensor]], ) -> Iterable[Tuple[str, torch.Tensor]]: # Quark spells the FP8-block scale ".weight_scale" where native V4 spells it # ".scale" (both e8m0); MXFP4 scales are uint8 and keep their name. for name, tensor in weights: if name.endswith(".weight_scale") and tensor.dtype == torch.float8_e8m0fnu: name = name.removesuffix(".weight_scale") + ".scale" yield name, tensor def _fuse_deepseek_v4_wqkv_a_pair( param_name: str, bucket: dict[str, torch.Tensor] ) -> torch.Tensor: """Fuse Q/KV rows while preserving their common GGUF type scalar.""" q = bucket["q"] kv = bucket["kv"] if param_name.endswith(".qweight_type"): if q.numel() != 1 or kv.numel() != 1 or q.item() != kv.item(): raise ValueError( f"cannot fuse different GGUF qweight types for {param_name}: " f"q={q.tolist()} kv={kv.tolist()}" ) return q return torch.cat([q, kv], dim=0)