skorotkiewicz/chesslm

Tiny chess model

Python

0

17 commits

updated Sep 20, 2026

See the code

See what people are saying (1)

README

chesslm — Small model. Your move.

chesslm

A tiny neural chess evaluator with alpha-beta search and a desktop chess game.

Python 3.10 or newer 100,353 parameters 392 KiB of model weights NumPy runtime

Quick start · Desktop game · Training · Benchmarking · Development

chesslm combines a small NumPy network with a fixed material evaluator to choose chess moves. Play against the included model.npz in a Tkinter window, or request a move from the command line. Playing uses your CPU and requires no Stockfish process. Stockfish supplies training labels and benchmark comparisons.

The network has 100,353 parameters and 401,412 bytes of float32 weights, about 392 KiB. Despite the name, it is not a language model. The included checkpoint's training history and playing strength have not been verified; no Elo is claimed.

Quick start

Use Python 3.10 or newer. Run these commands in a POSIX shell:

git clone https://github.com/skorotkiewicz/chesslm.git
cd chesslm
python -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python chess_game.py

The desktop game needs Tkinter and a display. Some Linux distributions provide Tkinter separately in a package named python3-tk or tk. On Windows, activate the environment with .venv\Scripts\Activate.ps1 in PowerShell.

For a terminal-only first move, run this from the project directory:

OPENBLAS_NUM_THREADS=1 python chesslm.py play --model model.npz

The OPENBLAS_NUM_THREADS=1 prefix limits OpenBLAS threading for the small network. In PowerShell, set $env:OPENBLAS_NUM_THREADS = "1" before running the Python command. The desktop game sets this default automatically.

Desktop game
python chess_game.py
python chess_game.py --color black
python chess_game.py --model model.npz --depth 3 --max-nodes 20000

Click a piece, then its destination. Dots and rings mark legal moves, and buttons let you choose a promotion piece. Use arrow keys and Enter or Space to move with the keyboard; Tab reaches the buttons. Playing as Black flips the board.

Search runs in a background thread, so the window can redraw and close while the model thinks. New game becomes available after the search finishes. The default checkpoint is model.npz beside the script.

If you use uv, you can also launch the game without setting up .venv:

uv run --python /usr/bin/python chess_game.py
uv run --python /usr/bin/python chess_game.py --color black

These examples use a Linux system Python with Tkinter installed. Choose a Python with Tkinter on your platform. uv installs the script's declared NumPy and python-chess dependencies in an isolated environment.

Web game
python web/chess_web.py
# Or use an isolated uv environment:
uv run --python /usr/bin/python web/chess_web.py

Open http://127.0.0.1:8000 in your browser. Choose White or Black, then click a piece and its destination. Legal moves are highlighted; promotions offer all four pieces. Arrow keys and Enter work on the board. Each tab has its own game; refreshing the page starts over.

The Python server loads model.npz beside the script and performs inference on CPU. The browser does not download the model. No Stockfish, training, external assets, or JavaScript packages are needed. Tkinter is not required.

Use --model /path/to/model.npz, --port 8080, --depth 3, or --max-nodes 20000 to change the defaults. The server preserves move history for repetition draws and validates every move. Games are limited to 1000 submitted plies. One search runs at a time; another tab can retry if the model is busy.

This is a localhost-only server for personal play, not a public hosting setup. Stop it with Ctrl+C. Keep chess_web.html beside chess_web.py.

GitHub Pages

The included Pages workflow builds and deploys the browser game when you push to main. In the repository, open Settings > Pages and select GitHub Actions as the source. Push these files, or run Deploy chess game to GitHub Pages from the Actions tab. For this repository, the expected address is https://skorotkiewicz.github.io/chesslm/.

Pages cannot run a Python server. This build instead loads Pyodide and NumPy in a Web Worker, then runs the same evaluator and search with the bundled model.npz. The checkpoint is copied unchanged; CI does not train or generate data. Relative asset paths support project Pages URLs such as /chesslm/.

The first visit downloads the Python runtime and NumPy from jsDelivr and can take a minute on a slow connection. Browser inference uses depth 2 and a 2,000-node limit per move, below the local server's default budget. The UI stays responsive while the worker searches. Refreshing discards the game.

To preview the Pages build locally, use an environment with requirements.txt:

python web/build_pages.py
python -m http.server 8001 --bind 127.0.0.1 --directory _site

Open http://127.0.0.1:8001. Opening index.html as a file:// URL will not work. The build bundles python-chess with its GPL license in chess.zip.

Command-line play

OPENBLAS_NUM_THREADS=1 python chesslm.py play \
  --model model.npz --depth 3 --max-nodes 20000

