Deep-dives on transformers, autograd, and LLM math — read the blog

← Blogs

Transformer from Scratch: The Full Forward Pass, Backprop, and Weight Update Math

One full transformer training step worked by hand — embeddings, positional encoding, attention, layer norm, the encoder-decoder, cross-entropy loss, backprop, and Adam.

AI from Scratch Part 11 of 15
View as Markdown

Most transformer explainers stop at the forward pass and let the words “and then we train it” do all the work. I did that for a long time — I could sketch how attention works conceptually, I could read token embeddings and positional encoding in isolation — but I could not trace one training step from input IDs to a weight that actually moves. This post fixes that.

Below is one complete training step of the original 2017 encoder-decoder transformer, solved like a single math problem. Every matrix multiply is written out. Every softmax row is summed. Every gradient lands on a number you can check with a calculator. The formulas are exactly those from Attention Is All You Need; the dimensions are a toy so you can verify every line by hand. If you’ve built autograd from scratch, this is the derivation you want open while debugging — and when something looks wrong, numerical gradient checking is how I sanity-check each layer.

[!NOTE] Conceptual attention (queries, keys, values, no arithmetic) lives in how attention works. This post is the arithmetic.


What machine am I solving?

The 2017 transformer is an encoder-decoder stack. The encoder reads the source sentence; the decoder reads a shifted target and attends to encoder memory. One layer on each side, one training step, every box below gets a number.

flowchart TB
    subgraph ENC["Encoder ×N"]
        EI["Input embed + PE"] --> ESA["Multi-head self-attention"]
        ESA --> EAN1["Add & Norm"]
        EAN1 --> EFF["Feed-forward"]
        EFF --> EAN2["Add & Norm"]
    end
    subgraph DEC["Decoder ×N"]
        DI["Output embed + PE"] --> DSA["Masked self-attention"]
        DSA --> DAN1["Add & Norm"]
        DAN1 --> DCA["Cross-attention<br/>(Q=decoder, K,V=encoder)"]
        DCA --> DAN2["Add & Norm"]
        DAN2 --> DFF["Feed-forward"]
        DFF --> DAN3["Add & Norm"]
    end
    EAN2 -->|"memory K,V"| DCA
    DAN3 --> LIN["Linear → vocab"]
    LIN --> SM["Softmax → probabilities"]
    SM --> LOSS["Cross-entropy + label smoothing"]

What toy setup am I using?

Task: translate je suisi am. The decoder is fed the shifted target <bos> i and must predict i am.

Symbol Toy Real (base paper)
$d_{\text{model}}$ 4 512
heads $h$ 2 8
$d_k=d_v=d_{\text{model}}/h$ 2 64
$d_{ff}$ 8 2048
layers $N$ 1 6
vocab $V$ 4 37,000

All formulas are exact at any scale; only the numbers shrink. Vocabulary order: [<bos>, i, am, <eos>].


How do token embeddings enter the encoder?

Each token ID picks a row from the learned embedding matrix $E \in \mathbb{R}^{V \times d_{\text{model}}}$, then scales by $\sqrt{d_{\text{model}}}$.

For $d_{\text{model}} = 4$:

$$ \sqrt{d_{\text{model}}} = \sqrt{4} = 2 $$

Encoder tokens je (position 0) and suis (position 1). After lookup and scaling, I round to clean integers for hand arithmetic (shapes stay $2 \times 4$):

$$ \text{embed}(\text{je}) = 2 \cdot [0.5,\, 0,\, 0.5,\, 0] = [1,\, 0,\, 1,\, 0] $$
$$ \text{embed}(\text{suis}) = 2 \cdot [0,\, 0.5,\, 0,\, 0.5] = [0,\, 1,\, 0,\, 1] $$

Stack into the encoder input matrix $X_{\text{raw}} \in \mathbb{R}^{2 \times 4}$:

