Resume Training from Checkpoints
Resume training from a checkpoint safely — LLM Finetuning tutorial, lesson 50. Learn key steps, troubleshooting, and what to study next.
Focus: resume training from a checkpoint safely
You've poured hours of compute and money into a fine-tuning run, and just when the loss curve looked beautiful, your GPU died, your cloud instance was preempted, or your session timed out. Without a plan, you'd be forced to restart from scratch, burning days of progress and budget. In this lesson, you'll master the art of resuming training from a checkpoint safely — not just hitting "resume" and hoping for the best, but doing it in a way that guarantees consistency, avoids silent corruption, and gets you back on track faster than ever.
The problem this lesson solves
Training a large language model (LLM) is a marathon, not a sprint. A single fine-tuning run can take hours or even days, using expensive GPUs and consuming significant energy. The harsh reality is that interruptions are inevitable: hardware failures, spot instance terminations, power outages, or even a careless Ctrl+C during a lunch break.
If you've ever had to restart a training run from epoch zero because you didn't have a robust checkpointing strategy, you know the pain. The loss curve resets, the learning rate schedule restarts, and you watch the same progress slowly climb again — a frustrating and costly déjà vu.
The core problem is state management. A training run isn't just the model weights; it includes the optimizer state (like Adam's momentum and variance), the learning rate scheduler, the random number generator state, the current global step, and your training/evaluation data loaders. If you only save the model weights, you can't truly resume training — you can only start a new run from those weights, losing the subtle dynamics that were built up over thousands of steps.
Pro tip: Always plan for checkpointing before you start training, not as an afterthought. The best time to design a recovery strategy is when everything is going well.
Core concept / mental model
Think of a training run as a long, complex journey. The model weights are your destination on a map; the optimizer state is your vehicle's fuel and engine tuning; and the scheduler state is your GPS, telling you how fast to go and when to stop for refueling. A checkpoint is a snapshot of the entire journey at a specific point in time.
To resume training from a checkpoint safely, you need to capture and restore the complete state of the run, not just the destination. A safe resume is like pressing "play" on a paused video — you expect to continue exactly where you left off, not rewind to the beginning.
Here's a mental model with the key components:
- Model weights (
model_state_dict): The learned parameters of your LLM. - Optimizer state (
optimizer_state_dict): Internal variables like momentum and variance for adaptive optimizers like AdamW. Without this, you lose the "memory" of past gradients. - Scheduler state (
scheduler_state_dict): The current learning rate and position in the decay schedule. - Global step and epoch: The position in your training loop.
- RNG states: For Python's
random, NumPy'snp.random, and PyTorch'storch.random. Critical for reproducibility, especially with data shuffling and augmentation. - Data loader state: Which batch you were on, especially important if you use distributed sampling or custom iterators.
A simple mental model: A checkpoint is a container that holds a full snapshot of your training universe. A safe resume restores the entire snapshot, not just a fragment.
How it works step by step
Safely resuming training from a checkpoint follows a systematic process. Let's break it down:
-
Create regular checkpoints: During training, save the complete state every N steps or epochs. Don't rely on a single checkpoint — keep a few recent ones (e.g., best model and latest step) to hedge against corruption.
-
Design a checkpoint format: Use a structured format like a directory with separate files (e.g.,
model.pt,optimizer.pt,scheduler.pt,metadata.json) or a single tar archive. Ensure atomic writes by saving to a temporary file and then renaming, to avoid partial checkpoints. -
Record all essential state: At each checkpoint, include the model, optimizer, scheduler, step, epoch, RNG states, and any custom training loop variables (e.g., best validation loss, patience counters).
-
Detect an interruption: When training stops (due to error or user interruption), catch the signal or exception and log that you're stopping. This is where you'll later decide to resume.
-
Load the checkpoint: When starting a new training script, check for the existence of a checkpoint. If found, load all components back into memory.
-
Validate the loaded state: After loading, perform sanity checks — e.g., verify the model weights match the expected shape, the optimizer state is consistent, and the current step is what you expect.
-
Restore the data loader state: Skip the number of samples already processed. This is often the trickiest part, especially with custom data pipelines. Use
torch.utils.data.DataLoaderwith a sampler that you can set to a specific index. -
Resume the loop: Continue the training loop from the saved step/epoch, not from scratch.
The key principle is atomicity and completeness. A safe resume is impossible if you miss any part of the state.
Remember: A checkpoint is not just a model file. It's a complete snapshot of your training run.
Hands-on walkthrough
Let's get our hands dirty with a concrete example using PyTorch and Hugging Face Transformers. We'll build a simple training script that can be safely interrupted and resumed.
Saving a checkpoint
First, let's define a helper function that saves the full training state. We'll use a temporary file and rename it for atomicity.
import os
import torch
import json
from pathlib import Path
def save_checkpoint(state, filename):
"""Save a checkpoint atomically."""
checkpoint_dir = Path(filename).parent
checkpoint_dir.mkdir(parents=True, exist_ok=True)
tmp_path = filename + ".tmp"
torch.save(state, tmp_path)
os.replace(tmp_path, filename) # atomic rename
print(f"Checkpoint saved to {filename}")
def save_training_state(model, optimizer, scheduler, step, epoch, best_loss, rng_states):
"""Assemble and save the full training state."""
state = {
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
"scheduler_state_dict": scheduler.state_dict() if scheduler else None,
"step": step,
"epoch": epoch,
"best_loss": best_loss,
"rng_states": rng_states,
}
save_checkpoint(state, f"checkpoints/checkpoint_step_{step}.pt")
# Also save a 'latest' checkpoint for easy resume
save_checkpoint(state, f"checkpoints/latest.pt")
Capturing RNG states
Before each training step, you can capture the current RNG states. This is useful for resuming with exact reproducibility.
def get_rng_states():
"""Capture RNG states for Python, NumPy, and PyTorch."""
return {
"python": random.getstate(),
"numpy": np.random.get_state(),
"torch": torch.random.get_rng_state(),
"cuda": torch.cuda.get_rng_state_all() if torch.cuda.is_available() else None,
}
Loading and resuming
Now, the resume function:
def load_checkpoint(config, model, optimizer, scheduler):
"""Load a checkpoint and restore the full training state."""
checkpoint_path = config.get("resume_from", "checkpoints/latest.pt")
if not os.path.exists(checkpoint_path):
return model, optimizer, scheduler, 0, 0, float('inf'), None
print(f"Loading checkpoint from {checkpoint_path}")
state = torch.load(checkpoint_path, map_location=config["device"])
model.load_state_dict(state["model_state_dict"])
if optimizer and state["optimizer_state_dict"]:
optimizer.load_state_dict(state["optimizer_state_dict"])
if scheduler and state["scheduler_state_dict"]:
scheduler.load_state_dict(state["scheduler_state_dict"])
rng_states = state.get("rng_states")
if rng_states:
random.setstate(rng_states["python"])
np.random.set_state(rng_states["numpy"])
torch.random.set_rng_state(rng_states["torch"])
if rng_states["cuda"] and torch.cuda.is_available():
torch.cuda.set_rng_state_all(rng_states["cuda"])
return model, optimizer, scheduler, state["step"], state["epoch"], state["best_loss"], state
Putting it together
Here's a minimal but complete training loop that uses these functions:
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_scheduler
def train(config):
# Setup model, optimizer, scheduler
model = AutoModelForSequenceClassification.from_pretrained(config["model_name"])
optimizer = torch.optim.AdamW(model.parameters(), lr=config["lr"])
num_training_steps = config["num_steps"]
scheduler = get_scheduler("linear", optimizer=optimizer, num_warmup_steps=100, num_training_steps=num_training_steps)
start_step, start_epoch, best_loss, _ = load_checkpoint(config, model, optimizer, scheduler)
# Dummy dataloader for example
train_loader = create_dataloader(config)
# Restore data loader position if needed
train_loader = restore_dataloader(train_loader, start_step, config)
model.train()
global_step = start_step
for epoch in range(start_epoch, config["num_epochs"]):
for batch in train_loader:
# forward/backward
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
scheduler.step()
optimizer.zero_grad()
global_step += 1
# Save checkpoint every N steps
if global_step % config["save_every"] == 0:
rng_states = get_rng_states()
save_training_state(model, optimizer, scheduler, global_step, epoch, loss.item(), rng_states)
# Optionally save at end of epoch
print("Training complete!")
Note: In a real project, you'd also handle
KeyboardInterruptto save a final checkpoint, and use distributed training utilities liketorch.distributedwith proper checkpoint sharding.
Compare options / when to choose what
There are several ways to implement checkpointing. Here's a comparison of common approaches:
| Approach | Description | When to choose |
|---|---|---|
| Full state checkpointing | Save model + optimizer + scheduler + RNG + step + best loss. Resume exactly where you left off. | Always the safest and preferred for auto-resume after crashes. |
| Model-only checkpointing | Save only model.state_dict(). Use from_pretrained to load and start new training. |
When you don't need to continue the same run (e.g., fine-tuning on new data) or when training is short and interruptions unlikely. |
Hugging Face Trainer |
Built-in save_strategy and load_best_model_at_end with TrainingArguments. Handles optimizer/scheduler automatically. |
When using Trainer API for standard tasks (classification, etc.). |
External libraries (e.g., pytorch-lightning) |
Automatic checkpointing with ModelCheckpoint callback. Easy resume with trainer.fit(ckpt_path=...). |
When you want a high-level abstraction and don't mind the framework. |
For most real-world LLM fine-tuning, full state checkpointing is the gold standard. It gives you the flexibility to resume from arbitrarily long interruptions without losing progress.
Pro tip: Always save at least two checkpoints: the latest one and the one with the best validation metric. If the latest is corrupted (e.g., due to a crash during save), you can fall back to the best.
Troubleshooting & edge cases
Even with a solid plan, things can go wrong. Here are common issues and how to fix them:
-
Mismatched model architecture: If you change the model configuration (e.g., hidden size) between runs, loading the state dict will fail. Fix: Always load with
strict=Falseand handle missing/unexpected keys, or better, don't change architecture when resuming. -
Corrupted checkpoint: A partial write (e.g., power loss during save) can make the file unreadable. Fix: Use atomic writes (like in our example) and verify file integrity with checksums or just try/except when loading.
-
Learning rate schedule restarts: If you forget to load
scheduler_state_dict, the LR will reset to the initial high value, potentially causing loss spikes. Fix: Always save and load the scheduler state. -
Data loader resets to beginning: If you don't restore the data loader state, you'll repeat previously seen data, overfitting and corrupting the training progress. Fix: Implement a sampler that can be advanced to a specific index.
-
RNG state mismatch: Different RNG states lead to different data orders, which may not be a real bug but can affect reproducibility. Fix: Restore RNG states as shown.
-
CUDA out-of-memory when loading: The checkpoint may contain tensors on GPU. Fix: Use
map_location='cpu'when loading, then move to GPU after.
Remember: The two most common silent bugs are forgetting to restore the scheduler and not resuming the data loader correctly. Always verify that your training loss curve continues smoothly after a resume.
What you learned & what's next
Congratulations! You've learned the fundamental principles of resuming training from a checkpoint safely. You can now:
- Explain the importance of saving the complete training state, not just model weights.
- Implement full-state checkpointing in a PyTorch training loop, including optimizer, scheduler, RNG states, and step counts.
- Load a checkpoint and resume training exactly where you left off, avoiding wasted compute.
- Troubleshoot common edge cases like corrupted checkpoints and scheduler resets.
These skills are essential for any serious LLM fine-tuning project, saving you time, money, and frustration.
Next in your learning path, you'll explore evaluating your fine-tuned model. You'll learn how to set up proper evaluation metrics, compare different checkpoints, and decide when your model is ready for deployment. With robust checkpointing under your belt, you'll be able to iterate confidently through multiple training cycles, always knowing you can recover from any interruption.
Practice recap
As a quick exercise, modify a simple fine-tuning script to save a checkpoint every 10 steps using the full-state method. After 20 steps, manually kill the process and try to resume from the last checkpoint. Confirm that the loss curve continues without a spike and that the learning rate does not reset. For extra credit, intentionally corrupt a checkpoint file and test your fallback strategy to the 'best' checkpoint.
Common mistakes
- Saving only model weights and forgetting optimizer/scheduler state — you lose the training dynamics and effectively start a new run, not a true resume.
- Not using atomic file writes (e.g., saving directly to the final path) — a crash during save can corrupt the checkpoint, causing load failures.
- Forgetting to restore the data loader state — you'll repeat data from the beginning, leading to overfitting and wasted steps.
- Ignoring RNG states — without restoring them, the training may not be reproducible, and subtle inconsistencies can appear.
- Loading a checkpoint onto GPU directly and hitting OOM when you have a large model — always load to CPU first and move later.
Variations
- Use Hugging Face
Trainerwithresume_from_checkpoint=Trueto leverage built-in checkpointing and resuming logic. - Employ PyTorch Lightning's
ModelCheckpointcallback for automatic checkpointing and simpletrainer.fit(ckpt_path=...)resume. - In distributed training, use
torch.distributedto save and load sharded checkpoints across GPUs to avoid OOM on a single device.
Real-world use cases
- Long-running fine-tuning of a 7B parameter LLM on a single A100 GPU where a cloud preemption could cost 12+ hours of compute; safe resume ensures no lost progress.
- Fine-tuning a model on a cluster of spot instances for a sentiment analysis task — unexpected terminations mid-training are routinely handled by automated resume scripts.
- A research team fine-tuning a domain-specific model (e.g., legal documents) over several days, relying on checkpointing to iterate through multiple experiments without restarting from scratch.
Key takeaways
- A checkpoint must include model weights, optimizer state, scheduler state, global step, RNG states, and data loader position.
- Use atomic writes (save to temp, then rename) to prevent corrupted checkpoints from crashes.
- Always save at least the latest and best checkpoints to hedge against corruption.
- Resume training by loading all state components and continuing the loop from the saved step, not from zero.
- Test your resume capability by deliberately interrupting a short training run and verifying the loss curve continues smoothly.
- For high-level APIs, use built-in resume features (e.g.,
TrainerorLightning) but understand their limitations.