skip to content

What is arithmetic intensity, and how does it decide whether a GPU operation is compute- or bandwidth-bound?

level: middleimportance: must knowfreq 58%

answer

  1. FLOPs per byte moved
  2. two ceilings, one crossover
  3. peak FLOP/s divided by peak bandwidth
  4. matmul reuses each loaded element many times
  5. elementwise: 2 FLOPs per 12 bytes

basics

~20 s

Arithmetic intensity is the floating-point operations an op performs per byte it moves to and from device memory. Compare it with the device's peak-FLOP-rate divided by peak bandwidth: below that ridge point the op is bandwidth-bound, above it compute-bound.

solid answer

~50 s

Arithmetic intensity is FLOPs per byte of traffic to off-chip device memory. The roofline model says attainable throughput is `min(peak FLOP/s, intensity * peak bandwidth)`, so the crossover — the ridge point — is peak FLOP/s divided by peak bandwidth, which on modern accelerators sits in the tens of FLOPs per byte. A dense n x n matmul does `2*n^3` FLOPs while ideally touching `3*n^2` elements, so at four bytes per element its intensity is `2*n/(3*4)`: for n = 4096 that is roughly 680 FLOPs per byte, far right of the ridge and comfortably compute-bound. An elementwise `out = a*x + y` does 2 FLOPs per element while moving 12 bytes — about 0.17 FLOPs per byte, deeply bandwidth-bound. That is why doubling a device's peak FLOP rating nearly halves the matmul's time and does nothing whatsoever for the elementwise op.

go deeper

for a junior

Be ready to say that a GPU has two separate limits — how fast it can compute and how fast it can move data — and that big matrix multiplies stress the first while elementwise operations stress the second.

for a middle

An interviewer at this level expects the actual ratio: FLOPs per byte, compared against peak FLOP rate divided by peak bandwidth. Derive the matmul's intensity from 2n^3 FLOPs over 3n^2 elements and contrast it with a two-FLOP, twelve-byte elementwise op.

for a senior

Show you classify before you optimise: measure achieved FLOP/s and achieved bytes/s against the device ceilings, and say plainly which optimisations are pointless on each side. Note that shape and batch size can move an operation across the ridge.

for a principal

Own the consequence for planning: hardware whose arithmetic rate grows faster than its bandwidth pushes more of a workload onto the bandwidth side over time, so capacity decisions and optimisation budgets should be argued from a measured mix of intensities, not from FLOP counts.

