Skip to content

Matrix calculus problem set: differentials, trace trick, VJPs, SDPA Jacobian

ModuleS-M08 · solve · none · Pass 2 · 8 to 10 h
You buildanswers in solve/S-M08.toml (42 checked by SymPy) and 4 derivations in solve/S-M08/qN.md (self-graded against their rubrics)
Contractnone: a pen and paper set
Testscourse/solve/S-M08/key.toml (hidden): typed answers plus reject canaries; the problems are in course/solve/S-M08/problems.md and in section 4
NeedsS-M05 (proof habits). Reading: the Matrix Calculus and Autodiff topic, and the gradients and chain rule of S-M04
Used byno call site (a solve set). It checks the derivations behind M08.3 (matmul_vjp, softmax_vjp, log_softmax_vjp, layernorm_vjp, rmsnorm_vjp, cross_entropy_vjp), which L0.2, L0.3, and L7.1 register as ops; q33 and q34 are the attention backward of L4.* and L9.4
MilestoneMS-P2 (the Pass 2 gate runs ol check on every solve part of the pass)
Optional depthParr and Howard, “The Matrix Calculus You Need for Deep Learning” (2018); Minka, “Old and New Matrix Algebra Useful for Statistics” (2000); Baydin et al., “Automatic Differentiation in Machine Learning: a Survey” (2018), sections 2 and 3; Dao et al., “FlashAttention” (2022), appendix B
  • A gradient has the shape of its variable, and broadcasting in the forward pass becomes a sum over the broadcast axes in the backward pass (q1, q3).
  • Write the differential dL=tr⁡(G⊤dY)dL = \operatorname{tr}(G^\top dY), push dYdY through the product rule, rotate with the cyclic trace, and read off the gradient: ∂L/∂W=X⊤G\partial L/\partial W = X^\top G, ∂L/∂X=GW⊤\partial L/\partial X = G W^\top (q18, q20).
  • Softmax’s VJP is y⊙(g−⟨g,y⟩)y \odot (g - \langle g, y\rangle) and cross-entropy’s gradient with respect to logits is softmax(z)−onehot(y)\mathrm{softmax}(z) - \mathrm{onehot}(y), both O(V)O(V) (q21, q24, q28).
  • Normalizations differentiate through their statistics: LayerNorm’s input gradient sums to zero, and RMSNorm’s is orthogonal to xx (q26, q27).
  • Reverse mode costs one pass per output, so one backward pass gives a million-parameter gradient, at about twice the forward FLOPs (q29, q32).
Terminal window
ol start S-M08 # writes solve/S-M08.toml and one file per derivation
ol check S-M08 # SymPy checks the answers, then asks each rubric (y/n)
ol check S-M08 --regrade # ask the rubrics again after you change a derivation

By this point in Pass 2 you have a scalar autograd engine (M08.2) that differentiates one number at a time. It is correct and hopeless at scale: applying one 576×576576 \times 576 weight to one vector would create a third of a million scalar multiplication nodes. M08.3 replaces it with closed-form vector-Jacobian products, one per operation, that take the upstream gradient as an array and return the input gradients as arrays, and L0.2 registers them as the ops of your tensor autograd. Each closed form is a derivation you must get right on paper first, because a gradient with the right shape and the wrong value trains, slowly and badly, rather than crashing. This set drills the shape bookkeeping, matrix differentials, the trace trick, the VJPs of softmax, log-softmax, cross-entropy, LayerNorm, and RMSNorm, the cost model of forward and reverse mode, and the attention backward you will meet again in L9.4.

SymbolMeaningType / shape
LLa scalar lossscalar
X∈Rm×kX \in \mathbb{R}^{m \times k}, W∈Rk×nW \in \mathbb{R}^{k \times n}matrices; Y=XW∈Rm×nY = XW \in \mathbb{R}^{m \times n}
G=∂L/∂YG = \partial L / \partial Yupstream gradient, same shape as YYm×nm \times n
dXdXdifferential: first-order change of XXlike XX
tr⁡(A)\operatorname{tr}(A)trace, ∑iAii\sum_i A_{ii}scalar
⟨A,B⟩\langle A, B \rangleFrobenius inner product, tr⁡(A⊤B)=∑ijAijBij\operatorname{tr}(A^\top B) = \sum_{ij} A_{ij} B_{ij}scalar
⊙\odotelementwise product
JfJ_fJacobian of f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m, (Jf)ij=∂fi/∂xj(J_f)_{ij} = \partial f_i / \partial x_jm×nm \times n
softmax(x)i\mathrm{softmax}(x)_iexi/∑kexke^{x_i} / \sum_k e^{x_k}vector
δij\delta_{ij}1 if i=ji = j, else 0
1\mathbf{1}the all-ones vector

