Skip to main content

logicaffeine_compile/codegen_cuda/
fused_row.rs

1//! The **fused row-op** CUDA emitter — the real one-block-per-row shared-memory reduction kernel.
2//!
3//! This generalizes the hand-written `cuda/qwen_forward.cu::rmsnorm` (one block per row, a strided partial
4//! into shared memory, a tree reduction, an `rsqrt`, a rescale) into a *generated* kernel parameterized by a
5//! recognized [`FusedRowSpec`]. It replaces the toy serial `CUDA_RMSNORM_TEMPLATE` / `CUDA_SOFTMAX_TEMPLATE`:
6//! those reduced on a single thread over one flat vector; this reduces per row in parallel over `rows × cols`,
7//! which is the transformer-normalization shape SOL-ExecBench scores. The flat sugar is the `rows == 1` case.
8//!
9//! It is **chip-parametric**: the provenance header records the target part (from [`CudaKnobs::chip`]), so the
10//! same recognized op emits a kernel stamped for the RTX 3060 or the B200. (Precision/bf16 loads and an
11//! `extern "C"` harness entrypoint layer on in the following steps; today the datapath is f32, matching the
12//! `Real` sugar it lowers, and the host prints `Σ Y` — the checksum contract the reduction lock reads.)
13
14use crate::codegen_cuda::knobs::CudaKnobs;
15use crate::tensor_recognize::{Epsilon, FusedRowSpec, RowReduceKind, SwiGluSpec};
16
17/// Threads per block for the row reduction — a power of two so the shared-memory tree reduction is exact;
18/// the strided load loop covers any `cols`. 256 is the oracle's width and fits every part's block budget.
19const ROW_THREADS: usize = 256;
20
21/// Emit a self-contained CUDA program for a recognized fused row op: one `__global__` block per row doing a
22/// shared-memory tree reduction over `cols`, plus a host that fills `X` deterministically and prints `Σ Y`.
23/// `knobs.chip` stamps the provenance header (and drives the arch the caller compiles for).
24pub fn emit_fused_row(spec: &FusedRowSpec, knobs: &CudaKnobs) -> String {
25    let rows = spec.rows.max(1);
26    let cols = spec.cols.max(1);
27    let chip = knobs.chip;
28    let is_rms = matches!(spec.reduce, RowReduceKind::RmsNorm);
29    let layernorm = matches!(spec.reduce, RowReduceKind::LayerNorm);
30    let residual = is_rms && spec.residual.is_some();
31    let weighted = is_rms && spec.weight.is_some();
32    let has_w = weighted || layernorm; // LayerNorm always scales by a per-column weight
33    let has_b = layernorm; //           …and shifts by a per-column bias
34    let kernel_body = match spec.reduce {
35        RowReduceKind::RmsNorm => rmsnorm_kernel(residual, weighted, spec.eps),
36        RowReduceKind::Softmax => SOFTMAX_KERNEL.to_string(),
37        RowReduceKind::LayerNorm => layernorm_kernel(spec.eps),
38    };
39    // Host input plumbing, in kernel-parameter order `x, [res,] [w,] [b,] y`. The self-contained host fills
40    // each input deterministically (`res[i]=((i+1)%5)-2`, `w[c]=1+0.5·(c%3)`, `b[c]=0.1·(c+1)`) so the emitted
41    // program stays runnable and its checksum is reproducible against a CPU reference over the same fills.
42    let mut allocs = String::new();
43    let mut fills = String::new();
44    let mut args = String::from("x,");
45    if residual {
46        allocs.push_str("    float* res; if(cudaMallocManaged(&res,n*sizeof(float))){ fprintf(stderr,\"malloc\\n\"); return 1; }\n");
47        fills.push_str("    for(size_t i=0;i<n;++i) res[i]=(float)((int)((i+1)%5)-2);\n");
48        args.push_str("res,");
49    }
50    if has_w {
51        allocs.push_str("    float* w; if(cudaMallocManaged(&w,(size_t)COLS*sizeof(float))){ fprintf(stderr,\"malloc\\n\"); return 1; }\n");
52        fills.push_str("    for(int c=0;c<COLS;++c) w[c]=1.0f+0.5f*(float)(c%3);\n");
53        args.push_str("w,");
54    }
55    if has_b {
56        allocs.push_str("    float* b; if(cudaMallocManaged(&b,(size_t)COLS*sizeof(float))){ fprintf(stderr,\"malloc\\n\"); return 1; }\n");
57        fills.push_str("    for(int c=0;c<COLS;++c) b[c]=0.1f*(float)(c+1);\n");
58        args.push_str("b,");
59    }
60    args.push('y');
61    let tag = format!(
62        "{}{}{}",
63        if residual { " (fused-add)" } else { "" },
64        if weighted { " (weighted)" } else { "" },
65        if layernorm { " (weight+bias)" } else { "" },
66    );
67    format!(
68        r#"// Auto-generated by LOGOS codegen_cuda::fused_row — a recognized LOGOS {op}{tag} lowered to CUDA.
69// Per-row reduction over rows={rows} x cols={cols}; one block per row, shared-memory tree reduction.
70// Target: {chip_name} (sm_{arch}). Not a template — the launch bounds are a function of the recognized shape.
71#include <cstdio>
72#include <cmath>
73#define ROWS {rows}
74#define COLS {cols}
75#define THREADS {threads}
76{kernel}
77int main(void){{
78    float *x,*y;
79    size_t n = (size_t)ROWS*COLS;
80    if(cudaMallocManaged(&x,n*sizeof(float))||cudaMallocManaged(&y,n*sizeof(float))){{ fprintf(stderr,"malloc\n"); return 1; }}
81{allocs}    for(size_t i=0;i<n;++i) x[i]=(float)((int)(i%7)-3);
82{fills}    fused_row<<<ROWS,THREADS>>>({args});
83    if(cudaDeviceSynchronize()!=cudaSuccess){{ fprintf(stderr,"sync\n"); return 1; }}
84    double sum=0.0; for(size_t i=0;i<n;++i) sum+=y[i];
85    printf("%.6f\n", sum);
86    return 0;
87}}
88"#,
89        op = spec.reduce.name(),
90        tag = tag,
91        rows = rows,
92        cols = cols,
93        threads = ROW_THREADS,
94        chip_name = chip.name,
95        arch = chip.sm_arch,
96        kernel = kernel_body,
97        allocs = allocs,
98        fills = fills,
99        args = args,
100    )
101}
102
103/// Emit a self-contained CUDA program for a recognized SwiGLU map — `y[i] = silu(gate[i]) * up[i]`, one thread
104/// per element, self-contained host (deterministic fills + `Σ Y` checksum). Chip-stamped like the fused-row.
105pub fn emit_swiglu(spec: &SwiGluSpec, knobs: &CudaKnobs) -> String {
106    let n = spec.total.max(1);
107    let chip = knobs.chip;
108    format!(
109        r#"// Auto-generated by LOGOS codegen_cuda::fused_row — a recognized LOGOS swiglu lowered to CUDA.
110// Y[i] = silu(gate[i]) * up[i]; silu(g) = g / (1 + e^-g); N={n} elements, one thread each.
111// Target: {chip_name} (sm_{arch}). Not a template — N is a function of the recognized shape.
112#include <cstdio>
113#include <cmath>
114#define N {n}
115#define THREADS 256
116__global__ void swiglu(const float* gate, const float* up, float* y){{
117    int i = blockIdx.x*blockDim.x + threadIdx.x; if(i>=N) return;
118    float g = gate[i];
119    y[i] = (g/(1.0f+expf(-g))) * up[i];
120}}
121int main(void){{
122    float *gate,*up,*y;
123    size_t n=(size_t)N;
124    if(cudaMallocManaged(&gate,n*sizeof(float))||cudaMallocManaged(&up,n*sizeof(float))||cudaMallocManaged(&y,n*sizeof(float))){{ fprintf(stderr,"malloc\n"); return 1; }}
125    for(size_t i=0;i<n;++i){{ gate[i]=(float)((int)(i%7)-3); up[i]=(float)((int)((i+1)%5)-2); }}
126    swiglu<<<(N+THREADS-1)/THREADS,THREADS>>>(gate,up,y);
127    if(cudaDeviceSynchronize()!=cudaSuccess){{ fprintf(stderr,"sync\n"); return 1; }}
128    double sum=0.0; for(size_t i=0;i<n;++i) sum+=y[i];
129    printf("%.6f\n", sum);
130    return 0;
131}}
132"#,
133        n = n,
134        chip_name = chip.name,
135        arch = chip.sm_arch,
136    )
137}
138
139/// Build the per-row RMSNorm kernel for the four `(residual, weighted)` variants: the reduced value is
140/// `X[r,i]` or `X[r,i] + res[r,i]` (fused-add), and the rescale is `·rsqrt(mean+eps)` optionally `·W[i]`, with the
141/// program's `eps`.
142fn rmsnorm_kernel(residual: bool, weighted: bool, eps: Epsilon) -> String {
143    let params = format!(
144        "const float* x, {}{}float* y",
145        if residual { "const float* res, " } else { "" },
146        if weighted { "const float* w, " } else { "" },
147    );
148    let res_row = if residual {
149        "    const float* rr = res + (long)row*COLS;\n"
150    } else {
151        ""
152    };
153    let elem = if residual { "(xr[i]+rr[i])" } else { "xr[i]" };
154    let wmul = if weighted { "*w[i]" } else { "" };
155    format!(
156        r#"__global__ void fused_row({params}){{
157    int row = blockIdx.x;
158    const float* xr = x + (long)row*COLS;
159{res_row}    float* yr = y + (long)row*COLS;
160    __shared__ float sh[THREADS];
161    float s = 0.0f;
162    for(int i = threadIdx.x; i < COLS; i += blockDim.x){{ float e = {elem}; s += e*e; }}
163    sh[threadIdx.x] = s; __syncthreads();
164    for(int k = blockDim.x/2; k > 0; k >>= 1){{ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x+k]; __syncthreads(); }}
165    float scale = rsqrtf(sh[0]/(float)COLS + {eps});
166    for(int i = threadIdx.x; i < COLS; i += blockDim.x){{ float e = {elem}; yr[i] = e*scale{wmul}; }}
167}}"#,
168        params = params,
169        res_row = res_row,
170        elem = elem,
171        wmul = wmul,
172        eps = eps_literal(eps),
173    )
174}
175
176/// The `f32` literal of a norm's epsilon, as the kernels spell it (`1e-6f`).
177fn eps_literal(eps: Epsilon) -> String {
178    format!("{:e}f", eps.get() as f32)
179}
180
181/// Build the per-row LayerNorm kernel: two-pass mean/variance, then `(x-mean)·rsqrt(var+eps)·W[i] + B[i]`, with the
182/// program's `eps`.
183fn layernorm_kernel(eps: Epsilon) -> String {
184    r#"__global__ void fused_row(const float* x, const float* w, const float* b, float* y){
185    int row = blockIdx.x;
186    const float* xr = x + (long)row*COLS;
187    float* yr = y + (long)row*COLS;
188    __shared__ float sh[THREADS];
189    float s = 0.0f;
190    for(int i = threadIdx.x; i < COLS; i += blockDim.x) s += xr[i];
191    sh[threadIdx.x] = s; __syncthreads();
192    for(int k = blockDim.x/2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x+k]; __syncthreads(); }
193    float mean = sh[0]/(float)COLS; __syncthreads();
194    float v = 0.0f;
195    for(int i = threadIdx.x; i < COLS; i += blockDim.x){ float d = xr[i]-mean; v += d*d; }
196    sh[threadIdx.x] = v; __syncthreads();
197    for(int k = blockDim.x/2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x+k]; __syncthreads(); }
198    float scale = rsqrtf(sh[0]/(float)COLS + EPS);
199    for(int i = threadIdx.x; i < COLS; i += blockDim.x) yr[i] = (xr[i]-mean)*scale*w[i]+b[i];
200}"#
201    .replace("EPS", &eps_literal(eps))
202}
203
204/// Per-row softmax: `Y[r,i] = exp(X[r,i]-max)/Σ exp(X[r,·]-max)`. One block per row; two tree reductions.
205const SOFTMAX_KERNEL: &str = r#"__global__ void fused_row(const float* x, float* y){
206    int row = blockIdx.x;
207    const float* xr = x + (long)row*COLS;
208    float* yr = y + (long)row*COLS;
209    __shared__ float sh[THREADS];
210    float m = -1e30f;
211    for(int i = threadIdx.x; i < COLS; i += blockDim.x) m = fmaxf(m, xr[i]);
212    sh[threadIdx.x] = m; __syncthreads();
213    for(int k = blockDim.x/2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] = fmaxf(sh[threadIdx.x], sh[threadIdx.x+k]); __syncthreads(); }
214    float mx = sh[0]; __syncthreads();
215    float s = 0.0f;
216    for(int i = threadIdx.x; i < COLS; i += blockDim.x) s += expf(xr[i]-mx);
217    sh[threadIdx.x] = s; __syncthreads();
218    for(int k = blockDim.x/2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x+k]; __syncthreads(); }
219    float sm = sh[0];
220    for(int i = threadIdx.x; i < COLS; i += blockDim.x) yr[i] = expf(xr[i]-mx)/sm;
221}"#;