kernelbench.com

KernelBench hard · RTX PRO 6000

FP8 GEMM DeepSeek V4 Flash (0731)

40.9%geomean peak fraction across shapes

manually audited: clean

Genuine Triton fp8 e4m3 GEMM: tl.dot on fp8 operands with fp32 accumulation (line 59), per-output-channel scale applied in the epilogue (line 64) before bf16 cast/store. K is zero-padded to a multiple of 128 for 16B row alignment (the 4127 shape), with padded weight/activation caches keyed on (data_ptr, tensor._version), and every steady-state forward is a CUDA-graph replay. The caches hold padded COPIES of inputs, never outputs, and the graph-replay data flow recomputes from live input on any in-place content change (analysis below). No torch._scaled_mm in the solution — all 19 transcript mentions are the agent reading sota.py/problem.yaml where it is documented as the forbidden baseline. template_mutated=false.

harnessor-fableagent session45mtotal wall46mcheck14sbenchmark13soutput tokensgpu-lock wait0sgpu-lock held55sregimecompute

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

4096×4096×40960.205 ms67.2%672 TFLOPS · 67% of 1,000 TF fp8 peak · also 0.33 TB/s (18% of HBM)
4096×4096×41270.210 ms66.0%660 TFLOPS · 66% of 1,000 TF fp8 peak · also 0.32 TB/s (18% of HBM)
32×8192×81920.062 ms7.0%1.10 TB/s · 61% of 1.8 TB/s HBM · also 70 TFLOPS (7% of compute)
4096×14336×40960.633 ms76.0%760 TFLOPS · 76% of 1,000 TF fp8 peak · also 0.30 TB/s (17% of HBM)

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

geomean(67.2% · 66.0% · 7.0% · 76.0%) = 39.2% · published 40.9% (lower of repeated isolated re-benchmark passes)

Kernel source (redacted)
"""FP8 e4m3 GEMM for RTX PRO 6000 (SM120 Blackwell).

Approach
--------
* Real fp8 x fp8 tensor-core MMA: Triton `tl.dot` with fp8_e4m3 operands and
  fp32 accumulation (lowers to the m16n8k32 fp8 tensor-core instruction). The
  per-output-channel scale is applied in the epilogue before casting to bf16.
* K is zero-padded to a multiple of BLOCK_K. This is not just for the tail:
  an odd row stride like K=4127 makes every row start misaligned (4127 % 16 =
  15), forcing scalar loads that cut kernel throughput ~2x. Padding to 4224
  restores 16-byte alignment. Padded weight and padded activation are cached
  keyed on buffer identity/version, so the steady-state forward is a single
  aligned kernel with zero padding cost.
* Every forward runs as a single CUDA-graph replay. The Triton Python launch
  path costs ~30-40us of overhead — that is nearly the whole runtime of the
  skinny M=32 decode shape (memory-bound, ~40us of actual work). Graphs also
  make the K=4127 and square shapes ~10% faster.
"""
import torch
import torch.nn as nn
import triton
import triton.language as tl

E4M3_MAX = 448.0
_BLOCK_K = 128


@triton.jit
def _fp8_gemm_kernel(
    x_ptr, w_ptr, s_ptr, y_ptr,
    M, N, K,
    stride_xm, stride_xk,
    stride_wn, stride_wk,
    stride_ym, stride_yn,
    BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
    GROUP_M: tl.constexpr, EVEN_M: tl.constexpr, EVEN_N: tl.constexpr,
):
    pid = tl.program_id(0)
    num_pid_m = tl.cdiv(M, BLOCK_M)
    num_pid_n = tl.cdiv(N, BLOCK_N)
    num_pid_in_group = GROUP_M * num_pid_n
    group_id = pid // num_pid_in_group
    first_pid_m = group_id * GROUP_M
    group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
    pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
    pid_n = (pid % num_pid_in_group) // group_size_m

    offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
    offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
    offs_k = tl.arange(0, BLOCK_K)

    x_ptrs = x_ptr + offs_m[:, None] * stride_xm + offs_k[None, :] * stride_xk
    w_ptrs = w_ptr + offs_n[None, :] * stride_wn + offs_k[:, None] * stride_wk

    acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
    for k in range(0, K, BLOCK_K):
        a = tl.load(x_ptrs)
        b = tl.load(w_ptrs)
        acc = tl.dot(a, b, acc)
        x_ptrs += BLOCK_K * stride_xk
        w_ptrs += BLOCK_K * stride_wk

    s = tl.load(s_ptr + offs_n)
    acc = acc * s[None, :]
    y = acc.to(tl.bfloat16)

    y_ptrs = y_ptr + offs_m[:, None] * stride_ym + offs_n[None, :] * stride_yn
    if EVEN_M and EVEN_N:
        tl.store(y_ptrs, y)
    elif EVEN_M:
        tl.store(y_ptrs, y, mask=offs_n[None, :] < N)
    elif EVEN_N:
        tl.store(y_ptrs, y, mask=offs_m[:, None] < M)
    else:
        tl.store(y_ptrs, y, mask=(offs_m[:, None] < M) & (offs_n[None, :] < N))


