kernelbench.com

KernelBench hard · H100

Sonic MoE Claude Opus 5

6.87%geomean peak fraction across shapes

manually audited: clean

Persistent Triton grouped GEMM: device-side tile-table prologue from expert_offsets (no host sync), dual gate/up fp32 accumulators sharing one A-tile fetch, TMA descriptor operands with mask-free mainloop, SwiGLU fused in the epilogue via relative-error-accurate exp2 formulation, cp.async pointer fallback. Only cache is the content-independent TMA descriptor dict; no graph tricks, no memoization -- simplest and cleanest of the six. Grader files Read-only, template_mutated false, no foreign-archive access. Passed check.py + stress on the isolated re-grade; clean 0.0687 (contended 0.0683).

harnessor-opusagent session1h 36mtotal wall1h 42mcheck4mbenchmark3moutput tokensgpu-lock wait5mgpu-lock held33mregimecompute

Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth

32768×4096×1536×128×818.575 ms5.9%0.33 TB/s · 16% of 2.0 TB/s HBM · also 44 TFLOPS (6% of compute)
4096×2048×1024×64×40.508 ms8.9%1.25 TB/s · 62% of 2.0 TB/s HBM · also 68 TFLOPS (9% of compute)
16384×2048×4096×64×811.772 ms6.2%0.32 TB/s · 16% of 2.0 TB/s HBM · also 47 TFLOPS (6% of compute)

compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)

geomean(5.9% · 8.9% · 6.2%) = 6.9%

Kernel source (redacted)
"""Sonic-MoE up-projection: variable-length grouped GEMM with fused SwiGLU.

Per expert e (rows [offsets[e], offsets[e+1]) of the permuted hidden states):

    h_e = silu(x_e @ W_gate[e]) * (x_e @ W_up[e])

Design
------
* One *persistent* Triton kernel (grid = #SMs) walks a flat list of output
  tiles.  A tiny prologue kernel turns `expert_offsets` into a
  (row_start, n_rows, expert) tile table on device, so the ragged group
  boundaries never require a host synchronisation.
* Each tile keeps **two** fp32 accumulators (gate and up).  The A-tile is
  fetched once and feeds two wgmma chains, i.e. the pair is one GEMM of
  N = 2*I with the SwiGLU folded into the epilogue.  128x128 with 8 warps is
  the largest tile that still fits 2 x BM x BN / 256 = 128 accumulator
  registers per thread without spilling.
* Operands are moved with Hopper TMA (`cp.async.bulk.tensor`).  Besides the
  cheaper address math this makes the mainloop *mask-free*: TMA zero-fills
  out-of-bounds elements, so the ragged last tile of an expert and a
  non-multiple-of-BLOCK_K reduction dim both come out right (garbage rows are
  dropped by the masked store; garbage k-rows of B are multiplied by
  zero-filled columns of A).  The weights are described as one 2D
  (E*H, I) tensor -- legal because W[e] is contiguous -- with row e*H + k.
"""
from __future__ import annotations

import torch
import torch.nn as nn
import triton
import triton.language as tl

try:  # Hopper tensor-memory-accelerator descriptors
    from triton.tools.experimental_descriptor import create_2d_tma_descriptor as _mk_desc
except Exception:  # pragma: no cover - falls back to the cp.async kernel
    _mk_desc = None

TILE_M = 128       # M-tiling shared by the table builder and the GEMM
_NEG_LOG2E = tl.constexpr(-1.4426950408889634)


