metalworkingGitHub
/techniques/roofline

Roofline

A roofline is the two-ceiling model (peak bandwidth and peak compute) that turns a kernel's measured time into a verdict: how far from the physical limit, and which limit. On a platform without a profiler, it's the primary analysis tool, and you build it yourself.

CUDA equivalent: the roofline chart Nsight Compute draws for you automatically. Here you assemble it from two measurements:

The bandwidth ceiling: measure, don't trust the spec. A STREAM-style probe (large buffer copies/reads swept past cache sizes) gives achievable DRAM bandwidth. The worked example in the case-study codebase, m5-gemm's bandwidth.metal/bandwidth.py, measured ~516 GB/s copy on an M5 Max: ~84% of the 614 GB/s spec, a typical achievable fraction for unified memory. Use the measured number as your roof; using the spec quietly inflates every "% of peak" you report.

The compute ceiling: from the reverse-engineered tables. metal-benchmarks' instruction throughput tables give per-op ceilings per core; scale by core count and clock. For matmul, a shortcut: the best measured GEMM on your chip class is a practical ceiling (13.5 TFLOPS fp32 on a 40-core M5 Max, from the case study).

performance (FLOP/s, log) arithmetic intensity (FLOPs / byte, log) memory roof · slope = measured bandwidth (~516 GB/s on M5 Max, ~84% of spec) compute roof · best measured GEMM (~13.5 TFLOPS fp32) ridge point minimum intensity that can escape the memory roof BANDWIDTH BOUND more kernels live here than CUDA intuition expects COMPUTE BOUND the GEMM school's territory elementwise op (intensity ~0.1) pinned to the slope: only fusion helps LLM decode tok/s tracks the bandwidth spec 4096² tiled GEMM (~85× below the memory roof) tiling moved it here; naive matmul stays on the slope both roofs are measured, not spec: bandwidth.metal for the slope, the best local GEMM for the ceiling

The chart the page has been describing. Everything left of the ridge point is capped by the amber slope no matter how clever the ALU work; everything right of it answers to the green ceiling. The example points are this glossary's own recurring cast.

Then every benchmark result gets the same two-line interrogation: achieved GFLOPS ÷ compute ceiling, achieved GB/s ÷ bandwidth ceiling. Whichever ratio is higher names your binding constraint. If both are low, the kernel is overhead-bound (dispatch, sync) or stalled (spills, occupancy); go look at what the compiler emitted.

Platform-specific hygiene, learned the hard way in the war stories:

  • Thermals are part of the model. Fanless and laptop chips decay under sustained load. One project measured long-run throughput decay dropping from 50% to 6.7% just by locking fans to max. Report sustained numbers, not first-second numbers.
  • The ceilings move with problem size in ways spec sheets hide: the GEMM ladder's winner flips at three different sizes because launch latency, unroll quality, and bandwidth trade dominance. A roofline claim without a size sweep is a guess.
  • End-to-end or it didn't happen: a kernel at 90% of roofline that's 3% of runtime buys nothing. Amdahl kills more optimizations here than physics does.