KernelBench hard · RTX PRO 6000
FP8 GEMM Qwen 3.8 Max
manually audited: clean
The scored implementation is genuine FP8 computation. For M > 64 it invokes a CUTLASS 3 SM120 f8f6f4 tensor-core GEMM with FP32 accumulation and a fused per-output-channel scale/bf16 epilogue; if the extension is unavailable it runs a Triton tl.dot GEMM. M <= 64 uses a dedicated Triton tl.dot kernel. Every call allocates a fresh output and launches a GEMM; there is no constant output, output memoization, CUDA graph replay, reference call, forbidden vendor op, or grader/stack sniffing. The only semantic cache is an input transformation cache on the odd-K padding path: _pad_cache reuses padded copies when the source tensor has the same Python identity and _version. A same-buffer overwrite test on the canonical odd-K shape confirmed that both x.copy_ and weight.copy_ advance the tracked versions, invalidate stale padded copies, recompute changed outputs, and remain reference-correct. Therefore the implementation verdict is clean. The subsequent publish-grade sequential isolated regrade passed with correct=true and peak_fraction=0.3269.
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(61.3% · 58.8% · 4.5% · 69.8%) = 32.7%
Kernel source (redacted)
"""FP8 e4m3 GEMM for RTX PRO 6000 (SM120 Blackwell).
y = ((x @ w.T) * weight_scale).to(bf16)
x: fp8_e4m3 (M, K); w: fp8_e4m3 (N, K); weight_scale: fp32 (N,).
Implementation notes
--------------------
* Genuine fp8 x fp8 tensor-core MMA (fp32 accumulate) everywhere; the
per-channel dequant scale is fused into the GEMM epilogue.
* Primary path (M > 64, K aligned): CUTLASS 3 SM120 warp-specialized TMA
kernel (f8f6f4 tensor-core MMA instruction, TN layout) with a custom
epilogue visitor tree that multiplies the accumulator by the
per-output-channel scale before converting to bf16. Tile 128x256x64,
1x1 cluster, grouped rasterization (max_swizzle_size) for L2 reuse.
* Fallback path: Triton tiled GEMM (mask-free main loop). The SM120 Triton
backend punishes predicated loads inside the fp8 loop severely (~5x), and
odd K also misaligns every row (stride not a multiple of 16B), so operands
are zero-padded to an aligned K and the hot loop stays mask-free. The
padded weight is cached across calls (invalidated through the tensor's
version counter, since the eval harness mutates buffers in place).
* Skinny path (M <= 64): dedicated Triton kernel, one CTA per N-tile
streaming the full K range. Bandwidth-bound; with the benchmark's L2 flush
this runs right at the DRAM ceiling (read + dirty-line writeback).
"""
import os
import torch
import torch.nn as nn
import triton
import triton.language as tl
E4M3_MAX = 448.0
_K_ALIGN = 32 # pad granularity for the CUTLASS path (K%32==0 measured safe+fast)
_K_ALIGN_TRITON = 256 # pad granularity for the Triton path (max BK in its spaces)
# ---------------------------------------------------------------------------
# CUTLASS SM120 extension (built lazily once; cached by torch extensions)
# ---------------------------------------------------------------------------
_CUTLASS_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include "cutlass/cutlass.h"
#include "cutlass/float8.h"
#include "cutlass/bfloat16.h"
#include "cutlass/gemm/dispatch_policy.hpp"
#include "cutlass/gemm/collective/collective_builder.hpp"
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/kernel_hardware_info.h"
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/epilogue/fusion/sm90_visitor_load_tma_warpspecialized.hpp"
#include "cutlass/epilogue/fusion/sm90_visitor_compute_tma_warpspecialized.hpp"
using namespace cute;
// Epilogue fusion: D = acc * row_scale, row_scale has length N (one value per
// output channel), broadcast along M.
template <class TileShape_MNK>
struct PerChannelScaleEVT {
using RowScale = cutlass::epilogue::fusion::Sm90RowBroadcast<
0, TileShape_MNK, float, float, Stride<_0,_1,_0>, 4>;
using Compute = cutlass::epilogue::fusion::Sm90Compute<
cutlass::multiplies, cutlass::bfloat16_t, float,
cutlass::FloatRoundStyle::round_to_nearest>;
using EVT = cutlass::epilogue::fusion::Sm90EVT<Compute, RowScale,
cutlass::epilogue::fusion::Sm90AccFetch>;
};
template <class TileShape_MNK, class Schedule>
struct GemmConfig {
using ElementA = cutlass::float_e4m3_t;
using ElementB = cutlass::float_e4m3_t;
using ElementD = cutlass::bfloat16_t;
using ElementAcc = float;
using ElementCompute = float;
using LayoutA = cutlass::layout::RowMajor; // (M,K) k-major
using LayoutB = cutlass::layout::ColumnMajor; // BLAS (K,N) col-major == (N,K) k-major (TN)
using ClusterShape = Shape<_1,_1,_1>;
static constexpr int AlignA = 16;
static constexpr int AlignB = 16;
static constexpr int AlignD = 8;
using FusionOp = typename PerChannelScaleEVT<TileShape_MNK>::EVT;
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp,
TileShape_MNK, ClusterShape,
cutlass::epilogue::collective::EpilogueTileAuto,
ElementAcc, ElementCompute,
void, cutlass::layout::RowMajor, 1, // C unused
ElementD, cutlass::layout::RowMajor, AlignD,
cutlass::epilogue::collective::EpilogueScheduleAuto,
FusionOp
>::CollectiveOp;
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp,
ElementA, LayoutA, AlignA,
ElementB, LayoutB, AlignB,
ElementAcc,
TileShape_MNK, ClusterShape,
cutlass::gemm::collective::StageCountAutoCarveout<
static_cast<int>(sizeof(typename CollectiveEpilogue::SharedStorage))>,
Schedule
>::CollectiveOp;
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
Shape<int,int,int,int>, CollectiveMainloop, CollectiveEpilogue, void>;
using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
};
template <class Gemm>
void run_gemm(uint8_t const* A, uint8_t const* B, float const* scale,
uint8_t* D, int M, int N, int K, int swizzle, cudaStream_t stream) {
using StrideA = typename Gemm::GemmKernel::StrideA;
using StrideB = typename Gemm::GemmKernel::StrideB;
using StrideD = typename Gemm::GemmKernel::StrideD;
cutlass::KernelHardwareInfo hw_info;
hw_info.device_id = 0;
hw_info.sm_count = cutlass::KernelHardwareInfo::query_device_multiprocessor_count(0);
typename Gemm::Arguments args{
cutlass::gemm::GemmUniversalMode::kGemm,
{M, N, K, 1},
{ reinterpret_cast<cutlass::float_e4m3_t const*>(A), StrideA{K, _1{}, 0},
reinterpret_cast<cutlass::float_e4m3_t const*>(B), StrideB{K, _1{}, 0} },
{ {}, nullptr, StrideD{}, reinterpret_cast<cutlass::bfloat16_t*>(D), StrideD{N, _1{}, 0} },
hw_info
};
// EVT arguments mirror the visitor tree: {row_scale, acc_fetch, compute}.
args.epilogue.thread = { { scale, 0.f, Stride<_0,_1,_0>{} }, {}, {} };
args.scheduler.max_swizzle_size = swizzle;
Gemm gemm;
size_t ws_size = Gemm::get_workspace_size(args);
void* ws_ptr = nullptr;
at::Tensor ws;
if (ws_size > 0) {
ws = at::empty({(long)ws_size}, at::TensorOptions().dtype(at::kByte).device(at::kCUDA));
ws_ptr = ws.data_ptr();
}
TORCH_CHECK(gemm.can_implement(args) == cutlass::Status::kSuccess, "cutlass cannot implement");
TORCH_CHECK(gemm.initialize(args, ws_ptr, stream) == cutlass::Status::kSuccess, "cutlass init failed");
TORCH_CHECK(gemm.run(stream, nullptr, false) == cutlass::Status::kSuccess, "cutlass run failed");
}
// Wide-N tile for large problems; narrower tile for small N.
using CfgWide = GemmConfig<Shape<_128,_256,_64>, cutlass::gemm::collective::KernelScheduleAuto>;
using CfgNarrow = GemmConfig<Shape<_128,_128,_64>, cutlass::gemm::collective::KernelScheduleAuto>;
void gemm_wide(int64_t A, int64_t B, int64_t S, int64_t D, int64_t M, int64_t N, int64_t K, int64_t sw) {
run_gemm<CfgWide::Gemm>((uint8_t*)A, (uint8_t*)B, (float*)S, (uint8_t*)D, M, N, K, sw,
at::cuda::getCurrentCUDAStream());
}
void gemm_narrow(int64_t A, int64_t B, int64_t S, int64_t D, int64_t M, int64_t N, int64_t K, int64_t sw) {
run_gemm<CfgNarrow::Gemm>((uint8_t*)A, (uint8_t*)B, (float*)S, (uint8_t*)D, M, N, K, sw,
at::cuda::getCurrentCUDAStream());
}
"""
_CUTLASS_CPP = """
void gemm_wide(int64_t A, int64_t B, int64_t S, int64_t D, int64_t M, int64_t N, int64_t K, int64_t sw);
void gemm_narrow(int64_t A, int64_t B, int64_t S, int64_t D, int64_t M, int64_t N, int64_t K, int64_t sw);
"""
_cutlass_ext = None
_cutlass_failed = False
def _ensure_build_tools_on_path() -> None:
"""JIT builds need `ninja`; make sure the venv bin dir is searchable."""
import shutil
if shutil.which("ninja"):
return
try:
# Walk up from torch's install dir until a sibling bin/ninja appears
# (i.e. the venv root that holds both torch and ninja).
d = os.path.dirname(os.path.abspath(torch.__file__))
for _ in range(6):
cand = os.path.join(d, "bin", "ninja")
if os.path.isfile(cand):
os.environ["PATH"] = os.path.join(d, "bin") + os.pathsep + os.environ.get("PATH", "")
return
d = os.path.dirname(d)
except Exception:
pass
def _get_cutlass():
"""Lazily build (once) the CUTLASS extension; None if unavailable."""
global _cutlass_ext, _cutlass_failed
if _cutlass_ext is not None or _cutlass_failed:
return _cutlass_ext
try:
from torch.utils.cpp_extension import load_inline
_ensure_build_tools_on_path()
here = os.path.dirname(os.path.abspath(__file__))
extra_flags = ["-O3", "-std=c++17", "-arch=sm_120a", "--expt-relaxed-constexpr"]
# Use a local CUTLASS checkout if present (scratch during development);
# otherwise fall back to any system-installed CUTLASS headers.
local_cutlass = os.path.join(here, "scratch", "cutlass")
if os.path.isdir(os.path.join(local_cutlass, "include")):
extra_flags += [
f"-I{os.path.join(local_cutlass, 'include')}",
f"-I{os.path.join(local_cutlass, 'tools', 'util', 'include')}",
]
_cutlass_ext = load_inline(
name="fp8_gemm_sm120_cutlass",
cpp_sources=[_CUTLASS_CPP],
cuda_sources=[_CUTLASS_SRC],
functions=["gemm_wide", "gemm_narrow"],
extra_cuda_cflags=extra_flags,
verbose=False,
)
except Exception:
_cutlass_failed = True
_cutlass_ext = None
return _cutlass_ext
# ---------------------------------------------------------------------------
# Triton kernels (fallback for general shapes; primary for skinny M)
# ---------------------------------------------------------------------------
@triton.autotune(
configs=[
triton.Config({"BM": 128, "BN": 128, "BK": 128, "GM": 8}, num_warps=8, num_stages=3),
triton.Config({"BM": 128, "BN": 128, "BK": 64, "GM": 8}, num_warps=8, num_stages=4),
triton.Config({"BM": 128, "BN": 128, "BK": 64, "GM": 8}, num_warps=8, num_stages=5),
triton.Config({"BM": 128, "BN": 256, "BK": 64, "GM": 8}, num_warps=8, num_stages=3),
triton.Config({"BM": 128, "BN": 256, "BK": 64, "GM": 8}, num_warps=8, num_stages=4),
triton.Config({"BM": 256, "BN": 128, "BK": 64, "GM": 8}, num_warps=8, num_stages=4),
triton.Config({"BM": 64, "BN": 128, "BK": 128, "GM": 8}, num_warps=4, num_stages=4),
triton.Config({"BM": 128, "BN": 128, "BK": 128, "GM": 16}, num_warps=8, num_stages=3),
],
key=["M", "N", "K"],
)
@triton.jit
def _fp8_gemm_kernel(
A, B, C, S,
M, N, K,
stride_am, stride_ak,
stride_bn, stride_bk,
stride_cm, stride_cn,
BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr, GM: tl.constexpr,
):
pid = tl.program_id(0)
grid_m = tl.cdiv(M, BM)
grid_n = tl.cdiv(N, BN)
# Grouped (swizzled) launch order for L2 reuse of both operands.
width = GM * grid_n
group_id = pid // width
group_size = min(grid_m - group_id * GM, GM)
pid_m = group_id * GM + (pid % group_size)
pid_n = (pid % width) // group_size
rm = pid_m * BM + tl.arange(0, BM)
rn = pid_n * BN + tl.arange(0, BN)
rk = tl.arange(0, BK)
A_ptr = A + rm[:, None] * stride_am + rk[None, :] * stride_ak
B_ptr = B + rn[None, :] * stride_bn + rk[:, None] * stride_bk # (BK, BN)
acc = tl.zeros((BM, BN), dtype=tl.float32)
for _kb in range(0, K // BK):
a = tl.load(A_ptr)
b = tl.load(B_ptr)
acc = tl.dot(a, b, acc)
A_ptr += BK * stride_ak
B_ptr += BK * stride_bk
s = tl.load(S + rn) # per-output-channel dequant scale
acc = acc * s[None, :]
C_ptr = C + rm[:, None] * stride_cm + rn[None, :] * stride_cn
mask = (rm[:, None] < M) & (rn[None, :] < N)
tl.store(C_ptr, acc.to(tl.bfloat16), mask=mask)
@triton.autotune(
configs=[
triton.Config({"BN": 64, "BK": 256}, num_warps=4, num_stages=3),
triton.Config({"BN": 64, "BK": 256}, num_warps=4, num_stages=5),
triton.Config({"BN": 64, "BK": 256}, num_warps=8, num_stages=3),
triton.Config({"BN": 64, "BK": 256}, num_warps=8, num_stages=5),
triton.Config({"BN": 32, "BK": 256}, num_warps=4, num_stages=5),
triton.Config({"BN": 128, "BK": 128}, num_warps=8, num_stages=4),
triton.Config({"BN": 64, "BK": 128}, num_warps=4, num_stages=8),
],
key=["M", "N", "K"],
)
@triton.jit
def _fp8_gemm_skinny_kernel(
A, B, C, S,
M, N, K,
stride_am, stride_ak,
stride_bn, stride_bk,
stride_cm, stride_cn,
BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr,
):
pid_n = tl.program_id(0)
pid_m = tl.program_id(1)
rm = pid_m * BM + tl.arange(0, BM)
rn = pid_n * BN + tl.arange(0, BN)
rk = tl.arange(0, BK)
A_ptr = A + rm[:, None] * stride_am + rk[None, :] * stride_ak
B_ptr = B + rn[None, :] * stride_bn + rk[:, None] * stride_bk
acc = tl.zeros((BM, BN), dtype=tl.float32)
for _k in range(0, K, BK):
a = tl.load(A_ptr)
b = tl.load(B_ptr)
acc = tl.dot(a, b, acc)
A_ptr += BK * stride_ak
B_ptr += BK * stride_bk
s = tl.load(S + rn) # per-output-channel dequant scale
acc = acc * s[None, :]
C_ptr = C + rm[:, None] * stride_cm + rn[None, :] * stride_cn
tl.store(C_ptr, acc.to(tl.bfloat16), mask=(rm[:, None] < M) & (rn[None, :] < N))
def _pad_last_dim(t: torch.Tensor, k_pad: int) -> torch.Tensor:
"""Zero-pad (rows, K) -> (rows, k_pad); zeros keep the GEMM exact."""
out = torch.zeros((t.shape[0], k_pad), dtype=t.dtype, device=t.device)
out[:, : t.shape[1]].copy_(t)
return out
def _fp8_gemm_triton(x: torch.Tensor, w: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
M, K = x.shape
N = w.shape[0]
y = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
grid = lambda meta: (
triton.cdiv(M, meta["BM"]) * triton.cdiv(N, meta["BN"]),
)
_fp8_gemm_kernel[grid](
x, w, y, scale,
M, N, K,
x.stride(0), x.stride(1),
w.stride(0), w.stride(1),
y.stride(0), y.stride(1),
)
return y
def _fp8_gemm_skinny(x: torch.Tensor, w: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
M, K = x.shape
N = w.shape[0]
y = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
BM = max(16, triton.next_power_of_2(M))
grid = lambda meta: (triton.cdiv(N, meta["BN"]), triton.cdiv(M, BM))
_fp8_gemm_skinny_kernel[grid](
x, w, y, scale,
M, N, K,
x.stride(0), x.stride(1),
w.stride(0), w.stride(1),
y.stride(0), y.stride(1),
BM=BM,
)
return y
class Model(nn.Module):
"""y = ((x @ w.T) * weight_scale).to(bf16).
x: fp8_e4m3 (M, K). w: fp8_e4m3 (N, K) normalized to the e4m3 range.
weight_scale: (N,) per-output-channel dequant scale.
"""
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) # (N,1)
w_fp8 = (w.float() / s).to(torch.float8_e4m3fn)
self.register_buffer("weight", w_fp8) # (N, K) fp8
self.register_buffer("weight_scale", s.squeeze(1).to(torch.float32)) # (N,)
# Cache of zero-padded operands for misaligned K. Entries hold a strong
# reference to the source tensor (so identity checks are sound) plus its
# version counter (so in-place mutation invalidates the entry).
self._pad_cache: dict = {}
def _padded(self, t: torch.Tensor, k_pad: int) -> torch.Tensor:
key = id(t)
ent = self._pad_cache.get(key)
if ent is not None and ent[0] is t and ent[1] == t._version and ent[2] == k_pad:
return ent[3]
padded = _pad_last_dim(t, k_pad)
if len(self._pad_cache) > 8:
self._pad_cache.clear()
self._pad_cache[key] = (t, t._version, k_pad, padded)
return padded
def forward(self, x: torch.Tensor) -> torch.Tensor:
# TMA / vectorized loads want contiguous, 16B-aligned rows; the
# harness-provided tensors already are, so this is a free no-op there.
x = x.contiguous()
M, K = x.shape
N = self.weight.shape[0]
scale = self.weight_scale
# --- skinny (decode) shapes: bandwidth-bound Triton kernel ---------
if M <= 64:
if K % _K_ALIGN_TRITON != 0:
k_pad = (K + _K_ALIGN_TRITON - 1) // _K_ALIGN_TRITON * _K_ALIGN_TRITON
x = self._padded(x, k_pad)
w = self._padded(self.weight, k_pad)
return _fp8_gemm_skinny(x, w, scale)
return _fp8_gemm_skinny(x, self.weight, scale)
# --- regular shapes: CUTLASS first, Triton fallback ----------------
ext = _get_cutlass()
if ext is not None:
k_pad = (K + _K_ALIGN - 1) // _K_ALIGN * _K_ALIGN
if k_pad != K:
x = self._padded(x, k_pad)
w = self._padded(self.weight, k_pad)
else:
w = self.weight
y = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
swizzle = 4 if N >= 8192 else 0
fn = ext.gemm_wide if N >= 256 else ext.gemm_narrow
try:
fn(x.data_ptr(), w.data_ptr(), scale.data_ptr(), y.data_ptr(),
M, N, k_pad, swizzle)
return y
except Exception:
pass # fall through to Triton
k_pad = (K + _K_ALIGN_TRITON - 1) // _K_ALIGN_TRITON * _K_ALIGN_TRITON
if k_pad != K:
x = self._padded(x, k_pad)
w = self._padded(self.weight, k_pad)
else:
w = self.weight
return _fp8_gemm_triton(x, w, scale)
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]
20260803_034401_qwen-claude_qwen3.8-max_01_fp8_gemm