KernelBench hard · H100
TopK Bitonic GLM-5.2
0.34%geomean peak fraction across shapes
harnesszai-claudeagent session45mtotal wall53mcheck2mbenchmark6moutput tokens—gpu-lock wait7mgpu-lock held48sregimememory
Per-shape vs governing ceilingeach shape graded against whichever binds — fp32 compute or HBM bandwidth
1×131072×641.549 ms0.0%0.00 TB/s · 0% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
64×8192×80.067 ms1.5%0.03 TB/s · 2% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
32×16384×320.649 ms0.2%0.00 TB/s · 0% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
16×12000×160.151 ms0.3%0.01 TB/s · 0% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
128×4096×10.022 ms4.6%0.09 TB/s · 5% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(0.0% · 1.5% · 0.2% · 0.3% · 4.6%) = 0.4%
Kernel source (redacted)
"""Custom CUDA top-k kernel for H100 PCIe (SM90).
Memory-bound: the input read dominates. Strategy:
- Partial kernel: a grid of (B blocks/row x batch) tiles. Each block's threads
cooperatively scan a tile, each maintaining a sorted top-K buffer in shared
memory (filtered by a register-resident running minimum so most elements cost
one register compare). A ping-pong merge tree in shared memory reduces the
per-thread buffers to the block's top-K.
- Reduce kernel (only when B>1): merges the B per-tile top-K buffers per row
into the final sorted top-k.
K = next power of two >= k (all evaluated k are already powers of two). Values
are copied verbatim from the input (never recomputed), so numeric error is ~0.
"""
import os
os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "9.0")
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
_CPP_DECL = r"""
#include <torch/extension.h>
#include <vector>
std::vector<at::Tensor> topk_forward(at::Tensor x, int64_t k, int64_t B,
int64_t THREADS, int64_t RTHREADS);
"""
_CUDA_SRC = r"""
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>
static __device__ __constant__ float NEG_INF_C = -INFINITY;
template <int K>
__device__ __forceinline__ void insert_sorted(float* val, int64_t* idx,
float x, int64_t gidx) {
// val[0..K-1] sorted DESCENDING (val[0] max, val[K-1] min). Insert x if it
// beats the current min, shifting smaller elements down.
if (x > val[K - 1]) {
int p = K - 1;
while (p > 0 && val[p - 1] < x) {
val[p] = val[p - 1];
idx[p] = idx[p - 1];
--p;
}
val[p] = x;
idx[p] = gidx;
}
}
// Ping-pong merge tree. On entry each thread tid has written its K-length sorted
// buffer to sv[tid*K..], si[tid*K..]. After the tree the block's top-K (sorted
// desc) lives in sv[0..K-1], si[0..K-1].
template <int K>
__device__ __forceinline__ void reduce_tree(float* sv, int64_t* si, int THREADS) {
float* bufs[2] = {sv, sv + (size_t)THREADS * K};
int64_t* bufi[2] = {si, si + (size_t)THREADS * K};
int src = 0;
int tid = threadIdx.x;
for (int stride = THREADS / 2; stride > 0; stride >>= 1) {
int dst = 1 - src;
if (tid < stride) {
float* A = bufs[src] + (size_t)tid * K;
float* B = bufs[src] + (size_t)(tid + stride) * K;
float* O = bufs[dst] + (size_t)tid * K;
int64_t* Ai = bufi[src] + (size_t)tid * K;
int64_t* Bi = bufi[src] + (size_t)(tid + stride) * K;
int64_t* Oi = bufi[dst] + (size_t)tid * K;
int i = 0, j = 0;
for (int t = 0; t < K; ++t) {
bool takeA = (j >= K) || (i < K && A[i] >= B[j]);
if (takeA) { O[t] = A[i]; Oi[t] = Ai[i]; ++i; }
else { O[t] = B[j]; Oi[t] = Bi[j]; ++j; }
}
}
__syncthreads();
src = dst;
}
if (src == 1 && tid < K) {
sv[tid] = bufs[1][tid];
si[tid] = bufi[1][tid];
}
__syncthreads();
}
// Partial kernel: top-K of one tile -> scratch (or output when B==1 & K==k).
template <int K>
__global__ void partial_topk(const float* __restrict__ x, int n,
int elts_per_block, int B,
float* __restrict__ sval,
int64_t* __restrict__ sidx) {
int row = blockIdx.y;
int b = blockIdx.x;
int tid = threadIdx.x;
int THREADS = blockDim.x;
const float* xrow = x + (int64_t)row * n;
int tile_start = b * elts_per_block;
int tile_end = min(tile_start + elts_per_block, n);
extern __shared__ char smem_raw[];
float* sv = reinterpret_cast<float*>(smem_raw);
int64_t* si = reinterpret_cast<int64_t*>(sv + 2 * (size_t)THREADS * K);
float* myval = sv + (size_t)tid * K;
int64_t* myidx = si + (size_t)tid * K;
#pragma unroll
for (int t = 0; t < K; ++t) { myval[t] = -INFINITY; myidx[t] = 0; }
float cur_min = -INFINITY;
for (int gi = tile_start + tid; gi < tile_end; gi += THREADS) {
float v = xrow[gi];
if (v > cur_min) {
insert_sorted<K>(myval, myidx, v, (int64_t)gi);
cur_min = myval[K - 1];
}
}
__syncthreads();
reduce_tree<K>(sv, si, THREADS);
int out_off = (row * B + b) * K;
if (tid < K) {
sval[out_off + tid] = sv[tid];
sidx[out_off + tid] = si[tid];
}
}
// Reduce kernel: merge B per-tile top-K buffers per row -> final top-k.
template <int K>
__global__ void reduce_topk(const float* __restrict__ sval,
const int64_t* __restrict__ sidx, int B, int k,
float* __restrict__ out_val,
int64_t* __restrict__ out_idx) {
int row = blockIdx.x;
int tid = threadIdx.x;
int THREADS = blockDim.x;
int total = B * K;
const float* rv = sval + (int64_t)row * total;
const int64_t* ri = sidx + (int64_t)row * total;
extern __shared__ char smem_raw[];
float* sv = reinterpret_cast<float*>(smem_raw);
int64_t* si = reinterpret_cast<int64_t*>(sv + 2 * (size_t)THREADS * K);
float* myval = sv + (size_t)tid * K;
int64_t* myidx = si + (size_t)tid * K;
#pragma unroll
for (int t = 0; t < K; ++t) { myval[t] = -INFINITY; myidx[t] = 0; }
float cur_min = -INFINITY;
for (int i = tid; i < total; i += THREADS) {
float v = rv[i];
if (v > cur_min) {
insert_sorted<K>(myval, myidx, v, ri[i]);
cur_min = myval[K - 1];
}
}
__syncthreads();
reduce_tree<K>(sv, si, THREADS);
if (tid < k) {
out_val[row * k + tid] = sv[tid];
out_idx[row * k + tid] = si[tid];
}
}
template <int K>
static void topk_impl(const float* xp, int batch, int n, int k, int B,
int THREADS, int RTHREADS, at::Tensor& out_val,
at::Tensor& out_idx, const at::TensorOptions& opts_f,
const at::TensorOptions& opts_l) {
int elts_per_block = (n + B - 1) / B;
size_t bytes_pair = (size_t)2 * THREADS * K;
size_t smem_p = sizeof(float) * bytes_pair + sizeof(int64_t) * bytes_pair;
size_t smem_r = sizeof(float) * (size_t)2 * RTHREADS * K +
sizeof(int64_t) * (size_t)2 * RTHREADS * K;
cudaFuncSetAttribute((void*)partial_topk<K>,
cudaFuncAttributeMaxDynamicSharedMemorySize,
(int)smem_p);
bool direct = (B == 1) && (K == k);
if (direct) {
dim3 grid(B, batch);
partial_topk<K><<<grid, THREADS, smem_p>>>(
xp, n, elts_per_block, B,
out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
} else {
auto sval = at::empty({batch, B, K}, opts_f);
auto sidx = at::empty({batch, B, K}, opts_l);
dim3 grid(B, batch);
partial_topk<K><<<grid, THREADS, smem_p>>>(
xp, n, elts_per_block, B,
sval.data_ptr<float>(), sidx.data_ptr<int64_t>());
cudaFuncSetAttribute((void*)reduce_topk<K>,
cudaFuncAttributeMaxDynamicSharedMemorySize,
(int)smem_r);
reduce_topk<K><<<batch, RTHREADS, smem_r>>>(
sval.data_ptr<float>(), sidx.data_ptr<int64_t>(), B, k,
out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
}
}
std::vector<at::Tensor> topk_forward(at::Tensor x, int64_t k, int64_t B,
int64_t THREADS, int64_t RTHREADS) {
TORCH_CHECK(x.dim() == 2, "expected 2D");
TORCH_CHECK(x.is_cuda(), "expected cuda");
TORCH_CHECK(x.is_contiguous(), "expected contiguous");
TORCH_CHECK(x.scalar_type() == at::kFloat, "expected fp32");
int batch = x.size(0);
int n = x.size(1);
int K = 1;
while (K < (int)k) K <<= 1;
auto opts_f = x.options();
auto opts_l = x.options().dtype(at::kLong);
auto out_val = at::empty({batch, k}, opts_f);
auto out_idx = at::empty({batch, k}, opts_l);
const float* xp = x.data_ptr<float>();
switch (K) {
case 1: topk_impl<1> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 2: topk_impl<2> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 4: topk_impl<4> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 8: topk_impl<8> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 16: topk_impl<16> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 32: topk_impl<32> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 64: topk_impl<64> (xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
case 128: topk_impl<128>(xp, batch, n, (int)k, (int)B, (int)THREADS, (int)RTHREADS, out_val, out_idx, opts_f, opts_l); break;
default: TORCH_CHECK(false, "K too large");
}
return {out_val, out_idx};
}
"""
_ext = load_inline(
name="kbh_topk_ext",
cpp_sources=[_CPP_DECL],
cuda_sources=[_CUDA_SRC],
functions=["topk_forward"],
extra_cuda_cflags=["-O3", "--use_fast_math", "-gencode=arch=compute_90,code=sm_90"],
verbose=False,
)
def _next_pow2(x):
k = 1
while k < x:
k <<= 1
return k
def _choose_config(batch, n, k):
"""Pick (blocks-per-row B, partial THREADS, reduce THREADS)."""
K = _next_pow2(k)
# Threads: smem budget 2*THREADS*K*12 <= ~200KB. K>=64 -> 128 threads.
if K >= 64:
threads = 128
else:
threads = 256
rthreads = 128 if K >= 64 else 256
# Target enough total blocks to fill ~132 SMs with some slack.
target = 132
if batch >= 100:
B = 1
else:
B = max(1, (target + batch - 1) // batch)
B = min(B, n) # never more blocks than elements
return B, threads, rthreads
class Model(nn.Module):
def __init__(self, batch: int, n: int, k: int):
super().__init__()
self.batch, self.n, self.k = batch, n, k
self.register_buffer("_dummy", torch.zeros(1))
self.B, self.THREADS, self.RTHREADS = _choose_config(batch, n, k)
def forward(self, x: torch.Tensor):
x = x.contiguous()
v, i = _ext.topk_forward(x, self.k, self.B, self.THREADS, self.RTHREADS)
return v, i
# Module-level shims rebuilt by check.py / benchmark.py per shape.
batch = 64
n = 8192
k = 8
def get_inputs():
x = torch.randn(batch, n, dtype=torch.float32)
return [x]
def get_init_inputs():
return [batch, n, k]
20260618_170904_zai-claude_glm-5.2_05_topk_bitonic