code wiki / _hdl_build / nx_nano_lm.nx
nx_nano_lm.nx source
↩ module page · 112 lines · 7582 B
1// nx_nano_lm.nx -- OUR OWN SOVEREIGN nanoGPT (the bigram/char rung where nanoGPT+makemore start), built from the
2// Nishi rung up. A char-level language model that ACTUALLY TRAINS (integer gradient descent) and GENERATES text back.
3// Model: a V*V logit table W (W[prev][next]); forward = softmax(W[prev]); cross-entropy gradient = probs - onehot
4// (NO log needed -- the softmax+CE gradient is just probs minus the target one-hot). 100% integer Q16 => bit-exact
5// deterministic, $0 on this laptop CPU (no GPU/CUDA/PyTorch). Trains on a tiny cyclic corpus, learns the transitions,
6// and generates the pattern back.
7// T1 P(correct next char) INCREASES across training (it learns the statistics).
8// T2 GENERATION reproduces the learned pattern (from 'a' -> a,b,c,a,b,c).
9// T3 (EXCEED) deterministic bit-identical. T4 argmax(W[prev]) == the correct next char for every char.
10// expect_exit: 0 Sovereign: nx_syscalls.
11import "nx_syscalls.nx"
12import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
13import "nx_g_puts_lib.nx"
14
15// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
16// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
17// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
18// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
19func g_pn(v: i64) -> i64 { nxi_out(v); return 0 }
20func g_ch(id: i64) -> i64 { let b: *u8=sys_mmap(1); b[0]=(97+id) as u8; sys_write(1,b,1); return 0 } // 0->a,1->b,2->c
21func ck(name: *u8, c: i64) -> i64 { if c==1 { g_puts(" PASS " as *u8) } else { g_puts(" FAIL " as *u8) } g_puts(name); g_puts("\n" as *u8); return c }
22
23const Q: i64 = 16
24const ONE: i64 = 65536
25const V: i64 = 3 // vocab: a,b,c
26const LOG2E: i64 = 94548
27const C1: i64 = 45426
28const C2: i64 = 15743
29func fxmul(a: i64, b: i64) -> i64 { return (a*b)>>Q }
30func exp_fx(x: i64) -> i64 {
31 var xx: i64=x; if xx>0 { xx=0 }
32 let yabs: i64=fxmul(0-xx, LOG2E); let nabs: i64=yabs>>Q; let fabs: i64=yabs-(nabs<<Q)
33 let f2: i64=fxmul(fabs,fabs); let p: i64=ONE+fxmul(fabs,C1)+fxmul(f2,C2); let inv: i64=(ONE*ONE)/p
34 if nabs>=31 { return 0 } return inv>>nabs
35}
36func softmax(logits: *i64, probs: *i64) -> i64 {
37 var mx: i64=logits[0]; var i: i64=1; while i<V { if logits[i]>mx { mx=logits[i] } i=i+1 }
38 let e: *i64=sys_mmap(V*8) as *i64; var sum: i64=0; i=0; while i<V { e[i]=exp_fx(logits[i]-mx); sum=sum+e[i]; i=i+1 }
39 i=0; while i<V { probs[i]=(e[i]*ONE)/sum; i=i+1 } return 0
40}
41func argmax(a: *i64) -> i64 { var bi: i64=0; var bv: i64=a[0]; var i: i64=1; while i<V { if a[i]>bv { bv=a[i]; bi=i } i=i+1 } return bi }
42
43// train the bigram LM on corpus[0..n); pcorr[0]=avg P(correct) epoch0, pcorr[1]=final. W is V*V, zero-init.
44func train(W: *i64, corpus: *i64, n: i64, epochs: i64, lr: i64, pcorr: *i64) -> i64 {
45 var i: i64=0; while i<V*V { W[i]=0; i=i+1 }
46 let probs: *i64=sys_mmap(V*8) as *i64
47 var ep: i64=0
48 while ep<epochs {
49 var pc: i64=0
50 var k: i64=0
51 while k<(n-1) {
52 let prev: i64=corpus[k]; let next: i64=corpus[k+1]
53 let row: *i64=sys_mmap(V*8) as *i64; var m: i64=0; while m<V { row[m]=W[prev*V+m]; m=m+1 } softmax(row, probs)
54 pc = pc + probs[next]
55 var j: i64=0; while j<V { var g: i64=probs[j]; if j==next { g=g-ONE } W[prev*V+j]=W[prev*V+j]-fxmul(lr,g); j=j+1 }
56 k=k+1
57 }
58 if ep==0 { pcorr[0]=pc/(n-1) }
59 if ep==(epochs-1) { pcorr[1]=pc/(n-1) }
60 ep=ep+1
61 }
62 return 0
63}
64func gen(W: *i64, start: i64, outids: *i64, steps: i64) -> i64 {
65 var cur: i64=start; var k: i64=0
66 while k<steps { outids[k]=cur; let row: *i64=sys_mmap(V*8) as *i64; var m: i64=0; while m<V { row[m]=W[cur*V+m]; m=m+1 } cur=argmax(row); k=k+1 }
67 return 0
68}
69
70func main() -> i64 {
71 g_puts("nx_nano_lm (OUR OWN SOVEREIGN nanoGPT -- char LM that TRAINS + GENERATES, integer/deterministic, $0)\n" as *u8)
72 var pass: i64=0; var total: i64=0
73 let N: i64=24
74 let corpus: *i64=sys_mmap(N*8) as *i64; var i: i64=0; while i<N { corpus[i]=i%V; i=i+1 } // a,b,c,a,b,c,...
75 g_puts(" corpus = abcabc... ("); g_pn(N); g_puts(" chars, vocab a/b/c); model = bigram logit table, integer SGD\n" as *u8)
76 let W: *i64=sys_mmap(V*V*8) as *i64; let pc: *i64=sys_mmap(2*8) as *i64
77 train(W, corpus, N, 400, ONE/4, pc)
78 g_puts(" P(correct next): epoch0="); g_pn(pc[0]); g_puts(" final="); g_pn(pc[1]); g_puts(" (ONE=65536; ~22000=1/3 chance -> ~ONE=learned)\n" as *u8)
79 var t1: i64=0; if pc[1]>pc[0] { if pc[1]>(ONE*8/10) { t1=1 } }
80 pass=pass+ck("T1: P(correct next char) INCREASED to >0.8 -- the model LEARNED the character statistics" as *u8, t1); total=total+1
81
82 let gids: *i64=sys_mmap(6*8) as *i64; gen(W, 0, gids, 6)
83 g_puts(" GENERATED from 'a': "); var gi: i64=0; while gi<6 { g_ch(gids[gi]); gi=gi+1 } g_puts(" (want abcabc)\n" as *u8)
84 var t2: i64=0; if gids[0]==0 { if gids[1]==1 { if gids[2]==2 { if gids[3]==0 { if gids[4]==1 { if gids[5]==2 { t2=1 } } } } } }
85 pass=pass+ck("T2: GENERATION reproduces the learned pattern (a->b->c->a->b->c) -- it generates text it learned" as *u8, t2); total=total+1
86
87 let W2: *i64=sys_mmap(V*V*8) as *i64; let pc2: *i64=sys_mmap(2*8) as *i64; train(W2, corpus, N, 400, ONE/4, pc2)
88 var same: i64=1; i=0; while i<V*V { if W[i]!=W2[i] { same=0 } i=i+1 }
89 var t3: i64=0; if same==1 { if pc[1]==pc2[1] { t3=1 } }
90 pass=pass+ck("T3 (EXCEED): DETERMINISTIC -- two trainings give a BIT-IDENTICAL model (float/CUDA LM training cannot)" as *u8, t3); total=total+1
91
92 let r0: *i64=sys_mmap(V*8) as *i64; let r1: *i64=sys_mmap(V*8) as *i64; let r2: *i64=sys_mmap(V*8) as *i64
93 var m: i64=0; while m<V { r0[m]=W[0*V+m]; r1[m]=W[1*V+m]; r2[m]=W[2*V+m]; m=m+1 }
94 var t4: i64=0; if argmax(r0)==1 { if argmax(r1)==2 { if argmax(r2)==0 { t4=1 } } }
95 pass=pass+ck("T4: learned transitions -- argmax(W[a])=b, argmax(W[b])=c, argmax(W[c])=a (the bigram statistics)" as *u8, t4); total=total+1
96
97 g_puts(" -- OUR OPTIONS to train without thousands of $ (grounded ct_*.raw) --\n" as *u8)
98 g_puts(" 1. DON'T train from scratch: transfer-learning + FINE-TUNE a free pretrained model (Hugging Face) -- minutes/$0-cheap.\n" as *u8)
99 g_puts(" 2. LoRA / parameter-efficient fine-tune: train ~1% of weights -> single consumer GPU.\n" as *u8)
100 g_puts(" 3. KNOWLEDGE DISTILLATION: train a small student from a big teacher's outputs (cheap, small final model).\n" as *u8)
101 g_puts(" 4. FREE compute: Kaggle / Colab free GPU/TPU hours; or rent a SPOT instance (vast.ai/RunPod) by the hour.\n" as *u8)
102 g_puts(" 5. SOVEREIGN (this organ): a tiny deterministic model trains on THIS laptop's CPU for $0 -- no GPU, no cloud, reproducible.\n" as *u8)
103
104 var okall: i64=0; if pass==total { okall=1 }
105 g_puts("---- nx_nano_lm: passed "); g_pn(pass); g_puts(" / "); g_pn(total); g_puts(" ----\n" as *u8)
106 if okall==1 {
107 let logf: i64=sys_openat_append("knowledge/status/nano_lm.log" as *u8, 420)
108 if logf>=0 { let z: i64=sys_write(logf,"NXNANOLM GREEN: sovereign char bigram LM -- trains by integer SGD (P-correct 1/3->~1), generates abcabc, deterministic, $0\n" as *u8,118); sys_close(logf) }
109 g_puts("verdict=GREEN (our own sovereign nanoGPT, bigram rung: trains by integer gradient descent + generates the learned pattern, deterministic, $0 on this laptop)\n" as *u8); sys_exit(0); return 0
110 }
111 g_puts("verdict=RED\n" as *u8); sys_exit(1); return 1
112}