$$ X_{\text{raw}} = \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{bmatrix} $$

Row 0 is je. Row 1 is suis. Shape: $(2,\, 4)$.


What is positional encoding and how do I add it?

Sinusoidal positional encoding from the paper:

$$ PE_{(pos,\, 2i)} = \sin\!\Big(\tfrac{pos}{10000^{2i/d_{\text{model}}}}\Big) $$
$$ PE_{(pos,\, 2i+1)} = \cos\!\Big(\tfrac{pos}{10000^{2i/d_{\text{model}}}}\Big) $$

For $d_{\text{model}} = 4$, the frequency divisors are:

$$ 10000^{0} = 1 $$
$$ 10000^{1/2} = 100 $$

Position $pos = 0$:

$$ PE_0 = [\sin 0,\, \cos 0,\, \sin 0,\, \cos 0] = [0,\, 1,\, 0,\, 1] $$

Position $pos = 1$:

$$ PE_1 = [\sin 1,\, \cos 1,\, \sin 0.01,\, \cos 0.01] = [0.84,\, 0.54,\, 0.01,\, 1.00] $$

Add to embeddings elementwise. For this toy I round the sum to keep later arithmetic tractable (same as the original derivation):

$$ X = X_{\text{raw}} + PE \approx \begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{bmatrix} $$

Shape: $(2,\, 4)$. This $X$ is what enters encoder self-attention.


How does encoder self-attention compute Q, K, and V?

Multi-head attention projects $X$ into queries, keys, and values per head:

$$ Q = X W_Q, \qquad K = X W_K, \qquad V = X W_V $$
$$ \text{Attn}(Q,K,V) = \text{softmax}\!\Big(\tfrac{QK^{\top}}{\sqrt{d_k}}\Big) V $$

Head 1 weights ($X$ is $4 \times 2$ per row-column convention: $X \in \mathbb{R}^{2 \times 4}$, $W_Q \in \mathbb{R}^{4 \times 2}$):

$$ W_Q^{(1)} = W_K^{(1)} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 0 \\ 0 & 1 \end{bmatrix} $$
$$ W_V^{(1)} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \\ 0 & 0 \end{bmatrix} $$

Matmul: $Q = X W_Q^{(1)}$

Row 0 of $X$: $[1,\, 0,\, 1,\, 0]$.

$$ Q[0,0] = 1 \cdot 1 + 0 \cdot 0 + 1 \cdot 1 + 0 \cdot 0 = 2 $$
$$ Q[0,1] = 1 \cdot 0 + 0 \cdot 1 + 1 \cdot 0 + 0 \cdot 1 = 0 $$

Row 1 of $X$: $[0,\, 1,\, 0,\, 1]$.

$$ Q[1,0] = 0 \cdot 1 + 1 \cdot 0 + 0 \cdot 1 + 1 \cdot 0 = 0 $$
$$ Q[1,1] = 0 \cdot 0 + 1 \cdot 1 + 0 \cdot 0 + 1 \cdot 1 = 2 $$
$$ Q^{(1)} = \begin{bmatrix} 2 & 0 \\ 0 & 2 \end{bmatrix} $$

Same $W_K^{(1)}$, so $K^{(1)} = Q^{(1)}$.

Matmul: $V = X W_V^{(1)}$

$$ V[0,0] = 1 \cdot 1 + 0 \cdot 0 + 1 \cdot 0 + 0 \cdot 0 = 1 $$
$$ V[0,1] = 1 \cdot 0 + 0 \cdot 1 + 1 \cdot 0 + 0 \cdot 0 = 0 $$
$$ V[1,0] = 0 \cdot 1 + 1 \cdot 0 + 0 \cdot 0 + 1 \cdot 0 = 0 $$
$$ V[1,1] = 0 \cdot 0 + 1 \cdot 1 + 0 \cdot 0 + 1 \cdot 0 = 1 $$
$$ V^{(1)} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} $$

