Memory Management › The Four Memory Consumers
Language AI HandbookAll chapters Memory Management › The Four Memory ConsumersOne section · Sections in this chapter2 / 10 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
GPU memory during training is divided among four distinct categories, each with different characteristics and different strategies for reduction. The total memory budget must accommodate all four simultaneously, and failing to account for any one of them is a common source of mysterious OOM errors late in training runs.
One useful framing: think of parameter memory, gradient memory, and optimizer state memory as static costs that depend only on model architecture. They exist as soon as the model and optimizer are instantiated and they persist until training ends. Activation memory, by contrast, is a dynamic cost that depends on the current batch: its size, the sequence length, and whether you have enabled techniques to reduce it. The two categories require different strategies for reduction and different tools for debugging.
Parameters
Model parameters are the learned weights stored in the model: embedding matrices, attention projection matrices, feed-forward network weights, layer normalization scales and biases, and output projection weights. These are the values that exist before training begins (initialized randomly or from a checkpoint) and persist throughout the entire training process.
For a transformer with parameters, the parameter memory in bytes depends directly on the numeric precision used:
- In full 32-bit float (FP32): bytes
- In 16-bit half-precision float (FP16 or BF16): bytes
- In 8-bit integer (INT8): bytes
Modern large-scale training almost universally uses BF16 or FP16 for parameters during the forward pass, so a model with 7 billion parameters requires roughly GB just for parameters. A 70B model requires 140 GB. These numbers set a hard floor: you need at least this much memory before accounting for anything else.
The count of parameters in a transformer scales predictably. For a model with:
- Vocabulary size
- Hidden dimension
- Number of layers
- Feedforward intermediate dimension (typically )
- Number of attention heads , with head dimension
The dominant parameter contributions are:
where the first term covers the embedding table, the second covers attention projections and feed-forward weights per layer, and the third covers the final layer norm. The attention projection contribution of comes from four projection matrices , , , , each of shape . The feedforward contribution comes from two linear layers: one expanding from to and one projecting back to .
To make this concrete, for a 7B-parameter model with , , , :
This derivation shows how the bulk of parameters live in the transformer layers themselves. The embedding table is a significant contributor for models with large vocabularies, but for modern decoder-only models the layer weights dominate.
Both BF16 and FP16 use 16 bits, but they partition those bits differently. FP16 allocates 10 bits to the mantissa (fractional precision) and 5 bits to the exponent (range). BF16 allocates only 7 bits to the mantissa but 8 bits to the exponent, matching the range of FP32. This matters because training involves large gradient magnitudes that can overflow FP16's limited range, causing numerical instability. BF16 avoids this overflow problem at the cost of slightly lower precision, making it the preferred choice for training large transformers. When you see a model described as "BF16 training," the parameters, forward pass activations, and gradient accumulation all happen in BF16 except where FP32 is explicitly required for numerical stability.
Gradients
During backpropagation, the training loop computes the gradient of the loss with respect to every parameter. These gradients have exactly the same shape as the parameters themselves, meaning they require an identical amount of memory. If you store parameters in FP16 ( bytes), you need additional memory for the corresponding gradients.
The precision question for gradients is subtler than for parameters. In mixed-precision training, the forward pass runs in FP16 or BF16 to take advantage of fast half-precision matrix multiplication hardware. But accumulating small gradient signals across millions of samples in FP16 is numerically dangerous: the limited mantissa precision causes small gradients to underflow to zero and large ones to overflow. The standard solution is to store and accumulate gradients in FP32, which provides enough dynamic range and precision to keep the update signal intact. This means gradients often cost bytes even when parameters themselves are stored in FP16.
In practice, the mixed-precision pipeline looks like this: during the forward pass, parameters are cast from their FP32 master copies to FP16 for the actual computation. The resulting FP16 activations flow through the network. During the backward pass, gradients are computed in FP16 and then immediately cast to FP32 before accumulation. The optimizer update then operates entirely in FP32. After the update, the updated FP32 parameters are cast back to FP16 for the next forward pass. The FP32 master copy exists to accumulate small updates that would otherwise vanish in FP16.
Gradients exist only during backpropagation. They do not accumulate across batches (unless you are using gradient accumulation, in which case you are intentionally summing them). After the optimizer applies the update and zero_grad() is called, this memory is freed. However, at peak training memory usage, the gradient tensors are fully populated and occupy their full allocation.
Gradient memory can be reduced through gradient compression (sending lower-precision gradients in distributed settings), gradient accumulation (which does not reduce peak memory but allows using smaller batches), and gradient checkpointing (which trades compute for activation memory, not directly for gradient memory). The gradient footprint itself is fundamentally tied to parameter count.
Optimizer States
The optimizer is often the largest single memory consumer, exceeding both parameters and gradients. This surprises many practitioners who focus on model size without accounting for the optimizer.
Adam, the most commonly used optimizer for transformer training, maintains two moving average tensors for every parameter:
- The first moment estimate (exponentially decaying average of gradients)
- The second moment estimate (exponentially decaying average of squared gradients)
The Adam update rule ties these together. Given the gradient at step , Adam computes:
where:
- is the decay rate for the first moment
- is the decay rate for the second moment
- is the learning rate
- is a small constant for numerical stability (typically )
Both and are typically stored in FP32, regardless of parameter precision. For a model with parameters, Adam requires:
This means that for a 7B parameter model, Adam alone requires approximately GB. Combined with FP16 parameters ( GB) and FP32 gradients ( GB), the total before activations is GB for a 7B model trained with Adam in mixed precision.
The reason both moments are stored in FP32 rather than FP16 is numerical stability. The second moment estimate accumulates squared gradient values, and the Adam update divides by . Very small values of in low-precision arithmetic lead to division instability and divergence. The first moment is similarly vulnerable: if the gradient signal is small and underflows to zero in FP16, the optimizer loses the ability to update parameters with slowly changing gradients. FP32 provides enough dynamic range to prevent both failure modes.
Alternative Optimizers
When Adam's bytes is too expensive, several alternatives offer lower memory overhead with varying tradeoffs:
Adafactor approximates the second moment using a factored representation. For a weight matrix of shape , Adam stores a full second moment matrix. Adafactor instead stores a single row vector of size and a column vector of size , whose outer product approximates the full matrix. This reduces memory from to for each matrix. Across the model, optimizer memory drops from to roughly . Adafactor was used to train T5 and many subsequent encoder-decoder models and works well when the effective learning rate schedule is managed carefully.
Lion (Evolved Sign Momentum) uses only the sign of the gradient update, maintaining a single exponential moving average of gradients rather than two. The update rule is:
Lion requires only one FP32 state tensor per parameter, halving optimizer memory from to bytes. Because it applies uniform update magnitude (scaled by learning rate), it is effectively a sign-gradient method and needs a lower learning rate than Adam to achieve comparable results.
SGD with Momentum stores only the velocity buffer ( bytes in FP32), consuming half the memory of Adam. It is rarely used for transformer pretraining because it converges more slowly and is more sensitive to learning rate choices. However, it can be effective for fine-tuning tasks where the model is already near a good solution.
8-bit Adam quantizes optimizer states to 8-bit integers using dynamic quantization. Each block of optimizer state values is stored with a per-block scaling factor. This reduces the bytes of standard Adam to bytes while maintaining similar training dynamics. The bitsandbytes library provides a drop-in replacement that works with most PyTorch training code.
The standard mixed precision training setup (FP16 parameters, FP32 gradients, FP32 Adam states) requires:
This "14x rule" is widely used in practice: multiply the parameter count in billions by 14 to get the minimum GPU memory in gigabytes needed before activations. A 7B model requires about 98 GB, a 13B model requires about 182 GB, and a 70B model requires close to 1 TB. These numbers immediately explain why multi-GPU and multi-node training is mandatory for large language model development.
Activations
Activations are the intermediate tensors computed during the forward pass, stored in memory so that backpropagation can compute gradients. Unlike parameters, gradients, and optimizer states, activation memory scales with model size, batch size, and sequence length. This dynamic behavior makes activation memory the hardest component to predict and the one most often responsible for late-stage OOM crashes.
What Activations Are Stored
To understand why so much memory is needed for activations, it helps to walk through exactly what needs to be stored for a single transformer layer during the backward pass.
Consider a single transformer layer processing an input tensor of shape , where is batch size, is sequence length, and is the hidden dimension. The forward pass computes:
- Layer norm pre-attention: The normalized output of shape must be retained to compute the layer norm gradient.
- Query, Key, Value projections: The tensors , , , each of shape , must be retained to compute projection weight gradients.
- Attention score matrix: The raw scores of shape must be retained for the softmax gradient.
- Attention weights after softmax: The softmax output of shape must be retained for the value gradient.
- Attention output: The attended values of shape must be retained.
- Output projection input: The pre-projection activations for .
- Layer norm pre-FFN: The normalized input to the feedforward block.
- FFN intermediate activations: The output of the first linear layer (before the activation function) of shape .
- FFN activation output: The post-GELU or post-ReLU tensor, shape .
- Layer norm values: The mean and variance used in layer normalization must be stored to compute layer norm gradients.
The attention weight matrices at items 3 and 4 are particularly expensive. Each of the layers stores two attention weight matrices of shape . At FP16 (2 bytes), each matrix costs:
For a 32-layer model with , , :
With two matrices (scores and weights) and 32 layers, the attention-only activation memory is approximately 134 GB. This is where the quadratic term dominates.
The Activation Memory Formula
Summing across all components of a single transformer layer and layers, the activation memory for standard full storage is approximately:
where the factor of 2 bytes per element (FP16) is absorbed into the constants. The first term captures all the linear activations (projections, layer norms, FFN intermediate tensors), and the second term captures the quadratic attention weight matrices. The dependence in the second term means:
- Doubling sequence length quadruples the attention activation cost
- For long sequences, attention activations dominate over all other activation memory
For a 7B parameter GPT-style model with , , , , and :
This activation footprint nearly matches the static memory cost. At longer sequence lengths or larger batch sizes, activations quickly become the dominant consumer.
Gradient Checkpointing
Gradient checkpointing directly addresses the activation memory problem. The key insight is that activations are needed for backpropagation but not for the forward pass itself. If you are willing to recompute them during the backward pass, you can discard them after the forward pass and only keep enough state to perform the recomputation.
In its simplest form, gradient checkpointing works as follows:
- During the forward pass: Do not store activations for backpropagation. Instead, store only the input to each checkpointed segment (typically one transformer layer or a small group of layers).
- During the backward pass: When the backward algorithm needs the activations of a layer, rerun the forward pass for that segment from its stored input. Use the recomputed activations to compute gradients, then discard them again.
The memory trade is: instead of storing all layers' activations simultaneously, you store only one layer's activations at any given time (the one currently being processed in the backward pass). This reduces activation memory from to approximately .
The compute cost is additional forward passes. For each layer, you run the forward computation twice: once during the original forward pass, and once during the backward pass. Across all layers, this means roughly forward passes instead of , an overhead of approximately 33% more compute (since the backward pass itself is roughly as expensive as the forward pass, the total goes from to , which for means about 33% more total compute).
PyTorch implements this through torch.utils.checkpoint.checkpoint(). For HuggingFace models, the convenience method model.gradient_checkpointing_enable() applies checkpointing to every transformer layer. The tradeoff is always worth it when you are activation-memory-limited: 33% more compute to save 10-100x activation memory is a favorable exchange in most training scenarios.
A more granular strategy is selective checkpointing: store activations for computationally cheap operations (like layer norms) but recompute activations for expensive operations (like attention matrices). FlashAttention, which we cover in Part 14, takes this idea further by restructuring the attention computation to never fully materialize the attention weight matrix, saving both memory and bandwidth.