# --------------------------------------------------------------------------- #
# Prologue: expert_offsets -> flat tile table (one program per expert)
# --------------------------------------------------------------------------- #
@triton.jit
def _tile_table_kernel(
    offsets_ptr,        # (E+1,) int32
    tile_row_ptr,       # (MAX_TILES,) int32  first row of the tile
    tile_nrow_ptr,      # (MAX_TILES,) int32  valid rows in the tile
    tile_exp_ptr,       # (MAX_TILES,) int32  expert id
    nm_ptr,             # (1,) int32          total number of m-tiles
    E: tl.constexpr,
    E_POW2: tl.constexpr,
    BLOCK_M: tl.constexpr,
    CHUNK: tl.constexpr,
):
    e = tl.program_id(0)
    ar = tl.arange(0, E_POW2)
    v = ar < E
    s = tl.load(offsets_ptr + ar, mask=v, other=0)
    t = tl.load(offsets_ptr + ar + 1, mask=v, other=0)
    cnt = tl.where(v, t - s, 0)
    ntl = (cnt + BLOCK_M - 1) // BLOCK_M

    if e == 0:
        tl.store(nm_ptr, tl.sum(ntl, axis=0))

    # Exclusive prefix sum, recomputed per program: E is small, this is free.
    base = tl.sum(tl.where(ar < e, ntl, 0), axis=0)
    my_cnt = tl.sum(tl.where(ar == e, cnt, 0), axis=0)
    my_row = tl.sum(tl.where(ar == e, s, 0), axis=0)
    my_ntl = (my_cnt + BLOCK_M - 1) // BLOCK_M

    for j0 in range(0, my_ntl, CHUNK):
        j = j0 + tl.arange(0, CHUNK)
        m = j < my_ntl
        tl.store(tile_row_ptr + base + j, my_row + j * BLOCK_M, mask=m)
        tl.store(tile_nrow_ptr + base + j,
                 tl.minimum(my_cnt - j * BLOCK_M, BLOCK_M), mask=m)
        tl.store(tile_exp_ptr + base + j, j * 0 + e, mask=m)


@triton.jit
def _swiglu(g, u):
    """silu(g) * u = g*u / (1 + exp(-g)).

    ex2.approx + div.full are both *relative*-error accurate (~2^-22), which
    matters here: for g << 0 the true result decays like g*u*exp(g), and a
    formulation with only absolute accuracy (e.g. tanh.approx) loses those
    elements entirely once |g*u| is large.
    """
    return tl.fdiv(g * u, 1.0 + tl.exp2(g * _NEG_LOG2E), ieee_rounding=False)


# --------------------------------------------------------------------------- #
# Main kernel: TMA operands, dual accumulator, fused SwiGLU epilogue
# --------------------------------------------------------------------------- #
@triton.jit
def _moe_up_swiglu_tma(
    a_desc, wg_desc, wu_desc, OUT,
    tile_row_ptr, tile_nrow_ptr, tile_exp_ptr, nm_ptr,
    H: tl.constexpr,
    I: tl.constexpr,
    BLOCK_M: tl.constexpr,
    BLOCK_N: tl.constexpr,
    BLOCK_K: tl.constexpr,
    GROUP_M: tl.constexpr,
):
    pid = tl.program_id(0)
    nprog = tl.num_programs(0)

    n_tiles: tl.constexpr = (I + BLOCK_N - 1) // BLOCK_N
    num_m = tl.load(nm_ptr)
    total = num_m * n_tiles
    pid_in_group: tl.constexpr = GROUP_M * n_tiles

    rel = tl.arange(0, BLOCK_M)
    cn = tl.arange(0, BLOCK_N)

    for tid in range(pid, total, nprog):
        # ---- flat tile id -> (m-tile, n-tile) with an L2-friendly swizzle ----
        group_id = tid // pid_in_group
        first_m = group_id * GROUP_M
        gsm = tl.minimum(num_m - first_m, GROUP_M)
        r = tid % pid_in_group
        pid_m = first_m + (r % gsm)
        pid_n = r // gsm

        row0 = tl.load(tile_row_ptr + pid_m)
        nrow = tl.load(tile_nrow_ptr + pid_m)
        wrow = tl.load(tile_exp_ptr + pid_m) * H
        n0 = pid_n * BLOCK_N

        acc_g = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        acc_u = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        for k in range(0, H, BLOCK_K):
            a = tl._experimental_descriptor_load(
                a_desc, [row0, k], [BLOCK_M, BLOCK_K], tl.bfloat16)
            bg = tl._experimental_descriptor_load(
                wg_desc, [wrow + k, n0], [BLOCK_K, BLOCK_N], tl.bfloat16)
            bu = tl._experimental_descriptor_load(
                wu_desc, [wrow + k, n0], [BLOCK_K, BLOCK_N], tl.bfloat16)
            acc_g = tl.dot(a, bg, acc_g)
            acc_u = tl.dot(a, bu, acc_u)

        o = _swiglu(acc_g, acc_u).to(OUT.dtype.element_ty)
        offs_n = n0 + cn
        o_ptrs = OUT + row0.to(tl.int64) * I + (rel[:, None] * I + offs_n[None, :])
        tl.store(o_ptrs, o, mask=(rel < nrow)[:, None] & (offs_n < I)[None, :])