The convention in this course (and in PyTorch) is the denominator layout: ∂L/∂X\partial L/\partial X has the shape of XX, entry by entry (∂L/∂X)ij=∂L/∂Xij(\partial L/\partial X)_{ij} = \partial L/\partial X_{ij}. A row-major array of shape (n0,n1,n2)(n_0, n_1, n_2) stores index (i,j,k)(i, j, k) at element offset i s0+j s1+k s2i\,s_0 + j\,s_1 + k\,s_2 with strides s=(n1n2,n2,1)s = (n_1 n_2, n_2, 1). A view reinterprets the same memory with other strides: swapping two axes swaps their strides and copies nothing. Broadcasting reuses one value along an axis; its backward is a sum over that axis, which L0.1’s unbroadcast performs. A Jacobian of a map from Rn\mathbb{R}^n to Rm\mathbb{R}^m is m×nm \times n, which for a layer is enormous; reverse mode never builds it.

The differential of FF at XX is the linear part of F(X+dX)−F(X)F(X + dX) - F(X). It obeys the rules you know for scalars, with the order of factors kept: d(A+B)=dA+dBd(A + B) = dA + dB, d(AB)=(dA)B+A(dB)d(AB) = (dA)B + A(dB), d(A⊤)=(dA)⊤d(A^\top) = (dA)^\top, dtr⁡(A)=tr⁡(dA)d\operatorname{tr}(A) = \operatorname{tr}(dA), and d(X−1)=−X−1(dX)X−1d(X^{-1}) = -X^{-1}(dX)X^{-1} (q16). For log⁡det⁡X\log \det X the result is tr⁡(X−1dX)\operatorname{tr}(X^{-1} dX). For an elementwise function y=f(x)y = f(x), dy=f′(x)⊙dxdy = f'(x) \odot dx, so its Jacobian is diagonal.

For a scalar LL of a matrix MM, dL=∑ij∂L∂Mij dMij=⟨∂L/∂M,dM⟩=tr⁡((∂L/∂M)⊤dM)dL = \sum_{ij} \frac{\partial L}{\partial M_{ij}}\, dM_{ij} = \langle \partial L/\partial M, dM \rangle = \operatorname{tr}((\partial L/\partial M)^\top dM). So once dLdL is written as tr⁡(N⊤dM)\operatorname{tr}(N^\top dM), the gradient is NN. The tool for getting there is the cyclic property: tr⁡(ABC)=tr⁡(BCA)=tr⁡(CAB)\operatorname{tr}(ABC) = \operatorname{tr}(BCA) = \operatorname{tr}(CAB) whenever the products are defined (a cyclic shift, never an arbitrary swap), together with tr⁡(A⊤)=tr⁡(A)\operatorname{tr}(A^\top) = \operatorname{tr}(A). A scalar is its own trace, so any scalar expression may be wrapped in one.

A vector-Jacobian product maps an upstream gradient g=∂L/∂yg = \partial L/\partial y to ∂L/∂x=J⊤g\partial L/\partial x = J^\top g without forming JJ. The ones M08.3 implements:

