tiny-math-1m / REPORT.md
Atharva1232's picture
final report + talk+math ratio-curve results (7 configs)
eded994 verified
|
Raw History Blame Contribute Delete
8.69 kB

tiny-math — Final Report

How small can a model be to do arithmetic — and can one small model also talk?


TL;DR — the three answers

Question Answer
1. Smallest model that does arithmetic (from scratch)? 805,632 params → 100.0% on 4-digit addition (5000/5000), after 1.78M practice problems
2. Smallest real pretrained LLM that does arithmetic? 3.75M params (TinyStories-1M) → 99.7%, but only after a one-rule tokenizer fix; with native BPE, it takes 14.1M (pythia-14m, 99.8%)
3. One model that both talks and does math? Yes: the 7:1 rehearsal mix → 89.8% math with language perfectly preserved (perplexity ×0.98 of base) — with a measured dial showing exactly what math costs you at each mix ratio

Cost: 15.6 of 30 free GPU-hours, ~13 GPU kernels + local CPU runs, run autonomously overnight and across the next day.


Part 1 — From scratch: how small can a model be?

A decoder-only GPT with a 16-token vocabulary (digits, +, =, newline, comma, pad), trained from random initialization on programmatic chain-of-thought addition. No pretraining, no RL.

The size × data sweep (n=5000 held-out, strict exact-match):

actual params 71k problems 357k problems 1.78M problems
805,632 0.0% 0.2% 100.0%
3.58M 0.0% 95.7% 100.0%
6.34M 0.0% 98.8% 100.0%

Three findings:

  1. The transition is a cliff, not a ramp. The 805K model sits at 0.2% with 357K problems and hits 100.0% with 1.78M — reproducing the sharp phase transitions of Teaching Arithmetic to Small Transformers (arXiv:2307.03381) at sub-million scale. Bigger models cross the cliff on less data: capacity buys sample efficiency.
  2. The floor is genuinely zero. A bigram count model (next-char probabilities from 10M training tokens) and an untrained model both score 0.0%. Every point above zero is learned.
  3. Perfect in-distribution ≠ learning the algorithm. All three models: 100% at the trained 4-digit length, 0% at every other length (1-, 2-, 3-, 5-, 6-digit, n≈2000 each, train-seen problems excluded by exact RNG replay). Asked 38+86=, the model answers 3+8=11,0+6+1=7,… — carries computed correctly, digits read from 4-digit-trained positions. Learned absolute position embeddings → fixed-slot choreography, not arithmetic. Zero truncation measured, so these are genuine errors.

Format choice was measured, not assumed: a 3-format bake-off at 1M scale picked chain-of-thought (16.6%) over left-to-right (15.0%) over right-to-left (8.7%).

Part 2 — Real LLMs: the tokenizer is the whole game

Zero-shot, every real LLM scores 0.0% (n=2000, one worked example in the prompt): TinyStories-1M (3.7M), TinyStories-3M (8.3M), pythia-14m (14.1M), TinyStories-33M (68.5M), pythia-70m (70.4M), SmolLM2-135M (134.5M). Failure modes: the TinyStories models tell stories ("One day, a 3 year old boy was walking in the park…"), pythia loops digit fragments, SmolLM2 collapses to 100+100+….

Fine-tuned on the same 1.78M-problem data (native BPE tokenizers):

LLM params accuracy
TinyStories-1M 3.7M 0.2%
TinyStories-3M 8.3M 0.0%
pythia-14m 14.1M 99.8% (only its 1.19M transformer blocks trained; vocab frozen)
TinyStories-33M 68.5M 98.5%
SmolLM2-135M 134.5M 76.9%
pythia-70m 70.4M NaN (training instability, not evaluated)

The closing experiment — a one-rule tokenizer ablation. TinyStories-1M, same checkpoint, same data, same recipe; the only change: number runs tokenize per digit instead of whole chunks (same 50,257-entry vocab, same embedding matrix, no new tokens):

tokenizer accuracy
native BPE (1842 → ['18','42'], 9920 → ['99','20'] — inconsistent boundaries) 0.2%
digit-split (1842 → ['1','8','4','2']) 99.7%

