Part I — Calculus, Optimization & Gradients · Chapter 10

Build a computational graph & autograd engine

Hook

You've applied the chain rule to one function composed with another. Every framework you'll use from here on applies it automatically, to graphs with thousands of nodes. What does that automation actually have to do, node by node?

Intuition
a3.0b4.0a·b12.0a·b + a15.0
forward pass: f(3, 4) = 15

This is the entire computational graph for f(a,b)=ab+af(a,b)=a\cdot b + a: two inputs, one multiply node, one add node. Tune aa and bb and watch every node's value update — that's the forward pass, nothing more than evaluating the graph in order.

Formalize

The backward pass seeds the output's gradient at 1, then walks the graph in reverse, applying one local rule at each node:

Lu+=Lvvu\frac{\partial L}{\partial u} \mathrel{+}= \frac{\partial L}{\partial v}\cdot\frac{\partial v}{\partial u}
  • vv — a node, already visited, whose incoming gradient L/v\partial L/\partial v is known.
  • uu — one of vv's inputs, receiving a contribution to its own gradient.
  • v/u\partial v/\partial uvv's local derivative with respect to that one input: 11 for a sum, the other input's value for a product.
  1. This is the chain rule, applied one node at a time

    Nothing here is new — it's the same chain rule from Chapter 3, just applied locally at every node instead of algebraically for the whole expression at once.

  2. += matters: a node used twice accumulates

    If a value feeds two different downstream nodes, its total gradient is the sum of what flows back from each path — never just one of them.

  3. A product node's local rule swaps its inputs

    For v=u1u2v=u_1\cdot u_2, v/u1=u2\partial v/\partial u_1 = u_2 and v/u2=u1\partial v/\partial u_2=u_1 — each input's local derivative is literally the forward-pass value of the other one.

Play
a3.0b4.0a·b12.0a·b + a15.0
f(3, 4) = 15

Toggle to "Backward pass" and watch the same graph now display gradients instead of values — and notice the readout cross-checks every one of them against a plain numerical (finite-difference) gradient, which never even looks at the graph.

Worked example

Run both passes on f(a,b)=ab+af(a,b)=a\cdot b+a at a=3,b=4a=3, b=4, using the graph n1=abn_1=a\cdot b, n2=n1+an_2=n_1+a (the output):

  1. Forward pass
    n1=3×4=12,n2=12+3=15n_1 = 3\times4=12, \qquad n_2 = 12+3=15
  2. Seed the output gradient and cross the add node

    f/n2=1\partial f/\partial n_2 = 1. The add node n2=n1+an_2=n_1+a passes its incoming gradient through unchanged to both inputs: gradient into n1n_1 is 11, and aa already receives a direct contribution of 11.

  3. Cross the multiply node

    n1=abn_1=a\cdot b: the gradient arriving at n1n_1 (which is 11) distributes as 1×b=41\times b=4 to aa, and 1×a=31\times a=3 to bb.

  4. Sum every path into a
    • aa received 11 from the direct add-path and 44 from the multiply-path: f/a=1+4=5\partial f/\partial a=1+4=5.
    • f/b=3\partial f/\partial b=3, with no second path to add.

    Both match the closed-form derivative, f/a=b+1=5\partial f/\partial a = b+1=5 and f/b=a=3\partial f/\partial b=a=3, exactly.

Checkpoint

At a = 5, b = −2, compute ∂f/∂a for f(a,b) = a·b + a. Remember: a feeds two nodes, so its gradient must combine both paths.

a5.0b-2.0a·b-10.0a·b + a-5.0
forward pass: f(5, -2) = -5
Compute ∂f/∂a, then pick a value
Summary
Lu+=Lvvu\frac{\partial L}{\partial u} \mathrel{+}= \frac{\partial L}{\partial v}\cdot\frac{\partial v}{\partial u}

That's the whole engine: a forward pass that evaluates every node once, and a backward pass that applies one local multiply-and-accumulate rule per node, in reverse. Real autograd systems — PyTorch, and every framework since — do exactly this at far larger scale, on graphs with millions of nodes instead of two. There's no more machinery to add; there's only more graph.