Skip to content

Training loop

The loop structure

The training loop works in this order:

  1. Rust runs self-play using the active rules and model guidance.
  2. Self-play writes game records, statistics, and indexing information.
  3. Python reads that data and builds training batches.
  4. Model training writes checkpoints and metrics.
  5. Later self-play uses the updated model.

The important property is not just that these stages exist. Each stage must leave reusable output behind so the next stage does not depend on hidden in-memory state.

Here is a minimal example of one cycle:

  1. the current model is used for 100 self-play games
  2. each move, score update, and end reason is saved into the run directory
  3. Python reads those records and turns them into training batches
  4. the model is updated and a new checkpoint is written
  5. later self-play uses that new checkpoint

Real runs may be much larger, but the data flow is the same.

What Rust handles

Rust owns the high-frequency and rules-sensitive parts:

  • board representation and bitboards
  • move legality
  • captures and terminal-state detection
  • MCTS search
  • self-play execution
  • compressed game records and indexing support

Why this split exists: if this logic drifts into Python in a second form, both performance and consistency get worse.

Inputs and outputs:

  • input: configuration, model inference interface, random seed, current run state
  • output: game records, state transitions, search statistics, and terminal summaries

How to judge a normal result:

  • Rust outputs can be read by training and tools
  • the FFI boundary does not let a Rust panic crash the Python interpreter

If FFI is new to you, treat it as the interface boundary where Rust and Python call each other.

What Python handles

Python owns training and experiment control:

  • configuration loading
  • hardware selection
  • residual CNN definition
  • checkpoint rotation
  • metrics writing
  • training loop control
  • Rust bridge integration

Why this split exists: PyTorch training and device management fit Python well, and they do not need a second rules implementation.

Inputs and outputs:

  • input: configuration files, game records, checkpoints, device availability
  • output: new checkpoints, training metrics, and the next model version

How to judge a normal result:

  • the selected device is explicit before training starts
  • checkpoints rotate through latest, previous, and best
  • metrics are written to the run directory instead of living only in process memory

Those checkpoint names can be read like this:

  • latest: the newest saved state
  • previous: the earlier fallback state
  • best: the saved state the project currently considers best

Why the run directory matters

Long-running experiments become hard to recover if outputs are scattered.

This repository uses:

data/runs/<run_id>/

as the shared container for one run's inputs and outputs.

How to judge whether the loop is healthy

Check at least four things:

  1. self-play reaches legal terminal states
  2. records, indexes, and metrics keep being written
  3. later training can reload the saved checkpoints
  4. recovery continues from prior state instead of resetting silently

After a healthy run starts producing output, you should expect to find at least:

  • checkpoint files
  • game records or a game index
  • metrics files

If you only have screenshots, loose logs, or model files without game data, the loop is not complete.