Automatic Differentiation
Automatic differentiation (AD) is a family of techniques for evaluating exact derivatives of functions specified as computer programs, by systematically applying the chain rule to elementary operations recorded during program execution. It is the computational engine behind virtually every modern deep learning framework — without it, training neural networks with millions or billions of parameters would be computationally intractable. The remarkable fact is that AD computes exact gradients (not numerical approximations) with a cost proportional to a small constant multiple of the forward pass, regardless of the number of parameters.
The "aha moment" is this: any computer program that computes a function is, implicitly, a sequence of elementary operations — additions, multiplications, activations — each of which has a trivially known derivative. AD threads the chain rule through this sequence automatically, producing exact gradients without ever writing a symbolic derivative by hand. In reverse mode (backpropagation), the cost of computing the gradient of a scalar loss with respect to all parameters is only about 2–3× the cost of the forward pass — completely independent of the number of parameters. This asymmetry is what makes training a 100-billion-parameter model feasible.
How It Works
The Computation Graph
Every differentiable program can be unrolled into a Wengert list (also called a tape): a directed acyclic graph (DAG) where nodes are intermediate values and edges represent elementary operations. Given a function , AD exploits the chain rule in one of two traversal orders.
Forward Mode (Tangent Linear) Forward mode propagates tangent vectors (directional derivatives) alongside primal values. For a single input perturbation , each intermediate variable carries a "dot" value .
Explanation: The tangent of each node is the sum of its parents' tangents weighted by local Jacobian factors — a single forward sweep computes one directional derivative of the output with respect to a chosen input direction. Forward mode requires sweeps to compute the full Jacobian , making it efficient when (few inputs, many outputs).
Reverse Mode (Adjoint / Backpropagation) Reverse mode propagates adjoint values (also called cotangents or bars) backward through the tape. Define the adjoint of node as the sensitivity of the scalar output to :
Explanation: The adjoint measures how much a small perturbation in intermediate value changes the final loss — it is the fundamental quantity backpropagation accumulates. The reverse sweep applies the chain rule to accumulate adjoints from outputs back to inputs:
Explanation: Each parent node receives a contribution to its adjoint from every child , scaled by the local partial derivative — this accumulation over all children implements the multivariate chain rule exactly. A single reverse sweep computes — all parameter gradients simultaneously — at cost . This is the fundamental asymmetry that makes deep learning feasible.
Jacobian Cost Comparison For with operations in the forward pass:
Explanation: When (scalar loss, as in training), reverse mode computes the entire gradient in — independent of — while forward mode would require passes, one per parameter.
Operator Overloading in PyTorch
PyTorch implements reverse-mode AD via operator overloading: every `torch.Tensor` with `requires_grad=True` records operations into a dynamic tape. ```python import torch
# Define inputs x = torch.tensor([2.0, 3.0], requires_grad=True) W = torch.tensor([[1.0, 2.0], [3.0, 4.0]], requires_grad=True)
# Forward pass — tape is recorded automatically h = torch.tanh(W @ x) # shape: [2] L = h.sum() # scalar loss
# Reverse pass — adjoints flow backward through the tape L.backward() print(x.grad) # dL/dx — exact, not approximate print(W.grad) # dL/dW — computed in a single backward sweep ``` PyTorch's autograd engine stores a `grad_fn` on every non-leaf tensor, forming a linked list of `Function` objects. Each `Function` implements both `forward()` (primal computation) and `backward()` (local Jacobian-vector product). During `loss.backward()`, the engine performs a topological sort of this graph and calls `backward()` in reverse order, accumulating `.grad` on leaf tensors.
Custom Backward Passes You can register custom primitives with explicitly specified derivatives: ```python class MySigmoid(torch.autograd.Function): @staticmethod def forward(ctx, x): s = torch.sigmoid(x) ctx.save_for_backward(s) # save primal for use in backward return s @staticmethod def backward(ctx, grad_output): (s,) = ctx.saved_tensors
# dL/dx = dL/ds ds/dx = grad_output s(1-s) return grad_output s * (1 - s) sigmoid = MySigmoid.apply ```
Explanation: The custom backward multiplies the incoming adjoint by the local derivative of sigmoid — a Jacobian-vector product — demonstrating the modular, chain-rule composition that all AD systems exploit.
Andreas Griewank, a German applied mathematician, developed much of the rigorous theoretical foundation for automatic differentiation, including complexity results, the treatment of higher-order derivatives, and the concept of "checkpointing" to trade memory for recomputation in reverse mode. His 2000 textbook Evaluating Derivatives remains the canonical reference for AD theory and is arguably the most cited work in the field. Remarkably, Griewank proved the "cheap gradient principle" — that reverse-mode AD computes gradients at cost no greater than 4–6× the forward pass — well before neural networks made this result famous to the broader computing community.
Seppo Linnainmaa, a Finnish mathematician, first described the reverse accumulation of derivatives in his 1970 master's thesis at the University of Helsinki, and published the result in English in 1976 — more than a decade before backpropagation was popularized in neural networks. His algorithm applied to floating-point arithmetic rounding errors, not neural networks, making his contribution essentially invisible to the AI community for years. The connection between Linnainmaa's algorithm and neural network training was not widely recognized until historians of science traced the lineage in the 2000s, leaving him one of the most consequential uncredited inventors in modern computing.
Intuition
Imagine you are a quality inspector on a factory assembly line. The line transforms raw materials (inputs) through a series of workstations (operations) into a final product (output). Your boss asks: "If we tweak every dial and lever in this factory, how much does each one affect the final quality score?" The naive approach (numerical differentiation) would be to walk back to each of the thousands of levers, nudge it slightly, run the whole line again, and observe the change — an enormous task repeated for every single lever. Reverse-mode AD is smarter: you start at the final quality inspector and ask them, "What were you most sensitive to?" They tell you which upstream station mattered most. You walk to that station, ask the same question, and so on — tracing blame backward through the line in one pass. Each worker only needs to know their own local inputs and outputs, and the "blame signal" naturally accumulates as it travels back. By the time you've walked from the end to the start of the line once, you know every lever's influence. The key "feel" of reverse-mode AD: you record the factory's operations during the forward run (building the tape), then play the tape in reverse, passing sensitivity signals backward like water flowing upstream. No global analysis needed — just local, modular, chain-rule accounting at each node.
Historical Development
Origins · 1 minhow this was discovered
Why It Matters
Prerequisites
What This Leads To
See Also
Backpropagation · Computational Graphs · JAX and Functional Transformations · Higher-Order Derivatives · Differentiable Programming