KernelBench hard · B200
TopK Bitonic Claude Fable 5
0.78%geomean peak fraction across shapes
manually audited: clean
harnessor-fableagent session1h 30mtotal wall1h 30mcheck2sbenchmark2soutput tokens—gpu-lock wait12mgpu-lock held30mregimememory
Per-shape vs governing ceilingeach shape graded against whichever binds — fp32 compute or HBM bandwidth
1×131072×640.025 ms0.3%0.02 TB/s · 0% of 8.0 TB/s HBM · also 0 TFLOPS (0% of compute)
64×8192×80.021 ms1.3%0.10 TB/s · 1% of 8.0 TB/s HBM · also 0 TFLOPS (0% of compute)
32×16384×320.024 ms1.1%0.09 TB/s · 1% of 8.0 TB/s HBM · also 0 TFLOPS (0% of compute)
16×12000×160.020 ms0.5%0.04 TB/s · 0% of 8.0 TB/s HBM · also 0 TFLOPS (0% of compute)
128×4096×10.016 ms1.7%0.13 TB/s · 2% of 8.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.3% · 1.3% · 1.1% · 0.5% · 1.7%) = 0.8%
Kernel source (redacted)
"""Custom CUDA top-k for B200 (SM100).
Single-launch warp-bitonic selection:
* each warp keeps a sorted-descending top-K candidate list in registers
(K = 32 for k <= 32, K = 64 for k <= 64, padded with -inf),
* the row is streamed in 32-wide chunks via software-pipelined float4 loads;
a warp ballot against the running k-th value filters each chunk; only
chunks holding a candidate pay for the bitonic sort32 + top-K merge,
* all warps of a block share a monotone threshold word in shared memory
(encoded-float atomicMax), so the whole block converges to one k-th-value
threshold quickly and nearly every later chunk is skipped with one ballot,
* warp lists combine through shared memory via a log2(NW) tree of sorted
list-vs-list bitonic merges (no re-sort),
* for large-n / small-batch shapes several blocks share a row: each block
publishes its top-K list to global scratch (release ordering) and the last
block to arrive (acq_rel atomic counter) merges the other lists starting
from its own already-warm list, so almost all candidates ballot-skip.
Everything happens in ONE kernel launch per forward call: at these shapes
(0.5-2 MB input) launch latency, not bandwidth, is the whole game.
"""
import os
os.environ.setdefault("CUDA_HOME", "/usr/local/cuda-12.8")
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
_CPP_DECL = "void topk_run(at::Tensor x, int64_t k, int64_t g, int64_t nw, at::Tensor scv, at::Tensor sci, at::Tensor cnt, at::Tensor ov, at::Tensor oi);"
_CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda_runtime.h>
#include <cuda/atomic>
#include <vector>
#define FULL 0xffffffffu
// Monotone order-preserving float <-> uint mapping (no NaNs in this problem).
__device__ __forceinline__ unsigned enc_f(float f) {
int i = __float_as_int(f);
return (unsigned)(i >= 0 ? (i ^ 0x80000000) : ~i);
}
__device__ __forceinline__ float dec_f(unsigned u) {
int i = (u & 0x80000000u) ? (int)(u ^ 0x80000000u) : ~(int)u;
return __int_as_float(i);
}
template <int KPL>
struct WList { // top-(32*KPL) list, element e lives at reg e/32, lane e%32
float v[KPL];
int i[KPL];
};
__device__ __forceinline__ void cswap(float& v, int& i, int d, bool keepMaxLow, int lane) {
float pv = __shfl_xor_sync(FULL, v, d);
int pi = __shfl_xor_sync(FULL, i, d);
bool lower = (lane & d) == 0;
bool wantMax = (lower == keepMaxLow);
bool take = wantMax ? (pv > v) : (pv < v);
if (take) { v = pv; i = pi; }
}
// Sort 32 values (one per lane) descending across the warp.
__device__ __forceinline__ void sort32_desc(float& v, int& i, int lane) {
#pragma unroll
for (int s = 2; s <= 32; s <<= 1) {
bool desc = ((lane & s) == 0);
#pragma unroll
for (int d = s >> 1; d >= 1; d >>= 1)
cswap(v, i, d, desc, lane);
}
}
// Merge a descending-sorted 32-chunk into the descending top-K list.
template <int KPL>
__device__ __forceinline__ void merge_chunk(WList<KPL>& L, float cv, int ci, int lane) {
float rv = __shfl_xor_sync(FULL, cv, 31); // chunk reversed -> ascending
int ri = __shfl_xor_sync(FULL, ci, 31);
if (KPL == 1) {
if (rv > L.v[0]) { L.v[0] = rv; L.i[0] = ri; } // elementwise max -> bitonic
#pragma unroll
for (int d = 16; d >= 1; d >>= 1) cswap(L.v[0], L.i[0], d, true, lane);
} else {
// treat chunk as [c_desc, -inf x32]; only upper half of L sees candidates
if (rv > L.v[1]) { L.v[1] = rv; L.i[1] = ri; }
if (L.v[1] > L.v[0]) { // distance-32 stage
float tv = L.v[0]; L.v[0] = L.v[1]; L.v[1] = tv;
int ti = L.i[0]; L.i[0] = L.i[1]; L.i[1] = ti;
}
#pragma unroll
for (int d = 16; d >= 1; d >>= 1) {
cswap(L.v[0], L.i[0], d, true, lane);
cswap(L.v[1], L.i[1], d, true, lane);
}
}
}
// Merge two descending-sorted K-lists (top-K of the union stays in L).
template <int KPL>
__device__ __forceinline__ void merge_lists(WList<KPL>& L, const WList<KPL>& B, int lane) {
if (KPL == 1) {
merge_chunk(L, B.v[0], B.i[0], lane); // a sorted list is a sorted chunk
} else {
float rv0 = __shfl_xor_sync(FULL, B.v[1], 31); // B[63-e], e in [0,32)
int ri0 = __shfl_xor_sync(FULL, B.i[1], 31);
float rv1 = __shfl_xor_sync(FULL, B.v[0], 31); // B[63-e], e in [32,64)
int ri1 = __shfl_xor_sync(FULL, B.i[0], 31);
if (rv0 > L.v[0]) { L.v[0] = rv0; L.i[0] = ri0; }
if (rv1 > L.v[1]) { L.v[1] = rv1; L.i[1] = ri1; }
if (L.v[1] > L.v[0]) {
float tv = L.v[0]; L.v[0] = L.v[1]; L.v[1] = tv;
int ti = L.i[0]; L.i[0] = L.i[1]; L.i[1] = ti;
}
#pragma unroll
for (int d = 16; d >= 1; d >>= 1) {
cswap(L.v[0], L.i[0], d, true, lane);
cswap(L.v[1], L.i[1], d, true, lane);
}
}
}
template <int KPL>
__device__ __forceinline__ float kth_val(const WList<KPL>& L, int k) {
if (KPL == 1) return __shfl_sync(FULL, L.v[0], k - 1);
return (k <= 32) ? __shfl_sync(FULL, L.v[0], k - 1)
: __shfl_sync(FULL, L.v[1], k - 33);
}
// Streaming accumulator with a block-shared monotone threshold.
template <int KPL>
struct Acc {
WList<KPL> L;
float thresh; // this warp's k-th largest so far
int lane;
int k;
unsigned* sthr; // block-wide encoded max threshold (shared memory)
__device__ __forceinline__ void init(int lane_, int k_, unsigned* sthr_) {
lane = lane_; k = k_; sthr = sthr_;
#pragma unroll
for (int r = 0; r < KPL; ++r) { L.v[r] = -INFINITY; L.i[r] = 0; }
thresh = -INFINITY;
}
__device__ __forceinline__ float eff_thresh() const {
unsigned u = *(volatile unsigned*)sthr;
return fmaxf(thresh, dec_f(u));
}
__device__ __forceinline__ void bump_shared() {
if (lane == 0) atomicMax(sthr, enc_f(thresh));
}
__device__ __forceinline__ void feed(float cv, int ci, float t) {
if (!__any_sync(FULL, cv > t)) return;
sort32_desc(cv, ci, lane);
merge_chunk(L, cv, ci, lane);
thresh = kth_val(L, k);
bump_shared();
}
};
// Free threshold warm-up pass: every lane keeps the top-2 of its strided
// elements (pure compare/max, no shuffles, also pre-warms L2). The k-th
// largest of any SUBSET of elements is a valid global threshold (>= k
// elements are >= it: the subset's own top k). The union of per-lane top-2s
// almost surely contains the true top-k (a lane would need >= 3 of the top-k
// to break tightness), so t0 lands within a hair of the exact k-th value and
// the main pass ballot-skips nearly everything. Elements EQUAL to t0 are not
// held in any list yet, so publish enc(t0)-1 (one step down in the
// order-isomorphic encoded space) to keep ties streaming in.
template <int KPL, int NW>
__device__ void warm_thresh(const float* __restrict__ vals, int count,
Acc<KPL>& A, float* sv, int warp) {
const int lane = A.lane;
const int nvec = count >> 2;
const float4* v4 = reinterpret_cast<const float4*>(vals);
float m1 = -INFINITY, m2 = -INFINITY; // lane top-2
for (int p = warp * 32 + lane; p < nvec; p += NW * 32) {
float4 q = v4[p];
float a1 = fmaxf(q.x, q.y), a2 = fminf(q.x, q.y);
float b1 = fmaxf(q.z, q.w), b2 = fminf(q.z, q.w);
float hi = fmaxf(a1, b1); // max of 4
float s2 = fmaxf(fminf(a1, b1), fmaxf(a2, b2)); // 2nd of 4
if (hi > m1) { m2 = fmaxf(m1, s2); m1 = hi; }
else { m2 = fmaxf(m2, hi); }
}
if (warp == 0) {
int pos = (nvec << 2) + lane;
if (pos < count) {
float v = vals[pos];
if (v > m1) { m2 = m1; m1 = v; }
else { m2 = fmaxf(m2, v); }
}
}
int d1 = 0, d2 = 0;
sort32_desc(m1, d1, lane);
sort32_desc(m2, d2, lane);
if (KPL == 1) { // k <= 32: top-32 of the 64 subset values
WList<1> P;
P.v[0] = m1; P.i[0] = 0;
merge_chunk(P, m2, d2, lane);
float t0 = kth_val(P, A.k);
if (lane == 0) atomicMax(A.sthr, enc_f(t0) - 1);
} else { // k <= 64: pool two warps' 64-value subsets
WList<2> P;
P.v[0] = m1; P.i[0] = 0;
P.v[1] = -INFINITY; P.i[1] = 0;
merge_chunk(P, m2, d2, lane); // sorted top-64 of this warp's subset
#pragma unroll
for (int r = 0; r < 2; ++r) sv[warp * 64 + r * 32 + lane] = P.v[r];
__syncthreads();
if (warp < NW / 2) {
WList<2> P2, Q;
#pragma unroll
for (int r = 0; r < 2; ++r) {
P2.v[r] = sv[(2 * warp) * 64 + r * 32 + lane]; P2.i[r] = 0;
Q.v[r] = sv[(2 * warp + 1) * 64 + r * 32 + lane]; Q.i[r] = 0;
}
merge_lists(P2, Q, lane); // top-64 of the pooled 128 subset values
float t0 = kth_val(P2, A.k);
if (lane == 0) atomicMax(A.sthr, enc_f(t0) - 1);
}
__syncthreads();
}
}
// Generic scalar streaming (candidate arrays / unaligned fallback).
// idxs == nullptr -> index is idx_base + position.
template <int KPL>
__device__ void stream_topk(const float* vals, const int* idxs, int count, int idx_base,
int warp, int nwarps, Acc<KPL>& A) {
const int lane = A.lane;
for (int base = warp * 32; base < count; base += nwarps * 32) {
int pos = base + lane;
bool in = pos < count;
float cv = in ? vals[pos] : -INFINITY;
int ci = idxs ? (in ? idxs[pos] : 0) : (idx_base + (in ? pos : 0));
A.feed(cv, ci, A.eff_thresh());
}
}
// Vectorized, software-pipelined streaming: each lane loads a float4
// (128 elems / warp / iter); the next iteration's load is issued before
// the current chunk is examined so DRAM latency overlaps the filtering.
template <int KPL>
__device__ void stream_vec(const float* __restrict__ vals, int count, int idx_base,
int warp, int nwarps, Acc<KPL>& A) {
const int lane = A.lane;
const int nvec = count >> 2;
const float4* v4 = reinterpret_cast<const float4*>(vals);
const int step = nwarps * 32;
int p = warp * 32 + lane;
bool in = p < nvec;
float4 q = make_float4(-INFINITY, -INFINITY, -INFINITY, -INFINITY);
if (in) q = v4[p];
for (int base = warp * 32; base < nvec; base += step) {
// prefetch next chunk before touching this one
int pn = p + step;
bool inn = pn < nvec;
float4 qn = make_float4(-INFINITY, -INFINITY, -INFINITY, -INFINITY);
if (inn) qn = v4[pn];
float t = A.eff_thresh();
float m = fmaxf(fmaxf(q.x, q.y), fmaxf(q.z, q.w));
if (__any_sync(FULL, m > t)) {
float qa[4] = {q.x, q.y, q.z, q.w};
int ibase = in ? (idx_base + 4 * p) : idx_base;
int ia[4] = {ibase, ibase + 1, ibase + 2, ibase + 3};
if (!in) { ia[1] = ia[2] = ia[3] = idx_base; }
#pragma unroll
for (int j = 0; j < 4; ++j) {
A.feed(qa[j], ia[j], t);
t = A.eff_thresh();
}
}
q = qn; p = pn; in = inn;
}
int tail = nvec << 2;
if (tail < count && warp == 0) { // < 4 leftover elements
int pos = tail + lane;
bool tin = pos < count;
float cv = tin ? vals[pos] : -INFINITY;
int ci = tin ? (idx_base + pos) : idx_base;
A.feed(cv, ci, A.eff_thresh());
}
}
// Tree-combine the NW warp lists through shared memory. On return warp 0's
// A.L (and sv/si[0..K)) hold the block's top-K list.
template <int KPL, int NW>
__device__ void combine_block(Acc<KPL>& A, float* sv, int* si, int lane, int warp) {
constexpr int K = 32 * KPL;
#pragma unroll
for (int r = 0; r < KPL; ++r) {
sv[warp * K + r * 32 + lane] = A.L.v[r];
si[warp * K + r * 32 + lane] = A.L.i[r];
}
__syncthreads();
#pragma unroll
for (int stride = NW / 2; stride >= 1; stride >>= 1) {
if (warp < stride) {
WList<KPL> B;
#pragma unroll
for (int r = 0; r < KPL; ++r) {
B.v[r] = sv[(warp + stride) * K + r * 32 + lane];
B.i[r] = si[(warp + stride) * K + r * 32 + lane];
}
merge_lists(A.L, B, lane);
#pragma unroll
for (int r = 0; r < KPL; ++r) {
sv[warp * K + r * 32 + lane] = A.L.v[r];
si[warp * K + r * 32 + lane] = A.L.i[r];
}
}
__syncthreads();
}
}
template <int KPL>
__device__ __forceinline__ void write_out(const WList<KPL>& L, float* out_v, int64_t* out_i,
int row, int k, int lane) {
#pragma unroll
for (int r = 0; r < KPL; ++r) {
int e = r * 32 + lane;
if (e < k) {
out_v[(size_t)row * k + e] = L.v[r];
out_i[(size_t)row * k + e] = (int64_t)L.i[r];
}
}
}
template <int KPL, int NW>
__global__ void __launch_bounds__(NW * 32)
topk_kernel(const float* __restrict__ x, int n, int k,
float* __restrict__ out_v, int64_t* __restrict__ out_i,
float* __restrict__ sc_v, int* __restrict__ sc_i,
int* __restrict__ counters) {
constexpr int K = 32 * KPL;
__shared__ float sv[NW * K];
__shared__ int si[NW * K];
__shared__ unsigned s_thr;
__shared__ bool s_last;
const int lane = threadIdx.x & 31;
const int warp = threadIdx.x >> 5;
const int row = blockIdx.y;
const int g = gridDim.x;
const int bid = blockIdx.x;
const float* xrow = x + (size_t)row * n;
if (threadIdx.x == 0) s_thr = enc_f(-INFINITY);
__syncthreads();
int seg = ((n + g - 1) / g + 3) & ~3; // per-block slice, multiple of 4
int s0 = bid * seg;
int s1 = min(n, s0 + seg);
int cnt = max(0, s1 - s0);
Acc<KPL> A;
A.init(lane, k, &s_thr);
if (cnt > 0) {
const float* seg_ptr = xrow + s0;
if ((reinterpret_cast<uintptr_t>(seg_ptr) & 15) == 0) {
warm_thresh<KPL, NW>(seg_ptr, cnt, A, sv, warp);
stream_vec<KPL>(seg_ptr, cnt, s0, warp, NW, A);
} else {
stream_topk<KPL>(seg_ptr, nullptr, cnt, s0, warp, NW, A);
}
}
combine_block<KPL, NW>(A, sv, si, lane, warp);
if (g == 1) {
if (warp == 0) write_out(A.L, out_v, out_i, row, k, lane);
return;
}
// multi-block row: publish this block's list, last block merges
float* scv = sc_v + (size_t)row * g * K;
int* sci = sc_i + (size_t)row * g * K;
if (warp == 0) {
#pragma unroll
for (int r = 0; r < KPL; ++r) {
scv[bid * K + r * 32 + lane] = A.L.v[r];
sci[bid * K + r * 32 + lane] = A.L.i[r];
}
}
__syncthreads(); // scratch writes happen-before the release below
if (threadIdx.x == 0) {
cuda::atomic_ref<int, cuda::thread_scope_device> c(counters[row]);
int prev = c.fetch_add(1, cuda::memory_order_acq_rel);
s_last = (prev == g - 1);
}
__syncthreads();
if (!s_last) return;
// The scratch lists are already sorted, so no re-sorting: each warp
// directly merge_lists() its share of the other blocks' lists (skipping
// any list whose max cannot improve its running k-th). Warp 0 keeps this
// block's own list (sv/si[0..K)); exactly one warp owns each element.
if (warp == 0) {
#pragma unroll
for (int r = 0; r < KPL; ++r) {
A.L.v[r] = sv[r * 32 + lane];
A.L.i[r] = si[r * 32 + lane];
}
A.thresh = kth_val(A.L, k);
} else {
A.init(lane, k, &s_thr);
}
__syncthreads(); // done reading sv/si; combine_block below reuses them
for (int b = warp; b < g; b += NW) {
if (b == bid) continue;
WList<KPL> B;
#pragma unroll
for (int r = 0; r < KPL; ++r) {
B.v[r] = scv[b * K + r * 32 + lane];
B.i[r] = sci[b * K + r * 32 + lane];
}
float bmax = __shfl_sync(FULL, B.v[0], 0);
if (bmax > A.thresh) {
merge_lists(A.L, B, lane);
A.thresh = kth_val(A.L, k);
}
}
combine_block<KPL, NW>(A, sv, si, lane, warp);
if (warp == 0) {
write_out(A.L, out_v, out_i, row, k, lane);
if (lane == 0) counters[row] = 0; // self-reset for the next launch
}
}
void topk_run(at::Tensor x, int64_t k, int64_t g, int64_t nw,
at::Tensor scv, at::Tensor sci, at::Tensor cnt,
at::Tensor ov, at::Tensor oi) {
TORCH_CHECK(x.is_cuda() && x.scalar_type() == at::kFloat, "x must be CUDA fp32");
TORCH_CHECK(x.dim() == 2 && x.is_contiguous(), "x must be contiguous 2D");
const int batch = (int)x.size(0);
const int n = (int)x.size(1);
TORCH_CHECK(k >= 1 && k <= 64 && k <= n, "k out of supported range");
dim3 grid((unsigned)g, (unsigned)batch);
auto stream = at::cuda::getCurrentCUDAStream();
const float* xp = x.data_ptr<float>();
float* ovp = ov.data_ptr<float>();
int64_t* oip = oi.data_ptr<int64_t>();
float* scvp = scv.data_ptr<float>();
int* scip = sci.data_ptr<int>();
int* cntp = cnt.data_ptr<int>();
if (k <= 32) {
if (nw == 16) topk_kernel<1, 16><<<grid, 512, 0, stream>>>(xp, n, (int)k, ovp, oip, scvp, scip, cntp);
else topk_kernel<1, 8><<<grid, 256, 0, stream>>>(xp, n, (int)k, ovp, oip, scvp, scip, cntp);
} else {
if (nw == 16) topk_kernel<2, 16><<<grid, 512, 0, stream>>>(xp, n, (int)k, ovp, oip, scvp, scip, cntp);
else topk_kernel<2, 8><<<grid, 256, 0, stream>>>(xp, n, (int)k, ovp, oip, scvp, scip, cntp);
}
}
"""
_mod = load_inline(
name="topk_warpsel_b200_v3",
cpp_sources=_CPP_DECL,
cuda_sources=_CUDA_SRC,
functions=["topk_run"],
verbose=False,
extra_cuda_cflags=["-O3", "-arch=sm_100"],
)
def _pick_config(batch: int, n: int, k: int) -> tuple[int, int]:
"""Blocks-per-row and warps-per-block, tuned on B200 for the deck shapes."""
if os.environ.get("TOPK_G"):
return int(os.environ["TOPK_G"]), int(os.environ.get("TOPK_NW", "16"))
if batch >= 48:
# enough rows to fill the GPU with one block each
return 1, (8 if k == 1 else 16)
g = max(1, min(64, 128 // max(batch, 1), (n + 2047) // 2048))
return g, 16
class Model(nn.Module):
"""Top-k over the last dim of a 2D fp32 tensor (values desc + int64 indices).
Output tensors are preallocated once and rewritten by the kernel on every
forward call (recomputed from the live input each time); this removes two
allocator round-trips from the latency-critical path.
"""
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))
g, nw = _pick_config(batch, n, k)
self.g = g
self.nw = nw
K = 32 if k <= 32 else 64
sc = batch * g * K if g > 1 else 1
self.register_buffer("_scv", torch.empty(sc, dtype=torch.float32), persistent=False)
self.register_buffer("_sci", torch.empty(sc, dtype=torch.int32), persistent=False)
self.register_buffer("_cnt", torch.zeros(batch, dtype=torch.int32), persistent=False)
self.register_buffer("_ov", torch.empty(batch, k, dtype=torch.float32), persistent=False)
self.register_buffer("_oi", torch.empty(batch, k, dtype=torch.int64), persistent=False)
self._graph = None
self._gkey = None
self._no_graph = os.environ.get("TOPK_NO_GRAPH") == "1"
def _launch(self, x: torch.Tensor):
_mod.topk_run(x, self.k, self.g, self.nw, self._scv, self._sci, self._cnt,
self._ov, self._oi)
def forward(self, x: torch.Tensor):
"""Run the top-k kernel on the CURRENT contents of x.
Launch latency on this box is ~14.5us even for an empty kernel while a
CUDA-graph replay of the same kernel is ~9.3us, so the single kernel
launch is wrapped in a graph keyed on (input ptr, buffer ptrs). This is
NOT result caching: the graph re-reads whatever data sits at x's
address at replay time and recomputes the full selection every call —
a new tensor at a new address (or moved module buffers) triggers a
recapture, and a reused address is recomputed on its live contents.
"""
if x.shape[0] != self.batch or x.shape[1] != self.n:
raise ValueError("input shape does not match Model(batch, n, k)")
if self._no_graph or not x.is_cuda:
self._launch(x)
return self._ov, self._oi
key = (x.data_ptr(), self._scv.data_ptr(), self._ov.data_ptr(),
self._oi.data_ptr(), self._cnt.data_ptr())
if key != self._gkey:
try:
torch.cuda.synchronize()
self._launch(x) # warm launch; also serves this call
torch.cuda.synchronize()
gr = torch.cuda.CUDAGraph()
with torch.cuda.graph(gr): # record (does not execute)
self._launch(x)
self._graph, self._gkey = gr, key
except Exception:
self._graph, self._gkey = None, None
self._no_graph = True
self._launch(x)
return self._ov, self._oi
self._graph.replay()
return self._ov, self._oi
# Bypass nn.Module.__call__'s hook machinery: this module has no hooks and
# the dispatch overhead is measurable against a ~10us kernel.
__call__ = forward
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]
20260719_052400_or-fable_anthropic_claude-fable-5_05_topk_bitonic