Gradient Clipping in Deep Learning
Explains how gradient clipping prevents training instability by capping gradient magnitudes. Topics include clip by value vs clip by norm strategies.
Language AI HandbookAll chapters Gradient ClippingFull chapter · Sections in this chapter1 / 14 ↓
Reading tools Definitions, links & notes
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
Gradient Clipping
Deep neural networks are powerful function approximators, but training them can be surprisingly fragile. One moment your loss is decreasing steadily, the next it explodes to infinity. The culprit is exploding gradients. When gradients grow too large during backpropagation, weight updates become catastrophically destabilizing, and training collapses. The parameters jump to extreme values, and from those extreme values, the network often never recovers.
Gradient clipping provides a simple but effective solution. By capping gradients at a maximum threshold before applying updates, we prevent the runaway feedback loops that cause explosions. This technique is essential for training recurrent neural networks, transformers, and many other deep architectures. Without it, models with long computational paths would be nearly impossible to train reliably.
Think of gradient clipping as a speed limiter for parameter updates. The loss landscape is the terrain your optimizer traverses, and gradients are the slopes you follow downhill. On most of the terrain, the slopes are gentle and you make steady progress. But occasionally you hit a cliff, a region where the loss function drops precipitously, creating an enormous gradient. Without intervention, you'd launch off that cliff and land somewhere completely random. Clipping prevents this by ensuring you never take more than a controlled step size, even on the steepest terrain.
The technique was popularized in a highly influential 2013 paper by Razvan Pascanu, Tomas Mikolov, and Yoshua Bengio, who provided both the theoretical analysis of why recurrent networks suffer from gradient explosions and the practical prescription for addressing them. Their insight was that the gradient explosion problem, while alarming in its consequences, is fundamentally geometric: gradients point in the right direction but are unreasonably long. The fix, therefore, is not to change the direction but to limit the length.
This chapter covers how to detect exploding gradients, the two main clipping strategies (by value and by global norm), and when to apply each approach. We will implement gradient clipping from scratch, explore the geometry of both methods through visualization, and develop practical guidance for choosing appropriate thresholds through gradient norm monitoring. By the end, you will understand how clipping works mechanically, why it is designed the way it is, and how to use it effectively in your own training pipelines.
The problem of exploding gradients in recurrent networks was understood qualitatively for years, but the 2013 paper "On the difficulty of training recurrent neural networks" by Pascanu, Mikolov, and Bengio gave practitioners both a rigorous mathematical analysis and actionable solutions. The paper showed that gradient explosion is a structural property of deep computational graphs, not a quirk of specific architectures. It demonstrated that gradient clipping, while heuristic, is theoretically motivated: the geometry of the loss landscape for RNNs features narrow, sharply curved valleys where unclipped gradients overshoot dramatically. The paper's recommendation to clip the global gradient norm (rather than individual elements) became the standard practice adopted by virtually every major deep learning framework.
The Exploding Gradient Problem
During backpropagation, gradients flow backward through the network, accumulating contributions from each layer. In deep networks or recurrent architectures, this can create a feedback loop where gradients compound multiplicatively. To understand why, consider what backpropagation computes at each layer: the chain rule tells us to multiply the downstream gradient by the local Jacobian matrix. In a sequence of layers, the gradient must pass through such multiplications before reaching the first layer's parameters. Each multiplication can scale the gradient up or down depending on the magnitude of the Jacobian.
The problem becomes acute in recurrent networks because the same weight matrix is applied at every timestep. This means the gradient must flow backward through potentially hundreds of identical multiplications by the same matrix. Unlike a feedforward network where each layer has its own independent weights, an RNN has a single recurrence matrix whose spectral properties determine whether gradients grow or shrink throughout the entire backpropagation pass.
Exploding gradients occur when gradient magnitudes grow exponentially during backpropagation. This happens when the chain rule produces products of values greater than 1 that compound across many layers, resulting in weight updates so large they destabilize training.
Consider a simple recurrent network processing a sequence of timesteps. At each step, the gradient is multiplied by the weight matrix . If the largest eigenvalue of that matrix exceeds 1, gradients grow exponentially. After multiplications, the gradient magnitude scales as:
where:
- : the initial gradient magnitude at the output layer
- : the gradient magnitude after backpropagating through timesteps
- : the largest eigenvalue of the weight matrix (or its spectral norm)
- : the number of timesteps (or layers) the gradient passes through
With timesteps and , the gradient grows by a factor of . Even this modest 10% amplification per step compounds into a catastrophic explosion. The key insight is that the amplification is multiplicative, not additive. Each step does not add a little bit to the gradient; it multiplies the gradient by a factor, so even small factors above 1.0 lead to exponential growth over long sequences.
Notice that this formulation assumes the eigenvalue is constant across timesteps, which is exactly what happens in recurrent networks where the same weight matrix is applied repeatedly. In feedforward networks, each layer has its own Jacobian, and the product of many different Jacobians can still explode, but it requires a more consistent directional alignment of the growth. Recurrent networks are especially susceptible precisely because the same amplification factor is applied over and over.
PythonIn [3]Expand
# Simulating gradient growth in a recurrent network
timesteps = 100
growth_factor = 1.1 # Gradient multiplier per step
# Track gradient magnitude over time
gradient_magnitudes = [1.0] # Start with unit gradient
for t in range(timesteps):
gradient_magnitudes.append(gradient_magnitudes[-1] * growth_factor)ConsoleOut [4]Expand
Gradient Growth Over Timesteps -------------------------------------------------- Initial gradient magnitude: 1.00 After 10 steps: 2.59 After 50 steps: 117.39 After 100 steps: 13780.61 Growth factor: 13781x
VisualizationsOut [5]Expand


