A 30-second test of your device's true memory-bandwidth ceiling for local AI: the number that decides your tokens/s. It also checks your driver for the subgroup bug that makes some in-browser LLMs produce garbage.
No model download, no account, no telemetry. Synthetic weights, FP64-checked. One outbound request: ~0.4 MB of public model metadata to derive bytes per token, no run data attached. Your numbers stay in this browser unless you post them.
| gemma 4 e2b qat · 2.46 GB (per-layer embeddings, ~1.05 GB/token est) | — |
| 8b class q4 · ≈4.7 GB dense | — |
| bonsai 27b 1-bit · ~3.8 GB (est; weights repo is gated) | — |
Projections are measured read bandwidth ÷ bytes touched per token, at empty context; derived manifests make bytes-per-token exact, estimates fall back to an honest band. Real engines land below the ceiling; the gap is engine overhead (see methodology).
Runs the transformers.js WebGPU path on the exact model your ceiling is quoted for (onnx-community/gemma-4-E2B-it-ONNX, apache-2.0). One-time model download (~1.6 GB, cached by your browser), then decode is timed with the same discipline as the probes: warmup excluded, 3 reps of 128 tokens, median. Utilization divides by the derived 0.81 GB/token split and is labeled approximate: the ONNX q4 layout differs slightly from the QAT artifact.
Reference points, measured by this page and receipted: an RTX 5070 reads 577-589 GB/s across three sessions (86-88% of its 672 spec) for a gemma 4 e2b ceiling of 717-731 tok/s, and the one in-browser engine we measured decodes 217 (30% of ceiling). An Apple M1 reads 51-56 GB/s for a ceiling of 63-69; the same engine does 30-40, while native Ollama on that M1 runs its model at 72-79% of ceiling. The gap is engine overhead, not bandwidth: that is the headroom. receipts · methodology
| collector | what ran | headline | vs this run |
|---|
Every timed kernel, verbatim, including the canary shapes. If you think a probe is unfair, the code to attack is right here.
| probe | result | bytes/pass | iters × reps | gate |
|---|
Each bar is one kind of machine, measured by this page. The left mark is what an LLM engine delivers today; the right mark is that hardware's ceiling; the span between them is the headroom. Solid engine marks were measured on our machines; hollow ones are the 30-50% range of the one engine we measured, applied as an estimate. Board is opt-in via a public GitHub issue: hardware only, no accounts, and canary verdicts appear only on author-run receipts.
The premise. Batch-1 LLM decode is memory-bound: every generated token must stream the weights it touches through the GPU once. Your tokens/s ceiling is therefore memory bandwidth ÷ bytes touched per token. Headroom measures that bandwidth three independent ways (buffer copy, triad read-write, and a pure-read reduction, the GEMV-shaped one), then runs a from-scratch GEMV ladder with deterministic synthetic weights to show how much of the ceiling real kernels reach on your device. The quoted measured ceiling is the strongest receipted evidence of deliverable bandwidth: the read probe or a verified kernel that beat it, whichever is higher. Synthetic weights are valid because streaming time doesn't depend on the values; only correctness checking does, and the hash-derived fill lets an FP64 CPU reference verify every kernel before it's timed.
The denominator is derived, not guessed. Headroom range-fetches the model's safetensors header (~0.4 MB of public metadata from huggingface.co, pinned revision, never a moving main; this is the page's one outbound request and no run data rides on it) and classifies every tensor for text decode. For Gemma 4 E2B QAT (2.46 GB artifact, 2,780 tensors): 0.81 GB streams every token (transformer layers 0.68 + lm_head 0.10 + per-layer projection 0.03), 1.31 GB of embedding tables is gathered one row per token, and 0.34 GB of vision/audio towers is never touched in text decode. Ceilings are quoted at empty context; KV-cache reads grow with context length and lower the real ceiling.
Validated against a real engine we measured ourselves (full receipts in evidence/). An RTX 5070 measured 577, 584, and 589 GB/s across three receipted sessions here (86-88% of its 672 GB/s GDDR7 spec, through a browser tab), for an E2B ceiling of 717-731 tok/s. The in-browser engine we measured (Xenova's Gemma 4 WebGPU engine, the same engine the canary shape comes from) decodes 217 tok/s on that machine (receipt): 30% of ceiling. A cross-check that the comparison is fair: at f16 that decode rate would need roughly 700 GB/s of weight streaming, above the machine's measured ceiling, so the engine necessarily streams quantized weights; at the derived q4 split its stream occupies ~30% of the pipe and the other ~70% of each token's budget is launch overhead and non-GEMV work. An Apple M1 measures 51-56 GB/s (74-82% of spec), ceiling 63-69 tok/s; the same engine does 30-40 (43-63% of ceiling), while native Ollama on that M1 (qwen2.5-1.5b, its own bytes-per-token) decodes at 72-79% of the page's ceiling (receipt in native/). One engine, two machines, one conclusion: that engine is launch-overhead-bound, not bandwidth-bound; the hardware has 2-3x headroom, which is the point of measuring it. The 30-50% band drawn on the dial is this one measured engine's range on these two machines, not a survey of all engines. Bonus finding from the same runs: uncoalesced access keeps ~50% of ceiling on Apple's unified memory but collapses to 27% on discrete GDDR7; coalescing is worth 3.7x on Blackwell. Expect ±10% between sessions on busy machines (we saw 50.7 vs 55.8 GB/s minutes apart on one laptop); medians of 5 are quoted within a run. If a card's number ever disagrees with reality, that disagreement is a finding: file it.
Canary v2: the shape that actually shipped. The v1 variants are the naive non-uniform shapes (current Chrome rejects them at compile time: reported as BLOCKED). Canary v2 replicates, verbatim, the analysis-clean shape from the July engine: subgroupAdd after a lane-divergent store with an earlier reduction result live across it. It compiles clean everywhere, runs 20 trials against an FP64 reference with a subgroupShuffleXor butterfly as the correct-everywhere control, and requires a 32-wide minimum subgroup (skipped honestly elsewhere). Verified live on 2026-07-19: an RTX 5070 (Blackwell) on stable Chrome 150 fails 20/20 trials with relative errors up to 1.7e-1 while the butterfly passes 20/20 (receipt): a live, silent correctness bug that static analysis cannot see, shown on the card as CANARY FAIL. A PASS from v2 means the engine reduce shape was behaviorally verified on your stack, not just assumed.
"A GEMV can't hit 100% of a ceiling: your probe must be slow." Three independent bandwidth probes are reported (copy, triad, pure-read), and a GEMV is ~pure-read too (it writes 16 KB per 128 MiB streamed). Verified kernels can land above the read probe: on one receipted M1 session the best FP64-gated GEMV measured 55.4 GB/s against a 50.7 GB/s probe, 9% over (probe reduction overhead, the x-vector living in cache, and session variance). That is exactly why the quoted ceiling is max(read probe, best gated kernel): a ceiling your own kernels can exceed would not be a ceiling. Every raw probe number stays in the receipt, and if our accounting were wrong the FP64 gates would say so.
"Wall-clock timing without timestamp-query is amateur." timestamp-query is quantized or absent in browsers depending on platform and flags. We time amortized blocks of ≥150 ms (auto-calibrated iteration counts) and take medians of five: timer resolution contributes <0.1% error at that block size. Every receipt states the block size and repeat count, results are re-verified for correctness after timing, and a native CUDA companion study on the same silicon is the cross-check.
"Your buffers fit in cache." The weight buffer is 128 MiB against a 48 MB L2 on the largest consumer GPU we've measured (and single-digit-MB SLCs elsewhere). Buffer sizes are in every receipt.
"Synthetic weights aren't a real model." Streaming time doesn't depend on the values being streamed: only correctness checking does, and hash-derived fills give an FP64-checkable reference with zero download. We measure the hardware ceiling and validate real engines against it separately; both sides are receipted.
"The canary is just wide-subgroup semantics, not a bug." No: the test gates on a 32-wide minimum subgroup, and the butterfly control runs the same shuffle lanes in the same kernel shape: it passing 20/20 proves the lane grouping is sane and the data is right; only the subgroupAdd lowering returns wrong sums, deterministically, against an FP64 reference. That's a miscompile, not a semantics gap.
"Which engines shipped this pattern?" The shape is taken verbatim from the July 2026 post-mortem of a production in-browser LLM engine: root cause, upstream patch, and evidence are public in the campaign repo.
The rules. Deterministic inputs (hash-derived, identically regenerable on CPU). Every kernel is checked against an FP64 CPU reference before it is timed: a kernel that fails correctness gets no performance number. Timing is wall-clock over large amortized dispatch blocks, median of 5 repeats, on your actual compositor-loaded browser: honest conditions, honestly labeled. If your machine hits 70% of its ceiling, the card says 70%.
Canary v1: the naive tier. Alongside v2, the original variants use the plainly non-uniform syntax from the same bug class. A guarded control must pass everywhere for the run to count; each risky variant reports PASS (your driver tolerates the pattern), FAIL (your stack corrupts it: engines beware), or BLOCKED (your browser's compiler rejects the class at compile time: the safe modern behavior, and what current Chrome does). v2 above exists precisely because BLOCKED here is not the end of the story: the shape that shipped was analysis-clean.