world-models

chess & go · distilled from Stockfish/KataGo · one GPU per run

2,301
Elo
R2 v2 (d15 46M, 20×256 cosine) ep 14 · sims=4,000 · vs UCI=1,800 · CI [2,190, 2,601] · full peak table

AlphaZero used 5,000 TPUs for self-play data generation and 64 TPU-v2s to train. We use the same network family (20- or 40-block × 256-channel ResNet, ~24–50M parameters) on one GPU, supervised distillation from Stockfish on 30–46M positions, evaluated against a calibrated Stockfish opponent. Total cost per training run: ~16–80 GPU-hours on a single L40S. Roughly four orders of magnitude less compute than the AlphaZero paper.

2,301 is the project's top point estimate and the tightest-CI measurement that crosses 2,300: the 95% lower bound (2,190) clears the previous peak (d10 30M ep 15 = 2,189). A wider-CI sibling, R2 v2 ep 4 at the same anchor, was 2,285 [2,177, 2,554]; both clear the d10 ceiling. R1 v2 ep 7 = 2,209 [2,115, 2,389] is the 40×256 cosine variant's best. Full numbers on the experiments page. The self-play follow-on hasn't survived a spot AZ on chess but is currently running successfully on the Go side — postmortem & live status.

How It Works

Two phases. Training is a single supervised pass: Stockfish labels positions, the student fits those labels. Inference is Monte Carlo Tree Search using the trained network as its prior. The network alone never picks a move — MCTS does, and the network just guides where MCTS looks.

1 · Training (one-shot, no self-play)

  1. Stockfish self-plays games at search-depth 15. At every position, before the chosen move is played, Stockfish reports its top-8 candidate moves with centipawn scores.
  2. Those scores become a soft distribution π over the 8 candidates (softmax at T = 1 pawn). The game outcome z ∈ {−1, 0, +1} is recorded from each side-to-move POV.
  3. The student trains on (state, π, z) triples — cross-entropy on the policy head against π, MSE on the value head against z. No self-play, no MCTS at training time.

2 · The Network

20 residual blocks × 256 channels, ~24M parameters. Input: a 19-plane encoding of the current board (piece positions, side-to-move, castling rights, repetitions). Output: a 4,672-dim policy logits vector and a scalar value in [−1, +1]. Same architecture AlphaZero used — only the way we got the weights differs.

input19 × 8 × 8conv 3×3256 chres block× 20policy head4,672 logitsvalue headtanh → [-1,+1]~ 23.7M paramssame as AZpolicy + valuejointly trainedcross-entropy+ MSE loss

3 · Playing a Move (Network + MCTS, end-to-end)

The trained network is a prior over moves and a value estimator for leaf positions; the actual move comes from Monte Carlo Tree Search built on top of those two signals. One move at the root costs N simulations (800 for the routine eval, 4,000 for the search-saturated eval). Each simulation is:

  1. Selection. Starting at the root (the current position), walk down the search tree by repeatedly picking the child that maximises Q + U: average value seen below this move + an exploration bonus proportional to the network's prior P and inversely proportional to how many times we've already visited this child. The network's policy is the bias; the running statistics correct it as evidence accumulates.
  2. Expansion + evaluation. When you hit a node that hasn't been expanded yet, encode that position to 19 planes, run one network forward pass, and store the policy logits as priors for its children + the value estimate as the value of the leaf.
  3. Backup. Propagate that leaf value back up the path you took, incrementing each parent's visit count and updating the running Q with the new estimate. Sign-flip at every ply because chess alternates side-to-move.