The exponential growth is striking. With just a 10% amplification per step, the gradient grows nearly 14,000 times larger by the end of the sequence. The linear scale plot makes this viscerally clear: the gradient is essentially invisible for most of the sequence, then explodes in the final timesteps. The log scale plot reveals the uniform, relentless nature of the growth: a perfectly straight line on a logarithmic axis, confirming that every step multiplies the gradient by the same factor.
In practice, these explosive gradients lead to NaN losses, parameter values shooting to infinity, and complete training failure. The model's weights get updated by amounts that can be thousands of times larger than the entire weight value itself, pushing parameters into regions of parameter space so extreme that the network cannot recover. If you have ever seen a training loss suddenly jump to NaN or watched a gradient norm histogram spike into millions, you have witnessed a gradient explosion firsthand.
Detecting Gradient Explosions
Before clipping, you need to know when gradients are problematic. The clearest signal is the gradient norm, which measures the overall magnitude of gradients across all parameters. For a model with parameters , each with gradient , the global L2 norm is:
where:
- : the gradient of the loss with respect to parameter (a tensor)
- : the L2 norm of that parameter's gradient
- : the global gradient norm across all parameters
This single scalar summarizes the entire gradient's magnitude, making it easy to monitor and compare across training steps. Think of the gradient norm as a speedometer for your optimizer: normal training keeps the needle in a reasonable range, while gradient explosions send it flying off the scale.
The global norm is also valuable because it aggregates information from all parameters simultaneously. Rather than tracking thousands of individual gradient tensors, you have one number that tells you whether the entire gradient update is within a safe range. When this number spikes dramatically above its usual baseline, that is your signal to investigate, and it is exactly what gradient clipping will automatically handle.
PythonIn [6]Expand
import torch
import torch.nn as nn
# Create a simple network to demonstrate gradient monitoring
model = nn.Sequential(
nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 1)
)
# Compute gradients for a random batch
x = torch.randn(32, 10)
y = torch.randn(32, 1)
loss = nn.MSELoss()(model(x), y)
loss.backward()
def compute_gradient_norm(model):
"""Compute the global L2 norm of all gradients."""
total_norm = 0.0
for param in model.parameters():
if param.grad is not None:
total_norm += param.grad.data.norm(2).item() ** 2
return total_norm**0.5ConsoleOut [7]Expand
Total gradient norm: 1.1497 Per-layer gradient norms: 0.weight: 0.2031 (3.1% of total) 0.bias: 0.0752 (0.4% of total) 2.weight: 0.6288 (29.9% of total) 2.bias: 0.1762 (2.3% of total) 4.weight: 0.7905 (47.3% of total) 4.bias: 0.4730 (16.9% of total)
The global gradient norm for this network is relatively modest, typical for a well-initialized shallow network on random data. The per-layer breakdown shows how each parameter contributes to the total. Notice that the contributions are not evenly distributed: some layers dominate the total norm while others contribute relatively little. In practice, you would monitor this norm over training batches to detect sudden spikes, which signal potential gradient explosions. Many practitioners log the gradient norm to a dashboard like TensorBoard or Weights and Biases and set up alerts for values exceeding a threshold.
What Causes Gradient Explosions?
Several architectural and training choices make gradient explosions more likely. Understanding these root causes helps you anticipate when clipping will be necessary before you see training fail.
The most fundamental cause is the depth of the computational graph. More layers mean more gradient multiplications. Each layer's Jacobian contributes to the product, and if many Jacobians have spectral norms greater than 1, gradients grow exponentially as they backpropagate toward earlier layers. This is purely a consequence of the chain rule: derivatives multiply, and multiplying many numbers greater than 1 together produces large numbers.
Recurrent networks are especially vulnerable because they process sequences by repeatedly applying the same weights. A sequence of 200 tokens through an RNN creates a computational graph 200 layers deep, but with only one set of weights shared across all those layers. The gradient must flow back through all 200 steps, multiplied by the same weight matrix each time. If the spectral radius of that matrix exceeds 1, the gradient explosion is inevitable regardless of how well the network is otherwise designed.
Several additional factors make explosions more likely:
- Large learning rates: Even moderate gradients become destructive updates with high learning rates. The explosion may be in the update magnitude rather than the gradient itself.
- Improper initialization: Weight initialization that starts parameters too large can push networks into explosive regimes from the first forward pass.
- Long sequences: For RNNs and LSTMs, longer sequences create deeper computational graphs, compounding any per-step amplification.
- Sparse, extreme data: Unusual data points with very large feature values can trigger large losses that backpropagate as large gradients throughout the network.
Understanding these causes is important because it reveals that gradient clipping is a preventive measure, not a cure. The underlying instability often persists even with clipping; you are simply preventing it from derailing training. If explosions happen constantly despite clipping, revisiting architecture design, initialization, or learning rates may be necessary.
Gradient Clipping Strategies
Now that we understand why exploding gradients are dangerous, let's examine how to prevent them. The core idea is simple: if gradients become too large, we shrink them before applying the weight update. But "too large" can mean different things, and how we shrink gradients matters for optimization. This leads to two distinct strategies, each with its own geometric interpretation and trade-offs.
The two strategies reflect a fundamental choice in how we define the problem. In machine learning, the gradient is a vector in parameter space, a high-dimensional arrow pointing in the direction of steepest ascent of the loss. When we say a gradient is "too large," we could mean two different things. The distinction between these interpretations is not just mathematical pedantry; it has real consequences for how your model learns.
The Central Question: What Should "Too Large" Mean?
When we say a gradient is "too large," we could mean two different things:
- Individual elements are extreme: Some specific gradient values are huge, even if others are small
- The overall magnitude is extreme: The gradient vector as a whole points too far, even if no single element is unusual
These two interpretations lead to fundamentally different clipping strategies. The first gives us "clip by value," which treats each gradient element independently. The second gives us "clip by global norm," which treats the gradient as a unified vector and scales it uniformly. Understanding this distinction matters, because the choice can affect the magnitude of updates and potentially their direction.
In practice, the choice between these strategies matters most when gradients are uneven across dimensions, which is common in neural networks. Different layers, different weight matrices, different output dimensions: they can all have very different natural gradient magnitudes. A clipping strategy that ignores these differences treats all dimensions equally when they are not, potentially distorting the optimization dynamics in subtle ways.
Clip by Value: Element-wise Constraint
The simplest approach treats each gradient element as an independent quantity to be bounded. If any element exceeds a threshold , we clamp it to that threshold. Elements within bounds remain unchanged.
Think of this geometrically: we are constraining the gradient to lie within a hypercube centered at the origin. In 2D, this is a square; in 3D, a cube; in higher dimensions, a hypercube with sides of length . The constraint is axis-aligned, meaning it treats each dimension independently without regard for how the dimensions relate to each other.
Given a gradient vector and a clipping threshold , clip by value applies the following transformation to each element independently:
where:
- : the -th element of the original gradient vector
- : the clipping threshold, defining the maximum allowed absolute value
- : the resulting element, guaranteed to satisfy
The operation caps positive values at , while floors negative values at . Together, they project each component onto the interval .
The operation is essentially the same as the "hard tanh" or "saturating linear" activation applied to gradients. Values within bounds pass through unchanged. Values outside bounds are snapped to the boundary. There is no interpolation, no scaling: the clipped components simply take the boundary value.
Let's see this in action with a concrete example. We'll create a gradient vector with some moderate values and some extreme outliers, then apply clip by value.
PythonIn [8]Expand
def clip_gradient_by_value(gradients, clip_value):
"""Clip each gradient element to [-clip_value, clip_value]."""
clipped = []
for grad in gradients:
clipped.append(torch.clamp(grad, -clip_value, clip_value))
return clipped
# Example: gradients with some extreme values
example_grads = torch.tensor([-5.0, -1.0, 0.5, 2.0, 10.0, -15.0])
clip_threshold = 2.0
clipped_grads = torch.clamp(example_grads, -clip_threshold, clip_threshold)ConsoleOut [9]Expand
Clip by Value Example -------------------------------------------------- Original gradients: [-5.0, -1.0, 0.5, 2.0, 10.0, -15.0] Clip threshold: ±2.0 Clipped gradients: [-2.0, -1.0, 0.5, 2.0, 2.0, -2.0] Elements clipped: 3/6 Index 0: -5.0 → -2.0 Index 4: 10.0 → 2.0 Index 5: -15.0 → -2.0
Three of the six elements exceeded the threshold and were clamped. The extreme values at indices 0, 4, and 5 are now bounded to ±2.0, while the moderate values at indices 1, 2, and 3 pass through unchanged.
This asymmetric treatment reveals the core limitation of clip by value: it changes the gradient direction. Before clipping, the gradient might have pointed predominantly toward adjusting parameter 4 (with gradient 10.0). After clipping, that dimension is capped at 2.0, the same as dimension 3. The relative importance of different parameters has been distorted. In optimization terms, we are no longer moving in the direction the loss landscape suggested, but in a direction bent by our clipping constraints.
This direction distortion is not always harmful. In situations where gradients become extreme because of a single unusual data point or a temporary numerical instability, removing that extreme element and letting the other gradients guide the update can be reasonable. But as a general optimization strategy, it introduces a systematic bias: parameters with large natural gradients are systematically under-updated relative to parameters with small natural gradients. Over many training steps, this can slow convergence for the parameters that most need large updates.
Clip by Global Norm: Preserving Direction
The direction distortion problem motivates a different approach. Instead of asking "is each element too large?", we ask "is the gradient vector as a whole too long?" If yes, we scale the entire vector down uniformly, preserving all relative proportions.
Geometrically, this constrains the gradient to lie within a hypersphere of radius centered at the origin. In 2D, this is a circle; in 3D, a sphere. Any gradient landing outside this sphere gets projected back onto its surface by scaling, not by truncating individual components.
Why does this preserve direction? Consider a gradient vector with norm when our threshold is . To bring the norm down to 5, we multiply every component by . Each component shrinks by the same factor, so the ratios between components remain unchanged. The vector points in exactly the same direction, just with half the length.
Given a gradient vector and a maximum norm threshold , clip by global norm applies the following transformation:
where:
- : the full gradient vector, treating all parameters as a single concatenated vector
- : the L2 (Euclidean) norm, measuring the vector's total magnitude
- : the maximum allowed norm (clipping threshold)
- : the scaling factor, always less than 1 when clipping occurs
After clipping, the resulting gradient satisfies while maintaining for all pairs of components.
The key insight is that uniform scaling preserves the update direction. If the original gradient suggested parameter A should change twice as much as parameter B, that relationship holds after clipping. We simply take a smaller step in the same direction. This is analogous to reducing your car's speed while keeping the steering wheel in the same position: you slow down, but you continue heading in the same direction. In contrast, clip by value is like randomly turning the steering wheel to avoid exceeding a speed limit on certain axes.
Notice that the formula has a natural threshold behavior: when the gradient norm is already below , the scaling factor exceeds 1, and we do not apply it. The gradient is only modified when it exceeds the threshold, never amplified. This "do nothing when safe, scale when needed" property makes norm clipping a conservative operation that only intervenes when necessary.
Let's implement this and verify that direction is preserved. We'll use two gradient tensors representing different parameters and show that their relative proportions remain unchanged after clipping.
PythonIn [10]Expand
def clip_gradient_by_norm(gradients, max_norm):
"""
Clip gradients by global L2 norm.
If the total norm exceeds max_norm, scale all gradients proportionally.
"""
# Step 1: Compute global norm across all gradients
total_norm = 0.0
for grad in gradients:
total_norm += (grad**2).sum()
total_norm = total_norm**0.5
# Step 2: Compute scaling factor
clip_coef = max_norm / (total_norm + 1e-6) # Add epsilon for stability
# Step 3: Apply uniform scaling if needed
if clip_coef < 1.0:
return (
[grad * clip_coef for grad in gradients],
total_norm.item(),
clip_coef.item(),
)
else:
return gradients, total_norm.item(), 1.0
# Example: two parameter groups with different magnitudes
grad1 = torch.tensor([3.0, 4.0]) # Norm = sqrt(9+16) = 5
grad2 = torch.tensor([6.0, 8.0]) # Norm = sqrt(36+64) = 10
# Global norm = sqrt(3^2 + 4^2 + 6^2 + 8^2) = sqrt(9+16+36+64) = sqrt(125) ≈ 11.18
max_norm = 5.0
clipped, original_norm, scale = clip_gradient_by_norm([grad1, grad2], max_norm)The implementation follows three steps:
- Compute the global norm by summing squared elements across all gradient tensors, then taking the square root
- Calculate the scaling factor as , which will be less than 1 if clipping is needed
- Apply uniform scaling to every gradient tensor if the norm exceeds the threshold
ConsoleOut [11]Expand
Clip by Global Norm Example -------------------------------------------------- Gradient 1: [3.0, 4.0] (norm = 5.00) Gradient 2: [6.0, 8.0] (norm = 10.00) Global norm: 11.18 Max norm threshold: 5.0 Scale factor: 0.4472 Clipped gradient 1: [1.3416407108306885, 1.7888543605804443] Clipped gradient 2: [2.683281421661377, 3.5777087211608887] New global norm: 5.00 Verifying direction is preserved: Ratio grad1[0]/grad2[0] before: 0.5000 Ratio grad1[0]/grad2[0] after: 0.5000
The results confirm the theory. The original global norm of 11.18 exceeded our threshold of 5.0, triggering clipping. Both gradient tensors were scaled by the same factor (approximately 0.45), bringing the global norm to exactly 5.0. Most importantly, the ratio between corresponding elements remains identical: 0.5 before and 0.5 after. The gradient direction is perfectly preserved.
In practice, this is why norm clipping is generally preferred for optimization. When we clip by norm, we are still moving in the direction the loss landscape suggested, just taking a more cautious step. The relative importance of each parameter's update remains unchanged. The optimizer's accumulated knowledge about which parameters should receive larger updates, encoded in the gradient direction, is fully respected.
Comparing the Two Approaches: A Geometric View
The difference between clip by value and clip by norm becomes crystal clear when we visualize them geometrically. Consider a 2D gradient vector, which we can plot as an arrow in a plane. The two clipping methods impose different constraints on where this arrow can point and how long it can be.
Clip by value constrains the gradient to a square (or hypercube in higher dimensions). The boundary is defined by and independently. A gradient in the corner of this square, like with threshold , gets clipped to , which changes its direction subtly. But a gradient like with the same threshold becomes , dramatically shifting its angle.
Clip by norm constrains the gradient to a circle (or hypersphere). The boundary is defined by . Any gradient outside this circle gets scaled toward the origin until it touches the circle's edge, preserving its angle perfectly.
Let's see both methods applied to the same gradient vector:
VisualizationsOut [12]Expand


