x = jax.random.normal(jax.random.PRNGKey(0), (256 * 1024 * 1024,)) # 1 GB
t = d2l.Benchmark(lambda: x * 2.0).time
print(f'measured {2 * x.size * 4 / t / 1e12:.2f} TB/s '
f'(spec: about 1.0 TB/s)')The roofline had two free parameters: peak compute P and bandwidth \beta. This section is where they come from — and why P/\beta has climbed over the long run.
Properties here; buying advice lives in the Tools appendix.
Every chip boundary ≈ one order of magnitude. Keep bytes home.
The highlighted rung — 5–15 µs to launch a kernel — is why the overhead regime exists.
A big elementwise op is a bandwidth meter — bytes known, arithmetic negligible:
measured 0.91 TB/s (spec: about 1.0 TB/s)
Streaming kernels land within tens of percent of spec.
Long run, per generation: compute ~4×, bandwidth ~2×, capacity ~1.7× — the ridge point climbs, the model starves a little more. Any single step can buck it: B200 and RTX 5090 both bought bandwidth faster, and their ridge points fell. Packaging picks which wall moves; HBM-on-interposer is the countermeasure.
Same 8-bit exponent (fp32/tf32/bf16) ⇒ same range: swapping costs precision, not magnitude. Every halving of storage wins twice: 2× FLOP/s and half the bytes — tf32 buys throughput only, it still stores 32 bits.
No P2P on GeForce: every inter-GPU byte staged through host DRAM — PCIe-limited, tens of GB/s for a raw copy (a couple for NCCL busbw at its default fallback — §13.5 finds the stage), flat from 2 to 4 GPUs. An NVL72 gives each GPU 1.8 TB/s. That gap is the multi-GPU chapter.
One DRAM read ≈ 500 fp32 multiplies.
Prefill: weight reuse ~ context length ⇒ typically compute-bound. Decode: every token reads all weights ⇒ tokens/s ≲ β / model bytes. One model, both ends of the roofline.