# --------------------------------------------------------------------------- #
# Portable fallback: same schedule, cp.async / pointer operands
# --------------------------------------------------------------------------- #
@triton.jit
def _moe_up_swiglu_ptr(
    X, WG, WU, OUT,
    tile_row_ptr, tile_nrow_ptr, tile_exp_ptr, nm_ptr,
    H: tl.constexpr,
    I: tl.constexpr,
    BLOCK_M: tl.constexpr,
    BLOCK_N: tl.constexpr,
    BLOCK_K: tl.constexpr,
    GROUP_M: tl.constexpr,
):
    pid = tl.program_id(0)
    nprog = tl.num_programs(0)

    n_tiles: tl.constexpr = (I + BLOCK_N - 1) // BLOCK_N
    num_m = tl.load(nm_ptr)
    total = num_m * n_tiles
    pid_in_group: tl.constexpr = GROUP_M * n_tiles

    ak = tl.arange(0, BLOCK_K)
    rel = tl.arange(0, BLOCK_M)
    cn = tl.arange(0, BLOCK_N)

    for tid in range(pid, total, nprog):
        group_id = tid // pid_in_group
        first_m = group_id * GROUP_M
        gsm = tl.minimum(num_m - first_m, GROUP_M)
        r = tid % pid_in_group
        pid_m = first_m + (r % gsm)
        pid_n = r // gsm

        row0 = tl.load(tile_row_ptr + pid_m)
        nrow = tl.load(tile_nrow_ptr + pid_m)
        eidx = tl.load(tile_exp_ptr + pid_m)

        # Index expressions stay affine in `arange` so Triton keeps the
        # contiguity facts it needs for vectorised cp.async; the ragged edges
        # are handled with loop-invariant masks instead of clamped pointers.
        n0 = pid_n * BLOCK_N
        offs_n = n0 + cn
        mask_m = rel < nrow
        mask_n = offs_n < I

        a_ptrs = X + row0.to(tl.int64) * H + (rel[:, None] * H + ak[None, :])
        woff = eidx.to(tl.int64) * (H * I) + (ak[:, None] * I + offs_n[None, :])
        bg_ptrs = WG + woff
        bu_ptrs = WU + woff

        acc_g = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        acc_u = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        for k0 in range(0, H, BLOCK_K):
            mask_k = ak < H - k0
            a = tl.load(a_ptrs, mask=mask_m[:, None] & mask_k[None, :], other=0.0)
            bg = tl.load(bg_ptrs, mask=mask_k[:, None] & mask_n[None, :], other=0.0)
            bu = tl.load(bu_ptrs, mask=mask_k[:, None] & mask_n[None, :], other=0.0)
            acc_g = tl.dot(a, bg, acc_g)
            acc_u = tl.dot(a, bu, acc_u)
            a_ptrs += BLOCK_K
            bg_ptrs += BLOCK_K * I
            bu_ptrs += BLOCK_K * I

        o = _swiglu(acc_g, acc_u).to(OUT.dtype.element_ty)
        o_ptrs = OUT + row0.to(tl.int64) * I + (rel[:, None] * I + offs_n[None, :])
        tl.store(o_ptrs, o, mask=mask_m[:, None] & mask_n[None, :])


# --------------------------------------------------------------------------- #
# Host side
# --------------------------------------------------------------------------- #
_NUM_SMS: int | None = None
_SMEM_CAP = 200 * 1024


def _num_sms() -> int:
    global _NUM_SMS
    if _NUM_SMS is None:
        _NUM_SMS = torch.cuda.get_device_properties(0).multi_processor_count
    return _NUM_SMS


def _pow2_le(n: int, lo: int) -> int:
    return max(lo, 1 << (max(n, 1).bit_length() - 1))


