KernelBench hard · RTX PRO 6000

FP8 GEMM Claude Fable 5

39.1%geomean peak fraction across shapes

manually audited: clean

Genuine fp8 e4m3 GEMM. Primary path is a CUTLASS 3.x SM120 warp-specialized cooperative kernel (TMA mainloop, mma.sync e4m3, fp32 accumulate, 256x128x64 tile) built at import via load_inline, with the per-channel weight scale fused into the epilogue through a custom EVT (Sm90RowBroadcast * accumulator). The off-alignment K=4127 shape is handled by a custom funnel-shift pad-copy kernel staging operands into zero-padded 16B-aligned buffers. Skinny M<64 decode uses a hand-tuned Triton tl.dot kernel replayed through a CUDA graph; a Triton TMA persistent kernel is the full fallback (verified live via KBH_SOL_NO_CUTLASS=1 check.py PASS in-session). No forbidden ops: the solution never calls torch._scaled_mm (all 6 transcript mentions are the agent reading problem.yaml and sota.py). template_mutated=false.

harnessor-fableagent session1h 4mtotal wall1h 5mcheck74sbenchmark9soutput tokensgpu-lock wait0sgpu-lock held83sregimecompute

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

4096×4096×40960.203 ms67.6%676 TFLOPS · 68% of 1,000 TF fp8 peak · also 0.33 TB/s (18% of HBM)
4096×4096×41270.223 ms62.1%621 TFLOPS · 62% of 1,000 TF fp8 peak · also 0.30 TB/s (17% of HBM)
32×8192×81920.056 ms7.7%1.22 TB/s · 68% of 1.8 TB/s HBM · also 77 TFLOPS (8% of compute)
4096×14336×40960.666 ms72.2%722 TFLOPS · 72% of 1,000 TF fp8 peak · also 0.29 TB/s (16% of HBM)

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

geomean(67.6% · 62.1% · 7.7% · 72.2%) = 39.1%

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

y = (x @ w.T) * weight_scale; x fp8_e4m3 (M,K), w fp8_e4m3 (N,K), out bf16.

Primary path: CUTLASS 3.x SM120 warp-specialized cooperative kernel
(TMA + mma.sync e4m3, fp32 accumulate, 256x128x64 tile) with the per-channel
scale fused into the epilogue via an EVT (Sm90RowBroadcast * accumulator),
compiled at import time with torch.utils.cpp_extension.load_inline.

Skinny-M shapes (decode) use a bandwidth-oriented Triton fp8 tl.dot kernel
with the scale fused into the store.

K not divisible by 16 (TMA stride requirement) is handled by staging the
operands into zero-padded 16B-aligned buffers.