The visualization crystallizes the main insight. On the left, clip by value creates a box (square) constraint. The original gradient lies outside this box, so its first component is clipped to 3. But the second component was already within bounds and remains unchanged. The result is that the clipped vector points at a different angle than the original, pulling the update direction away from where the loss landscape suggested we should go.
On the right, clip by norm creates a circular constraint. The same gradient with norm 5.0 exceeds our threshold of 3.0, so we scale the entire vector by . Both components shrink proportionally to , and the angle is perfectly preserved. We are still heading in the right direction, just taking a smaller step.
This geometric intuition extends to any number of dimensions. In the parameter spaces of neural networks with millions of dimensions, clip by value constrains gradients to a hypercube while clip by norm constrains them to a hypersphere. The direction preservation property of norm clipping becomes even more valuable in high dimensions, where the corners of a hypercube can point in vastly different directions than the original gradient. In very high dimensions, nearly all of the volume of a hypercube is near its corners, meaning clip by value almost always distorts the gradient direction nontrivially.
Worked Example: Gradient Clipping in a Two-Parameter Network
To solidify the mechanics, let's work through a concrete example with a tiny network where we can track every number. Consider a network with exactly two scalar parameters, and , and a simple quadratic loss. This stripped-down scenario lets us see exactly how each clipping method transforms the gradients and what effect that has on the parameter update.
Suppose after a forward and backward pass, we compute:
The gradient vector is , with global norm:
We will apply both clipping methods with threshold and compare the results.
Clip by value with : Each component is clamped independently to .
The clipped gradient is with norm . Notice what happened: the original gradient had , suggesting that should receive a larger update. After clipping, both parameters receive the same update magnitude of 5.0. The information that needed a bigger adjustment than has been discarded.
Clip by norm with : The entire gradient is scaled by .
The clipped gradient is with norm . The ratio is the same as the original ratio . The direction is perfectly preserved, and the magnitude is exactly at the threshold.
PythonIn [13]Expand
import torch
# Two-parameter gradient example
g = torch.tensor([8.0, 6.0])
tau = 5.0
# Clip by value
g_value = torch.clamp(g, -tau, tau)
# Clip by norm
g_norm_val = g.norm(p=2).item()
if g_norm_val > tau:
g_norm_clipped = g * (tau / g_norm_val)
else:
g_norm_clipped = g.clone()ConsoleOut [14]Expand
Worked Example: Two-Parameter Gradient Clipping ======================================================= Original gradient: [8.0, 6.0] Original norm: 10.0000 Threshold tau: 5.0 Clip by Value: Clipped gradient: [5.0, 5.0] Resulting norm: 7.0711 Ratio g1/g2 before: 1.3333 Ratio g1/g2 after: 1.0000 Direction preserved? No Clip by Norm: Scale factor: 0.5000 Clipped gradient: [4.0, 3.0] Resulting norm: 5.0000 Ratio g1/g2 before: 1.3333 Ratio g1/g2 after: 1.3333 Direction preserved? Yes
The output confirms the theoretical analysis. Clip by value modifies the gradient ratio from 1.33 to 1.00, erasing the relative information. Clip by norm maintains the exact ratio, preserving the directional intent of the original gradient while reducing its magnitude to the threshold.
This example, while small, captures the essential behavior in networks with millions of parameters. The gradient vector in a real network encodes the relative importance of updating every weight, and norm clipping respects that encoding while imposing a safe step size limit.
Implementing Gradient Clipping in PyTorch
PyTorch provides built-in functions for both clipping strategies, and integrating them into a training loop requires only a single additional line of code. The standard workflow places clipping between the backward pass (which computes gradients) and the optimizer step (which applies them).
The position matters: you must clip after calling loss.backward() so that gradients exist, but before calling optimizer.step() so that the modified gradients are what update the parameters. Clipping after the optimizer step would be too late; the damage would already be done.
PythonIn [15]Expand
import torch
import torch.nn as nn
import torch.optim as optim
# Create a model for demonstration
model = nn.Sequential(
nn.Linear(10, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, 1),
)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Training step with gradient clipping
def train_step_with_clipping(model, x, y, optimizer, max_norm=1.0):
"""Perform one training step with gradient clipping."""
optimizer.zero_grad()
# Forward pass
predictions = model(x)
loss = nn.MSELoss()(predictions, y)
# Backward pass
loss.backward()
# Compute gradient norm BEFORE clipping
grad_norm_before = torch.nn.utils.clip_grad_norm_(
model.parameters(), max_norm=float("inf")
)
# Actually clip gradients
grad_norm_after = torch.nn.utils.clip_grad_norm_(
model.parameters(), max_norm=max_norm
)
# Update weights
optimizer.step()
return loss.item(), grad_norm_before.item()
# Simulate training
x = torch.randn(64, 10)
y = torch.randn(64, 1)
loss, grad_norm = train_step_with_clipping(model, x, y, optimizer, max_norm=1.0)ConsoleOut [16]Expand
Training Step with Gradient Clipping -------------------------------------------------- Loss: 0.931612 Gradient norm (before clipping): 0.9401 Max norm threshold: 1.0 No clipping needed (norm within threshold)
The gradient norm determines whether clipping activates. In this case, the norm exceeds 1.0, so all gradients are scaled down proportionally. PyTorch's clip_grad_norm_ function handles this computation automatically, returning the original norm for logging purposes. The underscore suffix indicates it modifies gradients in-place, which is the standard convention in PyTorch for in-place operations.
Notice that we call clip_grad_norm_ twice in the example above: once with max_norm=float('inf') to retrieve the current norm without modifying gradients, and once with our actual threshold to apply clipping. In production code, you would typically combine these into a single call, using the returned value for logging. The PyTorch documentation recommends this pattern because clip_grad_norm_ always returns the pre-clipping norm.
Tracking Clipping Frequency
Monitoring how often gradients get clipped provides insight into training stability. If clipping happens on every batch, your threshold might be too low. If it never happens, you might not need clipping at all. A healthy signal is occasional clipping, triggered by gradient spikes but not routine gradient values.
Clipping frequency also reveals something about the loss landscape. If your model clips on nearly every batch, it suggests the loss landscape is consistently steep, possibly due to aggressive learning rates, poor initialization, or a fundamentally challenging optimization problem. If clipping frequency suddenly increases mid-training, that can indicate the model is entering a difficult region of parameter space, perhaps because it encountered a particularly challenging batch or because the data distribution shifted.
PythonIn [17]Expand
def train_with_clipping_stats(
model, dataloader, optimizer, max_norm=1.0, epochs=1
):
"""Train while tracking gradient clipping statistics."""
clip_counts = []
grad_norms = []
for epoch in range(epochs):
epoch_clips = 0
epoch_norms = []
for x, y in dataloader:
optimizer.zero_grad()
loss = nn.MSELoss()(model(x), y)
loss.backward()
# Get gradient norm before clipping
total_norm = 0.0
for p in model.parameters():
if p.grad is not None:
total_norm += p.grad.data.norm(2).item() ** 2
total_norm = total_norm**0.5
epoch_norms.append(total_norm)
# Check if clipping will occur
if total_norm > max_norm:
epoch_clips += 1
# Apply clipping
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
optimizer.step()
clip_counts.append(epoch_clips)
grad_norms.append(epoch_norms)
return clip_counts, grad_norms
# Create synthetic dataloader
from torch.utils.data import DataLoader, TensorDataset
X = torch.randn(1000, 10)
Y = torch.randn(1000, 1)
dataset = TensorDataset(X, Y)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
# Fresh model
model = nn.Sequential(
nn.Linear(10, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, 1),
)
optimizer = optim.Adam(model.parameters(), lr=0.001)
clip_counts, grad_norms = train_with_clipping_stats(
model, dataloader, optimizer, max_norm=1.0, epochs=3
)ConsoleOut [18]Expand
Gradient Clipping Statistics -------------------------------------------------- Epoch 1: Batches clipped: 17/32 (53.1%) Gradient norm - min: 0.6294, avg: 1.1707, max: 2.8505 Epoch 2: Batches clipped: 14/32 (43.8%) Gradient norm - min: 0.4770, avg: 1.0351, max: 2.0537 Epoch 3: Batches clipped: 10/32 (31.2%) Gradient norm - min: 0.5146, avg: 0.9937, max: 1.9324
The clipping statistics reveal training dynamics. The high initial clipping rate indicates that our threshold of 1.0 is relatively aggressive for this model, catching most gradient updates. As training progresses, gradient norms typically decrease and stabilize, leading to fewer clipping events. A healthy training run typically sees clipping on 5-20% of batches, preventing occasional spikes without constantly dampening gradients. If clipping occurs on nearly every batch, consider raising the threshold.
Visualizing Gradient Norms During Training
Plotting gradient norms over training reveals patterns that inform threshold selection. Let's train a model and visualize the gradient dynamics over hundreds of steps to understand what "normal" looks like and what problematic patterns to watch for.
The gradient norm trace is one of the most informative diagnostic plots available for neural network training. Unlike the loss curve, which can decrease smoothly even when the network is on the edge of instability, the gradient norm trace reveals the moment-to-moment health of optimization. A stable training run shows gradient norms fluctuating within a band, neither systematically growing nor shrinking dramatically. Occasional spikes are normal and expected; a sustained upward trend suggests instability.
VisualizationOut [19]Expand

