2.11 — Automatic differentiation: forward vs reverse mode
Goal: Understand why reverse-mode AD is O(forward) for a single scalar output and is the only practical choice for deep learning.
1. Why this session exists
Backprop in 2.5–2.7 was derived for one specific architecture (the MLP). In real frameworks — PyTorch, JAX, TensorFlow — the same chain-rule computation is generated automatically for any computational graph you can write down. This is automatic differentiation (AD), and it comes in two flavours, forward-mode and reverse-mode. Knowing the cost asymmetry between them is the difference between a working training loop and one that's slower than it should be.[1]
2. The computational graph
Every computation is a DAG of primitive operations: matmul, add, sigmoid, sum, log, …. Each node has inputs and outputs (usually for scalars, for batched activations). The full forward pass is one such DAG; the loss is the unique sink with output.
AD evaluates the Jacobian of the sink with respect to the leaves (parameters or inputs) by applying the chain rule along DAG edges. The two modes differ in the order of traversal.
3. Forward mode
Idea. Propagate Jacobian-times-vector products forward through the graph, alongside the values.
Suppose we want for one specific scalar parameter . Define a "tangent" for every intermediate . The tangents propagate by the chain rule:
Initialise and for , sweep forward through the graph alongside the value computation, and read off at the sink.
Cost. One forward sweep per parameter gives one partial derivative. For parameters: .
Good for. Few inputs, many outputs. Computing the Jacobian of an function when .
Bad for. Deep learning. We have parameters and one scalar output. Forward-mode AD would cost forward passes per gradient step. Infeasible.
4. Reverse mode
Idea. Propagate Jacobian-vector products backwards through the graph, from the scalar sink to the leaves.
Define a "cotangent" for every intermediate . The cotangents propagate by the chain rule applied to the reverse-mode adjoint:
Initialise and sweep backward through the graph (in reverse topological order from the sink). The cotangents at the leaves are exactly the partial derivatives of with respect to those leaves.
Cost. One backward sweep gives all parameter gradients at once: — same order as the forward pass.
Good for. One output, many inputs ( parameters and a scalar loss). The only practical choice for deep learning.
The "" claim is essentially the Baur–Strassen theorem (1983)[2]: the cost of computing the gradient of a scalar-valued function is within a constant factor of the cost of evaluating the function. The constant is small (usually –); for our MLP backprop derivation it was (one matmul backward one matmul forward).
5. Worked comparison
A toy: . Compute , , .
Forward mode for . Set , . Propagate:
- , so .
- , so .
Three operations to get one partial. Repeat for (another sweep, also ), then for . Total: .
Reverse mode (all three at once). Forward pass: , . Backward: initialise .
- and .
- and .
All three partials in one sweep. Total: .
Forward is slower than reverse for three parameters. Scale this up to and the asymmetry is the difference between "feasible" and "the heat death of the universe".
6. How real frameworks implement it
PyTorch and JAX both implement reverse-mode AD with two main components.
- The forward pass builds a graph (PyTorch's
Tensors carry agrad_fnattribute; JAX traces functions into ajaxpr) recording the operations performed. .backward()traverses the graph in reverse topological order, calling each node's pre-registered backward formula (a "vector–Jacobian product", or VJP).
Each primitive op (matmul, sigmoid, sum, log) ships with a forward + backward pair. Users don't write the backward by hand — they compose primitives. The chain-rule machinery from this chapter is implemented once, at the framework level, and called for every model.
7. Higher-order derivatives
Want a Hessian? Two ways:
- Forward over reverse. Run reverse-mode on to get , then run forward-mode on the function to get row-by-row. Cost .
- Reverse over reverse. Run reverse-mode on (a scalar) to get for an arbitrary direction in — useful for Hessian-vector products without forming the matrix.
For deep learning, we almost never form the full Hessian (it has entries, for our example). Hessian-vector products are the right primitive for second-order methods (K-FAC, Sophia, …; Chapter 3).
8. Takeaways
- Forward-mode AD: . Good when inputs few, outputs many.
- Reverse-mode AD: for one scalar output. The standard mode for DL.
- Backprop from 2.5–2.7 is reverse-mode AD specialised to the MLP graph.
- Framework-level VJPs make this transparent for arbitrary user code.
(a) A function has Jacobian . How many forward-mode sweeps to compute ? How many reverse-mode sweeps? Conclude they tie for square Jacobians.
(b) State the Baur–Strassen theorem in one sentence and explain why it justifies training -parameter models with reverse-mode AD.
(c) A custom op my_op(x) = x * sin(x) should ship with what backward function? Write the VJP in terms of .
(d) Why don't deep-learning frameworks materialise the full Hessian? Answer in terms of , FLOPs, and memory. What is the right object to materialise instead?
References
- [1]
Griewank, A., & Walther, A. (2008). Evaluating Derivatives: Principles and Techniques of Algorithmic Differentiation (2nd ed.). SIAM. — Canonical AD reference (2.11).
↩ - [2]
Baur, W., & Strassen, V. (1983). "The complexity of partial derivatives". Theoretical Computer Science, 22(3), 317–330. — Cost-of-gradient = cost-of-function theorem (2.11).
↩