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}