code wiki / (root) / nx_gen_linear_verify.nx

nx_gen_linear_verify.nx source

↩ module page · 415 lines · 18940 B

1// nx_gen_linear_verify.nx -- SOVEREIGN linear projection (matmul), verified vs a reference. 2// 3// y == x @ W^T 4// 5// This is the core compute of every diffusion and transformer architecture: qkv projection, 6// attention out-projection, every FFN matrix, every text-encoder layer. It is also where the 7// entire sovereignty gap lives -- correctness here was proven cheaply; THROUGHPUT is the wall. 8// 9// Usage: nx_gen_linear_verify <model_id> <x_name> <w_name> <y_name> [rows] [pass_idx] [accum] [nworkers] 10// 11// Names are ABSOLUTE fixture names -- weights carry their own checkpoint name 12// ("model.diffusion_model.layers.0.attention.qkv.weight"), so nothing here is tied to one 13// architecture or one block. Shapes are read from the manifest, never assumed. 14// 15// LAYOUT (ggml): ggml_mul_mat(w, x) with w[ne0=in, ne1=out] and x[ne0=in, ne1=tokens] gives 16// y[ne0=out, ne1=tokens]. ne[0] is the contiguous dim, so 17// y[t][o] = sum_i x[t*in + i] * w[o*in + i] 18// 19// TWO REFERENCE TIERS (measured 2026-08-06, not a stylistic choice): 20// The oracle's mul_mat on a Q8_0 weight does NOT compute dequant(W) @ x -- it quantizes the 21// ACTIVATIONS too and runs a blocked integer dot. An exact f64 reference deviates from the 22// oracle by ~0.52% median / 43% max on the Z-Image qkv projection. So grade against 23// - `<y>.exact` (f64 reference) -> "is our arithmetic right", tight band 24// - `<y>` (the oracle itself) -> only at a loose band; it is the LOWER-precision side 25// * A TOLERANCE IS A CLAIM ABOUT THE REFERENCE'S PRECISION, NOT THE IMPLEMENTATION'S. 26// 27// ACCUM MODES -- every one stays A/B-able, because a speed or accuracy claim that cannot be 28// re-measured against its predecessor is an opinion: 29// 0 naive sequential f32 30// 1 Kahan compensated 31// 2 __f32x8_dot (8-wide, but horizontally reduces EVERY 8 elements) 32// 3 __f32x8_dot + Kahan on the chunk sum 33// 4 __f32x8_fma (fused 8-wide multiply-add into a 32-byte accumulator, hsum ONCE) 34// 5 __f32x8_fma + multicore (forked workers over an out_dim band, shared result buffer) 35// 6 __f32x8_fma x4 independent accumulators (breaks the FMA latency chain) 36// 7 mode 6 + multicore 37// 8 Q8_0 dequant-dot (int8 weight x f32 activation via __f32_i8dot32a) -- reads the RAW 38// quantized weight, 3.77x less memory traffic than the f32 weight 39// 9 mode 8 + multicore 40// 10 Q8_0 with per-block f16 scales pre-decoded once (software f16 decode out of the hot loop) 41// 11 mode 10 + multicore 42// 12 mode 10 with OUTPUT-OUTER loop order: each weight row loads once and serves every token 43// 13 mode 12 + multicore 44// Modes 2/4 and the fork pattern are all PRE-EXISTING estate primitives (nx_f32_linear_simd, 45// nx_fma_matmul_gate, nx_f32x8_mt_matmul). * CHECK THE ESTATE BEFORE WRITING A KERNEL. 46// 47// license_tier: ORIGINAL 48 49import "nx_syscalls.nx" 50import "nx_le.nx" 51import "nx_f32.nx" 52import "nx_f32_div.nx" 53import "nx_f32_cvt.nx" 54import "nx_f16.nx" 55import "nx_strconv.nx" 56import "nx_genfix.nx" 57import "nx_genver.nx" 58 59func zl_strlen(s: *u8) -> i64 { 60 var n: i64 = 0 61 while s[n] != (0 as u8) { n = n + 1 } 62 return n 63} 64 65// Compute res[t*out_dim + o] for o in [o0, o1) across all `rows` tokens. 66// 67// Split out from the tally so every mode -- including the forked one, whose results have to 68// cross a process boundary -- produces its answer the same way and is scored by the same code. 69// Fusing the compare into the inner loop would have made the multicore mode a DIFFERENT 70// measurement rather than the same measurement done faster. 71func zl_band(mode: i64, x: *u8, w: *u8, res: *i64, scales: *i64, 72 rows: i64, in_dim: i64, out_dim: i64, o0: i64, o1: i64) -> i64 { 73 let fma_acc: *u8 = sys_mmap(256) // 4 x 32B accumulators; forked child gets its own COW copy 74 let az: *i64 = fma_acc as *i64 75 // MODE 12: output-outer, token-inner. 76 // 77 // Every other mode here loops token-outer, so for each token it streams the ENTIRE 47MB 78 // weight and nothing survives in cache to the next token: 64 tokens = 64 full passes over 79 // the weight. Swapping the loops makes each 4080-byte weight row load ONCE and serve all 80 // `rows` tokens from L1 -- the weight traffic drops by a factor of `rows`, and the activation 81 // rows (15KB each) are what stay resident instead. 82 // This is the classic blocking transform; nx_blocked_matmul already applies it, and the 83 // estate's own Q8_0 notes named MEMORY ACCESS -- not the dot kernel -- as the real gap. 84 if mode == 12 { 85 let nb3: i64 = in_dim / 32 86 var o3: i64 = o0 87 while o3 < o1 { 88 let qb3: i64 = (w as i64) + o3 * nb3 * 34 89 let sb3: i64 = o3 * nb3 90 var t3: i64 = 0 91 while t3 < rows { 92 let ab3: i64 = (x as i64) + t3 * in_dim * 4 93 var a3: i64 = 0 94 var b3: i64 = 0 95 while b3 < nb3 { 96 let r3: i64 = __f32_i8dot32a((qb3 + b3 * 34 + 2) as *u8, (ab3 + b3 * 128) as *u8) 97 a3 = __f32_add(a3, __f32_mul(scales[sb3 + b3], r3)) 98 b3 = b3 + 1 99 } 100 res[t3 * out_dim + o3] = a3 101 t3 = t3 + 1 102 } 103 o3 = o3 + 1 104 } 105 return 0 106 } 107 108 var t: i64 = 0 109 while t < rows { 110 let xb: i64 = t * in_dim * 4 111 var o: i64 = o0 112 while o < o1 { 113 let wb: i64 = o * in_dim * 4 114 var acc: i64 = 0 115 var comp: i64 = 0 116 var i: i64 = 0 117 118 if mode == 4 { 119 az[0] = 0 120 az[1] = 0 121 az[2] = 0 122 az[3] = 0 123 let ib: i64 = (x as i64) + xb 124 let wp: i64 = (w as i64) + wb 125 let nch: i64 = in_dim / 8 126 var ch: i64 = 0 127 while ch < nch { 128 __f32x8_fma(fma_acc, (ib + ch * 32) as *u8, (wp + ch * 32) as *u8) 129 ch = ch + 1 130 } 131 acc = __f32x8_hsum(fma_acc) 132 i = nch * 8 133 } 134 if mode == 6 { 135 // FOUR independent accumulators. A single accumulator serialises on the FMA's 136 // own latency: every vfmadd231ps must wait for the previous one to retire before 137 // it can add into the same register, so the pipeline runs at 1/latency instead of 138 // 1/throughput. Four chains let four FMAs be in flight at once. It also improves 139 // accuracy for free -- 32 partial sums instead of 8. 140 // Measured 1-core FMA sat at ~8% of the 8-wide FMA peak, which is the signature 141 // of a latency-bound chain rather than a bandwidth-bound one. 142 var q: i64 = 0 143 while q < 16 { az[q] = 0; q = q + 1 } // zero all four 32-byte accumulators 144 let a0: *u8 = fma_acc 145 let a1: *u8 = ((fma_acc as i64) + 32) as *u8 146 let a2: *u8 = ((fma_acc as i64) + 64) as *u8 147 let a3: *u8 = ((fma_acc as i64) + 96) as *u8 148 let ib: i64 = (x as i64) + xb 149 let wp: i64 = (w as i64) + wb 150 let nch: i64 = in_dim / 8 151 var ch: i64 = 0 152 while ch + 4 <= nch { 153 __f32x8_fma(a0, (ib + (ch + 0) * 32) as *u8, (wp + (ch + 0) * 32) as *u8) 154 __f32x8_fma(a1, (ib + (ch + 1) * 32) as *u8, (wp + (ch + 1) * 32) as *u8) 155 __f32x8_fma(a2, (ib + (ch + 2) * 32) as *u8, (wp + (ch + 2) * 32) as *u8) 156 __f32x8_fma(a3, (ib + (ch + 3) * 32) as *u8, (wp + (ch + 3) * 32) as *u8) 157 ch = ch + 4 158 } 159 while ch < nch { 160 __f32x8_fma(a0, (ib + ch * 32) as *u8, (wp + ch * 32) as *u8) 161 ch = ch + 1 162 } 163 acc = __f32_add(__f32_add(__f32x8_hsum(a0), __f32x8_hsum(a1)), 164 __f32_add(__f32x8_hsum(a2), __f32x8_hsum(a3))) 165 i = nch * 8 166 } 167 if mode == 10 { 168 // Q8_0 with the per-block f16 scale decoded ONCE per weight, not once per 169 // (token, block). nx_f16_to_f32 is SOFTWARE -- the same class of mistake as the 170 // software add: it sits in the hot loop and is re-executed for every token, so at 171 // rows=64 the identical 1.38M decodes were done 64 times. 172 let qb2: i64 = (w as i64) + o * (in_dim / 32) * 34 173 let ab2: i64 = (x as i64) + xb 174 let nb2: i64 = in_dim / 32 175 let sb2: i64 = o * nb2 176 var b2: i64 = 0 177 while b2 < nb2 { 178 let raw2: i64 = __f32_i8dot32a((qb2 + b2 * 34 + 2) as *u8, (ab2 + b2 * 128) as *u8) 179 acc = __f32_add(acc, __f32_mul(scales[sb2 + b2], raw2)) 180 b2 = b2 + 1 181 } 182 i = nb2 * 32 183 } 184 if mode == 8 { 185 // Q8_0 DEQUANT-DOT -- the same computation the engine's own Q8_0 path runs 186 // (_lw_q8_0_dot in nx_f32_lazy_weight): per 34-byte block, f16 scale d and 32 187 // int8 quants, dotted against 32 CONTIGUOUS f32 activations by __f32_i8dot32a, 188 // scaled by d and accumulated. 189 // 190 // Note what this is NOT: ggml's q8_0 x q8_0 quantizes the ACTIVATIONS too. This 191 // keeps activations in f32 and quantizes only the WEIGHT side, so it gets the 192 // 3.77x weight-traffic cut WITHOUT the activation quantization error -- more 193 // accurate than the engine it is replacing, at the engine's memory cost. 194 let qb: i64 = (w as i64) + o * (in_dim / 32) * 34 195 let ab: i64 = (x as i64) + xb 196 let nblk: i64 = in_dim / 32 197 var b: i64 = 0 198 while b < nblk { 199 let boff: i64 = b * 34 200 let d32: i64 = nx_f16_to_f32(nx_le_read_u16(w, (o * nblk * 34) + boff)) 201 let raw: i64 = __f32_i8dot32a((qb + boff + 2) as *u8, (ab + b * 32 * 4) as *u8) 202 // __f32_add/__f32_mul are the HARDWARE SSE ops; nx_f32_add/nx_f32_mul are 203 // full software IEEE-754 (dozens of integer ops each). Using the software 204 // pair here -- once per 32 values -- measured 8.4x slower and made the whole 205 // Q8_0 lever look like a dead end. The known-good _lw_q8_0_dot uses the 206 // hardware pair; deviating from it in a detail this small is what cost the 207 // measurement. * MATCH THE KNOWN GOOD EXACTLY BEFORE CONCLUDING IT LOSES. 208 acc = __f32_add(acc, __f32_mul(d32, raw)) 209 b = b + 1 210 } 211 i = nblk * 32 212 } 213 if mode == 2 || mode == 3 { 214 let ib: i64 = (x as i64) + xb 215 let wp: i64 = (w as i64) + wb 216 let nch: i64 = in_dim / 8 217 var ch: i64 = 0 218 while ch < nch { 219 let part: i64 = __f32x8_dot((ib + ch * 32) as *i64, (wp + ch * 32) as *i64) 220 // HARDWARE add: nx_f32_add is software IEEE-754 and runs 480x per dot here, 221 // which made mode 2 look ~9x worse than its kernel actually is. The A/B ladder 222 // has to differ ONLY in the thing being compared. 223 if mode == 2 { acc = __f32_add(acc, part) } 224 if mode == 3 { 225 let yv: i64 = nx_f32_sub(part, comp) 226 let tv: i64 = nx_f32_add(acc, yv) 227 comp = nx_f32_sub(nx_f32_sub(tv, acc), yv) 228 acc = tv 229 } 230 ch = ch + 1 231 } 232 i = nch * 8 233 } 234 // Scalar path, and the tail for every vector mode when in_dim is not a multiple of 8 235 // -- a silently truncated tail would read as a clean result. 236 while i < in_dim { 237 let prod: i64 = nx_f32_mul(nx_le_read_u32(x, xb + i * 4), 238 nx_le_read_u32(w, wb + i * 4)) 239 if mode == 1 { 240 let yv: i64 = nx_f32_sub(prod, comp) 241 let tv: i64 = nx_f32_add(acc, yv) 242 comp = nx_f32_sub(nx_f32_sub(tv, acc), yv) 243 acc = tv 244 } 245 if mode != 1 { acc = nx_f32_add(acc, prod) } 246 i = i + 1 247 } 248 res[t * out_dim + o] = acc 249 o = o + 1 250 } 251 t = t + 1 252 } 253 return 0 254} 255 256func main(argc: i64, argv: *i64) -> i64 { 257 if argc < 5 { 258 nx_genver_emit("usage_model_x_w_y_rows_passidx_accum_workers" as *u8, argc) 259 return 2 260 } 261 let model: *u8 = argv[1] as *u8 262 let xn: *u8 = argv[2] as *u8 263 let wn: *u8 = argv[3] as *u8 264 let yn: *u8 = argv[4] as *u8 265 let lx: i64 = zl_strlen(xn) 266 let lw: i64 = zl_strlen(wn) 267 let ly: i64 = zl_strlen(yn) 268 269 let errp: *i64 = sys_mmap(32) as *i64 270 var rows: i64 = 1 271 if argc >= 6 { 272 errp[0] = 0 273 rows = nx_strconv_parse_i64(argv[5] as *u8, errp) 274 if errp[0] != 0 { nx_genver_emit("bad_rows" as *u8, 1); return 3 } 275 if rows <= 0 { nx_genver_emit("bad_rows" as *u8, rows); return 4 } 276 } 277 var pass_idx: i64 = 0 278 if argc >= 7 { 279 errp[0] = 0 280 pass_idx = nx_strconv_parse_i64(argv[6] as *u8, errp) 281 if errp[0] != 0 { nx_genver_emit("bad_pass_idx" as *u8, 1); return 5 } 282 } 283 // Default 4 (__f32x8_fma): measured fastest AND 0 fails at 1e-4 on both projections. 284 var accum: i64 = 6 285 if argc >= 8 { 286 errp[0] = 0 287 accum = nx_strconv_parse_i64(argv[7] as *u8, errp) 288 if errp[0] != 0 { nx_genver_emit("bad_accum_mode" as *u8, 1); return 6 } 289 } 290 var nworkers: i64 = 8 291 if argc >= 9 { 292 errp[0] = 0 293 nworkers = nx_strconv_parse_i64(argv[8] as *u8, errp) 294 if errp[0] != 0 { nx_genver_emit("bad_nworkers" as *u8, 1); return 7 } 295 if nworkers <= 0 { nworkers = 1 } 296 } 297 298 let ne_x: *i64 = sys_mmap(64) as *i64 299 let ne_w: *i64 = sys_mmap(64) as *i64 300 let ne_y: *i64 = sys_mmap(64) as *i64 301 let c_x: i64 = nx_genfix_dims(model, xn, lx, ne_x) 302 let c_w: i64 = nx_genfix_dims(model, wn, lw, ne_w) 303 let c_y: i64 = nx_genfix_dims(model, yn, ly, ne_y) 304 if c_x < 0 { nx_genver_emit("missing_x" as *u8, 1); return 30 } 305 if c_w < 0 { nx_genver_emit("missing_w" as *u8, 1); return 31 } 306 if c_y < 0 { nx_genver_emit("missing_y" as *u8, 1); return 32 } 307 308 let in_dim: i64 = ne_w[0] 309 let out_dim: i64 = ne_w[1] 310 let n_tok: i64 = ne_x[1] 311 312 // Refuse a shape story that does not hold together, rather than produce a number from it. 313 if ne_x[0] != in_dim { nx_genver_emit("x_indim_mismatch" as *u8, ne_x[0]); return 33 } 314 if ne_y[0] != out_dim { nx_genver_emit("y_outdim_mismatch" as *u8, ne_y[0]); return 34 } 315 if ne_y[1] != n_tok { nx_genver_emit("y_tokens_mismatch" as *u8, ne_y[1]); return 35 } 316 317 let x: *u8 = nx_genfix_load(model, xn, lx, c_x) 318 if (x as i64) == 0 { nx_genver_emit("load_failed_x" as *u8, 1); return 40 } 319 // Modes 8/9 read the native quantized blocks; loading the 177MB f32 shadow too would 320 // triple the footprint and measure a kernel that is not the one under test. 321 var w: *u8 = 0 as *u8 322 if accum >= 8 { 323 let q_bytes: i64 = (in_dim / 32) * 34 * out_dim 324 w = nx_genfix_load_raw(model, wn, lw, q_bytes) 325 if (w as i64) == 0 { nx_genver_emit("load_failed_w_raw" as *u8, 1); return 43 } 326 nx_genver_emit("q8_weight_bytes" as *u8, q_bytes) 327 } 328 if accum < 8 { 329 w = nx_genfix_load(model, wn, lw, c_w) 330 if (w as i64) == 0 { nx_genver_emit("load_failed_w" as *u8, 1); return 41 } 331 } 332 let y: *u8 = nx_genfix_load(model, yn, ly, c_y) 333 if (y as i64) == 0 { nx_genver_emit("load_failed_y" as *u8, 1); return 42 } 334 335 if n_tok < rows { rows = n_tok } 336 337 nx_genver_emit("in_dim" as *u8, in_dim) 338 nx_genver_emit("out_dim" as *u8, out_dim) 339 nx_genver_emit("tokens_total" as *u8, n_tok) 340 nx_genver_emit("tokens_checked" as *u8, rows) 341 nx_genver_emit("multiplies" as *u8, rows * out_dim * in_dim) 342 nx_genver_emit("accum_mode" as *u8, accum) 343 344 // SHARED so forked workers' results are visible to the parent that scores them. 345 let res: *i64 = sys_mmap_shared(rows * out_dim * 8) as *i64 346 347 // Pre-decode every block scale once. SHARED so forked workers read it instead of each 348 // redoing the software f16 decode. 349 let nblk_row: i64 = in_dim / 32 350 var scales: *i64 = 0 as *i64 351 if accum >= 10 { 352 scales = sys_mmap_shared(out_dim * nblk_row * 8) as *i64 353 var so: i64 = 0 354 while so < out_dim { 355 var sb: i64 = 0 356 while sb < nblk_row { 357 scales[so * nblk_row + sb] = nx_f16_to_f32(nx_le_read_u16(w, (so * nblk_row + sb) * 34)) 358 sb = sb + 1 359 } 360 so = so + 1 361 } 362 } 363 364 let t_start: i64 = sys_now_us() 365 if accum == 5 || accum == 7 || accum == 9 || accum == 11 || accum == 13 { 366 nx_genver_emit("workers" as *u8, nworkers) 367 let pids: *i64 = sys_mmap(nworkers * 8 + 64) as *i64 368 var k: i64 = 0 369 while k < nworkers { 370 let o0: i64 = k * out_dim / nworkers 371 let o1: i64 = (k + 1) * out_dim / nworkers 372 let pid: i64 = sys_fork() 373 if pid == 0 { 374 var inner: i64 = 4 375 if accum == 7 { inner = 6 } 376 if accum == 9 { inner = 8 } 377 if accum == 11 { inner = 10 } 378 if accum == 13 { inner = 12 } 379 zl_band(inner, x, w, res, scales, rows, in_dim, out_dim, o0, o1) 380 sys_exit(0) 381 } 382 pids[k] = pid 383 k = k + 1 384 } 385 let st: *i64 = sys_mmap(64) as *i64 386 k = 0 387 while k < nworkers { 388 sys_wait4(pids[k], st, 0) 389 k = k + 1 390 } 391 } 392 if accum != 5 && accum != 7 && accum != 9 && accum != 11 && accum != 13 { 393 zl_band(accum, x, w, res, scales, rows, in_dim, out_dim, 0, out_dim) 394 } 395 let t_end: i64 = sys_now_us() 396 nx_genver_emit("compute_us" as *u8, t_end - t_start) 397 398 let tol: *i64 = sys_mmap(64) as *i64 399 nx_genver_tols(tol) 400 let c: *i64 = sys_mmap(128) as *i64 401 nx_genver_init(c, pass_idx) 402 403 var t2: i64 = 0 404 while t2 < rows { 405 var o2: i64 = 0 406 while o2 < out_dim { 407 let flat: i64 = t2 * out_dim + o2 408 nx_genver_tally(c, tol, res[flat], nx_le_read_u32(y, flat * 4), flat) 409 o2 = o2 + 1 410 } 411 t2 = t2 + 1 412 } 413 414 return nx_genver_report(c) 415}