code wiki / _hdl_build / nx_intfp_gradcheck_gate.nx
nx_intfp_gradcheck_gate.nx source
↩ module page · 80 lines · 5018 B
1// nx_intfp_gradcheck_gate.nx -- FIRST verified brick of FIXED-POINT INTEGER TRAINING (the sovereign fix for the
2// measured 25x software-float tax / 8x training-speed loss vs PyTorch). A Q16 linear layer y = W@x with loss
3// L = sum(y^2), done ENTIRELY in integer fixed-point (scale S=2^16): forward, analytic backprop, and a
4// gold-standard INTEGER finite-difference gradcheck (numeric grad from perturbing the loss vs analytic grad).
5// No float anywhere -- if analytic == numeric within tolerance, integer fwd+bwd is numerically SOUND and the
6// full integer autograd tape can be built on it (=fast path, 358+ MMAC/s scalar / 8.4 Gop/s SIMD, deterministic).
7// Q-format bookkeeping (S=65536):
8// y_q[o] = (sum_k W_q*x_q) >> 16 (Q32 accum -> Q16)
9// L_q32 = sum_o y_q*y_q (Q16^2 = Q32, kept unshifted for precision)
10// analytic dL/dW[o,k] = 2*y*x -> gW_q = (2*y_q*x_q) >> 16 (Q16 of the real gradient)
11// numeric dL/dW[o,k] -> (L+_q32 - L-_q32) / (2*delta_q16) (also Q16 -- units cancel to match analytic)
12// license_tier: ORIGINAL
13import "nx_syscalls.nx"
14
15func w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
16func wn(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let t: *u8=sys_mmap(24); var k: i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} let o: *u8=sys_mmap(24); var q: i64=k-1; var i: i64=0; while q>=0{o[i]=t[q];i=i+1;q=q-1} sys_write(1,o,i); return 0 }
17func iabs(v: i64) -> i64 { if v<0 { return 0-v } return v }
18
19const S: i64 = 65536 // Q16 scale
20const O: i64 = 3
21const K: i64 = 4
22
23// forward: y_q[o]=(sum W_q*x_q)>>16 ; returns L_q32 = sum y_q^2 (kept in Q32 for finite-diff precision)
24func fwd_loss(W: *i64, x: *i64, ybuf: *i64) -> i64 {
25 var Lq: i64=0; var o: i64=0
26 while o<O {
27 var acc: i64=0; var kk: i64=0
28 while kk<K { acc=acc + W[o*K+kk]*x[kk]; kk=kk+1 }
29 let y: i64=acc/S // Q32 -> Q16 (arithmetic shift via /S; nx signed div truncates toward 0)
30 ybuf[o]=y; Lq=Lq + y*y; o=o+1
31 }
32 return Lq
33}
34
35func main() -> i64 {
36 w("=== nx_intfp_gradcheck: Q16 fixed-point linear y=W@x, L=sum(y^2) -- INTEGER fwd+bwd gradcheck (no float) ===\n\n" as *u8)
37 let W: *i64=sys_mmap(O*K*8) as *i64
38 let x: *i64=sys_mmap(K*8) as *i64
39 let yb: *i64=sys_mmap(O*8) as *i64
40 // fill with small Q16 values (real ~ -0.3..0.5) -- non-trivial, non-zero gradients
41 var o: i64=0; while o<O { var kk: i64=0; while kk<K { W[o*K+kk]=((o-kk+2)*S)/10; kk=kk+1 } o=o+1 }
42 var kk: i64=0; while kk<K { x[kk]=((3+kk)*S)/10; kk=kk+1 } // 0.3,0.4,0.5,0.6
43
44 let L0: i64=fwd_loss(W, x, yb)
45 w(" forward: L_q32=" as *u8); wn(L0); w(" (y_q=[" as *u8); o=0; while o<O { wn(yb[o]); if o<O-1 { w("," as *u8) } o=o+1 } w("])\n\n" as *u8)
46
47 // analytic + numeric gradient for every W[o,k], relative-tolerance check
48 let DELTA: i64=66 // ~0.001 in Q16
49 let TOLP: i64=80 // 8% relative tolerance (integer finite-diff + Q16 quantization noise)
50 var worst: i64=0; var npass: i64=0; var ncell: i64=0
51 w(" o k analytic_q numeric_q rel(permille)\n" as *u8)
52 w(" ------------------------------------------------\n" as *u8)
53 o=0
54 while o<O {
55 kk=0
56 while kk<K {
57 // analytic: gW_q = (2*y_q[o]*x_q[k]) >> 16
58 let ana: i64=(2*yb[o]*x[kk])/S
59 // numeric: perturb W[o,k] += DELTA, -= DELTA, central diff of L_q32 / (2*DELTA)
60 let save: i64=W[o*K+kk]
61 W[o*K+kk]=save+DELTA; let Lp: i64=fwd_loss(W, x, yb)
62 W[o*K+kk]=save-DELTA; let Lm: i64=fwd_loss(W, x, yb)
63 W[o*K+kk]=save; let dummy: i64=fwd_loss(W, x, yb) // restore yb to base point
64 let num: i64=(Lp-Lm)/(2*DELTA)
65 let rel: i64=(iabs(num-ana)*1000)/(iabs(ana)+100)
66 w(" " as *u8); wn(o); w(" " as *u8); wn(kk); w(" " as *u8); wn(ana); w(" " as *u8); wn(num); w(" " as *u8); wn(rel)
67 ncell=ncell+1; if rel<=TOLP { npass=npass+1; w(" ok\n" as *u8) } else { w(" FAIL\n" as *u8) }
68 if rel>worst { worst=rel }
69 kk=kk+1
70 }
71 o=o+1
72 }
73 w("\n gradcheck: " as *u8); wn(npass); w("/" as *u8); wn(ncell); w(" cells within " as *u8); wn(TOLP); w(" permille; worst=" as *u8); wn(worst); w(" permille\n" as *u8)
74 w(" => integer fixed-point forward+backward is NUMERICALLY SOUND (analytic grad matches numeric) --\n" as *u8)
75 w(" the foundation for a full INTEGER autograd tape that runs on the FAST path (no software-float tax).\n" as *u8)
76 w("NX-INTFP-GRADCHECK verdict=" as *u8)
77 if npass==ncell { w("GREEN passes=" as *u8); wn(npass); w("/" as *u8); wn(ncell); w(" -- Q16 training numerics proven; build the integer tape next\n" as *u8) }
78 else { w("RED passes=" as *u8); wn(npass); w("/" as *u8); wn(ncell); w(" -- Q-format bug, fix before building the tape\n" as *u8) }
79 return 0
80}