AI News HubLIVE
In-site rewrite4 min read

Show HN: Headroom – measure your GPU's true bandwidth ceiling for local AI

Headroom is a 30-second browser-based test that measures your GPU's memory bandwidth ceiling for local AI inference, requiring no model download, using synthetic weights, and needing WebGPU support. It measures bandwidth via three independent methods and detects a subgroup bug that can cause silent garbage output in browser LLMs. Validations show in-browser engines are launch-overhead-bound, with 2-3x hardware headroom.

SourceHacker News AIAuthor: Ar5en1c

HEADROOM — how fast is your machine, really?

How fast is your machine, really?

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 GPU driver for the subgroup bug that makes some in-browser LLMs silently produce garbage.

No model download — the probes use synthetic weights (only ~0.4 MB of public tensor metadata is fetched to derive exact bytes-per-token). No account, no ads, no telemetry: measurements stay in your browser unless you share them. Requires WebGPU (Chrome/Edge on desktop, recent Android Chrome).

~30 s

full suite

0 B

model download

FP64

reference gate before timing

med·5

medians, not bests

RUN SHEET

detecting device…

preparing…

HEADROOM · RUN RECEIPTCANARY —

— tok/s ceiling

gemma 4 e2b class · 2.46 GB resident

0kernel efficiency —read ceiling —

gemma 4 e2b qat · 2.46 GB (ple, ~1.05 GB/token est)—

8b class q4 · ≈4.7 GB dense—

bonsai 27b 1-bit · 3.8 GB dense—

SUBGROUP CANARY: —HEADROOM

Projections are measured read bandwidth ÷ bytes touched per token, at empty context. When the tensor manifest derives (the default), bytes-per-token is exact: streamed core only, gathered embeddings and unused towers excluded. If derivation fails, the row falls back to an honest band (full-artifact floor → active-core estimate) and is labeled as such. Real engines land below the ceiling; the gap is engine overhead — see methodology for the M1 and RTX 5070 validations.

Imported receipts — other collectors vs this device's ceiling

collectorwhat ranheadlinevs this run

Receipts — every number, its method

proberesultbytes/passiters × repsgate

CAN'T RUN — NO WEBGPU

Methodology

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. 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 a pinned revision — never a moving main) 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 real engines we measured ourselves (full receipts in evidence/). An RTX 5070 measures 577 GB/s here — 86% of its 672 GB/s GDDR7 spec, through a browser tab — giving an E2B ceiling of ≈717 tok/s; Xenova's engine decodes ~217 tok/s on that machine (30% of ceiling): streaming the weights needs only 1.4 ms of each 4.6 ms token, so ~70% of the 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 (~50%). Two machines, one conclusion: today's in-browser engines are launch-overhead-bound, not bandwidth-bound — the hardware has 2–3× headroom, which is the point of measuring it. 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.7× 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.7×10⁻¹ 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.

Objections, answered

"A GEMV can't hit 100% of a ceiling — your probe must be slow." Three independent bandwidth probes are reported (copy, triad, pure-read); the ceiling is the read-shaped one, and a GEMV is ~pure-read too (it writes 16 KB per 128 MiB streamed). When a kernel lands a few percent above the probe, that's the probe's reduction overhead, the x-vector living in cache, and session variance — the receipts carry every raw number and the card just says "saturated." 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.