code wiki / _hdl_build / nx_roofline_test.nx

nx_roofline_test.nx source

↩ module page · 57 lines · 3781 B

1// nx_roofline_test.nx -- the team DIAGNOSES the real bottleneck of the targets (llama.cpp / 2// ggml), and proves the intelligent lever. Three checks, exit 0 only if all hold: 3// (1) the Q4 quantized MATVEC of single-token decode is MEMORY-bound on a real consumer CPU -- 4// matching the measured reality (AVX2 micro-opt = +0.8%). So SIMD is the WRONG lever. 5// (2) the SAME math, but a batched/reused matmul (each weight reused many times), is COMPUTE- 6// bound -- so the search-governor/superopt IS the right lever there. The roofline tells the 7// team WHICH regime it is in, per kernel, per chip. 8// (3) the data-movement lever (Q4 -> Q2, half the weight bytes) gives ~1.8x in the memory-bound 9// regime -- ~225x more than the SIMD micro-opt. "mental math" (move less) >> brute force. 10// license_tier: ORIGINAL 11 12import "nx_roofline.nx" 13import "nx_syscalls.nx" 14 15func rt_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 16func rt_num(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64=v; if m<0 {m=0-m}; let t: *u8 = sys_mmap(28); var k: i64=0; if m==0 {t[0]=48;k=1}; while m>0 {t[k]=48+(m%10); m=m/10; k=k+1}; var i: i64=0; while i<k {bb[i]=t[k-1-i]; i=i+1}; sys_write(1, bb, k); return 0 } 17 18func main() -> i64 { 19 rt_puts("=== ROOFLINE: where is the real bottleneck in the targets (ggml/llama.cpp)? ===\n" as *u8) 20 // a real consumer CPU: 8 cores * 3GHz * 16 flop/cyc (AVX2 FMA) ~ 384 GFLOP/s; DDR4 ~ 45 GB/s. 21 let peak_flops: i64 = 384 22 let peak_bw: i64 = 45 23 rt_puts(" machine ridge point (flop/byte x1000): " as *u8); rt_num(rf_ridge_milli(peak_flops, peak_bw)); rt_puts("\n" as *u8) 24 25 // (1) single-token decode = matrix-VECTOR: weights read once, no reuse. N=4096 hidden dim. 26 // flops = 2*N^2 ; Q4_K weights ~ 9/16 byte each. 27 let N: i64 = 4096 28 let mv_flops: i64 = 2 * N * N 29 let mv_bytes: i64 = (N * N * 9) / 16 30 let b1: i64 = rf_bound(mv_flops, mv_bytes, peak_flops, peak_bw) 31 rt_puts(" [decode matvec Q4] AIx1000=" as *u8); rt_num(rf_ai_milli(mv_flops, mv_bytes)) 32 rt_puts(" -> " as *u8); if b1 == RF_MEMORY_BOUND { rt_puts("MEMORY-bound (SIMD is the WRONG lever)\n" as *u8) } else { rt_puts("compute-bound\n" as *u8) } 33 34 // (2) same weights, batch/reuse B=64 (prompt prefill / batched serving): flops *= B, bytes same. 35 let B: i64 = 64 36 let mm_flops: i64 = 2 * N * N * B 37 let mm_bytes: i64 = (N * N * 9) / 16 38 let b2: i64 = rf_bound(mm_flops, mm_bytes, peak_flops, peak_bw) 39 rt_puts(" [batched matmul B=64] AIx1000=" as *u8); rt_num(rf_ai_milli(mm_flops, mm_bytes)) 40 rt_puts(" -> " as *u8); if b2 == RF_COMPUTE_BOUND { rt_puts("COMPUTE-bound (superopt/governor IS the lever)\n" as *u8) } else { rt_puts("memory-bound\n" as *u8) } 41 42 // (3) the data-movement lever in the memory-bound regime: Q4 (9/16 B) -> Q2 (~5/16 B). 43 let q4_bytes: i64 = (N * N * 9) / 16 44 let q2_bytes: i64 = (N * N * 5) / 16 45 let sp: i64 = rf_byte_speedup_milli(q4_bytes, q2_bytes) 46 rt_puts(" [Q4->Q2 fewer bytes] speedupx1000=" as *u8); rt_num(sp); rt_puts(" vs SIMD micro-opt ~1008 (+0.8%)\n" as *u8) 47 48 let r: *i64 = sys_mmap(8*8) as *i64 49 r[0] = 0; if b1 == RF_MEMORY_BOUND { r[0] = 1 } 50 r[1] = 0; if b2 == RF_COMPUTE_BOUND { r[1] = 1 } 51 r[2] = 0; if sp > 1500 { r[2] = 1 } // data lever > 1.5x, dwarfs SIMD's 1.008x 52 var pass: i64 = 0; var i: i64 = 0 53 while i < 3 { pass = pass + r[i]; i = i + 1 } 54 rt_puts("----\n passed " as *u8); rt_num(pass); rt_puts("/3\n" as *u8) 55 if pass == 3 { rt_puts(" DIAGNOSED: decode is memory-bound -> the win is MOVING LESS DATA (mental math), not SIMD.\n" as *u8); sys_exit(0); return 0 } 56 rt_puts(" FAIL\n" as *u8); sys_exit(1); return 1 57}