[2/N]Support DeepSeek-R1 w4a8 low latency deepep (#8464)

Co-authored-by: Hank Han <hanhan7630@outlook.com>
Co-authored-by: Shangchuan Huang <2510421000@qq.com>
This commit is contained in:
Jinwu
2025-10-25 08:41:16 +08:00
committed by GitHub
parent e51046beaa
commit 13bf565d60
8 changed files with 531 additions and 9 deletions

View File

@@ -11,12 +11,14 @@ from sgl_kernel import (
)
from sglang.srt.layers.moe.ep_moe.kernels import (
deepep_ll_get_cutlass_w4a8_moe_mm_data,
deepep_permute_triton_kernel,
deepep_post_reorder_triton_kernel,
deepep_run_moe_deep_preprocess,
post_reorder_triton_kernel_for_cutlass_moe,
pre_reorder_triton_kernel_for_cutlass_moe,
run_moe_ep_preproess,
silu_and_mul_masked_post_per_tensor_quant_fwd,
)
@@ -396,3 +398,139 @@ def cutlass_w4a8_moe_deepep_normal(
)
return output
def cutlass_w4a8_moe_deepep_ll(
a: torch.Tensor,
w1_q: torch.Tensor,
w2_q: torch.Tensor,
w1_scale: torch.Tensor,
w2_scale: torch.Tensor,
topk_ids_: torch.Tensor,
masked_m: torch.Tensor,
a_strides1: torch.Tensor,
b_strides1: torch.Tensor,
c_strides1: torch.Tensor,
a_strides2: torch.Tensor,
b_strides2: torch.Tensor,
c_strides2: torch.Tensor,
s_strides13: torch.Tensor,
s_strides2: torch.Tensor,
expert_offsets: torch.Tensor,
problem_sizes1: torch.Tensor,
problem_sizes2: torch.Tensor,
a1_scale: Optional[torch.Tensor] = None,
a2_scale: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""
This function computes a w4a8-quantized Mixture of Experts (MoE) layer
using two sets of quantized weights, w1_q and w2_q, and top-k gating
mechanism. The matrix multiplications are implemented with CUTLASS
grouped gemm.
Parameters:
- a (torch.Tensor): The input tensor to the MoE layer.
Shape: [num_local_experts, num_max_dispatch_tokens_per_rank * num_ranks, K]
- w1_q (torch.Tensor): The first set of int4-quantized expert weights.
Shape: [num_experts, N * 2, K // 2]
(the weights are passed transposed and int4-packed)
- w2_q (torch.Tensor): The second set of int4-quantized expert weights.
Shape: [num_experts, K, N // 2]
(the weights are passed transposed and int4-packed)
- w1_scale (torch.Tensor): The fp32 scale to dequantize w1_q.
Shape: [num_experts, K // 512, N * 8]
- w2_scale (torch.Tensor): The fp32 scale to dequantize w2_q.
Shape: [num_experts, N // 512, K * 4]
- topk_weights (torch.Tensor): The weights of each token->expert mapping.
- a_strides1 (torch.Tensor): The input strides of the first grouped gemm.
- b_strides1 (torch.Tensor): The weights strides of the first grouped gemm.
- c_strides1 (torch.Tensor): The output strides of the first grouped gemm.
- a_strides2 (torch.Tensor): The input strides of the second grouped gemm.
- b_strides2 (torch.Tensor): The weights strides of the second grouped gemm.
- c_strides2 (torch.Tensor): The output strides of the second grouped gemm.
- s_strides13 (torch.Tensor): The input and scale strides of the first grouped gemm.
- s_strides2 (torch.Tensor): The scale strides of the second grouped gemm.
- a1_scale (Optional[torch.Tensor]): The optional fp32 scale to quantize a.
Shape: scalar or [1, K]
- a2_scale (Optional[torch.Tensor]): The optional fp32 scale to
quantize the intermediate result between the gemms.
Shape: scalar or [1, N]
- apply_router_weight_on_input (bool): When true, the topk weights are
applied directly on the inputs. This is only applicable when topk is 1.
Returns:
- torch.Tensor: The fp8 output tensor after applying the MoE layer.
"""
assert w1_q.dtype == torch.int8
assert w2_q.dtype == torch.int8
assert a.shape[2] // 2 == w1_q.shape[2], "Hidden size mismatch w1"
assert w1_q.shape[2] * 2 == w2_q.shape[1], "Hidden size mismatch w2"
assert w1_q.shape[0] == w2_q.shape[0], "Expert number mismatch"
assert w1_q.shape[0] == w1_scale.shape[0], "w1 scales expert number mismatch"
assert w1_q.shape[0] == w2_scale.shape[0], "w2 scales expert number mismatch"
assert a_strides1.shape[0] == w1_q.shape[0], "A Strides 1 expert number mismatch"
assert b_strides1.shape[0] == w1_q.shape[0], "B Strides 1 expert number mismatch"
assert a_strides2.shape[0] == w2_q.shape[0], "A Strides 2 expert number mismatch"
assert b_strides2.shape[0] == w2_q.shape[0], "B Strides 2 expert number mismatch"
num_experts = w1_q.size(0)
m = a.size(1)
k = w1_q.size(2) * 2 # w1_q is transposed and packed
n = w2_q.size(2) * 2 # w2_q is transposed and packed
topk = topk_ids_.size(1)
device = a.device
problem_sizes1, problem_sizes2 = deepep_ll_get_cutlass_w4a8_moe_mm_data(
masked_m,
problem_sizes1,
problem_sizes2,
num_experts,
n,
k,
)
gateup_input = torch.empty(a.shape, dtype=torch.float8_e4m3fn, device=device)
sgl_per_tensor_quant_fp8(a, gateup_input, a1_scale.float(), True)
c1 = torch.empty((num_experts, m, n * 2), device=device, dtype=torch.bfloat16)
c2 = torch.empty((num_experts, m, k), device=device, dtype=torch.bfloat16)
cutlass_w4a8_moe_mm(
c1,
gateup_input,
w1_q,
a1_scale.float(),
w1_scale,
expert_offsets[:-1],
problem_sizes1,
a_strides1,
b_strides1,
c_strides1,
s_strides13,
128,
topk,
)
intermediate_q = torch.empty(
(num_experts, m, n), device=a.device, dtype=torch.float8_e4m3fn
)
silu_and_mul_masked_post_per_tensor_quant_fwd(
c1, intermediate_q, masked_m, a2_scale
)
cutlass_w4a8_moe_mm(
c2,
intermediate_q,
w2_q,
a2_scale.float(),
w2_scale,
expert_offsets[:-1],
problem_sizes2,
a_strides2,
b_strides2,
c_strides2,
s_strides2,
128,
topk,
)
return c2

View File

@@ -1014,3 +1014,197 @@ def zero_experts_compute_triton(
)
return output
@triton.jit
def compute_problem_sizes_w4a8_kernel(
masked_m_ptr,
problem_sizes1_ptr,
problem_sizes2_ptr,
n,
k,
num_experts,
BLOCK_SIZE: tl.constexpr,
):
pid = tl.program_id(axis=0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = pid < num_experts
final_occurrences = tl.load(masked_m_ptr + pid, mask=mask, other=0)
ps1_idx_0 = pid * 3
ps1_idx_1 = ps1_idx_0 + 1
ps1_idx_2 = ps1_idx_0 + 2
ps2_idx_0 = pid * 3
ps2_idx_1 = ps2_idx_0 + 1
ps2_idx_2 = ps2_idx_0 + 2
ps1_mask_0 = ps1_idx_0 < num_experts * 3
ps1_mask_1 = ps1_idx_1 < num_experts * 3
ps1_mask_2 = ps1_idx_2 < num_experts * 3
ps2_mask_0 = ps2_idx_0 < num_experts * 3
ps2_mask_1 = ps2_idx_1 < num_experts * 3
ps2_mask_2 = ps2_idx_2 < num_experts * 3
tl.store(problem_sizes1_ptr + ps1_idx_0, 2 * n, mask=ps1_mask_0)
tl.store(problem_sizes1_ptr + ps1_idx_1, final_occurrences, mask=ps1_mask_1)
tl.store(problem_sizes1_ptr + ps1_idx_2, k, mask=ps1_mask_2)
tl.store(problem_sizes2_ptr + ps2_idx_0, k, mask=ps2_mask_0)
tl.store(problem_sizes2_ptr + ps2_idx_1, final_occurrences, mask=ps2_mask_1)
tl.store(problem_sizes2_ptr + ps2_idx_2, n, mask=ps2_mask_2)
def compute_problem_sizes_w4a8(
masked_m, problem_sizes1, problem_sizes2, n, k, num_experts
):
BLOCK_SIZE = 256
grid = lambda meta: (triton.cdiv(num_experts, meta["BLOCK_SIZE"]),)
compute_problem_sizes_w4a8_kernel[grid](
masked_m,
problem_sizes1,
problem_sizes2,
n,
k,
num_experts,
BLOCK_SIZE=BLOCK_SIZE,
)
return problem_sizes1, problem_sizes2
def deepep_ll_get_cutlass_w4a8_moe_mm_data(
masked_m,
problem_sizes1,
problem_sizes2,
num_experts,
n,
k,
):
problem_sizes1, problem_sizes2 = compute_problem_sizes_w4a8(
masked_m, problem_sizes1, problem_sizes2, n, k, num_experts
)
return (
problem_sizes1.to(torch.int32),
problem_sizes2.to(torch.int32),
)
@triton.jit
def _silu_and_mul_post_per_tensor_quant_kernel(
input_ptr,
stride_input_expert,
stride_input_token,
stride_input_dim,
output_ptr,
stride_output_expert,
stride_output_token,
stride_output_dim,
scale_ptr,
masked_m_ptr,
inner_dim,
fp8_max,
fp8_min,
BLOCK_N: tl.constexpr,
NUM_STAGE: tl.constexpr,
):
"""
Triton kernel: fused SiLU(gate) * up + per-tensor FP8 quantization.
Shape:
input: [E, T_padded, 2*D] -> gate: [:,:,D], up: [:,:,D]
output: [E, T_padded, D], dtype=float8_e4m3fn
"""
expert_id = tl.program_id(2)
block_id_token = tl.program_id(1)
block_id_dim = tl.program_id(0)
num_token_blocks = tl.num_programs(1)
token_num_cur_expert = tl.load(masked_m_ptr + expert_id)
scale = 1.0 / tl.load(scale_ptr).to(tl.float32)
stride_input_expert = tl.cast(stride_input_expert, tl.int32)
stride_output_expert = tl.cast(stride_output_expert, tl.int32)
stride_input_token = tl.cast(stride_input_token, tl.int32)
stride_output_token = tl.cast(stride_output_token, tl.int32)
offset_d = block_id_dim * BLOCK_N + tl.arange(0, BLOCK_N)
mask_d = offset_d < inner_dim
# base pointers for current expert and dim block
input_base_offs = input_ptr + expert_id * stride_input_expert + offset_d
output_base_offs = output_ptr + expert_id * stride_output_expert + offset_d
for token_idx in tl.range(
block_id_token, token_num_cur_expert, num_token_blocks, num_stages=NUM_STAGE
):
gate_ptr = input_base_offs + token_idx * stride_input_token
up_ptr = gate_ptr + inner_dim
gate = tl.load(gate_ptr, mask=mask_d, other=0.0).to(tl.float32)
up = tl.load(up_ptr, mask=mask_d, other=0.0).to(tl.float32)
# SiLU: x * sigmoid(x)
gate = gate / (1 + tl.exp(-gate))
gate = gate.to(input_ptr.dtype.element_ty)
gate_up = up * gate
scaled = gate_up * scale
output_q = tl.clamp(scaled, fp8_min, fp8_max).to(output_ptr.dtype.element_ty)
out_ptr = output_base_offs + token_idx * stride_output_token
tl.store(out_ptr, output_q, mask=mask_d)
def silu_and_mul_masked_post_per_tensor_quant_fwd(
input: torch.Tensor,
output: torch.Tensor,
masked_m: torch.Tensor,
scale: torch.Tensor,
) -> torch.Tensor:
"""
Fused SiLU + Mul + Per-Tensor Quantization to FP8.
Args:
input: [expert_num, token_num_padded, 2 * inner_dim]
output: [expert_num, token_num_padded, inner_dim], dtype=torch.float8_e4m3fn
masked_m: [expert_num], actual token count for each expert
scale: [1] or [expert_num], quantization scale (per-tensor or per-expert)
Returns:
output tensor
"""
assert input.is_contiguous()
assert output.is_contiguous()
assert output.dtype == torch.float8_e4m3fn
assert input.ndim == 3
assert input.shape[0] == masked_m.shape[0]
assert input.shape[-1] % 2 == 0
assert scale.numel() == 1 or scale.shape[0] == input.shape[0]
expert_num = input.shape[0]
# 3584
inner_dim = input.shape[-1] // 2
BLOCK_N = 256
BLOCK_M = 64 if expert_num < 4 else 32
NUM_STAGES = 3
hidden_dim_split_block_num = triton.cdiv(inner_dim, BLOCK_N)
grid = (hidden_dim_split_block_num, BLOCK_M, expert_num)
finfo = torch.finfo(torch.float8_e4m3fn)
fp8_max = finfo.max
fp8_min = -fp8_max
_silu_and_mul_post_per_tensor_quant_kernel[grid](
input,
*input.stride(),
output,
*output.stride(),
scale,
masked_m,
inner_dim,
fp8_max,
fp8_min,
BLOCK_N=BLOCK_N,
NUM_STAGE=NUM_STAGES,
)
return output

View File

@@ -100,6 +100,7 @@ class DeepEPMoE(FusedMoE):
self.use_fp8_w8a8 = False
self.use_block_quant = False
else:
self.use_w4afp8 = False
self.use_fp8_w8a8 = False
self.use_block_quant = False
self.use_w4afp8 = False
@@ -199,6 +200,8 @@ class DeepEPMoE(FusedMoE):
return self.forward_flashinfer_cutedsl(
dispatch_output, down_gemm_overlap_args=down_gemm_overlap_args
)
elif self.use_w4afp8:
return self.forward_cutlass_w4afp8_masked(dispatch_output)
assert deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM and self.use_fp8_w8a8
assert down_gemm_overlap_args is None
return self.forward_deepgemm_masked(dispatch_output)
@@ -514,6 +517,20 @@ class DeepEPMoE(FusedMoE):
return down_output
def forward_cutlass_w4afp8_masked(
self,
dispatch_output: DeepEPNormalOutput,
):
assert self.moe_runner_config.activation == "silu"
assert isinstance(self.quant_method, W4AFp8MoEMethod)
assert get_bool_env_var(
"SGLANG_DEEPEP_BF16_DISPATCH"
), "W4AFP8 does not support FP8 dispatch; please set SGLANG_DEEPEP_BF16_DISPATCH=1."
return self.quant_method.apply_deepep_ll(
layer=self,
dispatch_output=dispatch_output,
)
def forward_npu(
self,
dispatch_output: Union[DeepEPNormalOutput, DeepEPLLOutput],

View File

@@ -23,6 +23,7 @@ if TYPE_CHECKING:
from sglang.srt.layers.moe.ep_moe.layer import DeepEPMoE
from sglang.srt.layers.moe.token_dispatcher import (
CombineInput,
DeepEPLLOutput,
DeepEPNormalOutput,
StandardDispatchOutput,
)
@@ -328,6 +329,41 @@ class W4AFp8MoEMethod(FusedMoEMethodBase):
output *= self.moe_runner_config.routed_scaling_factor
return StandardCombineInput(hidden_states=output)
def apply_deepep_ll(
self,
layer: DeepEPMoE,
dispatch_output: DeepEPLLOutput,
) -> torch.Tensor:
from sglang.srt.layers.moe.cutlass_w4a8_moe import cutlass_w4a8_moe_deepep_ll
hidden_states, _, topk_ids, _, masked_m, _ = dispatch_output
output = cutlass_w4a8_moe_deepep_ll(
hidden_states,
layer.w13_weight,
layer.w2_weight,
layer.w13_weight_scale_inv,
layer.w2_weight_scale_inv,
topk_ids,
masked_m,
layer.quant_method.a_strides1,
layer.quant_method.b_strides1,
layer.quant_method.c_strides1,
layer.quant_method.a_strides2,
layer.quant_method.b_strides2,
layer.quant_method.c_strides2,
layer.quant_method.s_strides13,
layer.quant_method.s_strides2,
layer.quant_method.expert_offsets,
layer.quant_method.problem_sizes1,
layer.quant_method.problem_sizes2,
layer.w13_input_scale,
layer.w2_input_scale,
)
return output
def apply_deepep_normal(
self,
layer: DeepEPMoE,