Multiplying Matrices
Matrix-times-vector
transforms exactly one vector at a time — feed it in, get the transformed vector
out. But real problems rarely stop at one vector. A game engine needs to transform
every vertex of a mesh with thousands of points. A rendering pipeline needs to rotate,
then scale, then move an object every single frame — three separate transformations chained
together, over and over. Recomputing the whole chain vector by vector, frame by frame, would be
painfully slow.
Matrix-times-matrix multiplication solves both problems in one stroke: it can
transform a whole batch of vectors at once, and it can also fuse two chained transformations into
a single combined matrix, computed just once. Best of all, it's built from nothing more exotic
than repeating matrix-times-vector, one column at a time.
It helps to think of a batch of vectors — say, every corner of a triangle — stacked side by side
as the columns of one matrix. Transforming the whole shape at once is then just
matrix-times-matrix: one multiplication moves every corner in a single step, instead of looping
over each vector one at a time by hand.
The recipe: column by column
Think of the second matrix B as a row of vectors stacked
side by side — its columns. To build AB, transform
each column of B by A, the
exact matrix-times-vector operation you already know, and place the results side by side as the
columns of the answer:
AB = A\begin{bmatrix} | & & | \\ \vec{b}_1 & \cdots & \vec{b}_p \\ | & & | \end{bmatrix} = \begin{bmatrix} | & & | \\ A\vec{b}_1 & \cdots & A\vec{b}_p \\ | & & | \end{bmatrix}.
There's a shortcut for reading off any single entry without writing out a whole column: take each
row of the left matrix and
dot it with
each column of the right matrix. The entry in row
i, column j of the product is
(AB)_{ij} = (\text{row } i \text{ of } A) \cdot (\text{column } j \text{ of } B).
For this to work the rows of A and columns of
B must be the same length — so the columns of
A must match the rows of B. An
(m\times n) times an (n\times p) gives an
(m\times p). The shared n is consumed.
Row dot column, cell by cell
Step through the four output cells. Each one lights up a row of A and
a column of B, and their dot product fills the answer. Four little dot
products build the whole product.
Notice that the highlighted row never touches any entry of B outside
its highlighted column, and vice versa — each output cell only ever depends on one row and one
column, never the whole matrices at once. That's exactly why the calculation splits so cleanly
into four independent little dot products instead of one tangled computation.
Worked example: two 2×2 matrices, entry by entry
Let A = \begin{bmatrix} 2 & 1 \\ 0 & 3 \end{bmatrix} and
B = \begin{bmatrix} 1 & 4 \\ 2 & 0 \end{bmatrix}. Every entry is a
dot product of a row of A with a column of B:
(AB)_{11} = (2)(1) + (1)(2) = 4, \qquad (AB)_{12} = (2)(4) + (1)(0) = 8,
(AB)_{21} = (0)(1) + (3)(2) = 6, \qquad (AB)_{22} = (0)(4) + (3)(0) = 0.
Four dot products, four entries:
AB = \begin{bmatrix} 4 & 8 \\ 6 & 0 \end{bmatrix}.
Worked example: checking one entry two ways
The column method and the row-dot-column shortcut are the exact same calculation seen two ways —
let's prove it on the same matrices. The column method transforms column 1 of
B, which is \begin{bmatrix}1\\2\end{bmatrix},
by A:
A\begin{bmatrix}1\\2\end{bmatrix} = \begin{bmatrix} (2)(1)+(1)(2) \\ (0)(1)+(3)(2) \end{bmatrix} = \begin{bmatrix}4\\6\end{bmatrix}.
That gives the whole first column of AB in one go:
(4, 6). Now check just its top entry with the row-dot-column shortcut —
row 1 of A dotted with column 1 of B:
(AB)_{11} = \begin{bmatrix}2 & 1\end{bmatrix}\cdot\begin{bmatrix}1\\2\end{bmatrix} = (2)(1)+(1)(2) = 4.
Both routes land on 4. That's not a coincidence — the row-dot-column
rule for a single entry is exactly what's hiding inside the column-by-column recipe; it just lets
you compute one number without writing out the whole column first.
Worked example: a 2×2 times a 2×3
Multiplication isn't limited to square matrices. Let
A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} (a
2\times 2) and
B = \begin{bmatrix} 1 & 0 & 2 \\ 3 & 1 & 0 \end{bmatrix} (a
2\times 3). The inner dimensions both equal
2, so the product is defined and has shape
2\times 3 — three dot products per row, one per column of
B:
AB = \begin{bmatrix} (1)(1)+(2)(3) & (1)(0)+(2)(1) & (1)(2)+(2)(0) \\ (3)(1)+(4)(3) & (3)(0)+(4)(1) & (3)(2)+(4)(0) \end{bmatrix} = \begin{bmatrix} 7 & 2 & 2 \\ 15 & 4 & 6 \end{bmatrix}.
Notice the shape rule in action: the 2's in the middle (the columns of
A, the rows of B) matched and vanished,
leaving a result built from A's 2 rows and
B's 3 columns.
Worked example: fusing two transformations into one
Here's the payoff promised in the hook. Let S = \begin{bmatrix} 2 & 0 \\ 0 & 3 \end{bmatrix}
stretch x by 2 and
y by 3, and let
R = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} rotate
90°. Applying S first and then
R to a vector \vec{v} means computing
R(S\vec{v}) — but matrix multiplication lets us fuse the two steps into
a single matrix M = RS up front:
M = RS = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}\begin{bmatrix} 2 & 0 \\ 0 & 3 \end{bmatrix} = \begin{bmatrix} 0 & -3 \\ 2 & 0 \end{bmatrix}.
Now try it on \vec{v} = (1, 1) two different ways. Step by step:
S\vec{v} = (2, 3), then R(2,3) = (-3, 2). In
one shot with the fused matrix: M\vec{v} = (0(1)+(-3)(1),\ 2(1)+0(1)) = (-3, 2).
Same answer, but the fused version only needed one matrix-times-vector instead of two —
and if you had a thousand vectors to transform, you'd only pay for computing
M once, no matter how many vectors follow.
Why "row dot column"?
It looks arbitrary until you remember what a matrix does. Multiplying by
B then by A means transforming a vector
twice: A(B\vec{x}). The product matrix
AB is the single matrix that does both steps at once — which is
exactly why composing transformations
is matrix multiplication, and why a deep
neural network
is just a chain of matrix products.
Turn it around and the row-dot-column rule stops looking arbitrary at all: entry
(AB)_{ij} has to be "what happens to component
j of the input, once it lands in output slot
i, after passing through both transformations" — and that is
precisely a dot product between a row of A and a column of
B. The rule isn't a memorised recipe; it falls straight out of asking
what "do this transformation, then that one" has to mean.
Two habits from ordinary number multiplication will trip you up here:
-
Matrix multiplication is not commutative. For ordinary numbers,
3 \times 5 and 5 \times 3 are the same.
For matrices, AB is usually not equal to
BA — sometimes one product isn't even defined when the other is!
This is exactly the order-matters
lesson from composing transformations: rotate-then-scale is a genuinely different
transformation from scale-then-rotate, and the two matrix products that represent them differ
too — the worked example below shows RS and
SR coming out as two different matrices from the very same
ingredients. Always keep the order of a matrix product exactly as written.
-
The inner dimensions must match, full stop. An
m\times n matrix can only multiply an
n\times p matrix — the number of columns on the left must equal the
number of rows on the right. If they don't match, the product is simply
undefined, not "padded with zeros" or rounded to fit. A
2\times 3 can multiply a 3\times 2, but
it can never multiply another 2\times 3.
Every time a 3D game renders a frame, each object on screen typically gets rotated, scaled, and
moved — three separate transformation matrices chained together. Multiplying those three matrices
together once, at the start, fuses them into a single combined matrix. Then every one of
an object's thousands of vertices only needs one matrix-times-vector each frame,
instead of three — a huge performance win repeated dozens of times a second, for every object on
screen.
Scaled up, matrix multiplication is also the single most expensive computation inside training a
modern neural network — layer after layer is nothing but matrices multiplying matrices of numbers,
over and over, millions or billions of entries at a time. That's the whole reason specialised
chips (GPUs and TPUs) exist: they're built almost entirely to do enormous matrix multiplications
as fast as physically possible.