94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
349 lines
13 KiB
Python
349 lines
13 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING, Tuple
|
|
|
|
import torch
|
|
import triton
|
|
import triton.language as tl
|
|
|
|
from sglang.jit_kernel.utils import cache_once, is_arch_support_pdl, load_jit
|
|
from sglang.kernel_api_logging import debug_kernel_api
|
|
|
|
if TYPE_CHECKING:
|
|
from tvm_ffi.module import Module
|
|
|
|
|
|
_SCORING_FUNC_MAP = {
|
|
"sigmoid": 0,
|
|
"sqrtsoftplus": 1,
|
|
"softmax": 2,
|
|
}
|
|
|
|
|
|
@cache_once
|
|
def _jit_moe_fused_gate_module() -> Module:
|
|
return load_jit(
|
|
"moe_fused_gate",
|
|
cuda_files=["moe/moe_fused_gate.cuh"],
|
|
cuda_wrappers=[("moe_fused_gate", "MoEFusedGateKernel::run")],
|
|
)
|
|
|
|
|
|
@cache_once
|
|
def can_use_moe_fused_gate() -> bool:
|
|
logger = logging.getLogger(__name__)
|
|
try:
|
|
_jit_moe_fused_gate_module()
|
|
return True
|
|
except Exception as e:
|
|
logger.warning(f"Failed to load JIT MoE fused gate kernel: {e}")
|
|
return False
|
|
|
|
|
|
def moe_fused_gate_jit(
|
|
input: torch.Tensor,
|
|
bias: torch.Tensor,
|
|
topk: int,
|
|
scoring_func: str = "sigmoid",
|
|
num_fused_shared_experts: int = 0,
|
|
renormalize: bool = True,
|
|
routed_scaling_factor: float = 1.0,
|
|
apply_routed_scaling_factor_on_output: bool = False,
|
|
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
scoring_func_int = _SCORING_FUNC_MAP.get(scoring_func.lower())
|
|
assert (
|
|
scoring_func_int is not None
|
|
), f"Unknown scoring_func '{scoring_func}', must be one of {list(_SCORING_FUNC_MAP.keys())}"
|
|
|
|
assert input.dtype == torch.float32, "input must be float32"
|
|
assert bias.dtype == torch.float32, "bias must be float32"
|
|
assert input.ndim == 2, "input must be 2D"
|
|
assert bias.ndim == 1, "bias must be 1D"
|
|
assert input.size(1) == bias.size(0), "input and bias must have same num_experts"
|
|
assert topk > num_fused_shared_experts, "topk must be > num_fused_shared_experts"
|
|
|
|
num_rows, _ = input.shape
|
|
device = input.device
|
|
|
|
output = torch.empty(num_rows, topk, dtype=torch.float32, device=device)
|
|
indices = torch.empty(num_rows, topk, dtype=torch.int32, device=device)
|
|
|
|
module = _jit_moe_fused_gate_module()
|
|
module.moe_fused_gate(
|
|
input,
|
|
bias,
|
|
output,
|
|
indices,
|
|
topk,
|
|
scoring_func_int,
|
|
num_fused_shared_experts,
|
|
renormalize,
|
|
routed_scaling_factor,
|
|
apply_routed_scaling_factor_on_output,
|
|
)
|
|
|
|
return output, indices
|
|
|
|
|
|
@triton.jit
|
|
def _router_triton_kernel(
|
|
scores_ptr, # [M, N] fp32, GEMM output (raw logits)
|
|
bias_ptr, # [N] fp32
|
|
out_weights_ptr, # [M, K] fp32
|
|
out_indices_ptr, # [M, K] int32
|
|
M,
|
|
routed_scaling_factor,
|
|
moe_softcapping,
|
|
N: tl.constexpr,
|
|
K: tl.constexpr, # total topk (includes fused shared experts)
|
|
K_ROUTED: tl.constexpr, # K - num_fused_shared_experts
|
|
BLOCK_M: tl.constexpr, # rows processed per program (row tiling)
|
|
BLOCK_N: tl.constexpr, # >= N, power of 2
|
|
BLOCK_K: tl.constexpr, # >= K, power of 2
|
|
N_GROUP: tl.constexpr, # expert groups (1 = ungrouped)
|
|
TOPK_GROUP: tl.constexpr, # groups kept per token (grouped routing)
|
|
EXPERTS_PER_GROUP: tl.constexpr, # N // N_GROUP
|
|
BLOCK_G: tl.constexpr, # >= N_GROUP, power of 2
|
|
SCORING_FUNC: tl.constexpr, # 0 = sigmoid, 1 = sqrtsoftplus, 2 = softmax
|
|
HAS_SOFTCAP: tl.constexpr, # tanh softcapping (softmax only)
|
|
RENORMALIZE: tl.constexpr,
|
|
APPLY_SCALE: tl.constexpr, # apply_routed_scaling_factor_on_output
|
|
USE_PDL: tl.constexpr,
|
|
stride_sm,
|
|
stride_sn,
|
|
stride_wm,
|
|
stride_wk,
|
|
stride_im,
|
|
stride_ik,
|
|
) -> None:
|
|
# Row-tiled: each program handles BLOCK_M rows; all reductions run along the
|
|
# expert (N) axis. Tiling rows keeps CTAs large enough to stay occupancy-bound
|
|
# rather than launch-bound at small N (many tiny 1-warp CTAs otherwise).
|
|
pid = tl.program_id(0)
|
|
offs_m = pid * BLOCK_M + tl.arange(0, BLOCK_M) # [BLOCK_M]
|
|
offs_n = tl.arange(0, BLOCK_N) # [BLOCK_N]
|
|
mask_m = offs_m < M
|
|
mask_n = offs_n < N
|
|
|
|
# prefetch bias before PDL wait
|
|
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0).to(
|
|
tl.float32
|
|
) # [BLOCK_N]
|
|
|
|
if USE_PDL:
|
|
tl.extra.cuda.gdc_wait()
|
|
|
|
row_ptr = scores_ptr + offs_m[:, None] * stride_sm + offs_n[None, :] * stride_sn
|
|
mask2d = mask_m[:, None] & mask_n[None, :]
|
|
scores = tl.load(row_ptr, mask=mask2d, other=0.0).to(
|
|
tl.float32
|
|
) # [BLOCK_M, BLOCK_N]
|
|
|
|
if SCORING_FUNC == 0:
|
|
# sigmoid(x) = 1 / (1 + exp(-x)); bias is for ranking only, weight is bias-free.
|
|
activated = tl.sigmoid(scores)
|
|
biased = activated + bias[None, :]
|
|
elif SCORING_FUNC == 1:
|
|
# sqrt(softplus(x)) = sqrt(log1p(exp(x))); guard against overflow when x is large.
|
|
sp = tl.where(scores > 20.0, scores, tl.log(1.0 + tl.exp(scores)))
|
|
activated = tl.sqrt(sp)
|
|
biased = activated + bias[None, :]
|
|
else:
|
|
# softmax over the row: weight is the softmax probability (bias kept), with
|
|
# optional tanh softcapping. Ranking by the (softcapped, biased) logit is
|
|
# monotonic with the softmax prob, so the topk loop below ranks on `biased`.
|
|
logit = scores
|
|
if HAS_SOFTCAP:
|
|
# tanh(z) = 2*sigmoid(2z) - 1 (avoids relying on tl.math.tanh availability).
|
|
z = logit / moe_softcapping
|
|
logit = moe_softcapping * (2.0 * tl.sigmoid(2.0 * z) - 1.0)
|
|
biased = logit + bias[None, :]
|
|
biased = tl.where(mask_n[None, :], biased, -float("inf"))
|
|
row_max = tl.max(biased, axis=1)[:, None] # [BLOCK_M, 1]
|
|
exp_row = tl.where(mask_n[None, :], tl.exp(biased - row_max), 0.0)
|
|
row_sum = tl.sum(exp_row, axis=1)[:, None] # [BLOCK_M, 1]
|
|
activated = exp_row / row_sum
|
|
|
|
biased = tl.where(mask_n[None, :], biased, -float("inf")) # [BLOCK_M, BLOCK_N]
|
|
|
|
# Map NaN -> a finite floor
|
|
biased = tl.where(biased == biased, biased, -1e30) # [BLOCK_M, BLOCK_N]
|
|
|
|
# Grouped routing (DeepSeek-V3 noaux_tc): per-group score = sum of the top-2
|
|
# biased values; keep TOPK_GROUP groups (lowest group id wins ties); mask the
|
|
# experts of dropped groups to -inf before the top-k below. Weight is still the
|
|
# bias-free `activated`. Constexpr N_GROUP <= 1 skips this entirely (ungrouped).
|
|
if N_GROUP > 1:
|
|
offs_g = tl.arange(0, BLOCK_G) # [BLOCK_G]
|
|
group_of_n = offs_n // EXPERTS_PER_GROUP # [BLOCK_N]
|
|
group_score = tl.full([BLOCK_M, BLOCK_G], -float("inf"), dtype=tl.float32)
|
|
for g in tl.static_range(N_GROUP):
|
|
in_g = (group_of_n[None, :] == g) & mask_n[None, :]
|
|
vals = tl.where(in_g, biased, -float("inf"))
|
|
top1 = tl.max(vals, axis=1)[:, None] # [BLOCK_M, 1]
|
|
vals2 = tl.where(vals >= top1, -float("inf"), vals)
|
|
top2 = tl.max(vals2, axis=1)[:, None] # [BLOCK_M, 1]
|
|
group_score = tl.where(offs_g[None, :] == g, top1 + top2, group_score)
|
|
|
|
gcur = group_score
|
|
keep = tl.zeros([BLOCK_M, BLOCK_N], dtype=tl.float32)
|
|
for _i in tl.static_range(TOPK_GROUP):
|
|
gmax = tl.max(gcur, axis=1)[:, None] # [BLOCK_M, 1]
|
|
glane = tl.where(gcur == gmax, offs_g[None, :], N_GROUP + 1)
|
|
win_g = tl.min(glane, axis=1)[:, None] # [BLOCK_M, 1] lowest-id on ties
|
|
keep = tl.where(group_of_n[None, :] == win_g, 1.0, keep)
|
|
gcur = tl.where(offs_g[None, :] == win_g, -float("inf"), gcur)
|
|
biased = tl.where(keep > 0.0, biased, -float("inf"))
|
|
|
|
offs_k = tl.arange(0, BLOCK_K) # [BLOCK_K]
|
|
mask_k_total = offs_k < K
|
|
mask_k_routed = offs_k < K_ROUTED
|
|
selected_vals = tl.zeros([BLOCK_M, BLOCK_K], dtype=tl.float32)
|
|
selected_idx = tl.zeros([BLOCK_M, BLOCK_K], dtype=tl.int32)
|
|
|
|
cur = biased # [BLOCK_M, BLOCK_N]
|
|
for k in tl.static_range(K_ROUTED):
|
|
max_val = tl.max(cur, axis=1)[:, None] # [BLOCK_M, 1]
|
|
is_max = cur == max_val
|
|
lane_id = tl.where(is_max, offs_n[None, :], N + 1) # lowest expert id wins ties
|
|
win_lane = tl.min(lane_id, axis=1)[:, None].to(tl.int32) # [BLOCK_M, 1]
|
|
win_activated = tl.sum(
|
|
tl.where(offs_n[None, :] == win_lane, activated, 0.0), axis=1
|
|
)[
|
|
:, None
|
|
] # [BLOCK_M, 1]
|
|
slot = offs_k[None, :] == k # [1, BLOCK_K]
|
|
selected_vals = tl.where(slot, win_activated, selected_vals)
|
|
selected_idx = tl.where(slot, win_lane, selected_idx)
|
|
cur = tl.where(offs_n[None, :] == win_lane, -float("inf"), cur)
|
|
|
|
routed_sum = tl.sum(tl.where(mask_k_routed[None, :], selected_vals, 0.0), axis=1)[
|
|
:, None
|
|
] # [BLOCK_M, 1]
|
|
|
|
# Fill fused-shared-expert slots: weight = routed_sum / routed_scaling_factor,
|
|
# id = num_experts + (slot - K_ROUTED).
|
|
if K_ROUTED < K:
|
|
is_shared = (offs_k[None, :] >= K_ROUTED) & mask_k_total[None, :]
|
|
shared_weight = routed_sum / routed_scaling_factor # [BLOCK_M, 1]
|
|
shared_idx = (N + (offs_k - K_ROUTED)).to(tl.int32)[None, :] # [1, BLOCK_K]
|
|
selected_vals = tl.where(is_shared, shared_weight, selected_vals)
|
|
selected_idx = tl.where(is_shared, shared_idx, selected_idx)
|
|
|
|
if USE_PDL:
|
|
tl.extra.cuda.gdc_launch_dependents()
|
|
|
|
if RENORMALIZE:
|
|
norm = tl.where(routed_sum > 0.0, routed_sum, 1.0) # [BLOCK_M, 1]
|
|
selected_vals = selected_vals / norm
|
|
if APPLY_SCALE:
|
|
selected_vals = selected_vals * routed_scaling_factor
|
|
|
|
out_w_ptr = (
|
|
out_weights_ptr + offs_m[:, None] * stride_wm + offs_k[None, :] * stride_wk
|
|
)
|
|
out_i_ptr = (
|
|
out_indices_ptr + offs_m[:, None] * stride_im + offs_k[None, :] * stride_ik
|
|
)
|
|
store_mask = mask_m[:, None] & mask_k_total[None, :]
|
|
tl.store(out_w_ptr, selected_vals, mask=store_mask)
|
|
tl.store(out_i_ptr, selected_idx, mask=store_mask)
|
|
|
|
|
|
@debug_kernel_api
|
|
def moe_fused_gate(
|
|
scores: torch.Tensor,
|
|
bias: torch.Tensor,
|
|
topk: int,
|
|
scoring_func: str = "sigmoid",
|
|
num_fused_shared_experts: int = 0,
|
|
renormalize: bool = True,
|
|
routed_scaling_factor: float = 1.0,
|
|
apply_routed_scaling_factor_on_output: bool = False,
|
|
moe_softcapping: float = 0.0,
|
|
num_expert_group: int = 1,
|
|
topk_group: int = 1,
|
|
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
"""Triton fused router: scoring + bias + topk + (optional) renorm/scale.
|
|
|
|
Mirrors the semantics of :func:`moe_fused_gate_jit` (the CUDA JIT kernel).
|
|
With ``num_expert_group > 1`` it performs DeepSeek-V3 grouped routing
|
|
(per-group top-2-sum group scores, keep ``topk_group`` groups, then top-k
|
|
within). The first argument is named ``scores`` (raw GEMM logits) to match
|
|
the existing call sites.
|
|
"""
|
|
scoring_func_int = _SCORING_FUNC_MAP.get(scoring_func.lower())
|
|
assert (
|
|
scoring_func_int is not None
|
|
), f"Unknown scoring_func '{scoring_func}', must be one of {list(_SCORING_FUNC_MAP.keys())}"
|
|
assert scores.dtype in (
|
|
torch.float32,
|
|
torch.float16,
|
|
torch.bfloat16,
|
|
), "scores must be float32/float16/bfloat16"
|
|
assert bias.dtype == torch.float32, "bias must be float32"
|
|
assert scores.ndim == 2, "scores must be 2D"
|
|
assert bias.ndim == 1, "bias must be 1D"
|
|
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"
|
|
if routed_scaling_factor is None:
|
|
routed_scaling_factor = 1.0
|
|
|
|
M, N = scores.shape
|
|
K = topk
|
|
K_routed = topk - num_fused_shared_experts
|
|
if num_expert_group > 1:
|
|
assert N % num_expert_group == 0, "num_experts must be divisible by group count"
|
|
assert 1 <= topk_group <= num_expert_group, "invalid topk_group"
|
|
experts_per_group = N // num_expert_group
|
|
BLOCK_G = triton.next_power_of_2(num_expert_group)
|
|
|
|
weights = torch.empty((M, K), dtype=torch.float32, device=scores.device)
|
|
indices = torch.empty((M, K), dtype=torch.int32, device=scores.device)
|
|
|
|
BLOCK_N = triton.next_power_of_2(N) # 256 -> 256, 384 -> 512
|
|
BLOCK_K = triton.next_power_of_2(K) # 6 -> 8, 8 -> 8
|
|
# Single warp per program keeps the per-row top-k reductions on cheap warp
|
|
# shuffles; pack a few rows per program only when N is small so tiny launches
|
|
# stay occupancy-bound. Swept on H100/B200: this beats the AOT kernels across
|
|
# shapes, whereas larger tiles / more warps regress (register pressure).
|
|
BLOCK_M = max(1, min(4, 256 // BLOCK_N))
|
|
num_warps = 1
|
|
grid = (triton.cdiv(M, BLOCK_M),)
|
|
use_pdl = is_arch_support_pdl()
|
|
extra = {"launch_pdl": True} if use_pdl else {}
|
|
_router_triton_kernel[grid](
|
|
scores,
|
|
bias,
|
|
weights,
|
|
indices,
|
|
M,
|
|
float(routed_scaling_factor),
|
|
float(moe_softcapping),
|
|
N=N,
|
|
K=K,
|
|
K_ROUTED=K_routed,
|
|
BLOCK_M=BLOCK_M,
|
|
BLOCK_N=BLOCK_N,
|
|
BLOCK_K=BLOCK_K,
|
|
N_GROUP=num_expert_group,
|
|
TOPK_GROUP=topk_group,
|
|
EXPERTS_PER_GROUP=experts_per_group,
|
|
BLOCK_G=BLOCK_G,
|
|
SCORING_FUNC=scoring_func_int,
|
|
HAS_SOFTCAP=bool(moe_softcapping != 0.0),
|
|
RENORMALIZE=bool(renormalize),
|
|
APPLY_SCALE=bool(apply_routed_scaling_factor_on_output),
|
|
USE_PDL=use_pdl,
|
|
stride_sm=scores.stride(0),
|
|
stride_sn=scores.stride(1),
|
|
stride_wm=weights.stride(0),
|
|
stride_wk=weights.stride(1),
|
|
stride_im=indices.stride(0),
|
|
stride_ik=indices.stride(1),
|
|
num_warps=num_warps,
|
|
**extra,
|
|
)
|
|
return weights, indices
|