Inference
Inference means feeding a position to the weights obtained from training (the network's parameters) and having them produce a policy (a distribution over how promising each move is) and a value. In DLManaka, the go command of the USI engine dl-manaka-usi runs a Gumbel AlphaZero-style MCTS (Monte Carlo tree search), and this inference gets called repeatedly inside it. Setting the option PolicyOnly to true skips the search and returns whichever legal move the policy of a single evaluation rates highest — the baseline for measuring what the search adds.
Getting it running: export a training checkpoint (a mid-training snapshot of the weights) to ONNX (a framework-independent interchange format for neural networks), and have the Rust engine load it.
uv sync --extra cu129
uv run python -m tideborn.tools.export_onnx \
--checkpoint runs/ckpt/rtx4070ti/step-000005000.pt \
--out runs/export/rtx4070ti.onnx
cargo build --release -p dl-manaka-usi
./build/target/release/dl-manaka-usi --model runs/export/rtx4070ti.onnxThe model can also be passed via the environment variable DLMANAKA_MODEL; an environment variable is easier to write into a GUI's engine settings than an argument. Likewise, setting DLMANAKA_DEVICE to cpu / cuda pins the execution device. The default is CUDA when available, falling back to CPU otherwise.
Inference lives on the Rust side. Position representation, legal-move generation, and encoding are all already in Rust, so pulling only inference back into Python would leave two implementations of the board. Weights travel as ONNX and are read by ort (ONNX Runtime). ort has a CoreML EP (EP = execution provider, the backend that decides which hardware runs the model), so the work can be verified here on the CPU EP while staying on one continuous road toward the actual goal, running on Apple Silicon.
Always cross-check an export before trusting it. A broken export breaks silently — you get a well-formed graph whose logits (the network's raw output values) are subtly off, and the engine keeps playing slightly wrong moves. export_onnx.py runs torch and onnxruntime on the same inputs and checks not just the numerical difference but whether the argmax (the position of the maximum) agrees before reporting success. If the check fails, the half-written .onnx is deleted — a graph that loads but is wrong is worse than no graph at all.
No inverse mapping from policy to moves. Each policy label only encodes "which direction the piece moved", so the origin square cannot be recovered from a label alone. Instead, iterate over legal_moves(), look up the label of each legal move, and take the maximum among those. The tests in encoding.rs guarantee, over 200 games' worth of positions, that every legal move gets a label and no two collide within the same position, so in this direction the answer is unique.
Do not run the input through pack_planes. encode_planes produces one byte per bit, so a cast to f32 is already the network input. packbits is compression for writing shards (the files a teacher dataset is split into) to disk; inserting it into the inference path just packs bits and immediately unpacks them again.
Sanity checks of the export path, done with 5000-step weights. From the initial position the engine plays the book-standard sequence 7g7f → 2g2f → 8c8d. Against 256 positions from the teacher data's val shard (val = validation, data held out from training for checking), best-move agreement with legal-move masking is 40.6%, matching the training log's val top1 (0.41) — meaning the ONNX path gives the same answers training did. Without the mask, agreement is 0.4%, which is corroboration that the legal-move mask is doing its job (the network knows nothing about legality, so with the mask removed it picks illegal moves).
Tests
uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run pytest -m "not gpu"
cargo test --workspaceThe inference tests in dl-manaka-core need a .onnx, so they are ignored by default. To run them, pass an absolute path.
DLMANAKA_TEST_MODEL=$PWD/runs/export/rtx4070ti.onnx cargo test -p dl-manaka-core -- --ignoredTests that require a GPU carry the gpu mark, which CI excludes. Everything else drives the training loop end to end on synthetic data (dummy data made from random numbers), so CI does not degrade into a mere import check.