AI / ML Theory
Chapter 2 · 16 min

2.4 — The forward pass in batched matrix form

Goal: Vectorise the MLP forward pass over a mini-batch of B examples and get every dimension right cold.

1. Why this session exists

In code, MLPs process a mini-batch of BB examples at a time, not one example at a time — that is what makes GPU computation efficient. The math is identical, but the shapes change. Getting layer shapes right cold (no derivation, you see "B×d2B \times d_2" and just know what's there) is a skill we want before we start computing gradients in 2.5–2.7.


2. Single example: refresher

From 2.1, per-example forward at layer ℓ\ell:

z(ℓ)=W(ℓ)a(ℓ−1)+b(ℓ),a(ℓ)=g(ℓ)(z(ℓ)),z^{(\ell)} = W^{(\ell)} a^{(\ell-1)} + b^{(\ell)}, \qquad a^{(\ell)} = g^{(\ell)}(z^{(\ell)}),

with a(ℓ)∈Rdℓa^{(\ell)} \in \mathbb{R}^{d_\ell}, W(ℓ)∈Rdℓ×dℓ−1W^{(\ell)} \in \mathbb{R}^{d_\ell \times d_{\ell-1}}, b(ℓ)∈Rdℓb^{(\ell)} \in \mathbb{R}^{d_\ell}.


3. Batch: stacking examples as rows

Let A(ℓ−1)∈RB×dℓ−1A^{(\ell-1)} \in \mathbb{R}^{B \times d_{\ell-1}} be the batch of activations at layer ℓ−1\ell - 1, one example per row. Then the batched pre-activation is

 Z(ℓ)  =  A(ℓ−1) (W(ℓ))⊤  +  1B (b(ℓ))⊤  ∈  RB×dℓ, \boxed{\ Z^{(\ell)} \;=\; A^{(\ell-1)}\, (W^{(\ell)})^\top \;+\; \mathbf{1}_B\, (b^{(\ell)})^\top \;\in\; \mathbb{R}^{B \times d_\ell},\ }

with 1B\mathbf{1}_B the all-ones column. In NumPy/PyTorch, the bias addition is implemented by broadcasting: you just write Z = A @ W.T + b.

The batched activation is the pointwise nonlinearity applied entry-by-entry:

A(ℓ)=g(ℓ)(Z(ℓ))  ∈  RB×dℓ.A^{(\ell)} = g^{(\ell)}(Z^{(\ell)}) \;\in\; \mathbb{R}^{B \times d_\ell}.

Why the transpose? Because we chose to put examples as rows of AA. The matrix-vector product W(ℓ)aW^{(\ell)} a from §2 has aa as a column on the right; to apply the same map to every row of AA, the convenient form is A W⊤A\, W^\top.


4. Dimension table

Every shape you'll ever need at the forward pass.

  • Input batch: A(0)=X∈RB×d0A^{(0)} = X \in \mathbb{R}^{B \times d_0}.
  • Weights: W(ℓ)∈Rdℓ×dℓ−1W^{(\ell)} \in \mathbb{R}^{d_\ell \times d_{\ell-1}} (output dim × input dim).
  • Bias: b(ℓ)∈Rdℓb^{(\ell)} \in \mathbb{R}^{d_\ell} (output dim).
  • Pre-activation: Z(ℓ)=A(ℓ−1)(W(ℓ))⊤+b(ℓ)∈RB×dℓZ^{(\ell)} = A^{(\ell-1)} (W^{(\ell)})^\top + b^{(\ell)} \in \mathbb{R}^{B \times d_\ell}.
  • Activation: A(ℓ)=g(ℓ)(Z(ℓ))∈RB×dℓA^{(\ell)} = g^{(\ell)}(Z^{(\ell)}) \in \mathbb{R}^{B \times d_\ell}.
  • Output logits: Z(L)∈RB×dLZ^{(L)} \in \mathbb{R}^{B \times d_L}.
  • Output probabilities: Y^=softmax⁡(Z(L), axis=1)∈RB×dL\hat Y = \operatorname{softmax}(Z^{(L)},\ \text{axis} = 1) \in \mathbb{R}^{B \times d_L} — softmax over the class axis, separately for each row.

A worked instance — MNIST with (d0,d1,d2,d3)=(784,256,128,10)(d_0, d_1, d_2, d_3) = (784, 256, 128, 10) and batch B=32B = 32:

  • XX is 32×78432 \times 784.
  • W(1)W^{(1)} is 256×784256 \times 784, b(1)b^{(1)} is 256256.
  • Z(1)Z^{(1)} is 32×25632 \times 256, A(1)A^{(1)} is 32×25632 \times 256.
  • W(2)W^{(2)} is 128×256128 \times 256, b(2)b^{(2)} is 128128.
  • Z(2)Z^{(2)} is 32×12832 \times 128, A(2)A^{(2)} is 32×12832 \times 128.
  • W(3)W^{(3)} is 10×12810 \times 128, b(3)b^{(3)} is 1010.
  • Z(3)Z^{(3)} is 32×1032 \times 10, Y^\hat Y is 32×1032 \times 10.

If you can write this table cold, the rest of the chapter follows mechanically.


5. The training loss in batch form

For multi-class classification with K=dLK = d_L classes and one-hot targets Y∈{0,1}B×KY \in \{0, 1\}^{B \times K},

L(θ)  =  −1B∑i=1B∑k=1KYi,k log⁡Y^i,k,L(\theta) \;=\; -\frac{1}{B} \sum_{i=1}^B \sum_{k=1}^K Y_{i, k}\, \log \hat Y_{i, k},

equivalently −1Btr⁡(Y⊤log⁡Y^)-\frac{1}{B} \operatorname{tr}(Y^\top \log \hat Y) with log⁡\log elementwise. Using the log-sum-exp form from 0.10 / 1.7,

L(θ)  =  1B∑i=1B[ −zi,yi(L)  +  log⁡∑j=1Ke zi,j(L) ],L(\theta) \;=\; \frac{1}{B} \sum_{i=1}^B \Bigl[\, -z^{(L)}_{i, y_i} \;+\; \log \sum_{j=1}^K e^{\,z^{(L)}_{i, j}} \,\Bigr],

i.e. per-row stable log-sum-exp plus per-row true-class logit subtraction, then average. In PyTorch this is F.cross_entropy(logits, y) with logits of shape B×KB \times K and y of shape BB (class indices).


6. Why this matters for what's next

Backprop in 2.5–2.7 will be derived in the same batched matrix form. Knowing the shapes ahead of time prevents silly transposition bugs: each gradient should be the same shape as its parameter, every δ(ℓ)\delta^{(\ell)} should be the same shape as Z(ℓ)Z^{(\ell)}, and any product that isn't dℓ×dℓ−1d_\ell \times d_{\ell-1} where we wanted weights is a sign we got something wrong.


7. Takeaways

  • Examples-as-rows: A(ℓ)∈RB×dℓA^{(\ell)} \in \mathbb{R}^{B \times d_\ell}, W(ℓ)∈Rdℓ×dℓ−1W^{(\ell)} \in \mathbb{R}^{d_\ell \times d_{\ell-1}}, Z(ℓ)=A(ℓ−1)(W(ℓ))⊤+b(ℓ)Z^{(\ell)} = A^{(\ell-1)} (W^{(\ell)})^\top + b^{(\ell)}.
  • Bias is broadcast across the batch axis.
  • The forward pass is LL matrix-multiplies and LL pointwise activations.
  • Loss is a scalar summary; we'll backprop from it through this graph in 2.5.

Self-test

(a) For an MLP with widths (d0,d1,d2,d3)=(10,64,64,3)(d_0, d_1, d_2, d_3) = (10, 64, 64, 3) and batch B=8B = 8, list the shapes of XX, W(ℓ)W^{(\ell)}, b(ℓ)b^{(\ell)}, Z(ℓ)Z^{(\ell)}, A(ℓ)A^{(\ell)} for ℓ=1,2,3\ell = 1, 2, 3.

(b) Verify that A(ℓ−1)(W(ℓ))⊤+b(ℓ)A^{(\ell-1)} (W^{(\ell)})^\top + b^{(\ell)} produces BB independent vector additions (one per row), not a single matrix add.

(c) Count the number of scalar multiplications in the forward pass of the MNIST network of §4 for a batch B=32B = 32. Compare with the parameter count.

(d) Suppose you swapped to the column-major convention A(ℓ)∈Rdℓ×BA^{(\ell)} \in \mathbb{R}^{d_\ell \times B}. Rewrite the forward formula. Which layout is more cache-friendly when BB is large? (Hint: think about how matmul libraries lay out memory and dispatch BLAS.)