Repository navigation
Conversation
skpulipaka26
force-pushed
the
feat/async-ckpt-offload
branch
2 times, most recently
from
November 3, 2026 19:50
3ec07c3 to
3ff1cde
Compare
skpulipaka26
force-pushed
the
feat/async-ckpt-offload
branch
from
November 3, 2026 23:11
3ff1cde to
7194fd1
Compare
garrett361
self-requested a review
November 5, 2026 21:27
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opt-in async trainer checkpoint saves when CPU optimizer offload is on (
model.offload=optimizerorfull). The optimizer state already lives in CPU RAM, so the background thread writes it from there. It is not copied again.What changes
New
ckpt.async_saveon the trainerCheckpointConfig(defaultfalse). It only applies with CPU optimizer offload. Without offload, or withasync_save = false, the save is synchronous as before.At save time, on the main thread:
fullmode the compute params) are copied to CPU. This is a real snapshot.fullmode's fp32 masters) is passed to DCP by reference. Inoptimizermode the async path skipsprepare_checkpoint_save, so the state no longer goes CPU→GPU just so DCP can copy it back.qkvwith the defaultshard_fused_on_dim1 = false) get all-gathered when they are split into their logical entries. For those entries only, the CPU state is copied to GPU first, so the gather runs over NCCL. The gathered result is a fresh copy either way.dcp.async_saveruns with a stager that makes no copy, on a gloo group used only by background writes.Consistency:
OffloadOptimizer.hold_state_updates(future)makes the optimizer wait for the write right before it next updates its CPU state in place:CPUOffloadOptimizer.step(): after the GPU step, before the states are copied back into the pinned buffers.FullCPUOffloadOptimizer: on the main thread atbegin_backward(final_backward=True), before any CPU optimizer chunk is scheduled, and at the start of the synchronousstep()path.The rest of the next step's forward/backward overlaps the write. A failed write raises at that point.
CheckpointManager: waits for the previous write before it starts a new save. A step is added tockpt_steps(and so becomes visible tokeep_last) only after its write has finished.wait()runs after the final save in RL and SFT, beforemark_savedon pause. The background write time is logged at info level.time/save_ckptmeasures the blocking part.loadis unchanged.CPU RAM cost
model.fusions.shard_fused_on_dim1 = trueno gather is needed.Measurements
Qwen3-0.6B SFT (reverse-text), 2x RTX PRO 6000 (FSDP 2), batch 32 × 1024,
ckpt.interval = 2,keep_last = 2. Each checkpoint is about 7.7 GB on local disk.time/save_ckptat steps 2 / 4 / 6:model.offloadoptimizerfullOn this small model the write (about 6 s) takes longer than a step (3.4 s / 4.2 s). So the step after a save waits about 2.5 s (
optimizer) or 3.5 s (full) for the write to finish. Each save then costs about 3 s of stall inoptimizermode instead of about 7 s. With longer steps the write fully overlaps.Related
state_offload.py. This PR adds one line there, before the copy back to CPU instep().AppState.load_state_dict. This PR does not change load.Test plan
optimizer, 0 / 1551 forfull). This also holds withshard_fused_on_dim1 = true.optimizer) and 243 (full) optimizer tensors differ (exp_avg,exp_avg_sq, masters). So the test catches a missing guard..metadataexists for every saved step.keep_last = 2cleanup kept the right steps.uv run pytest tests/unit/train/test_state_offload.py(GPU) andtests/unit/test_configs.py tests/unit/utils/test_pathing.py tests/unit/orchestrator/test_config_update.py.ruff checkandruff format.