The graph is built by running the program
Every operation on a tensor records itself: what it did, what it was applied to, and how to differentiate itself.
The result is a directed acyclic graph whose nodes are tensors and whose edges are operations, built as a side effect of the forward pass.
Nothing is symbolic. The graph holds the actual values, which is why a derivative comes out as numbers rather than as an expression.
Reverse mode, and why it is the one used
The chain rule can be applied from the inputs forward or from the loss backward, and the two cost wildly different amounts.
- : the number of inputs, which for a network is the parameter count.
- : the number of outputs, which for a loss is one.
Forward mode computes the derivative of everything with respect to one input. Reverse mode computes the derivative of one output with respect to everything.
Training has millions of inputs and a single scalar loss, so reverse mode gets every gradient in one backward pass and forward mode would need millions.
One backward pass, in order
- Seed the output node with a gradient of 1, since the loss with respect to itself is 1.
- Walk the graph in reverse topological order, so no node is visited before its consumers.
- At each node, multiply the incoming gradient by that operation's local derivative.
- Add the result into each input's gradient buffer, rather than overwriting it.
- Free the node once every consumer has been served.
Gradients accumulate because a tensor used in two places contributes through both paths, and the total derivative is their sum.
That accumulation is why frameworks require an explicit zeroing step between batches. Forgetting it silently sums the gradients of every batch so far.
What it costs
The backward pass does roughly twice the arithmetic of the forward one, so a training step costs about three forward passes.
Memory is the sharper constraint. Every intermediate activation stays alive until its gradient is computed, so peak memory scales with depth times batch size.
Gradient checkpointing trades that back: store only some activations and recompute the rest during the backward pass, which is how large models fit at all.
Where it stops
Under a no-grad context no graph is recorded, which is the right mode for inference and saves both the memory and the bookkeeping.
Detaching a tensor cuts the edge behind it, so gradients flow no further back. That is a deliberate tool, and it is also the usual cause of a gradient that mysteriously arrives as zero.
An in-place operation on a tensor the graph still needs corrupts the backward pass, which is why frameworks raise an error rather than return a wrong number.