KernelBench hard · H100
Sonic MoE Kimi K3 (256k)
manually audited: clean
Genuine fused grouped-GEMM + SwiGLU Triton kernel. A single kernel gives each CTA one (BM x BN) tile of one expert, keeps two fp32 accumulators (gate and up) fed by a shared activation tile per K-step, applies silu(gate) * up in the epilogue, and stores bf16 via TMA descriptors with a masked pointer-store fallback for ragged per-expert row tails. Expert lookup is a device-side prefix-sum of per-expert m-tile counts plus a branchless binary search — no host syncs. Every forward allocates a fresh output and builds TensorDescriptors from the live hidden_states / W_gate / W_up, so there is no path that could return stale results. No forbidden op, no cross-run contamination, no grader/tolerance tampering, no numeric-stress bypass. The regrade_note in result.json documents a legitimate harness-side regrade (original 180 s check window was consumed by an infra venv rebuild after the workspace .venv's interpreter vanished; manual rerun passed check and benchmarked 0.0793).
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
No per-shape benchmark data archived for this run.
Kernel source (redacted)
"""Grouped-GEMM + fused SwiGLU up-projection for top-K MoE (SM90 / H100).
Per expert e: out_e = silu(x_e @ W_gate[e]) * (x_e @ W_up[e]), where x_e is a
contiguous slice of the permuted hidden states given by expert_offsets.
Design
------
* Single fused Triton kernel. Each CTA owns one (BM x BN) output tile of one
expert and keeps TWO fp32 accumulators (gate and up). The shared activation
tile is loaded once per K-step and feeds two tl.dot calls against the
gate/up weight tiles, halving activation traffic vs. two separate GEMMs.
SwiGLU runs in the epilogue straight from the accumulators; the gate
product never touches HBM.
* Work distribution: a tiny kernel computes the per-expert m-tile counts'
prefix sum (cumulative ceil(n_e / BM)); each main-kernel CTA then finds its
(expert, row) with a branchless binary search over that prefix sum. No
host<->device syncs anywhere, and only two launches per forward call.
* Optional persistent scheduling cooperates with a GROUP_M swizzle so
concurrently-running CTAs share weight slices in L2 (weights dominate DRAM
traffic).
* TMA tensor descriptors for A/B/C give free boundary handling on loads and
stores; ragged per-expert row tails fall back to a masked pointer store so
the next expert's rows are never clobbered.
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
from triton.tools.tensor_descriptor import TensorDescriptor
_LOG_SMEM_LIMIT = 232448 # SM90 shared-memory budget per CTA in bytes
_NUM_SMS: int | None = None
# ---------------------------------------------------------------------------
# tiny schedule kernel: cum[e] = sum_{i<e} ceil((offsets[i+1]-offsets[i]) / BM)
# ---------------------------------------------------------------------------
@triton.jit
def _cum_tiles_kernel(
offsets_ptr, # (E+1,) int32
cum_ptr, # (E+1,) int32 out (inclusive exclusive-shifted prefix sum)
total_ptr, # (1,) int32 out: total m-tiles
E: tl.constexpr,
BM: tl.constexpr,
BLOCK: tl.constexpr, # >= E, power of 2
):
idx = tl.arange(0, BLOCK)
mask = idx < E
o0 = tl.load(offsets_ptr + idx, mask=mask, other=0)
o1 = tl.load(offsets_ptr + idx + 1, mask=mask, other=0)
t = (o1 - o0 + (BM - 1)) // BM
c = tl.cumsum(t, axis=0)
tl.store(cum_ptr + 1 + idx, c, mask=mask)
zero = tl.zeros((), dtype=tl.int32)
tl.store(cum_ptr, zero)
total = tl.sum(t, axis=0)
tl.store(total_ptr, total)
# ---------------------------------------------------------------------------
# main fused grouped-GEMM + SwiGLU kernel
# ---------------------------------------------------------------------------
@triton.jit
def _find_expert(cum_ptr, t, E: tl.constexpr, LOG2E: tl.constexpr):
"""Largest e in [0, E-1] with cum[e] <= t. (cum has E+1 entries, cum[0]=0)"""
pos = tl.zeros((), dtype=tl.int32)
step: tl.constexpr = 1 << (LOG2E - 1)
for _ in tl.static_range(LOG2E):
nxt = pos + step
ok = nxt <= E
c = tl.load(cum_ptr + nxt, mask=ok, other=2147483647)
pos = tl.where(ok & (c <= t), nxt, pos)
step = step // 2 if step > 1 else 0 # type: ignore[const-overflow]
return pos
@triton.jit
def _moe_up_swiglu_kernel(
a_desc,
g_desc, # W_gate flattened as (E*H, I)
u_desc, # W_up flattened as (E*H, I)
c_desc,
c_ptr, # raw output pointer for the masked tail-store path
offsets_ptr, # (E+1,) int32
cum_ptr, # (E+1,) int32 m-tile prefix sums
total_ptr, # (1,) int32 total m-tiles
H: tl.constexpr,
I: tl.constexpr,
E: tl.constexpr,
LOG2E: tl.constexpr,
NUM_N_TILES: tl.constexpr,
BM: tl.constexpr,
BN: tl.constexpr,
BK: tl.constexpr,
GROUP_M: tl.constexpr,
PERSISTENT: tl.constexpr,
):
pid = tl.program_id(0)
num_m_tiles = tl.load(total_ptr)
if PERSISTENT:
num_progs = tl.num_programs(0)
total_tiles = num_m_tiles * NUM_N_TILES
for tile_id in tl.range(pid, total_tiles, num_progs):
_do_tile(
a_desc, g_desc, u_desc, c_desc, c_ptr, offsets_ptr, cum_ptr,
tile_id, num_m_tiles, H, I, E, LOG2E, NUM_N_TILES, BM, BN, BK, GROUP_M,
)
else:
num_in_group = GROUP_M * NUM_N_TILES
if pid < num_in_group * ((num_m_tiles + GROUP_M - 1) // GROUP_M):
_do_tile(
a_desc, g_desc, u_desc, c_desc, c_ptr, offsets_ptr, cum_ptr,
pid, num_m_tiles, H, I, E, LOG2E, NUM_N_TILES, BM, BN, BK, GROUP_M,
)
@triton.jit
def _do_tile(
a_desc, g_desc, u_desc, c_desc, c_ptr, offsets_ptr, cum_ptr,
pid, num_m_tiles,
H: tl.constexpr, I: tl.constexpr, E: tl.constexpr, LOG2E: tl.constexpr,
NUM_N_TILES: tl.constexpr,
BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr, GROUP_M: tl.constexpr,
):
# swizzled decode pid -> (m_tile, n_tile), GROUP_M m-tiles per group for L2
num_in_group = GROUP_M * NUM_N_TILES
group_id = pid // num_in_group
first_m = group_id * GROUP_M
group_size = tl.minimum(num_m_tiles - first_m, GROUP_M)
m_tile = first_m + (pid % num_in_group) % group_size
n_tile = (pid % num_in_group) // group_size
e = _find_expert(cum_ptr, m_tile, E, LOG2E)
tiles_before = tl.load(cum_ptr + e)
start = tl.load(offsets_ptr + e)
e_end = tl.load(offsets_ptr + e + 1)
m0 = start + (m_tile - tiles_before) * BM
n0 = n_tile * BN
w_row0 = e * H # W flattened as (E*H, I)
acc_g = tl.zeros((BM, BN), dtype=tl.float32)
acc_u = tl.zeros((BM, BN), dtype=tl.float32)
for k in range(0, H, BK):
a = a_desc.load([m0, k])
wg = g_desc.load([w_row0 + k, n0])
wu = u_desc.load([w_row0 + k, n0])
acc_g = tl.dot(a, wg, acc_g)
acc_u = tl.dot(a, wu, acc_u)
out = acc_u * (acc_g * tl.sigmoid(acc_g))
out_bf16 = out.to(tl.bfloat16)
if m0 + BM <= e_end:
c_desc.store([m0, n0], out_bf16)
else:
# ragged tail: mask off rows that belong to the next expert
rows = m0 + tl.arange(0, BM)
cols = n0 + tl.arange(0, BN)
ptrs = c_ptr + rows.to(tl.int64)[:, None] * I + cols[None, :]
mask = (rows < e_end)[:, None] & (cols < I)[None, :]
tl.store(ptrs, out_bf16, mask=mask)
# ---------------------------------------------------------------------------
# host side
# ---------------------------------------------------------------------------
def _next_pow2(x: int) -> int:
return max(1, 1 << (x - 1).bit_length())
# tuned on H100 PCIe (see scratch/); keyed by (H, I)
_CONFIGS = {
(4096, 1536): {"BM": 128, "BN": 128, "BK": 64, "GROUP_M": 8, "num_warps": 8, "num_stages": 3, "PERSISTENT": True, "GRID_MULT": 2},
(2048, 1024): {"BM": 128, "BN": 128, "BK": 64, "GROUP_M": 2, "num_warps": 8, "num_stages": 3, "PERSISTENT": True, "GRID_MULT": 2},
(2048, 4096): {"BM": 128, "BN": 128, "BK": 64, "GROUP_M": 8, "num_warps": 8, "num_stages": 3, "PERSISTENT": True, "GRID_MULT": 2},
}
def _pick_config(H: int, I: int) -> dict:
cfg = _CONFIGS.get((H, I))
if cfg is None:
cfg = {"BM": 128, "BN": 128, "BK": 64, "GROUP_M": 8, "num_warps": 8, "num_stages": 3, "PERSISTENT": True}
return cfg
def _smem_fits(BM: int, BN: int, BK: int, num_stages: int) -> bool:
per_stage = (BM * BK + 2 * BK * BN) * 2 # a tile + two weight tiles, bf16
return per_stage * num_stages < _LOG_SMEM_LIMIT
def _run(
hidden_states: torch.Tensor,
expert_offsets: torch.Tensor,
W_gate: torch.Tensor,
W_up: torch.Tensor,
E: int,
) -> torch.Tensor:
T_perm, H = hidden_states.shape
I = W_gate.shape[-1]
device = hidden_states.device
out = torch.empty(T_perm, I, dtype=torch.bfloat16, device=device)
if T_perm == 0:
return out
offsets = expert_offsets
if offsets.dtype != torch.int32:
offsets = offsets.to(torch.int32)
cfg = _pick_config(H, I)
BM, BN, BK = cfg["BM"], cfg["BN"], cfg["BK"]
num_stages = cfg["num_stages"]
while not _smem_fits(BM, BN, BK, num_stages) and num_stages > 2:
num_stages -= 1
cum = torch.empty(E + 1, dtype=torch.int32, device=device)
total = torch.empty(1, dtype=torch.int32, device=device)
BLOCK = max(_next_pow2(E), 16)
_cum_tiles_kernel[(1,)](offsets, cum, total, E=E, BM=BM, BLOCK=BLOCK, num_warps=4)
a_desc = TensorDescriptor.from_tensor(hidden_states, [BM, BK])
g_desc = TensorDescriptor.from_tensor(W_gate.view(E * H, I), [BK, BN])
u_desc = TensorDescriptor.from_tensor(W_up.view(E * H, I), [BK, BN])
c_desc = TensorDescriptor.from_tensor(out, [BM, BN])
num_n_tiles = triton.cdiv(I, BN)
max_tiles = T_perm // BM + E
if cfg["PERSISTENT"]:
global _NUM_SMS
if _NUM_SMS is None:
_NUM_SMS = torch.cuda.get_device_properties(device).multi_processor_count
grid = (min(_NUM_SMS * cfg.get("GRID_MULT", 2), max_tiles * num_n_tiles),)
else:
grid = (max_tiles * num_n_tiles,)
_moe_up_swiglu_kernel[grid](
a_desc,
g_desc,
u_desc,
c_desc,
out,
offsets,
cum,
total,
H=H,
I=I,
E=E,
LOG2E=_next_pow2(E).bit_length() - 1 if E > 1 else 1,
NUM_N_TILES=num_n_tiles,
BM=BM,
BN=BN,
BK=BK,
GROUP_M=cfg["GROUP_M"],
PERSISTENT=cfg["PERSISTENT"],
num_warps=cfg["num_warps"],
num_stages=num_stages,
)
return out
class Model(nn.Module):
"""Up-projection of a top-K MoE FFN with fused SwiGLU (grouped GEMM)."""
def __init__(self, T_total: int, H: int, I: int, E: int, K: int): # noqa: E741
super().__init__()
self.T_total = T_total
self.H = H
self.I = I
self.E = E
self.K = K
self.W_gate = nn.Parameter(torch.empty(E, H, I, dtype=torch.bfloat16))
self.W_up = nn.Parameter(torch.empty(E, H, I, dtype=torch.bfloat16))
nn.init.normal_(self.W_gate, std=0.02)
nn.init.normal_(self.W_up, std=0.02)
def forward(
self,
hidden_states: torch.Tensor, # (T_perm, H) bf16
expert_offsets: torch.Tensor, # (E+1,) int32
) -> torch.Tensor:
hidden_states = hidden_states.contiguous()
return _run(
hidden_states,
expert_offsets,
self.W_gate,
self.W_up,
self.E,
)
# --- module-level shape shims, mirroring reference.py ----------------------
T_total = 32768
H = 4096
I = 1536 # noqa: E741
E = 128
K = 8
def _build_routing(T_total: int, E: int, K: int, device: str = "cpu") -> torch.Tensor:
T_perm = T_total * K
base = T_perm // E
rem = T_perm - base * E
counts = torch.full((E,), base, dtype=torch.int32, device=device)
counts[:rem] += 1
offsets = torch.zeros(E + 1, dtype=torch.int32, device=device)
offsets[1:] = torch.cumsum(counts, dim=0)
return offsets
def get_inputs():
T_perm = T_total * K
hidden_states = torch.randn(T_perm, H, dtype=torch.bfloat16) * 0.1
expert_offsets = _build_routing(T_total, E, K)
return [hidden_states, expert_offsets]
def get_init_inputs():
return [T_total, H, I, E, K]
20260716_091612_kinetic-claude_kinetic-0715_06_sonic_moe_swiglu