Lab 01 — Roofline, MFU Budget & the Latency Napkin
Build the arithmetic that turns "we want a real-time agent" into "the model must be smaller" — and reproduce the napkin math from Feinberg's Princeton talk.
The problem
A product manager says "sub-second responses." A serving engineer says "we'll add GPUs." Both are guessing. The roofline and the latency napkin turn that conversation into arithmetic, and the arithmetic usually says something neither of them expected: no amount of hardware fixes this; the model has to be smaller.
That conclusion is why a pre-training lead owns inference co-design. The levers that matter —
n_kv_heads, depth vs width, tile alignment, parameter count — are all frozen the moment
training starts.
What you build
| Group | Functions | The idea |
|---|---|---|
| Roofline | arithmetic_intensity, ridge_point, roofline_throughput, roofline_report | FLOPs per byte; below the ridge cut bytes, above it cut FLOPs |
| Utilization | mfu, hfu, mfu_budget | 35% is an identity, not a grade; the breakdown is an agenda |
| Latency | prefill_seconds, decode_seconds, interactive_latency, chips_for_latency_budget, weights_fit_chips | The napkin, end to end |
| Co-design | tile_efficiency, kv_cache_bytes, gqa_saving, decode_batch_intensity, depth_vs_width | The three irreversible levers |
Key concepts
| Concept | Why it is in this lab |
|---|---|
| Arithmetic intensity | Property of the algorithm; decides which resource you fight |
| Ridge point | peak ÷ bandwidth. H100 ≈ 296 — and higher than A100's 154 |
| MFU vs HFU | HFU counts recomputation as useful. Always ≥ MFU, ~33% for free |
| MFU budget | matmul / vector / memory / comms / optimizer → your work queue |
Prefill 2N/token | Compute-bound. 6N is training; using it here is a 3× error |
| Decode = bytes | Every token re-reads every weight. 140 GB per token at 70B |
| The 4×4 station | 16 v5e chips to get 8k prefill under 0.5 s — the talk's conclusion |
| GQA ratio = group size | 64→8 KV heads is exactly 8× less cache, ~0 quality cost |
| The decode wall | Even batch 1024 reaches only 17% of the ridge |
Files
| File | What it is |
|---|---|
| lab.py | Your implementation. Signatures, docstrings and validation contracts given. |
| solution.py | Reference. python solution.py runs a nine-part worked example. |
| test_lab.py | 50 tests: happy path, validation, boundaries, invariants, determinism. |
| requirements.txt | pytest only. Pure stdlib otherwise. |
Run
pytest test_lab.py -v # your lab.py — red until you implement
LAB_MODULE=solution pytest test_lab.py -v # the reference — must be green (50 passed)
python solution.py # the worked example
Where to start
arithmetic_intensity→ridge_point→roofline_throughput. The roofline is onemin(). Gettest_roofline_is_continuous_at_the_ridgegreen — the two roof segments must meet exactly.mfu_budget, thentest_perfect_mfu_when_only_matmul_runs. That boundary case is Feinberg's point: 100% requires a pure matmul loop, which is not a neural network.prefill_seconds/decode_seconds. Thentest_prefill_reproduces_the_talks_number— if you get ~5.8 s you have it right.chips_for_latency_budget. Note it must returnNonewhen the budget is unreachable.- The co-design levers.
The traps:
- Prefill is
2Nper token, forward only.6Nis training — a 3× error. decode_secondsmust not containpeak. Decode is driven by bytes and bandwidth. If peak FLOP/s appears in it, the model is wrong.chips_for_latency_budgetmust terminate and returnNonerather than loop. "More hardware does not fix this" is a real answer and the function has to be able to give it.- A budget below the scaffolding overhead is impossible — raise, don't return a number.
gqa_savingmust rejectn_kv_heads > n_query_headsand non-divisible configurations.
Success criteria
-
LAB_MODULE=solution pytest test_lab.py -v→ 50 passed. -
Your
lab.pyreaches 50 passed. -
python solution.pyruns and you can explain all nine sections. - You reproduce ~5.8 s single-chip prefill and the 4×4 station conclusion.
- You can explain why decode beats prefill at batch 1, and the batching caveat.
- You can state why H100's ridge point is higher than A100's, and why that matters.
How this maps to the real stack
| This lab | The real thing | Where the miniature lies |
|---|---|---|
roofline_report | NVIDIA Nsight Compute's roofline; Intel Advisor | Real tools measure achieved bytes and FLOPs from hardware counters. Ours computes the analytical intensity, which is the ceiling, not the achieved value. |
mfu / mfu_budget | Megatron-LM and MaxText log MFU per step; profiler timelines give the breakdown | Real breakdowns come from kernel traces, where ops overlap. Ours assumes serial phases, so it over-attributes time. Directionally right, and the right mental model. |
prefill_seconds / decode_seconds | vLLM / TensorRT-LLM benchmarks; llm-analysis | Ours ignores attention's T² term (fine at 8k, wrong at 128k), kernel launch overhead, scheduling, and the fact that real bandwidth utilization is 60–90% of peak. Use it to size, not to promise SLAs. |
gqa_saving | The num_key_value_heads field in any HF config | Exact. This one is not an approximation. |
tile_efficiency | XLA / cuBLAS padding behaviour | Real libraries pick among several tile sizes and may pad differently. The effect is real; the exact constant is not. |
HARDWARE table | Vendor spec sheets | Peak numbers are marketing maxima at ideal clocks. Sustained throughput under thermal load is 5–15% lower. |
What is not a lie: the ridge-point definition, 2N per token for prefill, the KV-cache
formula, the GQA ratio, and the fact that decode is bandwidth-bound. Those are exact, and those
are what get asked about.
Extensions
- Add the attention
T²term. At 128k context the sequence-dependent attention matmuls dominate prefill (Phase 00, Break 1). Extendprefill_secondsand watch the napkin's conclusions change completely for long-context products. - Model continuous batching properly. Add a queue with arrival rates and compute p50/p99 latency versus throughput. That curve — the latency/throughput frontier — is what serving teams actually optimize, and this lab only shows its two endpoints.
- Add speculative decoding. Model an acceptance rate
αand a draft model of sizeN_d; compute the effective intensity gain and find where it stops paying. - Validate against real hardware. Run a 7B model in vLLM, measure tokens/sec at several
batch sizes, and compare to
decode_batch_intensity. The gap between your model and reality is the lesson. - Build the co-design search. Given a latency budget and a compute budget, search over
(N, n_layers, d_model, n_kv_heads)for the configuration that maximizes predicted quality (using Phase 01's scaling law) subject to meeting the napkin. That is the actual job, and it is a genuinely good portfolio piece.
Interview / resume bullets
- "Built a roofline and MFU-accounting toolkit for LLM training and serving — arithmetic intensity versus ridge point with an explicit optimize-bytes-or-FLOPs verdict, MFU/HFU disambiguation, and wall-clock decomposition across matmul, vector, memory, collective and optimizer time to rank optimization work."
- "Reproduced Google DeepMind's published inference-scaling analysis from first principles: ~5.8 s single-chip prefill for a 70B model on TPU v5e, a 4×4 prefill station to meet a 0.5 s API limit, and the batch-1 result that decode costs ~3.8× prefill — concluding quantitatively that meeting an interactive latency budget requires a smaller model rather than more accelerators."
- "Quantified the irreversible inference co-design levers set at pre-training time: GQA group size (8× concurrent-request throughput at 8 KV heads versus 64), tile-aligned matrix dimensions, and depth-versus-width serial latency cost."
- Interview-ready: "Decode never becomes compute-bound at any realistic batch size — even 1024 reaches 17% of the ridge — because the KV cache grows with the batch while the weight read does not. That one fact explains batching, GQA, quantization and speculative decoding."