datarekha

Activation Checkpointing — Trading Compute for GPU Memory

Understand what training VRAM contains, why activations grow with batch and sequence length, and how PyTorch and Transformers recompute selected forward regions during backward to fit larger models.

11 min read Advanced Deep Learning Lesson 16 of 28

What you'll learn

  • Which training tensors consume VRAM and which of them checkpointing actually changes
  • How saved boundaries let backward recompute discarded activations
  • Why the method reduces memory but adds forward computation
  • How checkpointing differs from gradient accumulation, mixed precision, LoRA and FSDP
  • How to enable it safely in PyTorch and Hugging Face Transformers

Before you start

A 7-billion-parameter model can fit its quantized weights on a GPU and still run out of memory the moment training begins. Inference mainly needs weights and a working set such as the KV cache. Training must also retain information needed to differentiate the loss.

The important first question is therefore not “How large are the weights?” It is “Which category is filling memory?”

Tryactivation memory planner

Choose how much of the forward pass to remember

This is a relative activation-memory model, not a VRAM promise. Sequence length and batch size grow every bar; checkpoint spacing decides how many layer boundaries survive until backward.

save every block100%
selected plan28%
135791113151820222426283032
9 boundaries kept. Backward recomputes roughly 24 block outputs instead of reading them from memory. Parameters, gradients and optimizer state are unchanged—only saved activations move.

The training-memory ledger

Memory categoryWhy it existsDoes activation checkpointing reduce it?
ParametersThe model weights used by the forward passNo
GradientsOne derivative per trainable parameterNo
Optimizer stateAdam moments and often a higher-precision parameter copyNo
Saved activationsIntermediate tensors backward needs for the chain ruleYes
Temporary workspaceKernel buffers, allocator fragmentation and communicationNot directly

Activation memory depends on architecture, dtype and implementation, but it usually grows with micro-batch size × sequence length × hidden width × depth. For long-context fine-tuning, those saved tensors can rival or exceed the model state. This is why enabling checkpointing may rescue an out-of-memory run at 8,192 tokens while barely helping a short-sequence run dominated by Adam state.

Checkpoint boundaries: keep a few, recompute the restblock1block2block3block4x0x1x2x3lossgreen = saved boundary (x0, x2) · dashed = discarded (x1, x3), recomputed in backwardbackward replays each region’s forward from its saved boundary to rebuild what it dropped

The picture above is a relative sketch. Real VRAM must be measured because attention implementations, fused kernels, tensor parallelism, padding, allocator behavior and framework versions change the constant factors.

What normal backward expects

Suppose a four-block network computes:

x0 → block1 → x1 → block2 → x2 → block3 → x3 → block4 → loss

Without checkpointing, autograd saves tensors such as x1, x2 and x3. During backward, each block uses the saved inputs or outputs to calculate local derivatives. Keeping them is fast: backward reads rather than re-running the forward block. It is also memory hungry because early activations must survive until backward finally reaches them.

With block 1 and block 3 selected as checkpoint boundaries, the system keeps the boundary inputs and discards intermediates inside each region. When backward reaches the second region, it replays that region’s forward operations from the saved boundary, reconstructs the tensors, uses them for gradients and releases them again.

The computation graph is not removed. It is evaluated twice in selected regions: once during the original forward pass and once as backward requests the missing values.

The trade-off is adjustable

Checkpoint every block and you save many boundaries but recompute short regions. Checkpoint only a few large regions and you retain fewer boundaries but replay more computation. Selective checkpointing can keep expensive operations while recomputing cheap ones.

PyTorch’s current API also distinguishes reentrant and non-reentrant behavior; the documentation recommends passing use_reentrant explicitly. The non-reentrant implementation can stop recomputation once the needed tensors have been reconstructed and supports more autograd patterns.

There is no universal “20% slower” law. Hugging Face documents roughly 20% as a useful expectation for common Transformer training, but actual overhead depends on how much is checkpointed, the forward/backward ratio, kernel efficiency, communication overlap and whether memory savings permit a better micro-batch. Benchmark tokens per second, not just step time.

PyTorch: checkpoint a pure region

import torch
from torch.utils.checkpoint import checkpoint

class CheckpointedBlock(torch.nn.Module):
    def __init__(self, block):
        super().__init__()
        self.block = block

    def forward(self, hidden_states):
        return checkpoint(
            self.block,
            hidden_states,
            use_reentrant=False,
        )

The checkpointed function may run again during backward. Treat it like a pure function of its inputs. If it mutates global state, reads changing external state, or follows a different branch during recomputation, gradients may be wrong or PyTorch may raise an error.

Dropout needs special treatment because recomputation should reproduce the same mask as the original forward pass. PyTorch preserves and restores random-number generator state by default. That preservation has overhead, but disabling it is only safe when equivalent deterministic behavior is not required. Moving tensors to an unanticipated new device inside a checkpointed function can also break RNG equivalence.

Transformers: one flag, same mechanism

from transformers import TrainingArguments

args = TrainingArguments(
    output_dir="runs/demo",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    bf16=True,
    gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
)

For supported models, model.gradient_checkpointing_enable() exposes the same idea directly. A library flag is convenient, but verify it actually covers the expensive model blocks and measure peak allocated/reserved CUDA memory around a representative training step.