Shapes: $Q, K, V \in \mathbb{R}^{2 \times 2}$.


How do I score, scale, softmax, and mix values?

Matmul: $Q K^{\top}$

$$ (QK^{\top})[0,0] = 2 \cdot 2 + 0 \cdot 0 = 4 $$
$$ (QK^{\top})[0,1] = 2 \cdot 0 + 0 \cdot 2 = 0 $$
$$ (QK^{\top})[1,0] = 0 \cdot 2 + 2 \cdot 0 = 0 $$
$$ (QK^{\top})[1,1] = 0 \cdot 0 + 2 \cdot 2 = 4 $$
$$ QK^{\top} = \begin{bmatrix} 4 & 0 \\ 0 & 4 \end{bmatrix} $$

Scale by $\sqrt{d_k}$

$$ \sqrt{d_k} = \sqrt{2} \approx 1.4142 $$
$$ S = \frac{QK^{\top}}{\sqrt{d_k}} = \begin{bmatrix} 2.828 & 0 \\ 0 & 2.828 \end{bmatrix} $$

Softmax row 0

$$ e^{2.828} \approx 16.92 $$
$$ e^{0} = 1 $$
$$ \text{sum}_0 = 16.92 + 1 = 17.92 $$
$$ A[0,0] = 16.92 / 17.92 \approx 0.944 $$
$$ A[0,1] = 1 / 17.92 \approx 0.056 $$

Softmax row 1

By symmetry:

$$ A[1,0] = 0.056 $$
$$ A[1,1] = 0.944 $$
$$ A^{(1)} = \begin{bmatrix} 0.944 & 0.056 \\ 0.056 & 0.944 \end{bmatrix} $$

Each row sums to $1$.

Matmul: $O^{(1)} = A^{(1)} V^{(1)}$

$$ O^{(1)}[0,0] = 0.944 \cdot 1 + 0.056 \cdot 0 = 0.944 $$
$$ O^{(1)}[0,1] = 0.944 \cdot 0 + 0.056 \cdot 1 = 0.056 $$
$$ O^{(1)}[1,0] = 0.056 \cdot 1 + 0.944 \cdot 0 = 0.056 $$
$$ O^{(1)}[1,1] = 0.056 \cdot 0 + 0.944 \cdot 1 = 0.944 $$
$$ O^{(1)} = \begin{bmatrix} 0.944 & 0.056 \\ 0.056 & 0.944 \end{bmatrix} $$

Head 2 uses its own $W_Q^{(2)}, W_K^{(2)}, W_V^{(2)}$ and lands identically here with my chosen weights. Concatenate heads → width $4$, mix with $W_O = I_4$:

$$ \text{MHA}(X) = \begin{bmatrix} 0.944 & 0.056 & 0.944 & 0.056 \\ 0.056 & 0.944 & 0.056 & 0.944 \end{bmatrix} $$

Shape: $(2,\, 4)$.


What is Add & Norm (layer norm)?

Residual connection plus layer norm:

$$ \text{LayerNorm}(x + \text{Sublayer}(x)) $$
$$ \mu = \frac{1}{d} \sum_{i=1}^{d} x_i $$
$$ \sigma^2 = \frac{1}{d} \sum_{i=1}^{d} (x_i - \mu)^2 $$
$$ y_i = \gamma \cdot \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta $$

With $\gamma = 1$, $\beta = 0$, $\epsilon = 0$.

Residual add after attention

$$ X + \text{MHA}(X) = \begin{bmatrix} 1.944 & 0.056 & 1.944 & 0.056 \\ 0.056 & 1.944 & 0.056 & 1.944 \end{bmatrix} $$

Layer norm on row 0

$$ \mu_0 = (1.944 + 0.056 + 1.944 + 0.056) / 4 = 1.0 $$

Deviations from mean:

