Same Chip, Two Orders of Magnitude Apart
One square matrix multiplication, synchronized and timed at two sizes on the same GPU delivers a percent or so of peak at one size and nearly the full specification number at the other.
Explaining that gap yields the chapter’s whole toolkit:
- a cost model you can compute with pencil and paper,
- a measurement discipline the frameworks’ async dispatch makes non-optional,
- a diagnosis → fix table the next six sections fill in.
Arithmetic Intensity and the Roofline
Every op asks for FLOPs and bytes. Intensity = FLOPs/byte.
\textrm{performance} \leq \min(P,\; I \cdot \beta)
![]()
Matmul \mathbf{X}_{B\times D}\mathbf{W}_{D\times F}: intensity \approx 2B/b — batch size is intensity. Elementwise ops: intensity below 1, forever bandwidth-bound.
The Ridge Point, on Our Card
NVIDIA GeForce RTX 4090, 25 GB
ridge point = 164 FLOP/byte
~165 FLOP/byte: the machine wants about 165 ops for every byte fetched just to break even. Almost nothing you write naturally gets there. Performance work is mostly about bytes.
Correct GPU Timing
Dispatch is asynchronous — Python enqueues, the GPU runs behind.
naive timer: 0.69 ms with synchronize: 9.10 ms
Implicit Barriers
Anything that needs a value waits for the device: .item(), .numpy(), print(x), if loss < 0.1:, nonzero().
read every step: 0.060 s read once: 0.039 s
Rule: synchronize once per minibatch at most — and only when the host actually needs the value.
Synchronized Benchmarking
Warmup (kernel selection, compilation), sync, time, sync:
One Kernel across Three Regimes
['256: 2.0', '512: 18.9', '1024: 119.3', '2048: 157.6', '4096: 151.5', '8192: 165.8']
Small matrices are overhead-bound because launch time rivals arithmetic. Intermediate matrices use an increasing fraction of the 128 SMs. Large matrices approach the compute roof. The roofline is the ceiling; the measured knee (~2048–4096) sits well past the nominal crossover (~500) — that gap is utilization and overhead.
Diagnosis Determines the Fix
add: 0.14 ms/call
mul: 0.14 ms/call
sin: 0.14 ms/call
sigmoid: 0.14 ms/call
Below the ridge, sin and addition take similar time because memory traffic, rather than arithmetic, determines elapsed time.
The Method
An unfused elementwise chain performs one memory round trip per operation. The compiler section measures how fusion removes the redundant traffic.
measure → classify → fix → re-measure
The rest of the chapter is this loop, applied: hardware explains the constants; compilation attacks bandwidth and overhead; memory and precision buy headroom; more GPUs buy more roof.