code wiki / _hdl_build / nx_nofloat_vs_float_gate.nx

nx_nofloat_vs_float_gate.nx source

↩ module page · 128 lines · 6876 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" 18import "nx_gate_verdict.nx" 19 20func 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 } 21func v_pn(v: i64) -> i64 { 22 let b: *u8 = sys_mmap(28); var x: i64 = v 23 if x < 0 { b[0]=45; sys_write(1,b,1); x = 0 - x } 24 if x == 0 { b[0]=48; sys_write(1,b,1); return 0 } 25 var d: i64=0; var y: i64=x 26 while y>0 { d=d+1; y=y/10 } 27 var i: i64=d-1; y=x 28 while i>=0 { b[i]=(48+(y%10)) as u8; y=y/10; i=i-1 } 29 sys_write(1,b,d); return 0 30} 31func 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 } 32func v_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 33 34// f32 (nx_autograd's IEEE-754 bits) -> round(v*1000) signed int, via nx_f32 ops only (linear advance; values O(1)). 35func f32_milli(v: i64) -> i64 { 36 var s: i64 = nx_f32_mul(v, nx_i32_to_f32(1000)) 37 var neg: i64 = 0 38 if nx_f32_lt(s, AG_F32_ZERO) == 1 { neg = 1; s = nx_f32_neg(s) } 39 let half: i64 = ag_constf(1, 2) 40 var m: i64 = 0; var go: i64 = 1 41 while go == 1 { 42 let mid: i64 = nx_f32_add(nx_i32_to_f32(m), half) 43 if nx_f32_lt(mid, s) == 1 { m = m + 1; if m >= 100000 { go = 0 } } else { go = 0 } 44 } 45 if neg == 1 { return 0 - m } 46 return m 47} 48func 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 } 49 50// FLOAT gradients (nx_autograd scalar f32 tape): loss=(relu(w1*x+b1)*w2+b2)^2 -> gf[0..3] (f32 bits) 51func float_grads(gf: *i64) -> i64 { 52 let tape: *i64 = sys_mmap(64*5*8) as *i64 53 let np: *i64 = sys_mmap(8) as *i64 54 *np = 0 55 let nw1: i64 = ag_leaf(tape, np, ag_constf(2,3)) 56 let nb1: i64 = ag_leaf(tape, np, ag_constf(1,4)) 57 let nw2: i64 = ag_leaf(tape, np, ag_constf(3,2)) 58 let nb2: i64 = ag_leaf(tape, np, ag_constf(1,4)) 59 let nx: i64 = ag_leaf(tape, np, ag_constf(3,4)) 60 let h1: i64 = ag_add(tape, np, ag_mul(tape, np, nw1, nx), nb1) 61 let r: i64 = ag_relu(tape, np, h1) 62 let h2: i64 = ag_add(tape, np, ag_mul(tape, np, r, nw2), nb2) 63 let loss: i64 = ag_mul(tape, np, h2, h2) 64 ag_backward(tape, *np, loss) 65 gf[0]=ag_grad(tape,nw1); gf[1]=ag_grad(tape,nb1); gf[2]=ag_grad(tape,nw2); gf[3]=ag_grad(tape,nb2) 66 return 0 67} 68 69// NO-FLOAT gradients (nx_nofloat_autograd Q16 tensor tape): same graph, loss=mse(h2,zero)=h2^2 -> gnf[0..3] (Q16) 70func nofloat_grads(gnf: *i64) -> i64 { 71 let tape: *i64 = sys_mmap(64*7*8) as *i64 72 let vals: *i64 = sys_mmap(256*8) as *i64 73 let grads: *i64 = sys_mmap(256*8) as *i64 74 let st: *i64 = sys_mmap(2*8) as *i64 75 let p: *i64 = sys_mmap(6*8) as *i64 76 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 77 st[0]=0; st[1]=0 78 let nw1: i64 = nfa_leaf(tape,vals,st,1,1,p,0) 79 let nb1: i64 = nfa_leaf(tape,vals,st,1,1,p,1) 80 let nw2: i64 = nfa_leaf(tape,vals,st,1,1,p,2) 81 let nb2: i64 = nfa_leaf(tape,vals,st,1,1,p,3) 82 let nx: i64 = nfa_leaf(tape,vals,st,1,1,p,4) 83 let h1m: i64 = nfa_matvec(tape,vals,st,nw1,nx) 84 let h1: i64 = nfa_vadd(tape,vals,st,h1m,nb1) 85 let r: i64 = nfa_relu(tape,vals,st,h1) 86 let h2m: i64 = nfa_matvec(tape,vals,st,nw2,r) 87 let h2: i64 = nfa_vadd(tape,vals,st,h2m,nb2) 88 let nz: i64 = nfa_leaf(tape,vals,st,1,1,p,5) 89 let loss: i64 = nfa_mse(tape,vals,st,h2,nz) 90 nfa_backward(tape,vals,grads,st[0],loss) 91 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) 92 return 0 93} 94 95func main() -> i64 { 96 v_puts("nx_nofloat_vs_float gate (no-float gradients CROSS-VALIDATED against the independent FLOAT autograd)\n" as *u8) 97 let gf: *i64 = sys_mmap(4*8) as *i64 98 let gnf: *i64 = sys_mmap(4*8) as *i64 99 float_grads(gf) 100 nofloat_grads(gnf) 101 102 let nm: *i64 = sys_mmap(4*8) as *i64 // names not needed; iterate 103 var pass: i64 = 0; var total: i64 = 0 104 let tol: i64 = 60 105 var i: i64 = 0 106 while i < 4 { 107 let fm: i64 = f32_milli(gf[i]) 108 let qm: i64 = q16_milli(gnf[i]) 109 let diff: i64 = v_abs(fm - qm) 110 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) 111 var ok: i64 = 0; if diff < tol { ok = 1 } 112 if ok == 1 { v_puts(" agree\n" as *u8) } else { v_puts(" MISMATCH\n" as *u8) } 113 pass = pass + ok; total = total + 1 114 i = i + 1 115 } 116 117 v_puts(" (true analytic grads in milli: w1=3094 b1=4125 w2=2063 b2=2750 -> both engines land here)\n" as *u8) 118 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) 119 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 120 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 121 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 122 let ctr__dry: *i64 = gv_ctr() 123 ctr__dry[0] = pass 124 ctr__dry[1] = total 125 let rc__dry: i64 = gv_verdict("NOFLOAT-VS-FLOAT-GATE" as *u8, ctr__dry, "the FLOAT autograd is the verification ORACLE -- it CONFIRMS the no-float gradients; no parallel float trainer needed: float = oracle + competitor, not product)" as *u8) 126 sys_exit(rc__dry) 127 return rc__dry 128}