$$ [1.944 - 1,\, 0.056 - 1,\, 1.944 - 1,\, 0.056 - 1] = [0.944,\, -0.944,\, 0.944,\, -0.944] $$
$$ \sigma_0^2 = (0.944^2 + 0.944^2 + 0.944^2 + 0.944^2) / 4 = 0.891 $$
$$ \sigma_0 = \sqrt{0.891} \approx 0.944 $$

Normalized row 0:

$$ \text{LN}_1[0,:] = [0.944/0.944,\, -0.944/0.944,\, 0.944/0.944,\, -0.944/0.944] = [1,\, -1,\, 1,\, -1] $$

Row 1 is antisymmetric:

$$ \text{LN}_1[1,:] = [-1,\, 1,\, -1,\, 1] $$
$$ \text{LN}_1 = \begin{bmatrix} 1 & -1 & 1 & -1 \\ -1 & 1 & -1 & 1 \end{bmatrix} $$

How does the feed-forward network work?

Position-wise FFN:

$$ \text{FFN}(x) = \max(0,\, x W_1 + b_1)\, W_2 + b_2 $$

$W_1 = [I_4 \mid I_4]$ widens $4 \to 8$. $W_2 = [I_4;\, I_4]$ projects $8 \to 4$. Biases are zero.

Row 0 through FFN

Input: $[1,\, -1,\, 1,\, -1]$.

After $W_1$:

$$ [1,\, -1,\, 1,\, -1,\, 1,\, -1,\, 1,\, -1] $$

After ReLU (negative entries → 0):

$$ [1,\, 0,\, 1,\, 0,\, 1,\, 0,\, 1,\, 0] $$

After $W_2$ (sum pairs):

$$ \text{FFN}[0,:] = [2,\, 0,\, 2,\, 0] $$

Second Add & Norm (same recipe) yields encoder output — the decoder’s memory:

$$ \text{ENC} = \begin{bmatrix} 1 & -1 & 1 & -1 \\ -1 & 1 & -1 & 1 \end{bmatrix} $$

Shape: $(2,\, 4)$.


How does the decoder run masked self-attention?

Shifted target <bos> i, embedded and positioned (same rounding trick):

$$ Y = \begin{bmatrix} 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 \end{bmatrix} $$

Masked self-attention sets future positions to $-\infty$ before softmax:

flowchart LR
    A["Raw scores"] --> B["future → −∞"]
    B --> C["softmax → 0 there"]
    C --> D["attend self + past only"]

Position 0 sees only itself → attention weights $[1,\, 0]$.

Position 1 sees positions 0 and 1 equally → weights $[0.5,\, 0.5]$.

After mixing, residual, and layer norm:

$$ \text{DEC}_1 = \begin{bmatrix} 1 & 1 & -1 & -1 \\ -1 & -1 & 1 & 1 \end{bmatrix} $$

How does cross-attention connect encoder and decoder?

Cross-attention projections (chosen so every matmul is traceable):

$$ W_Q^{\text{cross}} = \begin{bmatrix} 1 & 0 \\ 0 & -1 \\ 0 & 0 \\ 0 & 0 \end{bmatrix} $$
$$ W_{K,V}^{\text{cross}} = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \\ 0 & 0 \end{bmatrix} $$

Matmul: $Q = \text{DEC}_1 \, W_Q^{\text{cross}}$

Row 0 of $\text{DEC}_1$: $[1,\, 1,\, -1,\, -1]$.

$$ Q[0,0] = 1 \cdot 1 + 1 \cdot 0 + (-1) \cdot 0 + (-1) \cdot 0 = 1 $$
$$ Q[0,1] = 1 \cdot 0 + 1 \cdot (-1) + (-1) \cdot 0 + (-1) \cdot 0 = -1 $$

Row 1: $[-1,\, -1,\, 1,\, 1]$.

