code wiki / (root) / _gemm_i32_throughput_bench.nx

_gemm_i32_throughput_bench.nx source

↩ module page · 127 lines · 4977 B

1// _gemm_i32_throughput_bench.nx -- pure-software GEMM i32 throughput. 2// 3// THE primary AI-workload baseline per the operator's cardinal of 4// 2026-05-26: "lets use this system and how it runs ai as the test 5// to see if we can optimize better than other systems like cuda 6// using nishi from 0 up". 7// 8// General matrix-matrix multiply (GEMM) -- the operation that 9// dominates the runtime of every transformer / FFN / attention 10// layer in 2026-era LLMs. CUDA's killer feature is GEMM 11// acceleration; if Nishi-silicon ever beats CUDA, this bench is 12// the yardstick. 13// 14// Computes C = A * B for N x N i32 matrices, accumulating in i64. 15// Runs ITERS iterations so wall-clock timing converts to GFLOPS: 16// 17// FLOPS = 2 * N^3 * ITERS / time_seconds 18// (factor of 2 because each MAC = 1 multiply + 1 add) 19// 20// Today: pure-NishiLang loop, no SIMD, no AVX, no MUL widening 21// intrinsics, no cache blocking. Expected ~50-200 MFLOPS on a 22// modern x86_64. CUDA on H100: ~70 TFLOPS for INT8. So today 23// we're ~350,000x slower than CUDA on this benchmark. 24// 25// That's the BASELINE. The silicon-feedback loop (M3 in 26// ZERO_TO_ADVANCED.md) consumes this number + the per-PC cycle 27// attribution from rv64im_min_sim (commit 4365e77) to identify 28// what silicon-side acceleration would close the gap most 29// efficiently: 30// - SIMD i32 vector instructions 31// - MAC fusion (multiply-add-accumulate as one cycle) 32// - Systolic array (TPU/silicon-vendor style) 33// - Sparse / quantized accelerators 34// 35// Each silicon decision gets a measured ROI number from THIS bench 36// post-acceleration; the gap shrinks generation by generation. 37// 38// Returns: 0 (success); non-zero indicates setup failure. 39 40import "nx_syscalls.nx" 41 42const NX_GEMM_N: i64 = 128 // matrix dimension 43const NX_GEMM_ITERS: i64 = 50 // matmul repetitions 44 45func main() -> i64 { 46 // ----- allocate three N x N i32 matrices ----- 47 let n: i64 = NX_GEMM_N 48 let bytes: i64 = n * n * 4 // i32 = 4 bytes 49 let a: *u8 = sys_mmap(bytes) 50 let b: *u8 = sys_mmap(bytes) 51 let c: *u8 = sys_mmap(bytes) 52 53 // ----- seed deterministic patterns ----- 54 // Both A and B get a sawtooth + multiplier so the compiler can't 55 // const-fold; values fit in i32 without overflow during accumulate. 56 var i: i64 = 0 57 while i < n { 58 var j: i64 = 0 59 while j < n { 60 let a_val: i64 = ((i + j * 17) & 0x7f) as i64 61 let b_val: i64 = ((i * 13 + j * 7) & 0x7f) as i64 62 let off: i64 = (i * n + j) * 4 63 // Little-endian i32 store 64 a[off] = (a_val & 0xff) as u8 65 a[off + 1] = ((a_val >> 8) & 0xff) as u8 66 a[off + 2] = ((a_val >> 16) & 0xff) as u8 67 a[off + 3] = ((a_val >> 24) & 0xff) as u8 68 b[off] = (b_val & 0xff) as u8 69 b[off + 1] = ((b_val >> 8) & 0xff) as u8 70 b[off + 2] = ((b_val >> 16) & 0xff) as u8 71 b[off + 3] = ((b_val >> 24) & 0xff) as u8 72 c[off] = 0 73 c[off + 1] = 0 74 c[off + 2] = 0 75 c[off + 3] = 0 76 j = j + 1 77 } 78 i = i + 1 79 } 80 81 // ----- GEMM main loop ----- 82 // 83 // The canonical IJK order (no cache blocking, no SIMD, no MAC 84 // intrinsic). Worst-case for cache behavior but THE baseline 85 // every other variant has to beat. 86 var iter: i64 = 0 87 while iter < NX_GEMM_ITERS { 88 i = 0 89 while i < n { 90 var j: i64 = 0 91 while j < n { 92 var acc: i64 = 0 93 var k: i64 = 0 94 while k < n { 95 // Load a[i,k] 96 let a_off: i64 = (i * n + k) * 4 97 let av: i64 = (a[a_off] as i64) | 98 ((a[a_off + 1] as i64) << 8) | 99 ((a[a_off + 2] as i64) << 16) | 100 ((a[a_off + 3] as i64) << 24) 101 // Load b[k,j] 102 let b_off: i64 = (k * n + j) * 4 103 let bv: i64 = (b[b_off] as i64) | 104 ((b[b_off + 1] as i64) << 8) | 105 ((b[b_off + 2] as i64) << 16) | 106 ((b[b_off + 3] as i64) << 24) 107 acc = acc + (av * bv) 108 k = k + 1 109 } 110 // Store c[i,j] = (low 32 bits of acc) 111 let c_off: i64 = (i * n + j) * 4 112 c[c_off] = (acc & 0xff) as u8 113 c[c_off + 1] = ((acc >> 8) & 0xff) as u8 114 c[c_off + 2] = ((acc >> 16) & 0xff) as u8 115 c[c_off + 3] = ((acc >> 24) & 0xff) as u8 116 j = j + 1 117 } 118 i = i + 1 119 } 120 iter = iter + 1 121 } 122 123 // Defeat dead-code-elimination: fold a byte from C into exit 124 // code (masked so process exits cleanly). 125 let sentinel: i64 = (c[0] as i64) & 0 126 return sentinel 127}