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 tokensregimememory

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