Usage
--offload-train-disk-chunk-mb regardless of how much is offloaded. Each rank writes to
its own directory under --offload-train-disk-dir (defaults to
$SCRATCH/miles_train_offload_<uid>), the files are overwritten in place every step, and
they are removed when the actor exits.
Point the directory at real node-local NVMe. A tmpfs mount (including /tmp on many
systems) keeps the backup in RAM and defeats the purpose.
How it works
This runs on torch_memory_saver, which hooks the allocator, so it does not care what the memory holds — weights, gradient buffers and optimizer state all move as one block when the actor is paused, and come back on resume. miles’ part is choosing the per-rank directory, launching each actor with the matchingTMS_DISK_BACKUP_* environment, and reclaiming the files at startup and exit.
Because pause and resume happen at phase boundaries, everything is resident again by the
time the optimizer step runs. If the binding constraint is instead that the optimizer
state does not fit the GPU during the step, actor offload cannot help; that case is what
--stream-optimizer-state-to-disk addresses, and the two compose.
Streaming the optimizer state
expert_bias, a GDN/Mamba A_log) stay GPU-resident under a small separate Adam: they
are tiny, and unlike the bf16 path their optimizer shards alias the model params directly.
For the Adam/DistributedOptimizer path, streaming also bounds initialization: Megatron releases
each fp32 main shard’s storage after creating its tensor handle, then Miles fills one existing
runtime bucket at a time directly in the final files. There is no temporary state file. Peak
initialization HBM is the largest individual shard during construction and one main-only bucket
afterward (normally at most about 200M fp32 elements, except for an oversized entry). Each
initialized range is synchronized and evicted from page cache before continuing.
fp32 storage is bit-identical to keeping the state on GPU, so turning this on does not
change results — it trades step time for memory. The step is I/O bound and the moments
tolerate less precision than the master copy, so they can be stored narrower:
exp_avg_sq needs
per-block scaling to survive 8-bit storage, which this does not implement.
Three limits to know about. Resume is same-topology only — the on-disk layout follows this
rank’s DP shard, so changing TP/PP/DP/EP fails the layout assert rather than resharding.
A checkpoint written before streaming was enabled cannot be resumed with it: the streamed
state is the only optimizer state read, so miles refuses rather than silently restarting
Adam from zero — pass --no-load-optim to accept a fresh optimizer state. And the
optimizer state is copied to the checkpoint directory synchronously, outside
--async-save, so expect checkpoint saves to take noticeably longer.
The two also help each other. With the optimizer state already on disk there is that much
less to move when the actor is paused: on Qwen3-30B-A3B, sleep/wake went from 24s/8.9s to
5.2s/1.3s once the paused actor no longer carried the state.
Choosing
--offload-train is the base mechanism, and it is not tied to colocation: the actor is
resident only during train() and sleeps for the whole rollout window either way. What
changes is who takes the freed HBM. Colocated, it is the engine on the same GPUs, so
offload is mandatory and defaults on. Disaggregated, it is whatever else you fit on the
training GPUs, which is the point when you are sizing engines against a fixed cluster.
- Paused actor fits in host RAM: keep
--offload-train-target=cpu(default). Fastest, no scratch space. - It does not fit:
--offload-train-target=disk. - On top of that, the optimizer state does not fit the GPU during the step, which offload
cannot help with: add
--stream-optimizer-state-to-disk, and consider--stream-optimizer-state-moment-dtype bf16to claw back some of the I/O cost.
--offload-train-target is never read. Pass
--stream-optimizer-state-to-disk alone there. Under --offload-train, though, the two go
together — a run that cannot hold the optimizer state on the GPU for the step will not hold
a pinned host copy of the whole actor either — and miles asserts that pairing.
Both mechanisms share --offload-train-disk-dir and --offload-train-disk-chunk-mb. Point the
directory at real node-local NVMe: a tmpfs mount, which /tmp is on many systems, keeps
the data in RAM and defeats both. The chunk is a pinned host staging buffer and each
mechanism allocates its own, so enabling both costs 2x that per rank.
