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}