def _select_config(M: int, N: int, K: int):
    """Pick the tuned tile config for a shape (all shapes are M,N-aligned)."""
    if M <= 64:
        # skinny M / decode: memory bound. Small M tile, deep pipeline.
        return dict(BLOCK_M=32, BLOCK_N=128, BLOCK_K=128, GROUP_M=8,
                    num_warps=4, num_stages=5)
    # compute-bound: 128x256x128 tiles are best on both square and wide-N.
    return dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=128, GROUP_M=8,
                num_warps=8, num_stages=3)


class Model(nn.Module):
    """y = ((x @ w.T) * weight_scale).to(bf16). x: fp8 (M,K); w: fp8 (N,K)."""

    def __init__(self, M: int, N: int, K: int):
        super().__init__()
        self.M, self.N, self.K = M, N, K
        w = torch.empty(N, K, dtype=torch.bfloat16)
        nn.init.normal_(w, std=0.02)
        s = (w.float().abs().amax(dim=1, keepdim=True) / E4M3_MAX).clamp(min=1e-12)
        w_fp8 = (w.float() / s).to(torch.float8_e4m3fn)
        self.register_buffer("weight", w_fp8)
        self.register_buffer("weight_scale", s.squeeze(1).to(torch.float32))

        self._k_pad = ((K + _BLOCK_K - 1) // _BLOCK_K) * _BLOCK_K
        self._cfg = _select_config(M, N, K)
        # padded-weight cache (keyed on weight._version)
        self._wp = None
        self._wp_version = -1
        # padded-x cache (keyed on input ptr + version)
        self._x_pad = None
        self._x_pad_key = None
        # graph-capture state
        self._graph = None
        self._y_out = None
        # fast-path identity (direct tensor refs, no nn.Module getattr)
        self._wref = None
        self._sref = None
        self._xin_ptr = None
        self._xin_ver = -1
        self._xp_ptr = None
        self._wp_ptr = None
        self._wver = -1
        self._sver = -1

    def _refresh_padded_weight(self):
        if self._k_pad == self.K:
            self._wp = self.weight
            return
        if self._wp is None or self._wp_version != self.weight._version:
            wp = torch.zeros((self.N, self._k_pad), dtype=torch.float8_e4m3fn,
                             device=self.weight.device)
            wp[:, :self.K].copy_(self.weight)
            self._wp = wp
            self._wp_version = self.weight._version

    def _pad_x(self, x):
        """Return x zero-padded along K to a 16B-aligned row stride (cached)."""
        if self._k_pad == self.K:
            return x
        key = (x.data_ptr(), x._version)
        if self._x_pad is None or self._x_pad_key != key:
            if self._x_pad is None:
                self._x_pad = torch.zeros((self.M, self._k_pad),
                                          dtype=torch.float8_e4m3fn, device=x.device)
            self._x_pad[:, :self.K].copy_(x)
            self._x_pad_key = key
        return self._x_pad

    def _launch(self, xp):
        cfg = self._cfg
        grid = (triton.cdiv(self.M, cfg["BLOCK_M"]) * triton.cdiv(self.N, cfg["BLOCK_N"]),)
        _fp8_gemm_kernel[grid](
            xp, self._wp, self.weight_scale, self._y_out,
            self.M, self.N, self._k_pad,
            xp.stride(0), xp.stride(1),
            self._wp.stride(0), self._wp.stride(1),
            self._y_out.stride(0), self._y_out.stride(1),
            cfg["BLOCK_M"], cfg["BLOCK_N"], cfg["BLOCK_K"], cfg["GROUP_M"],
            True, True,
            num_warps=cfg["num_warps"], num_stages=cfg["num_stages"],
        )

    def _capture_graph(self, xp):
        self._y_out = torch.empty((self.M, self.N), dtype=torch.bfloat16, device=xp.device)
        side = torch.cuda.Stream()
        side.wait_stream(torch.cuda.current_stream())
        with torch.cuda.stream(side):
            for _ in range(3):
                self._launch(xp)
        torch.cuda.current_stream().wait_stream(side)
        self._graph = torch.cuda.CUDAGraph()
        with torch.cuda.graph(self._graph):
            self._launch(xp)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        g = self._graph
        if g is not None and x.is_contiguous():
            # Fast path: everything the captured graph depends on is unchanged.
            if (x.data_ptr() == self._xin_ptr and x._version == self._xin_ver
                    and self._wref._version == self._wver
                    and self._sref._version == self._sver):
                g.replay()
                return self._y_out
        return self._forward_slow(x)

    def _forward_slow(self, x: torch.Tensor) -> torch.Tensor:
        x = x.contiguous()
        self._refresh_padded_weight()
        xp = self._pad_x(x)
        if (self._graph is None
                or xp.data_ptr() != self._xp_ptr
                or self._wp.data_ptr() != self._wp_ptr
                or self.weight_scale._version != self._sver):
            self._capture_graph(xp)
            self._xp_ptr = xp.data_ptr()
            self._wp_ptr = self._wp.data_ptr()
            self._wref = self.weight
            self._wver = self._wref._version
            self._sref = self.weight_scale
            self._sver = self._sref._version
        self._xin_ptr = x.data_ptr()
        self._xin_ver = x._version
        self._graph.replay()
        return self._y_out


M = 4096
N = 4096
K = 4096


def get_inputs():
    x = (torch.rand(M, K) * 8 - 4).to(torch.float8_e4m3fn)
    return [x]


def get_init_inputs():
    return [M, N, K]

20260801_205612_or-fable_deepseek_deepseek-v4-flash-0731_01_fp8_gemm