The gradient norm plot reveals the training dynamics. Early in training, gradients tend to be larger and more variable as the model makes large initial adjustments. As the model converges, gradients stabilize and shrink. The occasional spikes are the dangerous outliers that gradient clipping targets, and the threshold at 2.0 correctly identifies them as the minority of steps where intervention is needed.
In practice, you would generate this plot from real training data rather than a simulation, and you would use it to set your clipping threshold before finalizing your training configuration. A good workflow is to run a short exploratory training session without clipping, plot the gradient norm trace, identify the 90th or 95th percentile, and use that as your threshold. This data-driven approach is more reliable than using a default value like 1.0 without verification.
Choosing the Right Threshold
Selecting an appropriate clipping threshold requires balancing two concerns: clip too aggressively and you slow down learning; clip too loosely and explosions still occur. The threshold is not a hyperparameter that directly affects what the model learns in the way that learning rate or architecture does. Rather, it is a guard rail that prevents catastrophic failures.
The threshold should be high enough to allow the vast majority of gradients through unmodified. Only the outlier gradients, the ones significantly above the typical range, should trigger clipping. If you set the threshold too low, you are effectively capping the learning rate in a non-uniform way, which can distort optimization and slow convergence. The goal is emergency intervention, not routine regulation.
Empirical Guidelines
Start with a threshold based on observed gradient norms during initial training runs:
- Conservative: Set threshold at the 90th percentile of observed gradient norms. This clips only the most extreme values.
- Moderate: Use the 75th percentile. Clips more frequently but still allows most gradients through unchanged.
- Aggressive: Set threshold at the median. Halves most gradient magnitudes, useful for very unstable training.
PythonIn [20]Expand
def recommend_threshold(gradient_norms, percentile=90):
"""Recommend a clipping threshold based on observed gradient norms."""
threshold = np.percentile(gradient_norms, percentile)
return threshold
# Using our simulated gradient norms
thresholds = {
"Conservative (90th)": recommend_threshold(gradient_norms, 90),
"Moderate (75th)": recommend_threshold(gradient_norms, 75),
"Aggressive (50th)": recommend_threshold(gradient_norms, 50),
}ConsoleOut [21]Expand
Recommended Clipping Thresholds -------------------------------------------------- Gradient norm statistics: Min: 0.0135 Max: 4.2679 Mean: 0.7562 Std: 0.5029 Threshold recommendations: Conservative (90th): 1.0466 (would clip 10.0% of batches) Moderate (75th): 0.8660 (would clip 25.0% of batches) Aggressive (50th): 0.6914 (would clip 50.0% of batches)
The conservative threshold at the 90th percentile would clip only the most extreme 10% of gradients. This allows normal training dynamics while preventing outlier spikes. The moderate threshold clips about 25% of batches. This provides more aggressive stabilization. The aggressive option clips half of all gradients, useful only for severely unstable training where stability matters more than convergence speed.
VisualizationOut [22]Expand

