KernelBench cuda · RTX PRO 6000
MegaQwen Decode Claude Opus 5
manually audited: clean
Single persistent megakernel (188 blocks x 256 threads) running all 4 layers x all decode steps in one launch: hand-rolled release/acquire grid barrier (red.release.gpu.global.add.u32), constant-shift softmax bounded by q/k norm gains, .cs evict-first KV loads protecting the L2-resident 126MB weight pack, cross-barrier weight prefetch. Packed-weight context model._ctx invalidated by overriding load_state_dict/_apply — exactly the path check.py exercises. EMPIRICAL: reference-initialized weights match reference run() at 0.00195 max-err; after loading a different state dict, output changed and matched the fresh new-weight reference at 0.00195 (~12x tighter than the stale output would score; delta small because hidden state is dominated by the identical seeded per-step noise stream). Cache invalidates correctly. template_mutated=false, numeric stress on, zero cross-run access. Sequential isolated re-grade on anvil GPU0 2026-07-26 (contended 0.0657).
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
No per-shape benchmark data archived for this run.
Kernel source (redacted)
"""Fast multi-layer decode for Qwen3-0.6B geometry — CUDA megakernel (SM120).
Design
------
Batch-1 decode is pure GEMV: ~1 MAC per 2 bytes of weight, so every stage is
memory bound. On this GPU (188 SMs, 128 MB L2, ~1.64 TB/s DRAM read, ~8.7 TB/s
L2 read) the 4-layer weight set is 125.8 MB — it *fits in L2*. So the whole
decode (all layers, all steps) runs in ONE persistent kernel launch:
* 188 blocks x 256 threads (one block per SM), resident for the whole call.
* 5 stages per layer (qkv | attn | o+res | gate/up | down+res) separated by a
hand-rolled release/acquire grid barrier (~0.6 us) — no cooperative launch,
no cudaGraph, no per-stage launch overhead.
* Attention is flash-decoding style: blocks split (kv_head x seq); the two
sibling q heads that share a kv head live in the same block so K/V is read
once for both. K and V for a position are interleaved into one 512 B
record, so each block walks the cache as a single sequential stream.
* Softmax skips the running max: after RMSNorm+RoPE, |q| and |k| are bounded
(<= sqrt(128)*max|q_norm|*max|k_norm|), so a *constant* shift keeps exp() in
range. That makes the KV pass single-shot streaming with no rescaling.
* Per-head partials are accumulated with fp32 L2 atomics (only the ~24 blocks
of one kv head touch a given address) and the softmax divide is deferred to
the consumer, so no block ever waits on a serial cross-block gather.
* GEMV weights are pre-permuted on the host so a lane's 16 B weight load pairs
with a conflict-free stride-32 shared-memory read of the activation.
Numerics follow reference.py exactly: fp32 math, bf16 weights, bf16 hidden
between layers, bf16 KV cache round trip, fp32 residual inside a layer.
"""
from __future__ import annotations
import math
import os
import torch
import torch.nn as nn
HIDDEN = 1024
INTERMEDIATE = 3072
NUM_Q = 16
NUM_KV = 8
HEAD_DIM = 128
NUM_LAYERS = 4
EPS = 1e-6
# ----------------------------------------------------------------------------
# CUDA
# ----------------------------------------------------------------------------
_CPP = r"""
#include <torch/extension.h>
#include <vector>
#include <cstdint>
int64_t ctx_create(std::vector<torch::Tensor> tens, std::vector<int64_t> ints,
std::vector<double> fls);
void run_mega(int64_t handle, int64_t start_pos, int64_t n_steps, int64_t rnd,
int64_t hin, int64_t hout, int64_t nb, int64_t ph0);
std::vector<int64_t> scratch_sizes(int64_t nlayer, int64_t smax);
int64_t max_blocks();
std::vector<int64_t> set_persist(int64_t ptr, int64_t nbytes, double frac);
"""
_CUDA = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda_bf16.h>
#include <cuda_runtime.h>
#include <cstdint>
#include <cstring>
#include <vector>
#define TPB 256
#define NW (TPB/32)
#define HID 1024
#define INTER 3072
#define NQ 16
#define NKV 8
#define HD 128
#define QKVR 4096
#define EPSV 1e-6f
#define ASCALE 0.08838834764831845f
#define MAXL 8
typedef __nv_bfloat16 bf16;
__device__ __forceinline__ float b2f(bf16 x) { return __bfloat162float(x); }
struct LayerW {
const bf16* wqkv; // [4096][1024] permuted
const bf16* wo; // [1024][2048] permuted
const bf16* wgu; // [6144][1024] permuted
const bf16* wd; // [1024][3072] permuted
const float* nrm; // [1024 input_ln][1024 post_ln][128 q_norm][128 k_norm]
bf16* kv; // [NKV][max_seq][256] (k[128] then v[128])
float cb; // constant softmax shift
};
struct Args {
LayerW L[MAXL];
const float* rinv;
const bf16* rnd;
const bf16* hin;
bf16* hout;
bf16* xb;
float* qkvb;
float* alsum;
float* aout;
float* res2;
float* actb;
unsigned* ctr;
#if MQ_TIME
long long* tm;
#endif
int max_seq, nb, smax, start_pos, n_steps, nlayer;
unsigned ph0;
};
#if MQ_TIME
#define TDECL long long _t0 = clock64(); long long* _tm = A.tm + (size_t)blockIdx.x * 16
#define TMARK(i) { long long _t1 = clock64(); if (tid == 0) _tm[i] += _t1 - _t0; _t0 = _t1; }
#else
#define TDECL
#define TMARK(i)
#endif
// ---------------------------------------------------------------- reductions
__device__ __forceinline__ float wsum(float v) {
#pragma unroll
for (int o = 16; o > 0; o >>= 1) v += __shfl_xor_sync(0xffffffffu, v, o);
return v;
}
__device__ __forceinline__ float bsum(float v, float* shred, int warp, int lane) {
v = wsum(v);
__syncthreads();
if (lane == 0) shred[warp] = v;
__syncthreads();
float s = 0.f;
#pragma unroll
for (int i = 0; i < NW; ++i) s += shred[i];
return s;
}
// ------------------------------------------------------------------- barrier
// One counter, one poller thread per block. A dense 188-slot flag array (188
// independent release stores, no atomic serialisation) is *slower* in situ:
// every block then polls 1 KB per iteration, and at 188 blocks that spinning
// traffic (~1.9 TB/s) starves the weight stream it is supposed to overlap with.
// Serialising the arrive in one L2 slice is the cheaper trade.
#define FLAGN 32
__device__ __forceinline__ void gbar(unsigned* ctr, unsigned& ph, unsigned nb) {
__syncthreads();
ph += nb;
if (threadIdx.x == 0) {
// release on the arrive makes this block's stage output visible; the
// acquire after the spin picks up everyone else's. Folding the release
// into the red is ~95ns cheaper than a separate acq_rel fence.
asm volatile("red.release.gpu.global.add.u32 [%0], 1;" ::"l"(ctr) : "memory");
unsigned v;
do {
asm volatile("ld.relaxed.gpu.global.u32 %0, [%1];" : "=r"(v) : "l"(ctr) : "memory");
} while (v < ph);
asm volatile("fence.acquire.gpu;" ::: "memory");
}
__syncthreads();
}
// ------------------------------------------------------------- cached loads
// The 126 MB weight pack fits in the 128 MB L2, but the KV stream would evict it
// every step, so KV is loaded .cs (evict-first) to keep it out of the weights'
// way. The weight load is deliberately *unqualified*: the symmetric
// .L2::evict_last is not encodable at this width on SM120, which accepts the L2
// eviction-priority hints only on 32 B loads (.v8.b32 / .v4.b64) and has no
// .L2::no_allocate for ld at all. Widening the weight load to 32 B just to carry
// the hint costs more registers than the hint is worth (see set_persist, where the
// stream-policy equivalent measures neutral).
__device__ __forceinline__ uint4 ld_last(const void* p) {
uint4 r;
asm("ld.global.nc.v4.u32 {%0,%1,%2,%3}, [%4];"
: "=r"(r.x), "=r"(r.y), "=r"(r.z), "=r"(r.w) : "l"(p));
return r;
}
__device__ __forceinline__ uint4 ld_first(const void* p) {
uint4 r;
asm("ld.global.nc.cs.v4.u32 {%0,%1,%2,%3}, [%4];"
: "=r"(r.x), "=r"(r.y), "=r"(r.z), "=r"(r.w) : "l"(p));
return r;
}
// --------------------------------------------------------------------- gemv
// One warp per row segment, NCH chunks of 256 columns. Weight rows are
// pre-permuted so lane's uint4 holds columns {c*256 + lane + 32j : j<8}: the
// shared-memory activation read is then bank-conflict free and every weight
// load is a fully coalesced 512 B per-warp transaction.
template <int NCH>
__device__ __forceinline__ float wdot(const bf16* __restrict__ w,
const float* __restrict__ sv, int lane) {
const uint4* wp = (const uint4*)w;
uint4 v[NCH];
#pragma unroll
for (int c = 0; c < NCH; ++c) v[c] = ld_last(wp + c * 32 + lane);
float acc = 0.f;
#pragma unroll
for (int c = 0; c < NCH; ++c) {
const bf16* vb = (const bf16*)&v[c];
const float* s = sv + c * 256 + lane;
#pragma unroll
for (int j = 0; j < 8; ++j) acc = fmaf(b2f(vb[j]), s[32 * j], acc);
}
return acc;
}
// A stage's weight addresses depend only on blockIdx/warp, never on data, so
// every load can be issued *before* the barrier that precedes the stage. The
// loads then land in registers while the grid syncs: the memory pipe stays busy
// through the barrier and the stage itself starts with zero cold-miss latency.
// Each warp owns NT tasks of NCH 256-column chunks, task t at w + t*stride.
// T0..TMAX so the window can be split: tasks [0,PF) issue before the barrier
// (covering its latency), tasks [PF,TMAX) at the top of the stage (covering the
// activation-vector dependency). Both land before the first FMA needs them.
template <int NCH, int T0, int TMAX>
__device__ __forceinline__ void pf_issue(uint4* v, const bf16* __restrict__ w,
int stride, int nt, int lane) {
#pragma unroll
for (int t = T0; t < TMAX; ++t) {
if (t < nt) {
const uint4* wp = (const uint4*)(w + (size_t)t * stride);
#pragma unroll
for (int c = 0; c < NCH; ++c) v[t * NCH + c] = ld_last(wp + c * 32 + lane);
}
}
// opaque barrier: stops the scheduler from sinking these loads back down to
// their use points, which would defeat the whole point of issuing early
asm volatile("" ::: "memory");
}
// Cross-lane combine. A full 32-lane butterfly is a 5-deep *dependent* shuffle
// chain (~150 cyc); run once per task it is fully exposed, and a layer owns
// 3+3+9+3 = 18 tasks per warp -- 4.2 us/step, which the `no_shp` ablation
// confirms (-3.87 us). Two fixes, both here:
// * interleave all TMAX butterflies so the chains pipeline -- 5 steps of TMAX
// independent shuffles instead of TMAX chains of 5 (-0.87 us);
// * stop the butterfly at P surviving partials and let the epilogue absorb
// them. It already sums the row's 4 column groups out of shared memory, so
// P=4 just makes that 16 adds instead of 4 and saves 2 of the 5 steps
// (-0.96 us more). P=1 (no partials) is for stages whose output goes
// straight to global memory.
// Guarding the shuffles with t < nt is *slower* (76.9 vs 71.4 us/step): the
// branches break the interleave. Dead tasks reduce zeros instead.
template <int TMAX, int P>
__device__ __forceinline__ void red_store(float* acc, int nt, int lane,
float* out, int ostride) {
#pragma unroll
for (int d = 16 / P; d; d >>= 1)
#pragma unroll
for (int t = 0; t < TMAX; ++t) acc[t] += __shfl_xor_sync(0xffffffffu, acc[t], d);
if ((lane & (32 / P - 1)) == 0) {
const int m = lane / (32 / P);
#pragma unroll
for (int t = 0; t < TMAX; ++t)
if (t < nt) out[t * ostride + m] = acc[t];
}
}
// epilogue side of red_store: one row's partials, 4 (or 2) 16 B shared reads
__device__ __forceinline__ float psum8(const float* p) {
const float4 a = *(const float4*)p, b = *(const float4*)(p + 4);
return ((a.x + a.y) + (a.z + a.w)) + ((b.x + b.y) + (b.z + b.w));
}
__device__ __forceinline__ float psum16(const float* p) { return psum8(p) + psum8(p + 8); }
template <int NCH, int TMAX, int P>
__device__ __forceinline__ void pf_dot(const uint4* v, const float* __restrict__ sv,
int nt, int lane, float* out, int ostride) {
float acc[TMAX];
#pragma unroll
for (int t = 0; t < TMAX; ++t) {
acc[t] = 0.f;
if (t < nt) {
#pragma unroll
for (int c = 0; c < NCH; ++c) {
const bf16* vb = (const bf16*)&v[t * NCH + c];
const float* s = sv + c * 256 + lane;
#pragma unroll
for (int j = 0; j < 8; ++j) acc[t] = fmaf(b2f(vb[j]), s[32 * j], acc[t]);
}
}
}
red_store<TMAX, P>(acc, nt, lane, out, ostride);
}
// tasks past the prefetch window (only reachable when nb is far below the SM
// count, i.e. never in the graded configuration) -- plain load-and-use
template <int NCH, int P>
__device__ __forceinline__ void tail_dot(const bf16* __restrict__ w, int stride, int t0,
int nt, const float* __restrict__ sv, int lane,
float* out, int ostride) {
for (int t = t0; t < nt; ++t) {
float a[1] = {wdot<NCH>(w + (size_t)t * stride, sv, lane)};
red_store<1, P>(a, 1, lane, out + (size_t)t * ostride, 1);
}
}
// Prefetch window sizes. TM* = max tasks a warp can own (tight, because every
// slot costs registers -- 255 with no spills); PF* = tasks issued ahead of the
// preceding barrier. PF is a measured optimum, not a maximum: the pre-barrier
// loads keep the memory pipe busy through the sync, but past ~2-3 tasks they
// congest the same L2 slices the barrier poll has to reach, and the barrier is
// pure round-trip latency. Sweeping PF4 over {0,2,4,6,9} gives 76.2/72.3/73.3
// /74.4/76.0 us/step -- both ends lose. Tasks past TM* fall back to tail_dot,
// which only happens if nb drops far below the SM count.
#define TM1 3
#define TM3 3
#define TM4 9
#define TM5 3
#define PF1 2
#define PF3 3
#define PF4 2
#define PF5 3
extern "C" __global__ void __launch_bounds__(TPB, 1) mega(const Args A) {
__shared__ float shvec[INTER];
__shared__ float shred[NW];
__shared__ float shcos[64], shsin[64];
__shared__ float shq[2 * HD];
__shared__ float shkv[2 * HD];
__shared__ float shacc[2 * NW * HD];
__shared__ float shls[2 * NW];
// GEMV partials: 16 per output row (4 column groups x 4 surviving lanes of
// the butterfly, see red_store). Sized for the smallest legal nb (32, i.e.
// 96 rows of the 3072-wide stage); at 1 block/SM the extra 4 KB is free.
__shared__ float shp[16 * ((INTER + 31) / 32)];
const int tid = threadIdx.x, lane = tid & 31, warp = tid >> 5;
const int b = blockIdx.x, nb = A.nb;
// Attention split: block -> (kv head, split index). This deal is uneven --
// 188 blocks over 8 heads gives heads 0-3 24 blocks and heads 4-7 23, so a
// block on a 23-block head carries ceil(P/23) positions against a grid
// average of P/23.5. Two exactly-balanced replacements were built and both
// lost, so the 2.1% is deliberate:
// * cut the flat (kv_head x position) space into nb equal pieces, letting
// the ~7 boundary blocks do two heads: +2.7/+1.1/-0.1/-5.8 us/step at ctx
// 2048/8192/32768/131072, a 0.85% geomean loss.
// * 188 = 4 x 47, every block two heads over a 1/47 slice: worse again
// (+3.2/+2.3/+3.2), because each output address then takes 47 atomic adds
// instead of 24.
// The reason there is nothing to win: with the balanced split a-kv still
// measures max/avg 1.04x, i.e. that spread is memory-system variance, not work
// skew. Past ctx ~8192 the grid is DRAM-bound, so when the short blocks
// retire the survivors simply get their bandwidth and the step still ends when
// the bytes have landed. Balance buys nothing and the extra indexing is not
// free.
int h_kv, sidx, nbh;
{
int base8 = nb >> 3, rem8 = nb & 7, t1 = rem8 * (base8 + 1);
if (b < t1) { h_kv = b / (base8 + 1); sidx = b % (base8 + 1); nbh = base8 + 1; }
else { int bb = b - t1; h_kv = rem8 + bb / base8; sidx = bb % base8; nbh = base8; }
}
const int nl = A.nlayer;
unsigned ph = A.ph0;
TDECL;
// ---- GEMV row split + per-warp task descriptors. Identical for every
// layer and step, so hoist out of both loops. A "task" is NCH chunks of
// 256 columns; warp w owns tasks w, w+NW, w+2NW, ... at a fixed stride.
const int q_lo = (int)(((long)QKVR * b) / nb), q_hi = (int)(((long)QKVR * (b + 1)) / nb);
const int h_lo = (int)(((long)HID * b) / nb), h_hi = (int)(((long)HID * (b + 1)) / nb);
const int i_lo = (int)(((long)INTER * b) / nb), i_hi = (int)(((long)INTER * (b + 1)) / nb);
const int nr_h = h_hi - h_lo, nr_i = i_hi - i_lo;
const size_t o1 = (size_t)(q_lo + warp) * HID; // qkv row
const size_t o3 = (size_t)(h_lo + (warp >> 2)) * 2048 + (warp & 3) * 512;
const size_t o4 = (size_t)((((warp >> 1) & 1)) * INTER + i_lo + (warp >> 2)) * HID
+ (warp & 1) * 512;
const size_t o5 = (size_t)(h_lo + (warp >> 2)) * INTER + (warp & 3) * 768;
const int n1 = max(0, (q_hi - q_lo - warp + NW - 1) / NW);
const int n3 = max(0, (nr_h * 4 - warp + NW - 1) / NW);
const int n4 = max(0, (nr_i * 4 - warp + NW - 1) / NW);
const int n5 = n3;
const int s1 = NW * HID, s3 = (NW / 4) * 2048, s4 = (NW / 4) * HID, s5 = (NW / 4) * INTER;
float* const qo0 = A.qkvb + q_lo + warp;
// slot(row, colgroup, partial) = 16*row + 4*colgroup + partial, and every
// stage that lands here has row = (warp>>2) + (NW/4)*task, colgroup = warp&3
float* const shpw = shp + 16 * (warp >> 2) + 4 * (warp & 3);
const int sps = 16 * (NW / 4); // task stride
uint4 v1[TM1 * 4], v3[TM3 * 2], v4[TM4 * 2], v5[TM5 * 3];
pf_issue<4, 0, TM1>(v1, A.L[0].wqkv + o1, s1, n1, lane);
for (int step = 0; step < A.n_steps; ++step) {
const int pos = A.start_pos + step;
if (tid < 64) {
float s, c;
sincosf((float)pos * A.rinv[tid], &s, &c);
shcos[tid] = c; shsin[tid] = s;
}
for (int l = 0; l < nl; ++l) {
const LayerW LW = A.L[l];
// ---------------- stage 1: rmsnorm -> qkv ----------------
{
pf_issue<4, PF1, TM1>(v1, LW.wqkv + o1, s1, n1, lane);
// clear the accumulators stage 2 will atomically fill: one barrier
// ahead of the first add, and 3+ barriers behind the last reader
// (stage 3 of this layer in the previous step)
if (b < 17 && tid < HD) {
if (b < 16) A.aout[(size_t)l * 2048 + b * HD + tid] = 0.f;
else if (tid < NQ) A.alsum[(size_t)l * NQ + tid] = 0.f;
}
// 4 *contiguous* elements per thread, so every activation read
// below is a single 8/16 B vector load instead of 4 strided
// scalars -- one L2 round trip on the post-barrier critical path
float xr[HID / TPB];
const int i0 = tid * (HID / TPB);
if (l == 0) {
const bf16* hs = (step == 0) ? A.hin : (A.xb + (size_t)nl * HID);
const bf16* rr = A.rnd + (size_t)step * HID;
const uint2 rq = *(const uint2*)(rr + i0), hq = *(const uint2*)(hs + i0);
const bf16* rb = (const bf16*)&rq;
const bf16* hb = (const bf16*)&hq;
#pragma unroll
for (int i = 0; i < HID / TPB; ++i) {
bf16 xq = __float2bfloat16(0.5f * b2f(rb[i]) + 0.5f * b2f(hb[i]));
if (b == 0) A.xb[i0 + i] = xq;
xr[i] = b2f(xq);
}
} else {
const uint2 hq = *(const uint2*)(A.xb + (size_t)l * HID + i0);
const bf16* hb = (const bf16*)&hq;
#pragma unroll
for (int i = 0; i < HID / TPB; ++i) xr[i] = b2f(hb[i]);
}
float ss = 0.f;
#pragma unroll
for (int i = 0; i < HID / TPB; ++i) ss += xr[i] * xr[i];
ss = bsum(ss, shred, warp, lane);
float sc = rsqrtf(ss * (1.0f / HID) + EPSV);
const float4 gv = *(const float4*)(LW.nrm + i0);
float4 nv;
nv.x = xr[0] * sc * gv.x; nv.y = xr[1] * sc * gv.y;
nv.z = xr[2] * sc * gv.z; nv.w = xr[3] * sc * gv.w;
*(float4*)(shvec + i0) = nv;
__syncthreads();
float* qo = qo0 + (size_t)l * QKVR;
pf_dot<4, TM1, 1>(v1, shvec, n1, lane, qo, NW);
tail_dot<4, 1>(LW.wqkv + o1, s1, TM1, n1, shvec, lane, qo, NW);
// stage 3's weights are wanted two barriers from now: issue the
// whole window here so it lands during attention.
pf_issue<2, 0, TM3>(v3, LW.wo + o3, s3, n3, lane);
}
TMARK(0);
gbar(A.ctr, ph, nb);
TMARK(8);
// ---------------- stage 2: attention ----------------
{
const int P = pos + 1;
const int p0 = (int)(((long)P * sidx) / nbh);
const int p1 = (int)(((long)P * (sidx + 1)) / nbh);
const bool haslast = (p1 == P);
const int pend = haslast ? p1 - 1 : p1;
const float* qkvl = A.qkvb + (size_t)l * QKVR;
__syncthreads();
if (warp < 4) {
const float* src;
const float* gw;
if (warp < 2) { src = qkvl + (2 * h_kv + warp) * HD; gw = LW.nrm + 2 * HID; }
else if (warp == 2) { src = qkvl + 2048 + h_kv * HD; gw = LW.nrm + 2 * HID + HD; }
else { src = qkvl + 3072 + h_kv * HD; gw = nullptr; }
float a0 = src[lane], a1 = src[lane + 32], a2 = src[lane + 64], a3 = src[lane + 96];
if (warp < 3) {
float ss = wsum(a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3);
float sc = rsqrtf(ss * (1.0f / HD) + EPSV);
a0 *= sc * gw[lane]; a1 *= sc * gw[lane + 32];
a2 *= sc * gw[lane + 64]; a3 *= sc * gw[lane + 96];
float c0 = shcos[lane], s0 = shsin[lane];
float c1 = shcos[lane + 32], s1 = shsin[lane + 32];
float o0 = a0 * c0 - a2 * s0, o2 = a0 * s0 + a2 * c0;
float o1 = a1 * c1 - a3 * s1, o3 = a1 * s1 + a3 * c1;
a0 = o0; a1 = o1; a2 = o2; a3 = o3;
}
if (warp < 2) {
float* d = shq + warp * HD;
d[lane] = a0; d[lane + 32] = a1; d[lane + 64] = a2; d[lane + 96] = a3;
} else {
int off = (warp == 2) ? 0 : HD;
bf16 q0 = __float2bfloat16(a0), q1 = __float2bfloat16(a1);
bf16 q2 = __float2bfloat16(a2), q3 = __float2bfloat16(a3);
float* d = shkv + off;
d[lane] = b2f(q0); d[lane + 32] = b2f(q1);
d[lane + 64] = b2f(q2); d[lane + 96] = b2f(q3);
if (haslast) {
bf16* kvp = LW.kv + ((size_t)h_kv * A.max_seq + pos) * 256 + off;
kvp[lane] = q0; kvp[lane + 32] = q1;
kvp[lane + 64] = q2; kvp[lane + 96] = q3;
}
}
}
__syncthreads();
TMARK(5);
const int ll = lane & 15, hf = lane >> 4;
float qa[8], qb[8];
#pragma unroll
for (int j = 0; j < 8; ++j) { qa[j] = shq[ll * 8 + j]; qb[j] = shq[HD + ll * 8 + j]; }
float acc0[8], acc1[8];
#pragma unroll
for (int j = 0; j < 8; ++j) { acc0[j] = 0.f; acc1[j] = 0.f; }
float ls0 = 0.f, ls1 = 0.f;
const float cb = LW.cb;
const bf16* kvh = LW.kv + (size_t)h_kv * A.max_seq * 256;
for (int base = p0 + warp * 2; base < pend; base += 2 * NW) {
const int p = base + hf;
const bool ok = (p < pend);
float d0 = 0.f, d1 = 0.f;
uint4 vv;
if (ok) {
const bf16* kp = kvh + (size_t)p * 256 + ll * 8;
uint4 kk = ld_first(kp);
vv = ld_first(kp + HD);
const bf16* kb = (const bf16*)&kk;
#pragma unroll
for (int j = 0; j < 8; ++j) {
float kf = b2f(kb[j]);
d0 = fmaf(qa[j], kf, d0);
d1 = fmaf(qb[j], kf, d1);
}
}
#pragma unroll
for (int o = 1; o < 16; o <<= 1) {
d0 += __shfl_xor_sync(0xffffffffu, d0, o);
d1 += __shfl_xor_sync(0xffffffffu, d1, o);
}
if (ok) {
float e0 = __expf(d0 * ASCALE - cb), e1 = __expf(d1 * ASCALE - cb);
ls0 += e0; ls1 += e1;
const bf16* vb = (const bf16*)&vv;
#pragma unroll
for (int j = 0; j < 8; ++j) {
float vf = b2f(vb[j]);
acc0[j] = fmaf(e0, vf, acc0[j]);
acc1[j] = fmaf(e1, vf, acc1[j]);
}
}
}
if (haslast && warp == 0 && hf == 0) {
float d0 = 0.f, d1 = 0.f;
#pragma unroll
for (int j = 0; j < 8; ++j) {
float kf = shkv[ll * 8 + j];
d0 = fmaf(qa[j], kf, d0);
d1 = fmaf(qb[j], kf, d1);
}
#pragma unroll
for (int o = 1; o < 16; o <<= 1) {
d0 += __shfl_xor_sync(0x0000ffffu, d0, o);
d1 += __shfl_xor_sync(0x0000ffffu, d1, o);
}
float e0 = __expf(d0 * ASCALE - cb), e1 = __expf(d1 * ASCALE - cb);
ls0 += e0; ls1 += e1;
#pragma unroll
for (int j = 0; j < 8; ++j) {
float vf = shkv[HD + ll * 8 + j];
acc0[j] = fmaf(e0, vf, acc0[j]);
acc1[j] = fmaf(e1, vf, acc1[j]);
}
}
// fold the two half-warps together
#pragma unroll
for (int j = 0; j < 8; ++j) {
acc0[j] += __shfl_xor_sync(0xffffffffu, acc0[j], 16);
acc1[j] += __shfl_xor_sync(0xffffffffu, acc1[j], 16);
}
ls0 += __shfl_xor_sync(0xffffffffu, ls0, 16);
ls1 += __shfl_xor_sync(0xffffffffu, ls1, 16);
TMARK(6);
__syncthreads();
if (lane < 16) {
float* d0 = shacc + warp * HD;
float* d1 = shacc + NW * HD + warp * HD;
#pragma unroll
for (int j = 0; j < 8; ++j) { d0[lane * 8 + j] = acc0[j]; d1[lane * 8 + j] = acc1[j]; }
if (lane == 0) { shls[warp] = ls0; shls[NW + warp] = ls1; }
}
__syncthreads();
// Publish straight into the shared accumulator with L2 atomics.
// The obvious alternative -- park the partials, elect the last
// arriver by atomic counter, have it gather nbh of them -- puts a
// serial tail on the critical path of *every* block in the grid,
// because the barrier below cannot clear until that one block has
// done ~24 dependent strided loads: it cost 2.0 us of handshake
// plus ~11 us of b2 (measured at ctx 8192) against 0.1 us here.
// Only the nbh (~24) blocks of one kv head touch a given address,
// so each of the 2048 floats sees ~24 adds, spread over 64 lines.
if (tid < 2 * HD) {
const int qh = tid >> 7, d = tid & 127;
float s = 0.f;
#pragma unroll
for (int w = 0; w < NW; ++w) s += shacc[qh * NW * HD + w * HD + d];
float* ap = A.aout + (size_t)l * 2048 + (2 * h_kv + qh) * HD + d;
asm volatile("red.relaxed.gpu.global.add.f32 [%0], %1;"
::"l"(ap), "f"(s) : "memory");
if (d == 0) {
float t = 0.f;
#pragma unroll
for (int w = 0; w < NW; ++w) t += shls[qh * NW + w];
float* lp = A.alsum + (size_t)l * NQ + (2 * h_kv + qh);
asm volatile("red.relaxed.gpu.global.add.f32 [%0], %1;"
::"l"(lp), "f"(t) : "memory");
}
}
TMARK(7);
}
TMARK(1);
gbar(A.ctr, ph, nb);
TMARK(9);
// ---------------- stage 3: o proj + residual ----------------
{
const float* ao = A.aout + (size_t)l * 2048;
// softmax denominators, one per q head: the division the attention
// stage used to do is deferred to here so its numerator can be a
// plain atomic accumulation. 16 reciprocals per block, not 2048.
if (tid < NQ) shls[tid] = 1.0f / A.alsum[(size_t)l * NQ + tid];
__syncthreads();
{ // one float4 per thread per 4*TPB chunk: each warp's request is
// 512 B contiguous (4 sectors), so 2 loads instead of 8 with no
// loss of coalescing. A float4 never straddles a head (HD=128).
const int j0 = tid * 4;
#pragma unroll
for (int c = 0; c < 2048 / (4 * TPB); ++c) {
const int i = c * 4 * TPB + j0;
const float r = shls[i >> 7];
float4 t = *(const float4*)(ao + i);
t.x *= r; t.y *= r; t.z *= r; t.w *= r;
*(float4*)(shvec + i) = t;
}
}
__syncthreads();
const float* sv3 = shvec + (warp & 3) * 512;
pf_dot<2, TM3, 4>(v3, sv3, n3, lane, shpw, sps);
tail_dot<2, 4>(LW.wo + o3, s3, TM3, n3, sv3, lane, shpw, sps);
pf_issue<2, 0, PF4>(v4, LW.wgu + o4, s4, n4, lane);
__syncthreads();
const bf16* xs = A.xb + (size_t)l * HID;
float* r2 = A.res2 + (size_t)l * HID;
for (int rl = tid; rl < nr_h; rl += TPB)
r2[h_lo + rl] = b2f(xs[h_lo + rl]) + psum16(shp + rl * 16);
}
TMARK(2);
gbar(A.ctr, ph, nb);
TMARK(10);
// ---------------- stage 4: post rmsnorm + gate/up ----------------
{
// the weight stream stays ahead of the activation read: hoisting the
// res2 / norm-gain loads above pf_issue (they are on the critical
// path, the weights are not wanted until after the block reduction)
// costs 1.4 us/step -- 16 B of activation is not worth delaying
// 224 B of weights, which have much further to travel.
pf_issue<2, PF4, TM4>(v4, LW.wgu + o4, s4, n4, lane);
const float* r2 = A.res2 + (size_t)l * HID;
const int i0 = tid * (HID / TPB);
const float4 rv = *(const float4*)(r2 + i0);
float xr[HID / TPB];
xr[0] = rv.x; xr[1] = rv.y; xr[2] = rv.z; xr[3] = rv.w;
float ss = 0.f;
#pragma unroll
for (int i = 0; i < HID / TPB; ++i) ss += xr[i] * xr[i];
ss = bsum(ss, shred, warp, lane);
float sc = rsqrtf(ss * (1.0f / HID) + EPSV);
const float4 gv = *(const float4*)(LW.nrm + HID + i0);
float4 nv;
nv.x = xr[0] * sc * gv.x; nv.y = xr[1] * sc * gv.y;
nv.z = xr[2] * sc * gv.z; nv.w = xr[3] * sc * gv.w;
*(float4*)(shvec + i0) = nv;
__syncthreads();
const float* sv4 = shvec + (warp & 1) * 512;
pf_dot<2, TM4, 4>(v4, sv4, n4, lane, shpw, sps);
tail_dot<2, 4>(LW.wgu + o4, s4, TM4, n4, sv4, lane, shpw, sps);
pf_issue<3, 0, TM5>(v5, LW.wd + o5, s5, n5, lane);
__syncthreads();
float* ac = A.actb + (size_t)l * INTER;
for (int rl = tid; rl < nr_i; rl += TPB) {
// colgroup = 2*(gate|up) + column half, so 0..7 is the gate
// row and 8..15 the up row
float gv = psum8(shp + rl * 16), uv = psum8(shp + rl * 16 + 8);
ac[i_lo + rl] = (gv / (1.f + __expf(-gv))) * uv;
}
}
TMARK(3);
gbar(A.ctr, ph, nb);
TMARK(11);
// ---------------- stage 5: down + residual ----------------
{
const float* ac = A.actb + (size_t)l * INTER;
__syncthreads();
{ // 3 chunk-contiguous float4 loads instead of 12 scalars
const int j0 = tid * 4;
float4 a[INTER / (4 * TPB)];
#pragma unroll
for (int c = 0; c < INTER / (4 * TPB); ++c)
a[c] = *(const float4*)(ac + c * 4 * TPB + j0);
#pragma unroll
for (int c = 0; c < INTER / (4 * TPB); ++c)
*(float4*)(shvec + c * 4 * TPB + j0) = a[c];
}
__syncthreads();
const float* sv5 = shvec + (warp & 3) * 768;
pf_dot<3, TM5, 4>(v5, sv5, n5, lane, shpw, sps);
tail_dot<3, 4>(LW.wd + o5, s5, TM5, n5, sv5, lane, shpw, sps);
// next layer's qkv (wrapping to layer 0 for the next step)
pf_issue<4, 0, PF1>(v1, A.L[l + 1 == nl ? 0 : l + 1].wqkv + o1, s1, n1, lane);
__syncthreads();
const float* r2 = A.res2 + (size_t)l * HID;
bf16* xo = A.xb + (size_t)(l + 1) * HID;
for (int rl = tid; rl < nr_h; rl += TPB)
xo[h_lo + rl] = __float2bfloat16(r2[h_lo + rl] + psum16(shp + rl * 16));
}
TMARK(4);
gbar(A.ctr, ph, nb);
TMARK(12);
}
}
if (b == 0) {
const bf16* hs = A.xb + (size_t)nl * HID;
for (int i = tid; i < HID; i += TPB) A.hout[i] = hs[i];
}
}
// ------------------------------------------------------------------- host
static const size_t W_QKV = (size_t)QKVR * HID;
static const size_t W_O = (size_t)HID * 2048;
static const size_t W_GU = (size_t)6144 * HID;
static const size_t W_D = (size_t)HID * INTER;
static const size_t W_LAYER = W_QKV + W_O + W_GU + W_D;
std::vector<int64_t> scratch_sizes(int64_t nlayer, int64_t smax) {
int64_t nf = nlayer * (QKVR + NQ + 2048 + HID + INTER);
int64_t nb16 = (nlayer + 1) * HID;
int64_t nc = FLAGN; // one grid-barrier counter; the attention combine is
// atomic now, so it needs no per-head handshake slot
return {nf, nb16, nc};
}
int64_t ctx_create(std::vector<torch::Tensor> t, std::vector<int64_t> ii,
std::vector<double> ff) {
Args* a = new Args();
const int nl = (int)ii[0];
a->nlayer = nl;
a->max_seq = (int)ii[1];
a->smax = (int)ii[2];
const bf16* wp = (const bf16*)t[0].data_ptr();
const float* np = (const float*)t[1].data_ptr();
for (int l = 0; l < nl; ++l) {
size_t o = (size_t)l * W_LAYER;
a->L[l].wqkv = wp + o;
a->L[l].wo = wp + o + W_QKV;
a->L[l].wgu = wp + o + W_QKV + W_O;
a->L[l].wd = wp + o + W_QKV + W_O + W_GU;
a->L[l].nrm = np + (size_t)l * (2 * HID + 2 * HD);
a->L[l].kv = (bf16*)t[4 + l].data_ptr();
a->L[l].cb = (float)ff[l];
}
a->rinv = (const float*)t[2].data_ptr();
float* sf = (float*)t[3].data_ptr();
size_t off = 0;
a->qkvb = sf; off += (size_t)nl * QKVR;
a->alsum = sf + off; off += (size_t)nl * NQ;
a->aout = sf + off; off += (size_t)nl * 2048;
a->res2 = sf + off; off += (size_t)nl * HID;
a->actb = sf + off;
a->xb = (bf16*)t[4 + nl].data_ptr();
a->ctr = (unsigned*)t[5 + nl].data_ptr();
return (int64_t)a;
}
// Pin as much of the weight pack in L2 as the set-aside allows. The pack is
// 125.8 MB and re-read in full every single decode step, so every byte that
// stays resident is a byte of DRAM traffic saved per step; the KV stream is
// loaded with .cs so it cannot evict what we pinned.
//
// Two details the standalone probe (dev/l2s.cu, the ctx-2048 byte mix) says
// matter, both of which the obvious spelling gets wrong:
// * the window must be an 80 MB *subset* of the pack at hitRatio 1.0, not the
// whole pack at hitRatio 80/126. The ratio is a per-line lottery and the
// losers stay evictable: 44.0 vs 46.8 us.
// * missProp must be Normal. Streaming marks the un-pinned remainder
// evict-first, and it is still re-read every step: 49.4 vs 44.7 us.
// SM120 also refuses .L2::evict_last/evict_first on anything narrower than a
// 32 B load (.v8.b32/.v4.b64) and has no .L2::no_allocate for ld at all, so the
// stream policy window is the only lever available at this load width.
//
// And yet: correctly spelled, it does *nothing* for this kernel. Sweeping the
// set-aside in situ is flat at every graded context (frac 0 vs 1, interleaved:
// 88.63/88.70 at ctx 2048, 155.30/155.37 at 8192, 417.64/417.75 at 32768), and
// so is the same window applied inside the dev harness over its own cudaMalloc,
// which rules out the plumbing (off/80/64/40 MB: 86.0/86.7/86.6/86.5 at 2048,
// 414.3/410.6/413.4/413.8 at 32768). The probe only gains because its working
// set is 1.25x L2, where replacement policy decides everything; here ctx 2048 and
// 8192 are barrier-bound rather than DRAM-bound, and by ctx 32768 the 80 MB that
// can be pinned is only ~12% of the step's DRAM traffic. So this is left off by
// default -- kept, documented, and not worth a global device limit.
static void* g_pbase = nullptr;
static size_t g_pbytes = 0;
static float g_phit = 0.f;
static cudaStream_t g_pstream = nullptr;
static void apply_policy(cudaStream_t s) {
if (g_pbase == nullptr) return;
if (s == g_pstream) return;
g_pstream = s;
cudaStreamAttrValue v;
memset(&v, 0, sizeof(v));
v.accessPolicyWindow.base_ptr = g_pbase;
v.accessPolicyWindow.num_bytes = g_pbytes;
v.accessPolicyWindow.hitRatio = g_phit;
v.accessPolicyWindow.hitProp = cudaAccessPropertyPersisting;
v.accessPolicyWindow.missProp = cudaAccessPropertyNormal;
cudaStreamSetAttribute(s, cudaStreamAttributeAccessPolicyWindow, &v);
}
std::vector<int64_t> set_persist(int64_t ptr, int64_t nbytes, double frac) {
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
if (frac <= 0.0) {
g_pbase = nullptr;
g_pstream = nullptr;
cudaDeviceSetLimit(cudaLimitPersistingL2CacheSize, 0);
return {0, 0};
}
const size_t amax = (size_t)prop.persistingL2CacheMaxSize;
size_t aside = (size_t)(prop.persistingL2CacheMaxSize * frac);
if (aside > amax) aside = amax;
cudaDeviceSetLimit(cudaLimitPersistingL2CacheSize, aside);
// window = the front of the pack, exactly as large as the set-aside
size_t win = (size_t)nbytes < aside ? (size_t)nbytes : aside;
if (win > (size_t)prop.accessPolicyMaxWindowSize) win = prop.accessPolicyMaxWindowSize;
g_pbase = (void*)ptr;
g_pbytes = win;
g_phit = 1.0f;
g_pstream = nullptr;
apply_policy(at::cuda::getCurrentCUDAStream());
return {(int64_t)win, (int64_t)aside};
}
void run_mega(int64_t handle, int64_t start_pos, int64_t n_steps, int64_t rnd,
int64_t hin, int64_t hout, int64_t nb, int64_t ph0) {
Args* a = (Args*)handle;
a->start_pos = (int)start_pos;
a->n_steps = (int)n_steps;
a->rnd = (const bf16*)rnd;
a->hin = (const bf16*)hin;
a->hout = (bf16*)hout;
a->nb = (int)nb;
a->ph0 = (unsigned)ph0;
cudaStream_t s = at::cuda::getCurrentCUDAStream();
apply_policy(s);
mega<<<(int)nb, TPB, 0, s>>>(*a);
}
int64_t max_blocks() {
int n = 0;
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&n, mega, TPB, 0);
return (int64_t)n;
}
"""
_ext = None
def _get_ext():
global _ext
if _ext is None:
cap = torch.cuda.get_device_capability(0)
os.environ["TORCH_CUDA_ARCH_LIST"] = "%d.%d" % cap
from torch.utils.cpp_extension import load_inline
_ext = load_inline(
name="megaqwen_decode_v1",
cpp_sources=[_CPP],
cuda_sources=[_CUDA],
functions=[
"ctx_create",
"run_mega",
"scratch_sizes",
"max_blocks",
"set_persist",
],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3", "-lineinfo"],
verbose=False,
)
return _ext
# ----------------------------------------------------------------------------
# Model
# ----------------------------------------------------------------------------
class _Block(nn.Module):
def __init__(self):
super().__init__()
H, I, D = HIDDEN, INTERMEDIATE, HEAD_DIM
bf = torch.bfloat16
self.input_ln = nn.Parameter(torch.ones(H, dtype=bf))
self.q_proj = nn.Parameter(torch.zeros(NUM_Q * D, H, dtype=bf))
self.k_proj = nn.Parameter(torch.zeros(NUM_KV * D, H, dtype=bf))
self.v_proj = nn.Parameter(torch.zeros(NUM_KV * D, H, dtype=bf))
self.q_norm = nn.Parameter(torch.ones(D, dtype=bf))
self.k_norm = nn.Parameter(torch.ones(D, dtype=bf))
self.o_proj = nn.Parameter(torch.zeros(H, NUM_Q * D, dtype=bf))
self.post_ln = nn.Parameter(torch.ones(H, dtype=bf))
self.gate_proj = nn.Parameter(torch.zeros(I, H, dtype=bf))
self.up_proj = nn.Parameter(torch.zeros(I, H, dtype=bf))
self.down_proj = nn.Parameter(torch.zeros(H, I, dtype=bf))
def _perm(w):
"""Permute columns inside each 256-wide chunk for conflict-free smem reads."""
m, k = w.shape
assert k % 256 == 0
return w.reshape(m, k // 256, 8, 32).transpose(-1, -2).contiguous().reshape(m, k)
class Model(nn.Module):
def __init__(self, num_layers: int = NUM_LAYERS, max_seq: int = 131072):
super().__init__()
self.num_layers = num_layers
self.max_seq = max_seq
self.blocks = nn.ModuleList([_Block() for _ in range(num_layers)])
self._ctx = None
self._store = None
# any weight change invalidates the packed copy
def load_state_dict(self, *a, **kw):
self._ctx = None
return super().load_state_dict(*a, **kw)
def _apply(self, *a, **kw):
self._ctx = None
return super()._apply(*a, **kw)
def _ensure(model: Model):
if model._ctx is not None:
return model._store
ext = _get_ext()
dev = next(model.parameters()).device
assert dev.type == "cuda", "solution requires CUDA weights"
nl = model.num_layers
props = torch.cuda.get_device_properties(dev)
nb = int(os.environ.get("MQ_NB", props.multi_processor_count))
assert ext.max_blocks() >= 1, "megakernel not resident"
assert 32 <= nb <= 256
smax = (nb + NUM_KV - 1) // NUM_KV
st = model._store or {}
# ---- packed weights: one contiguous 126 MB buffer (L2 resident) ----
wparts, nparts, cbs = [], [], []
for blk in model.blocks:
wqkv = torch.cat([blk.q_proj.detach(), blk.k_proj.detach(), blk.v_proj.detach()], 0)
wgu = torch.cat([blk.gate_proj.detach(), blk.up_proj.detach()], 0)
for w in (wqkv, blk.o_proj.detach(), wgu, blk.down_proj.detach()):
wparts.append(_perm(w).reshape(-1))
nparts.append(
torch.cat(
[
blk.input_ln.detach().float(),
blk.post_ln.detach().float(),
blk.q_norm.detach().float(),
blk.k_norm.detach().float(),
]
)
)
qm = blk.q_norm.detach().float().abs().max().item()
km = blk.k_norm.detach().float().abs().max().item()
cbs.append(min(math.sqrt(HEAD_DIM) * qm * km, 40.0))
st["w"] = torch.cat(wparts)
st["n"] = torch.cat(nparts)
del wparts, nparts
half = HEAD_DIM // 2
st["rinv"] = 1.0 / (
10000 ** (torch.arange(0, half, device=dev, dtype=torch.float32) / half)
)
nf, nb16, nc = [int(x) for x in ext.scratch_sizes(nl, smax)]
st["sf"] = torch.zeros(nf, dtype=torch.float32, device=dev)
st["xb"] = torch.zeros(nb16, dtype=torch.bfloat16, device=dev)
st["ctr"] = torch.zeros(nc, dtype=torch.int32, device=dev)
st["ph"] = 0
if "kv" not in st:
st["kv"] = [
torch.zeros(NUM_KV, model.max_seq, 2 * HEAD_DIM, dtype=torch.bfloat16, device=dev)
for _ in range(nl)
]
if "cs" not in st:
st["cs"] = torch.cuda.Stream(device=dev)
st["ev"] = torch.cuda.Event()
st["nb"] = nb
st["dev"] = dev
# measured neutral on this kernel at every graded context (see set_persist)
frac = float(os.environ.get("MQ_PERSIST", 0.0))
ext.set_persist(st["w"].data_ptr(), st["w"].numel() * 2, frac)
tens = [st["w"], st["n"], st["rinv"], st["sf"]] + st["kv"] + [st["xb"], st["ctr"]]
model._ctx = ext.ctx_create(tens, [nl, model.max_seq, smax], cbs)
model._store = st
return st
_NBUF = 2
def _bufs(st, n):
"""Ring of (pinned host, device, copy-done event) pairs of >= n rows."""
b = st.get("bufs")
if b is None or b[0][0].shape[0] < n:
b = [
(
torch.empty(n, HIDDEN, dtype=torch.bfloat16, pin_memory=True),
torch.empty(n, HIDDEN, dtype=torch.bfloat16, device=st["dev"]),
torch.cuda.Event(),
)
for _ in range(_NBUF)
]
st["bufs"] = b
return b
@torch.no_grad()
def _run_steps(model, hidden, start_pos, n_steps, rseed):
"""n_steps fused decode steps. The CPU RNG stream is generated in growing
chunks and copied on a side stream so it overlaps with the megakernel."""
ext = _get_ext()
st = _ensure(model)
nl = model.num_layers
dev = st["dev"]
g = torch.Generator(device="cpu")
g.manual_seed(rseed)
out = torch.empty(HIDDEN, dtype=torch.bfloat16, device=dev)
hin_ptr = hidden.contiguous().data_ptr()
CAP = 1024
bufs = _bufs(st, min(n_steps, CAP))
main = torch.cuda.current_stream(device=dev)
cs, ev = st["cs"], st["ev"]
done = 0
chunk = 1
ri = 0
while done < n_steps:
host, devr, hev = bufs[ri % _NBUF]
ri += 1
hev.synchronize() # host rows: last copy out of them has completed
cs.wait_stream(main) # device rows: kernels still reading them are done
room = min(CAP, n_steps - done)
off = 0
while off < room:
n = min(chunk, room - off)
host[off : off + n].normal_(generator=g)
with torch.cuda.stream(cs):
devr[off : off + n].copy_(host[off : off + n], non_blocking=True)
ev.record(cs)
hev.record(cs)
main.wait_event(ev)
ph0 = st["ph"]
nbar = 5 * nl * n * st["nb"]
if ph0 + nbar >= 2 ** 31:
st["ctr"].zero_()
ph0 = 0
st["ph"] = ph0 + nbar
ext.run_mega(
model._ctx,
start_pos + done + off,
n,
devr[off].data_ptr(),
hin_ptr,
out.data_ptr(),
st["nb"],
ph0,
)
hin_ptr = st["xb"].data_ptr() + nl * HIDDEN * 2
off += n
chunk = min(chunk * 4, CAP)
done += room
return out
def _seeded_hidden(seed: int, device) -> torch.Tensor:
g = torch.Generator(device="cpu")
g.manual_seed(seed)
return torch.randn(HIDDEN, generator=g, dtype=torch.bfloat16).to(device)
@torch.no_grad()
def prefill(model: Model, ctx_len: int, seed: int, device=None):
"""Build a KV cache of length ctx_len. NOT timed."""
st = _ensure(model)
assert ctx_len <= model.max_seq
h = _seeded_hidden(seed, st["dev"])
if ctx_len > 0:
h = _run_steps(model, h, 0, ctx_len, seed + 1)
return h, st["kv"], st["kv"]
@torch.no_grad()
def decode_steps(model, hidden, k_caches, v_caches, start_pos, n_steps, seed):
"""Run n_steps decode steps starting at start_pos. TIMED."""
if n_steps <= 0:
return hidden, k_caches, v_caches
h = _run_steps(model, hidden, start_pos, n_steps, seed + 2)
return h, k_caches, v_caches
def run(ctx_len: int, n_decode: int, seed: int, model=None, max_seq=None) -> dict:
device = torch.device("cuda:0")
max_seq = max_seq or max(ctx_len + n_decode, 512)
if model is None:
model = Model(NUM_LAYERS, max_seq).to(device).eval()
h, k_caches, v_caches = prefill(model, ctx_len, seed, device=device)
h, k_caches, v_caches = decode_steps(
model, h, k_caches, v_caches, start_pos=ctx_len, n_steps=n_decode, seed=seed
)
return {"last_hidden": h.detach(), "ctx_len": ctx_len, "decode_steps": n_decode}
def get_init_inputs():
return [NUM_LAYERS, 131072]
def get_inputs():
return []
20260725_023443_or-opus_anthropic_claude-opus-5_03_megaqwen_decode