KernelBench hard · H100
FP8 GEMM Claude Fable 5
30.3%geomean peak fraction across shapes
manually audited: clean
harnessor-fableagent session1h 57mtotal wall1h 58mcheck15sbenchmark9soutput tokens341,185cost$69.05gpu-lock wait0sgpu-lock held24sregimecompute
Per-shape vs governing ceilingeach shape graded against whichever binds — fp8 compute or HBM bandwidth
4096×4096×40960.150 ms60.4%913 TFLOPS · 60% of 1,513 TF fp8 peak · also 0.45 TB/s (22% of HBM)
4096×4096×41270.185 ms49.5%748 TFLOPS · 49% of 1,513 TF fp8 peak · also 0.36 TB/s (18% of HBM)
32×8192×81920.065 ms4.4%1.05 TB/s · 52% of 2.0 TB/s HBM · also 67 TFLOPS (4% of compute)
4096×14336×40960.493 ms64.5%975 TFLOPS · 64% of 1,513 TF fp8 peak · also 0.39 TB/s (19% of HBM)
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(60.4% · 49.5% · 4.4% · 64.5%) = 30.3%
Kernel source (redacted)
"""FP8 e4m3 GEMM for H100 PCIe (SM90): y = (x @ w.T) * weight_scale -> bf16.
Primary path: CUTLASS 3.x SM90 kernels (TMA + WGMMA fp8 e4m3 x e4m3, fp32
accumulate, FastAccum warp-specialized schedules) with an EVT epilogue fusing
the per-output-channel scale and bf16 store. Compiled once via
torch.utils.cpp_extension from sources embedded below; a prebuilt .so in
./_sol_ext is imported directly when present.
K not divisible by 16 (e.g. 4127) makes byte strides TMA-illegal; those shapes
run through a fast row-pad kernel into a stride-aligned buffer (weight pad is
cached keyed on (data_ptr, _version) so in-place weight mutation invalidates
it; x is padded every call).
Fallback: Triton fp8 tl.dot kernel (wgmma on SM90) so the module always works.
"""
import os
import subprocess
import sys
from pathlib import Path
import torch
import torch.nn as nn
E4M3_MAX = 448.0
_HERE = Path(__file__).resolve().parent
_EXT_SOURCES = {
'fp8_params.h': r'''// Plain-C++ shared declarations (no CUDA/CUTLASS includes).
#pragma once
#include <cstddef>
#include <cstdint>
#include <cuda_runtime_api.h>
namespace fp8gemm {
struct LaunchParams {
const void* x;
const void* w;
const void* s;
void* y;
void* workspace;
int M, N, K;
int64_t lda, ldb;
int sm_count;
int max_swizzle;
int raster; // 0 heuristic, 1 along M, 2 along N
int splits; // stream-k splits (<=1: auto)
int pdl; // launch gemm with programmatic dependent launch
cudaStream_t stream;
};
#define FP8GEMM_DECLARE_CFG(i) \
void run_cfg##i(const LaunchParams& p); \
size_t ws_cfg##i(const LaunchParams& p);
FP8GEMM_DECLARE_CFG(0)
FP8GEMM_DECLARE_CFG(1)
FP8GEMM_DECLARE_CFG(2)
FP8GEMM_DECLARE_CFG(3)
FP8GEMM_DECLARE_CFG(4)
FP8GEMM_DECLARE_CFG(5)
FP8GEMM_DECLARE_CFG(6)
// Pad (M,K) contiguous fp8 rows into (M,Kp) with zero fill (fp8_pad.cu).
void pad_rows(const void* src, void* dst, int M, int K, int Kp, cudaStream_t stream);
} // namespace fp8gemm
''',
'fp8_common.cuh': r'''// Common CUTLASS SM90 fp8 GEMM config: y = (x @ w^T) * s[n], bf16 out.
#pragma once
#include <cutlass/cutlass.h>
#include <cutlass/numeric_types.h>
#include <cute/tensor.hpp>
#include <cutlass/gemm/dispatch_policy.hpp>
#include <cutlass/gemm/collective/collective_builder.hpp>
#include <cutlass/gemm/device/gemm_universal_adapter.h>
#include <cutlass/gemm/kernel/gemm_universal.hpp>
#include <cutlass/epilogue/dispatch_policy.hpp>
#include <cutlass/epilogue/collective/collective_builder.hpp>
#include <cutlass/epilogue/fusion/sm90_callbacks_tma_warpspecialized.hpp>
#include <cutlass/kernel_hardware_info.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include "fp8_params.h"
namespace fp8gemm {
using namespace cute;
// Scheduler-arg setter: persistent scheduler has {max_swizzle_size, raster_order};
// stream-k adds splits.
template <class SchedArgs>
void set_scheduler_args(SchedArgs& sched, const LaunchParams& p) {
if constexpr (requires { sched.max_swizzle_size; }) {
sched.max_swizzle_size = p.max_swizzle;
}
if constexpr (requires { sched.raster_order; }) {
using RO = decltype(sched.raster_order);
sched.raster_order = (p.raster == 1) ? RO::AlongM : (p.raster == 2) ? RO::AlongN : RO::Heuristic;
}
if constexpr (requires { sched.splits; }) {
if (p.splits > 1) sched.splits = p.splits;
}
}
template <class TileShape, class ClusterShape, class KernelSchedule,
class EpilogueSchedule, class TileScheduler>
struct GemmConfig {
using SchedulerTag = TileScheduler;
using ElementA = cutlass::float_e4m3_t;
using ElementB = cutlass::float_e4m3_t;
using ElementD = cutlass::bfloat16_t;
using LayoutA = cutlass::layout::RowMajor;
using LayoutB = cutlass::layout::ColumnMajor;
using LayoutD = cutlass::layout::RowMajor;
// D(i,j) = acc(i,j) * s(j): row-vector broadcast of s over the N mode.
using Scale = cutlass::epilogue::fusion::Sm90RowBroadcast<
0, TileShape, float, float, Stride<_0, _1, _0>>;
using Mul = cutlass::epilogue::fusion::Sm90Compute<
cutlass::multiplies, ElementD, float, cutlass::FloatRoundStyle::round_to_nearest>;
using EVT = cutlass::epilogue::fusion::Sm90EVT<Mul, Scale, cutlass::epilogue::fusion::Sm90AccFetch>;
using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
TileShape, ClusterShape,
cutlass::epilogue::collective::EpilogueTileAuto,
float, float,
void, LayoutD, 8,
ElementD, LayoutD, 8,
EpilogueSchedule,
EVT>::CollectiveOp;
using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
cutlass::arch::Sm90, cutlass::arch::OpClassTensorOp,
ElementA, LayoutA, 16,
ElementB, LayoutB, 16,
float,
TileShape, ClusterShape,
cutlass::gemm::collective::StageCountAutoCarveout<
static_cast<int>(sizeof(typename CollectiveEpilogue::SharedStorage))>,
KernelSchedule>::CollectiveOp;
using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
Shape<int, int, int, int>, CollectiveMainloop, CollectiveEpilogue, TileScheduler>;
using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
};
template <class Config>
size_t gemm_workspace_size(const LaunchParams& p) {
using Gemm = typename Config::Gemm;
typename Gemm::Arguments args;
args.mode = cutlass::gemm::GemmUniversalMode::kGemm;
args.problem_shape = {p.M, p.N, p.K, 1};
args.hw_info.device_id = 0;
args.hw_info.sm_count = p.sm_count;
return Gemm::get_workspace_size(args);
}
template <class Config>
typename Config::Gemm::Arguments make_args(const LaunchParams& p) {
using Gemm = typename Config::Gemm;
using StrideA = typename Gemm::GemmKernel::StrideA;
using StrideB = typename Gemm::GemmKernel::StrideB;
using StrideD = typename Gemm::GemmKernel::StrideD;
StrideA stride_a{p.lda, _1{}, int64_t(0)};
StrideB stride_b{p.ldb, _1{}, int64_t(0)};
StrideD stride_d{int64_t(p.N), _1{}, int64_t(0)};
typename Gemm::Arguments args{
cutlass::gemm::GemmUniversalMode::kGemm,
{p.M, p.N, p.K, 1},
{reinterpret_cast<cutlass::float_e4m3_t const*>(p.x), stride_a,
reinterpret_cast<cutlass::float_e4m3_t const*>(p.w), stride_b},
{{}, // epilogue.thread (EVT args), filled below
nullptr, stride_d,
reinterpret_cast<cutlass::bfloat16_t*>(p.y), stride_d}};
// EVT arguments: {scale_bcast_args, accfetch_args, multiplies_args}
args.epilogue.thread = {
{reinterpret_cast<const float*>(p.s), float(0), Stride<_0, _1, _0>{}},
{},
{}};
args.hw_info.device_id = 0;
args.hw_info.sm_count = p.sm_count;
using SchedArgs = typename Gemm::GemmKernel::TileSchedulerArguments;
set_scheduler_args<SchedArgs>(args.scheduler, p);
return args;
}
// Persistent-scheduler configs cache the initialized Gemm object keyed on all
// launch-relevant fields; re-initialize only on change (~8us saved per call).
// Stream-K configs always re-initialize (their workspace needs per-run setup).
template <class Config>
void run_gemm(const LaunchParams& p) {
using Gemm = typename Config::Gemm;
constexpr bool kCacheable =
!std::is_same_v<typename Config::SchedulerTag, cutlass::gemm::StreamKScheduler>;
static thread_local Gemm gemm;
static thread_local LaunchParams last{};
static thread_local bool valid = false;
bool hit = kCacheable && valid &&
last.x == p.x && last.w == p.w && last.s == p.s && last.y == p.y &&
last.workspace == p.workspace && last.M == p.M && last.N == p.N &&
last.K == p.K && last.lda == p.lda && last.ldb == p.ldb &&
last.max_swizzle == p.max_swizzle && last.raster == p.raster &&
last.splits == p.splits;
if (!hit) {
auto args = make_args<Config>(p);
cutlass::Status st = gemm.can_implement(args);
if (st != cutlass::Status::kSuccess) {
throw std::runtime_error(std::string("cutlass can_implement failed: ") +
cutlassGetStatusString(st));
}
st = gemm.initialize(args, p.workspace, p.stream);
if (st != cutlass::Status::kSuccess) {
throw std::runtime_error(std::string("cutlass initialize failed: ") +
cutlassGetStatusString(st));
}
last = p;
valid = true;
}
cutlass::Status st = gemm.run(p.stream, /*cuda_adapter=*/nullptr, /*launch_with_pdl=*/p.pdl != 0);
if (st != cutlass::Status::kSuccess) {
throw std::runtime_error(std::string("cutlass run failed: ") +
cutlassGetStatusString(st));
}
}
} // namespace fp8gemm
''',
'fp8_pad.cu': r'''// Row-pad kernel: (M, K) contiguous fp8 -> (M, Kp) buffer, zero-filled tail.
// Handles arbitrary (odd) K with aligned 4B loads + funnel-shift realignment.
#include "fp8_params.h"
#include <cuda_runtime.h>
#include <cstdint>
namespace fp8gemm {
// One warp per row. dst rows are 4B-aligned (Kp % 4 == 0); src rows start at
// arbitrary byte offsets. Read aligned uint32 words, funnel-shift to realign.
__global__ void pad_rows_kernel(const uint8_t* __restrict__ src, uint8_t* __restrict__ dst,
int M, int K, int Kp) {
int warp = (blockIdx.x * blockDim.x + threadIdx.x) >> 5;
int lane = threadIdx.x & 31;
if (warp >= M) return;
const uint8_t* srow = src + (int64_t)warp * K;
uint8_t* drow = dst + (int64_t)warp * Kp;
uintptr_t saddr = (uintptr_t)srow;
const uint32_t* sw = (const uint32_t*)(saddr & ~uintptr_t(3));
int shb = int(saddr & 3); // src misalignment in bytes
int sh = shb * 8; // ... in bits
uint32_t* dw = (uint32_t*)drow;
int nw = Kp >> 2; // dst words per row
int full = K >> 2; // dst words fully covered by src bytes
// number of src words this row's K bytes touch (for OOB-safe hi loads)
int span = (shb + K + 3) >> 2;
// Bulk: 16B stores; each lane covers 4 consecutive dst words per step.
int full4 = full >> 2; // uint4-sized dst chunks fully covered
uint4* dv = (uint4*)drow;
for (int j = lane; j < full4; j += 32) {
int i = j << 2;
uint32_t w0 = sw[i], w1 = sw[i + 1], w2 = sw[i + 2], w3 = sw[i + 3];
uint4 out;
if (sh == 0) {
out = make_uint4(w0, w1, w2, w3);
} else {
uint32_t w4 = sw[i + 4]; // i+4 <= full <= span-? safe: 4*(full4)<=full => i+4 <= full < span when sh>0
out.x = __funnelshift_r(w0, w1, sh);
out.y = __funnelshift_r(w1, w2, sh);
out.z = __funnelshift_r(w2, w3, sh);
out.w = __funnelshift_r(w3, w4, sh);
}
dv[j] = out;
}
// Remaining fully-covered words (full4*4 .. full)
for (int i = (full4 << 2) + lane; i < full; i += 32) {
uint32_t lo = sw[i];
uint32_t hi = (i + 1 < span) ? sw[i + 1] : 0u;
dw[i] = sh ? __funnelshift_r(lo, hi, sh) : lo;
}
// tail: word `full` holds the last K%4 bytes; words beyond are zero.
for (int i = full + lane; i < nw; i += 32) {
uint32_t v = 0;
if (i == full) {
int rem = K & 3;
for (int b = 0; b < rem; ++b) {
v |= uint32_t(srow[4 * full + b]) << (8 * b);
}
}
dw[i] = v;
}
#if __CUDA_ARCH__ >= 900
cudaTriggerProgrammaticLaunchCompletion();
#endif
}
void pad_rows(const void* src, void* dst, int M, int K, int Kp, cudaStream_t stream) {
int warps_per_block = 8;
int blocks = (M + warps_per_block - 1) / warps_per_block;
pad_rows_kernel<<<blocks, warps_per_block * 32, 0, stream>>>(
(const uint8_t*)src, (uint8_t*)dst, M, K, Kp);
}
} // namespace fp8gemm
''',
'fp8_dispatch.cpp': r'''// Python-facing dispatcher (compiled with gcc; no CUTLASS includes).
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include "fp8_params.h"
namespace fp8gemm {
static LaunchParams make_params(const torch::Tensor& x, const torch::Tensor& w,
const torch::Tensor& s, torch::Tensor& y,
int64_t lda, int64_t ldb, int64_t max_swizzle,
int64_t raster, int64_t splits, void* workspace) {
static int sm_count = [] {
int dev = 0, n = 0;
cudaGetDevice(&dev);
cudaDeviceGetAttribute(&n, cudaDevAttrMultiProcessorCount, dev);
return n;
}();
LaunchParams p;
p.x = x.data_ptr();
p.w = w.data_ptr();
p.s = s.data_ptr();
p.y = y.data_ptr();
p.workspace = workspace;
p.M = static_cast<int>(x.size(0));
p.K = static_cast<int>(x.size(1));
p.N = static_cast<int>(w.size(0));
p.lda = lda;
p.ldb = ldb;
p.sm_count = sm_count;
p.max_swizzle = static_cast<int>(max_swizzle);
p.raster = static_cast<int>(raster);
p.splits = static_cast<int>(splits);
p.pdl = 0;
p.stream = at::cuda::getCurrentCUDAStream();
return p;
}
using RunFn = void (*)(const LaunchParams&);
using WsFn = size_t (*)(const LaunchParams&);
static RunFn kRun[] = {run_cfg0, run_cfg1, run_cfg2, run_cfg3, run_cfg4, run_cfg5, run_cfg6};
static WsFn kWs[] = {ws_cfg0, ws_cfg1, ws_cfg2, ws_cfg3, ws_cfg4, ws_cfg5, ws_cfg6};
static constexpr int kNumCfgs = sizeof(kRun) / sizeof(kRun[0]);
int64_t num_configs() { return kNumCfgs; }
int64_t workspace_size(int64_t cfg, const torch::Tensor& x, const torch::Tensor& w,
const torch::Tensor& s, torch::Tensor y,
int64_t lda, int64_t ldb, int64_t max_swizzle, int64_t raster,
int64_t splits) {
TORCH_CHECK(cfg >= 0 && cfg < kNumCfgs, "bad cfg");
LaunchParams p = make_params(x, w, s, y, lda, ldb, max_swizzle, raster, splits, nullptr);
return static_cast<int64_t>(kWs[cfg](p));
}
void gemm(int64_t cfg, const torch::Tensor& x, const torch::Tensor& w,
const torch::Tensor& s, torch::Tensor y,
int64_t lda, int64_t ldb, int64_t max_swizzle, int64_t raster,
int64_t splits, c10::optional<torch::Tensor> workspace) {
TORCH_CHECK(cfg >= 0 && cfg < kNumCfgs, "bad cfg");
void* ws = workspace.has_value() ? workspace->data_ptr() : nullptr;
LaunchParams p = make_params(x, w, s, y, lda, ldb, max_swizzle, raster, splits, ws);
kRun[cfg](p);
}
// x is (M, K) contiguous with K odd/unaligned: pad into xpad (M, Kp) then gemm
// with lda = Kp. One python->C++ transition for the whole forward.
void gemm_pad_a(int64_t cfg, const torch::Tensor& x, torch::Tensor xpad,
const torch::Tensor& w, const torch::Tensor& s, torch::Tensor y,
int64_t ldb, int64_t max_swizzle, int64_t raster,
int64_t splits, c10::optional<torch::Tensor> workspace) {
TORCH_CHECK(cfg >= 0 && cfg < kNumCfgs, "bad cfg");
int M = static_cast<int>(x.size(0));
int K = static_cast<int>(x.size(1));
int Kp = static_cast<int>(xpad.size(1));
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
pad_rows(x.data_ptr(), xpad.data_ptr(), M, K, Kp, stream);
void* ws = workspace.has_value() ? workspace->data_ptr() : nullptr;
// Run with K = Kp: both operands are zero-padded in [K, Kp), which adds
// exact zeros to the accumulation. (TMA requires 16-aligned K extent.)
LaunchParams p = make_params(xpad, w, s, y, Kp, ldb, max_swizzle, raster, splits, ws);
p.pdl = 1; // overlap gemm prologue with the pad kernel tail
kRun[cfg](p);
(void)K;
}
void pad(const torch::Tensor& src, torch::Tensor dst) {
int M = static_cast<int>(src.size(0));
int K = static_cast<int>(src.size(1));
int Kp = static_cast<int>(dst.size(1));
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
pad_rows(src.data_ptr(), dst.data_ptr(), M, K, Kp, stream);
}
} // namespace fp8gemm
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("gemm", &fp8gemm::gemm, "fp8 gemm");
m.def("gemm_pad_a", &fp8gemm::gemm_pad_a, "fp8 gemm with A row padding");
m.def("pad", &fp8gemm::pad, "row pad");
m.def("workspace_size", &fp8gemm::workspace_size, "workspace size");
m.def("num_configs", &fp8gemm::num_configs, "number of configs");
}
''',
'cfg0.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg0 = GemmConfig<Shape<_128,_128,_128>, Shape<_2,_1,_1>,
cutlass::gemm::KernelTmaWarpSpecializedPingpongFP8FastAccum,
cutlass::epilogue::TmaWarpSpecialized,
cutlass::gemm::PersistentScheduler>;
void run_cfg0(const LaunchParams& p) { run_gemm<Cfg0>(p); }
size_t ws_cfg0(const LaunchParams& p) { return gemm_workspace_size<Cfg0>(p); }
}
''',
'cfg1.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg1 = GemmConfig<Shape<_128,_128,_128>, Shape<_1,_2,_1>,
cutlass::gemm::KernelTmaWarpSpecializedPingpongFP8FastAccum,
cutlass::epilogue::TmaWarpSpecialized,
cutlass::gemm::PersistentScheduler>;
void run_cfg1(const LaunchParams& p) { run_gemm<Cfg1>(p); }
size_t ws_cfg1(const LaunchParams& p) { return gemm_workspace_size<Cfg1>(p); }
}
''',
'cfg3.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg3 = GemmConfig<Shape<_128,_256,_128>, Shape<_1,_2,_1>,
cutlass::gemm::KernelTmaWarpSpecializedCooperativeFP8FastAccum,
cutlass::epilogue::TmaWarpSpecializedCooperative,
cutlass::gemm::PersistentScheduler>;
void run_cfg3(const LaunchParams& p) { run_gemm<Cfg3>(p); }
size_t ws_cfg3(const LaunchParams& p) { return gemm_workspace_size<Cfg3>(p); }
}
''',
'cfg4.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg4 = GemmConfig<Shape<_64,_128,_128>, Shape<_1,_1,_1>,
cutlass::gemm::KernelTmaWarpSpecializedPingpongFP8FastAccum,
cutlass::epilogue::TmaWarpSpecialized,
cutlass::gemm::PersistentScheduler>;
void run_cfg4(const LaunchParams& p) { run_gemm<Cfg4>(p); }
size_t ws_cfg4(const LaunchParams& p) { return gemm_workspace_size<Cfg4>(p); }
}
''',
'cfg5.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg5 = GemmConfig<Shape<_128,_128,_128>, Shape<_1,_1,_1>,
cutlass::gemm::KernelTmaWarpSpecializedCooperativeFP8FastAccum,
cutlass::epilogue::TmaWarpSpecializedCooperative,
cutlass::gemm::StreamKScheduler>;
void run_cfg5(const LaunchParams& p) { run_gemm<Cfg5>(p); }
size_t ws_cfg5(const LaunchParams& p) { return gemm_workspace_size<Cfg5>(p); }
}
''',
'cfg6.cu': r'''#include "fp8_common.cuh"
namespace fp8gemm {
using Cfg6 = GemmConfig<Shape<_128,_128,_128>, Shape<_1,_1,_1>,
cutlass::gemm::KernelTmaWarpSpecializedPingpongFP8FastAccum,
cutlass::epilogue::TmaWarpSpecialized,
cutlass::gemm::PersistentScheduler>;
void run_cfg6(const LaunchParams& p) { run_gemm<Cfg6>(p); }
size_t ws_cfg6(const LaunchParams& p) { return gemm_workspace_size<Cfg6>(p); }
}
''',
'cfg2.cu': r'''#include "fp8_params.h"
#include <stdexcept>
namespace fp8gemm {
void run_cfg2(const LaunchParams&) { throw std::runtime_error("cfg2 not built"); }
size_t ws_cfg2(const LaunchParams&) { return 0; }
}
''',
}
def _find_cutlass() -> Path | None:
for cand in [_HERE / "cutlass", _HERE / "third_party" / "cutlass"]:
if (cand / "include" / "cutlass" / "cutlass.h").exists():
return cand
# last resort: shallow clone (only if network available)
dest = _HERE / "cutlass"
try:
subprocess.run(
["git", "clone", "--depth", "1", "https://github.com/NVIDIA/cutlass.git", str(dest)],
check=True, capture_output=True, timeout=600,
)
if (dest / "include" / "cutlass" / "cutlass.h").exists():
return dest
except Exception:
pass
return None
def _import_so(so_path: Path):
import importlib.util
spec = importlib.util.spec_from_file_location("fp8gemm_sol", str(so_path))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def _load_ext():
if os.environ.get("FP8GEMM_DISABLE_EXT") == "1":
return None
build_dir = _HERE / "_sol_ext"
so_path = build_dir / "fp8gemm_sol.so"
# 1) prebuilt .so — trust it only if it was built from these exact sources
sources_current = all(
(build_dir / name).exists() and (build_dir / name).read_text() == text
for name, text in _EXT_SOURCES.items()
)
if so_path.exists() and sources_current:
try:
return _import_so(so_path)
except Exception:
pass
# 2) build from embedded sources
try:
cutlass = _find_cutlass()
if cutlass is None:
return None
build_dir.mkdir(exist_ok=True)
srcs = []
for name, text in _EXT_SOURCES.items():
p = build_dir / name
if not p.exists() or p.read_text() != text:
p.write_text(text)
if name.endswith((".cu", ".cpp")):
srcs.append(str(p))
os.environ["TORCH_CUDA_ARCH_LIST"] = "9.0a"
os.environ.setdefault("MAX_JOBS", str(min(16, os.cpu_count() or 8)))
from torch.utils.cpp_extension import load
return load(
name="fp8gemm_sol",
sources=sorted(srcs),
extra_include_paths=[str(build_dir), str(cutlass / "include"),
str(cutlass / "tools/util/include")],
extra_cflags=["-O3", "-std=c++20"],
extra_cuda_cflags=["-O3", "-std=c++20", "--expt-relaxed-constexpr",
"-DNDEBUG", "-DCUTLASS_ENABLE_GDC_FOR_SM90=1"],
build_directory=str(build_dir),
verbose=False,
)
except Exception as e:
print(f"[solution.py] extension build failed ({type(e).__name__}: {e}); "
f"falling back to Triton", file=sys.stderr)
return None
_EXT = None
_EXT_TRIED = False
def _get_ext():
global _EXT, _EXT_TRIED
if not _EXT_TRIED:
_EXT_TRIED = True
_EXT = _load_ext()
return _EXT
# ----------------------------------------------------------------------------
# Triton fallback (also handles shapes the CUTLASS build can't)
# ----------------------------------------------------------------------------
import triton
import triton.language as tl
@triton.jit
def _tt_fp8_gemm(
x_ptr, w_ptr, s_ptr, y_ptr,
M, N, K,
sxm, swn, sym,
BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr,
GROUP_M: tl.constexpr, MASK_M: tl.constexpr, MASK_N: tl.constexpr,
HAS_TAIL: tl.constexpr,
):
pid = tl.program_id(0)
grid_m = tl.cdiv(M, BM)
grid_n = tl.cdiv(N, BN)
width = GROUP_M * grid_n
group_id = pid // width
group_size = tl.minimum(grid_m - group_id * GROUP_M, GROUP_M)
pid_m = group_id * GROUP_M + (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)
x_ptrs = x_ptr + rm[:, None] * sxm + rk[None, :]
w_ptrs = w_ptr + rn[None, :] * swn + rk[:, None]
acc = tl.zeros((BM, BN), dtype=tl.float32)
m_mask = rm[:, None] < M
n_mask = rn[None, :] < N
for _k in range(0, K // BK):
if MASK_M:
a = tl.load(x_ptrs, mask=m_mask, other=0.0)
else:
a = tl.load(x_ptrs)
if MASK_N:
b = tl.load(w_ptrs, mask=n_mask, other=0.0)
else:
b = tl.load(w_ptrs)
acc = tl.dot(a, b, acc)
x_ptrs += BK
w_ptrs += BK
if HAS_TAIL:
k0 = (K // BK) * BK
k_mask = (k0 + rk) < K
am = k_mask[None, :] & m_mask if MASK_M else k_mask[None, :]
bm = k_mask[:, None] & n_mask if MASK_N else k_mask[:, None]
a = tl.load(x_ptrs, mask=am, other=0.0)
b = tl.load(w_ptrs, mask=bm, other=0.0)
acc = tl.dot(a, b, acc)
s = tl.load(s_ptr + rn, mask=rn < N, other=0.0)
acc = acc * s[None, :]
y_ptrs = y_ptr + rm[:, None] * sym + rn[None, :]
tl.store(y_ptrs, acc.to(tl.bfloat16), mask=m_mask & (rn[None, :] < N))
def _triton_forward(x, w, s, M, N, K):
y = torch.empty(M, N, dtype=torch.bfloat16, device=x.device)
if M <= 64:
BM, BN, BK, GM, nw, ns = 64, 128, 128, 1, 4, 6
else:
BM, BN, BK, GM, nw, ns = 128, 128, 128, 8, 8, 4
grid = (triton.cdiv(M, BM) * triton.cdiv(N, BN),)
_tt_fp8_gemm[grid](
x, w, s, y, M, N, K,
x.stride(0), w.stride(0), y.stride(0),
BM=BM, BN=BN, BK=BK, GROUP_M=GM,
MASK_M=(M % BM != 0), MASK_N=(N % BN != 0), HAS_TAIL=(K % BK != 0),
num_warps=nw, num_stages=ns,
)
return y
# ----------------------------------------------------------------------------
# Model
# ----------------------------------------------------------------------------
class Model(nn.Module):
"""y = ((x @ w.T) * weight_scale).to(bf16); x,w fp8_e4m3, scale fp32."""
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))
# K padded up to a multiple of 16 elements so TMA strides are legal.
self.Kp = K if K % 16 == 0 else (K + 15) & ~15
self._wpad_key = None
self._wpad = None
self._xpad = None
# CUDA-graph cache: key -> (graph, y). Graphs replay the recorded
# kernel launches on the recorded pointers; they read the tensors'
# CURRENT contents, so in-place data changes are safe. Anything that
# changes a pointer/shape (or the derived padded weight) is in the key.
self._graphs = {}
self._graphs_ok = True
def _padded_weight(self, ext):
w = self.weight
key = (w.data_ptr(), w._version)
if key != self._wpad_key:
wp = torch.empty(self.N, self.Kp, dtype=w.dtype, device=w.device)
ext.pad(w, wp)
self._wpad = wp
self._wpad_key = key
return self._wpad
def _launch(self, x, y, cfg, swz, raster):
"""Enqueue the kernels for y = (x @ w.T) * s (no allocations)."""
ext = _EXT
w = self.weight
s = self.weight_scale
K, Kp = self.K, self.Kp
if K % 16 == 0:
ext.gemm(cfg, x, w, s, y, K, K, swz, raster, 1, None)
else:
ext.gemm_pad_a(cfg, x, self._xpad, self._wpad, s, y, Kp, swz, raster, 1, None)
def forward(self, x: torch.Tensor) -> torch.Tensor:
M, N, K, Kp = self.M, self.N, self.K, self.Kp
w = self.weight
s = self.weight_scale
ext = _get_ext()
usable = (
ext is not None
and x.is_cuda and x.dtype == torch.float8_e4m3fn and x.is_contiguous()
and w.is_contiguous() and s.is_contiguous()
and x.data_ptr() % 16 == 0 and w.data_ptr() % 16 == 0
and N % 8 == 0 and K >= 32
)
if not usable:
return _triton_forward(x, w, s, M, N, K)
# config choice per shape (empirically tuned on H100 PCIe)
if M <= 64:
cfg, swz, raster = 4, 1, 0
elif N <= 8192:
cfg, swz, raster = 0, 2, 0
else:
cfg, swz, raster = 0, 1, 0
try:
if K % 16 != 0:
self._padded_weight(ext) # refresh version-keyed cache
if self._xpad is None or self._xpad.shape[0] != M:
self._xpad = torch.empty(M, Kp, dtype=x.dtype, device=x.device)
use_graph = self._graphs_ok and not torch.cuda.is_current_stream_capturing()
if not use_graph:
y = torch.empty(M, N, dtype=torch.bfloat16, device=x.device)
self._launch(x, y, cfg, swz, raster)
return y
key = (x.data_ptr(), x.shape[0], x.shape[1], w.data_ptr(), w._version,
s.data_ptr(), cfg, swz, raster)
ent = self._graphs.get(key)
if ent is not None:
ent[0].replay()
return ent[1]
# First call for this key: run eagerly (warms host-side param
# cache and produces the result), then capture for future calls.
y = torch.empty(M, N, dtype=torch.bfloat16, device=x.device)
self._launch(x, y, cfg, swz, raster)
try:
if len(self._graphs) >= 16:
self._graphs.clear()
g = torch.cuda.CUDAGraph()
with torch.no_grad(), torch.cuda.graph(g):
self._launch(x, y, cfg, swz, raster)
# keep refs to every buffer the recorded launches touch
self._graphs[key] = (g, y, self._xpad, self._wpad)
except Exception:
self._graphs_ok = False
return y
except Exception as e:
if not getattr(Model, "_warned", False):
Model._warned = True
print(f"[solution.py] cutlass path failed ({type(e).__name__}: {e}); "
f"using Triton fallback", file=sys.stderr)
return _triton_forward(x, w, s, M, N, K)
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]
20260702_055037_claude_claude-fable-5_01_fp8_gemm