The goal
We have a loss that depends on billions of parameters. We want to find parameter values that make small. We can’t solve this analytically. So we do it iteratively.
Gradient descent
The gradient tells you, for each parameter, “if I increase this weight slightly, does the loss go up or down, and by how much?” The update rule:
Breaking down the formula
Piece Meaning One weight in the network ”Replace the left side with the right side” The slope: how much changes per tiny nudge of . Positive means “increasing makes things worse” Learning rate, the step size (e.g. ) Go the opposite way of the slope, i.e. downhill
Move each weight a small step in the direction that reduces the loss. Repeat. Imagine standing on a foggy hillside and always stepping downhill. You never see the whole landscape, you just feel the local slope.
Backpropagation
The question is how to compute for every one of billions of weights efficiently. Answer: the chain rule from calculus, applied cleverly.
A network is a chain of functions:
The chain rule says the derivative of a composition is the product of derivatives along the chain. For a weight that feeds through :
Think of it as a chain of “how much does this change affect the next thing,” multiplied together.
Tiny example. Say and loss .
flowchart LR X["x"] --> H["h = w₁·x"] H --> A["a = ReLU(h)"] A --> Y["y = w₂·a"] Y --> L["L = (y − target)²"]
Forward pass (compute left to right, saving every value):
Backward pass (walk right to left, reusing each piece):
You run the forward pass (compute outputs, saving intermediate values), then the backward pass (walk the chain in reverse, reusing each computed piece). The reuse is the trick. The backward pass costs about 2× a forward pass regardless of how many parameters there are. That’s what makes training billion-parameter networks feasible at all.
Worked example with numbers (click to expand)
Let , , , target .
Forward: , , ,
Backward:
- (since )
Update with :
Check: new , , , . The loss dropped from to about in a single step.
(Try and the weights overshoot past the target. That’s the “learning rate too high” problem in miniature.)
Modern frameworks (PyTorch, JAX) do this automatically (autograd). You write the forward pass, and loss.backward() hands you every gradient.
Practical optimizers
Plain gradient descent is rarely used. Real training uses:
- Mini-batches: compute the gradient on a batch of examples (millions of tokens), not the whole dataset. It’s a noisy estimate, but good enough and way cheaper. This is stochastic gradient descent.
- Adam / AdamW: keeps running averages of each parameter’s gradient (momentum) and squared gradient (scale), then adapts the step size per parameter. This is the standard for LLMs.
- Learning rate schedule: warm up from tiny, ramp to a peak, then decay (often cosine). Too high a rate and training diverges. Too low and it crawls.
Adam in formulas (simplified)
Let be the current gradient.
and to control how long the “memory” lasts. Parameters with big, jumpy gradients get smaller steps; quiet ones get bigger steps. (The real algorithm also includes a small bias-correction term, omitted here.)
← 3. Loss and how wrong we are · Contents · 5. From generic networks to language →