Somewhere around 2017, GPU architects looked at the workloads pouring into their machines and noticed something almost embarrassing: the overwhelming majority of the arithmetic was one operation, repeated beyond counting — multiply two small matrices, add the result to a third. Deep learning had turned the world's compute demand into a monoculture of matrix multiplication. And the general-purpose SIMT machine, magnificent as it is, pays a tax on every single multiply: fetch an instruction, decode it, schedule it, read operands, write results — a whole administrative apparatus wrapped around one floating-point operation per lane.
So the architects did what architects always do when one operation dominates: they gave it its own silicon. A tensor core (NVIDIA's name; AMD says matrix core, Intel says XMX engine — the idea is the same) is a small, fierce matrix-multiply unit embedded inside each SM, sitting right next to the ordinary vector pipes. This lesson is about what that unit computes, why it multiplies in narrow precision but accumulates in wide precision, and why the headline "10×" speedup is something you must earn with data layout — not something you get for free.
The unit of work is not a scalar and not a vector — it is a tile. A single
matrix-multiply-accumulate (MMA) instruction says: take an
with typical shapes like
Compare the ledger. On the ordinary FP32 vector pipes, one instruction buys one fused multiply-add per lane — 32 MACs per warp instruction, each dragging its share of fetch, decode, scheduling and operand routing behind it. The MMA instruction buys thousands of MACs for the same administrative cost. This is the amortisation-of-control argument, and it is the entire justification for the hardware: the energy and area of a modern processor are mostly spent on deciding what to do, not on doing it. Decide once; compute four thousand times.
The figure shows a small tile multiply. Step through it: first one dot product, then all of them at once, then the part that surprises everyone — where the tiles actually live.
That last step deserves a pause. The tiles are not in some private matrix memory — they are
spread across the warp's ordinary register file, as fragments: lane
0 holds a few cells of
Now look again at the instruction above: inputs in FP16, accumulator in FP32. That asymmetry is the second big idea. Halving the width of the multiplier inputs roughly quarters the multiplier silicon (multiplier area grows with the square of operand width), so FP16 or BF16 inputs let you pack four times the multipliers into the same area and power. Neural networks tolerate low-precision products remarkably well.
What they do not tolerate is a low-precision accumulator. A dot product in deep
learning is a long sum — hundreds or thousands of terms. Watch what happens if the running
total is kept in FP16, which carries only 11 significant bits: once the total reaches
For inference the inputs shrink further — FP8 and INT8 formats double the multiplier count yet again (a trained network being used tolerates coarser products than one being trained) — but the accumulator stays wide (FP32, or INT32 for integer paths). The accumulator is always the last thing to give up its bits.
Don't take the cliff on faith — run it. This demo quantises values to an FP16-like format (10 explicit mantissa bits) and accumulates a long dot product two ways: once re-quantising the running total to FP16 after every add, once keeping it wide.
Half the sum simply vanished — no error, no warning, just a wrong answer. Real networks sum messier values than 1.0, so the drift is subtler, but the mechanism is identical: a narrow accumulator rounds away small addends once the total is large. That is why tensor cores wire the accumulation path wide even when the multipliers are narrow.
How much does all this buy? Take a representative modern SM. Its FP32 vector pipes sustain about
If this smells familiar, it should: it is the
What actually runs on them: deep-learning training and inference above all — dense layers directly,
and convolutions lowered into matrix multiplies so the same engines serve both (see
Because then you'd have built a TPU — and inherited its rigidity. A single huge array is unbeatable when the matrices are huge, dense and steady, but it fills and drains slowly, starves on small or odd-shaped tiles, and can do nothing whatever with the 20% of a real workload that isn't matmul. Scattering many small matrix engines through a programmable machine means the non-matmul work runs at full speed next to the tensor units, small tiles still achieve decent utilisation, and when the workload shifts — as it did from CNNs to transformers, and will again — the machine bends instead of breaking. The price is that each small engine re-pays some control overhead the giant array amortised away. Architecture, as always, is choosing which inefficiency you can live with.
Tensor cores do not accelerate "your code". They accelerate tiled, aligned, correctly-typed matrix operands, and nothing else. Feed the library an FP32 array when the fast path wants FP16, hand it matrices whose dimensions aren't multiples of the tile size, or misalign the rows in memory, and the kernel does not fail — it silently falls back to the vector pipes, and your "tensor-core-accelerated" code runs at one-eighth speed with no error message to tell you why. The speedup is opt-in, purchased with layout discipline: pad dimensions to tile multiples, align the strides, convert the types, and check a profiler — not the marketing number — to confirm the matrix units are actually lit. When a kernel underperforms by almost exactly the tensor:vector ratio, the fallback is the first suspect.