$$ Q[1,0] = (-1) \cdot 1 + (-1) \cdot 0 + 1 \cdot 0 + 1 \cdot 0 = -1 $$
$$ Q[1,1] = (-1) \cdot 0 + (-1) \cdot (-1) + 1 \cdot 0 + 1 \cdot 0 = 1 $$
$$ Q = \begin{bmatrix} 1 & -1 \\ -1 & 1 \end{bmatrix} $$

Matmul: $K = \text{ENC} \, W_{K,V}^{\text{cross}}$

Row 0 of ENC: $[1,\, -1,\, 1,\, -1]$.

$$ K[0,0] = 1 \cdot 1 + (-1) \cdot 0 + 1 \cdot 0 + (-1) \cdot 0 = 1 $$
$$ K[0,1] = 1 \cdot 0 + (-1) \cdot 1 + 1 \cdot 0 + (-1) \cdot 0 = -1 $$

Row 1: $[-1,\, 1,\, -1,\, 1]$.

$$ K[1,0] = (-1) \cdot 1 + 1 \cdot 0 + (-1) \cdot 0 + 1 \cdot 0 = -1 $$
$$ K[1,1] = (-1) \cdot 0 + 1 \cdot 1 + (-1) \cdot 0 + 1 \cdot 0 = 1 $$
$$ K = V = \begin{bmatrix} 1 & -1 \\ -1 & 1 \end{bmatrix} $$

Matmul: $Q K^{\top}$

$$ (QK^{\top})[0,0] = 1 \cdot 1 + (-1) \cdot (-1) = 2 $$
$$ (QK^{\top})[0,1] = 1 \cdot (-1) + (-1) \cdot 1 = -2 $$
$$ (QK^{\top})[1,0] = (-1) \cdot 1 + 1 \cdot (-1) = -2 $$
$$ (QK^{\top})[1,1] = (-1) \cdot (-1) + 1 \cdot 1 = 2 $$
$$ QK^{\top} = \begin{bmatrix} 2 & -2 \\ -2 & 2 \end{bmatrix} $$

Scale and softmax

$$ S = QK^{\top} / \sqrt{2} = \begin{bmatrix} 1.414 & -1.414 \\ -1.414 & 1.414 \end{bmatrix} $$

Row 0: $e^{1.414} \approx 4.10$, $e^{-1.414} \approx 0.243$, sum $\approx 4.34$.

$$ A[0,0] \approx 4.10 / 4.34 \approx 0.944 $$
$$ A[0,1] \approx 0.243 / 4.34 \approx 0.056 $$

Row 1 is symmetric:

$$ A = \begin{bmatrix} 0.944 & 0.056 \\ 0.056 & 0.944 \end{bmatrix} $$

Matmul: $O = A V$

$$ O[0,0] = 0.944 \cdot 1 + 0.056 \cdot (-1) = 0.888 $$
$$ O[0,1] = 0.944 \cdot (-1) + 0.056 \cdot 1 = -0.888 $$
$$ O[1,0] = 0.056 \cdot 1 + 0.944 \cdot (-1) = -0.888 $$
$$ O[1,1] = 0.056 \cdot (-1) + 0.944 \cdot 1 = 0.888 $$
$$ O = \begin{bmatrix} 0.888 & -0.888 \\ -0.888 & 0.888 \end{bmatrix} $$

FFN + second Add & Norm (same pattern as encoder). I score the final position — the one predicting am:

$$ h = [1,\, 0,\, -1,\, 0] $$

Shape: $(1,\, 4)$.


How do I project to vocabulary and get probabilities?

Linear layer (weight-tied to embeddings in the full model). Here $W_{\text{out}} = I_4$, bias $b = 0$:

