code wiki / _hdl_build / nx_train_r1_gate.nx
nx_train_r1_gate.nx source
↩ module page · 227 lines · 10115 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"
18
19const TR_LOG: *u8 = "knowledge/status/train_r1.log"
20
21func 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 }
22func tr_wn(fd: i64, v: i64) -> i64 {
23 let bb: *u8 = sys_mmap(28); var m: i64 = v
24 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
25 let t: *u8 = sys_mmap(28); var k: i64 = 0
26 if m == 0 { t[0] = 48; k = 1 }
27 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
28 var i: i64 = 0
29 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
30 sys_write(fd, bb, k); return 0
31}
32
33// f32 -> round(v*1000) as a signed int, for readable logging (no f32->int converter exists yet; nearest by
34// linear advance over the i32->f32 ladder; our values are O(1) so this is a few thousand cheap compares).
35func tr_f32_to_milli(v: i64) -> i64 {
36 var s: i64 = nx_f32_mul(v, nx_i32_to_f32(1000))
37 var neg: i64 = 0
38 if nx_f32_lt(s, AG_F32_ZERO) == 1 { neg = 1; s = nx_f32_neg(s) }
39 let half: i64 = ag_constf(1, 2)
40 var m: i64 = 0
41 var go: i64 = 1
42 while go == 1 {
43 let mid: i64 = nx_f32_add(nx_i32_to_f32(m), half)
44 if nx_f32_lt(mid, s) == 1 {
45 m = m + 1
46 if m >= 100000 { go = 0 }
47 } else { go = 0 }
48 }
49 if neg == 1 { return 0 - m }
50 return m
51}
52
53// build the gradcheck loss graph on `tape`; write leaf indices [w1,b1,w2,b2] into leaves; return loss node.
54func tr_build_loss(tape: *i64, np: *i64, w1: i64, b1: i64, w2: i64, b2: i64, x: i64, leaves: *i64) -> i64 {
55 *np = 0
56 let nw1: i64 = ag_leaf(tape, np, w1)
57 let nb1: i64 = ag_leaf(tape, np, b1)
58 let nw2: i64 = ag_leaf(tape, np, w2)
59 let nb2: i64 = ag_leaf(tape, np, b2)
60 let nx: i64 = ag_leaf(tape, np, x)
61 let h1: i64 = ag_add(tape, np, ag_mul(tape, np, nw1, nx), nb1)
62 let rr: i64 = ag_relu(tape, np, h1)
63 let h2: i64 = ag_add(tape, np, ag_mul(tape, np, rr, nw2), nb2)
64 let loss: i64 = ag_mul(tape, np, h2, h2)
65 leaves[0] = nw1; leaves[1] = nb1; leaves[2] = nw2; leaves[3] = nb2
66 return loss
67}
68
69func tr_loss_val(w1: i64, b1: i64, w2: i64, b2: i64, x: i64) -> i64 {
70 let tape: *i64 = (sys_mmap(64 * 5 * 8)) as *i64
71 let np: *i64 = (sys_mmap(8)) as *i64
72 let lv: *i64 = (sys_mmap(4 * 8)) as *i64
73 let loss: i64 = tr_build_loss(tape, np, w1, b1, w2, b2, x, lv)
74 return ag_val(tape, loss)
75}
76
77func tr_analytic_grads(w1: i64, b1: i64, w2: i64, b2: i64, x: i64, gout: *i64) -> i64 {
78 let tape: *i64 = (sys_mmap(64 * 5 * 8)) as *i64
79 let np: *i64 = (sys_mmap(8)) as *i64
80 let lv: *i64 = (sys_mmap(4 * 8)) as *i64
81 let loss: i64 = tr_build_loss(tape, np, w1, b1, w2, b2, x, lv)
82 ag_backward(tape, *np, loss)
83 gout[0] = ag_grad(tape, lv[0]); gout[1] = ag_grad(tape, lv[1])
84 gout[2] = ag_grad(tape, lv[2]); gout[3] = ag_grad(tape, lv[3])
85 return 0
86}
87
88// central finite-difference grad of the loss w.r.t. param index pi (0..3), step h.
89func tr_fd_grad(p: *i64, pi: i64, h: i64) -> i64 {
90 let pp: *i64 = (sys_mmap(5 * 8)) as *i64
91 let pm: *i64 = (sys_mmap(5 * 8)) as *i64
92 var i: i64 = 0
93 while i < 5 { pp[i] = p[i]; pm[i] = p[i]; i = i + 1 }
94 pp[pi] = nx_f32_add(p[pi], h)
95 pm[pi] = nx_f32_sub(p[pi], h)
96 let lp: i64 = tr_loss_val(pp[0], pp[1], pp[2], pp[3], pp[4])
97 let lm: i64 = tr_loss_val(pm[0], pm[1], pm[2], pm[3], pm[4])
98 return nx_f32_div(nx_f32_sub(lp, lm), nx_f32_add(h, h))
99}
100
101// full-batch GD training of y = w*x + b on y = 1.5x - 0.5. Writes final w,b + first/last loss.
102func tr_train(epochs: i64, wout: *i64, bout: *i64, lfirst: *i64, llast: *i64) -> i64 {
103 var w: i64 = AG_F32_ZERO
104 var b: i64 = AG_F32_ZERO
105 let lr: i64 = ag_constf(1, 20)
106 let inv8: i64 = ag_constf(1, 8)
107 let f15: i64 = ag_constf(3, 2)
108 let f05: i64 = ag_constf(1, 2)
109 let xs: *i64 = (sys_mmap(8 * 8)) as *i64
110 let ys: *i64 = (sys_mmap(8 * 8)) as *i64
111 var i: i64 = 0
112 while i < 8 {
113 let xi: i64 = ag_constf(i, 4)
114 xs[i] = xi
115 ys[i] = nx_f32_sub(nx_f32_mul(f15, xi), f05)
116 i = i + 1
117 }
118 let tape: *i64 = (sys_mmap(2048 * 5 * 8)) as *i64
119 let np: *i64 = (sys_mmap(8)) as *i64
120 var ep: i64 = 0
121 while ep < epochs {
122 *np = 0
123 let nw: i64 = ag_leaf(tape, np, w)
124 let nb: i64 = ag_leaf(tape, np, b)
125 var nsum: i64 = ag_leaf(tape, np, AG_F32_ZERO)
126 i = 0
127 while i < 8 {
128 let nxi: i64 = ag_leaf(tape, np, xs[i])
129 let nyi: i64 = ag_leaf(tape, np, ys[i])
130 let pred: i64 = ag_add(tape, np, ag_mul(tape, np, nw, nxi), nb)
131 let res: i64 = ag_sub(tape, np, pred, nyi)
132 let sq: i64 = ag_mul(tape, np, res, res)
133 nsum = ag_add(tape, np, nsum, sq)
134 i = i + 1
135 }
136 let ninv: i64 = ag_leaf(tape, np, inv8)
137 let nloss: i64 = ag_mul(tape, np, nsum, ninv)
138 ag_backward(tape, *np, nloss)
139 if ep == 0 { *lfirst = ag_val(tape, nloss) }
140 *llast = ag_val(tape, nloss)
141 w = nx_f32_sub(w, nx_f32_mul(lr, ag_grad(tape, nw)))
142 b = nx_f32_sub(b, nx_f32_mul(lr, ag_grad(tape, nb)))
143 ep = ep + 1
144 }
145 *wout = w; *bout = b
146 return 0
147}
148
149func tr_emit(fd: i64, r: *i64) -> i64 {
150 tr_w(fd, "TRAINR1GATE authored=organ engine=scalar-tape-autograd-f32" as *u8)
151 tr_w(fd, " | A_gradcheck_pass=" as *u8); tr_wn(fd, r[0])
152 tr_w(fd, " worst_|fd-analytic|_milli=" as *u8); tr_wn(fd, r[1])
153 tr_w(fd, " | B_learns_pass=" as *u8); tr_wn(fd, r[2])
154 tr_w(fd, " w_milli=" as *u8); tr_wn(fd, r[3]); tr_w(fd, " b_milli=" as *u8); tr_wn(fd, r[4])
155 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])
156 tr_w(fd, " | C_bitexact_repro_pass=" as *u8); tr_wn(fd, r[7])
157 if r[8] == 1 { tr_w(fd, " verdict=GREEN\n" as *u8) } else { tr_w(fd, " verdict=RED\n" as *u8) }
158 return 0
159}
160
161func main() -> i64 {
162 var ok: i64 = 1
163
164 // ---------- Gate A: gradcheck ----------
165 let p: *i64 = (sys_mmap(5 * 8)) as *i64
166 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)
167 let grads: *i64 = (sys_mmap(4 * 8)) as *i64
168 tr_analytic_grads(p[0], p[1], p[2], p[3], p[4], grads)
169 let h: i64 = ag_constf(1, 128)
170 let flo: i64 = ag_constf(1, 64)
171 let tol: i64 = ag_constf(1, 32)
172 var gradcheck_pass: i64 = 1
173 var worst_milli: i64 = 0
174 var pi: i64 = 0
175 while pi < 4 {
176 let ana: i64 = grads[pi]
177 let fd: i64 = tr_fd_grad(p, pi, h)
178 let num: i64 = nx_f32_abs(nx_f32_sub(fd, ana))
179 var den: i64 = nx_f32_abs(ana)
180 if nx_f32_lt(den, flo) == 1 { den = flo }
181 let thresh: i64 = nx_f32_mul(tol, den)
182 if nx_f32_lt(num, thresh) != 1 { gradcheck_pass = 0 }
183 let nm: i64 = tr_f32_to_milli(num)
184 if nm > worst_milli { worst_milli = nm }
185 pi = pi + 1
186 }
187 if gradcheck_pass != 1 { ok = 0 }
188
189 // ---------- Gate B: a model provably learns ----------
190 let wbox: *i64 = (sys_mmap(8)) as *i64
191 let bbox: *i64 = (sys_mmap(8)) as *i64
192 let lf: *i64 = (sys_mmap(8)) as *i64
193 let ll: *i64 = (sys_mmap(8)) as *i64
194 tr_train(500, wbox, bbox, lf, ll)
195 let w: i64 = *wbox
196 let b: i64 = *bbox
197 var learns_pass: i64 = 1
198 if nx_f32_lt(*ll, ag_constf(1, 1000)) != 1 { learns_pass = 0 } // loss < 1/1000
199 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
200 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
201 if nx_f32_lt(*ll, *lf) != 1 { learns_pass = 0 } // loss decreased
202 if learns_pass != 1 { ok = 0 }
203
204 // ---------- Gate C: bit-exact reproducible ----------
205 let wbox2: *i64 = (sys_mmap(8)) as *i64
206 let bbox2: *i64 = (sys_mmap(8)) as *i64
207 let lf2: *i64 = (sys_mmap(8)) as *i64
208 let ll2: *i64 = (sys_mmap(8)) as *i64
209 tr_train(500, wbox2, bbox2, lf2, ll2)
210 var repro_pass: i64 = 1
211 if *wbox2 != w { repro_pass = 0 }
212 if *bbox2 != b { repro_pass = 0 }
213 if repro_pass != 1 { ok = 0 }
214
215 // ---------- emit ----------
216 let r: *i64 = (sys_mmap(9 * 8)) as *i64
217 r[0] = gradcheck_pass; r[1] = worst_milli; r[2] = learns_pass
218 r[3] = tr_f32_to_milli(w); r[4] = tr_f32_to_milli(b)
219 r[5] = tr_f32_to_milli(*lf); r[6] = tr_f32_to_milli(*ll)
220 r[7] = repro_pass; r[8] = ok
221 tr_emit(1, r)
222 let logf: i64 = sys_openat_append(TR_LOG, 420)
223 if logf >= 0 { tr_emit(logf, r); sys_close(logf) }
224
225 if ok == 1 { return 0 }
226 return 1
227}