TAI BUI
Back to curriculum
CapstonePython

Checkpoint Save and Resume

Interrupts kill training runs; checkpoints let them continue. Save model, optimizer, scheduler, loss history, step counter, and RNG state, atomically, so a kill at any moment leaves a valid file on disk.

TIME: ~90 minutesPREREQUISITES: Phase 19 lessons 42 to 45

Learning Objectives

  • Capture the full training state into a single payload that can be reloaded into a freshly built model, optimizer, and scheduler.
  • Implement atomic save with write-to-temp then rename so a crash never leaves a half-written file.
  • Capture and restore the RNG state for Python, NumPy, and PyTorch, and resume mid-epoch so the post-resume loss matches the uninterrupted baseline.
  • Build a sharded checkpoint layout for models that no longer fit in a single file, with hash-verified shards and a JSON index.

The Problem

A checkpoint is a single file that holds everything needed to continue a run: model parameters, optimizer state, scheduler state, the loss history for plots, the current step and epoch and batch-in-epoch counters, and the RNG state for every source of randomness.

Here is what goes wrong without one. You set a training job for 18 hours. The wallclock cap is 4 hours. The cluster reboots at hour 11 because someone above your pay grade approved a kernel upgrade. With no checkpoint you start over. With weights but no resume logic you still lose the optimizer state that took the first 11 hours to learn, so the AdamW moments are gone and the next step lurches in a direction the training trajectory had already moved past.

RNG state matters too. Leave it out and the resumed loss curve is a different curve: same model, same data, different shuffle, different dropout mask, different number on the dashboard.

Atomic save is the other half of the contract. Write straight into the final filename and a crash mid-write leaves a corrupt file, so the resume reads garbage. Write into a temporary file in the same directory and rename it, and a crash mid-write leaves the previous good file untouched. The rename is atomic on POSIX file systems.

Warm-up0/3
1. A training script is killed in the middle of writing its save file directly to the final filename. What is the main risk on restart?Warm-up
2. You restart training from saved model weights only, with a brand-new AdamW optimizer. What do you most likely see in the loss curve?Warm-up
3. You call random.seed(7), draw three numbers, then call random.seed(7) again and draw three more. How do the two triples compare?Warm-up
Check: The Problem0/1
1. What is a checkpoint in a training run?Define

The Concept

Mermaid diagram

A checkpoint is the full training state serialized to disk, split into five buckets that together let a fresh process pick the run back up.

The five state buckets

BucketWhy it matters
ModelWeights and buffers; what the model is.
OptimizerMomentum and adaptive moments; without these the next step is a different optimization problem.
SchedulerWhere the learning rate is on its curve; cosine schedules in particular care.
Train countersStep, epoch, batch-in-epoch, plus the loss history that draws the dashboard.
RNG stateDeterminism for dropout, data shuffling, and any sampling inside the model.

Atomic save

An atomic save writes the payload to a temporary file and renames it over the target, so the target name points at a complete file.

Mermaid diagram

Two rules apply. The temporary file lives in the same directory as the target, so the rename stays within one file system (cross-device renames are not atomic). And the temporary name is unique per attempt, so two writers do not stomp on each other.

Sharded checkpoints

A sharded checkpoint splits the parameter state into several shard files and writes a small index that ties them together. You want one once the model is large enough that a single-file payload is too big to load fast, too big to inspect, and too painful when a network share hiccups mid-read.

Mermaid diagram

The index records the shard count, the sha256 of each shard, and the sha256 of the meta file. The loader checks the meta hash first, then each shard's hash as it reads that shard, and fails loudly on any mismatch. In main.py all shards are written to one directory, but because each shard is its own file you could place them on different disks or hosts. The meta file is small and reads first.

Resume continues mid epoch

A mid-epoch resume picks up at the exact batch inside the current epoch where the run stopped, using (epoch, batch_in_epoch). Snapping to the start of the next epoch instead wastes anywhere from minutes to a day. In main.py each epoch's batches come from a torch.Generator seeded with 12345 + epoch, so after load the training loop replays the batch_in_epoch draws already consumed in the current epoch and continues from there. The global Python, NumPy, and PyTorch RNG state is restored too, which matters for dropout and in-model sampling. The demo model has no dropout, so its comparison does not exercise that part. The demo's assertion is that max_loss_diff_after_resume, the largest absolute difference between the uninterrupted and resumed losses after the interruption step, stays under 1e-4.