Do not confuse five different memory tools

TechniquePrimary memory targetMain trade-off
Activation checkpointingSaved forward activationsRecompute selected forward work
Gradient accumulationPer-micro-batch activationsMore micro-steps per optimizer update
Mixed precisionTensor representation and kernel throughputNumerical behavior; some FP32 state remains
LoRA / QLoRATrainable parameters, gradients and optimizer stateAdapt low-rank weights rather than all weights
FSDP / ZeROModel state per GPUCommunication and distributed complexity

These methods compose. A QLoRA run may use quantized frozen base weights, low-rank trainable adapters, small micro-batches, gradient accumulation and activation checkpointing together. Each line solves a different part of the ledger.

Safe measurement lab

The proxy below models only activation units — saving every k-th boundary exchanges retained tensors for recomputation:

import math

layers = 32
checkpoint_every = 4
activation_mb_per_layer = 180

normal_mb = layers * activation_mb_per_layer
saved_boundaries = math.ceil(layers / checkpoint_every) + 1
checkpointed_mb = saved_boundaries * activation_mb_per_layer
recomputed_layers = layers - math.ceil(layers / checkpoint_every)

print(f"normal saved activations: {normal_mb:,} MB")
print(f"checkpointed proxy:       {checkpointed_mb:,} MB")
print(f"relative activation use:  {checkpointed_mb / normal_mb:.1%}")
print(f"block outputs replayed:   about {recomputed_layers}")
print("Weights, gradients and Adam state are unchanged.")
normal saved activations: 5,760 MB
checkpointed proxy:       1,620 MB
relative activation use:  28.1%
block outputs replayed:   about 24
Weights, gradients and Adam state are unchanged.

Production checklist

  • Record peak VRAM and tokens/second with and without checkpointing on the same batch, sequence distribution and warm-up period.
  • Checkpoint repeated, memory-heavy blocks; avoid tiny regions whose scheduling and RNG overhead outweigh memory saved.
  • Pass use_reentrant deliberately and follow the PyTorch version’s current recommendation.
  • Keep checkpointed regions deterministic with respect to their inputs.
  • Test dropout and multi-device code; do not casually disable RNG preservation.
  • Combine with accumulation only after distinguishing micro-batch size from effective batch size.
  • Validate gradients or loss curves after changing checkpoint boundaries.
  • Measure reserved and allocated memory; fragmentation can look like activation pressure but needs allocator or shape fixes instead.

In one breath

  • Training VRAM holds parameters, gradients, optimizer state, and saved activations — checkpointing only shrinks the last one.
  • Activation memory grows with micro-batch × sequence length × hidden width × depth, so it dominates long-context fine-tuning.
  • Checkpointing keeps a few boundary activations and discards the intermediates, then recomputes them during backward — trading extra forward compute for memory.
  • It composes with mixed precision, gradient accumulation, LoRA/QLoRA, and FSDP — each targets a different line of the memory ledger.
  • It can’t help if parameters + gradients + optimizer state alone exceed the GPU; diagnose the category first, keep checkpointed regions pure and deterministic, and benchmark tokens/second.

Quick check

Quick check

0/3
Q1Which training-memory category does activation checkpointing primarily reduce?
Q2Why can training become slower after checkpointing?
Q3A model's parameters and optimizer state alone exceed one GPU. Is activation checkpointing sufficient?

Sources and next step

Next, learn when model state—not activations—is the bottleneck in Multi-GPU training with DDP and FSDP.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What correctness issues should you consider when using activation checkpointing?

A checkpointed region is replayed during backward, so it should behave consistently from its inputs. Side effects, changing global state, divergent control flow, unexpected device movement, and random operations can break equivalence. PyTorch preserves RNG state for operations such as dropout by default; choose reentrant behavior explicitly and test gradients after changing boundaries.

What is the difference between activation checkpointing and gradient accumulation?

Activation checkpointing reduces saved activations inside one micro-batch by recomputing forward regions during backward. Gradient accumulation reduces the micro-batch processed at once and sums gradients across several micro-steps before one optimizer update, preserving a larger effective batch. They target different memory pressure and can be combined.

How does gradient checkpointing reduce GPU memory, and what is the trade-off?

Gradient checkpointing—more precisely activation checkpointing—keeps selected forward boundaries instead of every intermediate activation. During backward it re-runs checkpointed forward regions to reconstruct discarded tensors. It reduces activation memory but leaves parameters, gradients and optimizer state unchanged, trading extra computation for lower peak VRAM.

When the KV cache doesn't fit in GPU VRAM, what are your options?

The KV cache is working memory — it's re-read to generate every token — so it has to stay fast. When VRAM fills, you offload the least-active sessions down a memory hierarchy: GPU VRAM (active, ~3 TB/s), CPU RAM over PCIe (idle, ~50 GB/s), local SSD (long-idle), and networked storage (cold/durable only, never live decode). Idle sessions are parked lower and promoted back to VRAM on activity. The alternative is to drop the cache and recompute the prefill when the session returns; for long prompts, offloading and reloading usually beats recomputing attention over thousands of tokens.

Related lessons

Explore further

Skip to content