kernelbench.com

KernelBench hard · H100

Sonic MoE Claude Opus 4.8

5.79%geomean peak fraction across shapes
harnessclaudeagent session45mtotal wall57mcheck5mbenchmark7moutput tokens2,113gpu-lock wait5mgpu-lock held6mregimecompute

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

32768×4096×1536×128×819.438 ms5.6%0.32 TB/s · 16% of 2.0 TB/s HBM · also 42 TFLOPS (6% of compute)
4096×2048×1024×64×40.677 ms6.7%0.94 TB/s · 46% of 2.0 TB/s HBM · also 51 TFLOPS (7% of compute)
16384×2048×4096×64×814.612 ms5.0%0.26 TB/s · 13% of 2.0 TB/s HBM · also 38 TFLOPS (5% of compute)

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

geomean(5.6% · 6.7% · 5.0%) = 5.7%

Kernel source (redacted)
"""Top-K MoE up-projection: variable-length grouped GEMM + fused SwiGLU.

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

Implemented as a single Triton grouped-GEMM kernel. Each program computes one
(BLOCK_M, BLOCK_N) output tile, accumulating *both* the gate and up projections
over the H (K) dimension reusing the same A tile, then fuses SwiGLU in the
epilogue. Tokens are already permuted into expert order, so each M-tile lives
entirely within one expert; an M-tile schedule (tile -> expert, tile -> row
start) is precomputed on-device with no host sync.
"""
from __future__ import annotations

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


# ----------------------------------------------------------------------------
# M-tile schedule: map each padded M-tile to (expert_id, global_row_start).
# Computed entirely on-device with searchsorted -> no CUDA sync.
# ----------------------------------------------------------------------------
def _build_schedule(expert_offsets: torch.Tensor, E: int, T_perm: int, BLOCK_M: int):
    counts = (expert_offsets[1:] - expert_offsets[:-1]).to(torch.int64)        # (E,)
    ntiles = (counts + (BLOCK_M - 1)) // BLOCK_M                               # (E,)
    cum = torch.cumsum(ntiles, 0)                                             # (E,)
    total_tiles = int(T_perm // BLOCK_M + E)  # host-side upper bound (no sync)
    t = torch.arange(total_tiles, device=expert_offsets.device, dtype=torch.int64)
    # expert for tile t = number of experts whose cumulative tile count <= t
    expert_of = torch.searchsorted(cum, t, right=True)                        # (total,)
    valid = expert_of < E
    e_clamped = torch.clamp(expert_of, max=E - 1)
    tile_base = cum - ntiles                                                   # first tile idx of each expert
    tile_in_e = t - tile_base[e_clamped]
    row_start = expert_offsets[:-1].to(torch.int64)[e_clamped] + tile_in_e * BLOCK_M
    # encode invalid tiles with expert = E (kernel early-exits)
    expert_id = torch.where(valid, e_clamped, torch.full_like(e_clamped, E))
    return (expert_id.to(torch.int32).contiguous(),
            row_start.to(torch.int32).contiguous(),
            total_tiles)


def _configs():
    cfgs = []
    for bn in (128, 256):
        for bk in (64, 128):
            for w in (4, 8):
                for s in (3, 4):
                    cfgs.append(triton.Config(
                        {"BLOCK_N": bn, "BLOCK_K": bk}, num_warps=w, num_stages=s))
    return cfgs


@triton.autotune(configs=_configs(), key=["H", "I", "BLOCK_M"])
@triton.jit
def _grouped_swiglu_kernel(
    a_ptr, wg_ptr, wu_ptr, out_ptr,
    expert_id_ptr, row_start_ptr, expert_offsets_ptr,
    T_perm, H, I, n_experts,
    stride_am, stride_ak,
    stride_we, stride_wk, stride_wn,
    stride_om, stride_on,
    BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
):
    pid_m = tl.program_id(0)
    pid_n = tl.program_id(1)

    e = tl.load(expert_id_ptr + pid_m)
    # early-exit padding tiles (sentinel expert == n_experts)
    if e >= n_experts:
        return

    row0 = tl.load(row_start_ptr + pid_m)
    # number of experts available via expert_offsets length is E+1; sentinel handled below
    n_valid_total = tl.load(expert_offsets_ptr + e + 1)

    rm = row0 + tl.arange(0, BLOCK_M)
    rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
    rk = tl.arange(0, BLOCK_K)

    row_mask = rm < n_valid_total

    a_base = a_ptr + rm[:, None] * stride_am
    wg_base = wg_ptr + e * stride_we + rn[None, :] * stride_wn
    wu_base = wu_ptr + e * stride_we + rn[None, :] * stride_wn

    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):
        k = k0 + rk
        a = tl.load(a_base + k[None, :] * stride_ak,
                    mask=row_mask[:, None], other=0.0)
        wg = tl.load(wg_base + k[:, None] * stride_wk)
        wu = tl.load(wu_base + k[:, None] * stride_wk)
        acc_g = tl.dot(a, wg, acc_g)
        acc_u = tl.dot(a, wu, acc_u)

    # SwiGLU: silu(g) * u
    g = acc_g
    silu = g * tl.sigmoid(g)
    out = (silu * acc_u).to(out_ptr.dtype.element_ty)

    out_ptrs = out_ptr + rm[:, None] * stride_om + rn[None, :] * stride_on
    tl.store(out_ptrs, out, mask=row_mask[:, None])


class Model(nn.Module):
    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.BLOCK_M = 128
        self._sched_cache = {}

    def _schedule(self, expert_offsets, T_perm):
        key = (expert_offsets.data_ptr(), T_perm, self.BLOCK_M)
        cached = self._sched_cache.get(key)
        if cached is not None:
            return cached
        sched = _build_schedule(expert_offsets, self.E, T_perm, self.BLOCK_M)
        self._sched_cache[key] = sched
        return sched

    def forward(self, hidden_states: torch.Tensor, expert_offsets: torch.Tensor):
        T_perm, H = hidden_states.shape
        I = self.I
        out = torch.empty(T_perm, I, dtype=torch.bfloat16, device=hidden_states.device)
        expert_id, row_start, total_tiles = self._schedule(expert_offsets, T_perm)

        wg = self.W_gate
        wu = self.W_up
        grid = lambda meta: (total_tiles, triton.cdiv(I, meta["BLOCK_N"]))
        _grouped_swiglu_kernel[grid](
            hidden_states, wg, wu, out,
            expert_id, row_start, expert_offsets,
            T_perm, H, I, self.E,
            hidden_states.stride(0), hidden_states.stride(1),
            wg.stride(0), wg.stride(1), wg.stride(2),
            out.stride(0), out.stride(1),
            BLOCK_M=self.BLOCK_M,
        )
        return out


# Module-level shape shims (rewritten by check.py / benchmark.py per shape).
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]

20260618_041724_claude_claude-opus-4-8_06_sonic_moe_swiglu