Check: The Concept0/18
1. What is an atomic save?Define
2. In this lesson, what is a state dict?Define
3. What is a sharded checkpoint?Define
4. What does RNG state mean when a checkpoint saves it?Define
5. What is a mid-epoch resume?Define
6. Why must the temporary file in an atomic save live in the same directory as the target?Explain
7. Why is the temporary file name unique for each save attempt?Explain
8. Why does the payload hold scheduler state in addition to optimizer state?Explain
9. Why save the RNG state instead of only the original seed?Explain
10. What is the cost of resuming at the next epoch boundary instead of the exact batch?Explain
11. Why does the demo's resumed loss match the baseline even though the model never uses the restored global RNG?Explain
12. train_until starts with step=8, epoch=1, batch_in_epoch=3, batches_per_epoch=5 and stop_step=12. What is the state when it returns?Apply
13. A run is killed at epoch=3, batch_in_epoch=2 and resumed with main.py's train_until. What happens first?Apply
14. A team resumes a model that uses dropout. Weights, optimizer, scheduler and counters are restored, but RNG state is not. Losses wobble away from the baseline step by step. What explains it?Analyze
15. A team writes the temp file under /tmp, then renames it onto a checkpoint directory on a network mount. A kill during the rename sometimes leaves a bad file. Which rule did they break?Analyze
16. A run saves mid-epoch, but its batch_in_epoch is always stored as 0. After resume the weights, optimizer and RNG are exact, yet the loss differs from the baseline. Why?Analyze
17. Someone edits meta.pt so a shard's recorded hash matches a tampered shard file. The load still fails. Which check catches it?Analyze
18. You must checkpoint a model too large for one file, and the shards will sit on different hosts. Which layout fits?Design

Build It

code/main.py holds four primitives and a demo driver.

Step 1: capture and restore RNG state

capture_rng_state returns a dict with Python's random.getstate, NumPy's np.random.get_state, and PyTorch CPU and CUDA RNG bytes. restore_rng_state reverses it. The PyTorch states are stored as plain lists (via .tolist()) and rebuilt into uint8 tensors on restore, the byte-buffer form PyTorch's RNG consumes.

Step 2: atomic save

atomic_save writes the payload to a temp file in the target directory, then os.replace swaps it into the final name. atomic_write_json does the same for the sharded index.

Step 3: full checkpoint round trip

save_checkpoint packages the model, optimizer, scheduler, train state, and RNG into one dict. load_checkpoint reverses it and returns a TrainState. The schema field is the upgrade hook. When the format changes, you bump the version string and the loader dispatches on it.

Step 4: sharded variant

save_sharded_checkpoint round-robins the sorted parameter keys across N shards, writes each shard with its own atomic save, writes a meta file with optimizer and scheduler and train state, and writes the JSON index with the shard sha256s and the meta file's sha256. load_sharded_checkpoint checks the meta hash from index.json up front, then for each shard verifies its hash (taken from the shard list in the meta file) and merges that shard's tensors before moving to the next.

Step 5: resume demo

run_resume_demo trains a small model for total_steps, saves a checkpoint at interrupt_at, then continues to total_steps to produce the uninterrupted baseline. Next, in the same process, it reseeds, builds a fresh model, optimizer, and scheduler, loads the checkpoint into them, and runs the remaining steps. The function returns max_loss_diff_after_resume, the max absolute difference between the two loss trajectories after the interruption point. The match comes from the epoch-seeded batch generator plus the restored optimizer and scheduler state, so the difference is zero or floating-point noise.

Run it:

bash
python3 code/main.py

Both the single-file and sharded demos assert a max diff under 1e-4, and the summary lands in outputs/resume-demo.json.

