code wiki / _hdl_build / nx_train_r1_gate.nx
nx_train_r1_gate.nx source
↩ module page · 235 lines · 10649 B
1// nx_train_r1_gate.nx -- GATE for TRAIN-R1: the scalar-tape autograd keystone. Proves, by RUNNING, that the
2// team can TRAIN a model sovereignly. Three exact gates (spec 2026-06-09-tutoring-training-substrate-rung1.md):
3//
4// A GRADCHECK (oracle = mathematics): loss = (relu(w1*x+b1)*w2 + b2)^2 at params off the relu kink
5// (w1=2/3, b1=1/4, w2=3/2, b2=1/4, x=3/4). For each param: analytic grad (reverse-mode backward) vs
6// central finite difference (f(p+h)-f(p-h))/2h, h=1/128; relative error < 1/32 (floor 1/64). Catches
7// every sign/chain-rule defect a wrong autograd could ship.
8// B A MODEL PROVABLY LEARNS: fit y = 1.5x - 0.5 (8 points x=i/4, targets in-f32) by full-batch gradient
9// descent, lr=1/20, 500 epochs, MSE via the tape. Assert final loss < 1/1000 AND |w-1.5| < 1/16 AND
10// |b+0.5| < 1/16 AND final loss < first loss. The smallest EXACT statement of "we can train a model".
11// C BIT-EXACT REPRODUCIBLE: run the whole training twice from zero-init; assert the final (w,b) f32 BITS
12// are identical. GPU training is non-deterministic by default; the bits-up f32 substrate makes
13// byte-identical training a GATE -- the determinism exceed-axis carried into ML.
14//
15// Evidence -> knowledge/status/train_r1.log (TRAINR1GATE authored=organ ... verdict=GREEN). license_tier: ORIGINAL
16import "nx_autograd.nx" // ag_* tape + transitively nx_f32 / nx_f32_div / nx_f32_cvt / nx_syscalls
17import "nx_syscalls.nx"
18import "nx_gate_verdict.nx"
19
20const TR_LOG: *u8 = "knowledge/status/train_r1.log"
21
22func tr_w(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 }
23func tr_wn(fd: i64, v: i64) -> i64 {
24 let bb: *u8 = sys_mmap(28); var m: i64 = v
25 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
26 let t: *u8 = sys_mmap(28); var k: i64 = 0
27 if m == 0 { t[0] = 48; k = 1 }
28 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
29 var i: i64 = 0
30 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
31 sys_write(fd, bb, k); return 0
32}
33
34// f32 -> round(v*1000) as a signed int, for readable logging (no f32->int converter exists yet; nearest by
35// linear advance over the i32->f32 ladder; our values are O(1) so this is a few thousand cheap compares).
36func tr_f32_to_milli(v: i64) -> i64 {
37 var s: i64 = nx_f32_mul(v, nx_i32_to_f32(1000))
38 var neg: i64 = 0
39 if nx_f32_lt(s, AG_F32_ZERO) == 1 { neg = 1; s = nx_f32_neg(s) }
40 let half: i64 = ag_constf(1, 2)
41 var m: i64 = 0
42 var go: i64 = 1
43 while go == 1 {
44 let mid: i64 = nx_f32_add(nx_i32_to_f32(m), half)
45 if nx_f32_lt(mid, s) == 1 {
46 m = m + 1
47 if m >= 100000 { go = 0 }
48 } else { go = 0 }
49 }
50 if neg == 1 { return 0 - m }
51 return m
52}
53
54// build the gradcheck loss graph on `tape`; write leaf indices [w1,b1,w2,b2] into leaves; return loss node.
55func tr_build_loss(tape: *i64, np: *i64, w1: i64, b1: i64, w2: i64, b2: i64, x: i64, leaves: *i64) -> i64 {
56 *np = 0
57 let nw1: i64 = ag_leaf(tape, np, w1)
58 let nb1: i64 = ag_leaf(tape, np, b1)
59 let nw2: i64 = ag_leaf(tape, np, w2)
60 let nb2: i64 = ag_leaf(tape, np, b2)
61 let nx: i64 = ag_leaf(tape, np, x)
62 let h1: i64 = ag_add(tape, np, ag_mul(tape, np, nw1, nx), nb1)
63 let rr: i64 = ag_relu(tape, np, h1)
64 let h2: i64 = ag_add(tape, np, ag_mul(tape, np, rr, nw2), nb2)
65 let loss: i64 = ag_mul(tape, np, h2, h2)
66 leaves[0] = nw1; leaves[1] = nb1; leaves[2] = nw2; leaves[3] = nb2
67 return loss
68}
69
70func tr_loss_val(w1: i64, b1: i64, w2: i64, b2: i64, x: i64) -> i64 {
71 let tape: *i64 = (sys_mmap(64 * 5 * 8)) as *i64
72 let np: *i64 = (sys_mmap(8)) as *i64
73 let lv: *i64 = (sys_mmap(4 * 8)) as *i64
74 let loss: i64 = tr_build_loss(tape, np, w1, b1, w2, b2, x, lv)
75 return ag_val(tape, loss)
76}
77
78func tr_analytic_grads(w1: i64, b1: i64, w2: i64, b2: i64, x: i64, gout: *i64) -> i64 {
79 let tape: *i64 = (sys_mmap(64 * 5 * 8)) as *i64
80 let np: *i64 = (sys_mmap(8)) as *i64
81 let lv: *i64 = (sys_mmap(4 * 8)) as *i64
82 let loss: i64 = tr_build_loss(tape, np, w1, b1, w2, b2, x, lv)
83 ag_backward(tape, *np, loss)
84 gout[0] = ag_grad(tape, lv[0]); gout[1] = ag_grad(tape, lv[1])
85 gout[2] = ag_grad(tape, lv[2]); gout[3] = ag_grad(tape, lv[3])
86 return 0
87}
88
89// central finite-difference grad of the loss w.r.t. param index pi (0..3), step h.
90func tr_fd_grad(p: *i64, pi: i64, h: i64) -> i64 {
91 let pp: *i64 = (sys_mmap(5 * 8)) as *i64
92 let pm: *i64 = (sys_mmap(5 * 8)) as *i64
93 var i: i64 = 0
94 while i < 5 { pp[i] = p[i]; pm[i] = p[i]; i = i + 1 }
95 pp[pi] = nx_f32_add(p[pi], h)
96 pm[pi] = nx_f32_sub(p[pi], h)
97 let lp: i64 = tr_loss_val(pp[0], pp[1], pp[2], pp[3], pp[4])
98 let lm: i64 = tr_loss_val(pm[0], pm[1], pm[2], pm[3], pm[4])
99 return nx_f32_div(nx_f32_sub(lp, lm), nx_f32_add(h, h))
100}
101
102// full-batch GD training of y = w*x + b on y = 1.5x - 0.5. Writes final w,b + first/last loss.
103func tr_train(epochs: i64, wout: *i64, bout: *i64, lfirst: *i64, llast: *i64) -> i64 {
104 var w: i64 = AG_F32_ZERO
105 var b: i64 = AG_F32_ZERO
106 let lr: i64 = ag_constf(1, 20)
107 let inv8: i64 = ag_constf(1, 8)
108 let f15: i64 = ag_constf(3, 2)
109 let f05: i64 = ag_constf(1, 2)
110 let xs: *i64 = (sys_mmap(8 * 8)) as *i64
111 let ys: *i64 = (sys_mmap(8 * 8)) as *i64
112 var i: i64 = 0
113 while i < 8 {
114 let xi: i64 = ag_constf(i, 4)
115 xs[i] = xi
116 ys[i] = nx_f32_sub(nx_f32_mul(f15, xi), f05)
117 i = i + 1
118 }
119 let tape: *i64 = (sys_mmap(2048 * 5 * 8)) as *i64
120 let np: *i64 = (sys_mmap(8)) as *i64
121 var ep: i64 = 0
122 while ep < epochs {
123 *np = 0
124 let nw: i64 = ag_leaf(tape, np, w)
125 let nb: i64 = ag_leaf(tape, np, b)
126 var nsum: i64 = ag_leaf(tape, np, AG_F32_ZERO)
127 i = 0
128 while i < 8 {
129 let nxi: i64 = ag_leaf(tape, np, xs[i])
130 let nyi: i64 = ag_leaf(tape, np, ys[i])
131 let pred: i64 = ag_add(tape, np, ag_mul(tape, np, nw, nxi), nb)
132 let res: i64 = ag_sub(tape, np, pred, nyi)
133 let sq: i64 = ag_mul(tape, np, res, res)
134 nsum = ag_add(tape, np, nsum, sq)
135 i = i + 1
136 }
137 let ninv: i64 = ag_leaf(tape, np, inv8)
138 let nloss: i64 = ag_mul(tape, np, nsum, ninv)
139 ag_backward(tape, *np, nloss)
140 if ep == 0 { *lfirst = ag_val(tape, nloss) }
141 *llast = ag_val(tape, nloss)
142 w = nx_f32_sub(w, nx_f32_mul(lr, ag_grad(tape, nw)))
143 b = nx_f32_sub(b, nx_f32_mul(lr, ag_grad(tape, nb)))
144 ep = ep + 1
145 }
146 *wout = w; *bout = b
147 return 0
148}
149
150func tr_emit(fd: i64, r: *i64) -> i64 {
151 tr_w(fd, "TRAINR1GATE authored=organ engine=scalar-tape-autograd-f32" as *u8)
152 tr_w(fd, " | A_gradcheck_pass=" as *u8); tr_wn(fd, r[0])
153 tr_w(fd, " worst_|fd-analytic|_milli=" as *u8); tr_wn(fd, r[1])
154 tr_w(fd, " | B_learns_pass=" as *u8); tr_wn(fd, r[2])
155 tr_w(fd, " w_milli=" as *u8); tr_wn(fd, r[3]); tr_w(fd, " b_milli=" as *u8); tr_wn(fd, r[4])
156 tr_w(fd, " loss_first_milli=" as *u8); tr_wn(fd, r[5]); tr_w(fd, " loss_last_milli=" as *u8); tr_wn(fd, r[6])
157 tr_w(fd, " | C_bitexact_repro_pass=" as *u8); tr_wn(fd, r[7])
158 if r[8] == 1 { tr_w(fd, " verdict=GREEN\n" as *u8) } else { tr_w(fd, " verdict=RED\n" as *u8) }
159 return 0
160}
161
162func main() -> i64 {
163 var ok: i64 = 1
164
165 // ---------- Gate A: gradcheck ----------
166 let p: *i64 = (sys_mmap(5 * 8)) as *i64
167 p[0] = ag_constf(2, 3); p[1] = ag_constf(1, 4); p[2] = ag_constf(3, 2); p[3] = ag_constf(1, 4); p[4] = ag_constf(3, 4)
168 let grads: *i64 = (sys_mmap(4 * 8)) as *i64
169 tr_analytic_grads(p[0], p[1], p[2], p[3], p[4], grads)
170 let h: i64 = ag_constf(1, 128)
171 let flo: i64 = ag_constf(1, 64)
172 let tol: i64 = ag_constf(1, 32)
173 var gradcheck_pass: i64 = 1
174 var worst_milli: i64 = 0
175 var pi: i64 = 0
176 while pi < 4 {
177 let ana: i64 = grads[pi]
178 let fd: i64 = tr_fd_grad(p, pi, h)
179 let num: i64 = nx_f32_abs(nx_f32_sub(fd, ana))
180 var den: i64 = nx_f32_abs(ana)
181 if nx_f32_lt(den, flo) == 1 { den = flo }
182 let thresh: i64 = nx_f32_mul(tol, den)
183 if nx_f32_lt(num, thresh) != 1 { gradcheck_pass = 0 }
184 let nm: i64 = tr_f32_to_milli(num)
185 if nm > worst_milli { worst_milli = nm }
186 pi = pi + 1
187 }
188 if gradcheck_pass != 1 { ok = 0 }
189
190 // ---------- Gate B: a model provably learns ----------
191 let wbox: *i64 = (sys_mmap(8)) as *i64
192 let bbox: *i64 = (sys_mmap(8)) as *i64
193 let lf: *i64 = (sys_mmap(8)) as *i64
194 let ll: *i64 = (sys_mmap(8)) as *i64
195 tr_train(500, wbox, bbox, lf, ll)
196 let w: i64 = *wbox
197 let b: i64 = *bbox
198 var learns_pass: i64 = 1
199 if nx_f32_lt(*ll, ag_constf(1, 1000)) != 1 { learns_pass = 0 } // loss < 1/1000
200 if nx_f32_lt(nx_f32_abs(nx_f32_sub(w, ag_constf(3, 2))), ag_constf(1, 16)) != 1 { learns_pass = 0 } // |w-1.5|<1/16
201 if nx_f32_lt(nx_f32_abs(nx_f32_add(b, ag_constf(1, 2))), ag_constf(1, 16)) != 1 { learns_pass = 0 } // |b+0.5|<1/16
202 if nx_f32_lt(*ll, *lf) != 1 { learns_pass = 0 } // loss decreased
203 if learns_pass != 1 { ok = 0 }
204
205 // ---------- Gate C: bit-exact reproducible ----------
206 let wbox2: *i64 = (sys_mmap(8)) as *i64
207 let bbox2: *i64 = (sys_mmap(8)) as *i64
208 let lf2: *i64 = (sys_mmap(8)) as *i64
209 let ll2: *i64 = (sys_mmap(8)) as *i64
210 tr_train(500, wbox2, bbox2, lf2, ll2)
211 var repro_pass: i64 = 1
212 if *wbox2 != w { repro_pass = 0 }
213 if *bbox2 != b { repro_pass = 0 }
214 if repro_pass != 1 { ok = 0 }
215
216 // ---------- emit ----------
217 let r: *i64 = (sys_mmap(9 * 8)) as *i64
218 r[0] = gradcheck_pass; r[1] = worst_milli; r[2] = learns_pass
219 r[3] = tr_f32_to_milli(w); r[4] = tr_f32_to_milli(b)
220 r[5] = tr_f32_to_milli(*lf); r[6] = tr_f32_to_milli(*ll)
221 r[7] = repro_pass; r[8] = ok
222 tr_emit(1, r)
223 let logf: i64 = sys_openat_append(TR_LOG, 420)
224 if logf >= 0 { tr_emit(logf, r); sys_close(logf) }
225
226 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
227 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
228 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
229 let ctr__dry: *i64 = gv_ctr()
230 ctr__dry[0] = ok
231 ctr__dry[1] = 1
232 let rc__dry: i64 = gv_verdict("TRAIN-R1-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
233 sys_exit(rc__dry)
234 return rc__dry
235}