OpForwardVJP
matmulY=XWY = XW∂L/∂X=GW⊤\partial L/\partial X = G W^\top, ∂L/∂W=X⊤G\partial L/\partial W = X^\top G
softmaxy=softmax(x)y = \mathrm{softmax}(x)y⊙(g−⟨g,y⟩1)y \odot (g - \langle g, y \rangle \mathbf{1})
log-softmaxℓ=x−LSE(x)\ell = x - \mathrm{LSE}(x)g−softmax(x)∑igig - \mathrm{softmax}(x) \sum_i g_i
cross-entropyL=−log⁡softmax(z)tL = -\log \mathrm{softmax}(z)_tsoftmax(z)−et\mathrm{softmax}(z) - e_t; a mean over NN kept positions divides by NN
RMSNormy=w⊙x/ry = w \odot x / r, r=1n∑xi2+ϵr = \sqrt{\tfrac1n \sum x_i^2 + \epsilon}w⊙gr−x ⟨w⊙g,x⟩nr3\frac{w \odot g}{r} - x\,\frac{\langle w \odot g, x\rangle}{n r^3}
LayerNormx^=(x−μ)/σ\hat x = (x - \mu)/\sigma, y=γ⊙x^+βy = \gamma \odot \hat x + \betawith g^=γ⊙g\hat g = \gamma \odot g: 1σ(g^−g^‾−x^ g^⊙x^‾)\frac{1}{\sigma}\left(\hat g - \overline{\hat g} - \hat x\, \overline{\hat g \odot \hat x}\right)

Here v‾\overline{v} is the mean of the entries of vv, μ\mu the mean of xx, σ=1n∑(xi−μ)2+ϵ\sigma = \sqrt{\tfrac1n \sum (x_i - \mu)^2 + \epsilon}, and ete_t the one-hot vector of the target. LayerNorm and RMSNorm are functions of a whole row: LayerNorm centers and scales it to mean 0 and variance 1, RMSNorm only scales it to root-mean-square 1, and both then apply a learned gain. Because each normalizes by a statistic of its own input, the gradient has a correction term that a “treat the statistic as constant” derivation misses. Two checks catch it: LayerNorm’s input gradient sums to zero (shifting xx changes nothing), and RMSNorm’s is orthogonal to xx when ϵ=0\epsilon = 0 (scaling xx changes nothing).

For f:Rn→Rmf: \mathbb{R}^n \to \mathbb{R}^m, forward mode (M08.1’s dual numbers) computes one Jacobian-vector product JvJ v per pass, so the full Jacobian takes nn passes. Reverse mode computes one J⊤uJ^\top u per pass, so it takes mm. A loss has m=1m = 1: one backward pass gives the gradient with respect to every parameter, at the price of storing the forward activations it needs. For a matrix product the backward computes two products of the same size as the forward one, so backward costs about twice the forward FLOPs, and a training step about three times: the 6N6N per token of S-M05 q13.

Scaled dot-product attention (SDPA) for TT positions computes scores S=QK⊤/dS = QK^\top/\sqrt{d}, weights P=softmax(S)P = \mathrm{softmax}(S) along each row, and output O=PVO = PV. Its backward chains the three rules above: matmul for O=PVO = PV, softmax row by row, matmul again for SS (q34).

This is a sibling of q21 and q24, not one of the graded problems.

Softmax VJP. Let x=(0,0)x = (0, 0), so y=softmax(x)=(1/2,1/2)y = \mathrm{softmax}(x) = (1/2, 1/2), and let the upstream gradient be g=(1,3)g = (1, 3).

  • ⟨g,y⟩=1⋅12+3⋅12=2\langle g, y \rangle = 1 \cdot \tfrac12 + 3 \cdot \tfrac12 = 2.
  • g−2⋅1=(−1,1)g - 2 \cdot \mathbf{1} = (-1, 1).
  • ∂L/∂x=y⊙(−1,1)=(−1/2,1/2)\partial L/\partial x = y \odot (-1, 1) = (-1/2, 1/2).

Check against the Jacobian: for two classes J=(y1(1−y1)−y1y2−y1y2y2(1−y2))=(1/4−1/4−1/41/4)J = \begin{pmatrix} y_1(1-y_1) & -y_1 y_2 \\ -y_1 y_2 & y_2(1 - y_2)\end{pmatrix} = \begin{pmatrix} 1/4 & -1/4 \\ -1/4 & 1/4 \end{pmatrix}, and J⊤g=(1/4−3/4, −1/4+3/4)=(−1/2,1/2)J^\top g = (1/4 - 3/4,\ -1/4 + 3/4) = (-1/2, 1/2). The entries sum to 0, as every softmax input gradient must. In solve/ this is answer = "[-1/2, 1/2]".