class Model(nn.Module):
    """Up-projection of a top-K MoE FFN with fused SwiGLU."""

    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)
        self._tbl: dict = {}
        self._desc: dict = {}

    # -- tile-table scratch buffers, one set per (T_perm, BLOCK_M) ---------- #
    def _table(self, T_perm: int, bm: int, device) -> tuple:
        key = (T_perm, bm)
        t = self._tbl.get(key)
        if t is None:
            n = (T_perm + bm - 1) // bm + self.E + 1
            t = tuple(torch.empty(n, dtype=torch.int32, device=device)
                      for _ in range(3)) + (torch.empty(1, dtype=torch.int32, device=device),)
            self._tbl[key] = t
        return t

    # -- TMA descriptors: content-independent, so (ptr, dims, box) is a key - #
    def _descriptor(self, t: torch.Tensor, dim1: int, dim0: int, box1: int, box0: int):
        key = (t.data_ptr(), dim1, dim0, box1, box0)
        d = self._desc.get(key)
        if d is None:
            if len(self._desc) > 96:
                self._desc.clear()
            d = _mk_desc(t.data_ptr(), dim1, dim0, box1, box0, t.element_size())
            self._desc[key] = d
        return d

    def forward(
        self,
        hidden_states: torch.Tensor,   # (T_perm, H) bf16
        expert_offsets: torch.Tensor,  # (E+1,) int32
    ) -> torch.Tensor:
        T_perm, H = hidden_states.shape
        I = self.I                                                     # noqa: E741
        E = self.E
        dev = hidden_states.device
        out = torch.empty((T_perm, I), dtype=torch.bfloat16, device=dev)
        if T_perm == 0 or I == 0:
            return out

        x = hidden_states if hidden_states.is_contiguous() else hidden_states.contiguous()
        wg, wu = self.W_gate, self.W_up
        if expert_offsets.device != dev:
            expert_offsets = expert_offsets.to(dev, non_blocking=True)

        # ---- tile shape: 128x128x64 unless a dimension is smaller --------- #
        bm = TILE_M if T_perm >= TILE_M else _pow2_le(T_perm, 16)
        bn = 128 if I >= 128 else _pow2_le(I, 16)
        bk = 64 if H >= 64 else _pow2_le(H, 16)
        ns = 3
        while ns > 1 and (bm * bk + 2 * bk * bn) * 2 * ns > _SMEM_CAP:
            ns -= 1

        rows, nrows, exps, nm = self._table(T_perm, bm, dev)
        _tile_table_kernel[(E,)](
            expert_offsets, rows, nrows, exps, nm,
            E=E, E_POW2=triton.next_power_of_2(E), BLOCK_M=bm, CHUNK=256,
            num_warps=4,
        )

        grid = (_num_sms(),)
        # TMA needs 16B-aligned rows in both operands (element size is 2B).
        use_tma = (
            _mk_desc is not None
            and torch.cuda.get_device_capability(dev)[0] >= 9
            and H % 8 == 0 and I % 8 == 0 and bk % 8 == 0 and bn % 8 == 0
            and wg.is_contiguous() and wu.is_contiguous()
        )
        if use_tma:
            try:
                a_desc = self._descriptor(x, T_perm, H, bm, bk)
                wg_desc = self._descriptor(wg, E * H, I, bk, bn)
                wu_desc = self._descriptor(wu, E * H, I, bk, bn)
            except Exception:
                use_tma = False
        if use_tma:
            _moe_up_swiglu_tma[grid](
                a_desc, wg_desc, wu_desc, out,
                rows, nrows, exps, nm,
                H, I,
                BLOCK_M=bm, BLOCK_N=bn, BLOCK_K=bk, GROUP_M=8,
                num_warps=8, num_stages=ns,
            )
        else:
            _moe_up_swiglu_ptr[grid](
                x, wg, wu, out,
                rows, nrows, exps, nm,
                H, I,
                BLOCK_M=bm, BLOCK_N=bn, BLOCK_K=min(bk, 32), GROUP_M=8,
                num_warps=8, num_stages=4,
            )
        return out


# --------------------------------------------------------------------------- #
# Harness shims (mirrors 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]

20260725_085648_or-opus_anthropic_claude-opus-5_06_sonic_moe_swiglu