A 500× swing from one pre-tokenizer rule. The BPE floor was never about capacity or pretraining — it was literally the tokenizer chopping numbers at inconsistent boundaries, making a stable digit↔position mapping impossible. pythia-14m's native-BPE success at 14.1M is best read as The Pile's math/code exposure grinding through that inconsistency at larger scale.

The NaN forensics (pythia-14m saga): five training recipes died with NaN before a local autopsy found the real cause — transformers 5.x loads checkpoints in their native float16, and AdamW's eps=1e-8 is unrepresentable in fp16 (min normal 6×10⁻⁵), so one optimizer step explodes (g/(0+eps) → inf). Proof: finite loss, finite gradients, one step, 46/76 tensors NaN; SGD on the same batch clean (no division). Fix: explicit fp32 + frozen vocab matrices → stable, 99.8%.

Part 3 — Talk AND arithmetic: the mix-ratio dial

Full fine-tuning on pure math erases language (TinyStories-1M at 99.7% math answers "Once upon a time" with digit chains). Seven configurations tested on the same 3.75M model:

recipe math (strict) language (story PPL vs base 12.35) verdict
100% math 99.7% ❌ dead can add, can't talk
1:1 math:story mix 1.4% ✅ ×0.90 talks, can't add
3:1 mix 61.5% ✅ ×0.92 dial moving
7:1 mix (A3) 89.8% ✅ ×0.98 delivered model
7:1 → math-only tail (anneal) 99.3% ❌ dead (×586k) tail undoes anchoring
LoRA r=8 on frozen base 0.0% ❌ dead (×756k) too weak for math, strong enough to break talk
LoRA r=32, gentler LR 0.05% ❌ dead (×397k) closed: 2/2 LoRA configs fail both

Findings: (1) the math↔language tradeoff is a smooth, tunable dial — the ratio of math to story chunks in training; language survives at every mix below 100% math, with perplexity even better than base. (2) "Frozen base preserves language" (the standard LoRA intuition) is wrong at this scale — the adapter perturbs outputs enough to destroy language while never learning the task, at two very different ranks and LRs. (3) Annealing to a math-only tail is as destructive as pure-math training — the last gradients dominate the final weights.

Delivered model: A3 (7:1 rehearsal mix). 89.8% strict on held-out 4-digit addition, fluent English ("there was a little girl named Lily. She loved to play outside…"), story perplexity slightly better than the untouched base. The pre-registered 95% bar was not cleared with language intact; the full curve above documents why and what the next lever would be (a staged 15:1 experiment is written and ready, deliberately not run).

Honest disclosures

  • Metric: strict exact-match on the FIRST line of the chain-of-thought completion. Both flagship models share a charming failure: they answer correctly, then hallucinate additional problems (…15989\n5885+5690=…); a "last number in completion" metric scores these 0. Disclosed with examples in the raw results.
  • Contamination prevented by construction: eval sets built before training streams; streams reject every eval pair; extrapolation control excludes train-seen pairs by exact RNG replay; tuning/final eval sets on independent seeds with overlap asserts.
  • 12 bugs found and fixed across the project — full round-by-round record in STATUS.md. Highlights: an eval-set leak caught by assert before any GPU burn; two kernels lost to Kaggle's notebook-execution import rules (→ single-file kernel builder); my own push timeout killing a run at 59 min; the fp16/AdamW bomb; an eval prompt that didn't match training format (round-1 Stage B null results, all re-run); a published param count corrected from 1.07M to the true 805,632.
  • One seed per cell, no cross-seed error bars; eval n=5000 (Part 1 headline) / n=2000 (everything else).
  • Nothing was dropped for being inconvenient — every null result, crash, and wrong prediction (the LoRA hypothesis, the anneal hypothesis) is in this report and in results/.

Artifacts

artifact link
Code, results, session log github.com/Its-Atharva-Gupta/tiny-math
The 805K model + verdict chart + all raw results huggingface.co/Atharva1232/tiny-math-1m
Verdict document VERDICT.md
Model card (Part 1) MODEL_CARD.md
Full session log with every bug/decision STATUS.md
Reproduce: from-scratch arm train.py, baselines.py
Reproduce: all GPU runs kernels/ (single-file, make_kernel.py builds them)
Reproduce: final tables + chart verdict.py

Cost: 15.62 / 30 weekly GPU-hours on one free Kaggle T4, plus two local CPU training runs.