$$ z = h \, W_{\text{out}} + b $$
$$ z[0] = 1 \cdot 1 + 0 \cdot 0 + (-1) \cdot 0 + 0 \cdot 0 = 1 $$
$$ z[1] = 1 \cdot 0 + 0 \cdot 1 + (-1) \cdot 0 + 0 \cdot 0 = 0 $$
$$ z[2] = 1 \cdot 0 + 0 \cdot 0 + (-1) \cdot 1 + 0 \cdot 0 = -1 $$
$$ z[3] = 1 \cdot 0 + 0 \cdot 0 + (-1) \cdot 0 + 0 \cdot 1 = 0 $$
$$ z = [1,\, 0,\, -1,\, 0] $$

Softmax over vocab [<bos>, i, am, <eos>]:

$$ e^{1} \approx 2.718 $$
$$ e^{0} = 1 $$
$$ e^{-1} \approx 0.368 $$
$$ \text{denom} = 2.718 + 1 + 0.368 + 1 = 5.086 $$
$$ p[0] = 2.718 / 5.086 \approx 0.535 $$
$$ p[1] = 1 / 5.086 \approx 0.197 $$
$$ p[2] = 0.368 / 5.086 \approx 0.072 $$
$$ p[3] = 1 / 5.086 \approx 0.197 $$
$$ p = [0.535,\, 0.197,\, 0.072,\, 0.197] $$

The correct word am (index 2) gets only $0.072$. The model is confidently wrong — good setup for a nontrivial gradient.


What is the cross-entropy loss with label smoothing?

Plain cross-entropy on am:

$$ \mathcal{L}_{\text{CE}} = -\log p_{\text{am}} = -\log 0.072 \approx 2.63 $$

Label smoothing $\epsilon_{ls} = 0.1$ spreads mass onto every class:

$$ q_k = (1 - \epsilon_{ls}) \cdot \mathbb{1}[k = \text{am}] + \epsilon_{ls} / V $$
$$ \epsilon_{ls} / V = 0.1 / 4 = 0.025 $$
$$ q = [0.025,\, 0.025,\, 0.925,\, 0.025] $$

Smoothed loss:

$$ \mathcal{L} = -\sum_{k} q_k \log p_k $$
$$ \mathcal{L} = -\big(0.025 \cdot \log 0.535 + 0.025 \cdot \log 0.197 + 0.925 \cdot \log 0.072 + 0.025 \cdot \log 0.197\big) $$
$$ \log 0.535 \approx -0.626 $$
$$ \log 0.197 \approx -1.627 $$
$$ \log 0.072 \approx -2.63 $$
$$ \mathcal{L} \approx -\big(0.025 \cdot (-0.626) + 0.025 \cdot (-1.627) + 0.925 \cdot (-2.63) + 0.025 \cdot (-1.627)\big) $$
$$ \mathcal{L} \approx 2.53 $$

That is the scalar I backprop through.


How does backprop flow through the transformer?

flowchart RL
    Z["∂L/∂z = p − q"] --> WO["∂L/∂W_out"]
    Z --> H["∂L/∂h"]
    H --> LN["LayerNorm Jacobian"]
    LN --> F["FFN (ReLU gate)"]
    F --> AT["attention (softmax Jacobian)"]
    AT --> EMB["∂L/∂embeddings"]

Logits: softmax + cross-entropy collapse

The combined derivative is the elegant $p - q$:

$$ \frac{\partial \mathcal{L}}{\partial z} = p - q $$
$$ \frac{\partial \mathcal{L}}{\partial z}[0] = 0.535 - 0.025 = 0.510 $$
$$ \frac{\partial \mathcal{L}}{\partial z}[1] = 0.197 - 0.025 = 0.172 $$
$$ \frac{\partial \mathcal{L}}{\partial z}[2] = 0.072 - 0.925 = -0.853 $$
$$ \frac{\partial \mathcal{L}}{\partial z}[3] = 0.197 - 0.025 = 0.172 $$
$$ \frac{\partial \mathcal{L}}{\partial z} = [0.510,\, 0.172,\, -0.853,\, 0.172] $$

Sum check (must be zero — softmax is shift-invariant):

