KernelBench hard · RTX PRO 6000
FP8 GEMM DeepSeek V4 Flash (0731)
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.
Per-shape vs governing ceilingeach shape graded against whichever binds — fp8 compute or HBM bandwidth
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