Repeat for N sims. Then pick the move at the root with the highest visit count (not highest Q; visit counts are MCTS's improved policy estimate). At higher sims/move, MCTS spends more rollouts refining the prior into a sharper visit distribution — that's why the same checkpoint scores 1,807 Elo at 800 sims and 2,084 Elo at 4,000 sims (see the search ablation).

s₀current position12N=1228N=28112N=112 ← chosen18N=1861root: current board statechildren = candidate movessize ∝ visit count Nteal = most-visited path800 MCTS rollouts per move during training · same network used as prior + value

Implementation: the encode is encode_board, the network forward is AlphaZeroNet.forward, and the MCTS loop is run_mcts / run_mcts_batched. The selection formula is straight from the AlphaGo Zero paper: U(s,a) = c_puct · P(s,a) · √Σ_b N(s,b) / (1 + N(s,a)).

→ Full method, including the loss formula and Elo bisection

Experiments

Single-variable ablations on top of the distilled baseline. Each card shows the hypothesis, the verdict, and the headline numbers. Full setup + interpretation on the experiments page.

Network Capacity Rejected

Doubling Network Depth 20 → 40 Blocks Didn't Help

5M positions for a 24M-param network is already in the over-parameterized regime. More capacity finds no structure that isn't there.

20-block1,810
40-block (2× depth)1,810
delta±0
Eval-Side Search Confirmed

+277 Elo From Deeper MCTS at Inference — No Retraining

Distilled priors transfer well to deeper inference-time search, exactly Lc0's empirical finding. The "1,800 ceiling" was an eval-time artifact.

800 sims1,807
4,000 sims2,084
delta+277
Data Scale Confirmed

+199 Elo From 6× More Positions — The Bitter Lesson?

More data beat more parameters and a stronger teacher. The two confirmed levers (search, data) are both "more compute"; the rejected lever was "smarter network".

5M positions1,810
30M positions2,009
delta+199

vs AZ

AlphaZero (2017)This Project
approachtabula-rasa self-play RLsupervised distillation from stockfish
network20 blocks × 256 chidentical
training compute~45,000 TPU-v1-hr + 576 TPU-v2-hr~16 GPU-hours (single L40S)
training data~3.5 B positions5M–30M positions
peak elo~3,5002,301 (R2 v2 ep 14, lower-CI bound 2,190 clears 2,300) · 2,285 (ep 4) · 2,209 (R1 v2 ep 7)

→ Full comparison and what's literally identical

Results Matrix

Every checkpoint we trained, at the standard 800-sim eval. UCI=1,350 anchor — the only one every checkpoint has been measured against, so the comparison is apples-to-apples across runs. Bold marks the canonical run's best epoch.

Runep 5ep 10ep 15ep 20
d15 5M baseline (20×256)1,6511,8621,7591,807
d15 5M capacity (40×256)1,8511,8511,7591,820
d10 5M subset (20×256)1,7491,7701,6881,730
d10 30M full (20×256)2,0841,9611,8881,961
d15 46M R1 v1 (40×256, const LR)1,9332,033
d15 46M R2 v1 (20×256, const LR, lowlr)1,9331,9331,933 (ep 29 final)
d15 46M R1 v2 (40×256, cosine, killed at ep 15)2,0332,033
d15 46M R2 v2 (20×256, cosine, killed at ep 22)1,9331,9332,276 (ep 14)

100-game evals at UCI=1,350. CI is ~±100 Elo per cell — the variation epoch-to-epoch within a run (e.g. baseline 10 = 1,862) is mostly noise, not real swing. The cross-run comparisons (5M baseline → 5M capacity → 30M data) are larger than the noise.

Deep-Search Results

Same checkpoints, more MCTS sims at inference. Numbers in [ ] are 95% CIs.

CheckpointsimsopponentElo
d15 baseline ep 20800UCI=1,3501,807
d15 baseline ep 204,000UCI=1,3502,084
d10 30M ep 5800UCI=1,8002,004
d10 30M ep 52,000UCI=1,8002,034
d10 30M ep 54,000UCI=1,8002,084 [2005, 2197]
d10 30M ep 54,000UCI=2,0002,110 [2044, 2187]
d10 30M ep 104,000UCI=1,8002,171 [2082, 2324]
d10 30M ep 154,000UCI=1,8002,189 [2098, 2354]
d10 30M ep 204,000UCI=1,8002,154 [2067, 2297]
d15 46M R1 const-LR ep 74,000UCI=1,8002,146 [2060, 2285]
d15 46M R1 v2 cosine ep 24,000UCI=1,8002,138 [2054, 2274]
d15 46M R1 v2 cosine ep 74,000UCI=1,8002,209 [2115, 2389]
d15 46M R2 v2 cosine ep 44,000UCI=1,8002,285 [2177, 2554]
d15 46M R2 v2 cosine ep 144,000UCI=1,8002,301 [2190, 2601] ← lower CI clears 2,300

Highest point estimate: 2,301 Elo at R2 v2 cosine ep 14 — and uniquely, its 95% lower CI bound (2,190) clears the previous peak (d10 ep 15 = 2,189). The wider-CI ep 4 measurement (2,285) is consistent. R1 v2 cosine ep 7 at 2,209 is the 40×256 variant's best. The 4,000-sim deep-search numbers stack the two confirmed ablations (search + data) on one checkpoint; the cosine LR schedule on top recovered another ~+100 Elo over the constant-LR R1 v1 / R2 v1 runs.

What's Next

The largest remaining gap to AlphaZero is that AZ has no teacher — its targets come from its own MCTS visit counts, not from Stockfish. The Lc0 recipe — distill first, RL second — runs an AlphaZero-style self-play loop on top of the distilled prior.

Self-play has been attempted six times on spot since 2026-05-25; every run was evicted before iteration 1 finished (iters take ~90 min, AZ eviction windows are shorter). The algorithm is fixed (LR=1e-5, no catastrophic forgetting); the binding constraint is moving the trainer off spot. → Self-play postmortem

The d15-46M cosine trainers (R1 v2 and R2 v2) were killed at 2026-05-26 17:15 UTC after producing the top-2 ckpts in the deep-search table above. Late epochs at sims=800 were not trending up, and the autoeval daemon is offline — finishing them on OD would have been ~$320 with low chance of a new peak. → Why we stopped

Two cheap follow-up evals are in flight to confirm the ≥ 2,300 question: sims=8,000 vs UCI=2,000 on R2 v2 ep 4 and sims=4,000 vs UCI=1,800 on R2 v2 ep 14.

→ Full status & roadmap