If the CUTLASS build is unavailable, a Triton TMA persistent kernel covers
the compute shapes instead.
"""
import os

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

E4M3_MAX = 448.0
_NUM_SMS = None


def _num_sms():
    global _NUM_SMS
    if _NUM_SMS is None:
        _NUM_SMS = torch.cuda.get_device_properties(
            torch.cuda.current_device()).multi_processor_count
    return _NUM_SMS


def _alloc_fn(size: int, alignment: int, stream):
    return torch.empty(size, dtype=torch.int8, device="cuda")


triton.set_allocator(_alloc_fn)


# ===========================================================================
# CUTLASS SM120 extension (primary compute path)
# ===========================================================================
_CUTLASS_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>

#include "cutlass/cutlass.h"
#include "cute/tensor.hpp"
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/epilogue/fusion/sm90_visitor_tma_warpspecialized.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/util/packed_stride.hpp"

using namespace cute;

using ElementA = cutlass::float_e4m3_t;
using ElementB = cutlass::float_e4m3_t;
using ElementD = cutlass::bfloat16_t;
using ElementAcc = float;
using LayoutA = cutlass::layout::RowMajor;     // x (M,K), K-major
using LayoutB = cutlass::layout::ColumnMajor;  // w (N,K), K-major
using LayoutD = cutlass::layout::RowMajor;

// One SM120 kernel definition: warp-specialized cooperative, TMA mainloop,
// fp32 accumulate, epilogue D = bf16(acc * scale[n]).
using TileShape = Shape<_256, _128, _64>;
using ClusterShape = Shape<_1, _1, _1>;
static constexpr auto RoundStyle = cutlass::FloatRoundStyle::round_to_nearest;

using CustomEVT = cutlass::epilogue::fusion::Sm90EVT<
    cutlass::epilogue::fusion::Sm90Compute<cutlass::multiplies, ElementD,
                                           float, RoundStyle>,
    cutlass::epilogue::fusion::Sm90RowBroadcast<0, TileShape, float>,
    cutlass::epilogue::fusion::Sm90AccFetch>;

using CollectiveEpilogue =
    typename cutlass::epilogue::collective::CollectiveBuilder<
        cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp, TileShape,
        ClusterShape, cutlass::epilogue::collective::EpilogueTileAuto,
        ElementAcc, float, void, LayoutD, 8, ElementD, LayoutD, 8,
        cutlass::epilogue::TmaWarpSpecializedCooperative,
        CustomEVT>::CollectiveOp;

using CollectiveMainloop =
    typename cutlass::gemm::collective::CollectiveBuilder<
        cutlass::arch::Sm120, cutlass::arch::OpClassTensorOp,
        ElementA, LayoutA, 16,
        ElementB, LayoutB, 16,
        ElementAcc, TileShape, ClusterShape,
        cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
            sizeof(typename CollectiveEpilogue::SharedStorage))>,
        cutlass::gemm::KernelTmaWarpSpecializedCooperative>::CollectiveOp;

using GemmKernel = cutlass::gemm::kernel::GemmUniversal<
    Shape<int, int, int, int>, CollectiveMainloop, CollectiveEpilogue, void>;
using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;

void fp8_gemm(torch::Tensor x, torch::Tensor w, torch::Tensor s,
              torch::Tensor y, int64_t M, int64_t N, int64_t K) {
  using StrideA = typename Gemm::GemmKernel::StrideA;
  using StrideB = typename Gemm::GemmKernel::StrideB;
  using StrideD = typename Gemm::GemmKernel::StrideD;

  auto stride_A = cutlass::make_cute_packed_stride(StrideA{}, {int(M), int(K), 1});
  auto stride_B = cutlass::make_cute_packed_stride(StrideB{}, {int(N), int(K), 1});
  auto stride_D = cutlass::make_cute_packed_stride(StrideD{}, {int(M), int(N), 1});
  get<0>(stride_A) = x.stride(0);
  get<0>(stride_B) = w.stride(0);

  cutlass::KernelHardwareInfo hw_info;
  hw_info.device_id = x.get_device();
  hw_info.sm_count =
      cutlass::KernelHardwareInfo::query_device_multiprocessor_count(
          hw_info.device_id);

  typename Gemm::Arguments args{
      cutlass::gemm::GemmUniversalMode::kGemm,
      {int(M), int(N), int(K), 1},
      {reinterpret_cast<ElementA const*>(x.data_ptr()), stride_A,
       reinterpret_cast<ElementB const*>(w.data_ptr()), stride_B},
      {{}, nullptr, stride_D,
       reinterpret_cast<ElementD*>(y.data_ptr()), stride_D},
      hw_info};
  args.epilogue.thread = {
      {reinterpret_cast<float const*>(s.data_ptr())},  // scale row broadcast
      {},                                              // accumulator
      {}                                               // multiply node
  };

  Gemm gemm;
  TORCH_CHECK(gemm.can_implement(args) == cutlass::Status::kSuccess,
              "CUTLASS SM120 fp8 GEMM cannot implement this problem");
  size_t ws_size = Gemm::get_workspace_size(args);
  auto ws = torch::empty(
      {static_cast<int64_t>(ws_size)},
      torch::TensorOptions().dtype(torch::kUInt8).device(x.device()));
  auto stream = at::cuda::getCurrentCUDAStream();
  TORCH_CHECK(gemm.initialize(args, ws.data_ptr()) == cutlass::Status::kSuccess,
              "CUTLASS initialize failed");
  TORCH_CHECK(gemm.run(stream) == cutlass::Status::kSuccess,
              "CUTLASS run failed");
}

// Row-realignment pad copy: src (M,K) contiguous fp8 rows at arbitrary byte
// alignment -> dst (M,K_pad) with 16B-aligned rows, zero-filled K..K_pad.
// u32-granularity with funnel shifts so both sides stay vectorized/coalesced.
__global__ void pad_copy_kernel(const uint8_t* __restrict__ src,
                                uint8_t* __restrict__ dst,
                                int64_t M, int64_t K, int64_t K_pad) {
  int64_t words_per_row = K_pad >> 2;   // K_pad % 4 == 0
  int64_t total_words = M * words_per_row;
  int64_t total_src = M * K;
  uint32_t* dst32 = reinterpret_cast<uint32_t*>(dst);
  for (int64_t idx = blockIdx.x * (int64_t)blockDim.x + threadIdx.x;
       idx < total_words; idx += (int64_t)gridDim.x * blockDim.x) {
    int64_t row = idx / words_per_row;
    int64_t col = (idx - row * words_per_row) << 2;  // byte col within row
    uint32_t out = 0;
    if (col + 4 <= K) {
      int64_t addr = row * K + col;  // src byte offset
      if (addr + 7 < total_src) {
        int64_t base = addr >> 2;
        uint32_t sh = (addr & 3) * 8;
        const uint32_t* src32 = reinterpret_cast<const uint32_t*>(src);
        uint32_t w0 = src32[base];
        uint32_t w1 = src32[base + 1];
        out = sh ? __funnelshift_r(w0, w1, sh) : w0;
      } else {
        uint8_t b[4];
        #pragma unroll
        for (int j = 0; j < 4; ++j) b[j] = src[addr + j];
        out = b[0] | (b[1] << 8) | (b[2] << 16) | (uint32_t(b[3]) << 24);
      }
    } else if (col < K) {  // straddles the K boundary: partial bytes + zeros
      uint8_t b[4] = {0, 0, 0, 0};
      for (int j = 0; j < 4 && col + j < K; ++j) b[j] = src[row * K + col + j];
      out = b[0] | (b[1] << 8) | (b[2] << 16) | (uint32_t(b[3]) << 24);
    }
    dst32[idx] = out;
  }
}

void pad_copy(torch::Tensor src, torch::Tensor dst, int64_t M, int64_t K,
              int64_t K_pad) {
  auto stream = at::cuda::getCurrentCUDAStream();
  int64_t total_words = M * (K_pad >> 2);
  int threads = 256;
  int blocks = (int)std::min<int64_t>((total_words + threads - 1) / threads,
                                      65535 * 8);
  pad_copy_kernel<<<blocks, threads, 0, stream>>>(
      reinterpret_cast<const uint8_t*>(src.data_ptr()),
      reinterpret_cast<uint8_t*>(dst.data_ptr()), M, K, K_pad);
}
"""

