code wiki / _hdl_build / nx_autograd_gate.nx
nx_autograd_gate.nx source
↩ module page · 108 lines · 7321 B
1// nx_autograd_gate.nx -- SOVEREIGN general reverse-mode AUTOGRAD v1 (the R3.5 rung the substrate audit exposed:
2// nx_nn_train only does an ANALYTIC linear-layer gradient; deep learned transforms need general backprop through
3// nonlinearities). This is a fixed-point (Q16) computational-graph autodiff: a tape of nodes (val,grad,op,inputs),
4// eager forward on build, and a reverse pass that accumulates gradients via the chain rule through ADD/MUL/RELU.
5// Reverse-mode autodiff is foundational 1970s math (Linnainmaa) -- unpatentable -- so this is FTO-clean by
6// construction and research-independent. LIAR-KILL = NUMERICAL GRADIENT CHECK: autograd grads must match central
7// finite differences (a wrong backward cannot match). No FPU/GPU/3rd-party autograd; nx_cc->nxasm. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_gate_emit_lib.nx"
10
11// node layout: 5 i64 fields per node in one flat tape -- [0]=val(Q16) [1]=grad(Q16) [2]=op [3]=inA [4]=inB
12// op: 0=LEAF 1=ADD 2=MUL 3=RELU
13const Q: i64 = 65536
14
15func g_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=(48 as u8);k=1}; while m>0{t[k]=((48+(m%10)) as u8);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 }
16func g_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 }
17func g_wn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=(48 as u8);k=1}; while m>0{t[k]=((48+(m%10)) as u8);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
18func iabs(v: i64) -> i64 { if v<0 { return 0-v } return v }
19
20// --- autograd primitives over a flat tape (tp) with node count np[0] ---
21func ag_leaf(tp: *i64, np: *i64, v: i64) -> i64 { let k: i64=np[0]; tp[k*5]=v; tp[k*5+2]=0; tp[k*5+3]=0-1; tp[k*5+4]=0-1; np[0]=k+1; return k }
22func ag_add(tp: *i64, np: *i64, i: i64, j: i64) -> i64 { let k: i64=np[0]; tp[k*5]=tp[i*5]+tp[j*5]; tp[k*5+2]=1; tp[k*5+3]=i; tp[k*5+4]=j; np[0]=k+1; return k }
23func ag_mul(tp: *i64, np: *i64, i: i64, j: i64) -> i64 { let k: i64=np[0]; tp[k*5]=(tp[i*5]*tp[j*5])>>16; tp[k*5+2]=2; tp[k*5+3]=i; tp[k*5+4]=j; np[0]=k+1; return k }
24func ag_relu(tp: *i64, np: *i64, i: i64) -> i64 { let k: i64=np[0]; var v: i64=tp[i*5]; if v<0 { v=0 } tp[k*5]=v; tp[k*5+2]=3; tp[k*5+3]=i; tp[k*5+4]=0-1; np[0]=k+1; return k }
25// reverse pass: seed dL/dout = 1.0, accumulate gradients backward through the tape
26func ag_backward(tp: *i64, np: *i64, out: i64) -> i64 {
27 var i: i64=0; while i<np[0] { tp[i*5+1]=0; i=i+1 }
28 tp[out*5+1]=Q
29 i=np[0]-1
30 while i>=0 {
31 let g: i64=tp[i*5+1]; let o: i64=tp[i*5+2]; let ia: i64=tp[i*5+3]; let ib: i64=tp[i*5+4]
32 if o==1 { tp[ia*5+1]=tp[ia*5+1]+g; tp[ib*5+1]=tp[ib*5+1]+g }
33 if o==2 { tp[ia*5+1]=tp[ia*5+1]+((g*tp[ib*5])>>16); tp[ib*5+1]=tp[ib*5+1]+((g*tp[ia*5])>>16) }
34 if o==3 { if tp[ia*5]>0 { tp[ia*5+1]=tp[ia*5+1]+g } }
35 i=i-1
36 }
37 return 0
38}
39// build L = relu(a*b + a) for given leaf values, return the value of L (Q16). leaves are nodes 0 (a) and 1 (b).
40func build_L(tp: *i64, np: *i64, av: i64, bv: i64) -> i64 {
41 np[0]=0
42 let a: i64=ag_leaf(tp, np, av)
43 let b: i64=ag_leaf(tp, np, bv)
44 let ab: i64=ag_mul(tp, np, a, b)
45 let s: i64=ag_add(tp, np, ab, a)
46 let L: i64=ag_relu(tp, np, s)
47 return L
48}
49
50// numerical central-difference gradient of L w.r.t. leaf 0 (a) at (av,bv), returned in Q16
51func numgrad_a(tp: *i64, np: *i64, av: i64, bv: i64, eps: i64) -> i64 {
52 let Lp: i64=tp[build_L(tp, np, av+eps, bv)*5]
53 let Lm: i64=tp[build_L(tp, np, av-eps, bv)*5]
54 return ((Lp-Lm)*Q)/(2*eps)
55}
56func numgrad_b(tp: *i64, np: *i64, av: i64, bv: i64, eps: i64) -> i64 {
57 let Lp: i64=tp[build_L(tp, np, av, bv+eps)*5]
58 let Lm: i64=tp[build_L(tp, np, av, bv-eps)*5]
59 return ((Lp-Lm)*Q)/(2*eps)
60}
61// pass iff |auto-num| <= 5% of |auto| + small abs slack (fixed-point rounding)
62func close(auto: i64, num: i64) -> i64 { let d: i64=iabs(auto-num); let tol: i64=(iabs(auto)*5)/100 + 64; if d<=tol { return 1 } return 0 }
63
64func main() -> i64 {
65 g_puts("=== AUTOGRAD GATE: sovereign reverse-mode autodiff, gradient-checked vs finite differences ===\n" as *u8)
66 let tp: *i64=sys_mmap(64*5*8) as *i64
67 let np: *i64=sys_mmap(8) as *i64
68 let eps: i64=655 // 0.01 in Q16
69 var pass: i64=0; let rows: i64=6
70
71 // TEST 1: relu ACTIVE. a=1.5, b=2.0 -> L=relu(1.5*2+1.5)=relu(4.5)=4.5; dL/da=b+1=3.0, dL/db=a=1.5
72 let a1: i64=(3*Q)/2; let b1: i64=2*Q
73 let L1: i64=build_L(tp, np, a1, b1)
74 let Lval: i64=tp[L1*5]
75 ag_backward(tp, np, L1)
76 let ga: i64=tp[0*5+1]; let gb: i64=tp[1*5+1]
77 let na: i64=numgrad_a(tp, np, a1, b1, eps); let nb: i64=numgrad_b(tp, np, a1, b1, eps)
78 // rebuild after numgrad clobbered the tape, so the printed forward value is the real one
79 g_puts("-- T1 relu ACTIVE: L=" as *u8); g_num(Lval); g_puts("/Q (expect 294912=4.5)\n" as *u8)
80 g_puts(" d/da auto=" as *u8); g_num(ga); g_puts(" num=" as *u8); g_num(na); g_puts(" (expect ~196608=3.0)\n" as *u8)
81 g_puts(" d/db auto=" as *u8); g_num(gb); g_puts(" num=" as *u8); g_num(nb); g_puts(" (expect ~98304=1.5)\n" as *u8)
82 var t1a: i64=0; if Lval==294912 { t1a=1 }
83 pass=pass+g_check(" T1a forward L = relu(a*b+a) exact (4.5)" as *u8, t1a)
84 pass=pass+g_check(" T1b d/da autograd == finite-diff (chain rule thru MUL+ADD+RELU)" as *u8, close(ga, na))
85 pass=pass+g_check(" T1c d/db autograd == finite-diff" as *u8, close(gb, nb))
86
87 // TEST 2: relu INACTIVE. a=1.5, b=-2.0 -> a*b+a = -1.5 -> relu=0; gradients must be 0 (relu zero-region)
88 let a2: i64=(3*Q)/2; let b2: i64=0-(2*Q)
89 let L2: i64=build_L(tp, np, a2, b2)
90 let Lval2: i64=tp[L2*5]
91 ag_backward(tp, np, L2)
92 let ga2: i64=tp[0*5+1]; let gb2: i64=tp[1*5+1]
93 g_puts("-- T2 relu INACTIVE: L=" as *u8); g_num(Lval2); g_puts(" (expect 0); d/da auto=" as *u8); g_num(ga2); g_puts(" d/db auto=" as *u8); g_num(ga2); g_puts("\n" as *u8)
94 var t2a: i64=0; if Lval2==0 { t2a=1 }
95 pass=pass+g_check(" T2a forward clamps to 0 (relu off)" as *u8, t2a)
96 var t2b: i64=0; if ga2==0 { if gb2==0 { t2b=1 } }
97 pass=pass+g_check(" T2b gradients are 0 through an inactive relu (correct zero-region)" as *u8, t2b)
98
99 // NEG control: a deliberately-wrong gradient must FAIL close() -- proves the check is a real liar-kill
100 var t3: i64=0; if close(ga, na+50000)==0 { t3=1 }
101 pass=pass+g_check(" T3 NEG: a wrong gradient is REJECTED by the finite-diff check (liar-kill armed)" as *u8, t3)
102
103 g_puts("----\nAUTOGRAD rows=" as *u8); g_num(rows); g_puts(" pass=" as *u8); g_num(pass); g_puts("\n" as *u8)
104 let lg: i64=sys_openat_append("knowledge/status/autograd_gate.log" as *u8, 0x1a4)
105 if lg>=0 { g_w(lg, "AUTOGRAD rows=" as *u8); g_wn(lg, rows); g_w(lg, " pass=" as *u8); g_wn(lg, pass); if pass==rows { g_w(lg, " verdict=GREEN\n" as *u8) } else { g_w(lg, " verdict=RED\n" as *u8) } sys_close(lg) }
106 if pass==rows { g_puts("AUTOGRAD GREEN (general reverse-mode autodiff proven by gradient check -- R3.5 keystone for learned transforms)\n" as *u8); sys_exit(0); return 0 }
107 g_puts("AUTOGRAD RED\n" as *u8); sys_exit(1); return 1
108}