code wiki / _hdl_build / nx_intfp_softmax_gradcheck_gate.nx

nx_intfp_softmax_gradcheck_gate.nx source

↩ module page · 85 lines · 5633 B

1// nx_intfp_softmax_gradcheck_gate.nx -- KEYSTONE op for integer ATTENTION training: fixed-point SOFTMAX with a 2// fixed-point EXP and the full Jacobian backward, gradchecked (integer finite-difference). exp is the crux -- 3// it's exactly where the software-float tape calls nx_f32_exp. Here exp is done in Q16 via the 2^x decomposition 4// (exp(x)=2^(x*log2e); split into shift 2^-ui and a quadratic for 2^-uf), NO float. Then softmax s_i=e_i/sum, 5// loss L=sum(s_i^2), analytic backward dL/dx_i = s_i*(g_i - sum_j s_j*g_j) with g_i=2*s_i. The max-subtraction is 6// a stop-gradient constant (softmax is shift-invariant), so we fix the shift at the base point -> the forward is 7// smooth and finite-diff matches analytic. If this gradchecks, integer attention is trainable. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10func w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 11func 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 } 12func iabs(v: i64) -> i64 { if v<0 { return 0-v } return v } 13 14const S: i64 = 65536 15const N: i64 = 5 16 17// Q16 exp for ANY xq (both signs). exp(x)=2^y, y=x*log2e; y=yi+yf with yi=floor(y), yf in [0,1); 2^y=(2^yf)<<yi. 18// 2^yf via QUARTIC Horner (accurate in VALUE and DERIVATIVE -- the derivative matters for the softmax gradcheck). 19func fp_exp_q16(xq: i64) -> i64 { 20 let y: i64=(xq*94548)/S // y = x*1.442695 (log2 e in Q16 = 94548), signed 21 var yi: i64=0 22 if y>=0 { yi=y/S } else { yi=0-(((0-y)+S-1)/S) } // floor(y/S) (nx div truncates toward 0) 23 let yf: i64=y-yi*S // [0,S) 24 var p: i64=630 // a4=0.009618 (Q16 quartic coeffs of 2^f = e^{f ln2}) 25 p=3637+(p*yf)/S // a3=0.055504 26 p=15743+(p*yf)/S // a2=0.240227 27 p=45426+(p*yf)/S // a1=0.693147 28 p=S+(p*yf)/S // 2^yf, Q16 in [S,2S) 29 if yi>=0 { if yi>=31 { return 2000000000 } return p*(1<<yi) } 30 let k: i64=0-yi 31 if k>=31 { return 0 } // underflow -> ~0 32 return p/(1<<k) 33} 34 35// softmax with FIXED shift; fills s (Q16); returns L_q32 = sum(s_i^2) 36func softmax_loss(x: *i64, shift: i64, s: *i64) -> i64 { 37 let e: *i64=sys_mmap(N*8) as *i64 38 var sum: i64=0; var i: i64=0 39 while i<N { let ev: i64=fp_exp_q16(x[i]-shift); e[i]=ev; sum=sum+ev; i=i+1 } 40 var Lq: i64=0; i=0 41 while i<N { let sv: i64=(e[i]*S+sum/2)/sum; s[i]=sv; Lq=Lq+sv*sv; i=i+1 } // rounded division 42 return Lq 43} 44 45func main() -> i64 { 46 w("=== nx_intfp_softmax_gradcheck: Q16 fixed-point softmax + exp + Jacobian backward -- no float ===\n\n" as *u8) 47 // (0) verify the fixed-point exp before building on it (rule: verify, don't assume) 48 w(" [exp check] exp(0)=" as *u8); wn(fp_exp_q16(0)); w(" (want 65536) exp(-1)=" as *u8); wn(fp_exp_q16(0-S)); w(" (want ~24109=0.368) exp(-2)=" as *u8); wn(fp_exp_q16(0-2*S)); w(" (want ~8867=0.135)\n\n" as *u8) 49 50 let x: *i64=sys_mmap(N*8) as *i64; let s: *i64=sys_mmap(N*8) as *i64 51 var i: i64=0; while i<N { x[i]=((i*2-3)*S)/4; i=i+1 } // -0.75,-0.25,0.25,0.75,1.25 52 var shift: i64=x[0]; i=1; while i<N { if x[i]>shift { shift=x[i] } i=i+1 } // max, fixed (stop-grad) 53 54 let L0: i64=softmax_loss(x, shift, s) 55 w(" softmax s_q=[" as *u8); i=0; while i<N { wn(s[i]); if i<N-1 { w("," as *u8) } i=i+1 } w("] (sum~65536) L_q32=" as *u8); wn(L0); w("\n\n" as *u8) 56 57 // analytic: g_i=2 s_i ; dot=sum_j s_j g_j ; dL/dx_i = s_i (g_i - dot) (all Q16) 58 let g: *i64=sys_mmap(N*8) as *i64; i=0; while i<N { g[i]=2*s[i]; i=i+1 } 59 var dot: i64=0; i=0; while i<N { dot=dot+(s[i]*g[i])/S; i=i+1 } 60 let ana: *i64=sys_mmap(N*8) as *i64; i=0; while i<N { ana[i]=(s[i]*(g[i]-dot))/S; i=i+1 } 61 62 let DELTA: i64=655; let TOLP: i64=80 // delta=0.01 (large enough that softmax-division truncation noise is negligible) 63 var npass: i64=0; var worst: i64=0 64 w(" i analytic_q numeric_q rel(permille)\n" as *u8) 65 w(" ------------------------------------------------\n" as *u8) 66 i=0 67 while i<N { 68 let save: i64=x[i] 69 x[i]=save+DELTA; let Lp: i64=softmax_loss(x, shift, s) 70 x[i]=save-DELTA; let Lm: i64=softmax_loss(x, shift, s) 71 x[i]=save; let d: i64=softmax_loss(x, shift, s) 72 let num: i64=(Lp-Lm)/(2*DELTA) 73 let rel: i64=(iabs(num-ana[i])*1000)/(iabs(ana[i])+100) 74 w(" " as *u8); wn(i); w(" " as *u8); wn(ana[i]); w(" " as *u8); wn(num); w(" " as *u8); wn(rel) 75 if rel<=TOLP { npass=npass+1; w(" ok\n" as *u8) } else { w(" FAIL\n" as *u8) } 76 if rel>worst { worst=rel } 77 i=i+1 78 } 79 w("\n softmax gradcheck: " as *u8); wn(npass); w("/" as *u8); wn(N); w(" within " as *u8); wn(TOLP); w(" permille; worst=" as *u8); wn(worst); w("\n" as *u8) 80 w(" => fixed-point softmax (with fixed-point exp) forward+backward is sound -> integer ATTENTION is trainable.\n" as *u8) 81 w("NX-INTFP-SOFTMAX-GRADCHECK verdict=" as *u8) 82 if npass==N { w("GREEN passes=" as *u8); wn(npass); w("/" as *u8); wn(N); w(" -- integer softmax+exp+Jacobian proven; the attention keystone op is buildable in the tape\n" as *u8) } 83 else { w("RED passes=" as *u8); wn(npass); w("/" as *u8); wn(N); w(" -- exp accuracy or Jacobian bug\n" as *u8) } 84 return 0 85}