_CUTLASS_DECL = """
void fp8_gemm(torch::Tensor x, torch::Tensor w, torch::Tensor s, torch::Tensor y, int64_t M, int64_t N, int64_t K);
void pad_copy(torch::Tensor src, torch::Tensor dst, int64_t M, int64_t K, int64_t K_pad);
"""


def _find_cutlass():
    here = os.path.dirname(os.path.abspath(__file__))
    candidates = [
        os.environ.get("CUTLASS_PATH", ""),
        os.path.join(here, "scratch", "cutlass"),
        os.path.join(here, "cutlass"),
        os.path.expanduser("~/.cache/kbh_cutlass/cutlass"),
    ]
    for c in candidates:
        if c and os.path.isfile(os.path.join(c, "include", "cutlass", "cutlass.h")):
            return c
    # Last resort: shallow clone.
    try:
        import subprocess
        dst = os.path.expanduser("~/.cache/kbh_cutlass/cutlass")
        os.makedirs(os.path.dirname(dst), exist_ok=True)
        subprocess.run(
            ["git", "clone", "--depth", "1",
             "https://github.com/NVIDIA/cutlass.git", dst],
            check=True, capture_output=True, timeout=600)
        if os.path.isfile(os.path.join(dst, "include", "cutlass", "cutlass.h")):
            return dst
    except Exception:
        pass
    return None


