ChessMamba
Chess engine built on a from-scratch selective state-space model (Mamba/S6). Most engines are either classical alpha-beta with a hand-tuned evaluation (Stockfish, Sunfish), NNUE nets bolted onto alpha-beta, or AlphaZero-style CNN + MCTS. This is none of those. It reads a game as a sequence of moves, like a language model reads a sentence, and scans that sequence with a recurrent state-space layer instead of attention or a board-tensor convolution.
Each layer keeps a compact hidden state and updates it one move at a time. How much to forget and what to write into that state is decided per move rather than fixed, which is the "selective" part of S6. In practice this means the engine doesn't re-read the whole game history to pick its next move during play, the state just advances one step and the old tokens are gone.
Architecture
- dim=384, depth=10, state_dim=16, ~16.8M parameters
- three heads off the final hidden state: policy (4096-way, from-square times to-square), promotion (5-way), value (scalar, tanh)
- search on top of the model: policy-guided negamax, alpha-beta, quiescence through captures/promotions, top-K move ordering straight from the policy head. No piece-square tables, no hand-written evaluation term anywhere.
- movetime comes from the GUI directly, or gets computed from wtime/btime/winc/binc if a real clock is in use. Capped at 19s regardless.
model.py has the S6 block, search.py has the search.
Training
Supervised pretraining on real Lichess games, 1800+ Elo, run across several phases. Then self-play: the model plays itself, those games get weighted by its own search and folded back in, at a low learning rate with some of the original data mixed back in so it doesn't forget what pretraining taught it.
No Stockfish anywhere in this. No distilled labels, no wrapped eval, nothing borrowed. The search runs entirely on the model's own policy and value output. Sunfish shows up in the training scripts too, but only as a fixed opponent to benchmark checkpoints against, it never touches training data.
Results
Every checkpoint had to beat the one before it in a head-to-head match before it was allowed to replace it. That's how the regressions below got caught instead of shipped:
- supervised pass beat the prior best 4-0, with 4 draws
- that checkpoint beat the next one 3-1, with 4 draws
- one more refinement pass came back a dead 4-4 tie
Self-play fine-tuning regressed the model outright twice along the way, caught by the same head-to-head testing and reverted both times. That's basically why the final recipe leans on supervised training more than self-play. ckpt/model.pt is whatever survived all of that.
It still loses to Sunfish. Sunfish plays a genuinely strong ~2000 Elo off decades-old, well-tuned classical heuristics, and this is a model trained in a handful of days. Sunfish was always the yardstick here, not something this was expected to beat outright.
replay.mp4 up top is a real game from the 4-0 match, ends in an actual checkmate.
Running it
pip install -r requirements.txt
python3 engine_uci.py
Speaks UCI. Loads ckpt/model.pt by default, or point it elsewhere with CHESSMAMBA_CKPT.
Layout
engine_uci.py UCI loop, entry point
model.py ChessMamba
search.py negamax + quiescence over the model
chess_io.py move/board encoding
ckpt/model.pt trained weights
training/ pretraining, self-play, fine-tuning, match tooling
training/ is the actual code that produced ckpt/model.pt. Included so it's checkable, not because you need it to run the engine.
- Downloads last month
- 9