Check: Build It0/11
1. Why does index.json store the meta file's sha256 as well as the shard hashes?Explain
2. What does the schema string in the checkpoint payload buy you?Explain
3. In load_sharded_checkpoint, what happens when a shard's sha256 does not match the recorded value?Apply
4. main.py runs with batches_per_epoch=5 and saves at interrupt_at=10, counting from step 0. What do epoch and batch_in_epoch hold in that checkpoint?Apply
5. shard_keys_by_prefix splits the state dict of make_model(4, 6, 3) into 3 shards. Which keys land in shard 1?Apply
6. torch.save raises halfway through writing the temp file inside atomic_save, while ckpt.pt already holds the step-100 checkpoint. What does ckpt.pt contain afterwards?Apply
7. A test appends one zero byte to model.shard-000.pt after a sharded save, then calls load_sharded_checkpoint. Which check fails?Apply
8. shard_keys_by_prefix splits 7 keys across 3 shards. How many keys does each shard hold, in shard order 0, 1, 2?Apply
9. After a successful atomic_save to ckpt.pt, which files does the directory contain?Apply
10. The resume demo reports max_loss_diff_after_resume = 0.31. Which cause fits best?Analyze
11. Old runs saved ckpt.v1 payloads. You add an ema field to the payload. Which approach keeps old checkpoints loadable?Design

Use It

Production training stacks ship checkpointing as part of the trainer, and the shape matches what you built: model, optimizer, scheduler, counters, and RNG, written atomically (production trainers usually also name files by step so the latest is easy to find; main.py writes a fixed ckpt.pt). Sharded layouts power large model loading with parallel reads, and the index.json is what makes that work.

Three patterns to enforce:

  • Schema is a string in the payload. Migrations branch on it. Without it you cannot evolve the format without breaking old runs.
  • Sha256 every shard. A silently truncated download is a nasty bug, and the loader either fails fast or fails late.
  • Keep checkpoint cadence honest. Save every N steps and every wallclock-minute, whichever is shorter. Otherwise the long step that crashes wastes a full window of work. main.py saves at one chosen step only; the wallclock trigger is Exercise 3.
Check: Use It0/3
1. Why does the index record a sha256 for each shard?Explain
2. Why add a wallclock-interval save on top of saving every N steps, as Exercise 3 suggests?Explain
3. A job needs 18 hours, the wallclock cap is 4 hours and nodes reboot at random. Which plan survives best?Design

Ship It

outputs/skill-checkpoint-save-resume.md is the recipe for a new training script: payload shape, atomic write, RNG capture, sharded index. Drop the skill into a repo, wire save_checkpoint at the periodic save site and load_checkpoint at startup, and the run survives kills.

Exercises

  1. Replace round-robin sharding with sharding by parameter group (layers ending in .weight vs .bias). When is each layout preferable?
  2. Extend the save loop to keep the last K checkpoints and prune older ones. What is the right K when the disk is small?
  3. Add a --ckpt-every-seconds flag that triggers a save on a wallclock interval, not just step count.
  4. Add a checksum verification path that runs at startup, scans every checkpoint in the directory, and reports which ones are corrupt.
  5. Implement a migrate_v1_to_v2 function that adds a new field to the payload and bumps the schema string. Make load tolerate both versions.

Key Terms

TermWhat people sayWhat it actually means
Atomic save"Write and pray"Write to a temp file in the same directory, then os.replace into the target name
State dict"The weights"Model parameters and buffers, keyed by parameter name
Sharded checkpoint"Big model file"Multiple files, one per shard, plus a meta file and a JSON index with sha256s
RNG state"Random seed"Captured state for python random, numpy, torch CPU, torch CUDA; not just the seed
Mid-epoch resume"Restart"Replay the consumed draws of the epoch's generator and continue from the next batch in the same epoch
Author's Note

My bet is that the resume check matters more than the save code. A save function that runs without error tells me little. The number I'd trust is max_loss_diff_after_resume coming back near zero, because that is the contract: the optimizer, scheduler, and resume position all came back faithfully. I'd also write the schema string into the first payload I ever saved, since without it the format can't evolve without breaking old runs. And I'd treat a shard with a mismatched hash as a hard stop, not a warning. A bad state_dict that merges quietly is the failure I'd least want to discover at hour 11.

Further Reading

  • POSIX rename semantics for the atomicity claim that os.replace relies on.
  • PyTorch documentation on torch.save and torch.load, including map_location for cross-device restores.
  • Phase 19 lesson 46 covers the gradient accumulation that the checkpoint payload here survives across.
  • Phase 19 lesson 48 covers the distributed wrappers whose state dict format this scheme accommodates.
  • The Linux kernel fsync documentation for the durability guarantee behind atomic rename.

Code

Ships With