Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

541 lines
16 KiB
Python

"""Fused triton kernels for Gemma4 decoder layer operations.
Fuses standard RMSNorm + residual-add (+ optional scalar multiply) into
a single kernel pass to reduce kernel launch overhead.
"""
from typing import Optional
import torch
import triton
import triton.language as tl
@triton.jit
def _gemma_rmsnorm_residual_kernel(
X_ptr,
W_ptr,
Residual_ptr,
Scalar_ptr,
Out_ptr,
stride_x,
stride_r,
stride_o,
N,
eps,
HAS_SCALAR: tl.constexpr,
BLOCK_SIZE: tl.constexpr,
):
"""Fused kernel: out = rmsnorm(x, w) + residual [* scalar]
When HAS_SCALAR is True, also multiplies by a scalar loaded from Scalar_ptr.
"""
row = tl.program_id(0)
cols = tl.arange(0, BLOCK_SIZE)
mask = cols < N
x = tl.load(X_ptr + row * stride_x + cols, mask=mask, other=0.0).to(tl.float32)
w = tl.load(W_ptr + cols, mask=mask, other=0.0).to(tl.float32)
r = tl.load(Residual_ptr + row * stride_r + cols, mask=mask, other=0.0).to(
tl.float32
)
var = tl.sum(x * x, axis=0) / N
rrms = tl.rsqrt(var + eps)
out = x * rrms * w + r
if HAS_SCALAR:
scalar = tl.load(Scalar_ptr).to(tl.float32)
out = out * scalar
tl.store(Out_ptr + row * stride_o + cols, out.to(x.dtype), mask=mask)
def gemma_rmsnorm_residual_scalar(
x: torch.Tensor,
weight: torch.Tensor,
residual: torch.Tensor,
scalar: torch.Tensor,
eps: float = 1e-6,
) -> torch.Tensor:
"""Fused (rmsnorm(x) + residual) * scalar."""
assert x.dim() == 2 and x.stride(-1) == 1, "Expected contiguous 2D input"
M, N = x.shape
BLOCK_SIZE = triton.next_power_of_2(N)
out = torch.empty_like(x)
_gemma_rmsnorm_residual_kernel[(M,)](
x,
weight,
residual,
scalar,
out,
x.stride(0),
residual.stride(0),
out.stride(0),
N,
eps,
HAS_SCALAR=True,
BLOCK_SIZE=BLOCK_SIZE,
)
return out
@triton.jit
def _gemma_dual_rmsnorm_residual_kernel(
X1_ptr,
W1_ptr,
X2_ptr,
W2_ptr,
W3_ptr,
Residual_ptr,
Scalar_ptr,
Out_ptr,
stride_x1,
stride_x2,
stride_r,
stride_o,
N,
eps1,
eps2,
eps3,
BLOCK_SIZE: tl.constexpr,
):
"""Fused: out = (rmsnorm(rmsnorm(x1,w1) + rmsnorm(x2,w2), w3) + residual) * scalar"""
row = tl.program_id(0)
cols = tl.arange(0, BLOCK_SIZE)
mask = cols < N
x1 = tl.load(X1_ptr + row * stride_x1 + cols, mask=mask, other=0.0).to(tl.float32)
w1 = tl.load(W1_ptr + cols, mask=mask, other=0.0).to(tl.float32)
x2 = tl.load(X2_ptr + row * stride_x2 + cols, mask=mask, other=0.0).to(tl.float32)
w2 = tl.load(W2_ptr + cols, mask=mask, other=0.0).to(tl.float32)
w3 = tl.load(W3_ptr + cols, mask=mask, other=0.0).to(tl.float32)
r = tl.load(Residual_ptr + row * stride_r + cols, mask=mask, other=0.0).to(
tl.float32
)
var1 = tl.sum(x1 * x1, axis=0) / N
norm1 = x1 * tl.rsqrt(var1 + eps1) * w1
var2 = tl.sum(x2 * x2, axis=0) / N
norm2 = x2 * tl.rsqrt(var2 + eps2) * w2
combined = norm1 + norm2
var3 = tl.sum(combined * combined, axis=0) / N
norm3 = combined * tl.rsqrt(var3 + eps3) * w3
scalar = tl.load(Scalar_ptr).to(tl.float32)
out = (norm3 + r) * scalar
tl.store(Out_ptr + row * stride_o + cols, out.to(x1.dtype), mask=mask)
@triton.jit
def _gemma_qkv_rmsnorm_store(
X_ptr,
W_ptr,
stride_m,
m,
h,
cols,
mask,
HEAD_DIM: tl.constexpr,
eps,
HAS_WEIGHT: tl.constexpr,
):
off = m * stride_m + h * HEAD_DIM + cols
x = tl.load(X_ptr + off, mask=mask, other=0.0).to(tl.float32)
rrms = tl.rsqrt(tl.sum(x * x, axis=0) / HEAD_DIM + eps)
out = x * rrms
if HAS_WEIGHT:
w = tl.load(W_ptr + cols, mask=mask, other=0.0).to(tl.float32)
out = out * w
tl.store(X_ptr + off, out.to(X_ptr.dtype.element_ty), mask=mask)
@triton.jit
def _gemma_qkv_rmsnorm_kernel(
Q_ptr,
K_ptr,
V_ptr,
Q_w_ptr,
K_w_ptr,
stride_q_m,
stride_k_m,
stride_v_m,
NUM_Q_HEADS: tl.constexpr,
NUM_KV_HEADS: tl.constexpr,
HEAD_DIM: tl.constexpr,
eps,
HAS_KV: tl.constexpr,
BY_HEAD: tl.constexpr,
BLOCK: tl.constexpr,
):
"""Fused per-head RMSNorm for Q, K, V.
The same kernel supports two launch shapes:
- BY_HEAD=True: grid is (M, total_heads), one program per token/head.
- BY_HEAD=False: grid is (M,), one program per token looping over heads.
Layout assumption: each tensor's last dim packs (num_heads, head_dim)
contiguously so per-head offset is `h * HEAD_DIM`. The token (M) stride is
taken from stride_*_m so the kernel works on strided views (e.g. slices of a
larger qkv buffer produced by `qkv.split`) without requiring `.contiguous()`
copies. V uses `weight=ones` semantics so the multiply-by-weight is omitted.
"""
m = tl.program_id(0)
cols = tl.arange(0, BLOCK)
mask = cols < HEAD_DIM
if BY_HEAD:
h_all = tl.program_id(1)
if h_all < NUM_Q_HEADS:
_gemma_qkv_rmsnorm_store(
Q_ptr, Q_w_ptr, stride_q_m, m, h_all, cols, mask, HEAD_DIM, eps, True
)
elif HAS_KV and h_all < NUM_Q_HEADS + NUM_KV_HEADS:
h = h_all - NUM_Q_HEADS
_gemma_qkv_rmsnorm_store(
K_ptr, K_w_ptr, stride_k_m, m, h, cols, mask, HEAD_DIM, eps, True
)
elif HAS_KV:
h = h_all - NUM_Q_HEADS - NUM_KV_HEADS
_gemma_qkv_rmsnorm_store(
V_ptr, Q_w_ptr, stride_v_m, m, h, cols, mask, HEAD_DIM, eps, False
)
else:
for h in tl.static_range(NUM_Q_HEADS):
_gemma_qkv_rmsnorm_store(
Q_ptr, Q_w_ptr, stride_q_m, m, h, cols, mask, HEAD_DIM, eps, True
)
if HAS_KV:
for h in tl.static_range(NUM_KV_HEADS):
_gemma_qkv_rmsnorm_store(
K_ptr,
K_w_ptr,
stride_k_m,
m,
h,
cols,
mask,
HEAD_DIM,
eps,
True,
)
for h in tl.static_range(NUM_KV_HEADS):
_gemma_qkv_rmsnorm_store(
V_ptr,
Q_w_ptr,
stride_v_m,
m,
h,
cols,
mask,
HEAD_DIM,
eps,
False,
)
def gemma_qkv_rmsnorm(
q: torch.Tensor,
k: Optional[torch.Tensor],
v: Optional[torch.Tensor],
q_weight: torch.Tensor,
k_weight: Optional[torch.Tensor],
num_q_heads: int,
num_kv_heads: int,
head_dim: int,
eps: float = 1e-6,
) -> None:
"""In-place fused RMSNorm on Q, K, V for Gemma4 attention.
All three norms compute `x * rsqrt(mean(x^2) + eps)` independently per head.
Q is scaled by `q_weight`, K by `k_weight`, V by 1 (Gemma4's V-norm has
`with_scale=False`).
Inputs may be 2D `(M, num_heads * head_dim)` or strided views of a larger
buffer (such as q/k/v slices from `qkv.split`). The kernel uses the actual
`stride(0)` so no `.contiguous()` copy is required. Within a token, the
last dim must be contiguous so heads pack as `h * head_dim` offsets.
If k and v are both None (KV-shared layer), only Q is normalized.
"""
assert q.is_cuda or q.is_xpu
assert q.stride(-1) == 1, "Q's last dim must be contiguous"
assert q_weight.shape[-1] == head_dim
M = q.shape[0] if q.dim() >= 2 else 1
BLOCK = triton.next_power_of_2(head_dim)
has_kv = k is not None and v is not None
if has_kv:
assert (k.is_cuda and v.is_cuda) or (k.is_xpu and v.is_xpu)
assert k.stride(-1) == 1 and v.stride(-1) == 1
assert k_weight is not None and k_weight.shape[-1] == head_dim
if M <= 256:
total_heads = num_q_heads + (2 * num_kv_heads if has_kv else 0)
_gemma_qkv_rmsnorm_kernel[(M, total_heads)](
q,
k if has_kv else q,
v if has_kv else q,
q_weight,
k_weight if has_kv else q_weight,
q.stride(0),
k.stride(0) if has_kv else 0,
v.stride(0) if has_kv else 0,
NUM_Q_HEADS=num_q_heads,
NUM_KV_HEADS=num_kv_heads if has_kv else 0,
HEAD_DIM=head_dim,
eps=eps,
HAS_KV=has_kv,
BY_HEAD=True,
BLOCK=BLOCK,
)
return
_gemma_qkv_rmsnorm_kernel[(M,)](
q,
k if has_kv else q,
v if has_kv else q,
q_weight,
k_weight if has_kv else q_weight,
q.stride(0),
k.stride(0) if has_kv else 0,
v.stride(0) if has_kv else 0,
NUM_Q_HEADS=num_q_heads,
NUM_KV_HEADS=num_kv_heads if has_kv else 0,
HEAD_DIM=head_dim,
eps=eps,
HAS_KV=has_kv,
BY_HEAD=False,
BLOCK=BLOCK,
)
@triton.jit
def _gemma_routing_post_topk_kernel(
Logits_ptr,
Ids_ptr,
Scale_ptr,
Out_weights_ptr,
Out_ids_ptr,
stride_l,
stride_ow,
stride_oi,
K: tl.constexpr,
BLOCK_K: tl.constexpr,
):
"""Fused: softmax(topk_logits) * per_expert_scale[topk_ids] → float32 weights, int32 ids.
One program per token. K is the number of top-k experts (e.g. 8).
"""
row = tl.program_id(0)
cols = tl.arange(0, BLOCK_K)
mask = cols < K
logits = tl.load(
Logits_ptr + row * stride_l + cols, mask=mask, other=float("-inf")
).to(tl.float32)
ids_i64 = tl.load(Ids_ptr + row * stride_l + cols, mask=mask, other=0)
# Stable softmax
max_val = tl.max(logits, axis=0)
exp_val = tl.exp(logits - max_val)
sum_exp = tl.sum(exp_val, axis=0)
weights = exp_val / sum_exp
# Gather per_expert_scale and multiply
scale = tl.load(Scale_ptr + ids_i64, mask=mask, other=1.0).to(tl.float32)
weights = weights * scale
tl.store(Out_weights_ptr + row * stride_ow + cols, weights, mask=mask)
tl.store(Out_ids_ptr + row * stride_oi + cols, ids_i64.to(tl.int32), mask=mask)
def gemma_routing_post_topk(
topk_logits: torch.Tensor,
topk_ids: torch.Tensor,
per_expert_scale: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Fused softmax + scale-gather + casts for Gemma4 routing.
Replaces: softmax(topk_logits) * per_expert_scale[topk_ids] → (f32, i32).
"""
B, K = topk_logits.shape
BLOCK_K = triton.next_power_of_2(K)
out_weights = torch.empty((B, K), dtype=torch.float32, device=topk_logits.device)
out_ids = torch.empty((B, K), dtype=torch.int32, device=topk_logits.device)
_gemma_routing_post_topk_kernel[(B,)](
topk_logits,
topk_ids,
per_expert_scale,
out_weights,
out_ids,
topk_logits.stride(0),
out_weights.stride(0),
out_ids.stride(0),
K=K,
BLOCK_K=BLOCK_K,
)
return out_weights, out_ids
def gemma_dual_rmsnorm_residual_scalar(
x1: torch.Tensor,
weight1: torch.Tensor,
x2: torch.Tensor,
weight2: torch.Tensor,
weight3: torch.Tensor,
residual: torch.Tensor,
scalar: torch.Tensor,
eps1: float = 1e-6,
eps2: float = 1e-6,
eps3: float = 1e-6,
) -> torch.Tensor:
"""Fused (rmsnorm(rmsnorm(x1,w1) + rmsnorm(x2,w2), w3) + residual) * scalar."""
assert x1.dim() == 2 and x1.stride(-1) == 1
M, N = x1.shape
BLOCK_SIZE = triton.next_power_of_2(N)
out = torch.empty_like(x1)
_gemma_dual_rmsnorm_residual_kernel[(M,)](
x1,
weight1,
x2,
weight2,
weight3,
residual,
scalar,
out,
x1.stride(0),
x2.stride(0),
residual.stride(0),
out.stride(0),
N,
eps1,
eps2,
eps3,
BLOCK_SIZE=BLOCK_SIZE,
)
return out
@triton.jit
def _gemma4_routing_kernel(
gating_ptr, # [T, E] router logits, any float dtype
per_expert_scale_ptr, # [E] per-expert scale (any float dtype)
topk_weights_ptr, # [T, K] fp32 out
topk_ids_ptr, # [T, K] int32 out
stride_g_t, # stride of gating in the token dim
E: tl.constexpr,
K: tl.constexpr,
BLOCK_E: tl.constexpr,
):
pid = tl.program_id(0)
offs_e = tl.arange(0, BLOCK_E)
valid = offs_e < E
logits = tl.load(
gating_ptr + pid * stride_g_t + offs_e,
mask=valid,
other=-float("inf"),
).to(tl.float32)
# Pack (sort_key, expert_id) into one int64 so a single signed-ascending
# tl.sort yields logits in descending float order. The key bijection is
# anti-monotone on the float value, and the <<32 shift moves its high bit
# into the int64 sign bit. Ties break by expert id ascending. Invalid
# lanes use a max key so they sort last.
MIN32 = -2147483648
logit_bits = logits.to(tl.int32, bitcast=True)
sign = logit_bits >> 31
key = tl.where(sign == 0, logit_bits ^ -1, logit_bits ^ MIN32)
key = tl.where(valid, key, 0x7FFFFFFF)
sk64 = key.to(tl.int64) & 0x00000000FFFFFFFF
packed = (sk64 << 32) | offs_e.to(tl.int64)
sorted_p = tl.sort(packed, descending=False)
all_keys = ((sorted_p >> 32) & 0x00000000FFFFFFFF).to(tl.int32)
all_ids = (sorted_p & 0x00000000FFFFFFFF).to(tl.int32)
# Invert the key bijection to recover the original logit value.
sign_k = all_keys >> 31
all_bits = tl.where(sign_k < 0, all_keys ^ -1, all_keys ^ MIN32)
all_logits = all_bits.to(tl.float32, bitcast=True)
# softmax over the top-K logits; max sits at index 0 (sorted descending).
top_mask = offs_e < K
max_l = tl.max(tl.where(top_mask, all_logits, -float("inf")), axis=0)
raw_exp = tl.where(top_mask, tl.exp(all_logits - max_l), 0.0)
denom = tl.sum(raw_exp, axis=0)
denom = tl.where(denom > 0.0, denom, 1.0)
weights = raw_exp / denom
scales = tl.load(
per_expert_scale_ptr + all_ids.to(tl.int64),
mask=top_mask,
other=1.0,
).to(tl.float32)
weights = weights * scales
base_off = pid * K + offs_e
tl.store(topk_weights_ptr + base_off, weights, mask=top_mask)
tl.store(topk_ids_ptr + base_off, all_ids, mask=top_mask)
def gemma4_fused_routing(
gating_output: torch.Tensor,
per_expert_scale: torch.Tensor,
topk: int,
) -> tuple[torch.Tensor, torch.Tensor]:
"""One-launch Gemma4 router.
Args:
gating_output: [T, E] router logits in any floating dtype; will be
cast to fp32 inside the kernel.
per_expert_scale: [E] per-expert scale, any floating dtype.
topk: number of experts to keep per token.
Returns:
topk_weights: [T, topk] fp32 (matches SGLang TopK contract).
topk_ids: [T, topk] int32 (matches SGLang TopK contract).
"""
assert gating_output.dim() == 2, "expected [T, E] router logits"
assert per_expert_scale.dim() == 1
assert per_expert_scale.shape[0] == gating_output.shape[1]
T, E = gating_output.shape
assert topk <= E, f"topk ({topk}) must be <= E ({E})"
assert E <= 1024, f"gemma4_fused_routing only supports E<=1024, got E={E}"
gating_output = gating_output.contiguous()
per_expert_scale = per_expert_scale.contiguous()
BLOCK_E = triton.next_power_of_2(E)
topk_weights = torch.empty(
(T, topk), dtype=torch.float32, device=gating_output.device
)
topk_ids = torch.empty((T, topk), dtype=torch.int32, device=gating_output.device)
if T == 0:
return topk_weights, topk_ids
_gemma4_routing_kernel[(T,)](
gating_output,
per_expert_scale,
topk_weights,
topk_ids,
gating_output.stride(0),
E,
topk,
BLOCK_E,
num_warps=1,
)
return topk_weights, topk_ids