code wiki / _hdl_build / nx_f32_hw_matmul_mac4.nx
nx_f32_hw_matmul_mac4.nx source
↩ module page · 97 lines · 4438 B
1// nx_f32_hw_matmul_mac4.nx -- ILP matmul: 4 independent accumulators hide the addss latency.
2//
3// The single-acc inner loop serializes on `acc` (each addss waits ~4 cyc for the previous). Four
4// independent accumulator chains let the CPU pipeline them -> up to ~4x if latency-bound -- and it is
5// PURE .nx (scalar SSE via nx_f32_hw), so NO nx_cc change, NO rebuild, ZERO build risk. The safe win
6// to bank BEFORE the risky packed-SIMD compiler surgery (which adds 4 lanes/instruction on top).
7//
8// Sum order differs from sequential, so f32 results can differ by ROUNDING -- but for small ints
9// (exact) it is bit-identical to nx_f32_matmul/nx_f32_hw_matmul, so the KAT cross-checks exactly.
10// Sovereign: imports nx_f32_hw_matmul (-> nx_f32_hw SSE) + nx_syscalls. license_tier: ORIGINAL
11import "nx_f32_hw_matmul.nx"
12import "nx_syscalls.nx"
13const NX_MAGIC_32768: i64 = 32768
14
15const NX_MAC4_OK: i64 = 0
16const NX_MAC4_ERR: i64 = 1
17
18// C[m,n] = A[m,k] @ B[k,n], hardware f32, 4 accumulator chains.
19func nx_f32_hw_matmul_mac4(a: *i64, b: *i64, c: *i64, m: i64, k: i64, n: i64) -> i64 {
20 if m <= 0 { return NX_MAC4_ERR }
21 if k <= 0 { return NX_MAC4_ERR }
22 if n <= 0 { return NX_MAC4_ERR }
23 var i: i64 = 0
24 while i < m {
25 var j: i64 = 0
26 while j < n {
27 var acc0: i64 = f32_of(0); var acc1: i64 = f32_of(0)
28 var acc2: i64 = f32_of(0); var acc3: i64 = f32_of(0)
29 let arow: i64 = i * k
30 var l: i64 = 0
31 while l + 4 <= k {
32 acc0 = f32_add(acc0, f32_mul(a[arow + l], b[l * n + j]))
33 acc1 = f32_add(acc1, f32_mul(a[arow + l + 1], b[(l + 1) * n + j]))
34 acc2 = f32_add(acc2, f32_mul(a[arow + l + 2], b[(l + 2) * n + j]))
35 acc3 = f32_add(acc3, f32_mul(a[arow + l + 3], b[(l + 3) * n + j]))
36 l = l + 4
37 }
38 while l < k { acc0 = f32_add(acc0, f32_mul(a[arow + l], b[l * n + j])); l = l + 1 }
39 c[i * n + j] = f32_add(f32_add(acc0, acc1), f32_add(acc2, acc3))
40 j = j + 1
41 }
42 i = i + 1
43 }
44 return NX_MAC4_OK
45}
46
47func mac4_time(which: i64, A: *i64, B: *i64, C: *i64, N: i64) -> i64 {
48 var reps: i64 = 1; var dt: i64 = 0; var go: i64 = 1
49 while go == 1 {
50 let t0: i64 = sys_now_ms()
51 var r: i64 = 0
52 while r < reps {
53 if which == 0 { nx_f32_hw_matmul(A, B, C, N, N, N) } else { nx_f32_hw_matmul_mac4(A, B, C, N, N, N) }
54 r = r + 1
55 }
56 let t1: i64 = sys_now_ms()
57 dt = t1 - t0
58 if dt >= 60 { go = 0 } else { if reps >= NX_MAGIC_32768 { go = 0 } else { reps = reps * 2 } }
59 }
60 if dt <= 0 { dt = 1 }
61 return (2 * N * N * N * reps) / (dt * 1000)
62}
63
64func main() -> i64 {
65 hmm_puts("=== ILP matmul: 4 accumulators (pure .nx, no compiler change) ===\n")
66 // ---- correctness KAT: mac4 == single-acc hw matmul, 8x8 small ints (exact) ----
67 let N: i64 = 8
68 let A: *i64 = sys_mmap(N * N * 8) as *i64
69 let B: *i64 = sys_mmap(N * N * 8) as *i64
70 let C0: *i64 = sys_mmap(N * N * 8) as *i64
71 let C4: *i64 = sys_mmap(N * N * 8) as *i64
72 var i: i64 = 0
73 while i < N * N { A[i] = f32_of((i % 5) + 1); B[i] = f32_of((i % 7) + 1); i = i + 1 }
74 nx_f32_hw_matmul(A, B, C0, N, N, N)
75 nx_f32_hw_matmul_mac4(A, B, C4, N, N, N)
76 var same: i64 = 1; i = 0
77 while i < N * N { if C0[i] != C4[i] { same = 0; i = N * N } else { i = i + 1 } }
78 if same == 1 { hmm_puts(" KAT mac4==single (8x8 exact): PASS\n") } else { hmm_puts(" KAT mac4==single: FAIL\n"); sys_exit(1); return 1 }
79
80 // ---- perf: single-acc vs 4-acc ----
81 var nn: i64 = 64
82 while nn <= 128 {
83 let A2: *i64 = sys_mmap(nn * nn * 8) as *i64
84 let B2: *i64 = sys_mmap(nn * nn * 8) as *i64
85 let C2: *i64 = sys_mmap(nn * nn * 8) as *i64
86 var z: i64 = 0
87 while z < nn * nn { A2[z] = f32_of(2); B2[z] = f32_of(2); z = z + 1 }
88 let s: i64 = mac4_time(0, A2, B2, C2, nn)
89 let m4: i64 = mac4_time(1, A2, B2, C2, nn)
90 var sp: i64 = 0; if s > 0 { sp = m4 * 100 / s }
91 hmm_puts(" N="); hmm_putn(nn)
92 hmm_puts(" single="); hmm_putn(s); hmm_puts(" MFLOP/s 4-acc="); hmm_putn(m4); hmm_puts(" MFLOP/s ratio="); hmm_putn(sp); hmm_puts("%\n")
93 nn = nn * 2
94 }
95 hmm_puts(" (ratio>100% => ILP latency-hiding works in this codegen; pure .nx, zero build risk.)\n")
96 sys_exit(0); return 0
97}