Skip to content
mlmentorship

Backpropagation

Reverse-mode automatic differentiation applied to a computation graph. The algorithm that computes gradients for every parameter in one backward pass.

Published · 6 min read ·Core ·Foundation
Reading depth

Summary

Backpropagation accumulates a scalar loss’s sensitivity through every recorded route into each parameter. It applies reverse-mode automatic differentiation to a computation graph, reusing forward values needed by local derivative rules. For standard operations with comparable derivative costs, one scalar-output reverse pass costs for forward work . It computes gradients, not a parameter update or a guarantee of optimization progress.

The algorithm

Execute operations in dependency order and retain the values their backward rules need. Initialize all accumulated sensitivities to zero and seed . Visit operations in reverse dependency order, adding each contribution rather than overwriting earlier contributions:

The matrix-calculus pullback explains this transposed-Jacobian rule. Local reverse products usually avoid constructing the full Jacobian.

For , , let , , and . The reverse chain gives , , , and . These are the exact values and arrows in the diagram.

Spatial intuition

The same upstream gradient meets different cached multipliers

The same upstream gradient meets different local multipliersA cached forward value determines each local derivative. Multiplying by the shared upstream signal gives different parameter gradients.Forward: a times x32x6To a: upstream times cached x42x8To x: upstream times cached a43x12
Derivation
Forward and backward passes through a scalar computation graph Inputs x equals 2 and w equals 3 flow through multiplication, addition, and squaring to produce loss 49. A reverse pass starts with loss gradient 1 and applies local derivatives to produce gradients 42 for x and 28 for w. FORWARD: compute and cache values multiply + 1 square xvalue 2 wvalue 3 z₁value 6 z₂value 7 Lvalue 49 BACKWARD: upstream gradient × local derivative 1 × 2z₂ = 14 14 × 1 = 14 14 × x = 28 14 × w = 42 ∂L/∂x = 42 ∂L/∂w = 28 ∂L/∂z₁ = 14 ∂L/∂z₂ = 14 seed = 1
Read it this way: each reverse arrow multiplies one upstream gradient by one local derivative. The graph is traversed once in each direction; no full Jacobian is built.
Check: Why does w receive gradient 28 while x receives 42?
Read it this way: A cached forward value determines each local derivative. Multiplying by the shared upstream signal gives different parameter gradients.
If x doubles while the upstream signal stays fixed, which parameter gradient doubles?

Vector-Jacobian products (VJPs)

For , an incoming output gradient produces . Primitive rules combine to implement this vector-Jacobian product without materializing a network-wide Jacobian.

Reuse an Intermediate

New task: Differentiate and .

Picture and change: Two outgoing routes contribute to the same intermediate; sum them before propagating through multiplication.

Check: , hence at , gradients are , . Overwriting one contribution would incorrectly halve both answers.

Use when: Parameters or intermediate results feed multiple operations. Repeated references are multiple derivative contributions even when the graph node is stored once.

Memory cost

New task: Compute the same gradient with less retained activation memory.

Picture and change: Save selected boundaries and recompute needed forward values during backward.

Check: Recomputing and in the original example still gives gradients .

Use when: Activation checkpointing can trade extra work for a lower memory peak. Recomputation must reproduce required random choices and state-dependent behavior. Storing lower-precision activations is a separate numerical trade-off, not exact checkpointing. Total memory depends on retained tensors, lifetimes, parameters, optimizer state, and temporary workspace, not depth alone.

Connection to reverse-mode autodiff

For a full Jacobian with inputs and outputs, forward mode needs roughly directional sweeps. Reverse mode needs roughly output-seeded sweeps. One scalar loss therefore favors reverse mode for many parameters; a few requested directional derivatives may favor forward mode.

What backprop does NOT do

  • It does not choose a learning rate, regularizer, or optimizer.
  • It is not specific to neural networks; differentiable programs can use the same accumulation rule.
  • It does not differentiate through unrecorded operations or automatically supply a derivative for discrete choices.
  • It does not prove convergence; even an exact gradient can be used in a non-descending finite update.

Common pitfalls

  • Unintended accumulation. Framework parameter gradients may persist across backward calls; reset them at the intended optimization boundary.
  • Saved-value reuse. Backward may release needed saved tensors; a second backward through the same graph can require explicit retention.
  • Detached routes. detach() stops gradient flow through that route.
  • Retained graphs. Holding differentiable loss objects can retain graph metadata or saved values; log detached scalars when appropriate.
  • Leaf confusion. Requiring gradients and being a leaf are different properties; retained intermediate gradients need their own policy.

Reconstruct

Why must a shared node wait until every downstream contribution has arrived?

Its sensitivity is the sum over downstream routes. Propagating a partial sum and then discarding the node loses later routes; reverse dependency order prevents that mistake.