_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}