The command prints JSON with move, a UCI move string such as e2e4, and nodes, the visited node count. When the game has ended, move is null. Add --fen '...' with a valid FEN to choose a move in another position.

This command chooses one move per invocation; it does not implement a UCI engine server. CLI model paths are relative to your working directory.

How it works

ComponentImplementation
Input782 features: 12 piece planes, side to move, castling rights, legal en passant file, and halfmove clock
NetworkOne 128-unit tanh hidden layer, learning a correction to a fixed material evaluator
TargetWhite's evaluation, transformed with tanh(centipawns / 600)
TrainingMean squared error with Adam; save the weights with the lowest validation loss
SearchIterative deepening, alpha-beta pruning, capture ordering, and four quiescence plies
RuntimeNumPy and python-chess on CPU

The weight size excludes the NPZ header, Python, NumPy, and process memory.

A node limit can leave only a shallow completed search iteration. If none finishes, search returns a legal fallback move. Long tactics remain a limitation. The game and search recognize automatic draws but do not implement optional draw claims. A FEN does not include prior repetition history.

Training

Training is optional. The included checkpoint is enough to play.

Data generation and benchmarking require a Stockfish executable. Stockfish is not tracked in this repository. The default path is stockfish/stockfish-linux-x86-64-universal, relative to chesslm.py, for x86-64 Linux. Supply --engine /path/to/stockfish to use another location or a binary for your platform.

Run these commands on your training machine. Output files must not already exist; model-trained.npz leaves the included checkpoint available.

python chesslm.py generate \
  --engine /path/to/stockfish \
  --positions 100000 --nodes 20000 --threads 2 \
  --output positions-train.jsonl

OPENBLAS_NUM_THREADS=2 python chesslm.py train \
  --data positions-train.jsonl --epochs 30 --output model-trained.npz

Stockfish labels self-play positions with White's centipawn evaluation using a three-line search. Opening choices vary among those lines, with occasional variation later. Mate labels use ±10,000 centipawns. The node budget bounds Stockfish's search work, not elapsed time.

Validation holds out entire games and removes positions shared with those games from training. A dataset needs at least two games and some distinct positions. Training saves the initial weights if no epoch improves validation loss.

The dataset is loaded into memory. Input arrays need roughly 313 MB per 100,000 positions, plus JSON rows and temporary arrays. Larger datasets need more RAM.

Copy model-trained.npz to your playing machine and select it explicitly:

python chess_game.py --model model-trained.npz

Benchmarking

Generate a separate corpus with a different seed. Do not train on this file. Use fresh output filenames if you repeat the workflow.

python chesslm.py generate \
  --engine /path/to/stockfish \
  --seed 9001 --positions 5000 --nodes 20000 \
  --output positions-benchmark.jsonl

OPENBLAS_NUM_THREADS=1 python chesslm.py benchmark \
  --engine /path/to/stockfish \
  --model model-trained.npz --data positions-benchmark.jsonl \
  --positions 100 --nodes 50000

The JSON report contains the position count, best-move agreement, and mean centipawn loss against Stockfish. These are noisy, node-limited estimates. Different seeds can still produce repeated openings, and match testing is needed to establish an Elo rating. A tiny distilled model should not be expected to match Stockfish.

Development

From an environment containing requirements.txt, run:

OPENBLAS_NUM_THREADS=1 python -m unittest -v
python chesslm.py --help
python chess_game.py --help
python web/chess_web.py --help

Tests cover encoding, checkpoint size and loading, gradient math, invalid positions, validation separation, node limits, promotions, check evasion, forced mates, a free queen capture, and desktop interactions. GUI tests skip when no display is available.

The tests do not run Stockfish, generate data, or optimize model weights. Search tests use hand-set zero weights; the derivative check uses random weights without optimization. Passing them does not measure the supplied checkpoint's strength. Generation and training still need end-to-end verification on the training machine.

Project files

FilePurpose
chesslm.pyModel, search, data generation, training, and benchmark CLI
chess_game.pyTkinter desktop game
web/chess_web.pyLocal HTTP server and validated game API
web/chess_web.htmlBrowser chess board
test_chess_web.pyHTTP, rule, request-validation, and static-build checks
web/chess_position.pyShared move validation and board responses
web/chess_worker.jsBrowser-side model inference with Pyodide
web/build_pages.pyStatic site build, including the unchanged model
model.npzIncluded checkpoint for play
requirements.txtRuntime dependencies
test_chesslm.pyModel and search checks
test_chess_game.pyDesktop game checks

License

MIT.

Contributors

skorotkiewicz

17 commits

skorotkiewicz/chesslm