$$ 0.510 + 0.172 + (-0.853) + 0.172 = 0.001 \approx 0 $$

Only am is negative → gradient pushes that logit up, pulls the rest down.

Output layer: $z = h W_{\text{out}}$

$$ \frac{\partial \mathcal{L}}{\partial W_{\text{out}}} = h^{\top} \otimes \frac{\partial \mathcal{L}}{\partial z} $$
$$ \frac{\partial \mathcal{L}}{\partial h} = \frac{\partial \mathcal{L}}{\partial z} \, W_{\text{out}}^{\top} $$

With $W_{\text{out}} = I$:

$$ \frac{\partial \mathcal{L}}{\partial h} = [0.510,\, 0.172,\, -0.853,\, 0.172] $$

Layer norm backward

For $y_i = \gamma (x_i - \mu) / \sigma$ with $D$ dimensions:

$$ \frac{\partial \mathcal{L}}{\partial x_i} = \frac{\gamma}{\sigma} \Big[ \frac{\partial \mathcal{L}}{\partial y_i} - \frac{1}{D}\sum_j \frac{\partial \mathcal{L}}{\partial y_j} - \frac{x_i - \mu}{\sigma^2} \cdot \frac{1}{D}\sum_j \frac{\partial \mathcal{L}}{\partial y_j}(x_j - \mu) \Big] $$

The two sum terms strip gradient along the mean and variance directions — layer norm is not “just divide by std.”

FFN backward

ReLU gates the gradient to positions where pre-activation was positive:

$$ \frac{\partial \mathcal{L}}{\partial (x W_1)} = \Big( \frac{\partial \mathcal{L}}{\partial \text{FFN}} \, W_2^{\top} \Big) \odot \mathbb{1}[x W_1 + b_1 > 0] $$

Negative pre-activations get zero gradient. Dead ReLUs are real.

Attention backward

Forward: $O = AV$, $A = \text{softmax}(S)$, $S = QK^{\top} / \sqrt{d_k}$.

$$ \frac{\partial \mathcal{L}}{\partial V} = A^{\top} \frac{\partial \mathcal{L}}{\partial O} $$
$$ \frac{\partial \mathcal{L}}{\partial A} = \frac{\partial \mathcal{L}}{\partial O} \, V^{\top} $$

Softmax Jacobian (row $i$):

$$ \frac{\partial \mathcal{L}}{\partial S_i} = A_i \odot \Big( \frac{\partial \mathcal{L}}{\partial A_i} - \sum_j \frac{\partial \mathcal{L}}{\partial A_{ij}} A_{ij} \Big) $$

Then:

$$ \frac{\partial \mathcal{L}}{\partial Q} = \frac{1}{\sqrt{d_k}} \frac{\partial \mathcal{L}}{\partial S} \, K $$
$$ \frac{\partial \mathcal{L}}{\partial K} = \frac{1}{\sqrt{d_k}} \frac{\partial \mathcal{L}}{\partial S}^{\top} \, Q $$
$$ \frac{\partial \mathcal{L}}{\partial W_Q} = X^{\top} \frac{\partial \mathcal{L}}{\partial Q} $$

Masked positions had zero forward attention weight → zero gradient. Causality is preserved in backward pass too. Gradients from all positions and heads sum into shared weight matrices.

The one pattern underneath everything

If $Y = XW$ with $X : (N, d_{\text{in}})$, $W : (d_{\text{in}}, d_{\text{out}})$, $G = \partial L / \partial Y$:

$$ \frac{\partial L}{\partial W} = X^{\top} G $$
$$ \frac{\partial L}{\partial X} = G \, W^{\top} $$

Nearly every transformer gradient is this plus reshaping, layer-norm Jacobians, and the softmax Jacobian.


How does Adam update a weight?

Adam hyperparameters from the paper:

$$ \beta_1 = 0.9 $$
$$ \beta_2 = 0.98 $$
$$ \epsilon = 10^{-9} $$