Cross-entropy. Logits z=(log⁡3,0)z = (\log 3, 0), target class 0. Then softmax(z)=(3/4,1/4)\mathrm{softmax}(z) = (3/4, 1/4), L=−log⁡(3/4)=log⁡(4/3)L = -\log(3/4) = \log(4/3), and ∂L/∂z=(3/4−1,1/4−0)=(−1/4,1/4)\partial L/\partial z = (3/4 - 1, 1/4 - 0) = (-1/4, 1/4): lower the wrong logit, raise the right one, by how far the prediction is from the target.

Write each answer in solve/S-M08.toml; lettered parts are their own tables:

[q1.a]
answer = "[2, 3, 5]"
[q5]
answer = "[2*x1 + 2*x2, 2*x1 + 6*x2]"
[q18.a]
answer = "[[1, 3], [2, 4]]"
[q20]
proof = "S-M08/q20.md"

Vectors are flat lists; matrices are lists of rows; write ee as E and ⋅\sqrt{\cdot} as sqrt(...). A gradient answer must have the variable’s shape.

A linear layer computes Y=XW⊤+bY = X W^\top + b for a batch XX of shape (B,T,din)=(2,3,4)(B, T, d_{in}) = (2, 3, 4), stored row-major in float32, with WW of shape (dout,din)=(5,4)(d_{out}, d_{in}) = (5, 4) and bb of shape (5)(5). LL is a scalar loss.

q1. Give the shape of (a) YY, (b) ∂L/∂W\partial L / \partial W, (c) ∂L/∂b\partial L / \partial b. [vector]

q2. (a) At which element offset (counting from 0) is X[1,2,1]X[1, 2, 1] stored? [number] (b) Give the strides of XX in elements, one per axis. [vector] (c) Give the strides of the view that swaps the last two axes of XX (shape (2,4,3)(2, 4, 3), no copy). [vector]

q3. bb is broadcast over the batch and time axes. How many entries of ∂L/∂Y\partial L / \partial Y are summed into each entry of ∂L/∂b\partial L / \partial b? [number]

q4. Flatten XX and YY row-major into vectors. What is the shape of the Jacobian ∂ vec(Y)/∂ vec(X)\partial\, \mathrm{vec}(Y) / \partial\, \mathrm{vec}(X) (rows, columns)? [vector]

q5. f(x)=x⊤Axf(x) = x^\top A x with A=(1203)A = \begin{pmatrix} 1 & 2 \\ 0 & 3 \end{pmatrix} and x=(x1,x2)x = (x_1, x_2). Give ∇f\nabla f. [vector in x1, x2]

q6. f(X)=tr⁡(AX)f(X) = \operatorname{tr}(AX) with A=(1234)A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}. Give ∇Xf\nabla_X f. [matrix]

q7. f(x)=∥Ax−b∥2f(x) = \lVert Ax - b \rVert^2 with A=(1011)A = \begin{pmatrix} 1 & 0 \\ 1 & 1 \end{pmatrix}, b=(1,0)b = (1, 0). Give ∇f\nabla f at x=(1,2)x = (1, 2). [vector]

q8. X(t)=(2t11)X(t) = \begin{pmatrix} 2 & t \\ 1 & 1 \end{pmatrix}. Give ddtX(t)−1\frac{d}{dt} X(t)^{-1} at t=0t = 0. [matrix]

q9. Give ∇Xlog⁡det⁡X\nabla_X \log \det X at X=(2103)X = \begin{pmatrix} 2 & 1 \\ 0 & 3 \end{pmatrix}. [matrix]

q10. f(W)=∥Wx∥2f(W) = \lVert W x \rVert^2 with x=(1,2)x = (1, 2). Give ∇Wf\nabla_W f at W=I2W = I_2. [matrix]

q11. Give the derivative of the sigmoid σ(z)=11+e−z\sigma(z) = \frac{1}{1 + e^{-z}}. [expr in z]

q12. Give the derivative of softplus, f(z)=log⁡(1+ez)f(z) = \log(1 + e^z). [expr in z]

q13. Give the gradient of LSE(x1,x2)=log⁡(ex1+ex2)\mathrm{LSE}(x_1, x_2) = \log(e^{x_1} + e^{x_2}). [vector in x1, x2]

q14. Give the gradient of f(x)=∥x∥2f(x) = \lVert x \rVert_2 at x=(3,4)x = (3, 4). [vector]

q15. Give the Jacobian of elementwise ReLU\mathrm{ReLU} at x=(−1,2,1/2)x = (-1, 2, 1/2). [matrix]