Common Threshold Values
Certain threshold values have become standard through empirical practice across many research papers and production systems:
- 1.0: A common default for transformer models. Works well with Adam optimizer and typical learning rates.
- 5.0: More permissive, used when gradients are naturally larger or training is more stable.
- 0.5: Aggressive clipping for very deep or recurrent networks with known stability issues.
The right value depends on your architecture, optimizer, and learning rate. Higher learning rates generally require lower clipping thresholds to prevent large updates. In the original BERT paper, the authors used a max norm of 1.0 throughout training, and this value has propagated into many subsequent transformer implementations as a sensible default. GPT models have similarly adopted values between 1.0 and 5.0 depending on model size and training batch size.
In practice, the threshold also interacts with the batch size. Larger batches produce gradient estimates averaged over more examples, which tends to reduce the variance of gradient magnitudes. If you increase batch size, your gradient norms may decrease on average, potentially allowing a higher threshold. Conversely, if you use a very small batch size (or even single-example updates), expect higher variance in gradient norms and potentially need a lower threshold.
When to Use Gradient Clipping
Gradient clipping is not always necessary. Some architectures and training setups are naturally stable, while others require careful gradient management. Understanding when to apply clipping saves you from adding unnecessary complexity to stable training pipelines while also alerting you to situations where clipping is not optional.
The most reliable indicator that you need clipping is empirical: train without it and watch the gradient norm trace. If norms are consistently bounded and training is stable, clipping may not be needed. If you see frequent spikes or the norms drift upward over time, clipping is worth adding. For architectures like LSTMs and transformers, the community consensus from years of empirical experience is that clipping is essentially always beneficial, so practitioners often add it proactively.
Use Cases Where Clipping Helps
The following scenarios benefit most from gradient clipping:
- Recurrent Neural Networks: LSTMs and GRUs process sequences by repeatedly applying the same weights. Long sequences create deep computational graphs prone to gradient explosion. Clipping is nearly mandatory for RNN training.
- Transformers: Self-attention mechanisms can create gradient paths that amplify through many layers. Most transformer implementations use gradient clipping by default, and major frameworks like Hugging Face's Transformers library apply it automatically.
- Reinforcement Learning: Policy gradients in RL can be extremely noisy and variable. Clipping stabilizes training when rewards vary dramatically, and many RL algorithms (PPO, for instance) include clipping in their core formulation.
- Large Batch Training: Gradient averaging across large batches can occasionally produce extreme values when batches contain outliers.
- Fine-tuning with High Learning Rates: When adapting pre-trained models, initial gradients can be large relative to the already-trained weights, particularly for layers being trained for the first time.
When Clipping May Not Help
In some situations, gradient clipping addresses symptoms rather than causes, and you may need a different intervention:
- Vanishing gradients: Clipping only helps with exploding gradients. If your gradients are too small, clipping does nothing. Techniques like residual connections, layer normalization, and careful initialization address vanishing gradients.
- Fundamentally broken architectures: If gradient explosions happen constantly and early, the architecture may need revision. Clipping is a guard rail, not a fix for poor design. If you are clipping on 100% of batches from the very first step, the root cause is likely initialization or architecture, not occasional instability.
- Well-conditioned shallow networks: Simple feedforward networks with proper initialization often train stably without clipping. Adding clipping to an already stable pipeline adds computation and a hyperparameter without benefit.
Gradient Clipping with Different Optimizers
The interaction between gradient clipping and optimizers matters and is often misunderstood. Adaptive optimizers like Adam already normalize gradients per-parameter using second-moment estimates, which can reduce the severity of gradient explosions. However, this normalization happens after clipping, and it does not eliminate the need for clipping in the presence of true gradient explosions.
Think of it this way: Adam divides each parameter's gradient by the running estimate of its standard deviation, effectively normalizing the update scale per-parameter. This makes Adam less sensitive to poorly-scaled gradients in general. But if the gradient norm grows by a factor of 1000 due to an explosion, Adam's per-parameter normalization damps this effect somewhat but does not prevent it entirely. The explosion is distributed across all parameters, and while Adam reduces the relative impact, the absolute update magnitudes can still be catastrophically large.
PythonIn [23]Expand
# Comparing gradient clipping needs across optimizers
def analyze_optimizer_gradients(optimizer_class, lr, n_steps=100):
"""Analyze gradient behavior with different optimizers."""
model = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 64),
nn.ReLU(),
nn.Linear(64, 1),
)
optimizer = optimizer_class(model.parameters(), lr=lr)
gradient_norms = []
update_norms = []
for step in range(n_steps):
x = torch.randn(32, 10)
y = torch.randn(32, 1)
optimizer.zero_grad()
loss = nn.MSELoss()(model(x), y)
loss.backward()
# Record gradient norm
grad_norm = (
sum(
p.grad.norm().item() ** 2
for p in model.parameters()
if p.grad is not None
)
** 0.5
)
gradient_norms.append(grad_norm)
optimizer.step()
return gradient_norms
sgd_norms = analyze_optimizer_gradients(optim.SGD, lr=0.01)
adam_norms = analyze_optimizer_gradients(optim.Adam, lr=0.001)ConsoleOut [24]Expand
Gradient Norm Comparison Across Optimizers -------------------------------------------------- SGD (lr=0.01): Mean gradient norm: 0.8244 Max gradient norm: 2.0911 Std gradient norm: 0.2990 Coefficient of variation: 0.36 Adam (lr=0.001): Mean gradient norm: 0.8384 Max gradient norm: 2.3709 Std gradient norm: 0.3221 Coefficient of variation: 0.38
Both optimizers show similar gradient norm distributions since we measure gradients before the optimizer transforms them. The coefficient of variation (std/mean) indicates gradient stability. While Adam's adaptive per-parameter scaling helps with the actual updates, it does not change the raw gradient magnitudes that clipping operates on. For very unstable training, combining Adam with gradient clipping provides complementary stability mechanisms: Adam handles routine variation in gradient scale across parameters, while clipping handles the occasional catastrophic spikes that Adam alone cannot tame.
VisualizationsOut [25]Expand


