nx_reader_mlp_gate.nx source
↩ module page · 230 lines · 10776 B
1// nx_reader_mlp_gate.nx -- NEURAL-READER RUNG 1: prove the sovereign autograd (nx_autograd) can TRAIN a
2// nonlinear model, on the exact capability the deep-research reader's linear plateau lacks. The oracle
3// diagnostic proved SCORING is the deep-research wall and that a LINEAR perceptron over hand-features plateaus
4// (~250 vs oracle 727; two features net-negative/inert). The fix is LEARNED NONLINEAR representations. This
5// gate is the minimal liar-killed proof of that mechanism: a 2-layer MLP (2->4->1, ReLU) trained by SGD on the
6// autograd tape LEARNS XOR (100%), which a LINEAR model provably CANNOT (<=75%). Same loop scales to the reader
7// (features->hidden->score) and to any trained model; train in f32, quantize to integer for the no-float
8// inference path. TEETH: T1 MLP fits XOR 4/4 T2 MLP loss collapses T3 LINEAR baseline FAILS (<4/4, the
9// liar-kill: if a linear model 'passed' XOR the test would be meaningless) T4 gradcheck (autograd d/dx x^2=2x).
10// expect_exit: 0 license_tier: ORIGINAL
11import "nx_autograd.nx"
12
13func gw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
14func gn(v: i64) -> i64 {
15 if v==0 { sys_write(1,"0" as *u8,1); return 0 }
16 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m }
17 let t: *u8=sys_mmap(24); var k: i64=0
18 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
19 let b: *u8=sys_mmap(24); var i: i64=0; while i<k { b[i]=t[k-1-i]; i=i+1 } sys_write(1,b,k); return 0
20}
21// f32 (IEEE-754 binary32 bits) -> integer, truncated toward zero. small magnitudes (no overflow guard needed
22// for our loss/grad displays). No nx_f32_to_i32 in the cvt module, so decode exp/mantissa directly.
23func nx_f32_trunc(raw: i64) -> i64 {
24 if raw == 0 { return 0 }
25 let sign: i64 = (raw / 2147483648) % 2 // bit31 (avoid >31 immediate shift)
26 let exp: i64 = (raw / 8388608) % 256 // bits 23..30
27 let mant: i64 = raw % 8388608 // bits 0..22
28 if exp == 0 { return 0 } // subnormal ~ 0
29 let m: i64 = mant + 8388608 // implicit leading 1 -> 1.mant * 2^23
30 let e: i64 = exp - 127 - 23 // value = m * 2^e
31 var val: i64 = 0
32 if e >= 0 { val = m << e } else { let sh: i64 = 0 - e; val = m >> sh }
33 if sign == 1 { val = 0 - val }
34 return val
35}
36// f32 -> integer milli (x1000), truncated. for display.
37func f32_milli(v: i64) -> i64 { return nx_f32_trunc(nx_f32_mul(v, nx_i32_to_f32(1000))) }
38
39const NIN: i64 = 2
40const NHID: i64 = 8
41const B1_OFF: i64 = 16 // NHID*NIN
42const W2_OFF: i64 = 24 // B1_OFF + NHID
43const B2_OFF: i64 = 32 // W2_OFF + NHID
44const NP_MLP: i64 = 33 // W1[8x2]=16 + b1[8]=8 + W2[8]=8 + b2=1
45const NP_LIN: i64 = 3 // w0,w1,b
46
47// deterministic symmetry-breaking init in [-0.6,0.6]
48func init_weights(W: *i64, n: i64) -> i64 {
49 var k: i64 = 0
50 while k < n { W[k] = ag_constf(((k*37) % 13) - 6, 10); k = k + 1 }
51 return 0
52}
53
54// MLP init: W spread; b1 (hidden biases) a MIXED-SIGN spread [-1 .. +0.75] so hidden units have varied ReLU
55// thresholds -- the ReLU XOR solution needs a unit with a NEGATIVE bias (relu(x0+x1-1)); positive-only biases
56// stalled it. b2 = 0.
57func init_mlp(W: *i64) -> i64 {
58 var k: i64 = 0
59 while k < NP_MLP { W[k] = ag_constf(((k*37) % 13) - 6, 10); k = k + 1 }
60 var j: i64 = 0
61 while j < NHID { W[B1_OFF + j] = ag_constf(j - 4, 4); j = j + 1 } // b1 in [-1.0 .. +0.75]
62 W[B2_OFF] = AG_F32_ZERO // b2 = 0
63 return 0
64}
65
66// build the MLP forward on a FRESH tape (weights = leafs 0..16, x=17,18, target=19); return the loss node.
67// out node index is returned via ob[0]. Layout: W1[j*2+i], b1[8+j], W2[12+j], b2=16.
68func mlp_step(tape: *i64, np: *i64, W: *i64, x0: i64, x1: i64, tgt: i64, ob: *i64) -> i64 {
69 *np = 0
70 var k: i64 = 0
71 while k < NP_MLP { ag_leaf(tape, np, W[k]); k = k + 1 }
72 let xa: i64 = ag_leaf(tape, np, x0) // 17
73 let xb: i64 = ag_leaf(tape, np, x1) // 18
74 let tg: i64 = ag_leaf(tape, np, tgt) // NP_MLP+2
75 var out: i64 = B2_OFF // start accumulator at b2 leaf
76 var j: i64 = 0
77 while j < NHID {
78 let m0: i64 = ag_mul(tape, np, j*2+0, xa)
79 let m1: i64 = ag_mul(tape, np, j*2+1, xb)
80 let s: i64 = ag_add(tape, np, m0, m1)
81 let s2: i64 = ag_add(tape, np, s, B1_OFF+j)
82 let hj: i64 = ag_relu(tape, np, s2)
83 let om: i64 = ag_mul(tape, np, W2_OFF+j, hj)
84 out = ag_add(tape, np, out, om)
85 j = j + 1
86 }
87 ob[0] = out
88 let diff: i64 = ag_sub(tape, np, out, tg)
89 let loss: i64 = ag_mul(tape, np, diff, diff)
90 return loss
91}
92
93// linear forward (weights leafs 0,1,2; x=3,4; target=5); loss node; out via ob[0].
94func lin_step(tape: *i64, np: *i64, W: *i64, x0: i64, x1: i64, tgt: i64, ob: *i64) -> i64 {
95 *np = 0
96 ag_leaf(tape, np, W[0]); ag_leaf(tape, np, W[1]); ag_leaf(tape, np, W[2])
97 let xa: i64 = ag_leaf(tape, np, x0) // 3
98 let xb: i64 = ag_leaf(tape, np, x1) // 4
99 let tg: i64 = ag_leaf(tape, np, tgt) // 5
100 let m0: i64 = ag_mul(tape, np, 0, xa)
101 let m1: i64 = ag_mul(tape, np, 1, xb)
102 let s: i64 = ag_add(tape, np, m0, m1)
103 let out: i64 = ag_add(tape, np, s, 2)
104 ob[0] = out
105 let diff: i64 = ag_sub(tape, np, out, tg)
106 let loss: i64 = ag_mul(tape, np, diff, diff)
107 return loss
108}
109
110func main() -> i64 {
111 gw("=== nx_reader_mlp_gate -- neural-reader rung 1: autograd MLP learns XOR, linear cannot ===\n" as *u8)
112 let tape: *i64 = sys_mmap(2048 * 5 * 8) as *i64
113 let np: *i64 = sys_mmap(8) as *i64
114 let ob: *i64 = sys_mmap(8) as *i64
115
116 // XOR dataset: 4 points. inputs 0.0/1.0, targets 0.0/1.0.
117 let F0: i64 = AG_F32_ZERO
118 let F1: i64 = AG_F32_ONE
119 let X0: *i64 = sys_mmap(64) as *i64
120 let X1: *i64 = sys_mmap(64) as *i64
121 let TY: *i64 = sys_mmap(64) as *i64
122 X0[0]=F0; X1[0]=F0; TY[0]=F0
123 X0[1]=F0; X1[1]=F1; TY[1]=F1
124 X0[2]=F1; X1[2]=F0; TY[2]=F1
125 X0[3]=F1; X1[3]=F1; TY[3]=F0
126
127 let lr: i64 = ag_constf(1, 10) // 0.1 (on SUMMED batch gradient)
128 let half: i64 = ag_constf(1, 2) // 0.5 decision threshold
129 let EPOCHS: i64 = 20000
130
131 // ---------- train MLP (BATCH gradient descent: accumulate grads over all 4 points, one update/epoch --
132 // far more stable than per-example SGD on a 4-point set) ----------
133 let Wm: *i64 = sys_mmap(NP_MLP * 8) as *i64
134 let Gacc: *i64 = sys_mmap(NP_MLP * 8) as *i64
135 init_mlp(Wm)
136 var ep: i64 = 0
137 var lastloss_m: i64 = 0
138 while ep < EPOCHS {
139 var k0: i64 = 0
140 while k0 < NP_MLP { Gacc[k0] = AG_F32_ZERO; k0 = k0 + 1 }
141 var d: i64 = 0
142 var lsum: i64 = AG_F32_ZERO
143 while d < 4 {
144 let loss: i64 = mlp_step(tape, np, Wm, X0[d], X1[d], TY[d], ob)
145 ag_backward(tape, *np, loss)
146 lsum = nx_f32_add(lsum, ag_val(tape, loss))
147 var k: i64 = 0
148 while k < NP_MLP { Gacc[k] = nx_f32_add(Gacc[k], ag_grad(tape, k)); k = k + 1 }
149 d = d + 1
150 }
151 var ku: i64 = 0
152 while ku < NP_MLP { Wm[ku] = nx_f32_sub(Wm[ku], nx_f32_mul(lr, Gacc[ku])); ku = ku + 1 }
153 lastloss_m = lsum
154 if ep % 5000 == 0 { gw(" epoch "); gn(ep); gw(" loss_milli="); gn(f32_milli(lsum)); gw("\n" as *u8) }
155 ep = ep + 1
156 }
157 // eval MLP
158 var mlp_correct: i64 = 0
159 var d2: i64 = 0
160 gw("MLP predictions: " as *u8)
161 while d2 < 4 {
162 mlp_step(tape, np, Wm, X0[d2], X1[d2], TY[d2], ob)
163 let ov: i64 = ag_val(tape, ob[0])
164 var pred: i64 = 0
165 if nx_f32_gt(ov, half) == 1 { pred = 1 }
166 var tgt: i64 = 0
167 if nx_f32_gt(TY[d2], half) == 1 { tgt = 1 }
168 gw("(" as *u8); gn(f32_milli(X0[d2])/1000); gw("," as *u8); gn(f32_milli(X1[d2])/1000)
169 gw(")->" as *u8); gn(pred); gw("[t=" as *u8); gn(tgt); gw("] " as *u8)
170 if pred == tgt { mlp_correct = mlp_correct + 1 }
171 d2 = d2 + 1
172 }
173 gw(" = "); gn(mlp_correct); gw("/4 loss_milli="); gn(f32_milli(lastloss_m)); gw("\n" as *u8)
174
175 // ---------- train LINEAR baseline (the liar-kill: it MUST fail XOR) ----------
176 let Wl: *i64 = sys_mmap(NP_LIN * 8) as *i64
177 init_weights(Wl, NP_LIN)
178 ep = 0
179 while ep < EPOCHS {
180 var d: i64 = 0
181 while d < 4 {
182 let loss: i64 = lin_step(tape, np, Wl, X0[d], X1[d], TY[d], ob)
183 ag_backward(tape, *np, loss)
184 var k: i64 = 0
185 while k < NP_LIN { Wl[k] = nx_f32_sub(Wl[k], nx_f32_mul(lr, ag_grad(tape, k))); k = k + 1 }
186 d = d + 1
187 }
188 ep = ep + 1
189 }
190 var lin_correct: i64 = 0
191 var d3: i64 = 0
192 gw("LIN predictions: " as *u8)
193 while d3 < 4 {
194 lin_step(tape, np, Wl, X0[d3], X1[d3], TY[d3], ob)
195 let ov: i64 = ag_val(tape, ob[0])
196 var pred: i64 = 0
197 if nx_f32_gt(ov, half) == 1 { pred = 1 }
198 var tgt: i64 = 0
199 if nx_f32_gt(TY[d3], half) == 1 { tgt = 1 }
200 gw("(" as *u8); gn(f32_milli(X0[d3])/1000); gw("," as *u8); gn(f32_milli(X1[d3])/1000)
201 gw(")->" as *u8); gn(pred); gw("[t=" as *u8); gn(tgt); gw("] " as *u8)
202 if pred == tgt { lin_correct = lin_correct + 1 }
203 d3 = d3 + 1
204 }
205 gw(" = "); gn(lin_correct); gw("/4 (linear CANNOT separate XOR)\n" as *u8)
206
207 // ---------- gradcheck: d/dx (x*x) at x=3 == 6 ----------
208 *np = 0
209 let gx: i64 = ag_leaf(tape, np, nx_i32_to_f32(3))
210 let gy: i64 = ag_mul(tape, np, gx, gx)
211 ag_backward(tape, *np, gy)
212 let gxg: i64 = f32_milli(ag_grad(tape, gx)) // expect 6000
213 gw("gradcheck d/dx(x^2)@3 = "); gn(gxg); gw(" milli (expect 6000)\n" as *u8)
214
215 // ---------- teeth ----------
216 var pass: i64 = 0
217 if mlp_correct == 4 { pass = pass + 1 } // T1 MLP fits XOR
218 if f32_milli(lastloss_m) < 100 { pass = pass + 1 } // T2 loss collapsed (<0.1)
219 if lin_correct < 4 { pass = pass + 1 } // T3 LIAR-KILL: linear FAILS XOR
220 if gxg > 5900 { if gxg < 6100 { pass = pass + 1 } } // T4 gradcheck
221 gw("TEETH T1(mlp4/4)+T2(loss<0.1)+T3(lin-fails)+T4(gradcheck) = "); gn(pass); gw("/4\n" as *u8)
222 if pass == 4 {
223 gw("GREEN -- the sovereign autograd TRAINS a nonlinear model that beats the linear ceiling. This is the\n" as *u8)
224 gw("mechanism the deep-research reader needs (scoring plateau = linear); next = MLP over the 14 reader\n" as *u8)
225 gw("features -> quantize to integer -> wire into MODE-R; target: beat 251/148/101 toward oracle 727.\n" as *u8)
226 return 0
227 }
228 gw("RED -- neural-reader mechanism not proven\n" as *u8)
229 return 1
230}