One model or twenty-six?
Key takeaways
- At parameter parity the question isn't close: a 1.8M generalist conditioned on letter identity loses to the letter's own 1.8M specialist on every one of 26 letters (mean BPC 1.558 vs 1.521).
- Give the generalist the sum of the case's budget (47.8M ≈ 26 × 1.8M) and the loss flips: omni-xl takes 16 of 26 letters and the mean (1.490) — but its biggest wins sit exactly where the case's uniform training recipe over-trained the short, simple letters.
- The loss and the drawing disagree: sampled at temperature 1.0, the case produces a grammar-valid glyph 92.1% of the time, omni-xl 71.0% — and its survivors look worse than the gap sounds. Best model by loss, least reliable draughtsman.
- The 47.8M model also couldn't train on the shared recipe — three runs diverged at lr 3e-4 / beta2 0.99 before the standard big-model adjustment (1e-4 / 0.95) held. The one-big-model shape carried its own operational tax.
- The release inverts the scoreboard on purpose: the generalist ships as glyph-nanogpt-1 — one evolving instrument over a menu of twenty-six — and the case stays unreleased, its numbers frozen as the yardstick v2 has to overtake.
Give a fixed parameter budget two shapes: one 47.8M-parameter model that draws every lowercase letter, or twenty-six 1.8M specialists that each draw exactly one. The big model wins the loss table by 2% — and fails to finish a letter 29% of the time, against 8% for the specialists.
The corpus is type history flattened to text: 759 OFL-licensed sans-serif
families from Google Fonts, every upright weight, each glyph outline
serialized as one line where one printable character is one token — drawing
verbs M L Q Z, coordinates from a 96-character alphabet on a 16-unit grid,
the em normalized to 1024 (ADR-0027).
81,934 glyphs, deduped to a fixed family split so every model — specialist or
generalist — is scored on font families none of them ever saw. A specialist
compresses the distribution of one letter across hundreds of disagreeing
hands. Sampling it pulls out a new individual every time.
This is what the case draws. One sample per letter, its own specialist each,
no cherry-picking — the near-misses (that g, that k) ride along:
At parameter parity, splitting wins everywhere
The first arm answers the cleanest version of the question: same 1.8M parameters, same recipe, same tokens — split into 26 instruments or shared by one conditioned model? The specialists win the mean, and omni-s does not take a single letter outright. Twenty-six times more data through the same capacity bought nothing; the pilot's 3-letter generalist (val loss 1.014) and the full 26-letter one (1.012) landed within noise of each other, so capacity, not transfer, was the binding constraint all along.
The chart puts the three arms side by side. Look at the middle bar: omni-s loses to the case it was supposed to consolidate.
Give the generalist the whole budget and the loss flips
omni-xl — 12 layers, 576 embed, 47.8M parameters, the case's budget in one body — takes the mean by 0.031 BPC and 16 of 26 letters. That is the honest headline for the one-big-model position, and it deserves its chart.
But read where its wins live. Above the zero line, omni-xl's four biggest
margins are l (+0.268), v (+0.168), n (+0.131), and h (+0.117) —
short-sequence letters whose specialists overfit under the case's uniform
recipe (3,000 identical steps means a simple letter's tiny token corpus gets
epoched several times harder; l's specialist hit train loss 0.14 against
val 1.82 before best-val checkpointing rescued its weights). Below the line,
the case's wins are the deep, distinctive letters: o by 0.094, s, g,
a, e. The generalist wins where the case hurt itself. The case wins
where the letters have the most to say.
The best loss can't finish its letters
Loss is a promise; sampling is the product. Sixty-four samples per letter
per arm at temperature 1.0, run through the codec's strict decoder: the case
parses 92.1%, omni-s 94.2%, omni-xl 71.0%. The gap is not close, and it is
not bleed — the letter token leads every sequence, so every failure is pure
grammar and termination: contours that never close, coordinate pairs cut
short, 64 omni-xl samples that ran past the length budget without ever
emitting a glyph boundary. Its worst letters are j (51.6% valid) and g
(59.4%).
And the parse rate flatters it. Here is the same alphabet drawn by omni-xl — its survivors are wilder, blobbier, further from letterform than the case's:
Thirteen consecutive g attempts from each arm make the texture difference
plain — the specialist's failures are degraded g's; the generalist's are
often not letters at all:
One reading: at this corpus size the big model has capacity to spare for
distributional detail, which buys likelihood, while the small models are
forced into grammatical discipline, which buys legibility. Another: the
temperature was simply kind to sharp small models. The second reading is
partly right — a release-time sweep lifted omni-xl to 84.7% valid at temp
0.8 (the benchmark table stays at 1.0 for both arms, and the case was
never swept, so 92.1% is not its ceiling either). The sweep also found
j inverting the curve: best at temp 1.0, collapsing into repetition
loops below it. One global knob does not fit twenty-six letters. The case
never needed a knob.
Be honest: what still doesn't work
- The case over-trained its simplest letters, and that is the case's own
flaw. Per-letter token corpora vary several-fold with glyph complexity;
a uniform 3,000 steps over-epoched the short letters into overfit (
l: train 0.14, val 1.82). Best-val checkpointing contained the damage but didn't erase it — omni-xl's 16 BPC wins are concentrated exactly there. Twenty-six instruments need twenty-six tuning decisions; pretending otherwise cost the case the loss table. - The capacity comparison bent one control. The shared optimizer recipe (lr 3e-4, beta2 0.99) diverged three times at 47.8M — healthy to ~step 600, then the loss exploded, grad-clip active throughout — before the standard adjustment (1e-4, 0.95, longer warmup) held. So the arms did not train identically. One reading: an even better-tuned xl leaves more BPC on the table. Another: needing a bespoke recipe at all is a real cost of the one-big-model shape, and it nearly cost the run.
- One seed per model. The coin-flip letters —
m(+0.004),b(−0.005),w(−0.008) — are within plausible seed noise, and no variance estimate exists this round. The 10-vs-16 letter split should be read as roughly-even, not exact. - Valid ≠ legible. The parse rate is a grammar check, not a shape
metric; omni-xl's grammar-valid
gs are visibly less g-like than the specialist's, and nothing in this round quantifies that visual gap beyond the specimens above. - The grid is lossy at the extremes. Thin weights (~20-unit stems on a 16-unit grid) survive with visible stem wobble — an accepted cost of keeping the ensemble rather than any single designer's optical corrections, documented in the codec ADR.
The release: the one, chasing the twenty-six
The bake-off has a winner, and the release is not it. On the craft that a released glyph model exists for — finishing letters — the case won, clearly: 92% valid draws to 71%, with the visual gap wider than those numbers. This report does not soften that, and neither does the model card, which carries the yardstick table uncushioned.
The studio ships the generalist anyway. glyph-nanogpt-1 is omni-xl, and
the choice is aesthetic and deliberate: one evolving instrument over a
menu of twenty-six, a version series with a plot. The case's numbers are
now frozen — 92.1% valid, 1.521 mean BPC, letter by letter in the
leaderboard — as the standing
yardstick, and every future version of glyph-nanogpt trains against it.
The gap to close is stated in
MODELS.md so no future round can dodge
it: reach the case's 92.1% valid-glyph rate without giving back the BPC
lead. The first lever was pulled at release — a three-point temperature
sweep put the shipped default at 0.8, where the model draws 84.7% valid
instead of 71.0% — and the rest are named: a stable recipe from step zero
instead of a mid-run rescue, per-letter sampling settings (j alone
prefers temp 1.0), seeds, and data.
What the finding means: parameter count consolidates likelihood, not craft. Sliced against data this small, one big model learns the distribution's texture and loses the discipline of the line; twenty-six small hands each learn one letter well enough to finish it. The release makes that trade explicit instead of hiding it behind a winner's table — version 1 ships knowing exactly what it owes. Worth the rounds it will take.
Reproduce it
uv run python projects/glyph/data/fetch_fonts.py # freeze the pool (manifest pins the commit)
uv run python projects/glyph/data/encode_corpus.py # 82k glyphs -> one-char-per-token lines
uv run python projects/glyph/data/roundtrip_sheet.py # the legibility gate — look at it
uv run python projects/glyph/data/prepare.py # per-letter + omni datasets, splits, baselines
for ds in a b c d e f g h i j k l m n o p q r s t u v w x y z; do
uv run python core/nanogpt_core/train.py projects/glyph/config/specialist.py \
--dataset=$ds --out_dir=projects/glyph/runs/$ds-r1
done
uv run python core/nanogpt_core/train.py projects/glyph/config/omni_s.py
uv run python core/nanogpt_core/train.py projects/glyph/config/omni_xl.py
uv run python projects/glyph/matrix_eval.py # 78 BPC evals + harness -> matrix-results.json
Credits
Corpus design, codec, training, evaluation, and this write-up: Claude Fable 5
(Claude Code). Direction, scope calls, and the singular-release policy:
Romello Goodman. Fonts: 759 OFL families via
google/fonts, credited individually in
projects/glyph/data/manifest.json. Prior art conversation: Eli Heuer's
Virtua Grotesk, which trains one
model on one perfectly disciplined hand — this experiment inverts it.