def _build_cutlass_ext():
    if os.environ.get("KBH_SOL_NO_CUTLASS") == "1":  # testing hook
        return None
    if torch.cuda.get_device_capability(0)[0] < 12:
        return None
    root = _find_cutlass()
    if root is None:
        return None
    from torch.utils.cpp_extension import load_inline

    def _try_build(build_dir):
        if build_dir is not None:
            os.makedirs(build_dir, exist_ok=True)
        return load_inline(
            name="fp8_gemm_sm120_sol",
            cpp_sources=[_CUTLASS_DECL],
            cuda_sources=[_CUTLASS_SRC],
            functions=["fp8_gemm", "pad_copy"],
            extra_include_paths=[
                os.path.join(root, "include"),
                os.path.join(root, "tools", "util", "include"),
            ],
            extra_cuda_cflags=[
                "-O3",
                "-gencode=arch=compute_120a,code=sm_120a",
                "--expt-relaxed-constexpr",
                "-DNDEBUG",
            ],
            build_directory=build_dir,
            verbose=False,
        )

    here = os.path.dirname(os.path.abspath(__file__))
    # Prefer a build dir next to this file (ships the compiled .so with the
    # directory), fall back to torch's default extension cache.
    for build_dir in (os.path.join(here, ".cutlass_ext_build"), None):
        try:
            return _try_build(build_dir)
        except Exception:
            continue
    return None


_EXT = _build_cutlass_ext()


# ===========================================================================
# Triton kernels: skinny-M path and fallback persistent TMA path
# ===========================================================================
# Note: deliberately NOT autotuned. Triton's autotuner benchmarks without an
# L2 flush; with the whole weight matrix L2-resident it picks configs that
# lose ~20% when the weights actually stream from DRAM (the graded regime).
# This config measured best under an explicit L2 flush.
@triton.jit
def _fp8_gemm_skinny(
    x_ptr, w_ptr, s_ptr, y_ptr,
    M, N, K,
    stride_xm, stride_wn,
    BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
    EVEN_N: tl.constexpr, EVEN_K: tl.constexpr,
):
    pid_n = tl.program_id(0)
    rm = tl.arange(0, BLOCK_M)
    rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
    rk = tl.arange(0, BLOCK_K)
    m_mask = rm[:, None] < M
    n_mask = rn[:, None] < N

    x_ptrs = x_ptr + rm[:, None] * stride_xm + rk[None, :]
    w_ptrs = w_ptr + rn[:, None] * stride_wn + rk[None, :]

    acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
    for k in range(0, K, BLOCK_K):
        if EVEN_K:
            a = tl.load(x_ptrs, mask=m_mask, other=0.0)
            if EVEN_N:
                b = tl.load(w_ptrs)
            else:
                b = tl.load(w_ptrs, mask=n_mask, other=0.0)
        else:
            km = (k + rk)[None, :] < K
            a = tl.load(x_ptrs, mask=m_mask & km, other=0.0)
            if EVEN_N:
                b = tl.load(w_ptrs, mask=km, other=0.0)
            else:
                b = tl.load(w_ptrs, mask=n_mask & km, other=0.0)
        acc = tl.dot(a, tl.trans(b), acc)
        x_ptrs += BLOCK_K
        w_ptrs += BLOCK_K

    if EVEN_N:
        sc = tl.load(s_ptr + rn)
    else:
        sc = tl.load(s_ptr + rn, mask=rn < N, other=0.0)
    acc = acc * sc[None, :]
    tl.store(y_ptr + rm[:, None] * N + rn[None, :], acc.to(tl.bfloat16),
             mask=m_mask & (rn[None, :] < N))


