• English
  • Backpropagation

    Chapter 7 left one piece open. Gradient descent needs the slope of the loss for every parameter θ\theta, written ∂L/∂θ\partial L / \partial \theta: how much the loss changes per small nudge of that one parameter, with all the others held still (the curly ∂\partial is what marks "the others held still"). Measuring each slope by nudging works, but it does not scale: nudge one parameter, re-run the model, compare the losses, and repeat for the next. A billion parameters means a billion forward passes, per step. Hopeless.

    Backpropagation gets all of them in one backward sweep that costs only about twice as much as a single forward pass. It is nothing more than the chain rule from calculus, applied in a clever order.

    The chain rule, on a graph

    Any computation, including a whole transformer, can be broken into small steps: add, multiply, apply a function. Draw each step as a node and you get a computation graph. For an LLM, the inputs on the left are the tokens and every single parameter, and the one output on the right is the loss. Here is a tiny graph with four inputs:

    L=(a⋅b+c)⋅fL = (a \cdot b + c) \cdot f

    The intermediate results get names: e=a⋅be = a \cdot b, then d=e+cd = e + c, then L=d⋅fL = d \cdot f. Think of aa, bb, cc, ff as parameters and LL as the loss. (A real cross-entropy loss is never negative; this one comes out at −8, but the mechanics are identical.) Step through the figure: first the forward pass fills in the values from a=2a = 2, b=−3b = -3, c=10c = 10, f=−2f = -2, then the backward pass fills in a gradient under every node.

    Figure 8.1 · Forward and backward through a computation graph
    avalue 2bvalue −3cvalue 10fvalue −2e = a × bvalue ?d = e + cvalue ?L = d × fvalue ?
    1/9

    Four inputs and three operations. Think of a, b, c, f as weights and L as the loss we want to lower.

    Forward pass (accent color): values flow left to right. Backward pass (amber): gradients flow right to left. Every node only needs a local rule, its own small derivative, multiplied by the gradient arriving from above.

    Each node only knows its own small derivative:

    OperationLocal rule
    z=x+yz = x + ygradient passes to both inputs unchanged
    z=x⋅yz = x \cdot yxx receives y×y \times (gradient of zz), and vice versa
    z=tanh⁡(x)z = \tanh(x)xx receives (1−z2)×(1 - z^2) \times (gradient of zz)
    z=exz = e^{x}xx receives z×z \times (gradient of zz)

    (tanh⁡\tanh is an S-shaped curve that squashes any number into the range from −1 to 1.)

    The chain rule says that to get the gradient of an input you multiply these local derivatives along the path back from the output:

    ∂L∂a=∂L∂d⋅∂d∂e⋅∂e∂a=f⋅1⋅b=(−2)(1)(−3)=6\frac{\partial L}{\partial a} = \frac{\partial L}{\partial d} \cdot \frac{\partial d}{\partial e} \cdot \frac{\partial e}{\partial a} = f \cdot 1 \cdot b = (-2)(1)(-3) = 6

    Read it as a chain of exchange rates. Raise aa by 0.01 and e=a⋅be = a \cdot b moves bb times as much, by −0.03. d=e+cd = e + c passes that on unchanged. L=d⋅fL = d \cdot f multiplies it by f=−2f = -2, so LL rises by +0.06. Rates along a chain multiply: 6 units of loss per unit of aa. You can check it directly: (2.01⋅(−3)+10)⋅(−2)=−7.94(2.01 \cdot (-3) + 10) \cdot (-2) = -7.94, which is 0.06 above −8. That 6 is exactly the slope of chapter 7: positive, so gradient descent will lower aa.

    Backpropagation walks the graph from the output backwards and reuses every intermediate product. The gradient arriving at ee is computed once and then shared by both aa and bb. That reuse is what makes it cheap. When a value feeds several later nodes, the gradients coming back from each are added.

    One neuron

    Now a graph that is a real piece of a network. A neuron multiplies its input xx by a weight ww, adds a bias bb (a second parameter, a plain offset), and squashes the result through tanh: y=tanh⁡(z)y = \tanh(z) with z=wx+bz = w x + b. This one has a single job, to output t=0.5t = 0.5 for the input x=1.5x = 1.5. Its loss is the squared error L=(y−t)2L = (y - t)^2, a simpler cousin of cross-entropy for a numeric output: 0 when the output is right, growing as it misses.

    That makes a model with exactly two parameters, like the landscape in Figure 7.2. The curve in the figure is that landscape cut along ww, with bb held still. The chain rule runs back through the three steps of the neuron:

    ∂L∂w=∂L∂y⋅∂y∂z⋅∂z∂w=2(y−t)⋅(1−y2)⋅x\frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial z} \cdot \frac{\partial z}{\partial w} = 2(y - t) \cdot (1 - y^2) \cdot x
    Figure 8.2 · The gradient of a single neuron

    y = tanh(w·1.5 + b) = −0.905
    L = (y − 0.5)² = 1.974

    ∂L/∂w = 2(y−t) · (1−y²) · x
    = −2.810 · 0.181 · 1.5 = −0.762
    ∂L/∂b = −2.810 · 0.181 · 1 = −0.508
    w ← w − 0.5·∂L/∂w
    -2-101220L as w varies
    Move w and b and watch the three factors of the chain rule. The amber line is the tangent to the loss curve: its slope is exactly ∂L/∂w. Press Gradient step a few times and the dot slides down to the minimum. Try w near ±2: tanh saturates, (1 − y²) is almost 0, and learning stalls. That is the vanishing gradient problem.

    The three factors have clear meanings:

    • 2(y−t)2(y - t): how wrong the output is, and in which direction.
    • 1−y21 - y^2: how sensitive the neuron is right now (tanh's slope).
    • xx: how much this weight actually contributed. A weight attached to a zero input gets no blame.

    The middle factor is a warning. A deep network multiplies one such factor per layer on the way back, and if many of them are small, the product shrinks toward zero and the early layers stop learning. The residual connections of a transformer give the gradient a path around those factors.

    Training a real (tiny) network

    Stack neurons into layers and the same rules apply to every weight. Every training step, for this toy and for an LLM alike, is the same four moves:

    1. Forward: run the model on a batch and keep every intermediate value.
    2. Loss: compare the predictions with the right answers to get one number.
    3. Backward: walk the graph in reverse to get the slope of every parameter.
    4. Update: move every parameter a small step against its slope, θ←θ−η ∇L\theta \leftarrow \theta - \eta \, \nabla L, as in chapter 7.

    Then repeat with the next batch. The network below has two inputs (the coordinates of a point), two hidden layers (layers between the input and the output) of 16 tanh neurons, and one output: the probability that a point belongs to the accent-colored class rather than the amber one. It starts with random weights. Press Train: each step runs forward over all points, computes the cross-entropy loss from chapter 7, backpropagates, and nudges all 337 parameters.

    Figure 8.3 · Backpropagation, live
    The background shows what the network currently predicts at every location, the thin line is the decision boundary (p = 0.5), and the dots are the training data. Watch the boundary bend to wrap around the moons (the two crescent-shaped clouds). The spiral is harder: it needs more steps, and with a learning rate that is too high, the loss jumps around instead of falling.

    No one told the network what a moon or a spiral looks like. Its hidden neurons each learn a simple tilted boundary, and the next layer combines them into curves. A transformer does the same thing with far more parameters, learning features of language instead of features of a 2D plane.

    What PyTorch actually does

    The engine behind Figure 8.1 is about a hundred lines of TypeScript, in the spirit of Andrej Karpathy's micrograd:

    mul(other: Value): Value {
      const out = new Value(this.data * other.data, [this, other], '×');
      out.backwardStep = () => {
        this.grad += other.data * out.grad; // local derivative × incoming gradient
        other.grad += this.data * out.grad;
      };
      return out;
    }
    
    backward() {
      const order = topoSort(this);         // every node, inputs before outputs
      this.grad = 1;
      for (const v of order.reverse()) v.backwardStep();
    }

    PyTorch (the most widely used library for building and training neural networks), JAX and every other deep-learning framework do exactly this, with two differences that matter for speed:

    • Tensors, not scalars. Each node is a whole matrix operation, so one "multiply" is a matrix multiplication over millions of numbers, run on a GPU. The local rules become matrix rules, for example the gradient of Y=XWY = XW with respect to WW is X⊤∂L∂YX^\top \frac{\partial L}{\partial Y}.
    • Memory for activations. The backward pass needs the forward values (notice that the graph above kept them). For a large model these stored activations take more memory than the weights themselves, which is why tricks like activation checkpointing recompute some of them instead of storing them.

    When you write loss.backward() in PyTorch, it builds this graph as your code runs and then walks it in reverse, accumulating .grad for every parameter, just like the stepper above.

    Summary

    • Backpropagation computes the gradient of the loss with respect to every parameter in one backward pass.
    • It is the chain rule: multiply local derivatives along paths from the output, add where paths merge.
    • Each training step is forward, loss, backward, update. Backpropagation is the backward part; gradient descent is the update.
    • The same procedure trains a 337-parameter toy network and a trillion-parameter LLM; only the size of the tensors changes.

    With tokens, vectors, attention and training in place, the next part looks at how a trained model is actually used, starting with how it picks a token: sampling.