code wiki / _hdl_build / nx_nofloat_attn_gate.nx
nx_nofloat_attn_gate.nx source
↩ module page · 252 lines · 14255 B
1// nx_nofloat_attn_gate.nx -- HARD-EVIDENCE gate for the ATTENTION-CORE backward (CAP-NF-ATTN-CORE): a single-head
2// CAUSAL self-attention block backprops end-to-end in PURE INTEGER Q16. This is the heart of CAP-NF-TRAIN-ATTN.
3// New ops proven: matmul (C=A.B), matmul_nt (S=Q.K^T), cmul (1/sqrt(d) scale), causal softmax_rows.
4//
5// A1 matmul gradcheck : C=A.B, loss=mse(C,t); tape grad dL/dA == central finite difference.
6// A2 matmul_nt gradcheck : S=A.B^T (the Q.K^T form); tape grad == finite difference.
7// A3 attention-core gradcheck: full path X->{Q,K,V}=X.W -> S=Q.K^T -> scale -> CAUSAL softmax rows -> O=A.V ->
8// mse; gradcheck dL/dWq, the gradient that flows THROUGH softmax + both matmuls (the real attention backward).
9// D neg-control teeth : a deliberately wrong matmul grad is rejected.
10// B attention LEARNS : with Wq,Wk fixed (attention pattern A fixed), the VALUE path O=A.(X.Wv) is linear ->
11// train Wv from zero to a realizable target; assert loss collapses + Wv converges (the value projection trains).
12// C bit-exact : train twice -> identical integer Wv (determinism is structural for integer).
13//
14// Evidence -> knowledge/status/nofloat_attn.log. Sovereign: imports nx_nofloat_autograd (pure integer) + nx_syscalls.
15// HONEST scope: this is the attention CORE (matmuls + causal softmax). RoPE + output-projection + multi-head are the
16// next sub-rung (CAP-NF-TRAIN-ATTN full). license_tier: ORIGINAL expect_exit: 0
17import "nx_nofloat_autograd.nx"
18import "nx_syscalls.nx"
19import "nx_gate_emit_lib.nx"
20import "nx_gate_verdict.nx"
21
22const ALOG: *u8 = "knowledge/status/nofloat_attn.log"
23const Q16: i64 = 65536
24
25
26func g_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
27func q_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)/Q16; if neg==1 { return 0-m } return m }
28func a_ws(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 }
29func a_wn(fd: i64, v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(fd,"-" as *u8,1);m=0-m} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;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{b[i]=t[k-1-i];i=i+1} sys_write(fd,b,k); return 0 }
30
31// ---- 2x2x2 matmul / matmul_nt gradcheck (op=8 matmul, op=9 matmul_nt) ----
32func mm_loss(tape: *i64, vals: *i64, st: *i64, op: i64, As: *i64, Bs: *i64, Ts: *i64, leaves: *i64) -> i64 {
33 st[0]=0; st[1]=0
34 let nA: i64 = nfa_leaf(tape,vals,st,2,2,As,0)
35 let nB: i64 = nfa_leaf(tape,vals,st,2,2,Bs,0)
36 var nC: i64 = nA
37 if op == 8 { nC = nfa_matmul(tape,vals,st,nA,nB) }
38 if op == 9 { nC = nfa_matmul_nt(tape,vals,st,nA,nB) }
39 let nt: i64 = nfa_leaf(tape,vals,st,2,2,Ts,0)
40 let loss: i64 = nfa_mse(tape,vals,st,nC,nt)
41 leaves[0]=nA
42 return loss
43}
44func mm_lossval(tape: *i64, vals: *i64, st: *i64, op: i64, As: *i64, Bs: *i64, Ts: *i64) -> i64 {
45 let lv: *i64 = sys_mmap(8) as *i64
46 let loss: i64 = mm_loss(tape,vals,st,op,As,Bs,Ts,lv)
47 return nfa_val(tape,vals,loss,0)
48}
49func mm_fd(tape: *i64, vals: *i64, st: *i64, op: i64, As: *i64, Bs: *i64, Ts: *i64, pi: i64, h: i64) -> i64 {
50 let ap: *i64 = sys_mmap(4*8) as *i64
51 let am: *i64 = sys_mmap(4*8) as *i64
52 var i: i64=0
53 while i<4 { ap[i]=As[i]; am[i]=As[i]; i=i+1 }
54 ap[pi]=As[pi]+h; am[pi]=As[pi]-h
55 let lp: i64 = mm_lossval(tape,vals,st,op,ap,Bs,Ts)
56 let lm: i64 = mm_lossval(tape,vals,st,op,am,Bs,Ts)
57 return ((lp-lm)*Q16)/(2*h)
58}
59func mm_gradcheck(tape: *i64, vals: *i64, grads: *i64, st: *i64, op: i64, As: *i64, Bs: *i64, Ts: *i64, h: i64, tol_q: i64, floor_q: i64, worst: *i64) -> i64 {
60 let lv: *i64 = sys_mmap(8) as *i64
61 let loss: i64 = mm_loss(tape,vals,st,op,As,Bs,Ts,lv)
62 nfa_backward(tape,vals,grads,st[0],loss)
63 let nA: i64 = lv[0]
64 var ok: i64=1; worst[0]=0
65 var i: i64=0
66 while i<4 {
67 let ana: i64 = nfa_grad(tape,grads,nA,i)
68 let fd: i64 = mm_fd(tape,vals,st,op,As,Bs,Ts,i,h)
69 let num: i64 = g_abs(fd-ana)
70 var den: i64 = g_abs(ana); if den<floor_q { den=floor_q }
71 if num >= ((tol_q*den)>>16) { ok=0 }
72 let rel: i64 = (num*1000)/den
73 if rel>worst[0] { worst[0]=rel }
74 i=i+1
75 }
76 return ok
77}
78
79// ---- single-head causal attention forward on the tape; leaves[0..3]=nWq,nWk,nWv,nX; returns the O node ----
80func attn_fwd(tape: *i64, vals: *i64, st: *i64, Xs: *i64, Wqs: *i64, Wks: *i64, Wvs: *i64, T: i64, d: i64, scale: i64, leaves: *i64) -> i64 {
81 st[0]=0; st[1]=0
82 let nX: i64 = nfa_leaf(tape,vals,st,T,d,Xs,0)
83 let nWq: i64 = nfa_leaf(tape,vals,st,d,d,Wqs,0)
84 let nWk: i64 = nfa_leaf(tape,vals,st,d,d,Wks,0)
85 let nWv: i64 = nfa_leaf(tape,vals,st,d,d,Wvs,0)
86 let nQ: i64 = nfa_matmul(tape,vals,st,nX,nWq)
87 let nK: i64 = nfa_matmul(tape,vals,st,nX,nWk)
88 let nV: i64 = nfa_matmul(tape,vals,st,nX,nWv)
89 let nS: i64 = nfa_matmul_nt(tape,vals,st,nQ,nK)
90 let nSs: i64 = nfa_cmul(tape,vals,st,nS,scale)
91 let nA: i64 = nfa_softmax_rows(tape,vals,st,nSs,1)
92 let nO: i64 = nfa_matmul(tape,vals,st,nA,nV)
93 leaves[0]=nWq; leaves[1]=nWk; leaves[2]=nWv; leaves[3]=nX
94 return nO
95}
96func attn_loss(tape: *i64, vals: *i64, st: *i64, Xs: *i64, Wqs: *i64, Wks: *i64, Wvs: *i64, Ts: *i64, T: i64, d: i64, scale: i64, leaves: *i64) -> i64 {
97 let nO: i64 = attn_fwd(tape,vals,st,Xs,Wqs,Wks,Wvs,T,d,scale,leaves)
98 let nt: i64 = nfa_leaf(tape,vals,st,T,d,Ts,0)
99 return nfa_mse(tape,vals,st,nO,nt)
100}
101func attn_lossval(tape: *i64, vals: *i64, st: *i64, Xs: *i64, Wqs: *i64, Wks: *i64, Wvs: *i64, Ts: *i64, T: i64, d: i64, scale: i64) -> i64 {
102 let lv: *i64 = sys_mmap(4*8) as *i64
103 let loss: i64 = attn_loss(tape,vals,st,Xs,Wqs,Wks,Wvs,Ts,T,d,scale,lv)
104 return nfa_val(tape,vals,loss,0)
105}
106// copy the O (attention output) values for given weights into outO (for realizable target generation)
107func attn_out(tape: *i64, vals: *i64, st: *i64, Xs: *i64, Wqs: *i64, Wks: *i64, Wvs: *i64, T: i64, d: i64, scale: i64, outO: *i64) -> i64 {
108 let lv: *i64 = sys_mmap(4*8) as *i64
109 let nO: i64 = attn_fwd(tape,vals,st,Xs,Wqs,Wks,Wvs,T,d,scale,lv)
110 var i: i64 = 0
111 while i < T*d { outO[i] = nfa_val(tape,vals,nO,i); i = i + 1 }
112 return 0
113}
114
115func main() -> i64 {
116 g_puts("nx_nofloat_attn gate (single-head CAUSAL attention backprops in PURE INTEGER Q16 -- MEASURED)\n" as *u8)
117 var pass: i64=0; var total: i64=0
118 let tape: *i64 = sys_mmap(512*7*8) as *i64
119 let vals: *i64 = sys_mmap(8192*8) as *i64
120 let grads: *i64 = sys_mmap(8192*8) as *i64
121 let st: *i64 = sys_mmap(2*8) as *i64
122 let h: i64 = 512
123 let floor_q: i64 = 4096
124 let worst: *i64 = sys_mmap(8) as *i64
125
126 // ---- A1: matmul gradcheck ----
127 let A1: *i64 = sys_mmap(4*8) as *i64; A1[0]=32768; A1[1]=0-16384; A1[2]=49152; A1[3]=65536
128 let B1: *i64 = sys_mmap(4*8) as *i64; B1[0]=65536; B1[1]=16384; B1[2]=0-32768; B1[3]=49152
129 let T1: *i64 = sys_mmap(4*8) as *i64; T1[0]=13107; T1[1]=6554; T1[2]=0-19661; T1[3]=26214
130 let mm_ok: i64 = mm_gradcheck(tape,vals,grads,st,8,A1,B1,T1,h,4096,floor_q,worst)
131 g_puts(" [measure] matmul worst rel grad err = " as *u8); g_pn(worst[0]); g_puts(" /1000 (tol=62)\n" as *u8)
132 pass=pass+g_check("A1: matmul gradcheck -- C=A.B backward (dA=dC.B^T) == finite differences" as *u8, mm_ok); total=total+1
133
134 // ---- A2: matmul_nt gradcheck ----
135 let mmnt_ok: i64 = mm_gradcheck(tape,vals,grads,st,9,A1,B1,T1,h,4096,floor_q,worst)
136 g_puts(" [measure] matmul_nt worst rel grad err = " as *u8); g_pn(worst[0]); g_puts(" /1000 (tol=62)\n" as *u8)
137 pass=pass+g_check("A2: matmul_nt gradcheck -- S=A.B^T (Q.K^T) backward == finite differences" as *u8, mmnt_ok); total=total+1
138
139 // ---- A3: full attention-core gradcheck wrt Wq (through causal softmax + both matmuls) ----
140 let T: i64 = 3; let d: i64 = 2; let scale: i64 = 46341 // 1/sqrt(2) Q16
141 let Xs: *i64 = sys_mmap(T*d*8) as *i64; Xs[0]=32768; Xs[1]=16384; Xs[2]=0-16384; Xs[3]=49152; Xs[4]=65536; Xs[5]=0-32768
142 let Wq: *i64 = sys_mmap(d*d*8) as *i64; Wq[0]=49152; Wq[1]=0-16384; Wq[2]=32768; Wq[3]=65536
143 let Wk: *i64 = sys_mmap(d*d*8) as *i64; Wk[0]=16384; Wk[1]=32768; Wk[2]=0-32768; Wk[3]=49152
144 let Wv: *i64 = sys_mmap(d*d*8) as *i64; Wv[0]=65536; Wv[1]=0-32768; Wv[2]=16384; Wv[3]=49152
145 let Tg: *i64 = sys_mmap(T*d*8) as *i64; Tg[0]=13107; Tg[1]=0-6554; Tg[2]=19661; Tg[3]=6554; Tg[4]=0-13107; Tg[5]=26214
146 let lv: *i64 = sys_mmap(4*8) as *i64
147 let loss: i64 = attn_loss(tape,vals,st,Xs,Wq,Wk,Wv,Tg,T,d,scale,lv)
148 nfa_backward(tape,vals,grads,st[0],loss)
149 let nWq: i64 = lv[0]
150 var a3_ok: i64 = 1; var a3_worst: i64 = 0
151 var pi: i64 = 0
152 while pi < d*d {
153 let ana: i64 = nfa_grad(tape,grads,nWq,pi)
154 let wqp: *i64 = sys_mmap(d*d*8) as *i64
155 let wqm: *i64 = sys_mmap(d*d*8) as *i64
156 var z: i64=0
157 while z<d*d { wqp[z]=Wq[z]; wqm[z]=Wq[z]; z=z+1 }
158 wqp[pi]=Wq[pi]+h; wqm[pi]=Wq[pi]-h
159 let lp: i64 = attn_lossval(tape,vals,st,Xs,wqp,Wk,Wv,Tg,T,d,scale)
160 let lm: i64 = attn_lossval(tape,vals,st,Xs,wqm,Wk,Wv,Tg,T,d,scale)
161 let fd: i64 = ((lp-lm)*Q16)/(2*h)
162 let num: i64 = g_abs(fd-ana)
163 var den: i64 = g_abs(ana); if den<floor_q { den=floor_q }
164 if num >= ((8192*den)>>16) { a3_ok=0 } // tol 1/8 (long fixed-point chain through softmax)
165 let rel: i64 = (num*1000)/den
166 if rel>a3_worst { a3_worst=rel }
167 pi=pi+1
168 }
169 g_puts(" [measure] attention dL/dWq worst rel grad err = " as *u8); g_pn(a3_worst); g_puts(" /1000 (tol=125)\n" as *u8)
170 pass=pass+g_check("A3: attention-core gradcheck -- dL/dWq through causal-softmax + Q.K^T + A.V == finite diff" as *u8, a3_ok); total=total+1
171
172 // ---- D: neg-control teeth (matmul) ----
173 let lvd: *i64 = sys_mmap(8) as *i64
174 let lossd: i64 = mm_loss(tape,vals,st,8,A1,B1,T1,lvd)
175 nfa_backward(tape,vals,grads,st[0],lossd)
176 let ana0: i64 = nfa_grad(tape,grads,lvd[0],0)
177 let fd0: i64 = mm_fd(tape,vals,st,8,A1,B1,T1,0,h)
178 let bad: i64 = 0 - ana0
179 var den0: i64 = g_abs(ana0); if den0<floor_q { den0=floor_q }
180 var caught: i64 = 1
181 if g_abs(fd0-bad) < ((4096*den0)>>16) { caught=0 }
182 pass=pass+g_check("D: neg-control -- a deliberately WRONG matmul grad is rejected (teeth)" as *u8, caught); total=total+1
183
184 // ---- B: attention value-path LEARNS (Wq,Wk fixed -> A fixed -> O=A.(X.Wv) convex in Wv) ----
185 let Wvt: *i64 = sys_mmap(d*d*8) as *i64; Wvt[0]=65536; Wvt[1]=0-32768; Wvt[2]=16384; Wvt[3]=49152 // Wv* target
186 let tgt: *i64 = sys_mmap(T*d*8) as *i64
187 attn_out(tape,vals,st,Xs,Wq,Wk,Wvt,T,d,scale,tgt) // realizable target = attention out with Wv*
188 let Wvp: *i64 = sys_mmap(d*d*8) as *i64; Wvp[0]=0; Wvp[1]=0; Wvp[2]=0; Wvp[3]=0 // learn from zero
189 let lvb: *i64 = sys_mmap(4*8) as *i64
190 let gW: *i64 = sys_mmap(d*d*8) as *i64
191 var lf: i64 = 0; var ll: i64 = 0
192 var ep: i64 = 0
193 while ep < 6000 {
194 let lossb: i64 = attn_loss(tape,vals,st,Xs,Wq,Wk,Wvp,tgt,T,d,scale,lvb)
195 nfa_backward(tape,vals,grads,st[0],lossb)
196 if ep==0 { lf = nfa_val(tape,vals,lossb,0) }
197 ll = nfa_val(tape,vals,lossb,0)
198 var z: i64=0
199 while z<d*d { gW[z]=nfa_grad(tape,grads,lvb[2],z); z=z+1 } // lvb[2] = nWv leaf
200 nfa_sgd(Wvp, gW, d*d, 1024)
201 ep=ep+1
202 }
203 g_puts(" [measure] attention Wv-learn loss: start=" as *u8); g_pn(lf); g_puts(" end=" as *u8); g_pn(ll)
204 g_puts(" Wv=[" as *u8); g_pn(Wvp[0]); g_puts("," as *u8); g_pn(Wvp[1]); g_puts("," as *u8); g_pn(Wvp[2]); g_puts("," as *u8); g_pn(Wvp[3]); g_puts("] vs Wv*=[65536,-32768,16384,49152]\n" as *u8)
205 var learns: i64 = 1
206 if ll*10 > lf { learns=0 }
207 if g_abs(Wvp[0]-65536)>9830 { learns=0 }
208 if g_abs(Wvp[1]+32768)>9830 { learns=0 }
209 if g_abs(Wvp[2]-16384)>9830 { learns=0 }
210 if g_abs(Wvp[3]-49152)>9830 { learns=0 }
211 if lf<=0 { learns=0 }
212 pass=pass+g_check("B: attention value-path LEARNS in pure integer -- Wv converges to target, loss collapses" as *u8, learns); total=total+1
213
214 // ---- C: bit-exact ----
215 let Wvp2: *i64 = sys_mmap(d*d*8) as *i64; Wvp2[0]=0; Wvp2[1]=0; Wvp2[2]=0; Wvp2[3]=0
216 let lvc: *i64 = sys_mmap(4*8) as *i64
217 let gW2: *i64 = sys_mmap(d*d*8) as *i64
218 var ep2: i64=0
219 while ep2 < 6000 {
220 let lossc: i64 = attn_loss(tape,vals,st,Xs,Wq,Wk,Wvp2,tgt,T,d,scale,lvc)
221 nfa_backward(tape,vals,grads,st[0],lossc)
222 var z: i64=0
223 while z<d*d { gW2[z]=nfa_grad(tape,grads,lvc[2],z); z=z+1 }
224 nfa_sgd(Wvp2, gW2, d*d, 1024)
225 ep2=ep2+1
226 }
227 var bitexact: i64 = 1
228 var z2: i64=0
229 while z2<d*d { if Wvp2[z2]!=Wvp[z2] { bitexact=0 } z2=z2+1 }
230 pass=pass+g_check("C: bit-exact -- training twice gives IDENTICAL integer Wv (determinism)" as *u8, bitexact); total=total+1
231
232 // ---- emit ----
233 var okall: i64=0; if pass==total { okall=1 }
234 let logf: i64 = sys_openat_append(ALOG, 420)
235 if logf >= 0 {
236 a_ws(logf,"NOFLOATATTN ops=matmul,matmul_nt,cmul,causal_softmax_rows A1=" as *u8); a_wn(logf,mm_ok)
237 a_ws(logf," A2=" as *u8); a_wn(logf,mmnt_ok); a_ws(logf," A3_attn=" as *u8); a_wn(logf,a3_ok); a_ws(logf," D=" as *u8); a_wn(logf,caught)
238 a_ws(logf," B_learns=" as *u8); a_wn(logf,learns); a_ws(logf," C_bitexact=" as *u8); a_wn(logf,bitexact)
239 if okall==1 { a_ws(logf," verdict=GREEN\n" as *u8) } else { a_ws(logf," verdict=RED\n" as *u8) }
240 sys_close(logf)
241 }
242 g_puts("---- nofloat_attn gate: passed " as *u8); g_pn(pass); g_puts(" / " as *u8); g_pn(total); g_puts(" ----\n" as *u8)
243 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
244 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
245 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
246 let ctr__dry: *i64 = gv_ctr()
247 ctr__dry[0] = pass
248 ctr__dry[1] = total
249 let rc__dry: i64 = gv_verdict("NOFLOAT-ATTN-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
250 sys_exit(rc__dry)
251 return rc__dry
252}