Tiny chess model

Python

0

17 commits

updated Sep 20, 2026

See the code

See what people are saying (1)

README

chesslm — Small model. Your move.

chesslm

A tiny neural chess evaluator with alpha-beta search and a desktop chess game.

Python 3.10 or newer 100,353 parameters 392 KiB of model weights NumPy runtime

Quick start · Desktop game · Training · Benchmarking · Development

chesslm combines a small NumPy network with a fixed material evaluator to choose chess moves. Play against the included model.npz in a Tkinter window, or request a move from the command line. Playing uses your CPU and requires no Stockfish process. Stockfish supplies training labels and benchmark comparisons.

The network has 100,353 parameters and 401,412 bytes of float32 weights, about 392 KiB. Despite the name, it is not a language model. The included checkpoint's training history and playing strength have not been verified; no Elo is claimed.

Quick start

Use Python 3.10 or newer. Run these commands in a POSIX shell:

git clone https://github.com/skorotkiewicz/chesslm.git
cd chesslm
python -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python chess_game.py

The desktop game needs Tkinter and a display. Some Linux distributions provide Tkinter separately in a package named python3-tk or tk. On Windows, activate the environment with .venv\Scripts\Activate.ps1 in PowerShell.

For a terminal-only first move, run this from the project directory:

OPENBLAS_NUM_THREADS=1 python chesslm.py play --model model.npz

The OPENBLAS_NUM_THREADS=1 prefix limits OpenBLAS threading for the small network. In PowerShell, set $env:OPENBLAS_NUM_THREADS = "1" before running the Python command. The desktop game sets this default automatically.

Desktop game
python chess_game.py
python chess_game.py --color black
python chess_game.py --model model.npz --depth 3 --max-nodes 20000

Click a piece, then its destination. Dots and rings mark legal moves, and buttons let you choose a promotion piece. Use arrow keys and Enter or Space to move with the keyboard; Tab reaches the buttons. Playing as Black flips the board.

Search runs in a background thread, so the window can redraw and close while the model thinks. New game becomes available after the search finishes. The default checkpoint is model.npz beside the script.

If you use uv, you can also launch the game without setting up .venv:

uv run --python /usr/bin/python chess_game.py
uv run --python /usr/bin/python chess_game.py --color black

These examples use a Linux system Python with Tkinter installed. Choose a Python with Tkinter on your platform. uv installs the script's declared NumPy and python-chess dependencies in an isolated environment.

Web game
python web/chess_web.py
# Or use an isolated uv environment:
uv run --python /usr/bin/python web/chess_web.py

Open http://127.0.0.1:8000 in your browser. Choose White or Black, then click a piece and its destination. Legal moves are highlighted; promotions offer all four pieces. Arrow keys and Enter work on the board. Each tab has its own game; refreshing the page starts over.

The Python server loads model.npz beside the script and performs inference on CPU. The browser does not download the model. No Stockfish, training, external assets, or JavaScript packages are needed. Tkinter is not required.

Use --model /path/to/model.npz, --port 8080, --depth 3, or --max-nodes 20000 to change the defaults. The server preserves move history for repetition draws and validates every move. Games are limited to 1000 submitted plies. One search runs at a time; another tab can retry if the model is busy.

This is a localhost-only server for personal play, not a public hosting setup. Stop it with Ctrl+C. Keep chess_web.html beside chess_web.py.

GitHub Pages

The included Pages workflow builds and deploys the browser game when you push to main. In the repository, open Settings > Pages and select GitHub Actions as the source. Push these files, or run Deploy chess game to GitHub Pages from the Actions tab. For this repository, the expected address is https://skorotkiewicz.github.io/chesslm/.

Pages cannot run a Python server. This build instead loads Pyodide and NumPy in a Web Worker, then runs the same evaluator and search with the bundled model.npz. The checkpoint is copied unchanged; CI does not train or generate data. Relative asset paths support project Pages URLs such as /chesslm/.

The first visit downloads the Python runtime and NumPy from jsDelivr and can take a minute on a slow connection. Browser inference uses depth 2 and a 2,000-node limit per move, below the local server's default budget. The UI stays responsive while the worker searches. Refreshing discards the game.

To preview the Pages build locally, use an environment with requirements.txt:

python web/build_pages.py
python -m http.server 8001 --bind 127.0.0.1 --directory _site

Open http://127.0.0.1:8001. Opening index.html as a file:// URL will not work. The build bundles python-chess with its GPL license in chess.zip.

Command-line play

OPENBLAS_NUM_THREADS=1 python chesslm.py play \
  --model model.npz --depth 3 --max-nodes 20000

