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}