code wiki / _hdl_build / nx_fnet_grad_gate.nx
nx_fnet_grad_gate.nx source
↩ module page · 170 lines · 6980 B
1// nx_fnet_grad_gate.nx -- GATE for FNET-002: the FNet mixer's backward pass (VJP). Proves, by RUNNING, that
2// the mixer is SELF-ADJOINT, so dL/dx = fnet_mix(dL/dy) -- backprop is one forward mix, zero stored
3// activations, zero parameters. Three independent proofs (n=8, d=4 -> a 32-dim operator):
4//
5// G1 SYMMETRY extract the operator matrix M by impulse responses (M[:,k] = fnet_mix(S*e_k)); the
6// real-linear FNet operator must be SYMMETRIC (A = A^T) because it is the real part of a
7// Kronecker product of symmetric DFT matrices. Measure max|M[i,j]-M[j,i]|.
8// G2 ADJOINT TEST the relationship backprop relies on: <A x, g> == <x, A* g> with A* = A. Measure
9// |<Ax,g> - <x,Ag>| -- zero (to rounding) IFF using fnet_mix as the backward is the
10// correct VJP.
11// G3 GRADCHECK finite-difference the scalar loss L(x)=sum a*fnet_mix(x): central differences vs the
12// analytic backward fnet_mix_backward(a). Max |fd - analytic|.
13//
14// All three are robust to the Q14 fixed-point rounding (tolerances are a small % of signal, actuals reported).
15// Evidence -> knowledge/status/fnet_grad.log (FNETGRADGATE authored=organ ... verdict=GREEN). license_tier: ORIGINAL
16import "nx_fnet_mix.nx"
17import "nx_syscalls.nx"
18
19const FG_LOG: *u8 = "knowledge/status/fnet_grad.log"
20
21func fg_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
22func fg_wn(fd: i64, v: i64) -> i64 {
23 let bb: *u8 = sys_mmap(28); var m: i64 = v
24 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
25 let t: *u8 = sys_mmap(28); var k: i64 = 0
26 if m == 0 { t[0] = 48; k = 1 }
27 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
28 var i: i64 = 0
29 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
30 sys_write(fd, bb, k); return 0
31}
32func fg_abs(x: i64) -> i64 { if x < 0 { return 0 - x } return x }
33
34// real part of fnet_mix applied to a real vector src[nd] -> out[nd] (fresh buffers; src untouched).
35func fg_forward(src: *i64, n: i64, d: i64, out: *i64) -> i64 {
36 let nd: i64 = n * d
37 let xr: *i64 = (sys_mmap(nd * 8)) as *i64
38 let xi: *i64 = (sys_mmap(nd * 8)) as *i64
39 var i: i64 = 0
40 while i < nd { xr[i] = src[i]; xi[i] = 0; i = i + 1 }
41 let opc: *i64 = (sys_mmap(8)) as *i64
42 *opc = 0
43 fnet_mix(xr, xi, n, d, opc)
44 i = 0
45 while i < nd { out[i] = xr[i]; i = i + 1 }
46 return 0
47}
48
49func main() -> i64 {
50 let n: i64 = 8
51 let d: i64 = 4
52 let nd: i64 = n * d
53 var ok: i64 = 1
54
55 // ---------- G1: extract operator matrix by impulse responses; measure asymmetry ----------
56 let S: i64 = 65536 // large impulse so Q14 truncation is negligible vs entries
57 let mtx: *i64 = (sys_mmap(nd * nd * 8)) as *i64
58 let e: *i64 = (sys_mmap(nd * 8)) as *i64
59 let yk: *i64 = (sys_mmap(nd * 8)) as *i64
60 var col: i64 = 0
61 while col < nd {
62 var i: i64 = 0
63 while i < nd { e[i] = 0; i = i + 1 }
64 e[col] = S
65 fg_forward(e, n, d, yk)
66 i = 0
67 while i < nd { mtx[i * nd + col] = yk[i]; i = i + 1 }
68 col = col + 1
69 }
70 var asym: i64 = 0
71 var symmax: i64 = 0
72 var r: i64 = 0
73 while r < nd {
74 var c: i64 = 0
75 while c < nd {
76 let av: i64 = fg_abs(mtx[r * nd + c])
77 if av > symmax { symmax = av }
78 let df: i64 = fg_abs(mtx[r * nd + c] - mtx[c * nd + r])
79 if df > asym { asym = df }
80 c = c + 1
81 }
82 r = r + 1
83 }
84
85 // ---------- G2: adjoint dot-product test <Ax,g> vs <x,Ag> ----------
86 let xv: *i64 = (sys_mmap(nd * 8)) as *i64
87 let gv: *i64 = (sys_mmap(nd * 8)) as *i64
88 var j: i64 = 0
89 while j < nd {
90 xv[j] = (((j * 37 + 11) % 211) - 105) * 15
91 gv[j] = (((j * 53 + 7) % 197) - 98) * 15
92 j = j + 1
93 }
94 let ax: *i64 = (sys_mmap(nd * 8)) as *i64
95 let ag: *i64 = (sys_mmap(nd * 8)) as *i64
96 fg_forward(xv, n, d, ax)
97 fg_forward(gv, n, d, ag)
98 var lhs: i64 = 0
99 var rhs: i64 = 0
100 j = 0
101 while j < nd { lhs = lhs + ax[j] * gv[j]; rhs = rhs + xv[j] * ag[j]; j = j + 1 }
102 let adjdiff: i64 = fg_abs(lhs - rhs)
103
104 // ---------- G3: finite-difference gradcheck of the self-adjoint backward ----------
105 let a: *i64 = (sys_mmap(nd * 8)) as *i64 // loss weights (also the upstream gradient dL/dy)
106 let x0: *i64 = (sys_mmap(nd * 8)) as *i64
107 j = 0
108 while j < nd {
109 a[j] = (((j * 29 + 13) % 131) - 65) * 64
110 x0[j] = (((j * 41 + 5) % 233) - 116) * 18
111 j = j + 1
112 }
113 let analytic: *i64 = (sys_mmap(nd * 8)) as *i64
114 fg_forward(a, n, d, analytic) // dL/dx = fnet_mix_backward(a) = fnet_mix(a)
115
116 let eps: i64 = 512
117 let xp: *i64 = (sys_mmap(nd * 8)) as *i64
118 let xm: *i64 = (sys_mmap(nd * 8)) as *i64
119 let yp: *i64 = (sys_mmap(nd * 8)) as *i64
120 let ym: *i64 = (sys_mmap(nd * 8)) as *i64
121 var fdmax: i64 = 0
122 var fdana: i64 = 0
123 var k: i64 = 0
124 while k < nd {
125 var i: i64 = 0
126 while i < nd { xp[i] = x0[i]; xm[i] = x0[i]; i = i + 1 }
127 xp[k] = xp[k] + eps
128 xm[k] = xm[k] - eps
129 fg_forward(xp, n, d, yp)
130 fg_forward(xm, n, d, ym)
131 var lp: i64 = 0
132 var lm: i64 = 0
133 i = 0
134 while i < nd { lp = lp + a[i] * yp[i]; lm = lm + a[i] * ym[i]; i = i + 1 }
135 let fd: i64 = (lp - lm) / (2 * eps)
136 let df: i64 = fg_abs(fd - analytic[k])
137 if df > fdmax { fdmax = df }
138 let av: i64 = fg_abs(analytic[k])
139 if av > fdana { fdana = av }
140 k = k + 1
141 }
142
143 // ---------- verdicts (tolerances = small % of signal, robust to fixed-point rounding) ----------
144 if symmax <= 0 { ok = 0 }
145 if fdana <= 0 { ok = 0 }
146 if asym * 100 > symmax { ok = 0 } // G1: asymmetry < 1% of peak entry
147 if adjdiff * 100 > fg_abs(lhs) { ok = 0 } // G2: adjoint residual < 1% of <Ax,g>
148 if fdmax * 20 > fdana { ok = 0 } // G3: gradcheck error < 5% of peak gradient
149
150 // emit
151 var f: i64 = 1
152 while f >= 0 {
153 let fd2: i64 = f
154 var lf: i64 = 1
155 if f == 0 { lf = sys_openat_append(FG_LOG, 420) }
156 if lf >= 0 {
157 fg_w(lf, "FNETGRADGATE authored=organ kernel=fnet-backward n=8 d=4 self-adjoint" as *u8)
158 fg_w(lf, " G1_asym=" as *u8); fg_wn(lf, asym); fg_w(lf, " sym_peak=" as *u8); fg_wn(lf, symmax)
159 fg_w(lf, " | G2_adj_lhs=" as *u8); fg_wn(lf, lhs); fg_w(lf, " adj_rhs=" as *u8); fg_wn(lf, rhs)
160 fg_w(lf, " adj_residual=" as *u8); fg_wn(lf, adjdiff)
161 fg_w(lf, " | G3_gradcheck_maxdiff=" as *u8); fg_wn(lf, fdmax); fg_w(lf, " grad_peak=" as *u8); fg_wn(lf, fdana)
162 if ok == 1 { fg_w(lf, " verdict=GREEN\n" as *u8) } else { fg_w(lf, " verdict=RED\n" as *u8) }
163 if f == 0 { sys_close(lf) }
164 }
165 f = f - 1
166 }
167
168 if ok == 1 { return 0 }
169 return 1
170}