def _fallback_configs():
    out = []
    for bm, bn, bk, ns, nw, es in [
        (128, 256, 64, 3, 8, True),
        (128, 128, 128, 3, 8, False),
        (128, 128, 64, 5, 8, False),
        (64, 256, 128, 3, 4, True),
    ]:
        out.append(triton.Config(
            {"BLOCK_M": bm, "BLOCK_N": bn, "BLOCK_K": bk, "GROUP_M": 8,
             "EPILOGUE_SUBTILE": es},
            num_stages=ns, num_warps=nw))
    return out


@triton.autotune(configs=_fallback_configs(), key=["M", "N", "K", "stride_xm"])
@triton.jit
def _fp8_gemm_tma(
    x_ptr, w_ptr, s_ptr, y_ptr,
    M, N, K,
    stride_xm, stride_wn,
    BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
    GROUP_M: tl.constexpr, EPILOGUE_SUBTILE: tl.constexpr,
    NUM_SMS: tl.constexpr,
):
    start_pid = tl.program_id(0)
    num_pid_m = tl.cdiv(M, BLOCK_M)
    num_pid_n = tl.cdiv(N, BLOCK_N)
    k_tiles = tl.cdiv(K, BLOCK_K)
    num_tiles = num_pid_m * num_pid_n

    x_desc = tl.make_tensor_descriptor(
        x_ptr, shape=[M, K], strides=[stride_xm, 1],
        block_shape=[BLOCK_M, BLOCK_K])
    w_desc = tl.make_tensor_descriptor(
        w_ptr, shape=[N, K], strides=[stride_wn, 1],
        block_shape=[BLOCK_N, BLOCK_K])
    y_desc = tl.make_tensor_descriptor(
        y_ptr, shape=[M, N], strides=[N, 1],
        block_shape=[BLOCK_M, BLOCK_N // 2 if EPILOGUE_SUBTILE else BLOCK_N])

    for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
        num_pid_in_group = GROUP_M * num_pid_n
        group_id = tile_id // 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 + (tile_id % group_size_m)
        pid_n = (tile_id % num_pid_in_group) // group_size_m

        offs_am = pid_m * BLOCK_M
        offs_bn = pid_n * BLOCK_N

        acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
        for ki in range(k_tiles):
            offs_k = ki * BLOCK_K
            a = x_desc.load([offs_am, offs_k])
            b = w_desc.load([offs_bn, offs_k])
            acc = tl.dot(a, b.T, acc)

        rn = offs_bn + tl.arange(0, BLOCK_N)
        scale = tl.load(s_ptr + rn, mask=rn < N, other=0.0)
        acc = acc * scale[None, :]
        if EPILOGUE_SUBTILE:
            acc1 = tl.reshape(acc, (BLOCK_M, 2, BLOCK_N // 2))
            acc1 = tl.permute(acc1, (0, 2, 1))
            acc0, accn = tl.split(acc1)
            y_desc.store([offs_am, offs_bn], acc0.to(tl.bfloat16))
            y_desc.store([offs_am, offs_bn + BLOCK_N // 2], accn.to(tl.bfloat16))
        else:
            y_desc.store([offs_am, offs_bn], acc.to(tl.bfloat16))


def _next_pow2(v: int) -> int:
    n = 1
    while n < v:
        n *= 2
    return n


# ===========================================================================
# Model
# ===========================================================================
class Model(nn.Module):
    """y = ((x @ w.T) * weight_scale).to(bf16) via fp8 tensor-core MMA."""

    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))
        # Zero-padded 16B-aligned staging buffers, used only when K % 16 != 0.
        # Round to a multiple of 64: measured faster than the minimal multiple
        # of 16 (better DRAM/TMA behavior of the padded stride).
        self._k_pad = (K + 63) // 64 * 64
        self._x_pad = None
        self._w_pad = None
        self._w_pad_key = None
        self._skinny_graph = None
        self._skinny_key = None

    def _staged(self, x: torch.Tensor):
        """Copy x / weight into zero-padded buffers with 16B-aligned strides."""
        K_pad = self._k_pad
        if self._x_pad is None or self._x_pad.shape[0] != x.shape[0]:
            self._x_pad = torch.zeros(
                x.shape[0], K_pad, dtype=torch.float8_e4m3fn, device=x.device)
        if self._w_pad is None:
            self._w_pad = torch.zeros(
                self.weight.shape[0], K_pad, dtype=torch.float8_e4m3fn,
                device=x.device)
        self._copy_pad(x, self._x_pad)
        # The weight buffer can be mutated in place between calls (any
        # in-place torch op bumps _version), so restage when stale.
        w_key = (self.weight.data_ptr(), self.weight._version,
                 self._w_pad.data_ptr())
        if w_key != self._w_pad_key:
            self._copy_pad(self.weight, self._w_pad)
            self._w_pad_key = (self.weight.data_ptr(), self.weight._version,
                               self._w_pad.data_ptr())
        return self._x_pad, self._w_pad

    def _copy_pad(self, src, dst):
        rows, k = src.shape
        if (_EXT is not None and src.is_contiguous()
                and src.data_ptr() % 4 == 0 and dst.data_ptr() % 16 == 0):
            _EXT.pad_copy(src.view(torch.uint8), dst.view(torch.uint8),
                          rows, k, self._k_pad)
        else:
            dst[:, :k].view(torch.int8).copy_(src.view(torch.int8))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        w, s = self.weight, self.weight_scale
        M, K = x.shape
        N = w.shape[0]

        if M < 64:
            return self._forward_skinny(x, w, s, M, N, K)

        K_eff = K
        if K % 16 != 0:
            x, w = self._staged(x)
            K_eff = self._k_pad  # trailing columns are zero: no effect on sum

        y = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
        if _EXT is not None:
            try:
                _EXT.fp8_gemm(x, w, s, y, M, N, K_eff)
                return y
            except RuntimeError:
                pass  # e.g. unusual N alignment: fall through to Triton

        NUM_SMS = _num_sms()
        grid = lambda meta: (min(
            NUM_SMS,
            triton.cdiv(M, meta["BLOCK_M"]) * triton.cdiv(N, meta["BLOCK_N"])),)
        _fp8_gemm_tma[grid](
            x, w, s, y, M, N, K_eff, x.stride(0), w.stride(0), NUM_SMS=NUM_SMS)
        return y

    def _launch_skinny(self, x, w, s, y, M, N, K):
        BLOCK_M = max(16, _next_pow2(M))
        _fp8_gemm_skinny[(triton.cdiv(N, 64),)](
            x, w, s, y, M, N, K, x.stride(0), w.stride(0),
            BLOCK_M=BLOCK_M, BLOCK_N=64, BLOCK_K=256,
            EVEN_N=(N % 64 == 0), EVEN_K=(K % 256 == 0),
            num_stages=3, num_warps=4)

    def _forward_skinny(self, x, w, s, M, N, K):
        # Launch overhead is a measurable fraction of this memory-bound
        # kernel, so replay it through a CUDA graph with a static input slot.
        key = (x.shape, x.stride(), w.data_ptr(), s.data_ptr(), x.device.index)
        if self._skinny_graph is None or self._skinny_key != key:
            try:
                x_st = x.clone()
                y_st = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
                self._launch_skinny(x_st, w, s, y_st, M, N, K)  # warm cache
                torch.cuda.synchronize()
                g = torch.cuda.CUDAGraph()
                with torch.cuda.graph(g):
                    self._launch_skinny(x_st, w, s, y_st, M, N, K)
                self._skinny_graph = (g, x_st, y_st)
                self._skinny_key = key
            except Exception:
                self._skinny_graph, self._skinny_key = None, None
                y = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
                self._launch_skinny(x, w, s, y, M, N, K)
                return y
        g, x_st, y_st = self._skinny_graph
        x_st.copy_(x)
        g.replay()
        return y_st


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]

20260721_152955_or-fable_anthropic_claude-fable-5_01_fp8_gemm