code wiki / _hdl_build / nx_nofloat_autograd_gate.nx

nx_nofloat_autograd_gate.nx source

↩ module page · 229 lines · 13203 B

1// nx_nofloat_autograd_gate.nx -- HARD-EVIDENCE gate for the NO-FLOAT (integer Q16) general autograd 2// (nx_nofloat_autograd). Proves, by RUNNING in pure integer arithmetic, that the team can do general 3// reverse-mode backprop + train a model WITHOUT any float -- the genuine missing generation on the 4// no-float DeepMind ladder. Mirrors the proven nx_train_r1_gate evidence shape, but Q16-integer: 5// 6// A GRADCHECK (oracle = mathematics): loss = ( relu(w1*x+b1)*w2 + b2 )^2 at params OFF the relu kink 7// (w1=2/3, b1=1/4, w2=3/2, b2=1/4, x=3/4, all Q16). For each param: analytic grad (the tape's reverse 8// sweep) vs CENTRAL finite difference (L(p+h)-L(p-h))/2h, h=1/128, all in Q16. Relative error < 1/32 9// (floor 1/16). This exercises matvec + vadd + RELU + mse backward -> a GENERAL graph, not one layer. 10// D THE GRADCHECK HAS TEETH (neg-control): negate one analytic grad and assert the SAME check now FAILS 11// -- a wrong gradient is provably caught (so gate A is not vacuously passing). 12// B A MODEL PROVABLY LEARNS: the EXACT task nx_nn_train solves (linear layer W[1x2], target W*=[1.0,-0.5] 13// Q16, 4 samples), but trained by the GENERAL TAPE (matvec->mse summed, one reverse sweep, nfa_sgd) 14// instead of a hand-coded analytic gradient. Assert SSE loss collapses >=95% AND both weights converge 15// >=60% of the way to target with correct sign. The general autograd REPRODUCES the special trainer. 16// C BIT-EXACT REPRODUCIBLE: train twice from zero; assert the final integer weights are IDENTICAL. 17// Integer add is EXACTLY associative -> determinism is STRUCTURAL here (a stronger exceed-axis than the 18// f32 tower, which is only reproducible because it pins one summation order). 19// 20// Evidence -> knowledge/status/nofloat_autograd.log. Sovereign: imports nx_nofloat_autograd (pure integer; 21// NO nx_f32 anywhere in this organ's import graph) + nx_syscalls. license_tier: ORIGINAL expect_exit: 0 22import "nx_nofloat_autograd.nx" 23import "nx_g_check_lib.nx" 24import "nx_g_pn_lib.nx" 25import "nx_g_puts_lib.nx" 26import "nx_syscalls.nx" 27import "nx_gate_verdict.nx" 28 29const NLOG: *u8 = "knowledge/status/nofloat_autograd.log" 30const Q16: i64 = 65536 31 32func g_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 33func q_milli(q: i64) -> i64 { var neg: i64=0; var a: i64=q; if a<0 { neg=1; a=0-a } let m: i64=(a*1000)/Q16; if neg==1 { return 0-m } return m } 34 35// ---- evidence emit helpers (defined before nl_emit so it can call them) ---- 36func nl_ws(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 } 37func nl_wn(fd: i64, v: i64) -> i64 { 38 let b: *u8 = sys_mmap(28); var m: i64=v; if m<0 { sys_write(fd,"-" as *u8,1); m=0-m } 39 let t: *u8 = sys_mmap(28); var k: i64=0; if m==0 { t[0]=48; k=1 } 40 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 41 var i: i64=0; while i<k { b[i]=t[k-1-i]; i=i+1 } sys_write(fd,b,k); return 0 42} 43func nl_emit(fd: i64, r: *i64) -> i64 { 44 nl_ws(fd, "NOFLOATAUTOGRAD authored=organ engine=Q16-integer-tensor-tape-autograd" as *u8) 45 nl_ws(fd, " | A_gradcheck_pass=" as *u8); nl_wn(fd, r[0]); nl_ws(fd, " worst_rel_milli=" as *u8); nl_wn(fd, r[1]) 46 nl_ws(fd, " | D_negcontrol_caught=" as *u8); nl_wn(fd, r[2]) 47 nl_ws(fd, " | B_learns_pass=" as *u8); nl_wn(fd, r[3]) 48 nl_ws(fd, " w0_milli=" as *u8); nl_wn(fd, r[4]); nl_ws(fd, " w1_milli=" as *u8); nl_wn(fd, r[5]) 49 nl_ws(fd, " loss_first=" as *u8); nl_wn(fd, r[6]); nl_ws(fd, " loss_last=" as *u8); nl_wn(fd, r[7]) 50 nl_ws(fd, " | C_bitexact_pass=" as *u8); nl_wn(fd, r[8]) 51 if r[9]==1 { nl_ws(fd, " verdict=GREEN\n" as *u8) } else { nl_ws(fd, " verdict=RED\n" as *u8) } 52 return 0 53} 54 55// ---- Gate A graph: loss = ( relu(w1*x+b1)*w2 + b2 )^2, built on the general tape. p=[w1,b1,w2,b2,x,zero]. 56// loss = mse(h2, zero) reuses the squared-error head as a square. leaves[0..3] = the 4 param leaf nodes. 57func gA_loss(tape: *i64, vals: *i64, st: *i64, p: *i64, leaves: *i64) -> i64 { 58 st[0]=0; st[1]=0 59 let nw1: i64 = nfa_leaf(tape,vals,st,1,1,p,0) 60 let nb1: i64 = nfa_leaf(tape,vals,st,1,1,p,1) 61 let nw2: i64 = nfa_leaf(tape,vals,st,1,1,p,2) 62 let nb2: i64 = nfa_leaf(tape,vals,st,1,1,p,3) 63 let nx: i64 = nfa_leaf(tape,vals,st,1,1,p,4) 64 let h1m: i64 = nfa_matvec(tape,vals,st,nw1,nx) 65 let h1: i64 = nfa_vadd(tape,vals,st,h1m,nb1) 66 let rr: i64 = nfa_relu(tape,vals,st,h1) 67 let h2m: i64 = nfa_matvec(tape,vals,st,nw2,rr) 68 let h2: i64 = nfa_vadd(tape,vals,st,h2m,nb2) 69 let nz: i64 = nfa_leaf(tape,vals,st,1,1,p,5) 70 let loss: i64 = nfa_mse(tape,vals,st,h2,nz) 71 leaves[0]=nw1; leaves[1]=nb1; leaves[2]=nw2; leaves[3]=nb2 72 return loss 73} 74func gA_lossval(tape: *i64, vals: *i64, st: *i64, p: *i64) -> i64 { 75 let lv: *i64 = sys_mmap(4*8) as *i64 76 let loss: i64 = gA_loss(tape,vals,st,p,lv) 77 return nfa_val(tape,vals,loss,0) 78} 79func gA_analytic(tape: *i64, vals: *i64, grads: *i64, st: *i64, p: *i64, gout: *i64) -> i64 { 80 let lv: *i64 = sys_mmap(4*8) as *i64 81 let loss: i64 = gA_loss(tape,vals,st,p,lv) 82 nfa_backward(tape,vals,grads,st[0],loss) 83 gout[0]=nfa_grad(tape,grads,lv[0],0); gout[1]=nfa_grad(tape,grads,lv[1],0) 84 gout[2]=nfa_grad(tape,grads,lv[2],0); gout[3]=nfa_grad(tape,grads,lv[3],0) 85 return 0 86} 87// central finite-difference grad wrt param pi, Q16 step h: ((L(p+h)-L(p-h)) * Q16) / (2h) -> Q16 grad. 88func gA_fd(tape: *i64, vals: *i64, st: *i64, p: *i64, pi: i64, h: i64) -> i64 { 89 let pp: *i64 = sys_mmap(6*8) as *i64 90 let pm: *i64 = sys_mmap(6*8) as *i64 91 var i: i64 = 0 92 while i < 6 { pp[i]=p[i]; pm[i]=p[i]; i=i+1 } 93 pp[pi]=p[pi]+h; pm[pi]=p[pi]-h 94 let lp: i64 = gA_lossval(tape,vals,st,pp) 95 let lm: i64 = gA_lossval(tape,vals,st,pm) 96 return ((lp - lm) * Q16) / (2 * h) 97} 98 99// ---- Gate B/C: train W[1x2] to target W*=[1.0,-0.5] via the GENERAL tape. SSE over 4 samples. 100func gB_build(tape: *i64, vals: *i64, st: *i64, Wp: *i64, X: *i64, Y: *i64, T: i64, outW: *i64) -> i64 { 101 st[0]=0; st[1]=0 102 let nW: i64 = nfa_leaf(tape,vals,st,1,2,Wp,0) 103 outW[0]=nW 104 var root: i64 = 0 - 1 105 var t: i64 = 0 106 while t < T { 107 let nx: i64 = nfa_leaf(tape,vals,st,2,1,X,t*2) 108 let np: i64 = nfa_matvec(tape,vals,st,nW,nx) 109 let ny: i64 = nfa_leaf(tape,vals,st,1,1,Y,t) 110 let nm: i64 = nfa_mse(tape,vals,st,np,ny) 111 if root < 0 { root = nm } else { root = nfa_vadd(tape,vals,st,root,nm) } 112 t = t + 1 113 } 114 return root 115} 116func gB_train(tape: *i64, vals: *i64, grads: *i64, st: *i64, epochs: i64, lr_q: i64, Wout: *i64, lf: *i64, ll: *i64) -> i64 { 117 let T: i64 = 4 118 let Wt: *i64 = sys_mmap(2*8) as *i64; Wt[0]=65536; Wt[1]=0-32768 // target [1.0, -0.5] Q16 119 let X: *i64 = sys_mmap(T*2*8) as *i64 120 X[0]=65536; X[1]=65536; X[2]=131072; X[3]=32768; X[4]=32768; X[5]=131072; X[6]=98304; X[7]=65536 121 let Y: *i64 = sys_mmap(T*8) as *i64 122 var t: i64 = 0 123 while t < T { Y[t] = (Wt[0]*X[t*2] + Wt[1]*X[t*2+1]) >> 16; t = t + 1 } // same accumulate-shift as matvec 124 let Wp: *i64 = sys_mmap(2*8) as *i64; Wp[0]=0; Wp[1]=0 // learn from ZERO 125 let outW: *i64 = sys_mmap(8) as *i64 126 let g: *i64 = sys_mmap(2*8) as *i64 127 var ep: i64 = 0 128 while ep < epochs { 129 let root: i64 = gB_build(tape,vals,st,Wp,X,Y,T,outW) 130 nfa_backward(tape,vals,grads,st[0],root) 131 if ep == 0 { *lf = nfa_val(tape,vals,root,0) } 132 *ll = nfa_val(tape,vals,root,0) 133 g[0] = nfa_grad(tape,grads,outW[0],0) 134 g[1] = nfa_grad(tape,grads,outW[0],1) 135 nfa_sgd(Wp, g, 2, lr_q) 136 ep = ep + 1 137 } 138 Wout[0]=Wp[0]; Wout[1]=Wp[1] 139 return 0 140} 141 142func main() -> i64 { 143 g_puts("nx_nofloat_autograd gate (GENERAL reverse-mode autograd in PURE INTEGER Q16 -- no float, MEASURED)\n" as *u8) 144 var pass: i64 = 0; var total: i64 = 0 145 let tape: *i64 = sys_mmap(256*7*8) as *i64 146 let vals: *i64 = sys_mmap(2048*8) as *i64 147 let grads: *i64 = sys_mmap(2048*8) as *i64 148 let st: *i64 = sys_mmap(2*8) as *i64 149 150 // ---------- Gate A: gradcheck ---------- 151 let p: *i64 = sys_mmap(6*8) as *i64 152 p[0]=43691; p[1]=16384; p[2]=98304; p[3]=16384; p[4]=49152; p[5]=0 // w1=2/3,b1=1/4,w2=3/2,b2=1/4,x=3/4,zero 153 let ana: *i64 = sys_mmap(4*8) as *i64 154 gA_analytic(tape,vals,grads,st,p,ana) 155 let h: i64 = 512 // 1/128 in Q16 156 let tol_q: i64 = 2048 // 1/32 relative tolerance 157 let floor_q: i64 = 4096 // 1/16 grad-magnitude floor (so tiny grads don't blow up rel error) 158 var gradcheck_pass: i64 = 1 159 var worst_milli: i64 = 0 160 var pi: i64 = 0 161 while pi < 4 { 162 let fd: i64 = gA_fd(tape,vals,st,p,pi,h) 163 let num: i64 = g_abs(fd - ana[pi]) 164 var den: i64 = g_abs(ana[pi]); if den < floor_q { den = floor_q } 165 let thresh: i64 = (tol_q * den) >> 16 166 if num < thresh { g_puts(" [grad p" as *u8); g_pn(pi); g_puts("] ana_milli=" as *u8); g_pn(q_milli(ana[pi])); g_puts(" fd_milli=" as *u8); g_pn(q_milli(fd)); g_puts(" ok\n" as *u8) } else { gradcheck_pass = 0; g_puts(" [grad p" as *u8); g_pn(pi); g_puts("] ana_milli=" as *u8); g_pn(q_milli(ana[pi])); g_puts(" fd_milli=" as *u8); g_pn(q_milli(fd)); g_puts(" MISMATCH\n" as *u8) } 167 let rel_milli: i64 = (num * 1000) / den 168 if rel_milli > worst_milli { worst_milli = rel_milli } 169 pi = pi + 1 170 } 171 g_puts(" [measure] worst relative grad error = " as *u8); g_pn(worst_milli); g_puts(" / 1000 (tol=31/1000)\n" as *u8) 172 pass = pass + g_check("A: gradcheck -- tape reverse-sweep grads == finite differences (matvec+vadd+relu+mse)" as *u8, gradcheck_pass); total = total + 1 173 174 // ---------- Gate D: the gradcheck has teeth (neg-control) ---------- 175 // negate the analytic grad of param 0 and assert the SAME criterion now FAILS = a wrong grad is caught. 176 let fd0: i64 = gA_fd(tape,vals,st,p,0,h) 177 let bad: i64 = 0 - ana[0] 178 let num_bad: i64 = g_abs(fd0 - bad) 179 var den0: i64 = g_abs(ana[0]); if den0 < floor_q { den0 = floor_q } 180 let thresh0: i64 = (tol_q * den0) >> 16 181 var negcontrol_caught: i64 = 1 182 if num_bad < thresh0 { negcontrol_caught = 0 } 183 pass = pass + g_check("D: neg-control -- a deliberately WRONG gradient is rejected by the gradcheck (teeth)" as *u8, negcontrol_caught); total = total + 1 184 185 // ---------- Gate B: a model provably learns (general tape reproduces nx_nn_train's result) ---------- 186 let Wfin: *i64 = sys_mmap(2*8) as *i64 187 let lf: *i64 = sys_mmap(8) as *i64 188 let ll: *i64 = sys_mmap(8) as *i64 189 gB_train(tape,vals,grads,st, 4000, 2048, Wfin, lf, ll) 190 g_puts(" [measure] SSE loss: start=" as *u8); g_pn(*lf); g_puts(" after 4000 GD steps=" as *u8); g_pn(*ll); g_puts("\n" as *u8) 191 g_puts(" learned W=[" as *u8); g_pn(Wfin[0]); g_puts("," as *u8); g_pn(Wfin[1]); g_puts("] vs target [65536,-32768] (Q16; fixed-point-floor-limited)\n" as *u8) 192 var learns_pass: i64 = 1 193 if (*ll) * 20 > (*lf) { learns_pass = 0 } // >= 95% loss reduction 194 if Wfin[0] < 39322 { learns_pass = 0 } // w0 >= 60% of 65536 195 if Wfin[1] > 0 - 19661 { learns_pass = 0 } // w1 <= -60% of -32768, correct sign 196 if (*lf) <= 0 { learns_pass = 0 } // started genuinely untrained 197 pass = pass + g_check("B: a model LEARNS in pure integer -- SSE collapses >=95% and W converges to target" as *u8, learns_pass); total = total + 1 198 199 // ---------- Gate C: bit-exact reproducible ---------- 200 let Wfin2: *i64 = sys_mmap(2*8) as *i64 201 let lf2: *i64 = sys_mmap(8) as *i64 202 let ll2: *i64 = sys_mmap(8) as *i64 203 gB_train(tape,vals,grads,st, 4000, 2048, Wfin2, lf2, ll2) 204 var bitexact_pass: i64 = 1 205 if Wfin2[0] != Wfin[0] { bitexact_pass = 0 } 206 if Wfin2[1] != Wfin[1] { bitexact_pass = 0 } 207 pass = pass + g_check("C: bit-exact -- training twice from zero gives IDENTICAL integer weights (determinism)" as *u8, bitexact_pass); total = total + 1 208 209 // ---------- emit ---------- 210 var okall: i64 = 0 211 if pass == total { okall = 1 } 212 let r: *i64 = sys_mmap(10*8) as *i64 213 r[0]=gradcheck_pass; r[1]=worst_milli; r[2]=negcontrol_caught; r[3]=learns_pass 214 r[4]=q_milli(Wfin[0]); r[5]=q_milli(Wfin[1]); r[6]=(*lf); r[7]=(*ll); r[8]=bitexact_pass; r[9]=okall 215 nl_emit(1, r) 216 let logf: i64 = sys_openat_append(NLOG, 420) 217 if logf >= 0 { nl_emit(logf, r); sys_close(logf) } 218 219 g_puts("---- nofloat_autograd gate: passed " as *u8); g_pn(pass); g_puts(" / " as *u8); g_pn(total); g_puts(" ----\n" as *u8) 220 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 221 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 222 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 223 let ctr__dry: *i64 = gv_ctr() 224 ctr__dry[0] = pass 225 ctr__dry[1] = total 226 let rc__dry: i64 = gv_verdict("NOFLOAT-AUTOGRAD-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 227 sys_exit(rc__dry) 228 return rc__dry 229}