KernelBench hard · RTX PRO 6000
TopK Bitonic GLM-5.3 Flash
7.17%geomean peak fraction across shapes
manually audited: clean
Isolated regrade 0.0717 on RTX PRO 6000 (in-run 0.0628). One load_inline CUDA launch per call: order-preserving u64 keys, per-thread top-R filter, warp-shuffle bitonic, last-block candidate merge. No torch.topk/sort, no CUDA graph, no input-identity cache. Lint CLEAN. Numeric stress on. Transcript never left this run's archive.
harnessor-fableagent session3h 11mtotal wall3h 21mcheck3sbenchmark1soutput tokens—regimememory
Per-shape vs governing ceilingeach shape graded against whichever binds — fp32 compute or HBM bandwidth
1×131072×640.013 ms2.2%0.04 TB/s · 2% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
64×8192×80.010 ms11.3%0.20 TB/s · 11% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
32×16384×320.012 ms9.9%0.18 TB/s · 10% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
16×12000×160.010 ms4.3%0.08 TB/s · 4% of 1.8 TB/s HBM · also 0 TFLOPS (0% of compute)
128×4096×10.007 ms17.9%0.32 TB/s · 18% 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(2.2% · 11.3% · 9.9% · 4.3% · 17.9%) = 7.2%
Kernel source (redacted)
"""Fused single-launch top-k kernel for RTX PRO 6000 (SM120).
Design notes:
- ONE kernel launch per forward call. All shapes here are tiny (0.5-2 MB
reads), so per-call CPU submission latency dominates; the Python side is
a single pybind call and outputs are preallocated per Model.
- Values are packed into sortable u64 keys: (order-preserving float bits)
<< 32 | index. Everything downstream (filter, bitonic networks) is u64
integer work; indices ride along for free.
- Per thread: keep the top-R (R = pow2ceil(k), R <= 64) elements in R
registers via a threshold filter (replace current min on beat).
- Per block: bitonic top-R merge tree over shared memory (capped at R, so
cost per level is R*log(R) compares regardless of block size).
- Per row: each block writes its sorted top-R to a global candidate slab,
bumps an epoch counter; the last block to arrive merges all candidates
and writes the final top-k. Counters persist across calls; "last" is
detected modulo G, so no per-call memset is needed.
- k > 64 falls back to a simple iterative selection kernel (never hit by
the graded shapes).
"""
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
_CPP_SRC = r"""
#include <torch/extension.h>
int64_t make_state(int64_t batch, int64_t n, int64_t k, int64_t dev,
int64_t gopt, int64_t uopt);
std::tuple<torch::Tensor, torch::Tensor> outputs(int64_t handle);
void run(int64_t handle, const torch::Tensor& x);
"""
_CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cstdint>
#include <vector>
#define DEV_INLINE __device__ __forceinline__
// ---------------------------------------------------------------- key utils
DEV_INLINE uint64_t make_key(float v, uint32_t idx) {
// Order-preserving map float -> uint32: positives get 0x80000000 OR'd in,
// negatives get ALL bits flipped. NaN (positive-sign form) sorts above
// +inf, same as torch's NaN-is-largest convention.
uint32_t b = __float_as_uint(v);
uint32_t s = b >> 31;
uint32_t f = b ^ (0x80000000u ^ (0x7FFFFFFFu & (0u - s)));
return ((uint64_t)f << 32) | idx;
}
DEV_INLINE float key_val(uint64_t k) {
uint32_t f = (uint32_t)(k >> 32);
uint32_t b = (f & 0x80000000u) ? (f ^ 0x80000000u) : ~f;
return __uint_as_float(b);
}
// ------------------------------------------------------------ register set
template <int R>
DEV_INLINE void insert_key(uint64_t (&r)[R], uint64_t& tmin, uint64_t k) {
if (k <= tmin) return;
if (R == 1) { r[0] = k; tmin = k; return; }
// find smallest and second smallest; replace the min slot
uint64_t m1 = ~0ull, m2 = ~0ull;
int mi = 0;
#pragma unroll
for (int j = 0; j < R; j++) {
if (r[j] < m1) { m2 = m1; m1 = r[j]; mi = j; }
else if (r[j] < m2) { m2 = r[j]; }
}
r[mi] = k;
tmin = k < m2 ? k : m2;
}
// NOTE: the array is passed by reference so every access stays a compile-time
// index — a plain pointer would demote the whole set to local memory.
template <int R>
DEV_INLINE void sort_desc(uint64_t (&r)[R]) {
#pragma unroll
for (int kk = 2; kk <= R; kk <<= 1)
#pragma unroll
for (int j = kk >> 1; j > 0; j >>= 1)
#pragma unroll
for (int i = 0; i < R; i++) {
int l = i ^ j;
if (l > i) {
bool desc = ((i & kk) == 0);
bool swap = desc ? (r[i] < r[l]) : (r[i] > r[l]);
if (swap) { uint64_t t = r[i]; r[i] = r[l]; r[l] = t; }
}
}
}
// ------------------------------------------------------------ warp bitonic
//
// The shared-memory merge tree above costs log2(T) levels x log2(R) stages of
// __syncthreads executed by only a handful of warps; for R=64/T=128 that is
// ~50 barriers and ~9 us of pure stall (measured). These two helpers move the
// work into warp shuffles, which need no barriers at all.
// Full bitonic sort, descending, of the warp's 256 keys (8 per lane). Lane l
// ends holding global positions [8l, 8l+8).
DEV_INLINE void warp_sort8(uint64_t (&r)[8], int lane) {
#pragma unroll
for (int size = 2; size <= 256; size <<= 1) {
// direction flag: keep the max at my position iff my size-block is in
// the descending half. size >= 8 always here except the first two
// stages, where size>>3 == 0 and every pair is descending anyway.
const bool keepMax = ((lane & (size >> 3)) == 0);
#pragma unroll
for (int d = size >> 1; d > 0; d >>= 1) {
if (d >= 8) {
// partner element is 8*lp + j in another lane. Both partners
// share the same direction bit (it sits above the flipped
// bit), so unlike the serial formulation BOTH sides act here
// and must pick complementary halves: in a descending block
// the lower lane keeps the max, in an ascending one the min.
const int lp = lane ^ (d >> 3);
const bool lower = lane < lp;
const bool wantMax = keepMax == lower;
#pragma unroll
for (int j = 0; j < 8; j++) {
// NOTE: __shfl_xor_sync takes the lane MASK, not the
// source lane — passing lp would make every lane read
// lane (lane ^ lp) instead of its partner.
uint64_t o = __shfl_xor_sync(0xffffffffull, r[j], d >> 3);
if (wantMax == (o > r[j])) r[j] = o;
}
} else {
// partner is in my own registers. Direction is per-ELEMENT
// here: for stages with size < 8 the direction bit lives in
// the slot index, not the lane.
#pragma unroll
for (int j = 0; j < 8; j++) {
const int pj = j ^ d;
if (pj > j) {
const bool kmj = (((lane << 3) | j) & size) == 0;
if (kmj == (r[j] < r[pj])) {
uint64_t t = r[j]; r[j] = r[pj]; r[pj] = t;
}
}
}
}
}
}
}
// Merge two sorted-descending RW-key runs into the top-RW of their union.
// A occupies lanes [0, RW/8), B lanes [RW/8, 2*RW/8); the result lands back
// in lanes [0, RW/8). Classic fold-to-bitonic + bitonic merge, all shuffles.
template <int RW>
DEV_INLINE void warp_merge_topr(uint64_t (&a)[8], uint64_t (&b)[8], int lane) {
constexpr int L = RW >> 3;
// fold: C[i] = max(A[i], B[RW-1-i]) — a bitonic sequence. The register
// index MUST stay a compile-time function of j: a runtime-select index
// demotes b[] and the shuffle then reads the wrong slot (measured). Only
// the source-lane operand may be dynamic, so clamp that instead.
#pragma unroll
for (int j = 0; j < 8; j++) {
const int mraw = RW - 1 - ((lane << 3) + j); // negative for lane >= L
const int sj = (RW - 1 - j) & 7; // compile-time
int sl = L + (mraw >> 3);
sl = sl < 0 ? 0 : (sl > 31 ? 31 : sl);
const uint64_t bv = __shfl_sync(0xffffffffull, b[sj], sl);
if (lane < L) a[j] = a[j] > bv ? a[j] : bv;
}
// bitonic merge of the RW-element sequence, descending. Unlike a sort
// stage this is UNCONDITIONAL max-at-the-lower-index: the fold already
// guarantees a bitonic sequence, so no per-block direction bit is needed.
#pragma unroll
for (int d = RW >> 1; d > 0; d >>= 1) {
if (d >= 8) {
const int lp = lane ^ (d >> 3);
const bool lower = lane < lp;
#pragma unroll
for (int j = 0; j < 8; j++) {
// mask argument, not source lane — see note in warp_sort8
uint64_t o = __shfl_xor_sync(0xffffffffull, a[j], d >> 3);
if (lower == (o > a[j])) a[j] = o;
}
} else {
#pragma unroll
for (int j = 0; j < 8; j++) {
const int pj = j ^ d;
if (pj > j && a[j] < a[pj]) {
uint64_t t = a[j]; a[j] = a[pj]; a[pj] = t;
}
}
}
}
}
// ------------------------------------------------- shared-memory merge tree
//
// sk holds `ngroups` contiguous groups of R entries, each group sorted
// descending. Merge pairwise down to one group, keeping only the top R at
// every level. T must be a power of two, ngroups a power of two.
template <int R, int T>
DEV_INLINE void merge_tree(uint64_t* sk, int ngroups, int tid) {
int ng = ngroups;
while (ng > 1) {
__syncthreads();
int nmg = ng >> 1;
// stage 0: fold the reversal — out[i] = max(A[i], B[R-1-i]); result is
// a cyclically bitonic sequence of length R in group slot m.
for (int e = tid; e < nmg * R; e += T) {
int m = e / R;
int i = e % R;
uint64_t va = sk[(2 * m) * R + i];
uint64_t vb = sk[(2 * m + 1) * R + (R - 1 - i)];
sk[m * R + i] = va > vb ? va : vb;
}
__syncthreads();
// bitonic merge stages within each group
for (int d = R >> 1; d > 0; d >>= 1) {
if (d >= 1) {
int half = R >> 1;
int s = __ffs(d) - 1;
for (int e = tid; e < nmg * half; e += T) {
int m = e / half;
int i = e % half;
int p = ((i >> s) << (s + 1)) | (i & (d - 1));
uint64_t x = sk[m * R + p];
uint64_t y = sk[m * R + p + d];
if (x < y) { sk[m * R + p] = y; sk[m * R + p + d] = x; }
}
__syncthreads();
}
}
ng >>= 1;
}
__syncthreads();
}
// ------------------------------------------------------------------ kernels
template <int R, int RP, int T, int U>
__global__ void __launch_bounds__(T) topk_kernel(
const float* __restrict__ x,
uint64_t* __restrict__ cand,
uint64_t* __restrict__ ctr,
float* __restrict__ out_v,
int64_t* __restrict__ out_i,
int n, int k, int G, int lgG, int chunk) {
extern __shared__ uint64_t sk[]; // size T*R
const int sub = blockIdx.x & (G - 1);
const int row = blockIdx.x >> lgG;
// ---------------- phase 1: per-thread register filter ----------------
// RP is the register-resident width (kept small so the array stays in
// registers); the shared merge tree works on R-wide runs.
uint64_t regs[RP];
#pragma unroll
for (int j = 0; j < RP; j++) regs[j] = 0ull;
uint64_t tmin = 0ull;
const long row_start = (long)row * n;
const long cs = row_start + (long)sub * chunk; // absolute element offset
const long c0 = cs - row_start; // column of chunk start
long clen = chunk;
long rem = row_start + n - cs; // elements left in this row
if (rem < clen) clen = rem;
if (clen < 0) clen = 0;
// align to 16B for float4 loads
long pre = ((16 - ((uintptr_t)(x + cs) & 15)) & 15) >> 2;
if (pre > clen) pre = clen;
const float4* x4 = (const float4*)(x + cs + pre);
long nq = (clen - pre) >> 2;
long post0 = cs + pre + (nq << 2); // absolute
const long postc = c0 + pre + (nq << 2); // column
const int tid = threadIdx.x;
const int lane = tid & 31;
const int warp = tid >> 5;
const long strideQ = (long)T * U;
for (long q = tid; q < nq; q += strideQ) {
float4 vv[U];
long idx[U];
#pragma unroll
for (int u = 0; u < U; u++) {
long qq = q + (long)u * T;
if (qq < nq) {
vv[u] = x4[qq];
idx[u] = c0 + pre + (qq << 2);
}
}
#pragma unroll
for (int u = 0; u < U; u++) {
long qq = q + (long)u * T;
if (qq < nq) {
uint32_t ib = (uint32_t)idx[u];
insert_key<RP>(regs, tmin, make_key(vv[u].x, ib));
insert_key<RP>(regs, tmin, make_key(vv[u].y, ib + 1));
insert_key<RP>(regs, tmin, make_key(vv[u].z, ib + 2));
insert_key<RP>(regs, tmin, make_key(vv[u].w, ib + 3));
}
}
}
// scalar head/tail (at most 3 + 3 elements)
for (long i = tid; i < pre; i += T) {
float v = x[cs + i];
insert_key<RP>(regs, tmin, make_key(v, (uint32_t)(c0 + i)));
}
long npost = cs + clen - post0;
for (long i = tid; i < npost; i += T) {
float v = x[post0 + i];
insert_key<RP>(regs, tmin, make_key(v, (uint32_t)(postc + i)));
}
if (R >= 8) {
// warp-shuffle path: pad the per-thread set to 8 keys and let each
// warp bitonic-sort its 256 keys with shuffles (no barriers), then
// run the shared tree over only T/32 runs instead of T.
uint64_t w[8];
#pragma unroll
for (int j = 0; j < RP; j++) w[j] = regs[j];
#pragma unroll
for (int j = RP; j < 8; j++) w[j] = 0ull;
warp_sort8(w, lane);
if (lane < (R >> 3)) {
#pragma unroll
for (int j = 0; j < 8; j++) sk[warp * R + (lane << 3) + j] = w[j];
}
__syncthreads();
merge_tree<R, T>(sk, T / 32, tid);
} else {
// tiny R: the plain tree is already shallow-cheap here
sort_desc<RP>(regs);
__syncthreads();
#pragma unroll
for (int j = 0; j < RP; j++) sk[tid * R + j] = regs[j];
#pragma unroll
for (int j = RP; j < R; j++) sk[tid * R + j] = 0ull;
merge_tree<R, T>(sk, T, tid);
}
uint64_t* cand_row = cand + ((long)row << lgG) * R;
if (G == 1) {
if (tid < k) {
out_v[(long)row * k + tid] = key_val(sk[tid]);
out_i[(long)row * k + tid] = (int64_t)(uint32_t)sk[tid];
}
return;
}
// spill this block's sorted top-R to the candidate slab
if (tid < R) cand_row[sub * R + tid] = sk[tid];
__threadfence();
__shared__ int ticket_s;
if (tid == 0)
ticket_s = (int)atomicAdd(reinterpret_cast<unsigned long long*>(ctr + row), 1ull);
__syncthreads();
if ((ticket_s & (G - 1)) != G - 1) return;
// --------------- phase 2: last block merges all candidates ---------------
__threadfence();
for (int e = tid; e < G * R; e += T) sk[e] = __ldcg(cand_row + e);
__syncthreads();
if (R >= 8) {
// each warp fold-merges its slice of the G runs with shuffles, then a
// shallow tree finishes over T/32 runs
constexpr int NW = T / 32;
const int ng2 = G < NW ? G : NW;
const int rpw = (G + NW - 1) / NW;
const int r0 = warp * rpw;
if (r0 < G) {
const int r1 = (r0 + rpw < G) ? r0 + rpw : G;
const int L = R >> 3;
uint64_t a[8], b[8];
if (lane < L) {
#pragma unroll
for (int j = 0; j < 8; j++) a[j] = sk[r0 * R + (lane << 3) + j];
}
for (int r = r0 + 1; r < r1; r++) {
if (lane >= L && lane < 2 * L) {
#pragma unroll
for (int j = 0; j < 8; j++) b[j] = sk[r * R + ((lane - L) << 3) + j];
}
warp_merge_topr<R>(a, b, lane);
}
if (lane < L) {
#pragma unroll
for (int j = 0; j < 8; j++) sk[warp * R + (lane << 3) + j] = a[j];
}
}
__syncthreads();
merge_tree<R, T>(sk, ng2, tid);
} else {
merge_tree<R, T>(sk, G, tid);
}
if (tid < k) {
out_v[(long)row * k + tid] = key_val(sk[tid]);
out_i[(long)row * k + tid] = (int64_t)(uint32_t)sk[tid];
}
}
// ------------------------------------------------------- fallback for k > 64
//
// One block per row; iteratively extract the max among not-yet-selected
// elements. Slow but correct; never used by the graded shapes.
__global__ void __launch_bounds__(256) topk_slow_kernel(
const float* __restrict__ x,
uint8_t* __restrict__ taken, // batch x n bitmap
float* __restrict__ out_v,
int64_t* __restrict__ out_i,
int n, int k) {
const int row = blockIdx.x;
const int tid = threadIdx.x;
extern __shared__ uint8_t smem[];
uint8_t* vis = smem; // n bytes (n <= 96K enforced)
uint64_t* red = (uint64_t*)(smem + ((n + 7) & ~7)); // 256 keys, 8B aligned
for (int i = tid; i < n; i += 256) vis[i] = 0;
__syncthreads();
for (int j = 0; j < k; j++) {
uint64_t best = 0;
const float* xr = x + (long)row * n;
for (int i = tid; i < n; i += 256) {
if (!vis[i]) {
uint64_t key = make_key(xr[i], (uint32_t)i);
if (key > best) best = key;
}
}
red[tid] = best;
__syncthreads();
for (int s = 128; s > 0; s >>= 1) {
if (tid < s && red[tid + s] > red[tid]) red[tid] = red[tid + s];
__syncthreads();
}
uint64_t w = red[0];
uint32_t wi = (uint32_t)(w & 0xFFFFFFFFu);
if (tid == 0) {
out_v[(long)row * k + j] = key_val(w);
out_i[(long)row * k + j] = (int64_t)wi;
vis[wi] = 1;
}
__syncthreads();
}
}
// ------------------------------------------------------------------ state
struct Slot {
torch::Tensor v, i, ctr, cand, scratch;
int batch = 0, n = 0, k = 0, R = 0, G = 1, lgG = 0, chunk = 0, T = 0, smem = 0, u = 4;
};
static std::vector<Slot> g_slots;
static inline int pow2ceil(int v) {
int p = 1;
while (p < v) p <<= 1;
return p;
}
template <int R, int RP, int T, int U>
static void launch_cfg(const Slot& S, const float* xp, cudaStream_t stream) {
// Track the largest opt-in already granted so a later, bigger smem request
// of the same instantiation still gets its attribute set.
static int attr_set = 0;
const int smem = S.smem;
if (smem > attr_set && smem > 48 * 1024) {
cudaFuncSetAttribute(topk_kernel<R, RP, T, U>,
cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
attr_set = smem;
}
dim3 grid(S.batch * S.G);
topk_kernel<R, RP, T, U><<<grid, T, smem, stream>>>(
xp,
(uint64_t*)S.cand.data_ptr(),
(uint64_t*)S.ctr.data_ptr(),
S.v.data_ptr<float>(),
S.i.data_ptr<int64_t>(),
S.n, S.k, S.G, S.lgG, S.chunk);
}
int64_t make_state(int64_t batch, int64_t n, int64_t k, int64_t dev,
int64_t gopt, int64_t uopt) {
Slot S;
S.batch = (int)batch;
S.n = (int)n;
S.k = (int)k;
TORCH_CHECK(S.k >= 1 && S.k <= S.n, "k must be in [1, n]");
auto opts_f = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA, dev);
auto opts_i = torch::TensorOptions().dtype(torch::kInt64).device(torch::kCUDA, dev);
auto opts_u = torch::TensorOptions().dtype(torch::kUInt64).device(torch::kCUDA, dev);
if (S.k <= 64) {
S.R = pow2ceil(S.k);
S.T = 256; // shared use shrank to (T/32)*R keys, so R=64 fits at T=256
// One wave of blocks (~128 CTAs on 188 SMs), tuned by sweep: small
// batches stop at G=32 (more blocks only lengthens the candidate
// merge), row-heavy batches take G = 128/batch rounded DOWN to a
// power of two so grid ~= 128.
int want_g = 128 / S.batch;
if (want_g < 1) want_g = 1;
if (S.batch < 16 && want_g > 32) want_g = 32;
int g = 1;
while (g * 2 <= want_g && g * 2 <= S.T) g <<= 1;
S.G = g;
if (gopt > 0) {
// tuning override: largest power of two <= min(gopt, T)
int cap = (int)gopt < S.T ? (int)gopt : S.T;
int gg = 1;
while (gg * 2 <= cap) gg <<= 1;
S.G = gg;
}
S.u = (int)(uopt > 0 ? uopt : (S.R == 1 ? 8 : 4));
S.lgG = __builtin_ctz(S.G);
S.chunk = (S.n + S.G - 1) / S.G;
{
// phase 1 needs the surviving runs (T/32 for warp path, T for the
// tiny-R fallback); phase 2 needs all G candidate runs
const int runs1 = (S.R >= 8) ? (S.T / 32) : S.T;
const int runs = runs1 > S.G ? runs1 : S.G;
S.smem = runs * S.R * 8;
TORCH_CHECK(S.smem <= 101376, "shared memory budget exceeded");
}
S.v = torch::empty({batch, k}, opts_f);
S.i = torch::empty({batch, k}, opts_i);
S.cand = torch::empty({batch * (long)S.G * S.R}, opts_u);
S.ctr = torch::zeros({batch}, opts_u);
} else {
TORCH_CHECK((long)n + 4096 <= 101376, "slow path: n too large for shared bitmap");
S.v = torch::empty({batch, k}, opts_f);
S.i = torch::empty({batch, k}, opts_i);
S.scratch = torch::zeros({batch * n}, opts_u); // used as u8 bitmap
}
g_slots.push_back(std::move(S));
return (int64_t)g_slots.size() - 1;
}
std::tuple<torch::Tensor, torch::Tensor> outputs(int64_t handle) {
Slot& S = g_slots.at(handle);
return std::make_tuple(S.v, S.i);
}
void run(int64_t handle, const torch::Tensor& x) {
Slot& S = g_slots.at(handle);
TORCH_CHECK(x.is_cuda() && x.scalar_type() == torch::kFloat32, "need CUDA fp32 input");
torch::Tensor xc = x.is_contiguous() ? x : x.contiguous();
cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
if (S.k <= 64) {
const float* xp = xc.data_ptr<float>();
#define DISPATCH_RU(RV, RPV, TV) \
switch (S.u) { \
case 1: launch_cfg<RV, RPV, TV, 1>(S, xp, stream); break; \
case 2: launch_cfg<RV, RPV, TV, 2>(S, xp, stream); break; \
case 4: launch_cfg<RV, RPV, TV, 4>(S, xp, stream); break; \
case 8: launch_cfg<RV, RPV, TV, 8>(S, xp, stream); break; \
default: TORCH_CHECK(false, "bad U"); \
}
switch (S.R) {
case 1: DISPATCH_RU(1, 1, 256); break;
case 2: DISPATCH_RU(2, 2, 256); break;
case 4: DISPATCH_RU(4, 4, 256); break;
case 8: DISPATCH_RU(8, 8, 256); break;
case 16: DISPATCH_RU(16, 8, 256); break;
case 32: DISPATCH_RU(32, 8, 256); break;
case 64: DISPATCH_RU(64, 8, 256); break;
default: TORCH_CHECK(false, "bad R");
}
#undef DISPATCH_RU
} else {
long smem_slow = (((long)S.n + 7) & ~7) + 256 * 8;
topk_slow_kernel<<<S.batch, 256, smem_slow, stream>>>(
xc.data_ptr<float>(),
(uint8_t*)S.scratch.data_ptr(),
S.v.data_ptr<float>(), S.i.data_ptr<int64_t>(),
S.n, S.k);
}
}
"""
_ext = load_inline(
name="topk_fused_v2",
cpp_sources=[_CPP_SRC],
cuda_sources=[_CUDA_SRC],
functions=["make_state", "outputs", "run"],
extra_cuda_cflags=["-O3", "-ccbin", "/usr/bin/clang++-21"],
verbose=False,
)
_MAKE = _ext.make_state
_OUT = _ext.outputs
_RUN = _ext.run
class Model(nn.Module):
"""Top-k over the last dim; same contract as the reference model."""
def __init__(self, batch: int, n: int, k: int):
super().__init__()
self.batch, self.n, self.k = int(batch), int(n), int(k)
self._gopt = -1 # optional tuning overrides (blocks per row, unroll)
self._uopt = -1
self.register_buffer("_dummy", torch.zeros(1))
self._h = -1
self._out = None
def _apply(self, fn, recurse=True):
# device moves invalidate the lazily-built state
self._h = -1
self._out = None
return super()._apply(fn, recurse)
def _lazy(self, x):
h = _MAKE(self.batch, self.n, self.k, x.device.index, self._gopt, self._uopt)
self._h = h
self._out = _OUT(h)
return h
def forward(self, x: torch.Tensor):
h = self._h
if h < 0:
h = self._lazy(x)
_RUN(h, x)
return self._out
# Bypass nn.Module.__call__ machinery (hook dict checks) — the harness
# calls model(*inputs), and every microsecond of CPU path counts.
def __call__(self, x: torch.Tensor):
h = self._h
if h < 0:
h = self._lazy(x)
_RUN(h, x)
return self._out
20260822_061053_or-fable_stealth_ox-alpha_05_topk_bitonic