Grail is a hobby chess engine written in Rust. It began as an attempt to make a chess engine and has since become an elaborate system for turning my sanity and electricity bill into Elo. It uses modern search techniques and a fully self-taught NNUE trained on 99 million self-play games. The name refers to the Holy Grail, which may still be easier to find than perfect chess.
This repository hosts Grail's official releases and source code. The engine is developed entirely within this repository, which contains the self-play datagen, NNUE training pipeline, SPSA tuner, analysis tools, profiling, and build setup.
Grail is a command-line UCI engine built for Standard Chess, so it requires a UCI-compatible chess GUI (such as Arena, BanksiaGUI, or Cutechess) to play.
- Download: Grab the zip for your OS from the Releases page and extract it.
- Install: Open your chess GUI and add the right binary (see table below).
- Play: Start a game or analysis session.
Each release includes builds for a few different CPU architectures:
| OS | Binary | CPU compatibility |
|---|---|---|
| Linux / Windows | x86-64-v4 |
Intel Skylake-X/Ice Lake+ (2017+), AMD Zen 4+ (2022+) |
| Linux / Windows | x86-64-v3 |
Intel Haswell+ (2013+), AMD Zen 2+ (2019+) |
| Linux ARM / Android | aarch64 |
Any 64-bit ARM CPU |
| macOS | arm64 |
Apple Silicon (M1/M2/M3/M4) |
Tip
Not sure? On Windows/Linux, try x86-64-v4 first for best performance. If the engine crashes on startup, use x86-64-v3 instead - it has wider compatibility.
For technical details, see x86-64 Microarchitecture Levels.
macOS blocks unsigned binaries by default. Apple wants me to pay $99/year to sign the binary so you can avoid typing this command. So instead, after downloading, just run:
xattr -d com.apple.quarantine ~/Downloads/grail-arm64and you should be able to run it! 🍎
Once added to your GUI, you can configure Grail via the UCI options:
- Hash: Size of the transposition table in MB (Default: 16).
- Threads: Number of search threads (Default: 1).
- MultiPV: Number of principal variations to search (Default: 1).
- Move Overhead: Time buffer in milliseconds to account for communication lag (Default: 10).
- SyzygyPath: Paths to Syzygy tablebase files (separated by
;on Windows,:on Linux/macOS). - SyzygyProbeDepth: Minimum depth to probe tablebases (Default: 1).
The engine supports standard time controls (increment, sudden death, moves to go) and analysis modes (fixed depth, fixed nodes, soft nodes, infinite).
You can challenge the latest version of Grail on Lichess, running on a 1 vCPU Northflank instance with 256 MB hash.
Grail is built on Rust, so make sure you have it installed.
git clone https://github.com/jorgenhanssen/grail.git
cd grail
make grailThe resulting release binary is written to target/release/grail.
The project includes a Makefile for convenience:
makeormake grail: Release buildmake grail-pgo: Release build with PGO.make generate: Builds the NNUE self-play datagen.make generate-pgo: Builds the NNUE self-play datagen with PGO.make train: Builds the NNUE trainer (auto-detects CUDA/Metal).make tuner: Builds the SPSA tuner.make tuner-pgo: Builds the SPSA tuner with PGO.make nnue-analysis: Dumps a analysis of the current NNUE tonnue/model.analysis.txt.make profile: Profiles the built-in benchmark withsamply.make clean: Remove the build directory.
Everything needed to generate self-play data and train Grail's NNUE lives in this repository.
Build the generator and choose either an EPD opening book or random moves from startpos:
make generate
# Openings from an EPD opening book
./target/release/generate book --path books/your_opening_book.epd
# Openings from startpos + random moves
./target/release/generate random --plies 8Arguments:
--depth: Search depth for each move (default: 8).--nodes: Soft node limit for each move.--pv-lines: Number of PV lines to search at each decision point (default: 1).--threads: Number of threads (default: number of logical CPUs).--syzygy-path: Paths to Syzygy tablebase files.--max-opening-imbalance: Discard games whose opening eval exceeds this many centipawns in absolute value.--max-teleport-plies: Max plies to teleport along a PV between recorded positions (default: 8).--max-game-plies: Discard games lasting longer than this many moves (default: 300).--max-games: Stop after this many games total.--dry-run: Generate samples but don't write the dataset to disk.
Generated data is saved to nnue/data/YYYY-MM-DD-HH:MM.csv.
Train a new network using the generated data:
make train
./target/release/trainRun it again later and it'll pick up where it left off. Pass --restart to start from scratch.
Arguments:
--batch-size: Batch size for training (default: 8192).--learning-rate: Initial learning rate (default: 0.001).--epochs: Max number of epochs to train (default: 200).--workers: Number of worker threads for data loading (default: 4).--val-ratio: Fraction of data to use for validation (default: 0.05).--test-ratio: Fraction of data to use for testing (default: 0.01).--lr-decay: Learning rate decay factor (default: 0.95).--patience: Epochs without improvement before early stopping (default: 5).--shard-size-mb: Size of each data shard in megabytes (default: 500).--wdl: WDL blending weight, 0.0 = pure eval, 1.0 = pure WDL (default: 0.3).--draw-target: Target win-probability for drawn games, smaller = prefer wins over draws (default: 0.5).--restart: Discard saved progress and train from epoch 1.
If you want to initialize a new/random model without starting training:
./target/release/train initThere's a small SPSA tuner for optimizing search constants.
Gotcha: it uses plotters to draw some nice graphs of the params as it is tuning, so make sure to follow their install steps first (Ubuntu requires setup of fontconfig and freetype).
To set up for SPSA tuning, just copy tuning/params.example.toml to tuning/params.toml and add the EngineConfig fields you want to tune following the example format.
make tuner
./target/release/tuner --params tuning/params.toml --book books/UHO_Lichess_4852_v1.epdArguments:
--params: Path to the parameter TOML.--book: Path to an EPD opening book.--pairs: Game pairs per iteration (default: 100).--nodes: Soft node limit per move (default: 25000).--iterations: Stop after this many iterations (runs until Ctrl+C if omitted).--workers: Worker threads (default: logical CPUs).--gain: How hard to nudge parameters.--max-plies: Abort game as draw after this many plies (default: 500).--resign-score: Score threshold to adjudicate a win (default: 400).--resign-moves: Consecutive moves above resign score needed to resign (default: 3).--draw-score: Score threshold for adjudicated draws (default: 10).--draw-moves: Consecutive moves within draw score needed to draw (default: 8).--draw-after: Moves after the opening before adjudicated draws are allowed (default: 40).
- Chess Programming Wiki - A very helpful resource for chess programming concepts and techniques.
- Thanks to various engine testers, such as the CCRL, for testing and ranking Grail.
- Opening books in
/bookssourced from the Stockfish opening books and the computer chess community.