Training and optimization
Computational Graphs and the Gradient Signal
Trace how forward operations, parameter sharing, branching, stopping, and numerical choices shape the gradients received by each parameter.
By the end you can
- Explain reverse-mode differentiation as responsibility assignment through a graph
- Trace how branches, shared parameters, and detached values alter accumulated gradients
- Distinguish a correct derivative from a useful learning signal
- Use graph-level tests to localize broken gradient flow
Visual
A gradient follows executed dependencies, not intentions
This graph shows why a parameter receives only the signal connected to it. Cut one edge and the rest of the program still runs. It still reports a falling loss, and it still trains nothing that mattered.
SimSiam is the measured case. Chen and He held the architecture and every hyper-parameter fixed and removed one stop-gradient. ImageNet linear-evaluation top-1 accuracy moved from 67.7±0.1% to 0.1%, chance level, while the training loss fell to −1, its minimum possible value. Nothing in the optimizer changed. Only the graph did.
- 1
Inputs and state
A batch, parameters, masks, and mode flags enter the forward program.
- 2
Intermediate operations
Layers, normalizations, branches, and losses create dependent values.
- 3
Scalar objective
Reductions combine selected outputs into the value being differentiated.
- 4
Reverse accumulation
Local derivatives are multiplied and added along graph paths.
- 5
Parameter gradients
Each trainable tensor receives the sum of connected contributions.
Comparison
Zero, missing, and tiny gradients mean different things
A debugger should preserve these distinctions, because the three columns have different causes and different repairs. The rightmost one is misread most often, and two published measurements show what it actually looks like.
Start with precision. A histogram of activation gradients, collected across all layers while the Multibox SSD detector trained in FP32, found 67% of the values already at zero. Much of what remained sat in the band [2^-27, 2^-24), below anything half precision can represent. Micikevicius and his co-authors, at ICLR 2018, state the consequence in one sentence: “Note that much of the FP16 representable range was left unused, while many values were below the minimum representable range and became zeros.” The same network diverged in half precision with no loss scaling. Multiplying the loss by 8 before backpropagation gave 77.1% mAP against a 76.9% FP32 baseline. PyTorch's Automatic Mixed Precision documentation states the mechanism in one clause: such values “flush to zero ("underflow"), so the update for the corresponding parameters will be lost”. A tensor of exact zeros in that regime is not a saturated derivative. It is a number that did not fit.
Depth compounds the same loss without any help from precision. He and his co-authors trained a 30-layer model — 27 conv layers and 3 fc layers. At a fixed initialization std of 0.01 they computed that the gradient std propagated from conv10 back to conv2 is 1/(1.7 × 10^4) of the value their derivation prescribes. What that costs is in their ICCV 2015 paper: “Our initialization is able to make the extremely deep model converge. On the contrary, the “Xavier” method completely stalls the learning, and the gradients are diminishing as monitored in the experiments.” Eight layers of attenuation is the distance between a model that trains and one that never starts. On a norm plot it appears only as the third column of this comparison: small, finite, easy to dismiss.
No gradient object
The parameter was not connected to the differentiated objective.
- Cause: unused or detached path
- Signal: gradient is absent
- Question: should this parameter participate?
- Check: graph and requires-grad state
Exact zero gradient
The path exists but local derivatives or cancellation yield zero.
- Cause: saturation, masks, symmetry
- Signal: tensor of zeros
- Question: is zero expected here?
- Check: activations and local Jacobians
Very small gradient
The signal survives but may be attenuated or underflowed.
- Cause: depth, scale, precision
- Signal: tiny finite norms
- Question: can updates move parameters?
- Check: layerwise norm ratios
Reverse mode asks how one scalar changes with many parameters
Training usually differentiates one reduced objective with respect to millions of parameters. Reverse-mode automatic differentiation reuses intermediate dependencies efficiently for that direction. The system computes derivatives of the executed program. A conditional branch, detached tensor, in-place mutation, or train/eval mode can therefore change the graph and its gradients.
The efficiency has a number on it. A survey by Baydin and co-authors, in the Journal of Machine Learning Research, states that for f : Rn → Rm “the time it takes to calculate the m×n Jacobian by the forward mode is n c ops(f), whereas the same computation can be done via reverse mode in m c ops(f), where c is a constant guaranteed to be c < 6 and typically c ∼ [2, 3]”. A single scalar loss makes m equal to one. So the gradient of a billion parameters costs a few forward passes, and nothing in that bound depends on n. The cheapness is the reason nobody inspects the graph until it breaks.
Autodiff differentiates what ran, not what the programmer meant to run.
Example
Four graph bugs that look like optimizer problems
These failures often survive shape checks and forward unit tests. Four of the five below have been isolated in published experiments where the graph was the only thing that changed.
- Detached feature: An auxiliary head consumes a stopped-gradient representation, so its loss never trains the shared encoder. Chen and He ran the controlled version, and their CVPR 2021 report of it is one sentence: “Solely removing stop-gradient, the accuracy becomes 0.1%, which is the chance-level guess in ImageNet.” With the stop-gradient in place the same model reaches 67.7±0.1% top-1. Without it the training loss collapses to −1, its minimum possible value — exactly the reading a loss curve cannot distinguish from success. BYOL shows the same shape independently: in its Table 5, variants lacking the target network or the predictor score 0.3%, 0.2% and 0.1% against 72.5% for BYOL itself. Detachment can be load-bearing as well as accidental. The graph does not say which you intended.
- Unused branch: A routing condition prevents one expert from receiving examples, leaving its parameters with zero gradients. The forward pass is valid, the loss is finite, and the parameters are simply never addressed — a coverage fact about the batch, not a statement about the optimum.
- Shared parameter: The same embedding table appears in two branches, so its gradient is the sum of both responsibilities. Press and Wolf tied a language model's input and output embeddings into a single matrix and analysed that summed update rule directly. Their abstract reads: “We analyze the resulting update rules and show that the tied embedding evolves in a more similar way to the output embedding than to the input embedding in the untied model.” On Penn Treebank, with a Recurrent Highway Network and Bayesian dropout, the tied model used 24M parameters instead of 32M and reached a test perplexity of 66.0 against 68.5 untied. Inan and co-authors derived the same tying concurrently and independently at ICLR 2017. The summed gradient is not a hazard to be avoided; it is a design decision to be made deliberately.
- Non-differentiable decision: An argmax selects a hard path, removing useful local information unless a different estimator is designed. Jang and co-authors designed one at ICLR 2017: a Gumbel-Softmax relaxation of the categorical sample, with a straight-through variant that discretizes with argmax in the forward pass while differentiating the continuous approximation. On semi-supervised binarized MNIST (100 labeled, 50,000 unlabeled examples) ST Gumbel-Softmax reached 93.6% unlabeled test accuracy and plain Gumbel-Softmax 92.4%, against 92.6% for marginalization. It is also faster: “Training the model with the Gumbel-Softmax estimator is 2× as fast for 10 classes and 9.9× as fast for 100 classes.” PyTorch's torch.nn.functional.gumbel_softmax writes the straight-through trick as y_hard - y_soft.detach() + y_soft — the same detach that silently severs a graph, used on purpose to keep one alive.
- Mode mismatch: Dropout or normalization behaves differently during a diagnostic pass, changing both activations and gradient statistics. This is designed behaviour, not a framework quirk. Ioffe and Szegedy said so in 2015, when they introduced Batch Normalization: “The normalization of activations that depends on the mini-batch allows efficient training, but is neither necessary nor desirable during inference; we want the output to depend only on the input, deterministically.” PyTorch's torch.nn.BatchNorm2d implements that switch with running estimates, kept at a default momentum of 0.1 and a default eps of 1e-5, used for normalization during evaluation. A pass run in the wrong mode is differentiating a different function.
Key idea
A mathematically correct gradient can still teach the wrong behavior
Autodiff may perfectly compute the derivative of a leaked, badly reduced, or poorly chosen objective. A correct gradient is therefore necessary but not sufficient.
The model may also sit in a region where useful directions are tiny, noisy, or dominated by another term. Debug the objective and data alongside the graph. PyTorch ships a finite-difference check for exactly this boundary: torch.autograd.gradcheck compares “gradients computed via small finite differences against analytical gradients”, with defaults of eps=1e-6, atol=1e-5 and rtol=1e-3. A custom layer that passes gradcheck has a correct derivative to within those tolerances. It says nothing about whether the objective is the right one. The SimSiam ablation is the proof: a model differentiating a perfectly correct loss all the way down to −1 while learning nothing at all.
Derivative accuracy cannot validate the meaning of the objective.
Steps
Test gradient flow before a long run
Use small deterministic cases that show clearly which parameter answers for which output. Step 5 is the one that gets skipped, and Batch Normalization is the reason not to skip it. Training normalizes with mini-batch statistics, inference with population statistics. The two modes are two functions, not one function in different clothes. Ioffe and Szegedy reported that the technique matched the prior state of the art with 14 times fewer training steps, and that an ensemble reached 4.82% top-5 test error. That much performance riding on a mode flag means a diagnostic pass taken in the wrong mode is not a noisy measurement of your model. It is a clean measurement of a different one.
1. Overfit a tiny batch
Confirm that the complete graph can reduce loss on a handful of examples.
2. Inspect parameter coverage
List trainable tensors with missing, zero, finite, or invalid gradients.
3. Compare finite differences
Check selected scalar parameters or small tensors away from non-smooth points.
4. Isolate branches
Activate one loss or route at a time and inspect expected gradient destinations.
5. Test mode changes
Compare training and evaluation behavior where normalization, dropout, or caching matters.
Tracing responsibility through a supply chain
A defective component can be traced backward through suppliers and assembly stages. A shared supplier can contribute to several products, while an unused supplier receives no responsibility.
An audit like that ends in fault. Backpropagation ends in a number for how much a small change in one parameter would have moved the loss. That is neither blame nor proof of cause. What it does explain is branching, accumulation, and disconnected paths — the tied embedding that answers for two uses at once, and the detached encoder that answers for none.
Backpropagation assigns local sensitivity along recorded dependencies.
Layerwise ratios reveal more than a global norm
A single global gradient norm can hide dead layers and one dominating tensor. Track distributions by module, parameter type, and task contribution.
Two per-layer statistics can disagree about the same network. Glorot and Bengio monitored activations and gradients layer by layer after standard initialization. The variance of the back-propagated gradients shrank as it moved toward the input. The variance of the weight gradients stayed roughly constant across layers. They wrote: “What was initially really surprising is that even when the back-propagated gradients become smaller (standard initialization), the variance of the weights gradients is roughly constant across layers, as shown on Figure 8.” The caption of that figure puts it flatly: “Even though with standard initialization the back-propagated gradients get smaller, the weight gradients do not!” Two honest diagnostics, one network, opposite verdicts. A single averaged number would have reported neither.
The graph records operations, not intent
Useful diagnostics include gradient-to-parameter norm ratios, update-to-parameter ratios, activation statistics, and the fraction of missing or non-finite values. Read them per module. The ratio that identifies a stalled model in that 30-layer network is the one taken between two named conv layers, not the one taken over the whole net.
One default catches nearly everyone once. PyTorch's backward accumulates gradients in the leaves rather than replacing them. Its documentation for torch.Tensor.backward warns that you “might need to zero .grad attributes or set them to None before calling it”. A forgotten zero_grad turns the training loop into a running sum. The curve then looks like a learning-rate problem, and it is not one. That is the pattern this whole lesson repeats: an execution fact about the graph, wearing the costume of an optimizer failure.
Gradient observability should follow the structure of the model, not flatten it into one number.
Key takeaways
- Reverse-mode differentiation propagates local sensitivities through the operations and branches that actually executed, and Baydin and co-authors bound the cost of a scalar objective at c < 6, typically c ∼ [2, 3], function evaluations regardless of parameter count.
- A single stopped-gradient edge decides what trains: Chen and He's SimSiam goes from 67.7±0.1% to 0.1% ImageNet top-1 when only the stop-gradient is removed, while its loss falls to the minimum value of −1.
- Shared parameters accumulate contributions from every connected use — Press and Wolf's tied input and output embedding ran on 24M parameters instead of 32M and reached 66.0 test perplexity against 68.5 untied.
- Missing, exact-zero, tiny, and invalid gradients are different states: 67% of Multibox SSD activation gradients were already zero in the FP32 histogram, and scaling the loss by 8 recovered 77.1% mAP against a 76.9% baseline.
- A correct derivative can still optimize a leaked or misaligned objective; gradcheck's eps=1e-6, atol=1e-5 and rtol=1e-3 certify the derivative and nothing about the task.
- Layerwise statistics beat a global norm: Glorot and Bengio found back-propagated gradient variance shrinking toward the input while weight-gradient variance stayed roughly constant in the very same network.