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}