In Practice: Gradient Clipping in Large-Scale Training
The way gradient clipping is deployed in practice at the frontier of modern deep learning reveals much about its importance. Every major language model training framework, from the original BERT to GPT-4 and beyond, applies gradient clipping by default. This is not a coincidence or cargo-cult behavior; it reflects hard-won experience that large transformer models are prone to the occasional gradient spike, particularly early in training when the parameters are far from their converged values.
In the original attention mechanism paper, "Attention Is All You Need," the authors used gradient clipping with a max norm of 1.0 alongside a warmup learning rate schedule. These two techniques work in tandem: the warmup schedule prevents extreme gradients during the critical early phase of training when the model is most sensitive to large updates, while clipping provides a hard safety guarantee throughout training. Most subsequent transformer implementations have adopted this combination.
Distributed training across many GPU or TPU chips introduces additional considerations. When gradients are averaged across devices, the per-device noise is reduced, but all-reduce operations can occasionally produce numerical anomalies that appear as large gradients. Clipping provides a reliable defense against these hardware-level issues as well as the mathematical instabilities we have discussed.
Another practical consideration is gradient accumulation, where you accumulate gradients over several micro-batches before applying an optimizer step. This technique is used to simulate large batch sizes on hardware with limited memory. When using gradient accumulation, clip the gradients after accumulating all micro-batches, not after each individual micro-batch. Clipping after each micro-batch would be inconsistent with the semantics of accumulation and would clip more aggressively than intended.
The following shows the recommended pattern for gradient accumulation with clipping:
PythonIn [26]Expand
def train_with_gradient_accumulation(
model, dataloader, optimizer, max_norm=1.0, accumulation_steps=4
):
"""
Training loop with gradient accumulation and clipping.
Gradients accumulate over multiple micro-batches before clipping and updating.
"""
grad_norms = []
optimizer.zero_grad()
for step, (x, y) in enumerate(dataloader):
# Forward and backward pass for each micro-batch
loss = nn.MSELoss()(model(x), y)
# Scale loss by accumulation steps to keep gradient magnitude consistent
loss = loss / accumulation_steps
loss.backward()
# Apply optimizer step only after accumulating enough micro-batches
if (step + 1) % accumulation_steps == 0:
# Clip AFTER accumulation, not per-micro-batch
total_norm = torch.nn.utils.clip_grad_norm_(
model.parameters(), max_norm
)
grad_norms.append(total_norm.item())
optimizer.step()
optimizer.zero_grad()
return grad_norms
# Re-initialize model for clean demo
model_accum = nn.Sequential(nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 1))
optimizer_accum = optim.Adam(model_accum.parameters(), lr=0.001)
accum_norms = train_with_gradient_accumulation(
model_accum, dataloader, optimizer_accum, max_norm=1.0, accumulation_steps=4
)ConsoleOut [27]Expand
Gradient Accumulation with Clipping -------------------------------------------------- Number of optimizer steps taken: 8 Mean gradient norm at clip point: 0.7082 Max gradient norm at clip point: 1.1775 Percentage of steps clipped: 25.0%
The gradient accumulation pattern shows that clipping is applied after the full simulated batch (four micro-batches in this case) is accumulated. This ensures that the clipping threshold operates on gradients with the same statistical properties as a true large-batch training step.
Limitations and Practical Considerations
Gradient clipping is a stabilization technique, not an optimization improvement. Understanding its limitations helps you use it appropriately and avoid common pitfalls.
The Direction Distortion Trade-off
When using clip by value, we sacrifice gradient direction for bounded magnitude. This trade-off becomes more severe when gradients are highly unbalanced across dimensions. If one parameter has gradients of magnitude 100 while another has gradients of magnitude 0.1, clipping to 1.0 completely dominates the update direction toward the smaller gradient's direction. In such cases, clip by norm is strongly preferred.
Even with norm clipping, there is a subtle issue: clipping changes the effective learning rate. When gradients are clipped by a factor of 10, it is equivalent to temporarily using 1/10th of your learning rate. This can slow convergence during periods of high gradient activity, though it is usually a worthwhile trade-off for stability. In models trained with a carefully tuned learning rate schedule, frequent clipping during certain phases can effectively override the schedule, leading to slower-than-expected convergence in those phases.
Clipping Can Mask Underlying Problems
One of the most important limitations to understand is that gradient clipping is a symptom manager, not a problem solver. If your model experiences constant, severe gradient explosions despite clipping, the underlying cause, whether it is poor initialization, an ill-conditioned architecture, or mismatched learning rates, still exists. Clipping prevents the training run from failing, but it does not fix the root cause. A model that is heavily reliant on clipping throughout training may converge to a worse solution than one that was properly designed from the start.
The right mental model is to think of clipping as an airbag in a car. An airbag saves lives in a crash, but you would not design a car assuming it will crash constantly. The goal is to drive safely without needing the airbag most of the time, while having it available as a last-resort protection for the occasional severe event.
Interaction with Learning Rate Schedules
If you use learning rate warmup or decay, consider how clipping interacts with these schedules. Early in training with low learning rates, gradients might be naturally bounded. As learning rates increase during warmup, gradient clipping becomes more relevant. Some practitioners adjust clipping thresholds alongside learning rate schedules, though this adds complexity and is rarely necessary in practice.
A more important interaction occurs with learning rate decay: as the learning rate decreases late in training, gradient norms typically decrease proportionally. If you set your clipping threshold based on early-training gradient norms, it may be overly loose late in training (not providing any clipping when it might still be beneficial) or it may be calibrated correctly throughout if the decay is smooth.
Monitoring in Production
For production training runs, log gradient norms and clipping events as part of your standard monitoring suite. A sudden increase in clipping frequency can indicate data distribution shift, model instability, or other issues that warrant investigation. Conversely, if clipping never triggers, you might remove it to simplify your training pipeline.
Some practitioners track the ratio of clipped steps to total steps across training as a health metric. A healthy model in steady-state training might clip on 2-5% of steps. If this ratio climbs to 20% or higher, that is a signal worth investigating. Frameworks like PyTorch Lightning and Hugging Face Accelerate provide built-in hooks for gradient norm logging, making it straightforward to add this visibility to your training pipeline.
Key Parameters
When implementing gradient clipping in PyTorch, understanding the key parameters helps you configure clipping effectively for your specific training scenario:
-
max_norm (for
clip_grad_norm_): The maximum allowed L2 norm for the entire gradient vector. Gradients exceeding this threshold are scaled down proportionally. Common values range from 0.5 to 5.0, with 1.0 being a typical default for transformer training. -
clip_value (for
clip_grad_value_): The maximum absolute value for any single gradient element. Elements outside are clamped. Typically set higher than max_norm since it applies element-wise rather than globally. -
norm_type: The type of norm to use for clipping (default: 2 for L2 norm). Can be set to
float('inf')for infinity norm or other positive values for different -norms. -
error_if_nonfinite (PyTorch 1.9+): Whether to raise an error if gradients contain NaN or infinity values. Setting to
Truehelps catch gradient explosions early rather than silently propagating corrupted values.
When selecting thresholds, start with an exploratory training run without clipping to observe your gradient norm distribution. Set the threshold at the 90th percentile for conservative clipping, or lower if training remains unstable.
Summary
Gradient clipping prevents training instability caused by exploding gradients. The technique caps gradient magnitudes before weight updates, keeping optimization on track when backpropagation produces unreasonably large values. It is one of the most widely used stabilization techniques in deep learning, deployed in virtually every large-scale transformer training pipeline.
The key takeaways from this chapter:
- Exploding gradients occur when gradient magnitudes grow exponentially during backpropagation, especially in deep or recurrent architectures. They manifest as NaN losses and training collapse.
- Clip by value constrains each gradient element independently to , where is the clipping threshold. It is simple but can distort gradient direction.
- Clip by global norm rescales the entire gradient vector to have at most norm while preserving relative proportions. This preserves the update direction and is generally preferred.
- PyTorch's
clip_grad_norm_implements global norm clipping and is the standard choice for most applications. - Threshold selection should be based on observed gradient norm distributions. Common values range from 0.5 to 5.0, with 1.0 being a typical default.
- Monitor clipping frequency to calibrate thresholds. Clipping on 5-20% of batches usually indicates a well-tuned threshold.
- Gradient accumulation requires clipping after all micro-batches are accumulated, not per-micro-batch, to maintain consistent semantics.
- Clipping is a safety mechanism, not an optimizer. Consistent heavy clipping signals an underlying issue with architecture or initialization that should be addressed at its root cause.
Gradient clipping is essential for training RNNs, transformers, and other deep architectures. While it does not improve optimization directly, it prevents the catastrophic failures that would otherwise make training impossible. Combined with careful initialization, appropriate learning rate schedules, and normalization techniques, gradient clipping forms part of the standard toolkit for reliable deep learning at scale.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about gradient clipping and preventing exploding gradients.
Gradient Clipping Quiz
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!