tiny-math-1m / STATUS.md
Atharva1232's picture
tokenizer ablation: digit-split pre-tok = 0.2% -> 99.7% on TinyStories-1M; smallest arithmetic LLM now 3.75M
f117e6b verified
|
Raw History Blame Contribute Delete
14.5 kB
# 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