TL;DR: Arithmetic intensity = FLOPs ÷ bytes = 4e12 ÷ 40e9 = 100 FLOP/B. The H100's ridge is peak ÷ bandwidth = 989e12 ÷ 3.35e12 ≈ 295 FLOP/B in bf16, so at 100 the kernel is memory-bound: the bytes take 12 ms to move and the FLOPs 4 ms to compute, and the attainable throughput is 100 × 3.35 = 335 TFLOPS, a third of peak.
How to approach it
Ask which precision the kernel runs in, since the ridge doubles in fp8, and whether the 40 GB is DRAM traffic or includes L2 hits (the roofline uses the bytes that actually crossed HBM). Compute the ridge first because it is the card's property and does not depend on the kernel, then the intensity, then compare. State the two times, memory and compute, so the verdict has a magnitude. Close with the action that follows from the verdict.
A strong answer
A typical situation: a profiler is open, an optimization is chosen by instinct, and two divisions would have ruled out half the options first. The counters give both inputs directly.
The Roofline Model puts two ceilings on a kernel's throughput: the tensor-core peak, and bandwidth times the kernel's arithmetic intensity. Whichever is lower binds. The intensity at which they cross is the ridge point.
card: H100 SXM
peak (dense bf16) = 989e12 FLOP/s
HBM bandwidth = 3.35e12 B/s
ridge = peak ÷ bandwidth = 989e12 ÷ 3.35e12 ≈ 295 FLOP per byte
(fp8: 1,979e12 ÷ 3.35e12 ≈ 590 FLOP/B)
kernel:
FLOPs = 4e12
bytes = 40e9
intensity = 4e12 ÷ 40e9 = 100 FLOP/B
verdict: 100 < 295 → memory-bound in bf16 (and further from the fp8 ridge)
the two times:
memory time = 40e9 ÷ 3.35e12 = 11.9 ms
compute time = 4e12 ÷ 989e12 = 4.0 ms
the kernel cannot finish before the bytes arrive, so ~12 ms is its floor
attainable = min(peak, intensity × bandwidth) = min(989, 100 × 3.35) = 335 TFLOPS
sanity: 335 ÷ 989 = 34% of peak, which matches memory ÷ compute time = 4 ÷ 11.9.
The counters that supply those inputs come from Nsight Compute: sm__inst_executed_pipe_tensor or the smsp__sass_thread_inst_executed_op_* family for FLOPs, and dram__bytes_read.sum plus dram__bytes_write.sum for HBM traffic. Nsight Compute's own "Roofline" section plots the kernel against the ridge directly; the manual version is the same two divisions. A DCGM-level hint, without profiling, is DCGM_FI_PROF_DRAM_ACTIVE near 1.0 while DCGM_FI_PROF_PIPE_TENSOR_ACTIVE sits low.
What to do depends on which wall:
| verdict | the lever | example |
|---|---|---|
| memory-bound (intensity < ridge) | move fewer bytes per FLOP | fuse the kernel with its neighbors so intermediates stay in registers or SMEM; tile so operands are reused from L2; halve the bytes with fp8 |
| compute-bound (intensity > ridge) | do fewer FLOPs, or do them at higher peak | lower precision (fp8 tensor cores), sparsity, algorithmic change |
| neither ceiling reached | the kernel is latency- or occupancy-bound | check achieved occupancy, warp stalls, launch overhead |
For this kernel at 100 FLOP/B, tripling the intensity by keeping intermediates on chip would lift it to the ridge and to 989 TFLOPS. That is the argument for fusion: a chain of elementwise ops after a GEMM each re-reads and re-writes the whole tensor at intensity near 1, and fusing them into the GEMM epilogue makes their bytes disappear.
The reversal condition: the same kernel on a part with a lower ridge. The B200's ridge is 2,250 ÷ 8 ≈ 281, similar; but the MI300X's is 1,307 ÷ 5.3 ≈ 247 and the A100's is 312 ÷ 2.04 ≈ 153. On an A100 this kernel at 100 FLOP/B is closer to balanced, and after the fusion that took it to 300 it would be compute-bound. Memory-Bound vs Compute-Bound Kernels is the same classification stated as a rule.
What interviewers probe next
- "What is the intensity of a decode step at batch 1?" 2N FLOPs against 2N bytes in bf16, so about 1 FLOP/B, 300x below the ridge; batch b raises it to b.
- "And a large GEMM?" For M × K times K × N in bf16 the intensity is about 2MNK ÷ (2(MK + KN + MN)); for square 8k matrices that is around 2,700 FLOP/B, well above the ridge, so GEMMs are compute-bound and everything else is not.
- "The counters say 40 GB but the tensors total 4 GB. Why?" The kernel re-reads operands from HBM because its tiles do not fit in L2 or SMEM; poor reuse inflates DRAM bytes. The fix is tiling, and the counter that shows it is L2 hit rate.
- "Does the ridge move with precision?" Yes, peak doubles in fp8 while bandwidth is fixed, so the ridge doubles to about 590; a kernel that was balanced in bf16 is memory-bound in fp8.
Common mistakes
- Naming the roofline and then guessing "compute-bound" because 4 TFLOP sounds like a lot; the number that matters is the ratio.
- Using the sparsity-inflated peak (1,979 for bf16) and getting a ridge of 590 in bf16.
- Counting bytes from tensor sizes rather than DRAM traffic, which understates the bytes for any kernel with poor reuse.
- Treating "memory-bound" as a final answer without naming the change (fusion, tiling, precision) that would move the kernel.
Key takeaways
- Intensity = FLOPs ÷ bytes; ridge = peak ÷ bandwidth. H100 bf16 ridge ≈ 295 FLOP/B, fp8 ≈ 590, A100 ≈ 153.
- 4 TFLOP over 40 GB is 100 FLOP/B: memory-bound, 12 ms floor, 335 TFLOPS attainable.
- Memory-bound means move fewer bytes: fuse, tile for reuse, drop precision. Compute-bound means fewer or cheaper FLOPs.
- Nsight Compute's dram bytes and tensor-pipe counters give the two inputs; DCGM's DRAM_ACTIVE vs PIPE_TENSOR_ACTIVE is the coarse hint.
