PyTorch Cheatsheet

Saving and Loading

Use this PyTorch reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Checkpoints (Training Resume)

# Save
checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'scheduler_state_dict': scheduler.state_dict(),
    'loss': loss,
    'config': {'lr': 1e-3, 'batch_size': 32},
}
torch.save(checkpoint, 'checkpoint.pt')

# Load and resume
checkpoint = torch.load('checkpoint.pt', map_location=device, weights_only=True)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
start_epoch = checkpoint['epoch'] + 1

load_state_dict Options

# strict=True (default): every key must match exactly
model.load_state_dict(state, strict=True)

# strict=False: allow missing or extra keys (e.g., fine-tuning, partial load)
missing, unexpected = model.load_state_dict(state, strict=False)
print('Missing keys:', missing)
print('Unexpected keys:', unexpected)

# assign=False (default): copies data into existing params in-place
# assign=True (PyTorch ≥ 2.1): assigns new tensors — needed for meta device
model.load_state_dict(state, assign=True)

map_location (Cross-Device Loading)

# Load GPU checkpoint on CPU
state = torch.load('model.pt', map_location='cpu')

# Load on specific GPU
state = torch.load('model.pt', map_location='cuda:1')

# Dynamic device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
state = torch.load('model.pt', map_location=device)

# Remap specific devices
state = torch.load('model.pt', map_location={'cuda:0': 'cuda:1'})

torch.save / torch.load Basics

# Save any Python object that pickle can handle
torch.save(obj, 'file.pt')

# Load it back
obj = torch.load('file.pt', weights_only=True)   # tensors/dicts/lists only
obj = torch.load('file.pt', weights_only=False)  # full pickle (trusted source)

# Save multiple tensors in a dict
torch.save({'x': x, 'y': y, 'z': z}, 'tensors.pt')
data = torch.load('tensors.pt', weights_only=True)

# Save to a file-like object (BytesIO, network stream, etc.)
import io
buf = io.BytesIO()
torch.save(model.state_dict(), buf)
buf.seek(0)
state = torch.load(buf, weights_only=True)

state_dict Details

# Inspect the state_dict
sd = model.state_dict()
for name, tensor in sd.items():
    print(name, tensor.shape, tensor.dtype)

# Optimizer state_dict
opt_sd = optimizer.state_dict()
# Contains: 'state' (per-param moments) and 'param_groups' (hyperparams)

# Manually modify a value before loading (e.g., resize an embedding)
sd['embedding.weight'] = new_embedding_weights
model.load_state_dict(sd)

Handling Shape Mismatches (Transfer Learning)

pretrained = torch.load('pretrained.pt', weights_only=True)
model_sd = model.state_dict()

# Filter out keys with mismatched shapes
filtered = {
    k: v for k, v in pretrained.items()
    if k in model_sd and v.shape == model_sd[k].shape
}

model_sd.update(filtered)
model.load_state_dict(model_sd)

Saving with torch.serialization.add_safe_globals

# If your state_dict contains custom classes alongside tensors
import torch.serialization

torch.serialization.add_safe_globals([MyCustomClass])
state = torch.load('checkpoint.pt', weights_only=True)

TorchScript (Serializable + Deployable)

# Method 1: tracing — record operations on example inputs
example_input = torch.rand(1, 3, 224, 224)
traced = torch.jit.trace(model.eval(), example_input)
traced.save('model_traced.pt')

# Method 2: scripting — parse Python source (supports control flow)
scripted = torch.jit.script(model)
scripted.save('model_scripted.pt')

# Load (no class definition needed)
loaded = torch.jit.load('model_traced.pt')
output = loaded(input_tensor)

Trace vs. Script

torch.jit.tracetorch.jit.script
Control flowBaked-in at trace timeFully supported
Dynamic shapesMay failSupported with type hints
Ease of useEasierRequires type annotations
Use caseFixed CNN, Vision modelsRNNs, branches, loops

ONNX Export

# Recommended (PyTorch >= 2.5): dynamo=True — torch.export-based exporter
onnx_program = torch.onnx.export(
    model,
    (example_input,),
    dynamo=True,
    dynamic_shapes={'x': {0: 'batch_size'}},   # optional, torch.export-style
)
onnx_program.save('model.onnx')
# Legacy TorchScript-based exporter (dynamo=False, the old default)
import torch.onnx

torch.onnx.export(
    model,
    args=example_input,           # or tuple of inputs
    f='model.onnx',
    export_params=True,           # include weights
    opset_version=17,             # ONNX opset (use recent stable)
    do_constant_folding=True,
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={
        'input':  {0: 'batch_size'},
        'output': {0: 'batch_size'},
    },
)

# Verify the exported model
import onnx
onnx_model = onnx.load('model.onnx')
onnx.checker.check_model(onnx_model)

torch.export (PyTorch 2.x — Production Export)

import torch.export

# Export with strict symbolic tracing
exported = torch.export.export(model, args=(example_input,))
exported.module()(example_input)   # run exported graph

# Save / load ExportedProgram
torch.export.save(exported, 'exported.pt2')
loaded = torch.export.load('exported.pt2')

DDP / Multi-GPU Checkpointing

# With DDP, save only on rank 0
if dist.get_rank() == 0:
    torch.save(model.module.state_dict(), 'model.pt')   # model.module unwraps DDP

# Load on each rank
model.load_state_dict(torch.load('model.pt', map_location={'cuda:0': f'cuda:{rank}'}))

FSDP Checkpointing

from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import StateDictType, FullStateDictConfig

# Gather full state_dict on rank 0
with FSDP.state_dict_type(
    model,
    StateDictType.FULL_STATE_DICT,
    FullStateDictConfig(offload_to_cpu=True, rank0_only=True),
):
    state = model.state_dict()
    if dist.get_rank() == 0:
        torch.save(state, 'model_fsdp.pt')

Best Practices

# 1. Always include model architecture version in checkpoint
checkpoint = {
    'model_state_dict': model.state_dict(),
    'arch': 'resnet50_v2',
    'pytorch_version': torch.__version__,
}

# 2. Use atomic writes to avoid corruption
import os, tempfile

def safe_save(obj, path):
    dir_ = os.path.dirname(os.path.abspath(path))
    with tempfile.NamedTemporaryFile(dir=dir_, delete=False) as tmp:
        torch.save(obj, tmp.name)
    os.replace(tmp.name, path)   # atomic on POSIX

# 3. Keep the last K checkpoints
import glob
ckpts = sorted(glob.glob('ckpt_epoch_*.pt'), key=os.path.getmtime)
for old in ckpts[:-3]:   # keep last 3
    os.remove(old)

Gotchas

After loading a checkpoint, call model.eval() for inference or model.train() for continued training — load_state_dict does not restore the training/eval mode.

Optimizer state tensors are on the device they were saved from. Use map_location when resuming on a different device and move optimizer state after loading:

for state in optimizer.state.values():
    for k, v in state.items():
        if isinstance(v, torch.Tensor):
            state[k] = v.to(device)

torch.save uses pickle internally — never load untrusted checkpoint files with weights_only=False.