[Kernel] Avoid zero-bias allocation in fused softmax routing (#36811)

This commit is contained in:
YAMY
2026-09-01 16:29:57 -07:00
committed by GitHub
parent ed82bea146
commit 221a6273ce
3 changed files with 47 additions and 23 deletions
@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING, Tuple from typing import TYPE_CHECKING, Optional, Tuple
import torch import torch
import triton import triton
@@ -109,6 +109,7 @@ def _router_triton_kernel(
HAS_SOFTCAP: tl.constexpr, # tanh softcapping (softmax only) HAS_SOFTCAP: tl.constexpr, # tanh softcapping (softmax only)
RENORMALIZE: tl.constexpr, RENORMALIZE: tl.constexpr,
APPLY_SCALE: tl.constexpr, # apply_routed_scaling_factor_on_output APPLY_SCALE: tl.constexpr, # apply_routed_scaling_factor_on_output
HAS_BIAS: tl.constexpr,
USE_PDL: tl.constexpr, USE_PDL: tl.constexpr,
stride_sm, stride_sm,
stride_sn, stride_sn,
@@ -126,10 +127,13 @@ def _router_triton_kernel(
mask_m = offs_m < M mask_m = offs_m < M
mask_n = offs_n < N mask_n = offs_n < N
# prefetch bias before PDL wait # Prefetch a real bias before the PDL wait. Plain softmax routing has no
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0).to( # bias, so keep the zero value in registers rather than materializing and
tl.float32 # clearing a device tensor for every routing call.
) # [BLOCK_N] if HAS_BIAS:
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0).to(tl.float32)
else:
bias = tl.zeros([BLOCK_N], dtype=tl.float32)
if USE_PDL: if USE_PDL:
tl.extra.cuda.gdc_wait() tl.extra.cuda.gdc_wait()
@@ -254,7 +258,7 @@ def _router_triton_kernel(
@debug_kernel_api @debug_kernel_api
def moe_fused_gate( def moe_fused_gate(
scores: torch.Tensor, scores: torch.Tensor,
bias: torch.Tensor, bias: Optional[torch.Tensor],
topk: int, topk: int,
scoring_func: str = "sigmoid", scoring_func: str = "sigmoid",
num_fused_shared_experts: int = 0, num_fused_shared_experts: int = 0,
@@ -282,6 +286,12 @@ def moe_fused_gate(
torch.float16, torch.float16,
torch.bfloat16, torch.bfloat16,
), "scores must be float32/float16/bfloat16" ), "scores must be float32/float16/bfloat16"
assert scores.ndim == 2, "scores must be 2D"
if bias is None:
assert (
scoring_func.lower() == "softmax"
), "bias is required for non-softmax routing"
else:
# The kernel loads the bias and upcasts it to fp32 in-register (see # The kernel loads the bias and upcasts it to fp32 in-register (see
# _router_triton_kernel), so a non-fp32 bias (DeepSeek-V4 stores the # _router_triton_kernel), so a non-fp32 bias (DeepSeek-V4 stores the
# correction bias in bf16) needs no host-side cast/copy. # correction bias in bf16) needs no host-side cast/copy.
@@ -290,9 +300,10 @@ def moe_fused_gate(
torch.float16, torch.float16,
torch.bfloat16, torch.bfloat16,
), "bias must be float32/float16/bfloat16" ), "bias must be float32/float16/bfloat16"
assert scores.ndim == 2, "scores must be 2D"
assert bias.ndim == 1, "bias must be 1D" assert bias.ndim == 1, "bias must be 1D"
assert scores.size(1) == bias.size(0), "scores and bias must have same num_experts" assert scores.size(1) == bias.size(
0
), "scores and bias must have same num_experts"
assert topk > num_fused_shared_experts, "topk must be > num_fused_shared_experts" assert topk > num_fused_shared_experts, "topk must be > num_fused_shared_experts"
if routed_scaling_factor is None: if routed_scaling_factor is None:
routed_scaling_factor = 1.0 routed_scaling_factor = 1.0
@@ -349,7 +360,7 @@ def moe_fused_gate(
extra = {"launch_pdl": True} if use_pdl else {} extra = {"launch_pdl": True} if use_pdl else {}
_router_triton_kernel[grid]( _router_triton_kernel[grid](
scores, scores,
bias, bias if bias is not None else scores,
weights, weights,
indices, indices,
M, M,
@@ -369,6 +380,7 @@ def moe_fused_gate(
HAS_SOFTCAP=bool(moe_softcapping != 0.0), HAS_SOFTCAP=bool(moe_softcapping != 0.0),
RENORMALIZE=bool(renormalize), RENORMALIZE=bool(renormalize),
APPLY_SCALE=bool(apply_routed_scaling_factor_on_output), APPLY_SCALE=bool(apply_routed_scaling_factor_on_output),
HAS_BIAS=bias is not None,
USE_PDL=use_pdl, USE_PDL=use_pdl,
stride_sm=scores.stride(0), stride_sm=scores.stride(0),
stride_sn=scores.stride(1), stride_sn=scores.stride(1),
+1 -6
View File
@@ -986,14 +986,9 @@ def fused_topk(
moe_fused_gate as _jit_moe_fused_gate, moe_fused_gate as _jit_moe_fused_gate,
) )
zero_bias = torch.zeros(
gating_output.shape[1],
dtype=torch.float32,
device=gating_output.device,
)
topk_weights, topk_ids = _jit_moe_fused_gate( topk_weights, topk_ids = _jit_moe_fused_gate(
gating_output, gating_output,
zero_bias, None,
topk, topk,
scoring_func="softmax", scoring_func="softmax",
renormalize=renormalize, renormalize=renormalize,
@@ -343,6 +343,23 @@ def test_moe_fused_gate_softmax_matches_aot(
) )
@pytest.mark.parametrize("M", [1, 8, 32])
def test_moe_fused_gate_softmax_none_bias_matches_zero_bias(M: int) -> None:
torch.manual_seed(M)
scores = torch.randn(M, 256, dtype=torch.float32, device=DEVICE)
zero_bias = torch.zeros(256, dtype=torch.float32, device=DEVICE)
none_w, none_i = moe_fused_gate(
scores, None, topk=8, scoring_func="softmax", renormalize=True
)
zero_w, zero_i = moe_fused_gate(
scores, zero_bias, topk=8, scoring_func="softmax", renormalize=True
)
torch.testing.assert_close(none_w, zero_w, rtol=0, atol=0)
torch.testing.assert_close(none_i, zero_i, rtol=0, atol=0)
_SIGMOID_AOT_CASES = get_ci_test_range( _SIGMOID_AOT_CASES = get_ci_test_range(
[ [
(1, 128, 4, True, True, torch.float32), (1, 128, 4, True, True, torch.float32),