File size: 14,541 Bytes
9feb5f5 f117e6b 9feb5f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | # tiny-math β overnight run status log
<!-- Autonomous agent: update this file at every decision point. -->
## Goal
Find the smallest transformer that can do 4-digit addition. Sizes {1M, 5M, 8M},
from-scratch GPT, digit-level vocab (16 tokens), SFT on generated data, GRPO
polish only if SFT plateaus. Headline number + phase diagram + honest model card.
## Key facts
- Kaggle quota: 30h/week (checked at start: 0h used). Kernels run serially.
- Baselines (local, n=5000, 4-digit l2r): bigram = 0.0%, untrained 1M = 0.0%.
Floor is zero β any accuracy is real learning.
- Local CPU speed: ~3100 tok/s (1M model). Kaggle T4 expected ~100-200x faster.
- Data: train/eval contamination prevented BY CONSTRUCTION β both eval sets
built upfront, stream rejects any (a,b) in either. (Bug found + fixed after
local run crashed on the overlap assert; bakeoff v1 was deleted pre-run.)
## Formats (Phase 0b bakeoff picks winner)
l2r = "47+68=\n115\n" | rtl = "47+68=\n511\n" (answer reversed) |
cot = "47+68=\n7+8=15,4+6+1=11,115\n" (digit steps + answer, comma-sep)
Paper (arXiv 2307.03381) predicts rtl and cot >> l2r for addition.
## Decision rules (pre-committed, no drift overnight)
1. Format winner = highest final acc at 1M/4-digit/10M tokens. Tie β prefer cot
(most interesting), then rtl.
2. GRPO phase triggers ONLY if best SFT (8m, 50M tokens, 4-digit) < 90%.
GRPO reuses the 150-line trainer pattern from grpo-gsm8k (shaped reward with
format floor β zero-variance groups give zero advantage).
3. If bakeoff shows ALL formats ~0% at 1M/10M β raise 1M config to d=192 L=6
before sweeping (under-parameterized, don't burn sweep quota on a dead cell).
4. Extrapolation kernel runs after sweep; it mounts sweep outputs as a dataset.
## SWEEP RESULTS (n=5000, cot format, all 11 runs complete)
4-digit addition, exact-match on held-out problems:
| model | 71k probs | 357k probs | 1.78M probs |
|---|---|---|---|
| 1M | 0.0% | 0.2% | **100.0%** |
| 5M | 0.0% | 95.7% | 100.0% |
| 8M | 0.0% | 98.8% | 100.0% |
2-digit ceiling: 1M and 8M both 100% at 1.78M probs.
- Headline: **1M params does 4-digit addition perfectly** (0 errors / 5000).
- Phase transition visible in the 1M row: 0.2% -> 100.0% between 357k and 1.78M probs.
- Per rule 2: SFT hit 100% -> NO GRPO needed for 4-digit in-distribution.
- Wall time: whole sweep β 50 min on one T4, ~0.7h quota. Quota check needed
before extrapol push.
## MISSION UPDATE (user clarification)
Part 1 (from-scratch 1M calculator) = good reference, KEEP AND SHIP IT, but
the REAL target: **the smallest real LLM (pretrained, knows language) that is
good at arithmetic.** Plan:
- Stage A: zero-shot+1-shot quiz of real LLMs {TinyStories-1M/3M/33M,
pythia-14m/70m, SmolLM2-135M} on the same 4-digit task β kernel READY
(kernels/llmquiz/script.py, needs internet=true to pull HF weights).
- Stage B: SFT ladder β fine-tune the promising ones on the same arithmetic
lessons, find smallest to clear >=95%.
- Stage C: verdict number + language-retention probe (does it still speak
English after arithmetic training?) + final report.
Open question worth measuring: BPE tokenizers split numbers badly, so a
14M LLM might LOSE to our 1M calculator β that contrast IS the story.
- 01:0x extrapol v1 CANCELLED at 59min β MY push timeout (-t 3600) was the
kill (cancel_acknowledged = Kaggle honoring the timeout). No outputs
survived (jsonl written only at end). FIXES: (a) -t 7200 now, (b) incremental
extrapolation.jsonl append after EVERY eval row β a kill can no longer eat
results, (c) trunc_frac metric added β 5-6 digit cot needs >64 tokens, and
truncation would otherwise masquerade as model failure, (d) make_eval_set
attempts-guarded (1-digit space is only 100 pairs; unbounded while-loop
would hang forever).
- 01:3x extrapol v2 PUSHED + RUNNING, watcher armed (140min window).
## STAGE A RESULTS (zero/1-shot, n=2000, 1 worked cot example, complete)
**All 6 real LLMs: strict 0.000, lenient 0.000.** From 3.7M to 134.5M params.
Failure modes by family:
- TinyStories (1M/3M/33M): ignore the task, TELL STORIES ("One day, a 3
year old boy was walking in the park...")
- pythia-14m: attempts cot format, loops ("3+9=9,9+4=12,9+4=12,...")
- pythia-70m: emits plausible cot chains with wrong digits, then repeats
the worked example back
- SmolLM2-135M: closest but loops / collapses to "100+100+100..."
Reference: from-scratch 1M (trained) = 1.000. Zero-shot β trained; the LLM
advantage (language) buys nothing on pure arithmetic below 135M.
Saved: results/stage_a_results.jsonl.
## SHIPPED (part 1)
- HF: Atharva1232/tiny-math-1m (model + card + all results + chart), public
- GitHub: Its-Atharva-Gupta/tiny-math (public), pushed clean, token never
touched git config (one-shot GIT_ASKPASS helper)
- MODEL_CARD.md includes: 100% headline, phase-cliff finding, extrapolation
0%-everywhere table, bigram/untrained baselines at 0%, contamination-by-
construction note, positional-pattern failure analysis with examples.
## STAGE B ROUND 1 (500k problems, complete) β NULL RESULT + DIAGNOSIS
All LLMs ~0% after fine-tuning (TinyStories-1M: 0.001 strict = 2/2000 luck;
others 0.000). But curves reveal THREE distinct bugs, not one failure:
- pythia-14m/70m: loss NaN at step 1 (fp16 autocast instability) β models
were dead on arrival, hence empty generations. NOT a real result.
- TinyStories-1M/3M: loss fell properly (2.83->1.65, 2.30->1.05) β they
learned the text but eval 0%: (a) BPE splits numbers inconsistently, no
stable digit mapping; (b) my eval prompt had a worked example models never
saw in training (format mismatch, my bug).
- 33M/135M: OOM at batch 64 (50k-vocab logits vs 16GB) β never trained.
- Also found: round-1 final eval was same-seed superset of tuning eval ->
biased. Fixed in round 2 (independent seeds 7781/7782 + overlap assert).
- Side finding: fine-tuning DESTROYS TinyStories language ("Once upon a
time" -> digit chains). Language-retention probe kept for round 2.
## STAGE B ROUND 2 (RUNNING, watcher proc_9c5edaeb2e6f, -t 18000)
Pre-registered configs:
- TinyStories-1M: 1.78M problems (escalation: smallest LLM candidate gets
same budget our from-scratch 1M model got)
- pythia-14m/70m: 500k, pure fp32 (NaN fix)
- TinyStories-33M: 500k, batch 16 (OOM fix)
- SmolLM2-135M: 500k, batch 8 (OOM fix)
- All: eval prompt = "a+b=\n" matching training format exactly.
NaN guard aborts a model at first non-finite loss.
## STAGE B ROUND 2 RESULTS (complete, 142min) β BREAKTHROUGH
| LLM | params | problems | strict |
|---|---|---|---|
| **TinyStories-33M** | **68.5M** | 500k | **98.5% PASSES** |
| SmolLM2-135M | 134.5M | 500k | 76.9% |
| TinyStories-1M | 3.7M | 1.78M | 0.2% (loss 1.27->0.74, eval flat 0: BPE wall confirmed at the budget our from-scratch 1M hit 100%) |
| pythia-14m | 14.1M | 500k | NaN @ step 449 even in fp32 β GPT-NeoX instability, not fp16 |
| pythia-70m | 70.4M | 500k | NaN @ step 899 even in fp32 |
- TS-33M wrong answers: gets the sum RIGHT then hallucinates more problems
("...15989\\n8984+8984=...") β strict parses first line => 98.5%.
lenient parses last number => 0. Disclosed, not hidden.
- Language: TS-33M lost English ("Once upon a time" -> digit chains);
SmolLM2-135M KEPT it ("...a little girl named Lily..."). Capacity helps
retention. TS-1M also lost English.
- Headline so far: smallest passing real LLM = 68.5M (vs 1.07M from scratch).
## STAGE B ROUND 3 (RUNNING, watcher proc pending, -t 10800)
Two candidates that never got a fair shot:
- TinyStories-3M (8.3M): round-1 0% was under broken eval prompt; now
round-1-surviving recipe + fixed eval, 500k problems.
- pythia-14m (14.1M): anti-NaN recipe v3 β LR 2e-5, embedding LR x0.1
(embedding = 46% of its params, spike suspect), no wd on norms/biases,
10% warmup, NaN auto-retry ladder [5e-6].
## ROUND 3 RESULTS (partial β kernel crashed after TS-3M)
- TinyStories-3M (8.3M) @ 500k: **strict 0.000** β half-learned carry pattern
but BPE garbage ("cartridge1", "8irs") swallows digits. Below the floor.
- pythia-14m: NaN'd at 2e-5 AND at the 5e-6 retry, then MY retry handler
crashed (off-by-one: NAN_RETRIES[attempt] with attempt==len). Handler bug
fixed by design in round 4 (no retry ladder there).
- Verdict math: candidates below 68.5M are now 3.7M (0.2%), 8.3M (0%),
14.1M (untrainable so far). pythia-14m is the LAST one that could beat
the 68.5M headline -> round 4.
## ROUND 4 (RUNNING, watcher armed, -t 7200)
Recipe v5 for pythia-14m: freeze embed_in+embed_out (91% of its params,
the NaN suspect β 50277-vocab matrices under full-param AdamW), train only
the ~1.2M transformer blocks at lr 1e-4, grad-norm logged pre-clipping.
If this NaNs too -> documented stability blocker, verdict stands at 68.5M.
## ROUND 4 + LOCAL NaN AUTOPSY (diagnosis in progress)
- Round 4 (frozen "embed*" only): NaN at STEP 2, grad_norm=nan. Log revealed
my filter froze only embed_in (6.44M) β lm_head (also 6.44M) kept the name
"lm_head", not caught by "embed" substring.
- Local autopsy (nan_autopsy.py, pythia-14m on CPU, exact Kaggle recipe):
forward clean (loss 2.42, max|logit| 23), embedding rows used by cot data
fine (max|w| 0.135), step-1 gradients ALL FINITE (no NaN/inf, total norm
60.09, top grad 28) -> then ONE AdamW step turns 46/76 tensors NaN.
Finite grads + finite clip + one optimizer step = NaN weights means an
optimizer-internals bug (suspect: foreach implementation), not data/LR.
- LR-vs-time pattern across 5 recipes: 1e-4βstep2, 6e-5 fp16βstep1,
6e-5 fp32βstep449, 2e-5βlater, 5e-6βlater. Consistent with slow weight
drift into an overflow threshold, OR two separate mechanisms.
- nan_bisect.py running locally: 4 one-step variants (foreach default /
foreach=False / wd=0 / SGD) to name the culprit. If foreach=False is
clean -> that's the Kaggle round-5 fix (one flag).
- nan_bisect RESULT β CASE CLOSED: **param dtype = float16!** transformers
5.19 loads pythia-14m in its native fp16; Kaggle's transformers 4.x loaded
fp32. fp16 + AdamW explodes because eps=1e-8 is UNREPRESENTABLE in fp16
(min normal 6e-5): underflowed second-moment rows divide by ~0 -> inf ->
NaN weights in ONE step. All 4 bisect variants: AdamW (any wd/foreach)
= 51 NaN tensors; SGD = clean (no division) β confirms mechanism exactly.
This explains every instant NaN (rounds 1/3-retry/4 locally). The SLOW
Kaggle NaNs (step 449 @ 6e-5 fp32) are a separate real instability β
round 5 recipe addresses both: explicit fp32 + blocks-only + lr 3e-4.
- Skill patched: rlvr-finetuning now carries the fp16/AdamW eps lesson and
the clip-can't-save-NaN-grads lesson.
- round5_preflight.py running locally: 10-step fp32 blocks-only test; if
CLEAN -> Kaggle round 5 with same recipe at 500k problems.
## ROUND 5 RESULT (complete, 10min) β NaN SAGA ENDS, REAL LEARNING
pythia-14m blocks-only fp32: **strict 0.642 best / 0.6255 final** @ 500k
problems. Stable the whole run (grad_norm_max 56.7, zero NaN). Same
hallucinate-more-problems failure as TS-33M (gets 8857+7132 right -> 15989
then invents more problems; lenient=0 because last number is hallucination).
- Sub-68.5M ladder now: 3.7M=0.2%, 8.3M=0%, 14.1M=64.2% (blocks-only,
1.19M trainable), 68.5M=98.5% PASSES, 134.5M=76.9%.
- Per pre-committed escalation rule (10%<=acc<95% -> 1.78M problems):
ROUND 6 launched β pythia-14m @ 1.78M, same proven recipe.
If >=95%: headline drops 68.5M -> 14.1M. Else: verdict closes at 68.5M.
- SmolLM2-135M also in the escalation band (76.9%) but ABOVE the 68.5M
headline β escalating it cannot change the "smallest" answer, skipped
(quota discipline, reason recorded).
## TOKENIZER ABLATION (user request, RUNNING β kernel tiny-math-tokablation)
One-variable experiment: TinyStories-1M, native BPE scored strict 0.002 @
1.78M problems (control r2_ts1m_1780k). Treatment = SMALLEST tokenizer
change: number runs split PER DIGIT (pre-tok `?\\p{N}+` -> `?\\d`), same
50,257 vocab, SAME embedding matrix, no new tokens, no resize. Everything
else frozen: same checkpoint, same 1.78M problems, same fp16/lr 6e-5/
batch-32 recipe, same eval sets (tune 7781 / final 7782).
- Local validation done: OLD chops `1842 -> ['18','42']`, `9920 -> ['99','20']`
(inconsistent boundaries β the mechanism we suspected); NEW gives one
token per digit; 500-string round-trip check passed; alphabet closed,
no spaces (chunk-interaction assumption holds).
- Compute: digit-split = 1.23x tokens/problem (36.9 -> 45.4), disclosed as
a consequence of the treatment (same problems, ~1.23x steps).
- PRE-REGISTERED PREDICTIONS: BPE-chunking was THE wall -> big jump
(>>10%, plausibly 90%+; from-scratch 806K hit 100% on same data);
tokenization was not it -> <5% (wall = pretraining/capacity).
- Implementation: per-char id dict == patched-pretokenizer for this corpus
(single-char chunk = BPE identity), asserted in-kernel at startup.
## Timeline
- 23:0x Phase 0 done: train.py + baselines.py + unit tests all pass. Local
2-digit 2M-token run proved learning (loss 2.08β1.34, acc 0β~2% on l2r;
crashed only at final-eval overlap assert β the bug since fixed).
- 23:1x bakeoff v1+v2 FAILED: Kaggle runs kernels as notebooks; sibling import
`from train import train` not resolvable (ModuleNotFoundError at ~10s).
sys.path patch also failed. FIX: single-file merged kernels via make_kernel.py
(train.py core + driver concatenated, syntax-gated). No more sibling imports.
- 23:5x bakeoff v3 COMPLETE (single-file, ~3 min, 6 configs):
2-digit: cot 16.6% > l2r 15.0% > rtl 8.7% | 4-digit: cot 0.5%, others 0.
FORMAT = cot (pre-committed rule 1). 1M@10M tok is below phase transition
for 4-digit β model learns procedure format (see wrong-examples: valid digit
step chains with wrong sums) but not yet the arithmetic.
- 00:0x sweep PUSHED + RUNNING (11 configs: 3 sizes Γ {2M,10M,50M} @ 4d cot
+ 2 ceiling 2d @ 50M). Watcher proc_069900fd4eb2 armed.
- Also fixed: extrapol CHECKPOINTS tags now match sweep tag scheme
(sw_1m_4d_50000k etc, format not in tag); extrapol metadata kernel_sources
(was dataset_sources β would have failed at push).
- Next: sweep results β extrapol β decide GRPO (rule 2: only if best SFT <90%).
## Kernel IDs
- tiny-math-bakeoff (DONE v3): format winner = cot
- tiny-math-sweep (RUNNING): {1m,5m,8m} Γ {2M,10M,50M} tok @ 4d + 2d ceiling
- tiny-math-extrapol (pending, rebuilt single-file): ckpt eval at 1..6 digits
|