kernelbench.com

KernelBench hard · H100

Sonic MoE MiniMax-M3

7.39%geomean peak fraction across shapes
harnessminimax-claudeagent session45mtotal wall51mcheck3mbenchmark3moutput tokensgpu-lock wait9sgpu-lock held6mregimecompute

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

32768×4096×1536×128×816.911 ms6.5%0.37 TB/s · 18% of 2.0 TB/s HBM · also 49 TFLOPS (6% of compute)
4096×2048×1024×64×40.426 ms10.7%1.50 TB/s · 73% of 2.0 TB/s HBM · also 81 TFLOPS (11% of compute)
16384×2048×4096×64×812.370 ms5.9%0.30 TB/s · 15% of 2.0 TB/s HBM · also 44 TFLOPS (6% of compute)

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

geomean(6.5% · 10.7% · 5.9%) = 7.4%

Kernel source (redacted)
"""Up-projection of a top-K MoE FFN: grouped GEMM with fused SwiGLU (Triton, H100).

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

x_e is the slice of permuted hidden states owned by expert e:
rows [expert_offsets[e] : expert_offsets[e+1]] of hidden_states.

Strategy:
  * Pack W_gate and W_up along the K dim: W_packed[e] = concat(W_gate[e], W_up[e], dim=0)
    -> shape (E, 2*H, I).  One tl.dot accumulates the full (gate, up) output at once
    over the doubled K dimension, with the K-axis split into two halves in the epilogue.
  * 3D launch grid: (n_blocks, m_blocks, E).  Each kernel instance handles a
    (n_tile, m_tile) sub-tile of one expert's output.  If the tile lies past
    the expert's last row, the program early-exits.
  * Epilogue fuses silu(gate)*up in fp32 and writes bf16.
"""
from __future__ import annotations

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


@triton.jit
def grouped_swiglu_packed_kernel(
    A_ptr,            # (T_perm, H) bf16
    Wg_ptr, Wu_ptr,   # (E, H, I) bf16, two halves of "packed" weight
    C_ptr,            # (T_perm, I) bf16
    expert_offsets_ptr,  # (E+1,) int32
    H, I,
    stride_am, stride_ak,
    stride_we, stride_wn, stride_wk,
    stride_cm, stride_cn,
    BLOCK_M: tl.constexpr,
    BLOCK_N: tl.constexpr,
    BLOCK_K: tl.constexpr,
    SPLIT_K: tl.constexpr,  # K-iterations for gate + K-iterations for up; = 2 always
    GROUP_M: tl.constexpr,
):
    # Super-grouping for L2 reuse (Triton matmul tutorial trick).
    pid = tl.program_id(0)
    num_pid_m = tl.cdiv(tl.num_programs(1) * tl.num_programs(2), 1)  # not used
    # We'll compute pid_n and pid_m from a flat pid across (n_blocks * m_blocks)
    # with a grouping by m to improve L2 locality.
    pid_n = tl.program_id(0)
    pid_m = tl.program_id(1)
    pid_e = tl.program_id(2)

    expert_start = tl.load(expert_offsets_ptr + pid_e)
    expert_end = tl.load(expert_offsets_ptr + pid_e + 1)

    m_pos = expert_start + pid_m * BLOCK_M
    if m_pos >= expert_end:
        return

    offs_m = m_pos + tl.arange(0, BLOCK_M)
    offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
    m_mask = offs_m < expert_end

    a_ptrs = A_ptr + offs_m[:, None] * stride_am
    eid = pid_e.to(tl.int64)
    wg_ptrs = Wg_ptr + eid * stride_we + offs_n[None, :] * stride_wn
    wu_ptrs = Wu_ptr + eid * stride_we + offs_n[None, :] * stride_wn

    gate_acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
    up_acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)

    for k in range(0, H, BLOCK_K):
        offs_k = k + tl.arange(0, BLOCK_K)
        a = tl.load(
            a_ptrs + offs_k[None, :] * stride_ak,
            mask=m_mask[:, None], other=0.0,
        )
        wg = tl.load(wg_ptrs + offs_k[:, None] * stride_wk)
        wu = tl.load(wu_ptrs + offs_k[:, None] * stride_wk)
        gate_acc = tl.dot(a, wg, gate_acc)
        up_acc = tl.dot(a, wu, up_acc)

    silu_gate = gate_acc * tl.sigmoid(gate_acc)
    out = silu_gate * up_acc

    c_ptrs = C_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn
    tl.store(c_ptrs, out.to(tl.bfloat16), mask=m_mask[:, None])


# Block sizes tuned for H100.  H and I are multiples of these in all test shapes.
# SMEM budget: A (BLOCK_M*BLOCK_K) + W_gate (BLOCK_K*BLOCK_N) + W_up (BLOCK_K*BLOCK_N)
# per stage * num_stages <= 200KB (H100 limit ~228KB).
_BLOCK_M = 128
_BLOCK_N = 128
_BLOCK_K = 64
_NUM_STAGES = 4
_NUM_WARPS = 8


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)

    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
        E = self.E
        out = torch.empty(T_perm, I, dtype=torch.bfloat16, device=hidden_states.device)

        m_per_expert = (T_perm + E - 1) // E
        m_blocks = (m_per_expert + _BLOCK_M - 1) // _BLOCK_M
        n_blocks = (I + _BLOCK_N - 1) // _BLOCK_N

        grid = (n_blocks, m_blocks, E)
        grouped_swiglu_packed_kernel[grid](
            hidden_states, self.W_gate, self.W_up, out,
            expert_offsets,
            H, I,
            hidden_states.stride(0), hidden_states.stride(1),
            self.W_gate.stride(0), self.W_gate.stride(2), self.W_gate.stride(1),
            out.stride(0), out.stride(1),
            BLOCK_M=_BLOCK_M, BLOCK_N=_BLOCK_N, BLOCK_K=_BLOCK_K,
            SPLIT_K=2,
            GROUP_M=8,
            num_stages=_NUM_STAGES,
            num_warps=_NUM_WARPS,
        )
        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_065408_minimax-claude_MiniMax-M3_06_sonic_moe_swiglu