q16. Prove d(X−1)=−X−1(dX)X−1d(X^{-1}) = -X^{-1} (dX) X^{-1} for an invertible square matrix XX. [proof]

q17. For conformable matrices: (a) is tr⁡(ABC)=tr⁡(CAB)\operatorname{tr}(ABC) = \operatorname{tr}(CAB) always? (b) Is tr⁡(ABC)=tr⁡(BAC)\operatorname{tr}(ABC) = \operatorname{tr}(BAC) always? [bool]

q18. Y=XWY = XW with X=(1234)X = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}, W=(1102)W = \begin{pmatrix} 1 & 1 \\ 0 & 2 \end{pmatrix}, and upstream gradient G=∂L/∂Y=I2G = \partial L / \partial Y = I_2. Give (a) ∂L/∂W\partial L / \partial W and (b) ∂L/∂X\partial L / \partial X. [matrix]

q19. The Frobenius inner product is ⟨A,B⟩=tr⁡(A⊤B)\langle A, B \rangle = \operatorname{tr}(A^\top B). Give it for A=(1234)A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}, B=(0110)B = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}. [number]

q20. Let Y=XWY = XW and LL a scalar with dL=tr⁡(G⊤dY)dL = \operatorname{tr}(G^\top dY), G=∂L/∂YG = \partial L / \partial Y. Prove ∂L/∂W=X⊤G\partial L / \partial W = X^\top G and ∂L/∂X=GW⊤\partial L / \partial X = G W^\top. [proof]

q21. y=softmax(x)=(1/2,1/4,1/4)y = \mathrm{softmax}(x) = (1/2, 1/4, 1/4) and the upstream gradient is g=(1,0,0)g = (1, 0, 0). Give ∂L/∂x\partial L / \partial x. [vector]

q22. Give the Jacobian ∂y/∂x\partial y / \partial x of a 2-class softmax at y=(1/4,3/4)y = (1/4, 3/4). [matrix]

q23. ℓ=log_softmax(x)\ell = \mathrm{log\_softmax}(x) with softmax(x)=(1/2,1/4,1/4)\mathrm{softmax}(x) = (1/2, 1/4, 1/4) and upstream gradient g=(0,1,0)g = (0, 1, 0). Give ∂L/∂x\partial L / \partial x. [vector]

q24. Cross-entropy with logits z=(0,log⁡2,log⁡2)z = (0, \log 2, \log 2) and target class 1 (counting from 0): L=−log⁡softmax(z)1L = -\log \mathrm{softmax}(z)_1. (a) Give ∂L/∂z\partial L / \partial z. [vector] (b) Give LL. [number]

q25. A batch has 4 positions; one has the target ignore_index and the loss is the mean over the other positions. By what factor is each kept position’s per-position gradient softmax(z)−onehot(y)\mathrm{softmax}(z) - \mathrm{onehot}(y) scaled? [number]

q26. RMSNorm with ϵ=0\epsilon = 0: y=w⊙x/ry = w \odot x / r, r=1n∑ixi2r = \sqrt{\frac{1}{n} \sum_i x_i^2}. (a) For x=(3,4)x = (3, 4), w=(1,1)w = (1, 1), and upstream g=(1,0)g = (1, 0), give ∂L/∂x\partial L / \partial x. [vector] (b) Is x⋅∂L/∂x=0x \cdot \partial L / \partial x = 0 for every gg and every ww? [bool]

q27. LayerNorm with ϵ=0\epsilon = 0, γ=1\gamma = 1, β=0\beta = 0: y=(x−μ)/σy = (x - \mu)/\sigma with μ\mu the mean and σ2=1n∑i(xi−μ)2\sigma^2 = \frac{1}{n}\sum_i (x_i - \mu)^2. For x=(0,1,2)x = (0, 1, 2) and upstream g=(1,0,0)g = (1, 0, 0), give ∂L/∂x\partial L / \partial x. [vector]

q28. Prove the softmax VJP: if y=softmax(x)y = \mathrm{softmax}(x) and g=∂L/∂yg = \partial L / \partial y, then ∂L/∂x=y⊙(g−⟨g,y⟩1)\partial L / \partial x = y \odot (g - \langle g, y \rangle \mathbf{1}). [proof]

