Optimize MHC pipeline: DeepGemm, fused norm, fused hc_head (#24775)
Co-authored-by: Cheng Wan <chwan@rice.edu> Co-authored-by: Chunan Zeng <zcnrex@gmail.com>
This commit is contained in:
co-authored by
Cheng Wan
Chunan Zeng
parent
bd0aa22309
commit
2f06867128
+320
-65
@@ -7,6 +7,7 @@ import tilelang.language as T
|
||||
import torch
|
||||
|
||||
from sglang.jit_kernel.utils import is_arch_support_pdl
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.attention.nsa.utils import is_nsa_prefill_cp_round_robin_split
|
||||
from sglang.srt.layers.utils.common import strict_contiguous
|
||||
|
||||
@@ -138,12 +139,15 @@ def mhc_pre_big_fuse_tilelang(
|
||||
sinkhorn_repeat: int,
|
||||
n_splits: int = 16,
|
||||
hc_mult: int = 4,
|
||||
gemm_last_dim: int = -1,
|
||||
):
|
||||
num_tokens = T.dynamic("num_tokens")
|
||||
hc_mult3 = hc_mult * (2 + hc_mult)
|
||||
if gemm_last_dim < 0:
|
||||
gemm_last_dim = hc_mult3
|
||||
hidden_block = math.gcd(512, hidden_size)
|
||||
|
||||
gemm_out_mul: T.Tensor[[n_splits, num_tokens, hc_mult3], T.float32]
|
||||
gemm_out_mul: T.Tensor[[n_splits, num_tokens, gemm_last_dim], T.float32]
|
||||
gemm_out_sqrsum: T.Tensor[[n_splits, num_tokens], T.float32]
|
||||
hc_scale: T.Tensor[[3], T.float32]
|
||||
hc_base: T.Tensor[[hc_mult3], T.float32]
|
||||
@@ -438,6 +442,186 @@ def mhc_pre_gemm_sqrsum_splitk_kernel(
|
||||
)
|
||||
|
||||
|
||||
def _compute_num_split_for_mhc_pre(num_tokens: int, hc_hidden_size: int) -> int:
|
||||
block_m, block_k = 64, 64
|
||||
grid_size = (num_tokens + block_m - 1) // block_m
|
||||
num_block_k = (hc_hidden_size + block_k - 1) // block_k
|
||||
n_sms = torch.cuda.get_device_properties(0).multi_processor_count
|
||||
return max(1, min(n_sms // max(grid_size, 1), num_block_k // 4))
|
||||
|
||||
|
||||
@tilelang.jit(
|
||||
pass_configs={
|
||||
tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
|
||||
tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
|
||||
tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10,
|
||||
},
|
||||
)
|
||||
def mhc_pre_big_fuse_with_norm_tilelang(
|
||||
gemm_out_mul,
|
||||
gemm_out_sqrsum,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
residual,
|
||||
post_mix,
|
||||
comb_mix,
|
||||
layer_input,
|
||||
norm_weight,
|
||||
hidden_size: int,
|
||||
rms_eps: float,
|
||||
hc_pre_eps: float,
|
||||
hc_sinkhorn_eps: float,
|
||||
hc_post_mult_value: float,
|
||||
sinkhorn_repeat: int,
|
||||
norm_eps: float,
|
||||
n_splits: int = 16,
|
||||
hc_mult: int = 4,
|
||||
gemm_last_dim: int = -1,
|
||||
):
|
||||
"""Fused mhc_pre big_fuse + RMSNorm of layer_input.
|
||||
|
||||
Identical to mhc_pre_big_fuse_tilelang for the (post_mix, comb_mix) path.
|
||||
For the layer_input path, the weighted-sum result is stashed in shared
|
||||
memory while accumulating sum_sq, then a second pipelined sweep applies
|
||||
rsqrt(sum_sq/D + norm_eps) * norm_weight before writing to HBM.
|
||||
"""
|
||||
num_tokens = T.dynamic("num_tokens")
|
||||
hc_mult3 = hc_mult * (2 + hc_mult)
|
||||
if gemm_last_dim < 0:
|
||||
gemm_last_dim = hc_mult3
|
||||
hidden_block = math.gcd(1024, hidden_size)
|
||||
|
||||
gemm_out_mul: T.Tensor[[n_splits, num_tokens, gemm_last_dim], T.float32]
|
||||
gemm_out_sqrsum: T.Tensor[[n_splits, num_tokens], T.float32]
|
||||
hc_scale: T.Tensor[[3], T.float32]
|
||||
hc_base: T.Tensor[[hc_mult3], T.float32]
|
||||
residual: T.Tensor[[num_tokens, hc_mult, hidden_size], T.bfloat16]
|
||||
post_mix: T.Tensor[[num_tokens, hc_mult], T.float32]
|
||||
comb_mix: T.Tensor[[num_tokens, hc_mult * hc_mult], T.float32]
|
||||
layer_input: T.Tensor[[num_tokens, hidden_size], T.bfloat16]
|
||||
norm_weight: T.Tensor[[hidden_size], T.bfloat16]
|
||||
|
||||
ENABLE_PDL = is_arch_support_pdl()
|
||||
with T.Kernel(num_tokens, threads=96) as i:
|
||||
rms = T.alloc_fragment(1, T.float32)
|
||||
mixes = T.alloc_fragment(hc_mult3, T.float32)
|
||||
T.clear(mixes)
|
||||
rms[0] = 0
|
||||
|
||||
if ENABLE_PDL:
|
||||
T.pdl_sync()
|
||||
|
||||
for i_split in T.serial(n_splits):
|
||||
rms[0] += gemm_out_sqrsum[i_split, i]
|
||||
rms[0] = T.rsqrt(rms[0] / (hc_mult * hidden_size) + rms_eps)
|
||||
for j in T.Parallel(hc_mult3):
|
||||
mixes[j] = 0
|
||||
for i_split in T.serial(n_splits):
|
||||
mixes[j] += gemm_out_mul[i_split, i, j]
|
||||
mixes[j] *= rms[0]
|
||||
mixes_shared = T.alloc_shared(hc_mult3, T.float32)
|
||||
T.copy(mixes, mixes_shared)
|
||||
|
||||
if T.get_thread_binding() < 32:
|
||||
cm = T.alloc_fragment((hc_mult, hc_mult), T.float32)
|
||||
for j in T.Parallel(hc_mult):
|
||||
post_mix[i, j] = (
|
||||
T.sigmoid(
|
||||
mixes_shared[j + hc_mult] * hc_scale[1] + hc_base[j + hc_mult]
|
||||
)
|
||||
* hc_post_mult_value
|
||||
)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = (
|
||||
mixes_shared[j * hc_mult + k + hc_mult * 2] * hc_scale[2]
|
||||
+ hc_base[j * hc_mult + k + hc_mult * 2]
|
||||
)
|
||||
|
||||
row_sum = T.alloc_fragment(hc_mult, T.float32)
|
||||
col_sum = T.alloc_fragment(hc_mult, T.float32)
|
||||
|
||||
row_max = T.alloc_fragment(hc_mult, T.float32)
|
||||
T.reduce_max(cm, row_max, dim=1)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = T.exp(cm[j, k] - row_max[j])
|
||||
T.reduce_sum(cm, row_sum, dim=1)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = cm[j, k] / row_sum[j] + hc_sinkhorn_eps
|
||||
|
||||
T.reduce_sum(cm, col_sum, dim=0)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = cm[j, k] / (col_sum[k] + hc_sinkhorn_eps)
|
||||
|
||||
for _ in T.serial(sinkhorn_repeat - 1):
|
||||
T.reduce_sum(cm, row_sum, dim=1)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = cm[j, k] / (row_sum[j] + hc_sinkhorn_eps)
|
||||
|
||||
T.reduce_sum(cm, col_sum, dim=0)
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
cm[j, k] = cm[j, k] / (col_sum[k] + hc_sinkhorn_eps)
|
||||
|
||||
for j, k in T.Parallel(hc_mult, hc_mult):
|
||||
comb_mix[i, j * hc_mult + k] = cm[j, k]
|
||||
else:
|
||||
pre_mix_shared = T.alloc_shared(hc_mult, T.float32)
|
||||
for j in T.Parallel(hc_mult):
|
||||
pre_mix_shared[j] = (
|
||||
T.sigmoid(
|
||||
mixes_shared[j] * hc_scale[0] + hc_base[j],
|
||||
)
|
||||
+ hc_pre_eps
|
||||
)
|
||||
|
||||
# Stash unnormalized weighted-sum output in shared memory as bf16
|
||||
# (matches the rounding the reference path does when RMSNorm reads bf16).
|
||||
output_shared = T.alloc_shared(hidden_size, T.bfloat16)
|
||||
sumsq_per_pos = T.alloc_fragment(hidden_block, T.float32)
|
||||
T.clear(sumsq_per_pos)
|
||||
|
||||
for i0_h in T.Pipelined(hidden_size // hidden_block, num_stages=3):
|
||||
xs = T.alloc_shared((hc_mult, hidden_block), T.bfloat16)
|
||||
xl = T.alloc_fragment((hc_mult, hidden_block), T.float32)
|
||||
T.copy(residual[i, 0, i0_h * hidden_block], xs)
|
||||
T.copy(xs, xl)
|
||||
|
||||
ol = T.alloc_fragment(hidden_block, T.float32)
|
||||
T.clear(ol)
|
||||
|
||||
for i_hc in T.serial(hc_mult):
|
||||
pre = pre_mix_shared[i_hc]
|
||||
for i1_h in T.Parallel(hidden_block):
|
||||
ol[i1_h] += pre * xl[i_hc, i1_h]
|
||||
|
||||
for i1_h in T.Parallel(hidden_block):
|
||||
sumsq_per_pos[i1_h] += ol[i1_h] * ol[i1_h]
|
||||
output_shared[i0_h * hidden_block + i1_h] = T.bfloat16(ol[i1_h])
|
||||
|
||||
sumsq = T.alloc_fragment(1, T.float32)
|
||||
T.reduce_sum(sumsq_per_pos, sumsq, dim=0)
|
||||
rsqrt_norm = T.alloc_fragment(1, T.float32)
|
||||
rsqrt_norm[0] = T.rsqrt(sumsq[0] / hidden_size + norm_eps)
|
||||
|
||||
for i0_h in T.Pipelined(hidden_size // hidden_block, num_stages=2):
|
||||
w_shared = T.alloc_shared(hidden_block, T.bfloat16)
|
||||
w_local = T.alloc_fragment(hidden_block, T.float32)
|
||||
T.copy(norm_weight[i0_h * hidden_block], w_shared)
|
||||
T.copy(w_shared, w_local)
|
||||
|
||||
ol = T.alloc_fragment(hidden_block, T.float32)
|
||||
for i1_h in T.Parallel(hidden_block):
|
||||
ol[i1_h] = (
|
||||
output_shared[i0_h * hidden_block + i1_h]
|
||||
* rsqrt_norm[0]
|
||||
* w_local[i1_h]
|
||||
)
|
||||
|
||||
T.copy(ol, layer_input[i, i0_h * hidden_block])
|
||||
|
||||
if ENABLE_PDL:
|
||||
T.pdl_trigger()
|
||||
|
||||
|
||||
def mhc_pre(
|
||||
residual: torch.Tensor,
|
||||
fn: torch.Tensor,
|
||||
@@ -450,6 +634,9 @@ def mhc_pre(
|
||||
sinkhorn_repeat: int,
|
||||
n_splits: int = 1,
|
||||
n_splits_pre: int = 32,
|
||||
*,
|
||||
norm_weight: torch.Tensor | None = None,
|
||||
norm_eps: float | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
|
||||
assert residual.dtype == torch.bfloat16
|
||||
@@ -484,77 +671,145 @@ def mhc_pre(
|
||||
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
|
||||
)
|
||||
|
||||
gemm_out_mul = torch.empty(
|
||||
n_splits, num_tokens, hc_mult3, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
gemm_out_sqrsum = torch.empty(
|
||||
n_splits, num_tokens, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
if envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get():
|
||||
import deep_gemm
|
||||
|
||||
if num_tokens <= 2048:
|
||||
assert n_splits == 1
|
||||
if hc_hidden_size == 16384:
|
||||
hidden_block = 256
|
||||
elif hc_hidden_size == 28672:
|
||||
hidden_block = 128
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"mhc_pre splitk kernel only supports hc_hidden_size in {{16384, 28672}}, "
|
||||
f"got {hc_hidden_size}"
|
||||
)
|
||||
kernel_0, kernel_1 = mhc_pre_gemm_sqrsum_splitk_kernel(
|
||||
hc_mult3,
|
||||
hc_hidden_size,
|
||||
split_k=n_splits_pre,
|
||||
token_block=32,
|
||||
hidden_block=hidden_block,
|
||||
n_splits = _compute_num_split_for_mhc_pre(num_tokens, hc_hidden_size)
|
||||
|
||||
gemm_out_mul = torch.empty(
|
||||
n_splits, num_tokens, hc_mult3, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
partial_out = gemm_out_mul.new_empty(n_splits_pre, num_tokens, 32)
|
||||
partial_sqrsum = gemm_out_sqrsum.new_empty(n_splits_pre, num_tokens)
|
||||
kernel_0(
|
||||
gemm_out_sqrsum = torch.empty(
|
||||
n_splits, num_tokens, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
|
||||
deep_gemm.tf32_hc_prenorm_gemm(
|
||||
residual_flat.view(num_tokens, hc_hidden_size),
|
||||
fn_flat,
|
||||
partial_out,
|
||||
partial_sqrsum,
|
||||
gemm_out_mul,
|
||||
gemm_out_sqrsum,
|
||||
num_splits=n_splits,
|
||||
)
|
||||
kernel_1(
|
||||
partial_out,
|
||||
partial_sqrsum,
|
||||
gemm_out_mul.squeeze(0),
|
||||
gemm_out_sqrsum.squeeze(0),
|
||||
)
|
||||
del partial_out, partial_sqrsum
|
||||
gemm_last_dim = hc_mult3
|
||||
big_fuse_n_splits = n_splits
|
||||
else:
|
||||
assert (
|
||||
n_splits == 1
|
||||
), "The simple TileLang version gemm_sqrsum doesn't support split-k"
|
||||
mhc_pre_gemm_sqrsum_tilelang(
|
||||
residual_flat.view(num_tokens, hc_mult * hidden_size),
|
||||
fn_flat,
|
||||
gemm_out_mul.squeeze(0),
|
||||
gemm_out_sqrsum.squeeze(0),
|
||||
hc_mult3,
|
||||
hc_mult * hidden_size,
|
||||
)
|
||||
if num_tokens <= 2048:
|
||||
assert n_splits == 1
|
||||
if hc_hidden_size == 16384:
|
||||
hidden_block = 256
|
||||
elif hc_hidden_size == 28672:
|
||||
hidden_block = 128
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"mhc_pre splitk kernel only supports hc_hidden_size in {{16384, 28672}}, "
|
||||
f"got {hc_hidden_size}"
|
||||
)
|
||||
kernel_0, _ = mhc_pre_gemm_sqrsum_splitk_kernel(
|
||||
hc_mult3,
|
||||
hc_hidden_size,
|
||||
split_k=n_splits_pre,
|
||||
token_block=32,
|
||||
hidden_block=hidden_block,
|
||||
)
|
||||
partial_out = torch.empty(
|
||||
n_splits_pre,
|
||||
num_tokens,
|
||||
32,
|
||||
dtype=torch.float32,
|
||||
device=residual.device,
|
||||
)
|
||||
partial_sqrsum = torch.empty(
|
||||
n_splits_pre, num_tokens, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
kernel_0(
|
||||
residual_flat.view(num_tokens, hc_hidden_size),
|
||||
fn_flat,
|
||||
partial_out,
|
||||
partial_sqrsum,
|
||||
)
|
||||
# Stage_1 reduction is folded into big_fuse below; skip launching it.
|
||||
gemm_out_mul = partial_out
|
||||
gemm_out_sqrsum = partial_sqrsum
|
||||
gemm_last_dim = 32
|
||||
big_fuse_n_splits = n_splits_pre
|
||||
else:
|
||||
gemm_out_mul = torch.empty(
|
||||
n_splits,
|
||||
num_tokens,
|
||||
hc_mult3,
|
||||
dtype=torch.float32,
|
||||
device=residual.device,
|
||||
)
|
||||
gemm_out_sqrsum = torch.empty(
|
||||
n_splits, num_tokens, dtype=torch.float32, device=residual.device
|
||||
)
|
||||
assert (
|
||||
n_splits == 1
|
||||
), "The simple TileLang version gemm_sqrsum doesn't support split-k"
|
||||
mhc_pre_gemm_sqrsum_tilelang(
|
||||
residual_flat.view(num_tokens, hc_mult * hidden_size),
|
||||
fn_flat,
|
||||
gemm_out_mul.squeeze(0),
|
||||
gemm_out_sqrsum.squeeze(0),
|
||||
hc_mult3,
|
||||
hc_mult * hidden_size,
|
||||
)
|
||||
gemm_last_dim = hc_mult3
|
||||
big_fuse_n_splits = n_splits
|
||||
|
||||
mhc_pre_big_fuse_tilelang(
|
||||
gemm_out_mul,
|
||||
gemm_out_sqrsum,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
residual_flat,
|
||||
post_mix,
|
||||
comb_mix,
|
||||
layer_input,
|
||||
hidden_size,
|
||||
rms_eps,
|
||||
hc_pre_eps,
|
||||
hc_sinkhorn_eps,
|
||||
hc_post_mult_value,
|
||||
sinkhorn_repeat,
|
||||
n_splits,
|
||||
hc_mult,
|
||||
)
|
||||
if norm_weight is not None:
|
||||
assert norm_eps is not None, "norm_eps required when norm_weight is provided"
|
||||
assert norm_weight.shape == (
|
||||
hidden_size,
|
||||
), f"norm_weight shape {tuple(norm_weight.shape)} != (hidden_size={hidden_size},)"
|
||||
norm_weight_bf = (
|
||||
norm_weight.bfloat16()
|
||||
if norm_weight.dtype != torch.bfloat16
|
||||
else norm_weight
|
||||
)
|
||||
if not norm_weight_bf.is_contiguous():
|
||||
norm_weight_bf = norm_weight_bf.contiguous()
|
||||
mhc_pre_big_fuse_with_norm_tilelang(
|
||||
gemm_out_mul,
|
||||
gemm_out_sqrsum,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
residual_flat,
|
||||
post_mix,
|
||||
comb_mix,
|
||||
layer_input,
|
||||
norm_weight_bf,
|
||||
hidden_size,
|
||||
rms_eps,
|
||||
hc_pre_eps,
|
||||
hc_sinkhorn_eps,
|
||||
hc_post_mult_value,
|
||||
sinkhorn_repeat,
|
||||
norm_eps,
|
||||
big_fuse_n_splits,
|
||||
hc_mult,
|
||||
gemm_last_dim,
|
||||
)
|
||||
else:
|
||||
mhc_pre_big_fuse_tilelang(
|
||||
gemm_out_mul,
|
||||
gemm_out_sqrsum,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
residual_flat,
|
||||
post_mix,
|
||||
comb_mix,
|
||||
layer_input,
|
||||
hidden_size,
|
||||
rms_eps,
|
||||
hc_pre_eps,
|
||||
hc_sinkhorn_eps,
|
||||
hc_post_mult_value,
|
||||
sinkhorn_repeat,
|
||||
big_fuse_n_splits,
|
||||
hc_mult,
|
||||
gemm_last_dim,
|
||||
)
|
||||
|
||||
post_mix = post_mix.view(*outer_shape, hc_mult, 1)
|
||||
comb_mix = comb_mix.view(*outer_shape, hc_mult, hc_mult)
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
"""Fused triton kernel for the DSV4 hc_head LM-head mixer.
|
||||
|
||||
Reference torch implementation (deepseek_v4.py DeepseekV4Model.hc_head):
|
||||
|
||||
shape, dtype = x.size(), x.dtype
|
||||
x = x.flatten(1).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=1)
|
||||
return y.to(dtype)
|
||||
|
||||
Shapes (DSV4-Pro, hc_mult=4, hidden_size=7168 typical):
|
||||
x : (T, hc_mult, hidden_size) bf16
|
||||
hc_fn : (hc_mult, hc_mult * hidden_size) fp32
|
||||
scale : (1,) fp32
|
||||
base : (hc_mult,) fp32
|
||||
out y : (T, hidden_size) bf16
|
||||
|
||||
This is a one-shot LM-head op (fires once per forward on the last PP rank), so
|
||||
we use a 1-CTA-per-token design that does two passes over x without split-K.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _hc_head_kernel(
|
||||
x_ptr,
|
||||
fn_ptr,
|
||||
scale_ptr,
|
||||
base_ptr,
|
||||
y_ptr,
|
||||
hidden_size: tl.constexpr,
|
||||
HC_MULT: tl.constexpr,
|
||||
K_TOTAL: tl.constexpr,
|
||||
BLOCK_K: tl.constexpr,
|
||||
BLOCK_D: tl.constexpr,
|
||||
norm_eps: tl.constexpr,
|
||||
hc_eps: tl.constexpr,
|
||||
):
|
||||
pid = tl.program_id(0).to(tl.int64)
|
||||
|
||||
# ---------- Pass 1: sum_sq over flattened K dim, plus hc_mult inner products ----------
|
||||
sumsq = tl.zeros((), dtype=tl.float32)
|
||||
mix = tl.zeros((HC_MULT,), dtype=tl.float32)
|
||||
|
||||
x_row = x_ptr + pid * K_TOTAL
|
||||
m_idx = tl.arange(0, HC_MULT)
|
||||
|
||||
for k_off in tl.range(0, K_TOTAL, BLOCK_K):
|
||||
k_offs = k_off + tl.arange(0, BLOCK_K)
|
||||
k_mask = k_offs < K_TOTAL
|
||||
x_tile = tl.load(x_row + k_offs, mask=k_mask, other=0.0).to(tl.float32)
|
||||
|
||||
sumsq += tl.sum(x_tile * x_tile, axis=0)
|
||||
|
||||
fn_offs = m_idx[:, None] * K_TOTAL + k_offs[None, :]
|
||||
fn_mask = (m_idx[:, None] < HC_MULT) & k_mask[None, :]
|
||||
fn_tile = tl.load(fn_ptr + fn_offs, mask=fn_mask, other=0.0)
|
||||
mix += tl.sum(fn_tile * x_tile[None, :], axis=1)
|
||||
|
||||
rsqrt = tl.rsqrt(sumsq / K_TOTAL + norm_eps)
|
||||
scale_v = tl.load(scale_ptr).to(tl.float32)
|
||||
base_v = tl.load(base_ptr + m_idx).to(tl.float32)
|
||||
|
||||
# pre[m] = sigmoid(mix[m] * rsqrt * scale + base[m]) + hc_eps
|
||||
pre = tl.sigmoid(mix * rsqrt * scale_v + base_v) + hc_eps
|
||||
|
||||
# ---------- Pass 2: y[d] = sum_m pre[m] * x[m, d] for d in range(hidden_size) ----------
|
||||
y_row = y_ptr + pid * hidden_size
|
||||
|
||||
for d_off in tl.range(0, hidden_size, BLOCK_D):
|
||||
d_offs = d_off + tl.arange(0, BLOCK_D)
|
||||
d_mask = d_offs < hidden_size
|
||||
|
||||
x_offs = m_idx[:, None] * hidden_size + d_offs[None, :]
|
||||
x_mask = (m_idx[:, None] < HC_MULT) & d_mask[None, :]
|
||||
x_block = tl.load(x_row + x_offs, mask=x_mask, other=0.0).to(tl.float32)
|
||||
|
||||
y_block = tl.sum(pre[:, None] * x_block, axis=0)
|
||||
|
||||
tl.store(y_row + d_offs, y_block.to(y_ptr.dtype.element_ty), mask=d_mask)
|
||||
|
||||
|
||||
def fused_hc_head(
|
||||
x: torch.Tensor,
|
||||
hc_fn: torch.Tensor,
|
||||
hc_scale: torch.Tensor,
|
||||
hc_base: torch.Tensor,
|
||||
norm_eps: float,
|
||||
hc_eps: float,
|
||||
) -> torch.Tensor:
|
||||
"""Fused (RMSNorm + Linear + Sigmoid-gate + weighted-sum) for the DSV4 hc_head.
|
||||
|
||||
Args:
|
||||
x : (T, hc_mult, hidden_size) bf16/fp16, must be contiguous
|
||||
hc_fn : (hc_mult, hc_mult * hidden_size) fp32, contiguous
|
||||
hc_scale : (1,) fp32 scalar
|
||||
hc_base : (hc_mult,) fp32
|
||||
norm_eps : RMS epsilon
|
||||
hc_eps : additive epsilon after sigmoid
|
||||
|
||||
Returns:
|
||||
y : (T, hidden_size) same dtype as x
|
||||
"""
|
||||
assert x.is_contiguous(), "x must be contiguous"
|
||||
assert hc_fn.is_contiguous(), "hc_fn must be contiguous"
|
||||
assert hc_scale.dtype == torch.float32 and hc_base.dtype == torch.float32
|
||||
assert hc_fn.dtype == torch.float32
|
||||
assert x.dim() == 3, f"x must be 3D (T, hc_mult, hidden_size), got {x.shape}"
|
||||
|
||||
T, hc_mult, hidden_size = x.shape
|
||||
assert hc_fn.shape == (hc_mult, hc_mult * hidden_size), (
|
||||
f"hc_fn shape {hc_fn.shape} does not match (hc_mult={hc_mult}, "
|
||||
f"hc_mult*hidden_size={hc_mult * hidden_size})"
|
||||
)
|
||||
assert hc_base.shape == (hc_mult,)
|
||||
assert hc_scale.numel() == 1
|
||||
|
||||
y = torch.empty((T, hidden_size), dtype=x.dtype, device=x.device)
|
||||
|
||||
if T == 0:
|
||||
return y
|
||||
|
||||
BLOCK_K = 512
|
||||
BLOCK_D = 512
|
||||
|
||||
hc_mult_pow2 = max(1, triton.next_power_of_2(hc_mult))
|
||||
|
||||
grid = (T,)
|
||||
_hc_head_kernel[grid](
|
||||
x,
|
||||
hc_fn,
|
||||
hc_scale,
|
||||
hc_base,
|
||||
y,
|
||||
hidden_size=hidden_size,
|
||||
HC_MULT=hc_mult_pow2,
|
||||
K_TOTAL=hc_mult * hidden_size,
|
||||
BLOCK_K=BLOCK_K,
|
||||
BLOCK_D=BLOCK_D,
|
||||
norm_eps=norm_eps,
|
||||
hc_eps=hc_eps,
|
||||
num_warps=4,
|
||||
)
|
||||
return y
|
||||
@@ -653,7 +653,11 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
hc_fn: torch.Tensor,
|
||||
hc_scale: torch.Tensor,
|
||||
hc_base: torch.Tensor,
|
||||
norm: Optional[nn.Module] = 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()
|
||||
@@ -671,11 +675,16 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
comb = torch.empty(
|
||||
(0, self.hc_mult, self.hc_mult), dtype=dtype, device=x.device
|
||||
)
|
||||
return y, post, comb
|
||||
return y, post, comb, False
|
||||
|
||||
if envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get():
|
||||
from sglang.srt.layers.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,
|
||||
@@ -686,8 +695,9 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
hc_sinkhorn_eps=self.hc_eps,
|
||||
hc_post_mult_value=2.0,
|
||||
sinkhorn_repeat=self.hc_sinkhorn_iters,
|
||||
**norm_kwargs,
|
||||
)
|
||||
return y, post.squeeze(-1), comb
|
||||
return y, post.squeeze(-1), comb, norm is not None
|
||||
|
||||
if envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get():
|
||||
import deep_gemm
|
||||
@@ -717,7 +727,7 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
self.hc_eps,
|
||||
)
|
||||
y = (pre.squeeze(1).unsqueeze(-1) * x_flat.view(shape)).sum(dim=1)
|
||||
return y.to(dtype), post.squeeze(1), comb.squeeze(1)
|
||||
return y.to(dtype), post.squeeze(1), comb.squeeze(1), False
|
||||
|
||||
def hc_post(
|
||||
self,
|
||||
@@ -759,10 +769,15 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
input_ids_global: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
residual = hidden_states
|
||||
hidden_states, post, comb = self.hc_pre(
|
||||
hidden_states, self.hc_attn_fn, self.hc_attn_scale, self.hc_attn_base
|
||||
hidden_states, post, comb, norm_fused = self.hc_pre(
|
||||
hidden_states,
|
||||
self.hc_attn_fn,
|
||||
self.hc_attn_scale,
|
||||
self.hc_attn_base,
|
||||
norm=self.input_layernorm,
|
||||
)
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
if not norm_fused:
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
|
||||
hidden_states = self.self_attn(
|
||||
x=hidden_states,
|
||||
@@ -772,10 +787,15 @@ class DeepseekV4DecoderLayer(nn.Module):
|
||||
|
||||
hidden_states = self.hc_post(hidden_states, residual, post, comb)
|
||||
residual = hidden_states
|
||||
hidden_states, post, comb = self.hc_pre(
|
||||
hidden_states, self.hc_ffn_fn, self.hc_ffn_scale, self.hc_ffn_base
|
||||
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,
|
||||
)
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
if not norm_fused:
|
||||
hidden_states = self.post_attention_layernorm(hidden_states)
|
||||
|
||||
_use_cp = self.nsa_enable_prefill_cp and nsa_use_prefill_cp(forward_batch)
|
||||
_use_tp_moe_gather = (
|
||||
@@ -884,6 +904,17 @@ class DeepseekV4Model(nn.Module):
|
||||
hc_scale: torch.Tensor,
|
||||
hc_base: torch.Tensor,
|
||||
):
|
||||
if x.numel() > 0:
|
||||
from sglang.srt.layers.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,
|
||||
)
|
||||
shape, dtype = x.size(), x.dtype
|
||||
x = x.flatten(1).float()
|
||||
rsqrt = torch.rsqrt(x.square().mean(-1, keepdim=True) + self.norm_eps)
|
||||
|
||||
Reference in New Issue
Block a user