[AMD][DSV4] DSV4 MTP graph + sparse triton attn optimizations (#26383)
Co-authored-by: wunhuang <wunhuang@amd.com> Co-authored-by: Thomas Wang <1am9trash@gmail.com> Co-authored-by: Xinyi Song <86638975+RolaoDenthu@users.noreply.github.com> Co-authored-by: HaiShaw <hixiao@gmail.com> Co-authored-by: amd-danli103 <danli103@amd.com> Co-authored-by: Lin, Soga <soga.lin@amd.com> Co-authored-by: Raiden-Makoto <Raiden-Makoto@users.noreply.github.com> Co-authored-by: Hubert Lu <55214931+hubertlu-tw@users.noreply.github.com> Co-authored-by: yichiche@amd.com <jacky.cheng> Co-authored-by: yctseng0211 <yctseng@amd.com> Co-authored-by: Bingxu Chen <bingxche@amd.com>
This commit is contained in:
co-authored by
wunhuang
Thomas Wang
Xinyi Song
HaiShaw
amd-danli103
Lin, Soga
Raiden-Makoto
Hubert Lu
yichiche@amd.com
yctseng0211
Bingxu Chen
parent
e06058ed62
commit
deaba74745
@@ -650,6 +650,7 @@ class Envs:
|
||||
SGLANG_OPT_DEEPGEMM_HC_PRENORM = EnvBool(True)
|
||||
SGLANG_OPT_USE_TILELANG_MHC_PRE = EnvBool(True)
|
||||
SGLANG_OPT_USE_TILELANG_MHC_POST = EnvBool(True)
|
||||
SGLANG_OPT_USE_TRITON_FUSED_MHC = EnvBool(True)
|
||||
SGLANG_OPT_USE_TILELANG_INDEXER = EnvBool(False)
|
||||
SGLANG_OPT_USE_AITER_INDEXER = EnvBool(False)
|
||||
SGLANG_OPT_USE_JIT_INDEXER_METADATA = EnvBool(True)
|
||||
|
||||
@@ -51,6 +51,7 @@ from sglang.srt.layers.dp_attention import (
|
||||
)
|
||||
from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
|
||||
from sglang.srt.speculative.eagle_utils import per_step_draft_out_cache_loc
|
||||
from sglang.srt.speculative.spec_info import SpecInput
|
||||
from sglang.srt.utils import ceil_align
|
||||
|
||||
@@ -500,32 +501,21 @@ class DeepseekV4HipRadixBackend(
|
||||
req_pool_indices: torch.Tensor,
|
||||
seq_lens: torch.Tensor,
|
||||
out_cache_loc: Optional[torch.Tensor] = None,
|
||||
extend_seq_lens: Optional[torch.Tensor] = None,
|
||||
use_prefill_cuda_graph: bool = False,
|
||||
) -> Union[DSV4Metadata, DSV4RawVerifyMetadata]:
|
||||
if envs.SGLANG_PREP_IN_CUDA_GRAPH.get():
|
||||
assert out_cache_loc is not None
|
||||
if not hasattr(self, "extend_seq_lens_buffer"):
|
||||
self.extend_seq_lens_buffer = torch.tensor(
|
||||
[self.speculative_num_draft_tokens] * 1025, device=self.device
|
||||
)
|
||||
extend_seq_lens = self.extend_seq_lens_buffer[: len(seq_lens)]
|
||||
|
||||
return DSV4RawVerifyMetadata(
|
||||
req_pool_indices=req_pool_indices,
|
||||
seq_lens=seq_lens,
|
||||
out_cache_loc=out_cache_loc,
|
||||
extend_seq_lens=extend_seq_lens,
|
||||
)
|
||||
else:
|
||||
seq_lens_cpu = seq_lens.tolist()
|
||||
return self.init_forward_metadata_target_verify_old(
|
||||
max_seq_len=max_seq_len,
|
||||
req_pool_indices=req_pool_indices,
|
||||
seq_lens=seq_lens,
|
||||
seq_lens_cpu=seq_lens_cpu,
|
||||
out_cache_loc=out_cache_loc,
|
||||
use_prefill_cuda_graph=use_prefill_cuda_graph,
|
||||
)
|
||||
# HIP path: build target-verify metadata eagerly even when
|
||||
# SGLANG_PREP_IN_CUDA_GRAPH is enabled. The raw/lazy-upgrade route can
|
||||
# hit planner invariants during graph capture for DSV4+EAGLE.
|
||||
seq_lens_cpu = seq_lens.tolist()
|
||||
return self.init_forward_metadata_target_verify_old(
|
||||
max_seq_len=max_seq_len,
|
||||
req_pool_indices=req_pool_indices,
|
||||
seq_lens=seq_lens,
|
||||
seq_lens_cpu=seq_lens_cpu,
|
||||
out_cache_loc=out_cache_loc,
|
||||
use_prefill_cuda_graph=use_prefill_cuda_graph,
|
||||
)
|
||||
|
||||
def init_forward_metadata_target_verify_old(
|
||||
self,
|
||||
@@ -565,8 +555,15 @@ class DeepseekV4HipRadixBackend(
|
||||
out_cache_loc = raw_metadata.out_cache_loc
|
||||
|
||||
bs, num_draft_tokens = len(seq_lens), self.speculative_num_draft_tokens
|
||||
seq_lens = seq_lens + self.speculative_num_draft_tokens
|
||||
seq_lens = seq_lens + num_draft_tokens
|
||||
extend_seq_lens = raw_metadata.extend_seq_lens
|
||||
if extend_seq_lens is None or extend_seq_lens.numel() != bs:
|
||||
extend_seq_lens = torch.full_like(seq_lens, num_draft_tokens)
|
||||
else:
|
||||
extend_seq_lens = extend_seq_lens.to(
|
||||
device=seq_lens.device, dtype=seq_lens.dtype
|
||||
)
|
||||
extend_seq_lens = torch.minimum(extend_seq_lens, seq_lens).clamp_min_(1)
|
||||
|
||||
seq_lens_casual, req_pool_indices_repeated = (
|
||||
self.expand_extend_with_same_length(
|
||||
@@ -678,11 +675,22 @@ class DeepseekV4HipRadixBackend(
|
||||
max_seq_len = int(seq_lens_cpu.max().item())
|
||||
|
||||
if forward_batch.forward_mode.is_decode_or_idle():
|
||||
# DSv4 bakes this step's KV write target (c4/c128) into metadata,
|
||||
# so slice the shared multi-step out_cache_loc now rather than at
|
||||
# forward time.
|
||||
out_cache_loc = forward_batch.out_cache_loc
|
||||
if self.topk > 0 and self.speculative_num_steps > 1:
|
||||
out_cache_loc = per_step_draft_out_cache_loc(
|
||||
out_cache_loc,
|
||||
forward_batch.batch_size,
|
||||
self.topk,
|
||||
self.speculative_num_steps,
|
||||
)[self.speculative_step_id]
|
||||
metadata = self.init_forward_metadata_decode(
|
||||
max_seq_len=max_seq_len,
|
||||
req_pool_indices=req_pool_indices,
|
||||
seq_lens=seq_lens,
|
||||
out_cache_loc=forward_batch.out_cache_loc,
|
||||
out_cache_loc=out_cache_loc,
|
||||
)
|
||||
elif forward_batch.forward_mode.is_target_verify():
|
||||
metadata = self.init_forward_metadata_target_verify(
|
||||
@@ -690,6 +698,7 @@ class DeepseekV4HipRadixBackend(
|
||||
req_pool_indices=req_pool_indices,
|
||||
seq_lens=seq_lens,
|
||||
out_cache_loc=forward_batch.out_cache_loc,
|
||||
extend_seq_lens=forward_batch.extend_seq_lens,
|
||||
)
|
||||
elif forward_batch.forward_mode.is_prefill(include_draft_extend_v2=True):
|
||||
extend_seq_lens_cpu = forward_batch.extend_seq_lens_cpu
|
||||
|
||||
+12
-1
@@ -1208,6 +1208,9 @@ def _prune_splitk_configs(configs, named_args, **kwargs):
|
||||
triton.Config({"BLOCK_H": 64, "BLOCK_N": 128}, num_warps=4, num_stages=1),
|
||||
triton.Config({"BLOCK_H": 128, "BLOCK_N": 64}, num_warps=4, num_stages=1),
|
||||
triton.Config({"BLOCK_H": 128, "BLOCK_N": 128}, num_warps=4, num_stages=1),
|
||||
# BLOCK_H=32: critical for cc=32 with h_q=128 (gives 256 blocks with split_k=2)
|
||||
triton.Config({"BLOCK_H": 32, "BLOCK_N": 64}, num_warps=4, num_stages=1),
|
||||
triton.Config({"BLOCK_H": 32, "BLOCK_N": 128}, num_warps=4, num_stages=1),
|
||||
],
|
||||
key=["total_tokens_bucket", "h_q", "topk_per_split"],
|
||||
prune_configs_by={"early_config_prune": _prune_splitk_configs},
|
||||
@@ -1590,6 +1593,7 @@ def fused_gather_attn_decode_dsv4_dual_scope(
|
||||
topk_length_extra: Optional[torch.Tensor] = None,
|
||||
attn_sink: Optional[torch.Tensor] = None,
|
||||
s_q: int = 1,
|
||||
force_no_splitk: bool = False,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Fused gather+dequant+attention for DSV4 with dual scope (main + extra).
|
||||
@@ -1608,6 +1612,9 @@ def fused_gather_attn_decode_dsv4_dual_scope(
|
||||
topk_length_extra: Optional per-batch topk length for extra [b]
|
||||
attn_sink: Optional attention sink values [h_q]
|
||||
s_q: Sequence length per batch
|
||||
force_no_splitk: If True, skip split-K and use the non-splitk kernel
|
||||
directly. Used by the dispatch layer for large batch prefill where
|
||||
the non-splitk fused kernel avoids intermediate buffer allocation.
|
||||
|
||||
Returns:
|
||||
output: Attention output [total_tokens, h_q, d_v]
|
||||
@@ -1647,6 +1654,10 @@ def fused_gather_attn_decode_dsv4_dual_scope(
|
||||
or kv_cache_size_extra > BUFFER_OPS_DISABLE_THRESHOLD
|
||||
)
|
||||
|
||||
# When force_no_splitk is set, skip the split-K decision and fall
|
||||
# through to the non-splitk kernel path below.
|
||||
use_splitk = not force_no_splitk
|
||||
|
||||
# Use Split-K for dual scope in these cases:
|
||||
# 1. Small batch sizes with h_q=128 or large topk to increase GPU parallelism
|
||||
# 2. Large topk (>= 2048) with medium/large batch sizes
|
||||
@@ -1665,7 +1676,7 @@ def fused_gather_attn_decode_dsv4_dual_scope(
|
||||
# For h_q > 64 (e.g. h_q=128), the non-splitk grid has very few blocks
|
||||
# in the H dimension, leading to low GPU utilization at medium batch sizes.
|
||||
use_splitk_for_large_hq = h_q > 64 and total_tokens > 8 and total_topk >= 256
|
||||
if (
|
||||
if use_splitk and (
|
||||
use_splitk_for_small_bs
|
||||
or use_splitk_for_h64_large_topk
|
||||
or use_splitk_for_large_topk
|
||||
|
||||
+83
-25
@@ -28,6 +28,7 @@ from .triton_mla_kernels_decode_dsv4 import (
|
||||
)
|
||||
from .triton_mla_kernels_decode_fused import (
|
||||
fused_gather_attn_decode_dsv4,
|
||||
fused_gather_attn_decode_dsv4_dual_scope,
|
||||
fused_gather_attn_decode_dsv4_dual_scope_low_overhead,
|
||||
)
|
||||
|
||||
@@ -56,14 +57,8 @@ def triton_sparse_attn_decode(
|
||||
def _should_use_fused_dual_scope(total_tokens: int, h_q: int, total_topk: int) -> bool:
|
||||
"""Determine whether to use fused kernel for dual-scope cases.
|
||||
|
||||
The fused kernel avoids allocating a large intermediate gathered_kv
|
||||
buffer and eliminates a separate gather kernel launch. However, for
|
||||
h_q > 64 with medium-to-large batch sizes and larger topk, the
|
||||
non-splitk fused kernel suffers from low GPU utilization (the grid
|
||||
has only cdiv(h_q, BLOCK_H) blocks in the H dimension). In those
|
||||
cases the fallback (separate gather + attention) can be faster on
|
||||
the GPU, though it incurs extra torch.empty() overhead in CUDA
|
||||
graphs.
|
||||
Returns True if the fused kernel (with splitk for small bs) should be used.
|
||||
For large batch sizes (>= 256), use _should_use_fused_nosplitk instead.
|
||||
|
||||
The thresholds below were determined empirically on MI355X (256 CUs).
|
||||
"""
|
||||
@@ -74,17 +69,52 @@ def _should_use_fused_dual_scope(total_tokens: int, h_q: int, total_topk: int) -
|
||||
if h_q <= 64 and total_topk >= 1024:
|
||||
return total_tokens <= 128
|
||||
# h_q > 64 (e.g. h_q=128 when q is padded to full n_heads).
|
||||
# For small topk (c128 layers, topk~192), fused always wins.
|
||||
# For larger topk (c4 layers, topk~640), fused wins at small bs
|
||||
# but the fallback catches up at bs>=16 due to better GPU utilization.
|
||||
# However, the fallback has 4 extra torch.empty() calls that add
|
||||
# ~30us CUDA-graph replay overhead, roughly cancelling the GPU gain.
|
||||
# So we route to fused for all practical batch sizes.
|
||||
if h_q > 64:
|
||||
return total_tokens <= 256
|
||||
if total_topk >= 400:
|
||||
return total_tokens <= 32
|
||||
else:
|
||||
return total_tokens <= 128
|
||||
return True
|
||||
|
||||
|
||||
def _should_use_fused_nosplitk(total_tokens: int, h_q: int, total_topk: int) -> bool:
|
||||
"""Determine whether to use the fused no-splitk kernel for large batches.
|
||||
|
||||
Kernel-level benchmarking on MI355X shows that for large batch sizes
|
||||
(total_tokens >= 256), the fused dual-scope kernel WITHOUT split-K
|
||||
is ~10% faster than the separate gather+attention path:
|
||||
|
||||
total_tokens=256: fused-noSK=169us vs separate=194us (14% faster)
|
||||
total_tokens=512: fused-noSK=350us vs separate=408us (14% faster)
|
||||
total_tokens=1024: fused-noSK=700us vs separate=777us (10% faster)
|
||||
total_tokens=4096: fused-noSK=2761us vs separate=3063us (10% faster)
|
||||
|
||||
The fused no-splitk kernel avoids:
|
||||
1. Materializing the large intermediate gathered_kv buffer
|
||||
2. The separate gather kernel launch
|
||||
3. The split-K combine overhead
|
||||
|
||||
For total_tokens < 256, the separate path is faster because the
|
||||
fused kernel has insufficient parallelism.
|
||||
|
||||
For extend (total_tokens >= 1024), the fused kernel always wins
|
||||
regardless of h_q or total_topk because:
|
||||
- The grid already has thousands of blocks (good GPU utilization)
|
||||
- It eliminates 1.5-5 GB gathered_kv buffer allocation
|
||||
- It eliminates 2x gather_dequant kernel launches (~414 us)
|
||||
- It avoids chunking that TP>1 configs require with the separate path
|
||||
"""
|
||||
if total_tokens >= 1024:
|
||||
return True
|
||||
if h_q <= 64:
|
||||
return False # Not benchmarked for h_q <= 64
|
||||
if total_topk < 200:
|
||||
return False # Small topk doesn't benefit
|
||||
# For h_q > 64 and total_topk >= 200:
|
||||
# Fused no-splitk wins for total_tokens >= 256
|
||||
return total_tokens >= 256
|
||||
|
||||
|
||||
def _triton_sparse_attn_decode_dsv4(
|
||||
q: torch.Tensor,
|
||||
kv_scope,
|
||||
@@ -135,7 +165,39 @@ def _triton_sparse_attn_decode_dsv4(
|
||||
topk_extra = extra_kv_scope.indices_in_kvcache.shape[-1]
|
||||
total_topk = topk_main + topk_extra
|
||||
|
||||
# Check if chunking needed (fall back to original implementation)
|
||||
# For large batch sizes, use fused no-splitk kernel (10% faster than separate).
|
||||
# This check is BEFORE the chunking check because the fused kernel does NOT
|
||||
# allocate the intermediate gathered_kv buffer, so buffer size limits don't apply.
|
||||
if _should_use_fused_nosplitk(total_tokens, h_q, total_topk):
|
||||
q_reshaped = q.reshape(total_tokens, h_q, d_qk).contiguous()
|
||||
|
||||
indices_main = kv_scope.indices_in_kvcache.reshape(
|
||||
total_tokens, topk_main
|
||||
).contiguous()
|
||||
|
||||
block_size_extra = extra_kv_scope.blocked_k.shape[1]
|
||||
indices_extra = extra_kv_scope.indices_in_kvcache.reshape(
|
||||
total_tokens, topk_extra
|
||||
).contiguous()
|
||||
|
||||
output, lse = fused_gather_attn_decode_dsv4_dual_scope(
|
||||
q_reshaped,
|
||||
kv_quantized_main,
|
||||
indices_main,
|
||||
block_size_main,
|
||||
extra_kv_scope.blocked_k_quantized,
|
||||
indices_extra,
|
||||
block_size_extra,
|
||||
sm_scale,
|
||||
topk_length_main=kv_scope.topk_length,
|
||||
topk_length_extra=extra_kv_scope.topk_length,
|
||||
attn_sink=attn_sink,
|
||||
s_q=s_q,
|
||||
force_no_splitk=True,
|
||||
)
|
||||
return output.view(b, s_q, h_q, d_v), lse.view(b, s_q, h_q).transpose(1, 2)
|
||||
|
||||
# Check if chunking needed for separate path (fall back to original implementation)
|
||||
token_ranges = compute_token_ranges(total_tokens, total_topk, d_qk)
|
||||
if len(token_ranges) > 1:
|
||||
from .triton_mla_kernels_decode_dsv4 import triton_sparse_attn_decode_dsv4
|
||||
@@ -146,20 +208,16 @@ def _triton_sparse_attn_decode_dsv4(
|
||||
|
||||
# Use fused dual-scope kernel with low-overhead buffer pool
|
||||
if _should_use_fused_dual_scope(total_tokens, h_q, total_topk):
|
||||
q_reshaped = q.reshape(total_tokens, h_q, d_qk)
|
||||
if not q_reshaped.is_contiguous():
|
||||
q_reshaped = q_reshaped.contiguous()
|
||||
q_reshaped = q.reshape(total_tokens, h_q, d_qk).contiguous()
|
||||
|
||||
indices_main = kv_scope.indices_in_kvcache.reshape(total_tokens, topk_main)
|
||||
if not indices_main.is_contiguous():
|
||||
indices_main = indices_main.contiguous()
|
||||
indices_main = kv_scope.indices_in_kvcache.reshape(
|
||||
total_tokens, topk_main
|
||||
).contiguous()
|
||||
|
||||
block_size_extra = extra_kv_scope.blocked_k.shape[1]
|
||||
indices_extra = extra_kv_scope.indices_in_kvcache.reshape(
|
||||
total_tokens, topk_extra
|
||||
)
|
||||
if not indices_extra.is_contiguous():
|
||||
indices_extra = indices_extra.contiguous()
|
||||
).contiguous()
|
||||
|
||||
output, lse = fused_gather_attn_decode_dsv4_dual_scope_low_overhead(
|
||||
q_reshaped,
|
||||
|
||||
@@ -19,6 +19,7 @@ from sglang.srt.server_args import get_global_server_args
|
||||
from sglang.srt.utils.common import (
|
||||
get_bool_env_var,
|
||||
is_cuda,
|
||||
is_hip,
|
||||
is_musa,
|
||||
is_npu,
|
||||
)
|
||||
@@ -41,6 +42,9 @@ if is_musa():
|
||||
top_p_renorm_prob,
|
||||
)
|
||||
|
||||
_use_aiter = get_bool_env_var("SGLANG_USE_AITER") and is_hip()
|
||||
if _use_aiter:
|
||||
from aiter import greedy_sample as _aiter_greedy_sample
|
||||
|
||||
if is_npu():
|
||||
import torch_npu
|
||||
@@ -106,8 +110,13 @@ class Sampler(nn.Module):
|
||||
logits = self._preprocess_logits(logits, sampling_info)
|
||||
|
||||
if sampling_info.is_all_greedy:
|
||||
# Use torch.argmax if all requests use greedy sampling
|
||||
batch_next_token_ids = torch.argmax(logits, -1)
|
||||
if _use_aiter:
|
||||
batch_next_token_ids = torch.empty(
|
||||
logits.shape[0], device=logits.device, dtype=torch.int32
|
||||
)
|
||||
_aiter_greedy_sample(batch_next_token_ids, logits)
|
||||
else:
|
||||
batch_next_token_ids = torch.argmax(logits, -1)
|
||||
if return_logprob:
|
||||
original_logprobs = logprobs = torch.nn.functional.log_softmax(
|
||||
logits, dim=-1
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# AMD-specific DeepSeek common model helpers.
|
||||
@@ -0,0 +1,158 @@
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
import triton
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_FUSED_HC_POST_PRE_M_THRESHOLD = 64
|
||||
_FUSED_HC_POST_PRE_CACHE: dict[tuple, dict[str, torch.Tensor]] = {}
|
||||
_TRITON_MHC_POST_PRE_OPS = None
|
||||
_TRITON_MHC_POST_PRE_RUNTIME_DISABLED = False
|
||||
|
||||
|
||||
def _get_triton_mhc_post_pre_ops():
|
||||
global _TRITON_MHC_POST_PRE_OPS
|
||||
|
||||
if _TRITON_MHC_POST_PRE_OPS is not None:
|
||||
return _TRITON_MHC_POST_PRE_OPS
|
||||
|
||||
try:
|
||||
from aiter.ops.triton.fusions.mhc import mhc_post_pre
|
||||
from aiter.ops.triton.utils.mhc_config_utils import get_mhc_config
|
||||
except Exception as err:
|
||||
logger.warning(
|
||||
"Triton fused mHC (mhc_post_pre) is unavailable, falling back: %s", err
|
||||
)
|
||||
return None
|
||||
|
||||
_TRITON_MHC_POST_PRE_OPS = (mhc_post_pre, get_mhc_config)
|
||||
return _TRITON_MHC_POST_PRE_OPS
|
||||
|
||||
|
||||
def _get_fused_hc_post_pre_buffers(
|
||||
num_tokens: int,
|
||||
hidden_size: int,
|
||||
hc_mult: int,
|
||||
dtype: torch.dtype,
|
||||
device: torch.device,
|
||||
) -> Optional[dict[str, torch.Tensor]]:
|
||||
ops = _get_triton_mhc_post_pre_ops()
|
||||
if ops is None:
|
||||
return None
|
||||
_, get_mhc_config = ops
|
||||
|
||||
key = (num_tokens, hidden_size, hc_mult, dtype, device.type, device.index)
|
||||
bufs = _FUSED_HC_POST_PRE_CACHE.get(key)
|
||||
if bufs is not None:
|
||||
return bufs
|
||||
|
||||
try:
|
||||
cfg, _ = get_mhc_config("MHC_FUSED", num_tokens, hidden_size, mode="sinkhorn")
|
||||
except Exception as err:
|
||||
logger.warning("Failed to initialize fused mHC config, falling back: %s", err)
|
||||
return None
|
||||
|
||||
n_total = 2 * hc_mult + hc_mult * hc_mult
|
||||
k_dim = hc_mult * hidden_size
|
||||
block_k = cfg.get("BLOCK_K", min(512, triton.next_power_of_2(k_dim)))
|
||||
block_k = min(block_k, triton.next_power_of_2(k_dim))
|
||||
block_c_split = max(block_k // hc_mult, 1)
|
||||
num_ksplit = triton.cdiv(hidden_size, block_c_split)
|
||||
|
||||
bufs = {
|
||||
"residual_out": torch.empty(
|
||||
num_tokens, hc_mult, hidden_size, dtype=dtype, device=device
|
||||
),
|
||||
"layer_input_out": torch.empty(
|
||||
num_tokens, hidden_size, dtype=dtype, device=device
|
||||
),
|
||||
"h_post": torch.empty(num_tokens, hc_mult, dtype=torch.float32, device=device),
|
||||
"h_res": torch.empty(
|
||||
num_tokens, hc_mult, hc_mult, dtype=torch.float32, device=device
|
||||
),
|
||||
"acc_partial": torch.empty(
|
||||
num_ksplit, num_tokens, n_total, dtype=torch.float32, device=device
|
||||
),
|
||||
"acc_sq_partial": torch.empty(
|
||||
num_ksplit, num_tokens, dtype=torch.float32, device=device
|
||||
),
|
||||
}
|
||||
_FUSED_HC_POST_PRE_CACHE[key] = bufs
|
||||
return bufs
|
||||
|
||||
|
||||
def try_fused_hc_post_pre(
|
||||
x: torch.Tensor,
|
||||
residual: torch.Tensor,
|
||||
post: torch.Tensor,
|
||||
comb: torch.Tensor,
|
||||
hc_fn_t: torch.Tensor,
|
||||
hc_scale: torch.Tensor,
|
||||
hc_base: torch.Tensor,
|
||||
hc_mult: int,
|
||||
norm_eps: float,
|
||||
hc_eps: float,
|
||||
hc_post_mult: float,
|
||||
sinkhorn_iters: int,
|
||||
is_gfx95_supported: bool,
|
||||
) -> Optional[Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, bool]]:
|
||||
global _TRITON_MHC_POST_PRE_RUNTIME_DISABLED
|
||||
|
||||
if (
|
||||
_TRITON_MHC_POST_PRE_RUNTIME_DISABLED
|
||||
or not envs.SGLANG_OPT_USE_TRITON_FUSED_MHC.get()
|
||||
or not is_gfx95_supported
|
||||
or x.shape[0] == 0
|
||||
or x.shape[0] > _FUSED_HC_POST_PRE_M_THRESHOLD
|
||||
or x.dim() != 2
|
||||
or residual.dim() != 3
|
||||
):
|
||||
return None
|
||||
|
||||
ops = _get_triton_mhc_post_pre_ops()
|
||||
if ops is None:
|
||||
return None
|
||||
mhc_post_pre, _ = ops
|
||||
|
||||
bufs = _get_fused_hc_post_pre_buffers(
|
||||
x.shape[0], x.shape[1], hc_mult, residual.dtype, x.device
|
||||
)
|
||||
if bufs is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
_, _, layer_input_out, new_residual = mhc_post_pre(
|
||||
x,
|
||||
residual,
|
||||
post,
|
||||
comb,
|
||||
hc_fn_t,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
hc_mult,
|
||||
norm_eps,
|
||||
hc_eps,
|
||||
hc_post_mult,
|
||||
sinkhorn_iters,
|
||||
# Match sglang's exp-domain asymmetric Sinkhorn used in hc_pre.
|
||||
asymmetric_exp_domain=True,
|
||||
hc_sinkhorn_eps=hc_eps,
|
||||
residual_out=bufs["residual_out"],
|
||||
h_post=bufs["h_post"],
|
||||
h_res=bufs["h_res"],
|
||||
layer_input_out=bufs["layer_input_out"],
|
||||
acc_partial=bufs["acc_partial"],
|
||||
acc_sq_partial=bufs["acc_sq_partial"],
|
||||
)
|
||||
except Exception as err:
|
||||
logger.warning(
|
||||
"Triton fused mHC kernel failed, disabling fallback path: %s", err
|
||||
)
|
||||
_TRITON_MHC_POST_PRE_RUNTIME_DISABLED = True
|
||||
return None
|
||||
|
||||
return new_residual, layer_input_out, bufs["h_post"], bufs["h_res"], False
|
||||
@@ -87,6 +87,9 @@ from sglang.srt.model_executor.forward_context import (
|
||||
from sglang.srt.model_loader.utils import maybe_executor_submit, should_async_load
|
||||
from sglang.srt.model_loader.weight_utils import default_weight_loader
|
||||
from sglang.srt.models.dbrx import ReplicatedLinear
|
||||
from sglang.srt.models.deepseek_common.amd.deepseek_v4_fused_mhc import (
|
||||
try_fused_hc_post_pre,
|
||||
)
|
||||
from sglang.srt.models.deepseek_v2 import ParallelLMHead, _is_cuda, _is_hip, _is_npu
|
||||
|
||||
if not _is_hip:
|
||||
@@ -133,6 +136,28 @@ def _fused_rmsnorm_fp8_quant(hidden_states, weight, eps):
|
||||
return x_quant, x_bf16
|
||||
|
||||
|
||||
_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
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend import (
|
||||
DeepseekV4AttnBackend,
|
||||
@@ -319,7 +344,7 @@ class MQALayer(nn.Module):
|
||||
)
|
||||
|
||||
self.attn_sink = nn.Parameter(torch.empty(self.n_heads, dtype=torch.float32))
|
||||
self.fuse_wqa_wkv = not _is_hip and envs.SGLANG_OPT_FUSE_WQA_WKV.get()
|
||||
self.fuse_wqa_wkv = envs.SGLANG_OPT_FUSE_WQA_WKV.get()
|
||||
if self.fuse_wqa_wkv:
|
||||
self.wqkv_a = ReplicatedLinear(
|
||||
self.hidden_size,
|
||||
@@ -641,6 +666,7 @@ class MQALayer(nn.Module):
|
||||
x=x,
|
||||
q_lora=q_lora,
|
||||
forward_batch=forward_batch,
|
||||
attn_backend=attn_backend,
|
||||
skip_compressor=True,
|
||||
)
|
||||
elif self.compressor is not None:
|
||||
@@ -795,6 +821,7 @@ class MQALayer(nn.Module):
|
||||
and get_is_capture_mode()
|
||||
and x.shape[0] <= self._multi_stream_bs_limit
|
||||
and not (self.dsa_enable_prefill_cp and dsa_use_prefill_cp(forward_batch))
|
||||
and not (_is_hip and self.compressor is None)
|
||||
)
|
||||
|
||||
tp_slice, q_padded, q_out = slice(None), None, None
|
||||
@@ -1187,15 +1214,33 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
x_quant=x_quant,
|
||||
)
|
||||
|
||||
hidden_states = self.hc_post(hidden_states, residual, post, comb)
|
||||
residual = hidden_states
|
||||
hidden_states, post, comb, norm_fused = self.hc_pre(
|
||||
fused_mhc = try_fused_hc_post_pre(
|
||||
hidden_states,
|
||||
self.hc_ffn_fn,
|
||||
residual,
|
||||
post,
|
||||
comb,
|
||||
self.hc_ffn_fn.T,
|
||||
self.hc_ffn_scale,
|
||||
self.hc_ffn_base,
|
||||
norm=self.post_attention_layernorm,
|
||||
self.hc_mult,
|
||||
self.rms_norm_eps,
|
||||
self.hc_eps,
|
||||
2.0,
|
||||
self.hc_sinkhorn_iters,
|
||||
_is_gfx95_supported,
|
||||
)
|
||||
if fused_mhc is not None:
|
||||
residual, hidden_states, post, comb, norm_fused = fused_mhc
|
||||
else:
|
||||
hidden_states = self.hc_post(hidden_states, residual, post, comb)
|
||||
residual = hidden_states # [n, hc, d]
|
||||
hidden_states, post, comb, norm_fused = self.hc_pre(
|
||||
hidden_states,
|
||||
self.hc_ffn_fn,
|
||||
self.hc_ffn_scale,
|
||||
self.hc_ffn_base,
|
||||
norm=self.post_attention_layernorm,
|
||||
) # -> [n, d]
|
||||
if not norm_fused:
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
|
||||
@@ -1759,7 +1804,7 @@ class DeepseekV4ForCausalLM(nn.Module):
|
||||
cache_compressor_weight = {}
|
||||
COMPRESSOR_PART = ".compressor.w"
|
||||
|
||||
fuse_wqa_wkv = not _is_hip and envs.SGLANG_OPT_FUSE_WQA_WKV.get()
|
||||
fuse_wqa_wkv = envs.SGLANG_OPT_FUSE_WQA_WKV.get()
|
||||
cache_wqkv_a_weight: dict[str, dict[str, torch.Tensor]] = {}
|
||||
|
||||
def auto_weight_loader(module):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
|
||||
from sglang.srt.server_args import ServerArgs, get_global_server_args
|
||||
from sglang.srt.utils.common import is_blackwell, is_musa
|
||||
from sglang.srt.utils.common import is_blackwell, is_hip, is_musa
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -226,9 +226,14 @@ class DraftBackendFactory:
|
||||
)
|
||||
|
||||
def _create_dsv4_decode_backend(self):
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend import (
|
||||
DeepseekV4MultiStepBackend,
|
||||
)
|
||||
if is_hip():
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend_hip_radix import (
|
||||
DeepseekV4MultiStepBackend,
|
||||
)
|
||||
else:
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend import (
|
||||
DeepseekV4MultiStepBackend,
|
||||
)
|
||||
|
||||
return DeepseekV4MultiStepBackend(
|
||||
self.draft_model_runner, self.topk, self.speculative_num_steps
|
||||
@@ -318,6 +323,14 @@ class DraftBackendFactory:
|
||||
return None
|
||||
|
||||
def _create_dsv4_prefill_backend(self):
|
||||
if is_hip():
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend_hip_radix import (
|
||||
DeepseekV4HipRadixBackend,
|
||||
)
|
||||
|
||||
return DeepseekV4HipRadixBackend(
|
||||
self.draft_model_runner, skip_prefill=False
|
||||
)
|
||||
from sglang.srt.layers.attention.deepseek_v4_backend import (
|
||||
DeepseekV4AttnBackend,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user