How we predict tok/s: a calibrated roofline
Published 2026-08-28 · updated 2026-08-28 · engineering note
There are two easy ways to predict LLM inference speed, and both are wrong in instructive ways. Parameter heuristics ("a 70B model does X tok/s per GPU") ignore that the same model runs 4× differently across quantizations and frameworks. Benchmark lookup tables are accurate for the exact configuration someone measured — and silent about yours: a different context length, batch size, quantization, GPU count or engine version. This note describes the third approach this site uses: a roofline (Speed-of-Light) model, calibrated against published measurements and locked by regression tests.
The roofline
Transformer inference has two phases with opposite bottlenecks. Prefill (processing the prompt) is compute-bound: time ≈ FLOPs / achievable TFLOPS. Decode (generating tokens) is memory-bandwidth-bound: every new token re-reads the weights and the KV cache, so time ≈ bytes / achievable bandwidth. The roofline takes each phase at the maximum of its compute time and its memory time:
t_stage = max( FLOPs / (TFLOPS_dtype × efficiency),
bytes_moved / (bandwidth × efficiency) )
decode bytes/token ≈ active_weights + KV_read(context, layer mix)
prefill FLOPs/token ≈ 2 × active_params
The two efficiency factors carry all the realism. Peak spec-sheet numbers are never achieved: we use an empirical memory-bandwidth factor of ~0.8 for HBM-class GPUs (a convention shared with NVIDIA's aiconfigurator SDK, from which our stage formulas are ported) and measured 0.65–0.85 on LPDDR5 edge devices — our Jetson calibration corpus of ~69 published data points puts dense ≥4 GB models at ~0.7 under llama.cpp and ~0.8 under tuned vLLM.
What the naive roofline misses
A textbook roofline still gets real deployments wrong. The corrections that matter:
- Framework awareness. vLLM, SGLang, TensorRT-LLM, llama.cpp and Ollama differ in static overhead, activation workspace coefficients (SGLang runs ~1.3× the TRT-LLM activation table), sliding-window billing and recurrent-state dtype (SGLang/TRT default the state to float32 — twice vLLM's footprint for the same model).
- Quantization that follows the hardware. NVFP4 is Blackwell-native only; FP8 is emulated (slower than INT8) on Ampere; weight-only 4-bit decodes fast but prefills at fp16 compute rates. A dtype dropdown is not enough — the GPU decides what the dtype is worth.
- MoE with active parameters. Speed follows the ~activated share (e.g. 5.1B of gpt-oss-120b's 117B), memory follows the full expert weights, and expert-parallel sharding changes both the per-GPU bytes and the communication bill.
- Attention that isn't uniform. MLA models cache a compact latent
(~70× smaller than a naive GQA reading); 2026 hybrids cap most layers at a sliding
window or a fixed recurrent state — decode re-reads far fewer bytes than
layers × contextsuggests. See our companion note on hybrid attention memory. - Parallelism is sublinear. Tensor-parallel decode hits collective-communication
costs (we model a TP-tiered efficiency table, and treat NVLink vs PCIe differently);
KV shards at most
kv_headsways; past that, ranks re-read replicas. - Batching has a knee. Throughput grows with batch until the compute roof; per-user latency (TPOT) degrades past it. Our capacity planner solves for the largest batch that still meets a latency SLA rather than quoting best-case aggregate numbers.
Calibration, and staying honest
A model with this many terms can fit anything — unless it is pinned. Ours is pinned three ways:
- 16 datacenter anchors: published benchmark points (8B/70B dense at several batch sizes and TPs, DeepSeek-class MoE at EP8, single-stream and saturated) that the model must reproduce — enforced by a regression test, so a formula change that drifts the anchors fails CI.
- Control points for the hard cases: DeepSeek-R1 EP8 on 8×H200 (~103 tok/s single-stream against a ~100 target), and hybrid worked examples (gpt-oss-120b ≈ 4.8 GB KV @131K, Gemma 3 27B ≈ 10.4 GB) asserted within ±10% windows.
- An edge corpus: ~69 published Jetson measurements across power modes. They confirmed the bandwidth-bound decode model at 15W/25W within a few percent — and falsified it at 7W, where we apply a measured ×0.6 derate instead of pretending the roofline holds.
Honesty cuts both ways, so the same page that shows a prediction shows its limits: throughput carries a ±15–30% band (engine versions alone have moved a fixed model+GPU pair by up to 7× on young stacks — Jetson Thor's first-year vLLM containers being the extreme case); p99 latency under bursty load needs a queueing model we don't ship yet; and hybrid-attention throughput is labeled reduced-calibration while public benchmarks catch up. When the model can't know, the UI says so instead of printing a confident number.
Why bother, when you can benchmark?
You should benchmark — before you buy the fleet, not instead of sizing it. The calculator's job is the step before: shrinking a search space of 48 GPUs × quantizations × TP/PP/EP layouts × batch sizes to the two or three configurations worth measuring, with live rental prices attached. A calibrated roofline is exactly the right tool for that: directionally reliable everywhere, precise where it has anchors, and explicit about which is which.
Try the calculator — every throughput number links back to this methodology.
Sources
- Stage formulas ported from NVIDIA's aiconfigurator SDK (SOL backend, activation coefficient tables).
- Roofline model: Williams, Waterman & Patterson, “Roofline: An Insightful Visual Performance Model” (CACM 2009).
- Edge calibration corpus: jetson-ai-lab benchmarks, smolhub power-mode series, NVIDIA Jetson Thor vLLM container posts, llama.cpp discussions (full list in our Jetson note — forthcoming).
- Anchor values and tolerance windows are asserted in this site's public behavior — recompute any of them in the calculator.