Cheat sheets

Training Memory, Accumulation and Checkpoints

Fix NaN steps, memory limits, accumulation scale and broken resumes. Clip, AMP, recompute, atomic checkpoints.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

Training symptoms#

SymptomCauseFix
Loss goes vertical after one batchGradient norm spike, no clipclip_grad_norm_ at max_norm = 1.0
Every weight is NaN after a stepFP16 overflow gives Inf, then NaNtorch.amp.GradScaler skips the step
Scaling factor never changesNo scaler.update() on the skip pathCall scaler.update() every step
Clip acts on scaled gradientsClip runs before unscaleCall scaler.unscale_(optimizer) first
Hundreds of skips per epochModel is past the FP16 rangeAlert on a skip rate above 5 percent
Optimizer step 16 times too bigMicro-batch loss not dividedDivide the loss by accum_steps
Resumed loss curve differsNo RNG state in the checkpointSave capture_rng_state() output
Activations do not fit in memoryBackprop keeps every activationtorch.utils.checkpoint.checkpoint(fn, input)

Checkpoint load failures#

SymptomCauseFix
world_size mismatchResume on a different world sizeResume on the same world size
sha256 mismatch on rankShard truncated mid-flushWrite to .tmp, fsync, then rename
shard file missingFewer rank files than manifest shardsEnumerate shards, verify each exists
unknown schemaPayload schema does not start with ckptBranch migrations on the schema string
Loss goes to NaN 100 steps after resumePartial shard behind a present manifestAtomic write plus sha256 on load
Disk fills mid-runNo rotationrotate_checkpoints keeps the last K

Safe AMP step#

optimizer.zero_grad(set_to_none=True)grads to None
with torch.amp.autocast(device_type="cuda"):FP16 forward
loss = loss_fn(model(inputs), targets)
if not torch.isfinite(loss).all().item():check before backward
return skip("non_finite_loss")no scaler.update here
scaler.scale(loss).backward()scaled backward
scaler.unscale_(optimizer)unscale before the clip
if has_non_finite_grad(model.parameters()):
scaler.update()halves the factor
return skip("non_finite_grad")
pre, post = clip_global_l2_norm(params, max_norm)
scaler.step(optimizer)
scaler.update()doubles or halves

Accumulate, sync on the last#

accum_steps = len(micro_batches)
zero_grads(model)
for i, (x, y) in enumerate(micro_batches):
is_last = i == accum_steps - 1
if no_sync_until_last and not is_last:
with no_sync_context(model):DDP skips the all-reduce
loss = loss_scaled_for_accum(model(x), y, accum_steps, loss_fn)
loss.backward()grads add into param.grad
else:
loss = loss_scaled_for_accum(model(x), y, accum_steps, loss_fn)
loss.backward()one sync per effective step
optimizer.step()once per effective batch

Sharded atomic save#

sha256sha256resumegpurank 0 stateparam and optimisershardgpurank 1 stateone file per rankstorerank0.bin.tmpwrite, flush, fsyncstorerank1.bin.tmpwrite, flush, fsyncstoremanifest.json.tmpworld_size, step,shardsprocessos.replaceshards first,manifest lastprocessload_shardedsha256 check pershardsha256sha256resumegpurank 0 stateparam and optimisershardgpurank 1 stateone file per rankstorerank0.bin.tmpwrite, flush, fsyncstorerank1.bin.tmpwrite, flush, fsyncstoremanifest.json.tmpworld_size, step,shardsprocessos.replaceshards first, manifestlastprocessload_shardedsha256 check per shard

Tasks in code#

TaskCode
Clip to a global L2 normclip_grad_norm_(parameters, max_norm)
Log the scaling factorscaler.get_scale()
Scale a micro-batch lossloss_scaled_for_accum(logits, target, accum_steps, loss_fn)
Recompute a segment in backwardcheckpoint(fn, input)
Save the full state atomicallysave_checkpoint(model, optimizer, scheduler, state, out_path)
Load an untrusted filetorch.load(path, map_location="cpu", weights_only=True)
Save and load per-rank shardssave_sharded(per_rank_state, dest_dir, step)
Resume and verify every shardload_sharded(src_dir, expected_world_size)

Save and resume mid epoch#

1save_checkpointpacks model, optimizer, scheduler, counters andRNG.2atomic_savewrites a temp file, then calls os.replace.3load_checkpointcalls torch.load with weights_only=True.4restore_rng_staterestores Python, NumPy and torch RNG.5Training loopcontinues from batch_in_epoch. Loss diff under1e-4.

Run the lessons#

CommandWhat it does
python3 code/main.pyLesson 45: 20 steps, rows tagged STEP or SKIP
--num-steps 100Lesson 46: longer sweep for the curve
--accum-grid 1,2,4,8,16Lesson 46: accumulation steps to sweep
--total-steps 24 --interrupt-at 10Lesson 47: resume demo window
python3 code/main.pyLesson 80: 4 shards plus manifest, byte-equal reload
python3 code/main.pyLesson 81: 20 steps, 4 ranks, RESUME VERIFIED

Defaults in the code#

SettingDefaultNote
DEFAULT_MAX_NORM1.0Clip threshold of AmpTrainState
amp_dtype on cputorch.bfloat16torch.float16 on cuda
window1000Steps in rolling_skip_rate
k4Layers per checkpoint segment
attention_fraction0.15Recompute share, selective mode
keep_last5Checkpoints that rotate_checkpoints keeps
CHECKPOINT_SCHEMAckpt.v1Schema string in the payload
CHECKPOINT_STEP10Sharded save step of lesson 81

Memory and cost numbers#

Activations per layer
about 12 * B * L * d floats.
d=8192, L=8192, B=1
about 1.6 GB per layer in BF16. 64 layers is about 103 GB.
Full checkpointing
about 33% more FLOPs. A step is 4 units, not 3.
Selective checkpointing
recompute attention only. About 5% overhead.
sqrt(L) rule
one checkpoint every sqrt(L) layers. 8 checkpoints for L=64.
Effective batch
micro_batch * accum_steps, times the data parallel world size.
ZeRO-1 optimiser memory
12P/N bytes per rank.

Q&A#

Why does the CPU GradScaler do nothing?
CPU autocast uses BF16. It does not need loss scaling.
When does the factor change?
It doubles after clean stretches. It halves on every skip.
Does accumulation save time?
No. Double accum_steps and the wall time per step doubles.
Why weights_only=True?
A .pt file is a pickle. Unpickling runs code. Use torch 2.6 or newer.
Why one file per rank?
Write bandwidth scales. 4 hours on one rank is 4 minutes on 64.

Tips#

  • Match the autocast device to the optimizer device.
  • Raise ValueError, not assert. python -O strips asserts.
  • On a loss jump above 2x, roll back to the previous checkpoint.