## The quantity Every operation a GPU runs must pull its inputs from the device's off-chip memory onto the chip and push its results back. **Arithmetic intensity** is the ratio between the useful arithmetic and that traffic: floating-point operations performed per byte moved. It is a property of *an operation at a given shape and layout*, not of the hardware — the hardware supplies the yardstick you compare it against. ## The roofline and its ridge point A device has two ceilings: a peak arithmetic rate in FLOP/s and a peak memory bandwidth in bytes/s. If an operation streams its data once, the best it can do is ``` attainable FLOP/s = min(peak FLOP/s, intensity * peak bandwidth) ``` Plot that against intensity and you get the roofline: a sloped section where bandwidth is the limit, then a flat section where the arithmetic units are the limit. The corner between them — the **ridge point** — sits at `peak FLOP/s / peak bandwidth`, expressed in FLOPs per byte. Anything to the left of it is **bandwidth-bound**; anything to the right is **compute-bound**. The practical fact that makes this topic an interview staple is where that ridge sits: on current accelerators it is in the tens of FLOPs per byte, because arithmetic throughput has grown far faster across hardware generations than memory bandwidth has. A great many deep-learning operations therefore land on the bandwidth side, and the flat roof is irrelevant to them. ## Why dense matmul is on the compute side For `C = A * B` with square n x n matrices, the arithmetic is `2*n^3` FLOPs — one multiply and one add per accumulation step. The ideal traffic is reading A, reading B and writing C: `3*n^2` elements. So ``` intensity = 2*n^3 / (3*n^2 * bytes_per_element) = 2*n / (3 * bytes_per_element) ``` At four bytes per element and n = 4096 that is about 680 FLOPs per byte — an order of magnitude past any realistic ridge point. Two things matter here. First, intensity **grows with n**: the larger the problem, the more times each loaded element is reused. Second, the `3*n^2` figure assumes an implementation that loads blocks into fast on-chip memory and reuses them; a naive version that re-reads a row from device memory for every output element has intensity near 1 and runs nowhere near peak. It is the *reuse pattern*, not the raw FLOP count, that makes dense matmul suit massively parallel hardware. ## Why elementwise, normalization and softmax are on the bandwidth side Take `out = a*x + y`: per element that is 2 FLOPs against 12 bytes at four bytes per element (read x, read y, write out), so intensity is about 0.17. A pointwise activation is similar — one element in, one out, eight bytes, a handful of FLOPs. A normalization layer computes a mean and a variance across a feature vector and then scales and shifts each element: a small constant of arithmetic per element, still well under one FLOP per byte. A batched softmax over a large vocabulary is the same story read at scale — per logit you subtract a maximum, exponentiate, accumulate into a sum and divide, which is a few FLOPs against reading and writing several bytes, and an implementation that makes several passes moves the whole tensor several times. All of these are limited by how fast the memory system can stream the tensor past the chip. ## What follows from knowing the side - **The two sides answer to different investments.** Raising the peak FLOP rating lifts the flat roof and pushes the ridge point to the right: compute-bound work speeds up, bandwidth-bound work does not move at all, and some previously compute-bound work quietly becomes bandwidth-bound. - **For bandwidth-bound work, the only lever is moving fewer bytes** — merging consecutive passes so an intermediate never goes back to device memory, keeping a tile resident on-chip, avoiding gratuitous copies and layout changes. - **For compute-bound work, the lever is keeping the arithmetic units fed** — tiles large enough to amortise the loads, shapes that fill the units, enough parallel work to hide latency. - **Shape changes the answer.** The same weight matrix multiplied by a single input vector instead of a batch degenerates to a matrix-vector product: each weight is read once and used in two FLOPs, giving roughly 0.5 FLOPs per byte at four bytes per element. Same layer, other side of the ridge. ## Measure rather than guess Time the operation, divide its FLOP count by the time to get achieved FLOP/s, divide its byte count by the time to get achieved bytes/s, and compare each with the device's ceilings. Whichever is close to its ceiling is the binding one. If neither is close, the limit is elsewhere: not enough parallel work, per-launch overheads on tiny operations, or a device sitting idle waiting for input. ## The classic wrong conclusions Treating a lower FLOP count as automatically faster; assuming a GPU is always compute-limited because it has thousands of arithmetic units; counting only the model's weights as "memory" and forgetting the activation traffic that dominates it; and expecting one more powerful device to speed up every part of a step by the same factor.

  • Does batch size change which side of the ridge point a weight matrix multiply falls on?
    Yes. With a healthy batch, each weight loaded from device memory is reused across every row of the batch, so intensity grows with the batch dimension and the operation is compute-bound. At batch size one it degenerates into a matrix-vector product: every weight is read once and used in a multiply and an add, giving about 0.5 FLOPs per byte at four bytes per element. The same layer is then firmly bandwidth-bound, which is why tiny batches leave a device idle even though the FLOP count per sample is unchanged.
  • A 64-channel EEG classifier's forward pass is almost entirely elementwise gating and masking. Will a device with faster matrix units help?
    No. Gating and masking are one or two FLOPs per element against reading and writing the tensor, so the model lives far left of the ridge point and is limited by memory bandwidth end to end. Faster matrix units raise a ceiling this workload never touches. The levers that would help are fewer passes over the activations, merging consecutive elementwise stages into one pass, and a device with more bandwidth rather than more arithmetic.
  • How do you check empirically which side an operation is actually on?
    Count its FLOPs and its bytes analytically, time it in isolation, and convert both to rates. If achieved FLOP/s is near the device's peak, it is compute-bound; if achieved bytes/s is near peak bandwidth, it is bandwidth-bound. If neither is near its ceiling the operation is limited by something else entirely — too little parallel work, per-launch overhead on a small tensor, or the device waiting on data that has not arrived.

A device is a workshop with fast machinists and one narrow loading dock. Big matmuls are jobs where a single crate of parts keeps everyone busy for hours; elementwise ops are jobs where every part is unpacked, touched once and shipped back out.

saying these in an interview costs you the question

  • Treats a lower FLOP count as automatically meaning lower wall-clock time
  • Assumes GPUs are always compute-limited because they have many cores
  • Expects a device with double the peak FLOPs to speed up every operation
  • Confuses weight-memory footprint with memory bandwidth traffic
  • Forgets that each intermediate result is written back to device memory

context