Md. Asif Uddin

    Chapter 6 · I.6

    Problems

    A chapter that uses mathematics has to teach that mathematics by making the reader compute. A chapter that only displays equations has failed, however correct the equations are.

    M3Load-bearing

    5/5 problems5/4 variants10/10 exercisesquota met, and enforced

    The contract

    • 5 worked problems, minimum.
    • 4 distinct variants, and no variant more than half of them.
    • 10 exercises, every one with a published solution.
    • At least one numeric problem — present.
    • At least one symbolic problem — present.
    • At least one limit or counterexample problem — present.
    • At least one complexity or shape problem — present.
    • At least one ▲▲▲ problem — present.

    Numerical instantiationSymbolic derivationDimensional algebraConstructed failureCost accounting

    Problem I.6.B01

    One full backward pass through a 2-2-1 network

    numeric▲△△

    Eight decimal places throughout; the finite-difference check uses a step of 1e-6.

    STATEMENT

    Run one forward pass and one backward pass through a small network by hand, producing every intermediate and every parameter gradient. Then check the result against central differences, which is the only way to know a hand-derived gradient is right rather than merely consistent with itself.

    GIVEN

    A network with two inputs, two sigmoid hidden units and one linear output. One example, so every activation is a row.

    x=(12),y=1\vec{x} = \begin{pmatrix} 1 & 2 \end{pmatrix}, \qquad y = 1

    W(1)=(0.10.30.20.4),b(1)=(00.1)\mat{W}^{(1)} = \begin{pmatrix} 0.1 & 0.3 \\ 0.2 & 0.4 \end{pmatrix}, \qquad \vec{b}^{(1)} = \begin{pmatrix} 0 & 0.1 \end{pmatrix}W(2)=(0.5−0.6),b(2)=0.2\mat{W}^{(2)} = \begin{pmatrix} 0.5 \\ -0.6 \end{pmatrix}, \qquad b^{(2)} = 0.2

    The hidden nonlinearity is σ(z)=1/(1+e−z)\sigma(z) = 1/(1 + e^{-z}), the head carries none, and the loss is L=12(y^−y)2L = \tfrac{1}{2}(\hat{y} - y)^2.

    FIND

    Every intermediate of the forward pass; the adjoints δ(2)\delta^{(2)} and δ(1)\delta^{(1)}; the gradients with respect to all nine parameters; and the largest disagreement between those gradients and a central-difference estimate.

    STRATEGY

    Forward first, keeping everything — by Definition 3 the backward pass consumes the stored values, so discarding them means starting again. Then apply (I.6.2) from the sink: the loss adjoint is 1, and each node passes its own adjoint times its local Jacobian to its producers. For this architecture that is exactly the recurrence (I.6.3) and the outer product (I.6.4).

    SOLUTION

    Step 1 — forward. The pre-activations are z(1)=xW(1)+b(1)\vec{z}^{(1)} = \vec{x}\mat{W}^{(1)} + \vec{b}^{(1)}:

    z1(1)=1(0.1)+2(0.2)+0=0.5,z2(1)=1(0.3)+2(0.4)+0.1=1.2z^{(1)}_1 = 1(0.1) + 2(0.2) + 0 = 0.5, \qquad z^{(1)}_2 = 1(0.3) + 2(0.4) + 0.1 = 1.2

    a(1)=σ(z(1))=(0.622459330.76852478)\vec{a}^{(1)} = \sigma(\vec{z}^{(1)}) = \begin{pmatrix} 0.62245933 & 0.76852478 \end{pmatrix}

    y^=z(2)=0.62245933(0.5)+0.76852478(−0.6)+0.2=0.05011480\hat{y} = z^{(2)} = 0.62245933(0.5) + 0.76852478(-0.6) + 0.2 = 0.05011480

    L=12(0.05011480−1)2=0.45114095L = \tfrac{1}{2}(0.05011480 - 1)^2 = 0.45114095

    Step 2 — the adjoint at the sink. Lˉ=1\bar{L} = 1 by definition, and the head is linear, so

    δ(2)=∂L∂z(2)=y^−y=−0.94988520\delta^{(2)} = \frac{\partial L}{\partial z^{(2)}} = \hat{y} - y = -0.94988520

    The sign says the prediction is below the target, which it is.

    Step 3 — the second layer’s parameters. By (I.6.4) the weight gradient is the outer product of what entered the layer with what came back:

    ∂L∂W(2)=(a(1))Tδ(2)=(−0.59126491−0.73001032),∂L∂b(2)=−0.94988520\frac{\partial L}{\partial \mat{W}^{(2)}} = (\vec{a}^{(1)})^{\mathsf{T}} \delta^{(2)} = \begin{pmatrix} -0.59126491 \\ -0.73001032 \end{pmatrix}, \qquad \frac{\partial L}{\partial b^{(2)}} = -0.94988520

    The bias gradient is the adjoint, because a bias enters its node with coefficient one.

    Step 4 — travel back through the layer. By (I.6.3), multiply by (W(2))T(\mat{W}^{(2)})^{\mathsf{T}} and gate by the slope of the nonlinearity:

    δ(2)(W(2))T=(−0.474942600.56993112)\delta^{(2)}(\mat{W}^{(2)})^{\mathsf{T}} = \begin{pmatrix} -0.47494260 & 0.56993112 \end{pmatrix}

    For the logistic function σ′=σ(1−σ)\sigma' = \sigma(1 - \sigma), so it is read off the stored activations rather than recomputed:

    σ′(z(1))=(0.235003710.17789444)\sigma'(\vec{z}^{(1)}) = \begin{pmatrix} 0.23500371 & 0.17789444 \end{pmatrix}

    δ(1)=(−0.474942600.56993112)⊙(0.235003710.17789444)=(−0.111613270.10138758)\delta^{(1)} = \begin{pmatrix} -0.47494260 & 0.56993112 \end{pmatrix} \odot \begin{pmatrix} 0.23500371 & 0.17789444 \end{pmatrix} = \begin{pmatrix} -0.11161327 & 0.10138758 \end{pmatrix}

    Step 5 — the first layer’s parameters. The same outer product, one layer down:

    ∂L∂W(1)=xTδ(1)=(−0.111613270.10138758−0.223226550.20277516),∂L∂b(1)=δ(1)\frac{\partial L}{\partial \mat{W}^{(1)}} = \vec{x}^{\mathsf{T}} \delta^{(1)} = \begin{pmatrix} -0.11161327 & 0.10138758 \\ -0.22322655 & 0.20277516 \end{pmatrix}, \qquad \frac{\partial L}{\partial \vec{b}^{(1)}} = \delta^{(1)}

    The second row is exactly twice the first, because the second input is twice the first. A weight’s gradient carries the size of the thing that fed it.

    Step 6 — the check. Central differences with h=10−6h = 10^{-6}, evaluating the loss twice per parameter and taking (L(θ+h)−L(θ−h))/2h(L(\theta + h) - L(\theta - h))/2h, agree with all nine analytic gradients. The largest disagreement is 5.79×10−115.79 \times 10^{-11}.

    That agreement is worth more than any of the individual numbers. The analytic gradient came from the chain rule; the numeric one came from evaluating the forward function eighteen times and never differentiating anything. Two unrelated computations landing on the same nine numbers is evidence the derivation is correct.

    Answer

    quantityvalue
    z(1)\vec{z}^{(1)}0.5, 1.20.5,\ 1.2
    a(1)\vec{a}^{(1)}0.62245933, 0.768524780.62245933,\ 0.76852478
    y^\hat{y}0.050114800.05011480
    LL0.451140950.45114095
    δ(2)\delta^{(2)}−0.94988520-0.94988520
    δ(1)\delta^{(1)}−0.11161327, 0.10138758-0.11161327,\ 0.10138758
    ∂L/∂W(2)\partial L/\partial \mat{W}^{(2)}−0.59126491, −0.73001032-0.59126491,\ -0.73001032
    ∂L/∂W(1)\partial L/\partial \mat{W}^{(1)}(−0.11161327, 0.10138758; −0.22322655, 0.20277516)(-0.11161327,\ 0.10138758;\ -0.22322655,\ 0.20277516)
    largest gradient-check gap5.79×10−115.79 \times 10^{-11}

    Check — sanity

    The bias gradients equal the adjoints. ∂L/∂b(2)=δ(2)\partial L/\partial b^{(2)} = \delta^{(2)} and ∂L/∂b(1)=δ(1)\partial L/\partial \vec{b}^{(1)} = \delta^{(1)}, to the last digit. A bias adds into its node with coefficient one, so its local Jacobian is the identity and (I.6.2) leaves the adjoint unchanged. Any discrepancy here is an arithmetic slip, not a modelling question.

    The rows of ∂L/∂W(1)\partial L/\partial \mat{W}^{(1)} are in the ratio of the inputs. −0.22322655/−0.11161327=2-0.22322655 / -0.11161327 = 2 exactly, matching x2/x1=2x_2/x_1 = 2. The outer product in (I.6.4) says the gradient of a weight scales with the activation entering it, so this ratio is forced.

    The sigmoid slopes are below 1/41/4. 0.235003710.23500371 and 0.177894440.17789444, against the maximum σ′(0)=0.25\sigma'(0) = 0.25. Every layer of this kind multiplies the travelling adjoint by something no larger than a quarter, which is the observation I.6.B04 turns into a depth limit.

    Where this breaks

    The finite-difference check that validates the gradient here does not scale, and it is worth being precise about why. It costs two forward passes per parameter, so checking a model with NN parameters costs 2N2N evaluations — for N=567 434N = 567\,434, the network audited in I.5.B03, that is over a million forward passes to verify one gradient that reverse mode produced in a single traversal. By (I.6.6) the analytic gradient cost about four forward passes. The ratio of a million to four is the cheap gradient principle, seen from the other side.

    The step size has its own trap. Too large and the truncation error of the difference dominates; too small and floating-point cancellation does, because L(θ+h)L(\theta + h) and L(θ−h)L(\theta - h) agree in their leading digits and subtracting them discards exactly those. At h=10−12h = 10^{-12} in fp64 this same check returns visible garbage. Gradient checking is a debugging tool for small cases, not a verification method for a real model.

    Variation

    Replace the linear head with a second sigmoid, so y^=σ(z(2))\hat{y} = \sigma(z^{(2)}), and keep everything else. Which of the nine gradients change, and by what factor? Predict the factor before recomputing — one number in Step 2 is responsible for all of it.

    Problem I.6.B02

    Deriving the layer recurrence from the graph

    symbolic▲▲△

    Symbolic throughout; no numbers.

    STATEMENT

    Derive the two equations every implementation of backpropagation contains — the recurrence that carries an adjoint from one layer to the previous one, and the outer product that turns an adjoint into a weight gradient — from the graph rule (I.6.2) alone. Nothing else may be assumed.

    GIVEN

    One layer of an MLP, written as three nodes so that each has a Jacobian that can be stated:

    z(ℓ)=a(ℓ−1)W(ℓ)+b(ℓ),a(ℓ)=φ(z(ℓ))\vec{z}^{(\ell)} = \vec{a}^{(\ell-1)}\mat{W}^{(\ell)} + \vec{b}^{(\ell)}, \qquad \vec{a}^{(\ell)} = \varphi(\vec{z}^{(\ell)})

    with a(ℓ−1)∈R1×dℓ−1\vec{a}^{(\ell-1)} \in \R^{1 \times d_{\ell-1}}, W(ℓ)∈Rdℓ−1×dℓ\mat{W}^{(\ell)} \in \R^{d_{\ell-1} \times d_\ell} and φ\varphi applied elementwise. A scalar loss LL sits at the sink. Write δ(ℓ)=∂L/∂z(ℓ)\delta^{(\ell)} = \partial L / \partial \vec{z}^{(\ell)}.

    FIND

    Expressions for ∂L/∂W(ℓ)\partial L/\partial \mat{W}^{(\ell)}, ∂L/∂b(ℓ)\partial L/\partial \vec{b}^{(\ell)} and δ(ℓ−1)\delta^{(\ell-1)} in terms of δ(ℓ)\delta^{(\ell)} and quantities the forward pass already stored.

    STRATEGY

    Work one node at a time and never write down a full Jacobian. Each step asks the same question: given the adjoint of this node’s output, what is the adjoint of its input? That is the vector–Jacobian product of Definition 7, and for these three operations it has a closed form in each case.

    SOLUTION

    Step 1 — through the nonlinearity. φ\varphi acts elementwise, so ∂aj(ℓ)/∂zi(ℓ)\partial a^{(\ell)}_j / \partial z^{(\ell)}_i is zero unless i=ji = j. The Jacobian is diagonal, and multiplying by a diagonal matrix is multiplying elementwise:

    δ(ℓ)=∂L∂z(ℓ)=∂L∂a(ℓ)⊙φ′(z(ℓ))\delta^{(\ell)} = \frac{\partial L}{\partial \vec{z}^{(\ell)}} = \frac{\partial L}{\partial \vec{a}^{(\ell)}} \odot \varphi'(\vec{z}^{(\ell)})

    This is the first place the forward pass is consumed: φ′\varphi' is evaluated at the stored z(ℓ)\vec{z}^{(\ell)}, and without it there is nothing to multiply.

    Step 2 — the bias. In z=aW+b\vec{z} = \vec{a}\mat{W} + \vec{b} the bias enters with coefficient one, so ∂zj/∂bi=δij\partial z_j / \partial b_i = \delta_{ij} and the local Jacobian is the identity. By (I.6.2) the adjoint passes through unchanged:

    ∂L∂b(ℓ)=δ(ℓ)\frac{\partial L}{\partial \vec{b}^{(\ell)}} = \delta^{(\ell)}

    Step 3 — the weights. Take one entry. Only the jj-th output involves WijW_{ij}, and it involves it linearly with coefficient ai(ℓ−1)a^{(\ell-1)}_i:

    ∂zk(ℓ)∂Wij(ℓ)={ai(ℓ−1)k=j0k≠j\frac{\partial z^{(\ell)}_k}{\partial W^{(\ell)}_{ij}} = \begin{cases} a^{(\ell-1)}_i & k = j \\ 0 & k \neq j \end{cases}

    So the sum over consumers in (I.6.2) collapses to a single term:

    ∂L∂Wij(ℓ)=∑kδk(ℓ)∂zk(ℓ)∂Wij(ℓ)=ai(ℓ−1)δj(ℓ)\frac{\partial L}{\partial W^{(\ell)}_{ij}} = \sum_k \delta^{(\ell)}_k \frac{\partial z^{(\ell)}_k}{\partial W^{(\ell)}_{ij}} = a^{(\ell-1)}_i \delta^{(\ell)}_j

    Every entry is a product of one input coordinate and one output adjoint, which is exactly an outer product:

    ∂L∂W(ℓ)=(a(ℓ−1))Tδ(ℓ)\frac{\partial L}{\partial \mat{W}^{(\ell)}} = (\vec{a}^{(\ell-1)})^{\mathsf{T}} \delta^{(\ell)}

    The shapes confirm it: (dℓ−1×1)(1×dℓ)(d_{\ell-1} \times 1)(1 \times d_\ell) gives dℓ−1×dℓd_{\ell-1} \times d_\ell, the shape of W(ℓ)\mat{W}^{(\ell)}. A gradient always has the shape of the thing it differentiates.

    Step 4 — back to the previous layer. Now differentiate with respect to the input rather than the parameters. Coordinate ai(ℓ−1)a^{(\ell-1)}_i feeds every output zj(ℓ)z^{(\ell)}_j, so this time the sum does not collapse:

    ∂L∂ai(ℓ−1)=∑jδj(ℓ)∂zj(ℓ)∂ai(ℓ−1)=∑jδj(ℓ)Wij(ℓ)\frac{\partial L}{\partial a^{(\ell-1)}_i} = \sum_j \delta^{(\ell)}_j \frac{\partial z^{(\ell)}_j}{\partial a^{(\ell-1)}_i} = \sum_j \delta^{(\ell)}_j W^{(\ell)}_{ij}

    which is the ii-th entry of δ(ℓ)(W(ℓ))T\delta^{(\ell)}(\mat{W}^{(\ell)})^{\mathsf{T}}:

    ∂L∂a(ℓ−1)=δ(ℓ)(W(ℓ))T\frac{\partial L}{\partial \vec{a}^{(\ell-1)}} = \delta^{(\ell)}(\mat{W}^{(\ell)})^{\mathsf{T}}

    The transpose is not a convention. Forwards, W\mat{W} maps dℓ−1d_{\ell-1} coordinates to dℓd_\ell; backwards, the adjoint must travel from dℓd_\ell back to dℓ−1d_{\ell-1}, and the only way to do that with the same matrix is to use it the other way round.

    Step 5 — combine. Applying Step 1 at layer ℓ−1\ell - 1 to the result of Step 4 gives the recurrence:

    δ(ℓ−1)=(δ(ℓ)(W(ℓ))T)⊙φ′(z(ℓ−1))\delta^{(\ell-1)} = \left(\delta^{(\ell)}(\mat{W}^{(\ell)})^{\mathsf{T}}\right) \odot \varphi'(\vec{z}^{(\ell-1)})

    which is (I.6.3), and Steps 2 and 3 are (I.6.4). Both came from (I.6.2) and three local Jacobians, and no Jacobian was ever written out in full.

    Answer

    ∂L∂W(ℓ)=(a(ℓ−1))Tδ(ℓ),∂L∂b(ℓ)=δ(ℓ)\frac{\partial L}{\partial \mat{W}^{(\ell)}} = (\vec{a}^{(\ell-1)})^{\mathsf{T}} \delta^{(\ell)}, \qquad \frac{\partial L}{\partial \vec{b}^{(\ell)}} = \delta^{(\ell)}δ(ℓ−1)=(δ(ℓ)(W(ℓ))T)⊙φ′(z(ℓ−1))\delta^{(\ell-1)} = \left(\delta^{(\ell)}(\mat{W}^{(\ell)})^{\mathsf{T}}\right) \odot \varphi'(\vec{z}^{(\ell-1)})

    Check — sanity

    Every gradient has the shape of its parameter. (dℓ−1×1)(1×dℓ)=dℓ−1×dℓ(d_{\ell-1} \times 1)(1 \times d_\ell) = d_{\ell-1} \times d_\ell for the weights, and δ(ℓ)\delta^{(\ell)} is 1×dℓ1 \times d_\ell for the bias. This is the cheapest check there is and it catches a transposed derivation immediately.

    The recurrence terminates. δ(L)\delta^{(L)} comes from the loss, and each application of the recurrence produces δ\delta one layer earlier, so LL steps reach the input. Nothing in the derivation refers to a layer that has not been reached, which is what makes reverse topological order the right traversal.

    Only stored quantities appear. a(ℓ−1)\vec{a}^{(\ell-1)}, z(ℓ−1)\vec{z}^{(\ell-1)} and W(ℓ)\mat{W}^{(\ell)} — all present after the forward pass. No re-evaluation of φ\varphi or of any matrix product is required, which is why the backward pass costs about what the forward pass cost rather than twice it.

    Where this breaks

    The derivation assumes φ\varphi is elementwise, which is what makes its Jacobian diagonal and its VJP a Hadamard product. Softmax is not elementwise: its Jacobian is diag(p)−pTp\mathrm{diag}(\vec{p}) - \vec{p}^{\mathsf{T}}\vec{p}, dense and of size d×dd \times d, and Step 1 does not apply to it. Every layer that mixes coordinates — softmax, normalisation over a feature dimension, attention — needs its own VJP derived separately, and treating one of them as elementwise produces a gradient that is wrong in a way no shape check detects, because the shapes are unchanged.

    It also assumes each of a(ℓ−1)\vec{a}^{(\ell-1)}, W(ℓ)\mat{W}^{(\ell)} and b(ℓ)\vec{b}^{(\ell)} has exactly one consumer. Tie a weight across two layers and Step 3 acquires a second term, by (I.6.2); I.6.X04 is that case, and the failure of forgetting it is not subtle.

    Variation

    Redo Steps 3 and 4 for a batch of BB examples, where A(ℓ−1)∈RB×dℓ−1\mat{A}^{(\ell-1)} \in \R^{B \times d_{\ell-1}} and Δ(ℓ)∈RB×dℓ\Delta^{(\ell)} \in \R^{B \times d_\ell}. One of the two results acquires a sum over the batch and the other does not. Which, and why?

    Problem I.6.B03

    A shape audit of the backward pass, with a batch

    shape▲▲△

    Exact shapes; no arithmetic beyond integer dimensions.

    STATEMENT

    Tabulate the shape of every tensor in both directions through a batched MLP, and say where the batch dimension goes. One of the two gradients in (I.6.4) sums over the batch and the other does not; the shapes say which, before any calculus is done.

    GIVEN

    The network of I.5.B03 — widths (784,512,256,128,10)(784, 512, 256, 128, 10), four affine layers, ReLU after the first three, no activation on the head — now run on a batch of B=32B = 32 examples, so the input is A(0)∈R32×784\mat{A}^{(0)} \in \R^{32 \times 784}. The loss is a scalar: a mean over the batch.

    FIND

    For every layer ℓ\ell: the shapes of A(ℓ)\mat{A}^{(\ell)}, Δ(ℓ)\Delta^{(\ell)}, ∂L/∂W(ℓ)\partial L / \partial \mat{W}^{(\ell)} and ∂L/∂b(ℓ)\partial L / \partial \vec{b}^{(\ell)}. Then the total activation memory kept for the backward pass, in fp32.

    STRATEGY

    Two rules settle every row. A gradient has the shape of the thing it differentiates, so parameter gradients cannot carry a batch dimension — the parameters do not have one. An adjoint has the shape of the value it belongs to, so activation adjoints must carry it. Where those two rules disagree, a sum appears, and that is the only place a sum can appear.

    SOLUTION

    Step 1 — forward shapes. The batch dimension is untouched by every layer:

    ℓ\ellW(ℓ)\mat{W}^{(\ell)}Z(ℓ)\mat{Z}^{(\ell)}A(ℓ)\mat{A}^{(\ell)}
    1784×512784 \times 51232×51232 \times 51232×51232 \times 512
    2512×256512 \times 25632×25632 \times 25632×25632 \times 256
    3256×128256 \times 12832×12832 \times 12832×12832 \times 128
    4128×10128 \times 1032×1032 \times 1032×1032 \times 10

    Step 2 — adjoints. Δ(ℓ)=∂L/∂Z(ℓ)\Delta^{(\ell)} = \partial L / \partial \mat{Z}^{(\ell)} has the shape of Z(ℓ)\mat{Z}^{(\ell)}, so it carries the batch: 32×51232 \times 512, 32×25632 \times 256, 32×12832 \times 128, 32×1032 \times 10.

    Step 3 — the weight gradient absorbs the batch. Per example the gradient is the outer product of (I.6.4). Over a batch it is the sum of those outer products, which is a single matrix product:

    ∂L∂W(ℓ)=(A(ℓ−1))TΔ(ℓ)\frac{\partial L}{\partial \mat{W}^{(\ell)}} = (\mat{A}^{(\ell-1)})^{\mathsf{T}} \Delta^{(\ell)}

    with shapes (dℓ−1×32)(32×dℓ)→dℓ−1×dℓ(d_{\ell-1} \times 32)(32 \times d_\ell) \to d_{\ell-1} \times d_\ell. The 32 is contracted away — it is the inner dimension of the product. The summation over the batch is not an extra step anybody writes; it is what the matrix product already does.

    ℓ\ell∂L/∂W(ℓ)\partial L / \partial \mat{W}^{(\ell)}∂L/∂b(ℓ)\partial L / \partial \vec{b}^{(\ell)}
    1784×512784 \times 5121×5121 \times 512
    2512×256512 \times 2561×2561 \times 256
    3256×128256 \times 1281×1281 \times 128
    4128×10128 \times 101×101 \times 10

    Step 4 — the bias gradient needs an explicit sum. Per example ∂L/∂b(ℓ)=δ(ℓ)\partial L / \partial \vec{b}^{(\ell)} = \delta^{(\ell)}, of shape 1×dℓ1 \times d_\ell. Over a batch there is no matrix product to hide the contraction in, so it is written out:

    ∂L∂b(ℓ)=∑n=1BΔn,:(ℓ)\frac{\partial L}{\partial \vec{b}^{(\ell)}} = \sum_{n=1}^{B} \Delta^{(\ell)}_{n,:}

    This is the asymmetry the Statement promised. Both gradients sum over the batch; the weight gradient’s sum is the inner dimension of a matrix product and the bias gradient’s is a reduction along an axis. Forgetting the second is a common implementation error and it is caught immediately by a shape check — 32×51232 \times 512 where 1×5121 \times 512 was required.

    Step 5 — what must be kept. The backward pass consumes A(ℓ−1)\mat{A}^{(\ell-1)} for (I.6.4) and Z(ℓ)\mat{Z}^{(\ell)} for φ′\varphi'. For ReLU the mask 1[Z>0]\mathbb{1}[\mat{Z} > 0] suffices, but counting stored floats:

    32×(784+512+256+128+10)=32×1690=54 08032 \times (784 + 512 + 256 + 128 + 10) = 32 \times 1690 = 54\,080

    At 4 bytes each that is 216 320216\,320 bytes, about 0.220.22 MB.

    Step 6 — compare with the parameters. I.5.B03 counted 567 434567\,434 parameters, 2.272.27 MB in fp32. At B=32B = 32 the activations are a tenth of the parameters. At B=1024B = 1024 they are 1 730 5601\,730\,560 floats, 6.926.92 MB — three times the parameters. Activation memory is linear in the batch and parameter memory is constant in it, so which term dominates is a property of the batch size and not of the model.

    Answer

    Adjoints carry the batch: 32×dℓ32 \times d_\ell at every layer. Parameter gradients do not: ∂L/∂W(ℓ)\partial L/\partial \mat{W}^{(\ell)} is dℓ−1×dℓd_{\ell-1} \times d_\ell and ∂L/∂b(ℓ)\partial L/\partial \vec{b}^{(\ell)} is 1×dℓ1 \times d_\ell. The weight gradient’s batch sum is the inner dimension of (A(ℓ−1))TΔ(ℓ)(\mat{A}^{(\ell-1)})^{\mathsf{T}} \Delta^{(\ell)}; the bias gradient’s is an explicit reduction over rows.

    Stored activations at B=32B = 32: 54 08054\,080 floats, 216 320216\,320 bytes, 0.220.22 MB — against 2.272.27 MB of parameters. At B=1024B = 1024: 6.926.92 MB, three times the parameters.

    Check — sanity

    No parameter gradient carries a batch dimension. It cannot: the parameter does not have one, and a gradient has the shape of what it differentiates. Any implementation returning a 32×784×51232 \times 784 \times 512 weight gradient has computed per-example gradients and not yet reduced them — sometimes deliberately, which is what per-sample gradient methods want, but never by accident.

    Every adjoint does carry it. Different examples have different activations and therefore different adjoints, and they stay separate until a parameter is reached. The batch dimension survives exactly as far as the parameters and no further.

    The two contractions are the same contraction. Both gradients sum BB terms; only the notation differs. Writing the bias as a row of ones times Δ(ℓ)\Delta^{(\ell)} makes them identical in form, which is a good way to remember that the missing sum is missing rather than unnecessary.

    Where this breaks

    The audit counts the activations this architecture stores, and that count is a floor rather than an estimate. A real framework also holds the gradient buffer for every parameter, the optimiser state — two more copies for Adam — and workspace for the matrix products. It may also keep both Z(ℓ)\mat{Z}^{(\ell)} and A(ℓ)\mat{A}^{(\ell)} where one would do, since whether φ\varphi can be inverted from its output is a per-operation question the memory planner may not answer.

    The larger omission is that the numbers here are for an MLP, where activations are B×dB \times d. For a convolutional network they are B×C×H×WB \times C \times H \times W and for a transformer the attention matrices are B×h×T×TB \times h \times T \times T, quadratic in sequence length. The shape of the bill changes completely with the architecture even though the rule producing it — keep what the backward pass will read — does not.

    Variation

    Run the same audit with the batch dimension last, so activations are dℓ×Bd_\ell \times B. Every shape transposes and every product reverses. Which of the two batch contractions is now the explicit one, and which is hidden inside a matrix product?

    Problem I.6.B04

    A depth at which the first layer receives nothing

    counterexample▲▲▲

    Six significant figures; fp32's smallest normal is 1.17549435e-38.

    STATEMENT

    Backpropagation is exact. Show that exactness is not sufficiency: exhibit a network on which the gradient reaching the first layer is correct to the last bit and numerically zero, and establish the depth at which that happens. Then show that the obvious remedy — larger weights — makes it worse.

    GIVEN

    A chain of scalar sigmoid units, the simplest object with the property under study:

    a(0)=0.5,z(ℓ)=w a(ℓ−1),a(ℓ)=σ(z(ℓ))a^{(0)} = 0.5, \qquad z^{(\ell)} = w\,a^{(\ell-1)}, \qquad a^{(\ell)} = \sigma(z^{(\ell)})

    for ℓ=1…L\ell = 1 \dots L, with one shared weight ww and no biases. The adjoint leaving the loss is normalised to 11.

    FIND

    A bound on ∣∂L/∂a(0)∣|\partial L/\partial a^{(0)}| in terms of LL alone; the measured ratio at L=10L = 10 and L=40L = 40 for w=1w = 1; the smallest LL at which the ratio falls below fp32’s smallest normal number; and the effect of raising ww.

    STRATEGY

    Apply the recurrence (I.6.3) down the chain and collect factors. Each layer contributes two: the weight, and the slope of the nonlinearity at the stored pre-activation. The second factor has a ceiling that does not depend on anything trainable, and a product of numbers below a ceiling is bounded by the ceiling raised to the depth.

    SOLUTION

    Step 1 — the bound. For the logistic function σ′=σ(1−σ)\sigma' = \sigma(1-\sigma), maximised where σ=1/2\sigma = 1/2:

    max⁡zσ′(z)=12(1−12)=14\max_z \sigma'(z) = \tfrac{1}{2}\left(1 - \tfrac{1}{2}\right) = \tfrac{1}{4}

    The run confirms σ′(0)=0.250000\sigma'(0) = 0.250000. Now walk the chain back. Each layer multiplies the travelling adjoint by ww and by σ′(z(ℓ))\sigma'(z^{(\ell)}), so

    ∂L∂a(0)=∏ℓ=1Lw σ′(z(ℓ)),∣∂L∂a(0)∣≤(∣w∣4)L\frac{\partial L}{\partial a^{(0)}} = \prod_{\ell=1}^{L} w\,\sigma'(z^{(\ell)}), \qquad \left|\frac{\partial L}{\partial a^{(0)}}\right| \le \left(\frac{|w|}{4}\right)^{L}

    With w=1w = 1 this is 4−L4^{-L}, and nothing about the data, the loss or the training procedure can raise it. It is a property of the architecture.

    Step 2 — the measurement. Propagating the adjoint explicitly:

    depthmeasured ratiobound 4−L4^{-L}
    12.350037×10−12.350037 \times 10^{-1}2.5×10−12.5 \times 10^{-1}
    25.340838×10−25.340838 \times 10^{-2}6.25×10−26.25 \times 10^{-2}
    56.079863×10−46.079863 \times 10^{-4}9.765625×10−49.765625 \times 10^{-4}
    103.483116×10−73.483116 \times 10^{-7}9.536743×10−79.536743 \times 10^{-7}
    201.143096×10−131.143096 \times 10^{-13}9.094947×10−139.094947 \times 10^{-13}
    401.231153×10−261.231153 \times 10^{-26}8.271806×10−258.271806 \times 10^{-25}

    The measured values sit below the bound at every depth, as they must, and the gap widens because the units are not all sitting at their maximum slope.

    Step 3 — the depth where it stops being a small number. fp32’s smallest normal value is 1.17549435×10−381.17549435 \times 10^{-38}. The first depth at which the ratio falls below it is

    L=59,ratio 5.901063×10−39L = 59, \qquad \text{ratio } 5.901063 \times 10^{-39}

    At that depth the gradient of the first layer is not small. It is unrepresentable: it enters the subnormal range, where fp32 has almost no precision left, and one more layer flushes it to exactly zero. The parameter receives an update of zero and stays at its initialisation for the whole of training, while the last layers train perfectly well. The loss falls, then stalls, and nothing about the curve says which layers stopped.

    Step 4 — why more weight makes it worse. The bound (∣w∣/4)L(|w|/4)^L suggests ∣w∣>4|w| > 4 should grow the adjoint rather than shrink it. Measured:

    wwdepth 10depth 40
    1.03.483116×10−73.483116 \times 10^{-7}1.231153×10−261.231153 \times 10^{-26}
    2.02.954702×10−62.954702 \times 10^{-6}1.227533×10−231.227533 \times 10^{-23}
    4.05.486035×10−115.486035 \times 10^{-11}1.536491×10−441.536491 \times 10^{-44}
    4.51.449225×10−121.449225 \times 10^{-12}3.182489×10−513.182489 \times 10^{-51}

    Doubling ww to 2 helps a little. Raising it to 4 makes the gradient at depth 40 eighteen orders of magnitude smaller than at w=1w = 1. The bound is not wrong; it is not tight, and the reason is that the two factors are not independent. A large ww drives zz away from zero, and a sigmoid away from zero is saturated, so σ′(z)\sigma'(z) collapses. The weight enters the product linearly and takes the slope down exponentially with it.

    This is what makes the failure a counterexample rather than a difficulty. There is no setting of the free parameter that rescues it: small ww loses to the factor ww, large ww loses to saturation, and the best case is a shallow optimum near w=2w = 2 that still vanishes, only more slowly.

    Answer

    ∣∂L/∂a(0)∣≤(∣w∣/4)L|\partial L/\partial a^{(0)}| \le (|w|/4)^{L}, so with w=1w = 1 the adjoint is bounded by 4−L4^{-L} regardless of training.

    Measured at w=1w = 1: 3.483116×10−73.483116 \times 10^{-7} at depth 10, and 1.231153×10−261.231153 \times 10^{-26} at depth 40. The ratio falls below fp32’s smallest normal at L=59L = 59, where it is 5.901063×10−395.901063 \times 10^{-39}.

    Raising ww does not help: at depth 40, w=4w = 4 gives 1.536491×10−441.536491 \times 10^{-44}, worse than w=1w = 1 by eighteen orders of magnitude, because a large weight saturates the sigmoid and its slope falls faster than the weight rises.

    Check — sanity

    Every measured ratio is below its bound. 4−L4^{-L} was derived from σ′≤1/4\sigma' \le 1/4 with no reference to the values, so a measurement above it would mean the propagation is wrong rather than that the bound is loose.

    Depth 1 nearly attains the bound. 0.2350040.235004 against 0.250.25: the first unit sees z=0.5z = 0.5, close enough to zero that its slope is close to maximal. The later units see larger zz, saturate more, and the product falls away from the bound — which is why the gap at depth 40 is two orders of magnitude.

    The arithmetic of the exponent checks out. 4−59=8.6×10−364^{-59} = 8.6 \times 10^{-36}, and the measured 5.9×10−395.9 \times 10^{-39} is about 1500 times smaller — consistent with a mean slope near 0.230.23 rather than 0.250.25, since (0.23/0.25)59≈0.0075(0.23/0.25)^{59} \approx 0.0075.

    Where this breaks

    The chain here is one unit wide, and width changes the constant without changing the mechanism. In a real layer the adjoint is multiplied by (W(ℓ))T(\mat{W}^{(\ell)})^{\mathsf{T}} rather than by a scalar, so the relevant quantity is that matrix’s largest singular value and the product is over matrices rather than numbers. A width-512 layer can have a spectral norm well above one while every individual weight is small, which is why the same architecture can vanish in one initialisation and explode in another.

    The demonstration also uses the logistic function, whose 1/41/4 ceiling is what makes the bound so sharp. ReLU has slope exactly 11 wherever it is active, so this particular argument does not apply to it at all — and the failure returns in a different form, as dead units whose slope is exactly 00 rather than merely small. The general lesson is about products of Jacobians, and the specific number 1/41/4 belongs to one activation function.

    Nothing here is an argument against depth. It is an argument that depth needs a route the product cannot close, which is exactly (I.6.5): a residual block contributes I+JF\mat{I} + \mat{J}_F, and the identity term keeps a path of gain one open however many blocks are stacked.

    Variation

    Replace σ\sigma with tanh⁡\tanh, whose derivative is 1−tanh⁡21 - \tanh^2 and whose ceiling is 11 rather than 1/41/4. Predict the new bound, then find the depth at which the ratio underflows fp32 and compare it with 59. Does a four-times better ceiling buy four times the depth?

    Problem I.6.B05

    What the backward pass costs, in arithmetic and in memory

    complexity▲▲△

    GFLOP to two decimals, GB to three, at 4 bytes per fp32 value.

    STATEMENT

    Account for both resources a backward pass consumes. Show that the arithmetic is a small multiple of the forward pass and that the memory is not, find the batch size at which stored activations overtake parameters, and quantify what checkpointing buys.

    GIVEN

    A stack of L=48L = 48 square affine layers of width d=1024d = 1024, in fp32. Two working regimes: a batch of 32 rows, and a batch of 32 sequences of length 512, which presents 32×512=16 38432 \times 512 = 16\,384 rows to every layer.

    FIND

    Forward and backward FLOPs and their ratio; activation memory against parameter memory in both regimes; the batch at which the two are equal; and the memory and arithmetic under L\sqrt{L} checkpointing.

    STRATEGY

    Count matrix products, since everything else is lower order. Forward, each layer does one. Backward, by (I.6.3) and (I.6.4), each layer does two — one to send the adjoint onward and one to form the weight gradient. For memory, count what the backward pass will read and cannot recompute for free.

    SOLUTION

    Step 1 — arithmetic. A product of a B×dB \times d matrix with a d×dd \times d matrix costs 2Bd22Bd^2 floating-point operations, counting a multiply and an add for each of the Bd2Bd^2 pairs. Forward:

    F=L⋅2Bd2F = L \cdot 2Bd^2

    Backward does two such products per layer, so 2F2F, and the step costs 3F3F:

    regimeforwardbackwardtotal / forward
    batch 323.223.22 GFLOP6.446.44 GFLOP3.0×3.0\times
    batch 32×51232 \times 5121649.271649.27 GFLOP3298.533298.53 GFLOP3.0×3.0\times

    The ratio is 3 in both regimes and would be 3 for any BB, dd and LL: it is a property of the graph, not of the size. This is the cheap gradient principle (I.6.T1) at work, and the constant here — three — sits comfortably inside the c≤4c \le 4 the theorem allows.

    Step 2 — memory. The parameters are Ld2=50 331 648Ld^2 = 50\,331\,648 values, 0.2010.201 GB. The activations kept for the backward pass are LBdLBd by (I.6.7):

    regimeactivationsparametersratio
    batch 320.0060.006 GB0.2010.201 GB0.03×0.03\times
    batch 32×51232 \times 5123.2213.221 GB0.2010.201 GB16.00×16.00\times

    The same model, the same weights, the same arithmetic per row — and the memory picture inverts completely. At batch 32 the parameters dominate by thirty times; at batch 16 384 the activations dominate by sixteen.

    Step 3 — the crossover. Setting LBd=Ld2LBd = Ld^2 gives

    B=d=1024B = d = 1024

    The layer count cancels, and so does everything else. Activations overtake parameters when the batch reaches the width, which for this model is 1024 rows — a number a single sequence of moderate length already exceeds. That is why the second regime is the normal one and the first is the exception.

    Step 4 — checkpointing. Keep every L\sqrt{L}-th activation and recompute the rest during the backward pass. With L=48L = 48, 48≈7\sqrt{48} \approx 7: hold 7 segment boundaries, plus the 6 tensors of whichever segment is currently being recomputed, so 13 tensors instead of 48.

    3.221 GB⟶0.872 GB,3.7× less3.221\ \text{GB} \longrightarrow 0.872\ \text{GB}, \qquad 3.7\times \text{ less}

    The price is recomputing each segment’s forward pass once, which adds one forward pass to a step that cost three:

    2F+2FF+2F=43=1.33×\frac{2F + 2F}{F + 2F} = \frac{4}{3} = 1.33\times

    A third more arithmetic for nearly four times less memory. On a device where memory is the binding constraint and arithmetic is not — which describes most training — that is not a close decision.

    Answer

    Arithmetic: backward is exactly twice forward, so a step costs 3×3\times a forward pass, independent of BB, dd and LL.

    Memory: activations are LBdLBd and parameters Ld2Ld^2, so they are equal when B=d=1024B = d = 1024. At batch 32 activations are 0.03×0.03\times the parameters; at batch 32×51232 \times 512 they are 16×16\times — 3.2213.221 GB against 0.2010.201 GB.

    Checkpointing at 7 segments holds 13 tensors instead of 48: 3.2213.221 GB becomes 0.8720.872 GB, a 3.7×3.7\times reduction, for 1.33×1.33\times the arithmetic.

    Check — sanity

    The FLOP ratio is exactly 3 in both regimes. It has to be — it is a count of matrix products per layer, one forward and two back, and no dimension enters it. A measured ratio far from 3 on real hardware is a statement about memory bandwidth and kernel efficiency, not about the arithmetic.

    The crossover has no free parameters. LBd=Ld2LBd = Ld^2 reduces to B=dB = d with LL cancelling, so the answer cannot depend on depth. A deeper model has more activations and more parameters, in the same proportion.

    Checkpointed memory is near 2L2\sqrt{L}. 248≈13.92\sqrt{48} \approx 13.9 against the 13 tensors counted, and 48/13=3.748/13 = 3.7 matches the measured reduction. The L\sqrt{L} of (I.6.7) is where boundary storage and segment storage balance; away from it, one term or the other grows.

    Where this breaks

    Every number here counts one thing each and ignores the rest of the bill. The gradient buffer is another copy of the parameters, and Adam adds two more, so the parameter side of the ledger is nearer 4×0.2014 \times 0.201 GB than 0.2010.201 GB before any activation is stored — a point I.7 makes properly. Workspace for the matrix kernels, fragmentation and the framework’s own overhead are all real and none is counted.

    The FLOP count is also not a time. It assumes every operation costs the same, which on a device with a hundred times more arithmetic throughput than memory bandwidth is badly wrong: the backward pass touches more memory per FLOP than the forward pass and is routinely slower than the 2×2\times its arithmetic suggests. The 3×3\times is a lower bound on the time ratio and an exact statement about the arithmetic, and confusing the two is how a training run comes in over its estimate.

    Finally, checkpointing assumes recomputation is deterministic. A segment containing dropout, or any other sampling, recomputes a different forward pass unless the random state is stored and restored — and the gradient then belongs to neither the first forward pass nor the second.

    Variation

    Recompute the crossover for a model whose layers are not square: width dd in and 4d4d out, as a transformer’s feed-forward block is. The parameter count gains a factor of 4 and the stored activation gains one too. Does the crossover move?

    Exercises

    Every one has a published solution. A hidden solution is a solution; a missing one is an abandonment.

    I.6.X01One extra local Jacobian rescales every gradient below itnumeric▲△△

    Take the network of I.6.B01 and replace the linear head with a sigmoid, so y^=σ(z(2))\hat{y} = \sigma(z^{(2)}). Everything else is unchanged. Predict which of the nine gradients move and in what proportion before recomputing, then verify.

    Hint

    The change adds exactly one node to the graph, between z(2)z^{(2)} and the loss. By (I.6.2), what does an extra node on the path from the sink do to every adjoint below it?

    Solution

    The prediction. A new node sits between z(2)z^{(2)} and LL. Every adjoint in the graph is reached through it, so every one is multiplied by the same scalar — gradients change, but all by a common factor. Nothing changes shape and nothing changes sign relative to the others.

    The numbers.

    quantitylinear headsigmoid head
    y^\hat{y}0.050114800.050114800.512526080.51252608
    LL0.451140950.451140950.118815410.11881541
    δ(2)\delta^{(2)}−0.94988520-0.94988520−0.12179199-0.12179199
    ∂L/∂W(2)\partial L/\partial \mat{W}^{(2)}−0.59126491, −0.73001032-0.59126491,\ -0.73001032−0.07581056, −0.09360017-0.07581056,\ -0.09360017
    δ(1)\delta^{(1)}−0.11161327, 0.10138758-0.11161327,\ 0.10138758−0.01431079, 0.01299967-0.01431079,\ 0.01299967

    The common factor. Every ratio is identical to eight decimals:

    δσ(2)δlin(2)=∂L/∂W1(2)∣σ∂L/∂W1(2)∣lin=∂L/∂W11(1)∣σ∂L/∂W11(1)∣lin=0.12821759\frac{\delta^{(2)}_{\sigma}}{\delta^{(2)}_{\text{lin}}} = \frac{\partial L/\partial W^{(2)}_1|_\sigma}{\partial L/\partial W^{(2)}_1|_{\text{lin}}} = \frac{\partial L/\partial W^{(1)}_{11}|_\sigma}{\partial L/\partial W^{(1)}_{11}|_{\text{lin}}} = 0.12821759

    Where it comes from. It is not σ′(z(2))\sigma'(z^{(2)}) alone, because the head also changed the prediction and therefore the loss. Decomposing:

    z(2)=0.05011480,σ(z(2))=0.51252608,σ′(z(2))=0.24984310z^{(2)} = 0.05011480, \qquad \sigma(z^{(2)}) = 0.51252608, \qquad \sigma'(z^{(2)}) = 0.24984310(σ(z(2))−y) σ′(z(2))z(2)−y=(−0.48747392)(0.24984310)−0.94988520=0.12821759\frac{(\sigma(z^{(2)}) - y)\,\sigma'(z^{(2)})}{z^{(2)} - y} = \frac{(-0.48747392)(0.24984310)}{-0.94988520} = 0.12821759

    Two effects: the sigmoid moved the prediction closer to the target, shrinking the residual from −0.94988520-0.94988520 to −0.48747392-0.48747392, and then contributed its own slope of 0.249843100.24984310. The product of the two is the factor.

    Why the factor is uniform. By (I.6.2) each adjoint is the one above it times a local Jacobian, and the new node is above all of them. A common factor at the sink propagates unchanged through every subsequent multiplication — which is also why scaling a loss by a constant scales every gradient by that constant, and why loss scaling in mixed-precision training is sound rather than a heuristic.

    What it costs. The slope 0.249843100.24984310 is near the sigmoid’s ceiling of 0.250.25, because z(2)z^{(2)} happens to be near zero. Had the head been confident — z(2)=6z^{(2)} = 6, say — the slope would be 0.00250.0025 and every gradient in the network would be four hundred times smaller. That is the saturation term of I.6.B04 arriving at the output layer instead of in the middle, and it is the reason a sigmoid head is paired with cross-entropy rather than squared error: I.6.X02 shows the pairing that cancels it.

    I.6.X05Where the batch dimension goes, and when you want it keptshape▲△△

    A layer has W∈R256×128\mat{W} \in \R^{256 \times 128} and sees a batch A∈R64×256\mat{A} \in \R^{64 \times 256}, producing adjoints Δ∈R64×128\Delta \in \R^{64 \times 128}. Give the shape of ∂L/∂W\partial L/\partial \mat{W} and say where the 64 went. Then give the shape of the object a per-sample gradient method needs instead, and its size in fp32.

    Hint

    A gradient has the shape of the thing it differentiates. W\mat{W} has no batch dimension, so the answer is forced — the question is only which operation removes it.

    Solution

    The shape. ∂L/∂W\partial L/\partial \mat{W} is 256×128256 \times 128, the shape of W\mat{W}. The batch is contracted away by the product of (I.6.4):

    ∂L∂W=ATΔ,(256×64)(64×128)→256×128\frac{\partial L}{\partial \mat{W}} = \mat{A}^{\mathsf{T}}\Delta, \qquad (256 \times 64)(64 \times 128) \to 256 \times 128

    The 64 is the inner dimension. Nobody writes a sum over the batch because the matrix product already is one — it adds the 64 per-example outer products together as it goes.

    What is lost. The sum is not invertible. Once formed, ATΔ\mat{A}^{\mathsf{T}}\Delta cannot be taken apart into the 64 contributions that made it, so any method needing individual examples must intervene before the contraction rather than after it.

    The per-sample object. That is the stack of outer products, one per example:

    G∈R64×256×128,Gn=An,:TΔn,:\mat{G} \in \R^{64 \times 256 \times 128}, \qquad \mat{G}_n = \mat{A}_{n,:}^{\mathsf{T}} \Delta_{n,:}

    64×256×128=2 097 15264 \times 256 \times 128 = 2\,097\,152 values, 8 388 6088\,388\,608 bytes in fp32 — 8.48.4 MB for one layer’s gradient at batch 64, against 131 072131\,072 bytes for the reduced version. Sixty-four times the memory, which is the batch size, and that is the whole cost of the method.

    Who needs it. Differentially private training clips each example’s gradient to a fixed norm before averaging, which is meaningless once they are summed. Influence functions and per-example importance weighting want the same object. All of them pay the same B×B\times memory, and the usual response is to recompute per-sample gradients layer by layer rather than hold them all at once.

    I.6.X02The pairing that collapses a dense Jacobian to a subtractiongradient▲▲△

    Softmax is not elementwise, so I.6.B02’s Step 1 does not apply to it: its Jacobian is dense. Derive ∂L/∂z\partial L/\partial \vec{z} for softmax composed with cross-entropy, show the dense product collapses, and then show what the same softmax does when paired with squared error instead.

    Hint

    Write out ∂pj/∂zi=pj(δij−pi)\partial p_j/\partial z_i = p_j(\delta_{ij} - p_i) and ∂L/∂pj=−yj/pj\partial L/\partial p_j = -y_j/p_j, then form the sum over jj. Most of it cancels.

    Solution

    The two pieces. For p=softmax(z)\vec{p} = \mathrm{softmax}(\vec{z}) and L=−∑jyjlog⁡pjL = -\sum_j y_j \log p_j with y\vec{y} one-hot:

    ∂pj∂zi=pj(δij−pi),∂L∂pj=−yjpj\frac{\partial p_j}{\partial z_i} = p_j(\delta_{ij} - p_i), \qquad \frac{\partial L}{\partial p_j} = -\frac{y_j}{p_j}

    The Jacobian is d×dd \times d and dense — for a 50 000-token vocabulary that is 2.5×1092.5 \times 10^9 entries, which is why nobody forms it.

    The collapse. By (I.6.2), summing over consumers:

    ∂L∂zi=∑j(−yjpj)pj(δij−pi)=−∑jyj(δij−pi)=−yi+pi∑jyj\frac{\partial L}{\partial z_i} = \sum_j \left(-\frac{y_j}{p_j}\right) p_j(\delta_{ij} - p_i) = -\sum_j y_j(\delta_{ij} - p_i) = -y_i + p_i \sum_j y_j

    and ∑jyj=1\sum_j y_j = 1 for a one-hot target, so

    ∂L∂z=p−y\frac{\partial L}{\partial \vec{z}} = \vec{p} - \vec{y}

    The pjp_j in the Jacobian cancels the pjp_j in the denominator of the loss derivative. That cancellation is the only reason this pairing is cheap.

    Verified. With logits (2.0, 1.0, 0.1)(2.0,\ 1.0,\ 0.1) and the true class 00:

    class 0class 1class 2
    p\vec{p}0.659001140.659001140.242432970.242432970.098565890.09856589
    p−y\vec{p} - \vec{y}−0.34099886-0.34099886+0.24243297+0.24243297+0.09856589+0.09856589
    via the Jacobian−0.34099886-0.34099886+0.24243297+0.24243297+0.09856589+0.09856589

    Largest disagreement 1.39×10−171.39 \times 10^{-17} — machine precision. Two routes, one of which forms a dense matrix and one of which subtracts two vectors.

    The other pairing. Replace cross-entropy with squared error and there is no 1/pj1/p_j to cancel the pjp_j. The Jacobian survives:

    class 0class 1class 2
    δ\delta−0.12176312-0.12176312+0.09664897+0.09664897+0.02511415+0.02511415
    ratio to p−y\vec{p} - \vec{y}0.357077780.357077780.398662630.398662630.254795550.25479555

    Smaller, and by a different factor per class — not a rescaling, a distortion.

    Why it matters most when the model is wrong. Take logits (8,0,0)(8, 0, 0) with the true class 22: the model is confident and wrong, p=(0.99932952, 0.00033524, 0.00033524)p = (0.99932952,\ 0.00033524,\ 0.00033524).

    Cross-entropy gives δ2=−0.99966476\delta_2 = -0.99966476 — a full-strength signal, near the largest it can be. Squared error routes through the softmax slope on the true class, which is 3.35125324×10−43.35125324 \times 10^{-4}: a gradient three thousand times smaller, at the exact moment the model most needs correcting.

    The general point. A loss and its output nonlinearity are chosen together, not separately. Sigmoid with cross-entropy collapses the same way, for the same reason, to y^−y\hat{y} - y — which is why I.6.B01’s linear head with squared error also gave δ(2)=y^−y\delta^{(2)} = \hat{y} - y, and why frameworks expose a single fused cross_entropy that takes logits rather than a softmax the caller composes themselves. The fusion is not an optimisation; it is where the cancellation lives.

    I.6.X03The vector–Jacobian product, without forming the Jacobiansymbolic▲▲△

    For y=xW\vec{y} = \vec{x}\mat{W} with x∈R1×n\vec{x} \in \R^{1 \times n} and W∈Rn×m\mat{W} \in \R^{n \times m}, write down the Jacobian ∂y/∂x\partial \vec{y}/\partial \vec{x} explicitly, count its entries, and then show that the quantity backpropagation actually needs — yˉJ\bar{\vec{y}}\mat{J} — can be computed without ever building it. State the cost of each route.

    Hint

    Ask what ∂yj/∂xi\partial y_j / \partial x_i is for this particular operation. The answer does not depend on x\vec{x} at all, which is why the product collapses.

    Solution

    The Jacobian. yj=∑ixiWijy_j = \sum_i x_i W_{ij}, so

    ∂yj∂xi=Wij\frac{\partial y_j}{\partial x_i} = W_{ij}

    The Jacobian is W\mat{W}, with nmnm entries. For a modest layer with n=m=1024n = m = 1024 that is 1 048 5761\,048\,576 numbers.

    The product. By Definition 7 the backward pass needs xˉ=yˉJT\bar{\vec{x}} = \bar{\vec{y}}\mat{J}^{\mathsf{T}}, and since J=W\mat{J} = \mat{W}:

    xˉ=yˉWT\bar{\vec{x}} = \bar{\vec{y}}\mat{W}^{\mathsf{T}}

    which is one matrix–vector product of exactly the shape the forward pass already performed. No new object is constructed.

    The costs. Forming J\mat{J} and multiplying costs O(nm)O(nm) storage and O(nm)O(nm) arithmetic. The VJP costs O(nm)O(nm) arithmetic and O(n+m)O(n + m) storage. The arithmetic is the same; the storage is not, and storage is what decides whether the layer fits.

    Why this generalises. The saving is not special to matrix products. For every primitive the derivative is known in closed form, and in every case the closed form is a rule for computing yˉJ\bar{\vec{y}}\mat{J} rather than a recipe for J\mat{J} — elementwise operations give a Hadamard product, a sum gives a broadcast, a broadcast gives a sum. A system that materialised Jacobians would be quadratic in width at every layer and could not train anything.

    Where it stops working. A second derivative needs the Jacobian of the Jacobian, and the same trick applies one level up — a Hessian–vector product is computable without the Hessian, by differentiating a VJP. But asking for the full Hessian of a model with NN parameters really does mean N2N^2 numbers, and no reformulation avoids it.

    I.6.X04A tied weight that receives half its gradientcounterexample▲▲△

    A scalar weight ww is used twice in the same graph:

    u=wx,v=wu,L=12v2u = wx, \qquad v = wu, \qquad L = \tfrac{1}{2}v^2

    Compute ∂L/∂w\partial L/\partial w correctly. Then compute what a backward pass would produce if each use assigned into the gradient slot instead of adding to it, and say why the resulting number is not merely inaccurate.

    Hint

    ww has two consumers. (I.6.2) says its adjoint is a sum over them, and an assignment keeps whichever term arrives last.

    Solution

    Correct. Substituting, v=w2xv = w^2x and L=12w4x2L = \tfrac{1}{2}w^4x^2, so

    ∂L∂w=2w3x2\frac{\partial L}{\partial w} = 2w^3x^2

    By the graph. vˉ=v=w2x\bar{v} = v = w^2x. The node v=wuv = wu has two producers:

    wˉ∣v=vˉu=w2x⋅wx=w3x2,uˉ=vˉw=w3x\bar{w}\big|_{v} = \bar{v}u = w^2x \cdot wx = w^3x^2, \qquad \bar{u} = \bar{v}w = w^3x

    The node u=wxu = wx then contributes

    wˉ∣u=uˉx=w3x2\bar{w}\big|_{u} = \bar{u}x = w^3x^2

    By (I.6.2) the adjoint of ww is the sum of the contributions from its consumers:

    wˉ=w3x2+w3x2=2w3x2\bar{w} = w^3x^2 + w^3x^2 = 2w^3x^2

    which matches the direct computation.

    With assignment instead of accumulation. Whichever contribution is written last survives, and each is exactly w3x2w^3x^2 — half the truth. The gradient is not noisy, not biased in a complicated way, and not sometimes right: it is consistently a factor of two short.

    Why that is worse than it sounds. A gradient scaled by a constant is still a descent direction, so training does not diverge or produce obvious garbage. It converges, more slowly, and looks like a learning-rate problem. Now make the two uses asymmetric — an embedding matrix that is also the output projection, a convolutional filter applied at 4096 positions, a recurrent matrix at 512 timesteps — and the surviving term is one of many, in a proportion that varies per example. That is not a rescaled gradient; it is the gradient of a different function, and it descends confidently to the wrong place.

    Which is why frameworks accumulate. .grad is added into, never assigned, and the mirror-image bug follows immediately: forget to zero it between steps and every step’s gradient is the sum of all steps so far. Both errors come from the same line of (I.6.2), read in opposite directions.

    I.6.X06The one point where ReLU has no derivativelimit▲▲△

    ReLU(z)=max⁡(0,z)\mathrm{ReLU}(z) = \max(0, z) is not differentiable at z=0z = 0. Compute the left and right limits of the difference quotient there, say what a framework returns instead, and explain why a chapter built on the chain rule can proceed anyway.

    Hint

    The chain rule requires differentiability at the point evaluated. Ask how often a floating-point pre-activation is exactly zero, and what happens when it is.

    Solution

    The limits disagree.

    lim⁡h→0−max⁡(0,h)−0h=lim⁡h→0−0h=0,lim⁡h→0+hh=1\lim_{h \to 0^-} \frac{\max(0, h) - 0}{h} = \lim_{h \to 0^-} \frac{0}{h} = 0, \qquad \lim_{h \to 0^+} \frac{h}{h} = 1

    No derivative exists at 00. Every value in [0,1][0, 1] is a subgradient, and the subdifferential is that whole interval.

    What is returned. PyTorch, TensorFlow and JAX all return 00. That is a choice among the valid subgradients, not a derivation, and the frameworks differ in principle even where they agree in practice — nothing in the mathematics selects 00 over 11 or 1/21/2.

    Why it does not matter much. The set of inputs producing exactly z=0z = 0 has measure zero in the parameter space, and in floating point it is reached only by exact cancellation. Training touches it approximately never, and when it does the choice moves one coordinate of one gradient by at most the adjoint arriving there.

    Why it is not nothing. Three cases make it visible. A network initialised with all-zero weights has every pre-activation exactly zero, so the convention decides whether anything moves at all — with 00 returned, nothing does, and the network never leaves its initialisation. A ReLU unit driven far negative has slope exactly 00 everywhere it is evaluated, not merely at one point, and stops receiving gradient permanently: the dead-unit failure, which is a statement about the whole negative half-line rather than about the kink. And when a learning rate is large enough to jump across the kink each step, the loss can oscillate in a way that looks like instability but is the function being genuinely non-smooth.

    The honest framing. Backpropagation as derived in I.6.B02 assumes differentiability at the evaluated point, and ReLU violates it on a set of measure zero. What is computed is a subgradient, descent methods for convex problems have a theory for subgradients, and this problem is not convex — so the practice is better supported by its results than by its theorems. Saying so is more useful than pretending the kink is not there.

    I.6.X07An in-place operation that corrupts a stored intermediatecounterexample▲▲△

    Consider u=wxu = wx, a=σ(u)a = \sigma(u), L=12(a−y)2L = \tfrac{1}{2}(a - y)^2, and suppose an implementation overwrites the buffer holding uu with aa to save memory. Give the gradient the backward pass then computes, compare it with the truth, and say why this is a different kind of error from the one in I.6.X04.

    Hint

    Which stored value does σ′\sigma' need? Write out what is read after the buffer has been overwritten.

    Solution

    The truth. aˉ=a−y\bar{a} = a - y, and through the nonlinearity

    uˉ=aˉ σ′(u)=(a−y) σ(u)(1−σ(u)),∂L∂w=uˉ x\bar{u} = \bar{a}\,\sigma'(u) = (a - y)\,\sigma(u)\big(1 - \sigma(u)\big), \qquad \frac{\partial L}{\partial w} = \bar{u}\,x

    After the overwrite. The buffer that held uu now holds a=σ(u)a = \sigma(u), so the backward pass evaluates σ′\sigma' at the wrong argument:

    uˉwrong=(a−y) σ(a)(1−σ(a))\bar{u}_{\text{wrong}} = (a - y)\,\sigma(a)\big(1 - \sigma(a)\big)

    With w=1w = 1, x=2x = 2, y=1y = 1: u=2u = 2, a=σ(2)=0.880797a = \sigma(2) = 0.880797. The correct slope is σ′(2)=0.104994\sigma'(2) = 0.104994; the computed one is σ′(0.880797)=0.207987\sigma'(0.880797) = 0.207987. The gradient is out by a factor of 1.981.98 — and the factor depends on uu, so it is different for every example and every step.

    Why this is worse than X04. The tied-weight bug produced a gradient that was consistently half the truth: wrong, but a fixed rescaling, and therefore still a descent direction. This one produces a state-dependent distortion. Examples with small uu are barely affected and examples with large uu are affected enormously, so the effective loss being descended is not the stated loss and not any fixed multiple of it. There is no learning rate that compensates.

    Why it is hard to catch. The shapes are unchanged, no exception is raised in principle, the loss still decreases, and the model still learns something. Only a gradient check against finite differences — I.6.B01’s last step — separates this from a correct implementation.

    What frameworks do. PyTorch versions every tensor and raises at backward time if a saved tensor was modified after being stored, which converts a silent numerical error into a loud one. That check is why in-place operations are sometimes rejected with an error rather than quietly producing this. The assumption it protects is the one this chapter states plainly: the stored intermediates must still be the values the forward pass produced.

    I.6.X08Choosing a mode is arithmetic, not preferencecomplexity▲▲△

    Reverse mode is not simply better than forward mode. For f:Rn→Rmf: \R^n \to \R^m, price both, decide which wins for four different tasks, and say what reverse mode pays for its advantage.

    Hint

    A forward sweep propagates a derivative with respect to one input; a reverse sweep propagates the derivative of one output. Count how many of each you need to fill a Jacobian.

    Solution

    The rule. Forward mode carries ∂⋅/∂xi\partial \cdot / \partial x_i for one input ii through the whole graph, so it fills one column of the Jacobian per sweep: nn sweeps. Reverse mode carries ∂yj/∂⋅\partial y_j / \partial \cdot for one output, filling one row per sweep: mm sweeps. That is I.6.T2, and everything else follows from it.

    tasknnmmforwardreversecheaper
    training a network10810^8110810^81reverse
    a scalar of few inputs3131reverse
    sensitivity of an ODE410610^6410610^6forward
    Jacobian of a 102421024^2 layer1024102410241024tie
    one directional derivative10810^810610^6110610^6forward

    Training. nn in the hundred millions, m=1m = 1. Reverse mode is cheaper by a factor of 10810^8 — the entire parameter count. This is the case the field is built around, and it is why “autodiff” and “backpropagation” are used interchangeably even though the first is much more general than the second.

    The last row is the interesting one. Both nn and mm are enormous, yet forward mode wins outright, because only one direction is wanted rather than the whole Jacobian. A single forward sweep seeded with that direction returns Jv\mat{J}\vec{v} — a Jacobian–vector product — at the price of one evaluation. Reverse mode has no way to produce it in fewer than mm sweeps.

    What reverse mode pays. Memory. Forward mode carries one extra value alongside each node and never looks back, so nothing is stored: 32 76832\,768 values live at any moment for the model of I.6.B05. Reverse mode must keep the whole tape to have the local Jacobians on the way back: 1 572 8641\,572\,864 values, 48×48\times more, exactly the depth of the network.

    That is the trade in one line — reverse mode buys a factor of nn in arithmetic with a factor of LL in memory, and the exchange is worth it precisely because n≫Ln \gg L.

    Where neither is cheap. The middle row: a full 1024×10241024 \times 1024 Jacobian costs 1024 sweeps either way. There is no mode that produces a dense Jacobian cheaply, and a method that needs one — some second-order and some sensitivity-analysis methods — is expensive for a reason no implementation detail will fix.

    I.6.X09What it costs when the sink is not a scalarcomplexity▲▲△

    This chapter assumes a scalar loss. Price what happens when that assumption is dropped: give the cost of a backward pass for a vector output of width mm, for per-example gradients across a batch, and for a full Jacobian — then say why the assumption is not the restriction it appears to be.

    Hint

    Reverse mode fills one row of the Jacobian per traversal. How many rows does each of these three objects have?

    Solution

    The model. L=48L = 48 layers of width 10241024 at batch 3232, as in I.6.B05. One forward pass is 3.223.22 GFLOP and one backward traversal is twice that.

    sinktraversalsGFLOPvs scalar
    a scalar loss19.669.661.0×1.0\times
    a 10-vector output1067.6567.657.0×7.0\times
    per-example gradients, batch 3232209.38209.3821.7×21.7\times
    full Jacobian of 1024 outputs10246600.296600.29683.0×683.0\times

    The pattern. Cost is F+2tFF + 2tF for tt traversals, so it is linear in tt and the ratios approach tt from below — the single shared forward pass is amortised across them. At t=1024t = 1024 the ratio is 683683 rather than 10241024 for exactly that reason.

    Per-example gradients are the same problem. A batch of 32 losses is a 32-vector sink, so 32 traversals. This is why the memory answer in I.6.X05 has a matching arithmetic answer: per-sample methods cost B×B\times in both resources, and neither cost is avoidable by clever bookkeeping — the object genuinely has BB times as many numbers in it.

    Why the assumption is not a restriction. A loss is a reduction. Any vector-valued objective is summed or averaged before differentiation, and that reduction is one more node in the graph — the sink where Lˉ=1\bar{L} = 1 starts. Summing 32 per-example losses costs 31 additions, which against a 3.223.22 GFLOP forward pass is a share of 9.6×10−99.6 \times 10^{-9}: free, to nine decimal places.

    So the scalar assumption costs nothing whenever a single number is what is wanted, and training is that case by construction. It bites only when the individual outputs are wanted separately — and then what is being asked for is a Jacobian, and the price above is the honest price of one.

    The one to remember. Multi-task learning with kk losses does not cost kk traversals, as long as the tasks are combined into one weighted sum before the backward pass. Wanting the kk gradients separately — to inspect their conflict, to normalise their scales — is what costs kk traversals, and the difference between those two situations is a modelling decision with a large and easily overlooked price attached.

    I.6.X10Why a skip connection cannot lose the gradientproof▲▲▲

    For a residual block y=x+F(x)\vec{y} = \vec{x} + F(\vec{x}), derive the Jacobian and then the adjoint recurrence for a stack of LL such blocks. Show that the product cannot vanish for want of a route, and state precisely what the identity term does and does not guarantee.

    Hint

    Differentiate the sum term by term. One of the two derivatives does not depend on FF at all.

    Solution

    The Jacobian. Differentiating y=x+F(x)\vec{y} = \vec{x} + F(\vec{x}) coordinate by coordinate:

    ∂yj∂xi=δij+∂Fj∂xi,soJ=I+JF\frac{\partial y_j}{\partial x_i} = \delta_{ij} + \frac{\partial F_j}{\partial x_i}, \qquad \text{so} \qquad \mat{J} = \mat{I} + \mat{J}_F

    which is (I.6.5).

    The recurrence. By (I.6.2) the adjoint entering block ℓ\ell from block ℓ+1\ell + 1 is

    xˉ(ℓ)=xˉ(ℓ+1)(I+JFℓ)T=xˉ(ℓ+1)+xˉ(ℓ+1)JFℓT\bar{\vec{x}}^{(\ell)} = \bar{\vec{x}}^{(\ell+1)}\left(\mat{I} + \mat{J}_{F_\ell}\right)^{\mathsf{T}} = \bar{\vec{x}}^{(\ell+1)} + \bar{\vec{x}}^{(\ell+1)}\mat{J}_{F_\ell}^{\mathsf{T}}

    Across the stack. Expanding the product over LL blocks:

    ∂L∂x(0)=xˉ(L)∏ℓ=1L(I+JFℓ)T\frac{\partial L}{\partial \vec{x}^{(0)}} = \bar{\vec{x}}^{(L)}\prod_{\ell=1}^{L}\left(\mat{I} + \mat{J}_{F_\ell}\right)^{\mathsf{T}}

    Multiplying out gives 2L2^L terms, one for each subset of blocks whose JFJ_F is taken. Exactly one of those terms takes the identity from every block:

    xˉ(L)IL=xˉ(L)\bar{\vec{x}}^{(L)}\mat{I}^L = \bar{\vec{x}}^{(L)}

    The adjoint that left the loss arrives at the first block undiminished, whatever the FℓF_\ell are and however many there are. In the plain stack of I.6.B04 every term carried a factor below 1/41/4, so the whole product decayed; here one term carries no factor at all.

    What this does not promise. Three things.

    It is not a guarantee that the gradient is useful. The identity path transmits the adjoint of the loss with respect to the block’s input, not with respect to its parameters — the parameter gradients still flow through JFℓ\mat{J}_{F_\ell}, and a block whose FF is saturated still learns nothing. The signal reaches it; whether it can act on it is separate.

    It does not prevent explosion. I+JF\mat{I} + \mat{J}_F has eigenvalues 1+λi1 + \lambda_i, and if JF\mat{J}_F has eigenvalues near +1+1 the product grows like 2L2^L. Residual stacks are normally paired with normalisation for exactly this reason, and the pairing is not decoration.

    It says nothing about optimisation beyond the gradient’s magnitude. A route for the adjoint is a necessary condition for training a deep stack, not a sufficient one, and the empirical fact that residual networks train at depths plain stacks cannot is a result rather than a corollary of this algebra.