The command prints JSON with move, a UCI move string such as e2e4, and nodes, the visited node count. When the game has ended, move is null. Add --fen '...' with a valid FEN to choose a move in another position.

This command chooses one move per invocation; it does not implement a UCI engine server. CLI model paths are relative to your working directory.

How it works

ComponentImplementation
Input782 features: 12 piece planes, side to move, castling rights, legal en passant file, and halfmove clock
NetworkOne 128-unit tanh hidden layer, learning a correction to a fixed material evaluator
TargetWhite's evaluation, transformed with tanh(centipawns / 600)
TrainingMean squared error with Adam; save the weights with the lowest validation loss
SearchIterative deepening, alpha-beta pruning, capture ordering, and four quiescence plies
RuntimeNumPy and python-chess on CPU

The weight size excludes the NPZ header, Python, NumPy, and process memory.

A node limit can leave only a shallow completed search iteration. If none finishes, search returns a legal fallback move. Long tactics remain a limitation. The game and search recognize automatic draws but do not implement optional draw claims. A FEN does not include prior repetition history.

Training

Training is optional. The included checkpoint is enough to play.

Data generation and benchmarking require a Stockfish executable. Stockfish is not tracked in this repository. The default path is stockfish/stockfish-linux-x86-64-universal, relative to chesslm.py, for x86-64 Linux. Supply --engine /path/to/stockfish to use another location or a binary for your platform.

Run these commands on your training machine. Output files must not already exist; model-trained.npz leaves the included checkpoint available.

python chesslm.py generate \
  --engine /path/to/stockfish \
  --positions 100000 --nodes 20000 --threads 2 \
  --output positions-train.jsonl

OPENBLAS_NUM_THREADS=2 python chesslm.py train \
  --data positions-train.jsonl --epochs 30 --output model-trained.npz

Stockfish labels self-play positions with White's centipawn evaluation using a three-line search. Opening choices vary among those lines, with occasional variation later. Mate labels use ±10,000 centipawns. The node budget bounds Stockfish's search work, not elapsed time.

Validation holds out entire games and removes positions shared with those games from training. A dataset needs at least two games and some distinct positions. Training saves the initial weights if no epoch improves validation loss.

The dataset is loaded into memory. Input arrays need roughly 313 MB per 100,000 positions, plus JSON rows and temporary arrays. Larger datasets need more RAM.

Copy model-trained.npz to your playing machine and select it explicitly:

python chess_game.py --model model-trained.npz

Benchmarking

Generate a separate corpus with a different seed. Do not train on this file. Use fresh output filenames if you repeat the workflow.

python chesslm.py generate \
  --engine /path/to/stockfish \
  --seed 9001 --positions 5000 --nodes 20000 \
  --output positions-benchmark.jsonl

OPENBLAS_NUM_THREADS=1 python chesslm.py benchmark \
  --engine /path/to/stockfish \
  --model model-trained.npz --data positions-benchmark.jsonl \
  --positions 100 --nodes 50000

The JSON report contains the position count, best-move agreement, and mean centipawn loss against Stockfish. These are noisy, node-limited estimates. Different seeds can still produce repeated openings, and match testing is needed to establish an Elo rating. A tiny distilled model should not be expected to match Stockfish.

Development

From an environment containing requirements.txt, run:

OPENBLAS_NUM_THREADS=1 python -m unittest -v
python chesslm.py --help
python chess_game.py --help
python web/chess_web.py --help

Tests cover encoding, checkpoint size and loading, gradient math, invalid positions, validation separation, node limits, promotions, check evasion, forced mates, a free queen capture, and desktop interactions. GUI tests skip when no display is available.

The tests do not run Stockfish, generate data, or optimize model weights. Search tests use hand-set zero weights; the derivative check uses random weights without optimization. Passing them does not measure the supplied checkpoint's strength. Generation and training still need end-to-end verification on the training machine.

Project files

FilePurpose
chesslm.pyModel, search, data generation, training, and benchmark CLI
chess_game.pyTkinter desktop game
web/chess_web.pyLocal HTTP server and validated game API
web/chess_web.htmlBrowser chess board
test_chess_web.pyHTTP, rule, request-validation, and static-build checks
web/chess_position.pyShared move validation and board responses
web/chess_worker.jsBrowser-side model inference with Pyodide
web/build_pages.pyStatic site build, including the unchanged model
model.npzIncluded checkpoint for play
requirements.txtRuntime dependencies
test_chesslm.pyModel and search checks
test_chess_game.pyDesktop game checks

License

MIT.

Contributors

skorotkiewicz

17 commits

Languages

Python

77.1%

HTML

20.0%

JavaScript

2.9%