q29. A loss f:Rn→Rf: \mathbb{R}^n \to \mathbb{R} has n=106n = 10^6 parameters. How many (a) Jacobian-vector products (forward mode) and (b) vector-Jacobian products (reverse mode) give the full gradient? [number]

q30. You need the full Jacobian of f:R3→R1000f: \mathbb{R}^3 \to \mathbb{R}^{1000}. Which mode needs fewer passes? (a) forward (b) reverse [choice]

q31. Backward of a 16-layer MLP stores each layer’s input: a batch of 32 rows of width 1024 in float32. How many bytes of activations does it keep? [number]

q32. Y=XWY = XW with XX of shape m×km \times k and WW of shape k×nk \times n. (a) How many FLOPs does backward take to compute both ∂L/∂X\partial L/\partial X and ∂L/∂W\partial L/\partial W? [expr in m, k, n] (b) What is the ratio (forward + backward) / forward? [number]

Scaled dot-product attention: S=QK⊤/dS = Q K^\top / \sqrt{d}, P=softmax(S)P = \mathrm{softmax}(S) row by row, O=PVO = P V. Take T=2T = 2 positions, d=1d = 1, Q=(10)Q = \begin{pmatrix} 1 \\ 0 \end{pmatrix}, K=(01)K = \begin{pmatrix} 0 \\ 1 \end{pmatrix}, V=(13)V = \begin{pmatrix} 1 \\ 3 \end{pmatrix}, no mask. Write ee as E.

q33. (a) Give OO. [matrix] With upstream gradient ∂L/∂O=(10)\partial L / \partial O = \begin{pmatrix} 1 \\ 0 \end{pmatrix}, give (b) ∂L/∂V\partial L / \partial V and (c) ∂L/∂Q\partial L / \partial Q. [matrix]

q34. Prove the SDPA backward rules: with dO=∂L/∂OdO = \partial L / \partial O, dV=P⊤dOdV = P^\top dO, dP=dO V⊤dP = dO\, V^\top, dSij=Pij(dPij−∑kdPikPik)dS_{ij} = P_{ij} (dP_{ij} - \sum_k dP_{ik} P_{ik}), dQ=dS K/ddQ = dS\, K / \sqrt{d}, and dK=dS⊤Q/ddK = dS^\top Q / \sqrt{d}. [proof]

PitfallSymptomCaught by
Gradient transposed relative to its variableshape error in the optimizer, or a silently wrong square gradientq1 (canary [4, 5]), q9 (canary: the inverse)
Not summing over broadcast axesbias gradient of shape (B, T, d)q1, q3 (canaries 30 and 2)
Counting bytes as strides, or using a contiguous copy’s strides for a viewwrong element read through a viewq2 (canaries in bytes and [12, 3, 1])
∇x⊤Ax=2Ax\nabla x^\top A x = 2Ax for a non-symmetric AAwrong gradient for asymmetric formsq5 (canary 2Ax)
Dropping the factor 2 of a squaregradients half as large; learning rates tuned to hide itq7, q10 (canaries)
Swapping a non-cyclic order inside a tracean identity that holds only for commuting matricesq17 (canary true)
Softmax VJP without the ⟨g,y⟩\langle g, y\rangle terminput gradients that do not sum to zeroq21 (canary y * g)
One-hot minus softmaxgradient ascent on the lossq24 (canary with the sign flipped)
Mean over all positions including ignored onesloss and gradient scaled down by the padding fractionq25 (canary 1/4)
Treating a normalization’s statistics as constantsRMSNorm or LayerNorm gradients without the correction termq26 and q27 (canaries)
Using P dOP\,dO for dVdVvalue gradients from the wrong positionsq33 (canary)
DirectionModuleHow it uses this
BackS-M05proof structure for q16, q20, q28, q34
ForwardM08.1dual numbers compute the JVPs of section 2.5
ForwardM08.2scalar reverse mode, the oracle against which M08.3 is tested
ForwardM08.3the VJP table of section 2.4 as code, gradchecked rule by rule
ForwardL0.2registers those VJPs as tensor ops, with unbroadcast from q1 and q3
ForwardL0.3fused cross-entropy with ignore_index (q24, q25)
ForwardL7.1RMSNorm in the modern block (q26)
ForwardL9.4FlashAttention backward recomputes PP and applies q34’s rules tile by tile