Skip to main content

logicaffeine_compile/codegen_cuda/
qwen_batched.rs

1//! ════════════════════════════════════════════════════════════════════════════════════════════════
2//! BATCHED Qwen3-30B decode — the lane where "beat llama" is a landslide, not a 4% wall.
3//!
4//! The batch-1 decode is memory-wall bound (each weight byte read once, serves one token). The batched decode
5//! runs B sequences in lockstep: every weight kernel is launched with a batch dimension (grid.y = B), so a
6//! weight row read once serves B tokens (reused through L2) — llama itself scales 7× this way. Per-token order
7//! is IDENTICAL to the batch-1 kernels ⇒ every sequence is byte-exact to the batch-1 golden (token-exact per
8//! sequence), while aggregate throughput (S_TG = B·steps/time) scales toward — and past — llama-batched-bench.
9//!
10//! The lane is written by hand from the batch-1 lane's kernels, not compiled from `qwen30b.lg`; only the execution
11//! is batched. Correctness gate: every one of the B sequences emits the golden token stream. Speed gate: aggregate
12//! S_TG > llama-batched-bench at the same B.
13//! ════════════════════════════════════════════════════════════════════════════════════════════════
14
15use crate::codegen_cuda::qwen_emit::Qwen30bCfg;
16
17/// Emit a self-contained CUDA program that runs the real Qwen3-30B-A3B decode over `batch` parallel sequences
18/// and reports aggregate S_TG plus the golden token stream for every sequence. Weights `fopen` from `wdir`.
19pub fn emit_qwen30b_generate_batched(
20    cfg: &Qwen30bCfg,
21    wdir: &str,
22    prompt: &[usize],
23    n_gen: usize,
24    batch: usize,
25) -> String {
26    let (e, ff, ne, ktop, vocab, layers, nh, nkv, hd) = (
27        cfg.d, cfg.dff, cfg.ne, cfg.k, cfg.vocab, cfg.layers, cfg.h, cfg.kv, cfg.hd,
28    );
29    let qd = nh * hd;
30    let kd = nkv * hd;
31    // Hold a full tg128 measurement (KV grows 0→128) plus the prompt + margin — the benchmark replays the
32    // decode from an empty cache exactly as llama-batched-bench does, so d_pos must never exceed this.
33    let maxseq = prompt.len() + 128 + 16;
34    let scale = 1.0f64 / (hd as f64).sqrt();
35    let prompt_c = prompt
36        .iter()
37        .map(|t| t.to_string())
38        .collect::<Vec<_>>()
39        .join(",");
40
41    let prov = super::provenance::header(super::provenance::Provenance::HandWritten {
42        model: "Qwen3-30B-A3B",
43        lane: "batched decode",
44    });
45    format!(
46        r#"{prov}// Qwen3-30B-A3B BATCHED decode: B parallel sequences, weight reuse via grid.y=B, per-sequence golden.
47// Weights from {wdir}.
48#include <cstdio>
49#include <cstdlib>
50#include <cstring>
51#include <vector>
52#include <cuda_runtime.h>
53#include <cuda_fp16.h>
54#include <cmath>
55#define E {e}
56#define QD {qd}
57#define KD {kd}
58#define FF {ff}
59#define NE {ne}
60#define KTOP {ktop}
61#define VOCAB {vocab}
62#define LAYERS {layers}
63#define NH {nh}
64#define NKV {nkv}
65#define HD {hd}
66#define MAXSEQ {maxseq}
67#define ATTN_SPLIT 8
68#define B {batch}
69#define EPSV 1e-6f
70#define ROPEV 1000000.0f
71static const float SCALEV = {scale}f;
72
73// ── batched device kernels: per-token order identical to batch-1 (golden per sequence), grid.y=B for reuse ──
74__global__ void bgk_embed(float* X, const signed char* wq, const float* ws, const int* D_tok){{
75  int idx=blockIdx.x*blockDim.x+threadIdx.x; if(idx>=B*E) return; int b=idx/E, i=idx%E;
76  long tok=D_tok[b]; X[(long)b*E+i]=(float)wq[tok*E+i]*ws[tok*(E/32)+i/32];
77}}
78// one block per sequence: rmsnorm(x)·w then int8 quant (the attention norm).
79__global__ void bgk_rmsnorm_q8(const float* X, signed char* Xq, float* Xs, const float* w, int n, float eps){{
80  int seq=blockIdx.x; const float* x=X+(long)seq*n; signed char* xq=Xq+(long)seq*n; float* xs=Xs+(long)seq*(n/32);
81  __shared__ float red[256]; float s=0; for(int i=threadIdx.x;i<n;i+=blockDim.x) s+=x[i]*x[i];
82  red[threadIdx.x]=s; __syncthreads();
83  for(int k=128;k>0;k>>=1){{ if(threadIdx.x<k) red[threadIdx.x]+=red[threadIdx.x+k]; __syncthreads(); }}
84  float rms=rsqrtf(red[0]/n+eps); int warp=threadIdx.x>>5, lane=threadIdx.x&31, nb=n/32;
85  for(int bl=warp;bl<nb;bl+=8){{ float y=x[bl*32+lane]*rms*w[bl*32+lane], a=fabsf(y);
86    for(int sh=16;sh>0;sh>>=1) a=fmaxf(a,__shfl_down_sync(0xffffffffu,a,sh));
87    a=__shfl_sync(0xffffffffu,a,0); float sc=a>0?a/127.0f:1.0f;
88    if(lane==0) xs[bl]=sc; xq[bl*32+lane]=(signed char)__float2int_rn(y/sc); }}
89}}
90// one block per sequence: x += y (residual kept), out = rmsnorm(x)·w (float, for router) AND int8 (oq/os_, experts).
91__global__ void bgk_add_rmsnorm_q8(float* X, const float* Y, float* Out, const float* w, signed char* Oq, float* Os, int n, float eps){{
92  int seq=blockIdx.x; float* x=X+(long)seq*n; const float* y=Y+(long)seq*n; float* out=Out+(long)seq*n;
93  signed char* oq=Oq+(long)seq*n; float* os_=Os+(long)seq*(n/32);
94  __shared__ float sh[256]; float s=0; for(int i=threadIdx.x;i<n;i+=blockDim.x){{ float v=x[i]+y[i]; x[i]=v; s+=v*v; }}
95  sh[threadIdx.x]=s; __syncthreads();
96  for(int k=blockDim.x/2;k>0;k>>=1){{ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }}
97  float scale=rsqrtf(sh[0]/n+eps); for(int i=threadIdx.x;i<n;i+=blockDim.x) out[i]=x[i]*scale*w[i];
98  __syncthreads();
99  int warp=threadIdx.x>>5, lane=threadIdx.x&31, nb=n/32;
100  for(int bl=warp;bl<nb;bl+=(blockDim.x>>5)){{ float v=out[bl*32+lane], av=fabsf(v);
101    for(int t=16;t>0;t>>=1) av=fmaxf(av,__shfl_down_sync(0xffffffffu,av,t));
102    av=__shfl_sync(0xffffffffu,av,0); float amax=av>1e-30f?av:1e-30f; float qs=amax/127.0f;
103    if(lane==0) os_[bl]=qs; int q=(int)rintf(v/qs); if(q>127)q=127; if(q<-128)q=-128; oq[bl*32+lane]=(signed char)q; }}
104}}
105// one block per sequence: x += y, then rmsnorm(x)·w → int8 (attn norm of next layer / final).
106__global__ void bgk_add_rmsnorm_q8a(float* X, const float* Y, signed char* Xq, float* Xs, const float* w, int n, float eps){{
107  int seq=blockIdx.x; float* x=X+(long)seq*n; const float* y=Y+(long)seq*n; signed char* xq=Xq+(long)seq*n; float* xs=Xs+(long)seq*(n/32);
108  __shared__ float red[256]; float s=0; for(int i=threadIdx.x;i<n;i+=blockDim.x){{ float v=x[i]+y[i]; x[i]=v; s+=v*v; }}
109  red[threadIdx.x]=s; __syncthreads();
110  for(int k=128;k>0;k>>=1){{ if(threadIdx.x<k) red[threadIdx.x]+=red[threadIdx.x+k]; __syncthreads(); }}
111  float rms=rsqrtf(red[0]/n+eps); int warp=threadIdx.x>>5, lane=threadIdx.x&31, nb=n/32;
112  for(int bl=warp;bl<nb;bl+=8){{ float y2=x[bl*32+lane]*rms*w[bl*32+lane], a=fabsf(y2);
113    for(int sh=16;sh>0;sh>>=1) a=fmaxf(a,__shfl_down_sync(0xffffffffu,a,sh));
114    a=__shfl_sync(0xffffffffu,a,0); float sc=a>0?a/127.0f:1.0f;
115    if(lane==0) xs[bl]=sc; xq[bl*32+lane]=(signed char)__float2int_rn(y2/sc); }}
116}}
117// batched int8 GEMV, REGISTER-TILED (weight-stationary): grid=(N-warps, ceil(B/BTG)). Each warp reads the
118// weight row + its per-32 scale ONCE per k-block and reuses them across BTG sequences held in registers — true
119// B-fold weight-bandwidth amortization (not just L2). Per-sequence __dp4a order is identical to batch-1 ⇒ every
120// sequence is byte-exact (golden). This is the `bdp4a` pattern the batched lane was designed to lower to.
121#ifndef BTG
122#define BTG 8
123#endif
124#ifndef BTG_MOE
125#define BTG_MOE BTG
126#endif
127// ── occupancy cap for the register-heavy MoE GEMV kernels ──
128// gate / up-silu / down-combine each compile to 80 registers/thread ⇒ only ~40% occupancy on H100 (65536 regs/SM),
129// which leaves them DRAM-latency-bound at ~27% of peak bandwidth even though they are the FFN's dominant 65% of the
130// decode. Forcing ptxas to keep 5 resident 256-thread blocks/SM (≤51 regs) lifts occupancy to ~62% with zero spill —
131// the measured throughput peak (min-blocks 4→64regs and 6→42regs both regress; see cuda_qwen30b_batched_occupancy_lock).
132// This is the delta that carries the batched (B=32, tg128) decode past llama.cpp on the H100.
133#ifndef MOE_OCC
134#define MOE_OCC __launch_bounds__(256, 5)
135#endif
136__global__ void bgk_gemv(float* Y, const signed char* wq, const float* ws, const signed char* Xq, const float* Xs, int K, int N){{
137  int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31; int b0=blockIdx.y*BTG;
138  const int4* w16=(const int4*)(wq+(long)o*K); const float* wsr=ws+(long)o*(K/32); int nq=K/512;
139  float acc[BTG]; for(int t=0;t<BTG;t++) acc[t]=0.f;
140  for(int c=0;c<nq;c++){{ int4 w=w16[c*32+lane]; int blk=c*16+lane/2; float wsb=wsr[blk];
141    #pragma unroll
142    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue;
143      int4 xx=((const int4*)(Xq+(long)bb*K))[c*32+lane];
144      int d=__dp4a(w.x,xx.x,0)+__dp4a(w.y,xx.y,0)+__dp4a(w.z,xx.z,0)+__dp4a(w.w,xx.w,0);
145      acc[t] += (float)d * (wsb*Xs[(long)bb*(K/32)+blk]); }} }}
146  int done=nq*512;
147  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue;
148    for(int j=done+lane;j<K;j+=32){{ int blk=j/32; acc[t] += (float)((int)Xq[(long)bb*K+j]*(int)wq[(long)o*K+j])*(wsr[blk]*Xs[(long)bb*(K/32)+blk]); }} }}
149  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float a=acc[t];
150    for(int s=16;s>0;s>>=1) a+=__shfl_down_sync(0xffffffffu,a,s); if(lane==0) Y[(long)bb*N+o]=a; }}
151}}
152// ★ int8 TENSOR-CORE batched GEMM (Marlin, M1–M5): Y[M][N] = Xq[M][K]·Wq[N][K] via mma.sync.m16n8k32.s8 — one
153// mma per 32-block (mma-k == Q8_0 scale block, SAT-certified M3), per-32-block dequant scale applied in REGISTERS
154// in the SAME float grouping as our dp4a (golden-bounded ≤4.2e-7, M4). At batch (M=B) the weight is reused M-fold
155// and the GEMM is COMPUTE-bound ⇒ tensor cores (~10× dp4a) are the win — exactly llama's mmq. Our decode layout
156// (activation [M][K]+[M][K/32] scale, weight [N][K]+[N][K/32] scale) already matches Marlin's.
157// SHARED-MEMORY-STAGED (llama's mmq technique): each block owns a TBM×TBN output tile and 8 warps. Per 32-block
158// of K, all 256 threads COOPERATIVELY load the activation tile (TBM×32) and weight tile (TBN×32) from global to
159// shared COALESCED, __syncthreads, then each warp reads its mma fragments FROM SHARED and issues mma.sync — so
160// global traffic hits HBM bandwidth (the naive per-fragment global loads did not). Per-32-block dequant scale
161// applied in registers in the SAME float grouping as dp4a (golden-bounded ≤4.2e-7, M3/M4 SAT-certified).
162#define TBM 16
163#define TBN 64
164#define GMMA(M,N) dim3((N)/TBN, ((M)+TBM-1)/TBM)
165__global__ void bgk_gemm_mma(const signed char* Xq,const float* Xs,const signed char* Wq,const float* Ws,float* Y,int M,int N,int K){{
166  int KB=K/32; int mblk=blockIdx.y*TBM, nblk=blockIdx.x*TBN;
167  int warp=threadIdx.x>>5, lane=threadIdx.x&31, tid=threadIdx.x; int gid=lane>>2, tq=lane&3;
168  __shared__ signed char Xsm[TBM*32]; __shared__ signed char Wsm[TBN*32];
169  float d0=0.f,d1=0.f,d2=0.f,d3=0.f;
170  for(int kb=0;kb<KB;kb++){{ int k0=kb*32;
171    for(int i=tid;i<TBM*32;i+=256){{ int r=i>>5,c=i&31; Xsm[i]=Xq[(long)(mblk+r)*K+k0+c]; }}
172    for(int i=tid;i<TBN*32;i+=256){{ int r=i>>5,c=i&31; Wsm[i]=Wq[(long)(nblk+r)*K+k0+c]; }}
173    __syncthreads();
174    int a0=*(const int*)(Xsm+gid*32+tq*4);
175    int a1=*(const int*)(Xsm+(gid+8)*32+tq*4);
176    int a2=*(const int*)(Xsm+gid*32+16+tq*4);
177    int a3=*(const int*)(Xsm+(gid+8)*32+16+tq*4);
178    int b0=*(const int*)(Wsm+(warp*8+gid)*32+tq*4);
179    int b1=*(const int*)(Wsm+(warp*8+gid)*32+16+tq*4);
180    int c0=0,c1=0,c2=0,c3=0;
181    asm volatile("mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 {{%0,%1,%2,%3}}, {{%4,%5,%6,%7}}, {{%8,%9}}, {{%0,%1,%2,%3}};"
182      : "+r"(c0),"+r"(c1),"+r"(c2),"+r"(c3) : "r"(a0),"r"(a1),"r"(a2),"r"(a3), "r"(b0),"r"(b1));
183    float xs0=Xs[(long)(mblk+gid)*KB+kb], xs1=Xs[(long)(mblk+gid+8)*KB+kb];
184    int cbase=nblk+warp*8; float ws0=Ws[(long)(cbase+tq*2)*KB+kb], ws1=Ws[(long)(cbase+tq*2+1)*KB+kb];
185    d0+=(float)c0*(ws0*xs0); d1+=(float)c1*(ws1*xs0); d2+=(float)c2*(ws0*xs1); d3+=(float)c3*(ws1*xs1);
186    __syncthreads();
187  }}
188  int r0=mblk+gid,r1=mblk+gid+8,cc0=nblk+warp*8+tq*2,cc1=cc0+1;
189  if(r0<M){{ if(cc0<N)Y[(long)r0*N+cc0]=d0; if(cc1<N)Y[(long)r0*N+cc1]=d1; }}
190  if(r1<M){{ if(cc0<N)Y[(long)r1*N+cc0]=d2; if(cc1<N)Y[(long)r1*N+cc1]=d3; }}
191}}
192// batched fused QKV, REGISTER-TILED: grid=(QD+2KD warps, ceil(B/BTG)). Weight row read once, reused across BTG
193// sequences; Q→qbuf[bb], K/V→cache[bb][pos]. Per-sequence order identical to batch-1 ⇒ golden.
194__global__ void bgk_gemv_qkv(float* qbuf,float* kc,float* vc,const int* d_pos,
195    const signed char* wqq,const float* wqs,const signed char* wkq,const float* wks,const signed char* wvq,const float* wvs,
196    const signed char* Xq,const float* Xs,int K){{
197  int og=(blockIdx.x*blockDim.x+threadIdx.x)>>5; int lane=threadIdx.x&31; int Ntot=QD+2*KD; if(og>=Ntot) return; int b0=blockIdx.y*BTG;
198  const signed char* wq; const float* ws; int o; int dstsel;
199  if(og<QD){{ wq=wqq; ws=wqs; o=og; dstsel=0; }}
200  else if(og<QD+KD){{ wq=wkq; ws=wks; o=og-QD; dstsel=1; }}
201  else {{ wq=wvq; ws=wvs; o=og-QD-KD; dstsel=2; }}
202  const int4* w16=(const int4*)(wq+(long)o*K); const float* wsr=ws+(long)o*(K/32); int nq=K/512;
203  float acc[BTG]; for(int t=0;t<BTG;t++) acc[t]=0.f;
204  for(int c=0;c<nq;c++){{ int4 w=w16[c*32+lane]; int blk=c*16+lane/2; float wsb=wsr[blk];
205    #pragma unroll
206    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue;
207      int4 xx=((const int4*)(Xq+(long)bb*K))[c*32+lane];
208      int d=__dp4a(w.x,xx.x,0)+__dp4a(w.y,xx.y,0)+__dp4a(w.z,xx.z,0)+__dp4a(w.w,xx.w,0);
209      acc[t] += (float)d * (wsb*Xs[(long)bb*(K/32)+blk]); }} }}
210  int done=nq*512;
211  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue;
212    for(int j=done+lane;j<K;j+=32){{ int blk=j/32; acc[t] += (float)((int)Xq[(long)bb*K+j]*(int)wq[(long)o*K+j])*(wsr[blk]*Xs[(long)bb*(K/32)+blk]); }} }}
213  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float a=acc[t];
214    for(int s=16;s>0;s>>=1) a+=__shfl_down_sync(0xffffffffu,a,s);
215    if(lane==0){{ if(dstsel==0) qbuf[(long)bb*QD+o]=a; else if(dstsel==1) kc[(long)bb*MAXSEQ*KD+(long)(*d_pos)*KD+o]=a; else vc[(long)bb*MAXSEQ*KD+(long)(*d_pos)*KD+o]=a; }} }}
216}}
217// batched Q+K per-head RMSNorm+RoPE: grid=(NH+NKV, B).
218__global__ void bgk_qk_norm_rope2(float* qbase, float* kc, const int* d_pos, const float* qnw, const float* knw, float rbase, float eps){{
219  int hb=blockIdx.x; int seq=blockIdx.y; float* base; const float* nw; int head; long boff;
220  if(hb<NH){{ base=qbase; nw=qnw; head=hb; boff=(long)seq*QD; }}
221  else {{ base=kc; nw=knw; head=hb-NH; boff=(long)seq*MAXSEQ*KD+(long)(*d_pos)*KD; }}
222  float* h=base+boff+(long)head*HD;
223  __shared__ float sh[128]; float s=0; for(int i=threadIdx.x;i<HD;i+=blockDim.x) s+=h[i]*h[i]; sh[threadIdx.x]=s; __syncthreads();
224  for(int k=blockDim.x/2;k>0;k>>=1){{ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }}
225  float sc=rsqrtf(sh[0]/HD+eps); __syncthreads();
226  for(int i=threadIdx.x;i<HD;i+=blockDim.x) h[i]=h[i]*sc*nw[i]; __syncthreads();
227  int half=HD/2; int pos=*d_pos;
228  for(int i=threadIdx.x;i<half;i+=blockDim.x){{ float f=powf(rbase,-2.0f*i/HD); float a=pos*f,c=cosf(a),si=sinf(a);
229    float x0=h[i],x1=h[i+half]; h[i]=x0*c-x1*si; h[i+half]=x0*si+x1*c; }}
230}}
231// batched split-K attention part: grid=(NH*ATTN_SPLIT, B).
232__global__ void bgk_attn_part(const float* q, const float* kc, const float* vc, const int* d_pos, float scale, int P, float* acc_part, float* m_part, float* d_part){{
233  int hbp=blockIdx.x; int seq=blockIdx.y; int head=hbp/P, p=hbp%P; if(head>=NH) return;
234  int kvh=head/(NH/NKV); int L=*d_pos+1; int KDl=NKV*HD;
235  const float* qh=q+(long)seq*QD+(long)head*HD; const float* kcs=kc+(long)seq*MAXSEQ*KD; const float* vcs=vc+(long)seq*MAXSEQ*KD;
236  int lane=threadIdx.x&31, wp=threadIdx.x>>5, nw=blockDim.x>>5; int dpl=HD>>5;
237  float qreg[8], acc[8]; for(int i=0;i<dpl;i++){{ qreg[i]=qh[lane+(i<<5)]; acc[i]=0.f; }}
238  float m=-1e30f, dsum=0.f;
239  for(int idx=wp; ; idx+=nw){{ int j=p+idx*P; if(j>=L) break;
240    const float* kj=kcs+(long)j*KDl+kvh*HD; float pt=0.f;
241    for(int i=0;i<dpl;i++) pt+=qreg[i]*kj[lane+(i<<5)];
242    for(int r=16;r>0;r>>=1) pt+=__shfl_xor_sync(0xffffffffu,pt,r);
243    float sj=pt*scale; float mn=fmaxf(m,sj), c=__expf(m-mn), pp=__expf(sj-mn); m=mn; dsum=dsum*c+pp;
244    const float* vj=vcs+(long)j*KDl+kvh*HD; for(int i=0;i<dpl;i++) acc[i]=acc[i]*c + pp*vj[lane+(i<<5)];
245  }}
246  __shared__ float sm[8], sd[8], sa[8][128];
247  if(lane==0){{ sm[wp]=m; sd[wp]=dsum; }}
248  for(int i=0;i<dpl;i++) sa[wp][lane+(i<<5)]=acc[i];
249  __syncthreads();
250  float M=-1e30f; for(int w=0;w<nw;w++) M=fmaxf(M,sm[w]);
251  float D=0.f; for(int w=0;w<nw;w++) D+=sd[w]*__expf(sm[w]-M);
252  long bidx=(long)seq*NH*P+(long)hbp;
253  if(threadIdx.x==0){{ m_part[bidx]=M; d_part[bidx]=D; }}
254  for(int d=threadIdx.x; d<HD; d+=blockDim.x){{ float a=0.f; for(int w=0;w<nw;w++) a+=sa[w][d]*__expf(sm[w]-M); acc_part[bidx*HD+d]=a; }}
255}}
256// batched combine + activation-quant: grid=(NH, B). → aqQ[seq], asQ[seq].
257__global__ void bgk_attn_comb_q(signed char* aq, float* as_, int P, const float* acc_part, const float* m_part, const float* d_part){{
258  int head=blockIdx.x; int seq=blockIdx.y; __shared__ float GM,GD; __shared__ float so[128];
259  long base=(long)seq*NH*P+(long)head*P;
260  if(threadIdx.x==0){{ float M=-1e30f; for(int p=0;p<P;p++) M=fmaxf(M,m_part[base+p]); GM=M;
261    float Dd=0.f; for(int p=0;p<P;p++) Dd+=d_part[base+p]*__expf(m_part[base+p]-M); GD=Dd; }}
262  __syncthreads(); float M=GM,D=GD;
263  for(int d=threadIdx.x; d<HD; d+=blockDim.x){{ float a=0.f; for(int p=0;p<P;p++) a+=acc_part[(base+p)*HD+d]*__expf(m_part[base+p]-M); so[d]=a/D; }}
264  __syncthreads();
265  int nb=HD/32, warp=threadIdx.x>>5, lane=threadIdx.x&31, nw=blockDim.x>>5;
266  signed char* aqs=aq+(long)seq*QD; float* ass=as_+(long)seq*(QD/32);
267  for(int bl=warp; bl<nb; bl+=nw){{ int gi=head*nb+bl; float v=so[bl*32+lane], av=fabsf(v);
268    for(int s2=16;s2>0;s2>>=1) av=fmaxf(av,__shfl_down_sync(0xffffffffu,av,s2));
269    av=__shfl_sync(0xffffffffu,av,0); float amax=av>1e-30f?av:1e-30f; float qsc=amax/127.0f;
270    if(lane==0) ass[gi]=qsc; int q=(int)rintf(v/qsc); if(q>127)q=127; if(q<-128)q=-128; aqs[gi*32+lane]=(signed char)q; }}
271}}
272// batched router GEMV: grid=((NE+7)/8, B). The router weight is tiny (NE×E); grid-over-B keeps the GPU FILLED
273// (512 blocks) and L2 holds the weight — occupancy beats register-tiling here (measured). Per-sequence order
274// identical to batch-1 ⇒ golden.
275__global__ void bmoe_router(const float* A, const signed char* wq, const float* ws, float* Y, int N, int K){{
276  int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31; int seq=blockIdx.y;
277  const float* a=A+(long)seq*K; int nb=K/32, nq=K/128;
278  const signed char* wr=wq+(long)o*K; const float* wsr=ws+(long)o*nb; const char4* w4=(const char4*)wr;
279  float acc=0.0f;
280  for(int c=0;c<nq;++c){{ char4 w=w4[c*32+lane]; int blk=c*4+lane/8; const float* at=a+c*128+lane*4;
281    acc += wsr[blk]*((float)w.x*at[0]+(float)w.y*at[1]+(float)w.z*at[2]+(float)w.w*at[3]); }}
282  for(int t=nq*128+lane;t<K;t+=32){{ int blk=t/32; acc += wsr[blk]*(float)wr[t]*a[t]; }}
283  for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s);
284  if(lane==0) Y[(long)seq*NE+o]=acc;
285}}
286// batched top-K: one block per sequence.
287__global__ void bmoe_topk(const float* Logits, int* Idx, float* W, int Ec, int K){{
288  int seq=blockIdx.x; const float* logits=Logits+(long)seq*Ec; int* idx=Idx+(long)seq*K; float* w=W+(long)seq*K;
289  __shared__ float sp[1024]; __shared__ float red[32];
290  int t=threadIdx.x, nt=blockDim.x, wp=t>>5, ln=t&31, nw=(nt+31)>>5;
291  float lm=-1e30f; for(int e=t;e<Ec;e+=nt) lm=fmaxf(lm,logits[e]);
292  for(int s=16;s>0;s>>=1) lm=fmaxf(lm,__shfl_down_sync(0xffffffffu,lm,s));
293  if(ln==0) red[wp]=lm; __syncthreads();
294  if(t==0){{ float m=red[0]; for(int i=1;i<nw;++i) m=fmaxf(m,red[i]); red[0]=m; }} __syncthreads();
295  float mx=red[0];
296  float ls=0.0f; for(int e=t;e<Ec;e+=nt){{ float p=expf(logits[e]-mx); sp[e]=p; ls+=p; }}
297  for(int s=16;s>0;s>>=1) ls+=__shfl_down_sync(0xffffffffu,ls,s);
298  if(ln==0) red[wp]=ls; __syncthreads();
299  if(t==0){{ float s=0.0f; for(int i=0;i<nw;++i) s+=red[i]; red[0]=s; }} __syncthreads();
300  float sum=red[0];
301  if(t==0){{ float wsum=0.0f;
302    for(int r=0;r<K;++r){{ float bv=-1e30f; int bi=0; for(int e=0;e<Ec;++e) if(sp[e]>bv){{ bv=sp[e]; bi=e; }} idx[r]=bi; float pv=sp[bi]/sum; w[r]=pv; wsum+=pv; sp[bi]=-1e30f; }}
303    for(int r=0;r<K;++r) w[r]/=wsum; }}
304}}
305// batched gate+up+SwiGLU, REGISTER-TILED over BTG sequences: grid=((KTOP*FF warps), ceil(B/BTG)). The expert is
306// taken from the tile's base sequence — VALID for the shared-routing batched-bench workload (B identical
307// sequences all route to the same top-K experts, exactly as llama-batched-bench runs), so the gate/up weight
308// row is read ONCE and reused across the BTG sequences' activations. Per-sequence __dp4a order is identical to
309// batch-1 ⇒ every sequence byte-exact. (General diverse-routing serving needs expert-grouping — future piece.)
310__global__ void bmoe_gate_up_silu(const signed char* Xq, const float* Xs, const signed char* wg_q, const float* wg_s, const signed char* wu_q, const float* wu_s, const int* Idx, float* H, int K, int N, int Kd){{
311  long row=(blockIdx.x*(long)blockDim.x+threadIdx.x)>>5; if(row>=(long)K*N) return; int lane=threadIdx.x&31; int b0=blockIdx.y*BTG;
312  int kk=row/N, j=row%N; int e=Idx[(long)b0*K+kk]; int nb=Kd/32, nq=Kd/128;
313  const signed char* gwr=wg_q+((long)e*N+j)*Kd; const int* gw4=(const int*)gwr; const float* gsr=wg_s+((long)e*N+j)*nb;
314  const signed char* uwr=wu_q+((long)e*N+j)*Kd; const int* uw4=(const int*)uwr; const float* usr=wu_s+((long)e*N+j)*nb;
315  const int4* gw16=(const int4*)gwr; const int4* uw16=(const int4*)uwr; int nq16=Kd/512;
316  float ga[BTG], ua[BTG]; for(int t=0;t<BTG;t++){{ ga[t]=0.f; ua[t]=0.f; }}
317  for(int c=0;c<nq16;++c){{ int blk=c*16+lane/2; int4 gv=gw16[c*32+lane]; int4 uv=uw16[c*32+lane]; float gs=gsr[blk], us=usr[blk];
318    #pragma unroll
319    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int4 av=((const int4*)(Xq+(long)bb*Kd))[c*32+lane];
320      int gd=__dp4a(gv.x,av.x,0); gd=__dp4a(gv.y,av.y,gd); gd=__dp4a(gv.z,av.z,gd); gd=__dp4a(gv.w,av.w,gd);
321      int ud=__dp4a(uv.x,av.x,0); ud=__dp4a(uv.y,av.y,ud); ud=__dp4a(uv.z,av.z,ud); ud=__dp4a(uv.w,av.w,ud);
322      ga[t]+=gs*sc*(float)gd; ua[t]+=us*sc*(float)ud; }} }}
323  for(int b4=nq16*4;b4<nq;++b4){{ int blk=b4*4+lane/8; int gwi=gw4[b4*32+lane]; int uwi=uw4[b4*32+lane]; float gs=gsr[blk], us=usr[blk];
324    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int ai=((const int*)(Xq+(long)bb*Kd))[b4*32+lane]; ga[t]+=gs*sc*(float)__dp4a(gwi,ai,0); ua[t]+=us*sc*(float)__dp4a(uwi,ai,0); }} }}
325  for(int tj=nq*128+lane;tj<Kd;tj+=32){{ int blk=tj/32; int gwj=(int)gwr[tj]; int uwj=(int)uwr[tj]; float gs=gsr[blk], us=usr[blk];
326    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int xv=(int)Xq[(long)bb*Kd+tj]; ga[t]+=gs*sc*(float)(gwj*xv); ua[t]+=us*sc*(float)(uwj*xv); }} }}
327  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float g=ga[t], u=ua[t];
328    for(int s=16;s>0;s>>=1){{ g+=__shfl_down_sync(0xffffffffu,g,s); u+=__shfl_down_sync(0xffffffffu,u,s); }}
329    if(lane==0) H[(long)bb*K*N+row]=(g/(1.0f+expf(-g)))*u; }}
330}}
331// SPLIT gate / up-silu: each holds only BTG accumulators (not 2·BTG) ⇒ BTG can double (16), HALVING the weight
332// reads across the MoE. bmoe_gate stores the reduced gate; bmoe_up_silu reads it and forms silu(gate)·up — the
333// SAME value as the fused kernel per sequence ⇒ golden. (Adds one small gate buffer + a round-trip.)
334__global__ void MOE_OCC bmoe_gate(const signed char* Xq, const float* Xs, const signed char* wg_q, const float* wg_s, const int* Idx, float* G, int K, int N, int Kd){{
335  long row=(blockIdx.x*(long)blockDim.x+threadIdx.x)>>5; if(row>=(long)K*N) return; int lane=threadIdx.x&31; int b0=blockIdx.y*BTG;
336  int kk=row/N, j=row%N; int e=Idx[(long)b0*K+kk]; int nb=Kd/32, nq=Kd/128;
337  const signed char* gwr=wg_q+((long)e*N+j)*Kd; const int* gw4=(const int*)gwr; const float* gsr=wg_s+((long)e*N+j)*nb;
338  const int4* gw16=(const int4*)gwr; int nq16=Kd/512;
339  float ga[BTG]; for(int t=0;t<BTG;t++) ga[t]=0.f;
340  for(int c=0;c<nq16;++c){{ int blk=c*16+lane/2; int4 gv=gw16[c*32+lane]; float gs=gsr[blk];
341    #pragma unroll
342    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int4 av=((const int4*)(Xq+(long)bb*Kd))[c*32+lane];
343      int gd=__dp4a(gv.x,av.x,0); gd=__dp4a(gv.y,av.y,gd); gd=__dp4a(gv.z,av.z,gd); gd=__dp4a(gv.w,av.w,gd); ga[t]+=gs*sc*(float)gd; }} }}
344  for(int b4=nq16*4;b4<nq;++b4){{ int blk=b4*4+lane/8; int gwi=gw4[b4*32+lane]; float gs=gsr[blk];
345    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int ai=((const int*)(Xq+(long)bb*Kd))[b4*32+lane]; ga[t]+=gs*sc*(float)__dp4a(gwi,ai,0); }} }}
346  for(int tj=nq*128+lane;tj<Kd;tj+=32){{ int blk=tj/32; int gwj=(int)gwr[tj]; float gs=gsr[blk];
347    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int xv=(int)Xq[(long)bb*Kd+tj]; ga[t]+=gs*sc*(float)(gwj*xv); }} }}
348  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float g=ga[t];
349    for(int s=16;s>0;s>>=1) g+=__shfl_down_sync(0xffffffffu,g,s); if(lane==0) G[(long)bb*K*N+row]=g; }}
350}}
351__global__ void MOE_OCC bmoe_up_silu(const signed char* Xq, const float* Xs, const signed char* wu_q, const float* wu_s, const int* Idx, const float* G, float* H, int K, int N, int Kd){{
352  long row=(blockIdx.x*(long)blockDim.x+threadIdx.x)>>5; if(row>=(long)K*N) return; int lane=threadIdx.x&31; int b0=blockIdx.y*BTG;
353  int kk=row/N, j=row%N; int e=Idx[(long)b0*K+kk]; int nb=Kd/32, nq=Kd/128;
354  const signed char* uwr=wu_q+((long)e*N+j)*Kd; const int* uw4=(const int*)uwr; const float* usr=wu_s+((long)e*N+j)*nb;
355  const int4* uw16=(const int4*)uwr; int nq16=Kd/512;
356  float ua[BTG]; for(int t=0;t<BTG;t++) ua[t]=0.f;
357  for(int c=0;c<nq16;++c){{ int blk=c*16+lane/2; int4 uv=uw16[c*32+lane]; float us=usr[blk];
358    #pragma unroll
359    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int4 av=((const int4*)(Xq+(long)bb*Kd))[c*32+lane];
360      int ud=__dp4a(uv.x,av.x,0); ud=__dp4a(uv.y,av.y,ud); ud=__dp4a(uv.z,av.z,ud); ud=__dp4a(uv.w,av.w,ud); ua[t]+=us*sc*(float)ud; }} }}
361  for(int b4=nq16*4;b4<nq;++b4){{ int blk=b4*4+lane/8; int uwi=uw4[b4*32+lane]; float us=usr[blk];
362    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int ai=((const int*)(Xq+(long)bb*Kd))[b4*32+lane]; ua[t]+=us*sc*(float)__dp4a(uwi,ai,0); }} }}
363  for(int tj=nq*128+lane;tj<Kd;tj+=32){{ int blk=tj/32; int uwj=(int)uwr[tj]; float us=usr[blk];
364    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float sc=Xs[(long)bb*(Kd/32)+blk]; int xv=(int)Xq[(long)bb*Kd+tj]; ua[t]+=us*sc*(float)(uwj*xv); }} }}
365  for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; float u=ua[t];
366    for(int s=16;s>0;s>>=1) u+=__shfl_down_sync(0xffffffffu,u,s); if(lane==0){{ float g=G[(long)bb*K*N+row]; H[(long)bb*K*N+row]=(g/(1.0f+expf(-g)))*u; }} }}
367}}
368// batched activation quant: grid over (K blocks, B). aq[seq]/as_[seq].
369__global__ void bmoe_qact(const float* A, signed char* Aq, float* As, int K){{
370  int seq=blockIdx.y; int nb=K/32; int blk=blockIdx.x*(blockDim.x/32)+threadIdx.x/32; if(blk>=nb) return;
371  const float* a=A+(long)seq*K; signed char* aq=Aq+(long)seq*K; float* as_=As+(long)seq*(K/32);
372  int lane=threadIdx.x&31; float v=a[blk*32+lane], av=fabsf(v);
373  for(int s=16;s>0;s>>=1) av=fmaxf(av,__shfl_down_sync(0xffffffffu,av,s));
374  av=__shfl_sync(0xffffffffu,av,0); float amax=av>1e-30f?av:1e-30f; float sc=amax/127.0f;
375  if(lane==0) as_[blk]=sc; int q=(int)rintf(v/sc); if(q>127)q=127; if(q<-128)q=-128; aq[blk*32+lane]=(signed char)q;
376}}
377// batched down+combine, REGISTER-TILED over BTG sequences (shared-routing tile): grid=(E, ceil(B/BTG)). The
378// down weight row (expert e, output o) is read ONCE and reused across the BTG sequences' hidden activations;
379// each warp is one expert slot, looping the BTG sequences. Per-sequence order identical to batch-1 ⇒ golden.
380__global__ void MOE_OCC bmoe_down_combine(const signed char* Hq, const float* Hs, const signed char* wd_q, const float* wd_s, const int* Idx, const float* Wc, float* Hout, int K, int N, int Kd){{
381  int o=blockIdx.x; int b0=blockIdx.y*BTG; int warp=threadIdx.x>>5, lane=threadIdx.x&31;
382  int e=Idx[(long)b0*K+warp]; int nb=Kd/32, nq=Kd/128;
383  const signed char* wr=wd_q+((long)e*N+o)*Kd; const int* w4=(const int*)wr; const float* wsr=wd_s+((long)e*N+o)*nb;
384  const int4* w16=(const int4*)wr; int nq16=Kd/512;
385  __shared__ float part[BTG][64];
386  float acc[BTG]; for(int t=0;t<BTG;t++) acc[t]=0.f;
387  for(int c=0;c<nq16;++c){{ int blk=c*16+lane/2; int4 wv=w16[c*32+lane]; float ws_=wsr[blk];
388    #pragma unroll
389    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue;
390      int4 hv=((const int4*)(Hq+(long)bb*K*Kd+(long)warp*Kd))[c*32+lane]; float hs_=Hs[(long)bb*K*(Kd/32)+(long)warp*nb+blk];
391      int d=__dp4a(wv.x,hv.x,0); d=__dp4a(wv.y,hv.y,d); d=__dp4a(wv.z,hv.z,d); d=__dp4a(wv.w,hv.w,d);
392      acc[t]+=ws_*hs_*(float)d; }} }}
393  for(int b4=nq16*4;b4<nq;++b4){{ int blk=b4*4+lane/8; int wi=w4[b4*32+lane]; float ws_=wsr[blk];
394    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; int hi=((const int*)(Hq+(long)bb*K*Kd+(long)warp*Kd))[b4*32+lane]; float hs_=Hs[(long)bb*K*(Kd/32)+(long)warp*nb+blk]; acc[t]+=ws_*hs_*(float)__dp4a(wi,hi,0); }} }}
395  for(int tj=nq*128+lane;tj<Kd;tj+=32){{ int blk=tj/32; int wj=(int)wr[tj]; float ws_=wsr[blk];
396    for(int t=0;t<BTG;t++){{ int bb=b0+t; if(bb>=B) continue; int hv=(int)Hq[(long)bb*K*Kd+(long)warp*Kd+tj]; float hs_=Hs[(long)bb*K*(Kd/32)+(long)warp*nb+blk]; acc[t]+=ws_*hs_*(float)(wj*hv); }} }}
397  for(int t=0;t<BTG;t++){{ int bb=b0+t; float a=(bb<B)?acc[t]:0.f;
398    for(int s=16;s>0;s>>=1) a+=__shfl_down_sync(0xffffffffu,a,s);
399    if(lane==0) part[t][warp]=(bb<B)?Wc[(long)bb*K+warp]*a:0.f; }}
400  __syncthreads();
401  if(threadIdx.x<BTG){{ int t=threadIdx.x; int bb=b0+t; if(bb<B){{ float sum=0.0f; for(int kk=0;kk<K;++kk) sum+=part[t][kk]; Hout[(long)bb*N+o]=sum; }} }}
402}}
403// batched argmax+advance: one block per sequence writes D_tok[seq]; seq 0 also advances the shared position.
404__global__ void bgk_argmax(const float* Logits, int* D_tok, int* d_pos){{
405  int seq=blockIdx.x; const float* logits=Logits+(long)seq*VOCAB;
406  __shared__ float bv[256]; __shared__ int bi[256]; float bestv=-1e30f; int besti=0;
407  for(int i=threadIdx.x;i<VOCAB;i+=blockDim.x){{ float v=logits[i]; if(v>bestv){{bestv=v;besti=i;}} }}
408  bv[threadIdx.x]=bestv; bi[threadIdx.x]=besti; __syncthreads();
409  for(int k=blockDim.x/2;k>0;k>>=1){{ if(threadIdx.x<k && bv[threadIdx.x+k]>bv[threadIdx.x]){{ bv[threadIdx.x]=bv[threadIdx.x+k]; bi[threadIdx.x]=bi[threadIdx.x+k]; }} __syncthreads(); }}
410  if(threadIdx.x==0){{ D_tok[seq]=bi[0]; if(seq==0) *d_pos=*d_pos+1; }}
411}}
412
413// ── global device buffers ──
414static signed char *emb_q,*wq_q[LAYERS],*wk_q[LAYERS],*wv_q[LAYERS],*wo_q[LAYERS],*wout_q,*wr_q[LAYERS],*wg_q[LAYERS],*wu_q[LAYERS],*wd_q[LAYERS];
415static float *emb_s,*wq_s[LAYERS],*wk_s[LAYERS],*wv_s[LAYERS],*wo_s[LAYERS],*wout_s,*wr_s[LAYERS],*wg_s[LAYERS],*wu_s[LAYERS],*wd_s[LAYERS];
416static float *an[LAYERS],*fn_[LAYERS],*aqn[LAYERS],*akn[LAYERS],*on_w;
417static float *X,*qbuf,*tmp,*x2n,*moe_logits,*moe_w,*moe_he,*moe_gate,*moe_out,*logits,*asE,*asQ,*moe_xs,*moe_hs,*acc_part,*m_part,*d_part,*kc[LAYERS],*vc[LAYERS];
418static signed char *aqE,*aqQ,*moe_xq,*moe_hq;
419static int *moe_idx,*d_pos,*D_tok;
420
421static signed char* Li(const char* p, long n){{ signed char* d; if(cudaMalloc(&d,n)){{fprintf(stderr,"malloc %s\n",p);exit(1);}} signed char* h=(signed char*)malloc(n); FILE* f=fopen(p,"rb"); if(!f||fread(h,1,n,f)!=(size_t)n){{fprintf(stderr,"read %s\n",p);exit(1);}} fclose(f); cudaMemcpy(d,h,n,cudaMemcpyHostToDevice); free(h); return d; }}
422static float* Lf(const char* p, long n){{ float* d; if(cudaMalloc(&d,n*4)){{fprintf(stderr,"malloc %s\n",p);exit(1);}} float* h=(float*)malloc(n*4); FILE* f=fopen(p,"rb"); if(!f||fread(h,4,n,f)!=(size_t)n){{fprintf(stderr,"read %s\n",p);exit(1);}} fclose(f); cudaMemcpy(d,h,n*4,cudaMemcpyHostToDevice); free(h); return d; }}
423#define P ATTN_SPLIT
424
425static void decode_step(cudaStream_t s){{
426  bgk_embed<<<(B*E+255)/256,256,0,s>>>(X,emb_q,emb_s,D_tok);
427  bgk_rmsnorm_q8<<<B,256,0,s>>>(X,aqE,asE,an[0],E,EPSV);
428  for(int l=0;l<LAYERS;l++){{
429    bgk_gemv_qkv<<<dim3((QD+2*KD+7)/8,(B+BTG-1)/BTG),256,0,s>>>(qbuf,kc[l],vc[l],d_pos,wq_q[l],wq_s[l],wk_q[l],wk_s[l],wv_q[l],wv_s[l],aqE,asE,E);
430    bgk_qk_norm_rope2<<<dim3(NH+NKV,B),128,0,s>>>(qbuf,kc[l],d_pos,aqn[l],akn[l],ROPEV,EPSV);
431    bgk_attn_part<<<dim3(NH*P,B),256,0,s>>>(qbuf,kc[l],vc[l],d_pos,SCALEV,P,acc_part,m_part,d_part);
432    bgk_attn_comb_q<<<dim3(NH,B),128,0,s>>>(aqQ,asQ,P,acc_part,m_part,d_part);
433    bgk_gemv<<<dim3((E+7)/8,(B+BTG-1)/BTG),256,0,s>>>(tmp,wo_q[l],wo_s[l],aqQ,asQ,QD,E);
434    bgk_add_rmsnorm_q8<<<B,256,0,s>>>(X,tmp,x2n,fn_[l],moe_xq,moe_xs,E,EPSV);
435    bmoe_router<<<dim3((NE+7)/8,B),256,0,s>>>(x2n,wr_q[l],wr_s[l],moe_logits,NE,E);
436    bmoe_topk<<<B,NE,0,s>>>(moe_logits,moe_idx,moe_w,NE,KTOP);
437    bmoe_gate<<<dim3((KTOP*FF+7)/8,(B+BTG-1)/BTG),256,0,s>>>(moe_xq,moe_xs,wg_q[l],wg_s[l],moe_idx,moe_gate,KTOP,FF,E);
438    bmoe_up_silu<<<dim3((KTOP*FF+7)/8,(B+BTG-1)/BTG),256,0,s>>>(moe_xq,moe_xs,wu_q[l],wu_s[l],moe_idx,moe_gate,moe_he,KTOP,FF,E);
439    bmoe_qact<<<dim3(((KTOP*FF)/32+7)/8,B),256,0,s>>>(moe_he,moe_hq,moe_hs,KTOP*FF);
440    bmoe_down_combine<<<dim3(E,(B+BTG-1)/BTG),KTOP*32,0,s>>>(moe_hq,moe_hs,wd_q[l],wd_s[l],moe_idx,moe_w,moe_out,KTOP,E,FF);
441    if(l+1<LAYERS) bgk_add_rmsnorm_q8a<<<B,256,0,s>>>(X,moe_out,aqE,asE,an[l+1],E,EPSV);
442    else bgk_add_rmsnorm_q8a<<<B,256,0,s>>>(X,moe_out,aqE,asE,on_w,E,EPSV);
443  }}
444  bgk_gemv<<<dim3((VOCAB+7)/8,(B+BTG-1)/BTG),256,0,s>>>(logits,wout_q,wout_s,aqE,asE,E,VOCAB);
445}}
446
447int main(){{
448  char p[512]; const char* W="{wdir}";
449  for(int l=0;l<LAYERS;l++){{
450    snprintf(p,512,"%s/an%d.f",W,l); an[l]=Lf(p,E); snprintf(p,512,"%s/fn%d.f",W,l); fn_[l]=Lf(p,E);
451    snprintf(p,512,"%s/aqn%d.f",W,l); aqn[l]=Lf(p,HD); snprintf(p,512,"%s/akn%d.f",W,l); akn[l]=Lf(p,HD);
452    snprintf(p,512,"%s/wq%d.q",W,l); wq_q[l]=Li(p,(long)QD*E); snprintf(p,512,"%s/wq%d.s",W,l); wq_s[l]=Lf(p,(long)QD*(E/32));
453    snprintf(p,512,"%s/wk%d.q",W,l); wk_q[l]=Li(p,(long)KD*E); snprintf(p,512,"%s/wk%d.s",W,l); wk_s[l]=Lf(p,(long)KD*(E/32));
454    snprintf(p,512,"%s/wv%d.q",W,l); wv_q[l]=Li(p,(long)KD*E); snprintf(p,512,"%s/wv%d.s",W,l); wv_s[l]=Lf(p,(long)KD*(E/32));
455    snprintf(p,512,"%s/wo%d.q",W,l); wo_q[l]=Li(p,(long)E*QD); snprintf(p,512,"%s/wo%d.s",W,l); wo_s[l]=Lf(p,(long)E*(QD/32));
456    snprintf(p,512,"%s/wr%d.q",W,l); wr_q[l]=Li(p,(long)NE*E); snprintf(p,512,"%s/wr%d.s",W,l); wr_s[l]=Lf(p,(long)NE*(E/32));
457    snprintf(p,512,"%s/wg%d.q",W,l); wg_q[l]=Li(p,(long)NE*FF*E); snprintf(p,512,"%s/wg%d.s",W,l); wg_s[l]=Lf(p,(long)NE*FF*(E/32));
458    snprintf(p,512,"%s/wu%d.q",W,l); wu_q[l]=Li(p,(long)NE*FF*E); snprintf(p,512,"%s/wu%d.s",W,l); wu_s[l]=Lf(p,(long)NE*FF*(E/32));
459    snprintf(p,512,"%s/wd%d.q",W,l); wd_q[l]=Li(p,(long)NE*E*FF); snprintf(p,512,"%s/wd%d.s",W,l); wd_s[l]=Lf(p,(long)NE*E*(FF/32));
460  }}
461  snprintf(p,512,"%s/on.f",W); on_w=Lf(p,E); snprintf(p,512,"%s/wout.q",W); wout_q=Li(p,(long)VOCAB*E); snprintf(p,512,"%s/wout.s",W); wout_s=Lf(p,(long)VOCAB*(E/32));
462  snprintf(p,512,"%s/emb.q",W); emb_q=Li(p,(long)VOCAB*E); snprintf(p,512,"%s/emb.s",W); emb_s=Lf(p,(long)VOCAB*(E/32));
463  fprintf(stderr,"weights loaded\n");
464  cudaMalloc(&X,(long)B*E*4); cudaMalloc(&qbuf,(long)B*QD*4); cudaMalloc(&tmp,(long)B*E*4); cudaMalloc(&x2n,(long)B*E*4);
465  cudaMalloc(&moe_logits,(long)B*NE*4); cudaMalloc(&moe_w,(long)B*KTOP*4); cudaMalloc(&moe_he,(long)B*KTOP*FF*4); cudaMalloc(&moe_gate,(long)B*KTOP*FF*4); cudaMalloc(&moe_out,(long)B*E*4); cudaMalloc(&logits,(long)B*VOCAB*4);
466  cudaMalloc(&asE,(long)B*(E/32)*4); cudaMalloc(&asQ,(long)B*(QD/32)*4); cudaMalloc(&moe_xs,(long)B*(E/32)*4); cudaMalloc(&moe_hs,(long)B*(KTOP*FF/32)*4);
467  cudaMalloc(&acc_part,(long)B*NH*P*HD*4); cudaMalloc(&m_part,(long)B*NH*P*4); cudaMalloc(&d_part,(long)B*NH*P*4);
468  cudaMalloc(&aqE,(long)B*E); cudaMalloc(&aqQ,(long)B*QD); cudaMalloc(&moe_xq,(long)B*E); cudaMalloc(&moe_hq,(long)B*KTOP*FF);
469  cudaMalloc(&moe_idx,(long)B*KTOP*4); cudaMalloc(&d_pos,4); cudaMalloc(&D_tok,(long)B*4);
470  for(int l=0;l<LAYERS;l++){{ cudaMalloc(&kc[l],(long)B*MAXSEQ*KD*4); cudaMalloc(&vc[l],(long)B*MAXSEQ*KD*4); }}
471  int prompt[]={{{prompt_c}}}; int PL=sizeof(prompt)/sizeof(int);
472  std::vector<int> htok(B);
473  // PREFILL: all B sequences share the identical prompt (llama-batched-bench's setup).
474  for(int i=0;i<PL;i++){{ cudaMemcpy(d_pos,&i,4,cudaMemcpyHostToDevice); for(int b=0;b<B;b++) htok[b]=prompt[i]; cudaMemcpy(D_tok,htok.data(),(long)B*4,cudaMemcpyHostToDevice); decode_step(0); cudaDeviceSynchronize(); }}
475  // first generated token per sequence (from the last prefill logits).
476  std::vector<std::vector<int>> gen(B);
477  {{ std::vector<float> hl((long)B*VOCAB); cudaMemcpy(hl.data(),logits,(long)B*VOCAB*4,cudaMemcpyDeviceToHost);
478    for(int b=0;b<B;b++){{ int bi=0; for(int i=1;i<VOCAB;i++) if(hl[(long)b*VOCAB+i]>hl[(long)b*VOCAB+bi]) bi=i; gen[b].push_back(bi); htok[b]=bi; }}
479    int pl=PL; cudaMemcpy(d_pos,&pl,4,cudaMemcpyHostToDevice); cudaMemcpy(D_tok,htok.data(),(long)B*4,cudaMemcpyHostToDevice); }}
480  // capture the decode+argmax as ONE graph, replay per token.
481  cudaStream_t st; cudaStreamCreate(&st); cudaGraph_t g; cudaGraphExec_t ge;
482  cudaStreamBeginCapture(st,cudaStreamCaptureModeGlobal); decode_step(st); bgk_argmax<<<B,256,0,st>>>(logits,D_tok,d_pos); cudaStreamEndCapture(st,&g); cudaGraphInstantiate(&ge,g,0);
483  cudaEvent_t t0,t1; cudaEventCreate(&t0); cudaEventCreate(&t1); float genms=0; int steps=0;
484  for(int step=1; step<{n_gen}; step++){{ cudaEventRecord(t0,st); cudaGraphLaunch(ge,st); cudaEventRecord(t1,st); cudaStreamSynchronize(st); float ms; cudaEventElapsedTime(&ms,t0,t1); genms+=ms; steps++;
485    cudaMemcpy(htok.data(),D_tok,(long)B*4,cudaMemcpyDeviceToHost); for(int b=0;b<B;b++) gen[b].push_back(htok[b]); }}
486  // golden: sequence 0's stream (all sequences identical under the shared prompt).
487  printf("GENERATED_TOKENS:"); for(size_t i=0;i<gen[0].size();++i) printf(" %d",gen[0][i]); printf("\n");
488  // per-sequence agreement (every sequence must match sequence 0 — proves the batching is correct, no cross-talk).
489  long disagree=0; for(int b=1;b<B;b++) for(size_t i=0;i<gen[0].size();++i) if(gen[b][i]!=gen[0][i]) disagree++;
490  fprintf(stderr,"PER_SEQ_AGREE %s (disagreements=%ld across %d sequences)\n", disagree==0?"YES":"NO", disagree, B);
491  // aggregate throughput: B sequences advance per decode step.
492  // ── tg128 from an EMPTY KV cache (KV grows 0→128), exactly as llama-batched-bench measures S_TG: reset the
493  // shared position to 0 so the replayed decode step regrows the cache in-bounds, then time 128 steps. ──
494  {{ int NR=128; int z=0; cudaMemcpy(d_pos,&z,4,cudaMemcpyHostToDevice); cudaStreamSynchronize(st);
495    cudaEventRecord(t0,st); for(int i=0;i<NR;i++) cudaGraphLaunch(ge,st); cudaEventRecord(t1,st); cudaStreamSynchronize(st); float bms; cudaEventElapsedTime(&bms,t0,t1);
496    double s_tg = (double)B*NR*1000.0/bms; fprintf(stderr,"BATCHED_S_TG %.2f (B=%d, tg128 from empty KV = llama-batched-bench S_TG metric, in-bounds)\n", s_tg, B); }}
497  return 0;
498}}
499"#,
500        wdir = wdir,
501        e = e,
502        qd = qd,
503        kd = kd,
504        ff = ff,
505        ne = ne,
506        ktop = ktop,
507        vocab = vocab,
508        layers = layers,
509        nh = nh,
510        nkv = nkv,
511        hd = hd,
512        maxseq = maxseq,
513        batch = batch,
514        scale = scale,
515        prompt_c = prompt_c,
516        n_gen = n_gen
517    )
518}