I shrank my chess engine 70x by trying to compress it

oli2662 pts0 comments

I shrank my chess engine 70x by trying to compress it · Latent Heat

Log 006

03 Aug 2026<br>by flirpLaptop24-core CPU

Second in the series. Part one built a strong player out of a weak model and a good search, and warned you that what came later would supersede it. This is where that happens. The model gets an architecture worth searching with, then turns out to be 70 times larger than it needs to be, then goes on Lichess and plays like a maniac.

The architecture that should have been there on day one

Everything up to this point ran on a 39M-parameter ternary looped transformer, and three of those words were dead weight. The loops were flat from the first iteration. The ternary quantisation was a costume with no kernel to cash it in. And the 39M model had a zero train/val gap, which is the tell of a model too small for its data rather than one at risk of overfitting.

So the plan was one retrain with five changes, each forced by a number. Ternary off, because there was no kernel on this path to collect the capacity tax. 39M to 122M parameters and 9 to 14 blocks, because a zero gap means capacity-limited, so buy some. Recurrence removed, because it was flat and dropping it ran 3x faster per step. And the standard modern attention stack on top: QK-norm, SwiGLU, geometric attention bias, plus a recall@4 hinge that pushes Stockfish's best move above the K-th logit, because cross-entropy never optimises the metric that actually gates search.

metric<br>v1<br>v2

val agreement<br>49.4%<br>53.3%

value-mse<br>0.0330<br>0.0213

vs SF-2250<br>56% (~2294 Elo)<br>88% (~2588 Elo)

Roughly +294 Elo from a single retrain. It is the largest jump in the project, and the architecture change alone beat the entire data flywheel from part one. The exotic scaffolding came off and the model got dramatically stronger. That is the fifth time in this project that removing something clever has beaten adding something clever.

One number set up the next problem: v2 finished with a 6.7-point train/val gap. It had flipped from capacity-limited to data-limited, which turned "get more data" into the priority.

The data problem ends in thirteen minutes

I had generated about 2.7M labelled positions with my own Stockfish over several days. Then someone pointed me at the Lichess community evaluation database: 371M positions, each analysed at depth 46 to 95, roughly four billion nodes apiece. That is on the order of 140,000 times the search behind my own labels, and every record carries five principal variations, which is my exact soft-target format plus the opponent replies a lookahead head would want. Converting the whole thing to my format took thirteen minutes.

There is one trap in that data, and it is the kind that trains perfectly and plays terribly. Their centipawn score is White-relative. Mine is side-to-move. On black-to-move positions the sign is inverted, which I verified eight times out of eight against a local search. Negate when Black is to move, or half of every value target you train on quietly points the wrong way, and the model converges to a clean, confident, backwards evaluation.

The value head is the weakness, and the metric lied again

By now the question was not "is the model wrong" but "where, and which head". So I split the held-out positions by how much a deep evaluation disagrees with a shallow one, and scored the model on each half. The volatile half is where it falls apart. Value error more than doubles, from 0.113 to 0.270, while recall stays almost flat, and in the positions where shallow and deep search disagree on the best move, recall@4 collapses from 90% to 63%. The policy is distilled from shallow search, so wherever depth overturns the shallow read it is confidently on the wrong side. The weakness is tactical, it lives where depth matters most, and it is mostly the value head.

That pointed at a knob nobody had touched: the policy-versus-value loss balance, set to 0.25 once and forgotten. So I ran three full retrains, identical except for that one weight, played at depth 5 against Stockfish limited to 2750:

value weight<br>result vs SF-2750<br>approx Elo

0.25 (v4)<br>54%<br>~2779

0.50 (v7)<br>71%<br>~2904

1.00 (v6)<br>44%<br>~2706

v7 at 0.5 became the deployed model, and it had the worst validation scores of the three: the lowest agreement, 50.6% against 53.4 and 52.3, and the worst value-mse. It played the best chess by a wide margin. The metric did not merely fail to notice the improvement, it ranked the eventual winner dead last. Agreement measures a policy prior, and what you deploy is a searcher. A strong validation number is now a hypothesis I check, not a result I act on.

v7 kept one visible flaw. Three of its draws were 300-move grinds where it reached a winning endgame and could not convert. That flaw later justified deleting most of the endgame from the training set.

The compression detour

A transformer's residual stream is a shared bus. Every layer reads and writes the same d-dimensional vector, and mine was 768 wide. The...

model from value search data move

Related Articles