Learning rate schedule:

$$ \text{lr}(t) = d_{\text{model}}^{-0.5} \cdot \min\!\big( t^{-0.5},\; t \cdot \text{warmup}^{-1.5} \big) $$
$$ \text{warmup} = 4000 $$

Take one scalar: $W_{\text{out}}[0,0] = 1$, gradient $g = \partial \mathcal{L} / \partial W_{\text{out}}[0,0] = 0.510$.

First moment at step $t = 1$:

$$ m_1 = \beta_1 \cdot 0 + (1 - \beta_1) \cdot g = 0.9 \cdot 0 + 0.1 \cdot 0.510 = 0.0510 $$

Second moment:

$$ v_1 = \beta_2 \cdot 0 + (1 - \beta_2) \cdot g^2 = 0.98 \cdot 0 + 0.02 \cdot 0.510^2 = 0.00520 $$

Bias correction:

$$ \hat{m}_1 = m_1 / (1 - \beta_1^1) = 0.0510 / 0.1 = 0.510 $$
$$ \hat{v}_1 = v_1 / (1 - \beta_2^1) = 0.00520 / 0.02 = 0.260 $$

Adam step direction:

$$ \hat{m}_1 / (\sqrt{\hat{v}_1} + \epsilon) = 0.510 / (\sqrt{0.260} + 10^{-9}) = 0.510 / 0.510 \approx 1.0 $$

Step 1 (warmup, real $d_{\text{model}} = 512$)

$$ \text{lr}(1) = 512^{-0.5} \cdot 4000^{-1.5} = 1.75 \times 10^{-7} $$

Weight update:

$$ W_{\text{out}}[0,0] \leftarrow 1 - 1.75 \times 10^{-7} \cdot 1.0 = 0.99999983 $$

Barely moves — warmup is conservative on purpose.

Step 4000 (end of warmup)

$$ \text{lr}(4000) = 512^{-0.5} \cdot 4000^{-0.5} \approx 0.000699 $$
$$ W_{\text{out}}[0,0] \leftarrow 1 - 0.000699 \cdot 1.0 = 0.999301 $$

Now you can see the weight shift. Run millions of steps on billions of tokens and am eventually beats <bos>.


What invariants should I check while deriving?

I treat these as unit tests for the hand derivation:

  • Write tensor shape beside every equation. Residual add requires identical shapes.
  • Attention weights sum to $1$ across the key axis per query row.
  • Masked positions get exactly zero probability forward and zero gradient backward.
  • LayerNorm outputs have approximately zero mean and unit variance per token row.
  • Softmax-cross-entropy gradients $\partial \mathcal{L} / \partial z$ sum to zero across the vocabulary.

Production code fuses kernels, uses mixed precision, checkpoints activations, and parallelizes — but none of that changes the calculus above. I use this derivation to debug toy implementations and to understand memory/compute flow before trusting a framework.


Where does one training step leave me?

Forward: embeddings → positional encoding → encoder attention (every matmul) → layer norm → FFN → decoder masked attention → cross-attention → vocab projection → softmax.

Loss: $\mathcal{L} = 2.53$ with label smoothing.

Backward: $\partial \mathcal{L} / \partial z = p - q$ → chain rule through every weight.

Update: Adam ticks $W_{\text{out}}[0,0]$ from $1.000000$ toward $0.999301$ by step 4000.

Next batch runs the same machine on slightly better weights. The whole thing is a matmul, a softmax, a normalize, a ReLU, an outer product, and the chain rule — stacked $N$ deep on each side, iterated over trillions of tokens.

Conceptual version: how attention works in transformers. Building blocks: token embeddings, positional encoding, autograd from scratch. Source: Attention Is All You Need (2017).

Support the writing

If this post helped, a coffee keeps the deep dives, paper picks, and news digests coming.

Buy Me a Coffee