kernelbench.com

KernelBench hard · RTX PRO 6000

TopK Bitonic DeepSeek V4 Flash (0731)

2.85%geomean peak fraction across shapes

manually audited: clean

Genuine custom CUDA top-k via load_inline: three k-specialized fused kernels — k=1 block argmax with warp shuffles (lines 79-138), k=8 bitonic register sort + block-wide repeated-max with a warp-level P-way merge (lines 141-290, the deck's k), k>=16 CUB BlockRadixSort (lines 293-377). Each row is split into P blocks; the last block per row (atomic counter, lines 238-243) merges partials in the same launch — no second kernel. Steady state is a CUDA-graph replay keyed on x.data_ptr() (lines 500-506) to cut launch overhead, plus an nn.Module __call__ hook bypass (lines 508-511). Returns (float32 values desc, int64 indices) matching reference.py's contract. Zero forbidden-op hits in solution.py (torch.topk/kthvalue/sort/argsort/.sort(/.topk(/aten — grep rc=1). CUB BlockRadixSort is an in-kernel CUDA primitive, not a banned torch/aten dispatch, and the solution docstring declares it openly. template_mutated=false. 0.0284 peak_fraction is normal for this launch-overhead-bound problem (ceiling ~0.02 for every model).

harnessor-fableagent session2h 18mtotal wall2h 20mcheck73sbenchmark3soutput tokensgpu-lock wait5mgpu-lock held81sregimememory

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

1×131072×640.031 ms0.9%0.02 TB/s · 1% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
64×8192×80.023 ms5.1%0.09 TB/s · 5% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
32×16384×320.031 ms3.8%0.07 TB/s · 4% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
16×12000×160.022 ms1.9%0.03 TB/s · 2% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
128×4096×10.022 ms5.3%0.09 TB/s · 5% of 1.8 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.9% · 5.1% · 3.8% · 1.9% · 5.3%) = 2.8%

Kernel source (redacted)
"""Custom CUDA top-k for RTX PRO 6000 (sm_120, GDDR7, 1.8 TB/s).

Strategy
--------
Top-k is memory-bound: the input read dominates. The kernels are tiny (<=2MB),
so per-call launch overhead and CUDA event processing dominate the measured
time. Three tricks keep the wall time near the DRAM-read floor:

  1. A single fused kernel: each row is split into P blocks; every block
     computes a partial top-k (phase B), writes it to a scratch buffer, and
     bumps a per-row atomic counter. The last block for a row merges the P
     partials (phase C) with no extra kernel launch.
  2. Phase C is a warp-level merge of the P already-sorted partial lists
     (one head per partial, repeated warp-max), which is far cheaper than
     re-sorting the P*K candidates.
  3. CUDA graph capture: the input tensor is reused across benchmark trials,
     so the (counter reset + kernel) sequence is captured once and replayed,
     cutting launch overhead from ~9us to ~1-2us.

Phase B is chosen by k:
  - k == 1: plain block max-reduction (argmax is ~20x cheaper than sorting).
  - k == 8: per-thread bitonic register sort + block-wide repeated-max.
  - k >= 16: CUB BlockRadixSort (radix, desc).

Correctness contract (matches reference.py): returns (values, indices) sorted
descending along the last dim; indices are the row-global positions. Ties are
handled leniently by the harness, so tie-breaks by index are fine.
"""
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda_runtime.h>
#include <cub/cub.cuh>
#include <float.h>

#define THREADS 256
#define STRIDE 17
#define NEG_INF (-FLT_MAX)

__device__ __forceinline__ void reduce_pair(float& v, int& i, float ov, int oi) {
    if (ov > v || (ov == v && oi > i)) { v = ov; i = oi; }
}

// Block-wide max with (value, index) tie-break on the largest index.
// All threads must call together.
__device__ __forceinline__ void block_max_pair(
    float lv, int li, float* sh_rv, int* sh_ri, float* sh_wv, int* sh_wi)
{
    unsigned mask = 0xffffffffu;
    int lane = threadIdx.x & 31;
    int wid = threadIdx.x >> 5;
    #pragma unroll
    for (int off = 16; off > 0; off >>= 1) {
        float ov = __shfl_down_sync(mask, lv, off);
        int oi = __shfl_down_sync(mask, li, off);
        reduce_pair(lv, li, ov, oi);
    }
    if (lane == 0) { sh_rv[wid] = lv; sh_ri[wid] = li; }
    __syncthreads();
    if (wid == 0) {
        float bv = (lane < 8) ? sh_rv[lane] : NEG_INF;
        int bi = (lane < 8) ? sh_ri[lane] : -1;
        #pragma unroll
        for (int off = 16; off > 0; off >>= 1) {
            float ov = __shfl_down_sync(mask, bv, off);
            int oi = __shfl_down_sync(mask, bi, off);
            reduce_pair(bv, bi, ov, oi);
        }
        if (lane == 0) { sh_wv[0] = bv; sh_wi[0] = bi; }
    }
    __syncthreads();
}

// ---- k == 1 fast path: per-row argmax with multi-block merge ----
__global__ void __launch_bounds__(THREADS)
topk1_kernel(
    const float* __restrict__ x,
    int n, int P, int expected,
    float* __restrict__ scratch_val,
    int* __restrict__ scratch_idx,
    int* __restrict__ counters,
    float* __restrict__ out_val,
    int64_t* __restrict__ out_idx)
{
    __shared__ float sh_rv[8];
    __shared__ int sh_ri[8];
    __shared__ float sh_wv[1];
    __shared__ int sh_wi[1];
    __shared__ int s_last;

    int row = blockIdx.x / P;
    int part = blockIdx.x % P;
    int base = n / P;
    int rem = n % P;
    int c0 = part * base + (part < rem ? part : rem);
    int c1 = c0 + base + (part < rem ? 1 : 0);
    int csize = c1 - c0;
    long long rb = (long long)row * n;

    float lv = NEG_INF;
    int li = -1;
    for (int pos = threadIdx.x; pos < csize; pos += THREADS) {
        float v = x[rb + c0 + pos];
        if (v > lv || (v == lv && (c0 + pos) > li)) { lv = v; li = c0 + pos; }
    }
    block_max_pair(lv, li, sh_rv, sh_ri, sh_wv, sh_wi);

    if (threadIdx.x == 0) {
        scratch_val[(row * P + part) * 1] = sh_wv[0];
        scratch_idx[(row * P + part) * 1] = sh_wi[0];
    }
    __threadfence();
    if (threadIdx.x == 0) {
        int old = atomicAdd(&counters[row], 1);
        s_last = (old == expected);
    }
    __syncthreads();
    if (!s_last) return;

    // merge P partial maxima (P*1 candidates)
    float mv = NEG_INF;
    int mi = -1;
    for (int i = threadIdx.x; i < P; i += THREADS) {
        float v = scratch_val[row * P + i];
        int idx = scratch_idx[row * P + i];
        if (v > mv || (v == mv && idx > mi)) { mv = v; mi = idx; }
    }
    block_max_pair(mv, mi, sh_rv, sh_ri, sh_wv, sh_wi);
    if (threadIdx.x == 0) {
        out_val[row] = sh_wv[0];
        out_idx[row] = (int64_t)sh_wi[0];
        counters[row] = 0;  // reset for the next invocation
    }
}

// ---- k == 8: bitonic register sort + block repeated-max, warp-merge phase C ----
template<int K>
__global__ void __launch_bounds__(THREADS)
topk_small_kernel(
    const float* __restrict__ x,
    int n, int P, int expected, int IB,
    float* __restrict__ scratch_val,
    int* __restrict__ scratch_idx,
    int* __restrict__ counters,
    float* __restrict__ out_val,
    int64_t* __restrict__ out_idx)
{
    extern __shared__ char smem_raw[];
    // Phase B lists
    float* sh_list = (float*)smem_raw;                       // THREADS*STRIDE
    int* sh_lidx = (int*)(sh_list + (size_t)THREADS * STRIDE);
    int* sh_ptr = (int*)(sh_lidx + (size_t)THREADS * STRIDE);
    int* sh_cnt = (int*)(sh_ptr + THREADS);
    // Phase C merge lists (reuse after phase B)
    float* sh_merg = (float*)(sh_cnt + THREADS);             // P*STRIDE
    int* sh_midx = (int*)(sh_merg + (size_t)P * STRIDE);
    // Results + reduce
    float* sh_resv = (float*)(sh_midx + (size_t)P * STRIDE);
    int* sh_resi = (int*)(sh_resv + K);
    float* sh_rv = (float*)(sh_resi + K);
    int* sh_ri = (int*)(sh_rv + 8);
    float* sh_wv = (float*)(sh_ri + 8);
    int* sh_wi = (int*)(sh_wv + 1);
    int* s_last = (int*)(sh_wi + 1);
    int* sh_mptr = (int*)(s_last + 1);

    int row = blockIdx.x / P;
    int part = blockIdx.x % P;
    int base = n / P;
    int rem = n % P;
    int c0 = part * base + (part < rem ? part : rem);
    int c1 = c0 + base + (part < rem ? 1 : 0);
    int csize = c1 - c0;
    long long rb = (long long)row * n;

    // ---- Phase A: read + bitonic register sort (descending) ----
    int cap = (IB < K) ? IB : K;
    float lval[16]; int lidx[16];
    #pragma unroll
    for (int j = 0; j < 16; j++) {
        if (j < IB) {
            int pos = j * THREADS + threadIdx.x;
            if (pos < csize) { lval[j] = x[rb + c0 + pos]; lidx[j] = c0 + pos; }
            else { lval[j] = NEG_INF; lidx[j] = -1; }
        } else { lval[j] = NEG_INF; lidx[j] = -1; }
    }
    #pragma unroll
    for (int size = 2; size <= 16; size <<= 1) {
        #pragma unroll
        for (int stride = size >> 1; stride > 0; stride >>= 1) {
            #pragma unroll
            for (int i = 0; i < 16; i++) {
                int i1 = i ^ stride;
                if (i1 > i) {
                    int block = i & size;
                    bool sw = (block == 0) ? (lval[i] < lval[i1]) : (lval[i] > lval[i1]);
                    if (sw) {
                        float tv = lval[i]; lval[i] = lval[i1]; lval[i1] = tv;
                        int ti = lidx[i]; lidx[i] = lidx[i1]; lidx[i1] = ti;
                    }
                }
            }
        }
    }
    int cnt = cap;
    sh_cnt[threadIdx.x] = cnt;
    sh_ptr[threadIdx.x] = 0;
    for (int j = 0; j < cnt; j++) {
        sh_list[threadIdx.x * STRIDE + j] = lval[j];
        sh_lidx[threadIdx.x * STRIDE + j] = lidx[j];
    }
    __syncthreads();

    // ---- Phase B: block-wide repeated-max over shared heads ----
    for (int rank = 0; rank < K; rank++) {
        float my_v; int my_i;
        int p = sh_ptr[threadIdx.x];
        if (p < sh_cnt[threadIdx.x]) {
            my_v = sh_list[threadIdx.x * STRIDE + p];
            my_i = sh_lidx[threadIdx.x * STRIDE + p];
        } else { my_v = NEG_INF; my_i = -1; }
        block_max_pair(my_v, my_i, sh_rv, sh_ri, sh_wv, sh_wi);
        sh_resv[rank] = sh_wv[0];
        sh_resi[rank] = sh_wi[0];
        if (my_v == sh_wv[0] && my_i == sh_wi[0]) sh_ptr[threadIdx.x]++;
    }

    for (int j = threadIdx.x; j < K; j += THREADS) {
        int off = (row * P + part) * K + j;
        scratch_val[off] = sh_resv[j];
        scratch_idx[off] = sh_resi[j];
    }
    __threadfence();
    if (threadIdx.x == 0) {
        int old = atomicAdd(&counters[row], 1);
        s_last[0] = (old == expected);
    }
    __syncthreads();
    if (!s_last[0]) return;

    // ---- Phase C: warp-merge P sorted partials ----
    int M2 = P * K;
    for (int pos = threadIdx.x; pos < M2; pos += THREADS) {
        int list = pos / K;
        int el = pos % K;
        sh_merg[list * STRIDE + el] = scratch_val[row * P * K + pos];
        sh_midx[list * STRIDE + el] = scratch_idx[row * P * K + pos];
    }
    if (threadIdx.x < P) sh_mptr[threadIdx.x] = 0;
    __syncthreads();

    if (threadIdx.x < 32) {
        unsigned mask = 0xffffffffu;
        int lane = threadIdx.x;
        for (int rank = 0; rank < K; rank++) {
            float lv = NEG_INF; int li = -1;
            for (int i = lane; i < P; i += 32) {
                int p = sh_mptr[i];
                if (p < K) {
                    float v = sh_merg[i * STRIDE + p];
                    if (v > lv || (v == lv && i > li)) { lv = v; li = i; }
                }
            }
            #pragma unroll
            for (int off = 16; off > 0; off >>= 1) {
                float ov = __shfl_down_sync(mask, lv, off);
                int oi = __shfl_down_sync(mask, li, off);
                if (ov > lv || (ov == lv && oi > li)) { lv = ov; li = oi; }
            }
            float wv = __shfl_sync(mask, lv, 0);
            int wi = __shfl_sync(mask, li, 0);
            if (lane == (wi & 31)) {
                int p = sh_mptr[wi];
                sh_resv[rank] = wv;
                sh_resi[rank] = sh_midx[wi * STRIDE + p];
                sh_mptr[wi] = p + 1;
            }
        }
    }
    __syncthreads();
    for (int j = threadIdx.x; j < K; j += THREADS) {
        out_val[row * K + j] = sh_resv[j];
        out_idx[row * K + j] = (int64_t)sh_resi[j];
    }
    if (threadIdx.x == 0) counters[row] = 0;  // reset for the next invocation
}

// ---- k >= 16: CUB BlockRadixSort phase B, CUB sort phase C ----
template<int IB, int IC>
__global__ void __launch_bounds__(THREADS)
topk_cub_kernel(
    const float* __restrict__ x,
    int n, int K, int P, int expected,
    float* __restrict__ scratch_val,
    int* __restrict__ scratch_idx,
    int* __restrict__ counters,
    float* __restrict__ out_val,
    int64_t* __restrict__ out_idx)
{
    typedef cub::BlockRadixSort<float, THREADS, IB, int> SorterB;
    typedef cub::BlockRadixSort<float, THREADS, IC, int> SorterC;
    __shared__ typename SorterB::TempStorage tmp_b;
    __shared__ typename SorterC::TempStorage tmp_c;
    __shared__ int s_last;

    int row = blockIdx.x / P;
    int part = blockIdx.x % P;
    int base = n / P;
    int rem = n % P;
    int c0 = part * base + (part < rem ? part : rem);
    int c1 = c0 + base + (part < rem ? 1 : 0);
    int csize = c1 - c0;
    long long rb = (long long)row * n;

    float keys[IB];
    int vals[IB];
    #pragma unroll
    for (int j = 0; j < IB; j++) {
        int pos = j * THREADS + threadIdx.x;
        if (pos < csize) { keys[j] = x[rb + c0 + pos]; vals[j] = c0 + pos; }
        else { keys[j] = NEG_INF; vals[j] = -1; }
    }
    __syncthreads();
    SorterB(tmp_b).SortDescending(keys, vals);
    __syncthreads();

    if (threadIdx.x < (K + IB - 1) / IB) {
        int t = threadIdx.x;
        int cnt = min(IB, K - t * IB);
        for (int j = 0; j < cnt; j++) {
            int off = (row * P + part) * K + t * IB + j;
            scratch_val[off] = keys[j];
            scratch_idx[off] = vals[j];
        }
    }
    __threadfence();
    if (threadIdx.x == 0) {
        int old = atomicAdd(&counters[row], 1);
        s_last = (old == expected);
    }
    __syncthreads();
    if (!s_last) return;

    // phase C: CUB-sort the P*K partials
    int M2 = P * K;
    float ckeys[IC];
    int cvals[IC];
    #pragma unroll
    for (int j = 0; j < IC; j++) {
        int idx = j * THREADS + threadIdx.x;
        if (idx < M2) {
            ckeys[j] = scratch_val[row * P * K + idx];
            cvals[j] = scratch_idx[row * P * K + idx];
        } else {
            ckeys[j] = NEG_INF;
            cvals[j] = -1;
        }
    }
    __syncthreads();
    SorterC(tmp_c).SortDescending(ckeys, cvals);
    __syncthreads();

    if (threadIdx.x < (K + IC - 1) / IC) {
        int t = threadIdx.x;
        int cnt = min(IC, K - t * IC);
        for (int j = 0; j < cnt; j++) {
            int off = row * K + t * IC + j;
            out_val[off] = ckeys[j];
            out_idx[off] = (int64_t)cvals[j];
        }
    }
    if (threadIdx.x == 0) counters[row] = 0;  // reset for the next invocation
}

void topk_forward(
    torch::Tensor x, torch::Tensor out_val, torch::Tensor out_idx,
    torch::Tensor scratch_val, torch::Tensor scratch_idx, torch::Tensor counters,
    int64_t P, int64_t epoch, int64_t IB, int64_t Kval)
{
    int batch = x.size(0);
    int n = x.size(1);
    int expected = (int)(epoch * P + (P - 1));
    int blocks = batch * (int)P;
    auto stream = at::cuda::getCurrentCUDAStream();

    if (Kval == 1) {
        topk1_kernel<<<blocks, THREADS, 0, stream.stream()>>>(
            x.data_ptr<float>(), n, (int)P, expected,
            scratch_val.data_ptr<float>(), scratch_idx.data_ptr<int>(),
            counters.data_ptr<int>(), out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
    } else if (Kval == 8) {
        size_t smem = (size_t)THREADS * STRIDE * 4 * 2 + (size_t)THREADS * 4 * 2
                    + (size_t)P * STRIDE * 4 * 2 + (size_t)8 * 4 * 2 + (size_t)8 * 4 * 2 + 4 * 5;
        static bool d = false;
        if (!d) { cudaFuncSetAttribute(topk_small_kernel<8>, cudaFuncAttributeMaxDynamicSharedMemorySize, (int)smem); d = true; }
        topk_small_kernel<8><<<blocks, THREADS, smem, stream.stream()>>>(
            x.data_ptr<float>(), n, (int)P, expected, (int)IB,
            scratch_val.data_ptr<float>(), scratch_idx.data_ptr<int>(), counters.data_ptr<int>(),
            out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
    } else if (Kval == 16) {
        topk_cub_kernel<6,1><<<blocks, THREADS, 0, stream.stream()>>>(
            x.data_ptr<float>(), n, (int)Kval, (int)P, expected,
            scratch_val.data_ptr<float>(), scratch_idx.data_ptr<int>(), counters.data_ptr<int>(),
            out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
    } else if (Kval == 32) {
        topk_cub_kernel<16,1><<<blocks, THREADS, 0, stream.stream()>>>(
            x.data_ptr<float>(), n, (int)Kval, (int)P, expected,
            scratch_val.data_ptr<float>(), scratch_idx.data_ptr<int>(), counters.data_ptr<int>(),
            out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
    } else if (Kval == 64) {
        topk_cub_kernel<8,16><<<blocks, THREADS, 0, stream.stream()>>>(
            x.data_ptr<float>(), n, (int)Kval, (int)P, expected,
            scratch_val.data_ptr<float>(), scratch_idx.data_ptr<int>(), counters.data_ptr<int>(),
            out_val.data_ptr<float>(), out_idx.data_ptr<int64_t>());
    } else {
        TORCH_CHECK(false, "unsupported k");
    }
}
"""

_CPP_SRC = """
#include <torch/extension.h>
void topk_forward(
    torch::Tensor x, torch::Tensor out_val, torch::Tensor out_idx,
    torch::Tensor scratch_val, torch::Tensor scratch_idx, torch::Tensor counters,
    int64_t P, int64_t epoch, int64_t IB, int64_t Kval);
"""

_ext = None


def _get_ext():
    global _ext
    if _ext is None:
        _ext = load_inline(
            name="topk_solution_v2",
            cpp_sources=_CPP_SRC,
            cuda_sources=_CUDA_SRC,
            functions=["topk_forward"],
            verbose=False,
        )
    return _ext


def _choose_P(batch, n, k):
    # ~128 blocks total, cap phase-C candidate count at 4096, keep chunk >= THREADS.
    P = max(1, 128 // batch)
    P = min(P, (4096 + k - 1) // k)
    P = min(P, max(1, n // 256))
    return max(P, 1)


def _ib(batch, n, k, P):
    csize = (n + P - 1) // P
    return (csize + 255) // 256


class Model(nn.Module):
    """Top-k over the last dim of a 2D tensor (custom CUDA selection)."""

    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))
        P = _choose_P(batch, n, k)
        self._P = P
        self._IB = _ib(batch, n, k, P)
        dev = torch.device("cuda:0")
        self._scratch_val = torch.empty(batch * P * k, device=dev)
        self._scratch_idx = torch.empty(batch * P * k, dtype=torch.int32, device=dev)
        self._counters = torch.zeros(batch, dtype=torch.int32, device=dev)
        self._out_val = torch.empty(batch, k, device=dev)
        self._out_idx = torch.empty(batch, k, dtype=torch.int64, device=dev)
        self._graph = None
        self._graph_ptr = None

    def _launch(self, x):
        _get_ext().topk_forward(
            x, self._out_val, self._out_idx,
            self._scratch_val, self._scratch_idx, self._counters,
            self._P, 0, self._IB, self.k,
        )

    def _capture(self, x):
        g = torch.cuda.CUDAGraph()
        s = torch.cuda.Stream()
        s.wait_stream(torch.cuda.current_stream())
        with torch.cuda.stream(s):
            for _ in range(3):
                self._launch(x)
        torch.cuda.current_stream().wait_stream(s)
        with torch.cuda.graph(g):
            self._launch(x)
        self._graph = g

    def forward(self, x):
        ptr = x.data_ptr()
        if self._graph is None or ptr != self._graph_ptr:
            self._capture(x)
            self._graph_ptr = ptr
        self._graph.replay()
        return self._out_val, self._out_idx

    def __call__(self, *args, **kwargs):
        # Bypass nn.Module's hook machinery: this is a leaf module with no
        # parameters, so forward directly. Cuts ~4us of per-call overhead.
        return self.forward(*args, **kwargs)


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]

20260801_225649_or-fable_deepseek_deepseek-v4-flash-0731_05_topk_bitonic