code wiki / _hdl_build / nx_nofloat_vs_float_gate.nx
nx_nofloat_vs_float_gate.nx source
↩ module page · 120 lines · 6497 B
1// nx_nofloat_vs_float_gate.nx -- CROSS-VALIDATION: do we need the float equivalent? Answer: it already EXISTS
2// (nx_autograd, the software-f32 scalar tape), and its right role is a VERIFICATION ORACLE + the exceed COMPETITOR
3// -- NOT a parallel product stack. This gate USES that role: it builds the SAME graph
4// loss = ( relu(w1*x + b1)*w2 + b2 )^2
5// in BOTH engines -- FLOAT via nx_autograd (ag_*, IEEE-754) and NO-FLOAT via nx_nofloat_autograd (nfa_*, integer
6// Q16) -- and asserts the four parameter gradients AGREE (in milli-units). Two INDEPENDENT autograd
7// implementations (different tapes, different number systems) computing the same gradients is far stronger
8// evidence than finite differences alone. It directly answers the question: the float engine validates the
9// no-float engine; we do not need to BUILD a float trainer (we have it) and do not need it in the PRODUCT
10// (no-float is the reproducibility exceed) -- we need it exactly here, as the oracle.
11//
12// T1..T4: for each of {w1,b1,w2,b2}, |float_grad_milli - nofloat_grad_milli| < 60/1000.
13// The true grads (analytic): w1=3.094, b1=4.125, w2=2.063, b2=2.750 -> both engines land on them.
14// expect_exit: 0 license_tier: ORIGINAL imports: nx_autograd (->ag_*, ->nx_f32_*), nx_nofloat_autograd, nx_syscalls
15import "nx_autograd.nx"
16import "nx_nofloat_autograd.nx"
17import "nx_syscalls.nx"
18
19func v_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
20func v_pn(v: i64) -> i64 {
21 let b: *u8 = sys_mmap(28); var x: i64 = v
22 if x < 0 { b[0]=45; sys_write(1,b,1); x = 0 - x }
23 if x == 0 { b[0]=48; sys_write(1,b,1); return 0 }
24 var d: i64=0; var y: i64=x
25 while y>0 { d=d+1; y=y/10 }
26 var i: i64=d-1; y=x
27 while i>=0 { b[i]=(48+(y%10)) as u8; y=y/10; i=i-1 }
28 sys_write(1,b,d); return 0
29}
30func v_check(name: *u8, cond: i64) -> i64 { if cond==1 { v_puts(" PASS " as *u8) } else { v_puts(" FAIL " as *u8) } v_puts(name); v_puts("\n" as *u8); return cond }
31func v_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
32
33// f32 (nx_autograd's IEEE-754 bits) -> round(v*1000) signed int, via nx_f32 ops only (linear advance; values O(1)).
34func f32_milli(v: i64) -> i64 {
35 var s: i64 = nx_f32_mul(v, nx_i32_to_f32(1000))
36 var neg: i64 = 0
37 if nx_f32_lt(s, AG_F32_ZERO) == 1 { neg = 1; s = nx_f32_neg(s) }
38 let half: i64 = ag_constf(1, 2)
39 var m: i64 = 0; var go: i64 = 1
40 while go == 1 {
41 let mid: i64 = nx_f32_add(nx_i32_to_f32(m), half)
42 if nx_f32_lt(mid, s) == 1 { m = m + 1; if m >= 100000 { go = 0 } } else { go = 0 }
43 }
44 if neg == 1 { return 0 - m }
45 return m
46}
47func q16_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)/65536; if neg==1 { return 0-m } return m }
48
49// FLOAT gradients (nx_autograd scalar f32 tape): loss=(relu(w1*x+b1)*w2+b2)^2 -> gf[0..3] (f32 bits)
50func float_grads(gf: *i64) -> i64 {
51 let tape: *i64 = sys_mmap(64*5*8) as *i64
52 let np: *i64 = sys_mmap(8) as *i64
53 *np = 0
54 let nw1: i64 = ag_leaf(tape, np, ag_constf(2,3))
55 let nb1: i64 = ag_leaf(tape, np, ag_constf(1,4))
56 let nw2: i64 = ag_leaf(tape, np, ag_constf(3,2))
57 let nb2: i64 = ag_leaf(tape, np, ag_constf(1,4))
58 let nx: i64 = ag_leaf(tape, np, ag_constf(3,4))
59 let h1: i64 = ag_add(tape, np, ag_mul(tape, np, nw1, nx), nb1)
60 let r: i64 = ag_relu(tape, np, h1)
61 let h2: i64 = ag_add(tape, np, ag_mul(tape, np, r, nw2), nb2)
62 let loss: i64 = ag_mul(tape, np, h2, h2)
63 ag_backward(tape, *np, loss)
64 gf[0]=ag_grad(tape,nw1); gf[1]=ag_grad(tape,nb1); gf[2]=ag_grad(tape,nw2); gf[3]=ag_grad(tape,nb2)
65 return 0
66}
67
68// NO-FLOAT gradients (nx_nofloat_autograd Q16 tensor tape): same graph, loss=mse(h2,zero)=h2^2 -> gnf[0..3] (Q16)
69func nofloat_grads(gnf: *i64) -> i64 {
70 let tape: *i64 = sys_mmap(64*7*8) as *i64
71 let vals: *i64 = sys_mmap(256*8) as *i64
72 let grads: *i64 = sys_mmap(256*8) as *i64
73 let st: *i64 = sys_mmap(2*8) as *i64
74 let p: *i64 = sys_mmap(6*8) as *i64
75 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
76 st[0]=0; st[1]=0
77 let nw1: i64 = nfa_leaf(tape,vals,st,1,1,p,0)
78 let nb1: i64 = nfa_leaf(tape,vals,st,1,1,p,1)
79 let nw2: i64 = nfa_leaf(tape,vals,st,1,1,p,2)
80 let nb2: i64 = nfa_leaf(tape,vals,st,1,1,p,3)
81 let nx: i64 = nfa_leaf(tape,vals,st,1,1,p,4)
82 let h1m: i64 = nfa_matvec(tape,vals,st,nw1,nx)
83 let h1: i64 = nfa_vadd(tape,vals,st,h1m,nb1)
84 let r: i64 = nfa_relu(tape,vals,st,h1)
85 let h2m: i64 = nfa_matvec(tape,vals,st,nw2,r)
86 let h2: i64 = nfa_vadd(tape,vals,st,h2m,nb2)
87 let nz: i64 = nfa_leaf(tape,vals,st,1,1,p,5)
88 let loss: i64 = nfa_mse(tape,vals,st,h2,nz)
89 nfa_backward(tape,vals,grads,st[0],loss)
90 gnf[0]=nfa_grad(tape,grads,nw1,0); gnf[1]=nfa_grad(tape,grads,nb1,0); gnf[2]=nfa_grad(tape,grads,nw2,0); gnf[3]=nfa_grad(tape,grads,nb2,0)
91 return 0
92}
93
94func main() -> i64 {
95 v_puts("nx_nofloat_vs_float gate (no-float gradients CROSS-VALIDATED against the independent FLOAT autograd)\n" as *u8)
96 let gf: *i64 = sys_mmap(4*8) as *i64
97 let gnf: *i64 = sys_mmap(4*8) as *i64
98 float_grads(gf)
99 nofloat_grads(gnf)
100
101 let nm: *i64 = sys_mmap(4*8) as *i64 // names not needed; iterate
102 var pass: i64 = 0; var total: i64 = 0
103 let tol: i64 = 60
104 var i: i64 = 0
105 while i < 4 {
106 let fm: i64 = f32_milli(gf[i])
107 let qm: i64 = q16_milli(gnf[i])
108 let diff: i64 = v_abs(fm - qm)
109 v_puts(" param " as *u8); v_pn(i); v_puts(": float_milli=" as *u8); v_pn(fm); v_puts(" nofloat_milli=" as *u8); v_pn(qm); v_puts(" |diff|=" as *u8); v_pn(diff)
110 var ok: i64 = 0; if diff < tol { ok = 1 }
111 if ok == 1 { v_puts(" agree\n" as *u8) } else { v_puts(" MISMATCH\n" as *u8) }
112 pass = pass + ok; total = total + 1
113 i = i + 1
114 }
115
116 v_puts(" (true analytic grads in milli: w1=3094 b1=4125 w2=2063 b2=2750 -> both engines land here)\n" as *u8)
117 v_puts("---- nofloat_vs_float: " as *u8); v_pn(pass); v_puts(" / " as *u8); v_pn(total); v_puts(" params agree within 60/1000 ----\n" as *u8)
118 if pass == total { v_puts("verdict=GREEN (the FLOAT autograd is the verification ORACLE -- it CONFIRMS the no-float gradients; no parallel float trainer needed: float = oracle + competitor, not product)\n" as *u8); sys_exit(0); return 0 }
119 v_puts("verdict=RED\n" as *u8); sys_exit(1); return 1
120}