Skip to content

Checkpoints

StableTested and in active use. Breaking changes will be called out.

Checkpointing walks a Module’s parameters and states and writes one named section per field. The format can carry enough for an exact training resume — Adam’s per-parameter moments ride along with the weights — but whether a given checkpoint does depends on who wrote it. The agents’ own checkpoints mostly do not; see what the agents save.

The generic entry points live in noeira/nn/core/checkpoint.mojo:

from noeira.nn.core.checkpoint import save_params, load_params
save_params["gpu"](model, "model.ckpt", ctx) # weights + moments
save_params["gpu"](model, "model.ckpt", ctx, save_moments=False) # weights only
load_params["cpu"](model, "model.ckpt")

save_params_multi / load_params_multi pack several modules into one file (an actor and its critics, for instance), in a fixed order.

The batched drivers auto-checkpoint when the trainer implements save_state. Pass a cadence and a path:

_ = agent.train[BatchedEnvT, N_ENVS=N_ENVS](
env, 600_000,
checkpoint_every=50_000, # env-steps between saves
checkpoint_path="sac_half_cheetah_nn.ckpt",
)

Saves happen in host code between driver iterations, as a device-to-host copy of the live parameters, so they are safe alongside CUDA-graph capture (the graphs are per step, not the whole loop). A final save runs at the end of training. The replay buffer and episode tracker are never saved.

Drivers built on the project layer take the path from their run instead — run.checkpoint_path("best") is <run>/checkpoints/best.ckpt — so two runs never overwrite each other. See Projects and runs.

save_params writes storage-ckpt v3:

storage-ckpt v3
P <dotted-name> <size> <has_moments:0|1>\n
<size raw little-endian Scalar[DT] payload bytes>[<m bytes><v bytes>]
S <dotted-name> <size>\n
<size raw payload bytes>
...

P sections are parameters, S sections are module state. Names are dotted paths derived from the module walk — "0.weight" for the first layer’s weights in a Sequential.

Three properties are worth knowing about:

Names are validated on load. The dotted name and size of each section are checked against the in-memory walk order. A mismatch raises rather than loading garbage, which catches topology drift between the architecture that saved and the one trying to load.

Writes are atomic. The payload lands in a temporary file and is renamed over the target only once fully written. A crash mid-save cannot destroy the previous good checkpoint.

I/O is chunked at 1 GiB. This is not incidental — see the v2 note below.

Adam’s m and v live on each Param and are written into the same section when save_moments=True (the default for save_params) and they are populated. That is what makes a resume exact rather than approximate: reloading weights without moments restarts Adam’s bias correction and momentum from zero, which visibly perturbs training.

For inference or evaluation you do not need them, and save_moments=False writes a smaller, model-only checkpoint.

An agent facade’s save and the drivers’ auto-checkpoint both go through the trainer’s save_state. These save online weights only — no optimizer moments and no replay buffer; where an agent has target networks, they are hard-copied from the online ones on load. A resumed run re-warms its optimizers.

Trainersave_state writes
SACv3: actor + both online critics, in one file. The entropy temperature is not saved.
DQN, C51, TD3, DDPG, PPO, PPO-discrete, REDQ, REDQ-OFE, MBPOthe v2 text layout, through a single plain write — not chunked and not atomic. DQN and C51 add their ε-schedule and step counter as key=value lines; MBPO adds its dynamics ensemble.

Parameters living in device memory download on save and upload on load. No manual staging is required; the checkpoint layer handles the transfer.

The pattern the examples use is a paired eval script that rebuilds the same architecture and loads the checkpoint:

Terminal window
# trains, writes sac_half_cheetah_nn.ckpt
pixi run -e nvidia mojo run -I . examples/half_cheetah/sac_half_cheetah_training_gpu.mojo
# reloads and renders it
pixi run mojo run -I . examples/half_cheetah/sac_half_cheetah_nn_eval_cpu.mojo

The architecture must match, because the parameter layout is derived from it — including the fused-versus-unfused choice. A network built from LinearReLU does not have the same walk order as one built from Linear plus ReLU, and the name validation will reject the mismatch. The HalfCheetah pair avoids the problem by building both sides from the same SAC[...] preset, so the definitions cannot drift apart.

A GPU-trained checkpoint loads fine into a CPU evaluation script. The weights are device-agnostic.

  • Projects and runs — where a run’s checkpoints live, and how one becomes a deployable policy
  • CPU and GPU paths — where checkpoints fit in a device training loop
  • Traits — Module, Param and the Saveable contract