Checkpointing
nnx-ppo can periodically write the full
TrainingState to disk and reload it
later to resume training (or to load a trained policy for inference).
The checkpoint format is split across orbax (for the bulk array
state) and pickle (for new-style PRNG-key variables, which orbax does
not handle).
What’s saved
Each checkpoint persists every field of
TrainingState:
Network params (
nnx.Paramarrays for the actor, critic, samplers, heads, and any custom submodules) — via orbax.Non-param NNX variables — e.g.
Normalizer’s runningmean/M2/counter, any customnnx.Variablesubclasses your code defines — via orbax.RNG variables on the network (
nnx.RngKey,nnx.RngCount). The count goes through orbax; the key goes through pickle (because orbax does not yet support thekey<…>dtype).Optimizer state — Adam moments, learning rate schedule state — via orbax.
Carry state for the network and the env (
network_states,env_states).The top-level training RNG key, the cumulative step count, and optionally the
TrainConfigthat produced the run.
Disk layout per checkpoint:
{directory}/step_{step:010d}/
networks/ # orbax: non-key network variables
optimizer/ # orbax: optimizer state
metadata.pkl # RngKey vars + TrainingState fields + step + config
Saving during training
Pass a checkpoint_fn to train_ppo().
The make_checkpoint_fn()
helper builds one for you:
from nnx_ppo.algorithms.ppo import train_ppo
from nnx_ppo.algorithms.checkpointing import make_checkpoint_fn
result = train_ppo(
env=env,
networks=nets,
config=config,
checkpoint_fn=make_checkpoint_fn("/tmp/my_run", config=config),
)
Checkpoints are written every
checkpoint_every_steps
cumulative env steps. The config keyword is optional but
recommended: it stores the TrainConfig alongside the
checkpoint so the run is self-describing.
A checkpoint_fn is just a callable
(training_state: TrainingState, step: int) -> None — you can
plug in your own (e.g. to write to a remote object store) instead of
using make_checkpoint_fn().
Resuming a run
Restoring a checkpoint requires a network instance with the same architecture as the one that was saved. The network’s params are overwritten in-place by the checkpoint values; the architecture itself is not reconstructed from disk.
from nnx_ppo.algorithms import ppo
from nnx_ppo.algorithms.checkpointing import load_checkpoint
from nnx_ppo.networks.factories import make_mlp_actor_critic
# 1. Rebuild the same network architecture you trained with.
nets = make_mlp_actor_critic(...) # same kwargs as the original run
training_state = ppo.new_training_state(env, nets, n_envs, seed)
# 2. Load: weights / optimizer / carry / RNGs are restored in place.
ckpt = load_checkpoint(
"/tmp/my_run/step_0000500000",
training_state.networks,
training_state.optimizer,
)
# 3. Continue training from where the checkpoint left off.
result = ppo.train_ppo(
env, nets, ckpt["config"],
initial_state=ckpt["training_state"],
)
The returned dict also contains ckpt["step"] (int) and
ckpt["config"] (the persisted TrainConfig, or None
if none was stored).
Loading for inference only
To load a trained policy without resuming training, build a network
+ optimizer template, call load_checkpoint(), and then ignore
the optimizer / training-state fields:
nets = make_mlp_actor_critic(...)
training_state = ppo.new_training_state(env, nets, n_envs, seed)
load_checkpoint("/tmp/my_run/step_0000500000",
training_state.networks, training_state.optimizer)
nets.eval() # deterministic action sampler
# ... call nets(...) directly on observations from the env.