Vol. II · Issue 07 · JUL 2026Methodology — Truth Seriesv3.7 · Jul 2026
// METHODOLOGY · v3.7
Every formula. Every constant. Every weight.
If you find a flaw in our scoring, file an issue on GitHub.
We ship the patch the same week. This page documents the entire
9bench composite score, including the parts we can't measure perfectly.
1. What we measure
— GPU compute
WebGPU matrix multiplication
We run a 1024×1024 FP32 matrix multiplication compute shader on
your GPU. The shader executes 2 × N³ floating-point
operations per pass. Best-of-3 measurement with adaptive warm-up
so laptop GPUs that idle at low power get a fair reading.
Output is GFLOPS (billions of FP operations per second).
— CPU single + multi
SHA-256 hash chain
Single-core: SHA-256 hash chain on the main thread via
Web Crypto API. Multi-core: same workload across N Web Workers
where N = navigator.hardwareConcurrency. Output is
hashes per second. Multi-core scaling efficiency is also reported.
— RAM bandwidth
TypedArray throughput
Sequential reads, sequential writes, and a random-access pointer-chase
on a 256 MB Float32Array. Reports GB/s read, GB/s write, and ns latency.
Browser sandboxing caps this at 30-50% of native — we never pretend
otherwise.
2. The formulas
Every component score is a simple linear or logarithmic transform of
the raw measurement. There is no proprietary "magic" — anyone can
reproduce these numbers.
GPU score
round(GFLOPS × 3)
Calibration: a typical 2024 mid-range GPU (~500 GFLOPS in browser)
scores ~1500. RTX 4070-class hits ~3000 in browser (well below
native ceiling).
CPU single-core score
round(SHA_per_sec_single / 300)
Calibration: Apple Silicon high-end ~1300, modern x86 ~1000-1500.
CPU multi-core score
round(SHA_per_sec_multi / 600)
Multi-core counts cores divided by 600. Browser Web-Worker scaling
is structurally below native (~3-5× speedup typical for 16-core
instead of native ~12×).
RAM score
round(((read_GBs + write_GBs) / 2) × 60)
Average of read + write bandwidth × 60. Browser-sandbox-capped — see
section 5 for limits.
3. Composite weighting
The 9bench overall score is a weighted geometric mean
of the four component scores. Geometric mean prevents a single weak
component from being masked by strong ones — the "no shortcut" version.
GPU: 35% — modern workloads (AI, video, gaming) are GPU-bound.
CPU multi-core: 45% — primary CPU path for development, multitasking, productivity.
RAM: 20% — bandwidth matters, but capped by browser-sandbox so we don't over-weight.
We don't include CPU single-core in the composite because it
correlates strongly with multi-core for in-browser SHA workloads.
Including it would double-count.
When a component can't be measured — no WebGPU adapter, a
parallel phase that doesn't report, a memory allocation the browser
refuses — that component is dropped and the remaining weights renormalize
over what was actually measured. Written in general form, over the set
S of measured components with total weight W:
overall = exp( Σi∈S wi × ln(scorei) / W ), where W = Σi∈S wi
The two common cases fall straight out of it and produce exactly the same
numbers as before, so no stored score has ever changed:
No WebGPU (W = 0.65) — exp((0.45·ln CPU·M + 0.20·ln RAM) / 0.65)
No memory measurement (W = 0.80) — exp((0.35·ln GPU + 0.45·ln CPU·M) / 0.80)
A run that measures less than 65% of the total weight is not
scored at all. It is shown to you with whatever did measure, but
it gets no composite, is never submitted, is in no percentile pool and is
not on the leaderboard. Below that threshold the number would be a
statement about our measurement failing rather than about your hardware.
Partial runs are labelled by basis everywhere they appear — page, share
card and preview image — ranked only against runs measured the same way,
and kept off the main leaderboard. An unmeasured component is
never reported as a zero and never named as your bottleneck.
4. Tier brackets
Tiers are descriptive labels for sharing, not gospel.
We use letter-tiers (S/A/B) for the top half because that's
Reddit-shareable, and word-labels for the bottom half because no
one shares "D-tier".
Tier
Score
Description
S-tier
≥ 1386
Enthusiast / workstation
A-tier
≥ 900
Power user
Solid daily driver
≥ 600
Comfortable for most work
Working machine
≥ 300
Office class
Patient & honest
≥ 0
It still gets the job done
5. What we cannot measure
⚠ Honest Disclaimer · Truth Series
Browsers sandbox memory access. The RAM number is relative, not native bandwidth.
We could hide that and pretend our number is gospel. We don't. Read the methodology.
Browsers cap absolute throughput on RAM workloads via the JavaScript
memory model (no SIMD-vectorized memcpy, no DMA, no direct mmap).
Browser RAM scores typically run at 30-50% of native. We disclose this
on every result page.
We also cannot measure: thermal throttling under sustained load,
disk/SSD I/O, network bandwidth, GPU memory bandwidth (only compute),
peak-vs-sustained turbo behavior. For those, install a native benchmark.
What we can measure: relative ranking across machines.
9bench is calibrated for cross-platform fairness — a 2024 MacBook Pro
and a 2024 Windows desktop running the same browser produce comparable
composite scores within ±5-10%.
6. Validation against native benchmarks
Until July 2026 this page claimed 9bench landed
"within 3-10% of native benchmarks for CPU and GPU compute."
That number was never measured. It was an estimate that got written down as
a fact and then repeated on the landing page. This section replaces it with
the only paired data we actually have, including the part that makes us
look bad.
The test
Two laptops, same chassis model, same session, same day, both on mains
power. Each ran 9bench once and Geekbench 7 once.
n = 2 machines, one run each.
Device A — i7-12650H (6P+4E, 16 threads) · RTX 4060 Laptop, 24 CU · 32 GB
Device B — i7-12700H (6P+8E, 20 threads) · RTX 4050 Laptop, 20 CU · 64 GB DDR4
The numbers
Measurement
Device A
Device B
A / B
Agrees?
9bench GPU (GFLOPS)
372.8
230.3
1.62×
—
Geekbench 7 OpenCL
87,334
71,559
1.22×
same order
9bench CPU single (h/s)
468,274
387,297
1.21×
—
Geekbench 7 CPU single
1,932
2,011
0.96×
INVERTED
9bench CPU multi (h/s)
1,293,243
916,599
1.41×
—
Geekbench 7 CPU multi
9,675
10,816
0.89×
INVERTED
Multi ÷ single (9bench)
2.76×
2.37×
—
—
Multi ÷ single (Geekbench)
5.01×
5.38×
—
we understate
What agrees
The GPU ranking. Both benchmarks put Device A ahead; we simply show a wider
gap. Our test is a single untiled 1024×1024 FP32 matmul with an arithmetic
intensity of about 2 FLOP/byte, so it is limited by cache and memory
bandwidth rather than raw FP32 throughput. Geekbench 7's GPU suite is a
geometric mean over ML, raytracing and image workloads that lean on units
scaling with SM count — its 1.22× is close to the 1.20× SM ratio. Two
benchmarks measuring different things, agreeing on order.
For scale: 372.8 GFLOPS is 2.9% of the 4060 Laptop's theoretical
FP32 peak, and 2.0% for the 4050. A naive-but-coalesced native
CUDA kernel reaches roughly 5% of peak; cuBLAS reaches about 60%. Our GPU
number is therefore around half a naive native kernel and a twentieth of an
optimised one. It is a throughput probe, not a peak-FLOPS measurement.
What does not agree
Both CPU rankings inverted. 9bench ranked A above B on
single-core and multi-core; Geekbench ranked B above A on both. Two
separate causes, both in our code:
The workload is not really a CPU workload. One
iteration costs about 2,135 ns on Device A. A 64-byte SHA-256 using the
SHA-NI instructions present on both chips is roughly 20 ns. The other
~99% is crypto.subtle argument validation, an ArrayBuffer
allocation, a promise and a microtask drain — the loop awaits every
digest, so it can never batch. We measure the browser's crypto call path,
and that path's cost tracks the JS engine and the OS scheduler, not core
width.
The multi-core estimator measures the slowest thread.
Every worker is handed an identical iteration count and we wait for all of
them, then divide total hashes by wall clock. When the per-worker cap
binds — as it did on both these machines — that reduces algebraically to
threads × (rate of the single slowest worker). Fast cores
contribute nothing except by not being slowest. Equal-work-plus-barrier is
a documented anti-pattern on hybrid CPUs.
The scaling number gives it away. A 20-thread Alder
Lake scales 2.37× in 9bench and 5.38× in Geekbench. An 8-thread Snapdragon
phone scales 2.77× in 9bench. When a 20-thread laptop scales worse than an
8-thread phone, the benchmark has hit a shared bottleneck and stopped
measuring cores.
Note which way the barrier bias actually points: it penalises the machine
with more threads, so on its own it should have put Device B
ahead. The observed result is the other way. The barrier is
a real defect, but the dispatch-bound workload is what produced this
inversion.
Re-tested after the v3.6 estimator fix
Both machines were re-run on v3.6, same day, same conditions. Two of the
three rankings are now correct. One is not.
A / B ratio
9bench v3.6
9bench before
Geekbench 7
Result
GPU
1.23×
1.62×
1.22×
matches
CPU single-core
0.88×
1.21×
0.96×
order fixed
CPU multi-core
1.14×
1.41×
0.89×
STILL INVERTED
GPU: essentially exact now. Replacing the
max-of-samples with a median moved the ratio from 1.62× to 1.23×
against Geekbench's 1.22×. The old figure was 33% too wide; the gap is
now about one percent. Absolute numbers dropped — Device A from 372.8 to
256.3 GFLOPS — which is the upward bias coming out, not the hardware
changing.
CPU single-core: order corrected. Not by the
estimator, which does not touch the single-core path, but by letting the
2-second budget bind instead of the old iteration cap. The previous 21%
inversion was largely noise from a 0.4-second window. It now agrees with
Geekbench on which machine is faster, while overstating the margin
(14% vs 4%).
CPU multi-core: still wrong. The gap narrowed from
41% to 14% in the wrong direction, but the ranking did not flip. The
per-worker telemetry shows why the estimator is no longer the problem:
on both machines every worker ran the full window within 25 ms of every
other (9,966–9,975 ms and 9,952–9,961 ms), so the barrier effect is
gone. What remains is the workload. Scaling is still 3.21× and 2.47× on
16 and 20 threads where Geekbench measures 5.01× and 5.38× — a shared
bottleneck in the browser's crypto dispatch path that gets worse with
more concurrent workers, which penalises exactly the machine with more
threads.
So the v3.6 note that the estimator fix would correct the ranking
was itself too confident. It corrected two of three. The
multi-core ranking needs the workload replaced, not the estimator — that
is v4.0, and it does not ship until it has been validated against native
references on at least five machines. Until then the multi-core number
should be read as a measure of the browser's parallel crypto throughput,
which is a real thing and reproducible, but is not a CPU ranking.
What this does NOT establish
n = 2 machines, one run each, one browser, one day. No
repeats, no idle gate, no best-of. A single background stall on one of 20
workers moves the multi-core number directly.
It does not give a correction factor. We can say the
ranking inverted on this pair. We cannot say by how much 9bench is wrong
in general, and we will not publish a percentage that pretends otherwise.
It does not prove the GPU test is right — only that it
agreed on order once. One agreeing pair is not validation.
It says nothing about non-hybrid CPUs, Apple Silicon, AMD, or
any other browser. Both machines are Intel hybrid running the
same browser.
If you have run both 9bench and Geekbench on the same machine,
file an
issue with both result links. Paired data is the only thing that turns
this section into a real validation, and right now we have one pair.
7. FAQ
Is 9bench accurate?
It depends which number, and we now have data instead of an assertion. GPU: a real relative measure. On our one paired test the GPU ranking matched Geekbench 7's order, with a wider gap (9bench 1.62x, Geekbench 1.22x) because our untiled matmul is bound by cache and memory bandwidth. CPU: currently not a reliable ranking. On the same pair, 9bench put machine A ahead by 21% single-core and 41% multi-core; Geekbench put machine B ahead by 4% and 12%. Both inverted. Two causes: at a 64-byte payload most of the measured time is crypto.subtle dispatch rather than SHA-256, and the multi-core estimator reduces to thread count times the slowest worker's rate. RAM: browser-sandboxed, 30-50% of native, as always stated. n=2, one run per machine — enough to disprove a claim, not enough to calibrate one. Section 6 has the full table. The earlier answer here claimed 'within 3-10% of native for CPU and GPU compute'; that figure was never measured against a native benchmark and is retracted.
Does 9bench take money from chip vendors?
No. Zero. The 'Vendors paid us: 0' counter on the Landing page is hardcoded to 0 because that's a fact. r/hardware banned UserBenchmark for documented anti-AMD bias; we built 9bench to fill that gap with an open-source, vendor-neutral alternative.
Why does my RAM score look so low?
Browsers (V8, SpiderMonkey, JavaScriptCore) sandbox memory access for security. Float32Array reads and writes are not vectorized to the same degree as native code, and there is no SIMD pathway for arbitrary memory work. Typical browser RAM throughput is 30-50% of native. We could pretend our number is gospel; we choose to disclose this on every result page instead.
Does it run on iPhone / iPad / Android?
Partly. Safari 26+ on iOS/iPadOS 26 has WebGPU. On Android it depends on the GPU vendor and Android version, not just the Chrome version — many phones have it switched off. Those devices run the CPU + RAM tests and get a clearly-labelled CPU+RAM-only score, ranked in its own pool.
Why is it free?
Because it costs us $0 to run. The benchmark executes entirely in your browser. The only server cost is storing the optional submitted score in Cloudflare D1, which is essentially free at our scale. No tracking, no upsell, no premium tier.
How is this different from UserBenchmark?
UserBenchmark has documented anti-AMD bias (caught by independent reviewers in 2019, banned from r/hardware and r/AMD). 9bench takes zero money from chip vendors. The scoring formula is in this page and on GitHub. r/hardware can verify every weight.
Where is my data stored?
The benchmark runs locally in your browser. The test page has a consent box (default-on) — when checked, an anonymous summary is submitted to Cloudflare D1 after the test finishes. The submitted row contains: score breakdown, GPU name string, CPU core count, AI capability snapshot, a short 'Browser-Major / OS / Arch' UA fragment, timestamp, and a random 8-char hash ID. No IP address, no cookies, no fingerprints, no geolocation. Uncheck the box before clicking Start to run the benchmark without storing anything.
What does 'self-reported' mean on a GPU name?
Some browsers (Firefox in strict privacy mode, Tor, Brave Strict) hide GPU info from web pages. When that happens, 9bench shows 'Browser hides this — add manually' and lets you type your GPU. Self-reported entries are tagged so they cannot be confused with detected hardware.
8. License
9bench is MIT-licensed. You can fork it, ship your own variant, run it
commercially. Truth-series tools should not be locked behind proprietary
licenses. Source on
GitHub.
A benchmark that changes its measurement silently cannot be trusted.
Every change to how scores are measured or computed is documented here,
including our own mistakes.
v3.2 — July 10, 2026
Fixed a GPU timing bug that produced impossible scores.
On fast GPUs, a measurement pass could finish in under a millisecond —
below the resolution of the browser's performance.now() timer.
The quantized duration inflated GFLOPS by 10-100×, which is how a
GTX 1070 briefly topped the leaderboard at a physically impossible
51 TFLOPS. The benchmark now verifies the compute shader actually
produced output before timing, and scales iteration counts until each
sample runs long enough (≥25 ms) to measure reliably.
Removed 16 invalid results produced by that bug from
the leaderboard and percentile pool. Every remaining score comes from
a verified measurement.
Server-side validation added. Submissions are now
checked for physical plausibility and internal consistency against
the documented formulas before they enter the database.
Fairer scoring without WebGPU. Previously a missing
GPU measurement collapsed the overall score. Now the weights
renormalize over CPU + RAM (45/65 · 20/65), those runs rank in their
own percentile pool, and the leaderboard only lists GPU-measured runs.
v3.7 — July 29, 2026
A candidate replacement CPU workload now runs after every
consenting test, unscored. Once the scored phases finish and
your result is saved, the page spends about twelve extra seconds on the
v4.0 candidate: a 0.4-second re-probe of the current workload (to measure
how much your chip has throttled since the scored phase), then two
WebAssembly kernels — a SHA-256 integer chain and a Mandelbrot
floating-point loop — single-threaded and then one worker per thread.
The raw rates are stored next to your result and printed in the console
log, labelled experimental. They are not scored, not ranked, not shown on
your result page, and do not touch your number or anyone else's.
Why: the multi-core ranking is still inverted. Section 6
has the numbers. v3.6 fixed the estimator; re-testing showed the workload
is the remaining defect. Every iteration of the current test crosses into
the browser's crypto machinery, which runs on a small shared thread pool
the browser controls — so the multi-core number measures that pool, not
your cores. Worse, hashing this way rides on dedicated SHA hardware that
2021+ CPUs have and 2015–2020 Intel desktops don't — a 4× cliff that has
nothing to do with how fast a CPU is. The workload has to be replaced,
and after once publishing an accuracy claim we had never measured, we are
not scoring a new one until it passes a gate.
The pass criteria are published now, before the data
exists. Section 10: at least 20 devices with paired Geekbench
references across four CPU families, rank correlation with its confidence
interval, an inversion-rate bound on close pairs including the exact pair
we got wrong, scaling, cross-browser and repeatability bounds, and a
self-verification rate. The kernels check their own output against tables
computed by an independent implementation at build time — a run that
computes the wrong answer cannot produce a number. If the candidate fails
the gate, we revise it and the clock restarts, or we drop it. The
criteria do not move after we see the numbers, and every qualifying pair
enters the published dataset — we cannot drop the ones that make us look
bad.
Known limits of the shadow measurement itself. It runs
on a chip that has just done 25–40 seconds of benchmark work, so on
thermally limited devices its rates read low — that is why it starts with
a throttle probe, and why promotion never rests on shadow-position
numbers: accuracy runs use a cold, full-length solo mode
(/test/?v4solo=1). If you close the tab during the shadow
phase, your scored result is unaffected; we lose the shadow row, and we
count how often that happens.
What this does not change: every scored number. Same
workloads, same estimator, same pools, same leaderboard. The scored
submit path is byte-identical to v3.6 — you can diff it. The test grows
by about twelve seconds, all of them after your result is already saved,
and your result link appears first so you can skip them.
v3.6 — July 29, 2026
The multi-core estimator is fixed. Every worker now runs
until the same absolute deadline and reports its own iteration count and
elapsed time; the aggregate is the sum of per-worker rates.
The old estimator handed every worker an identical iteration count and
divided total work by wall clock, which — whenever the per-worker cap
bound — reduced algebraically to threads × the rate of the single
slowest worker. On a hybrid CPU the efficiency cores set the
result and every performance core was discarded. Worker spawn time is
also out of the measured window now, which had penalised machines with
more threads.
The GPU headline is a median, not a maximum. It was
resolving to the larger of the measurement and the best warm-up sample
across up to 15 draws, while the code comment claimed a median. A maximum
over noisy samples is biased upward and rewards variance, so a machine
with background load could out-score a quiet machine of identical
throughput. It now takes the median of the timed samples and nothing
else. Expect GPU numbers a few percent lower than before — that is the
bias coming out, not your hardware changing.
These two changes rebase the scores, so the pool forks.
Old results cannot be converted — only the aggregate was ever
stored, never the per-worker detail, so there is no arithmetic that turns
a pre-v3.6 number into a v3.6 one. We are not going to invent one. Every
existing result keeps its permalink and its numbers, gains a notice
explaining which estimator produced it, and is ranked only against other
pre-v3.6 runs. The leaderboard shows one generation at a time and says
which. Nothing was recomputed, extrapolated or deleted.
Per-worker timings are now recorded. Min, median and max
worker elapsed time for the multi-core window. The spread between them is
a direct measurement of core heterogeneity — it distinguishes "this
machine has slow efficiency cores" from "one worker stalled". This data
was already being collected and thrown away.
The single-core measurement window is no longer truncated on
fast hardware. v3.4 capped iterations at the old constant so no
device would run more work than before; on fast machines that cap bound
and collapsed the window to about 0.4 seconds — the shortest measurement
on exactly the machines where a scheduler migration does the most damage.
The 2-second budget now decides. It is a rate, so the number does not
shift; it just varies less.
What this does NOT fix: the magnitude — and, it turns out, the
multi-core ranking either. At a 64-byte payload the CPU test
still spends most of its time in crypto.subtle dispatch
rather than in SHA-256. When this entry was written we expected the
estimator fix to at least correct which machine ranks higher.
We then re-ran both validation machines and it corrected two of three:
GPU now matches Geekbench almost exactly and single-core order is right,
but the multi-core ranking is still inverted. The
numbers and the reason are in Section 6. Replacing the workload is a
genuinely different benchmark that needs its own validation against
native references on at least five machines before it ships. That is
v4.0, and it will be announced the same way. We are leaving this
paragraph's original prediction visible rather than editing it away.
v3.5 — July 29, 2026
We published an accuracy claim we had never measured, and it was
wrong. Both the landing page and this page said 9bench lands
"within 3-10% of native benchmarks for CPU and GPU compute." No native
benchmark had ever been run alongside 9bench to check it — it was an
estimate that hardened into a fact through repetition. On the natural
reading it is wrong by roughly an order of magnitude: our GPU test reaches
about 3% of a card's theoretical FP32 peak, and the CPU test is a similar
fraction of native SHA-256 throughput. Both claims are retracted and
replaced by Section 6, which shows the actual comparison including the
result that contradicts us.
On the two machines we finally tested, our CPU ranking came out
backwards. Two laptops, same chassis, same session, both on mains
power. Geekbench 7 ranked the i7-12700H machine ahead on both CPU
single-core (+4%) and multi-core (+12%). 9bench ranked the i7-12650H
machine ahead on both (+21% and +41%). The GPU ranking agreed with
Geekbench; both CPU rankings inverted. n = 2 machines, one run
each — enough to falsify our claim, not enough to calibrate a
replacement, and we are not going to present it as more than that.
The multi-core estimator measures the slowest thread, not
aggregate throughput. Every worker gets an identical iteration
count and we wait for all of them, then divide total hashes by wall clock.
When the per-worker cap binds — as it did on both machines above — that is
algebraically identical to thread count × the rate of the single
slowest worker. Every fast core's contribution is discarded. Each
worker already reports its own elapsed time and we have been throwing that
value away. This is a defect, it is ours, and Section 6 documents it in
full.
The GPU headline number takes a maximum where the code comment
says median. The result resolves to the larger of the measurement
and the best warm-up sample, across up to 15 draws. A maximum over many
noisy samples is biased upward, and the bias grows with variance — a
machine with background load can score higher than a quiet machine with
identical real throughput. Flagged here because it is live.
What did not change: any stored score. Nothing in this
release alters a number. Everything above is disclosure. The fixes, and
what they mean for the roughly 2,900 stored results, come in v3.6 —
announced before they ship, not after.
v3.4 — July 29, 2026
Slow devices were being given a near-zero score and told their
CPU was the bottleneck. The multi-core test ran a fixed 500,000
hash iterations per worker with a 60-second timeout. Any device slow
enough that a worker couldn't finish in time got no multi-core result at
all — and the missing measurement then entered the score as the number 0.
On an Android phone this produced an overall score of 5 and a callout
reading "Bottleneck identified: CPU·M — scored 18.0× lower than your
strongest component." That phone's multi-core performance was never
measured. We reported a failure of our own benchmark as a fact about
someone's hardware, which is the exact thing this site exists not to do.
60 stored runs were affected, including several fast desktops where the
workers failed for a different reason.
The workload now sizes itself to the device. Both CPU
phases calibrate against a short probe and pick an iteration count that
fits a fixed time budget, capped at the old constants so nothing ever
runs more work than before. The score is a throughput rate, so measuring
the same rate over a shorter window gives the same number — but it means
a low-end phone finishes the CPU section in about 12 seconds instead of
never. The formula and the way the rate is computed are unchanged.
No unmeasured component is ever a zero, anywhere. v3.3
fixed this for the GPU. It was still live for CPU single-core, CPU
multi-core, memory bandwidth and memory latency — including a latency
probe that could fail on its own and print "0.0 ns" as if it were
measured. Every component now carries an explicit measured state, shows
as "—" when absent, and is excluded from the bottleneck comparison.
The score formula is now stated once, in general form
(Section 3): the weighted geometric mean over the components that were
actually measured, renormalized over that set. Runs below 65% weight
coverage are shown but not scored, not submitted and not ranked. Runs
affected by the old bug are identified by a multi-core hash rate of
exactly zero — which no real measurement can produce — and are excluded
from every ranking. Their permalinks still work and now show what did
and didn't measure.
The in-browser LLM test works on phones again. It was
picking a model from a memory probe that measures JavaScript heap
allocation, which on Android can appear to succeed at 2 GB while
WebAssembly cannot get a few hundred megabytes — the failure users saw
as "Can't create a session". Mobile now always uses the smallest model,
desktop retries one step down on a memory failure, and the error message
explains the cause instead of quoting the runtime.
Known limitation, stated plainly. At the 64-byte payload
this benchmark uses, most of the measured time is
crypto.subtle dispatch overhead rather than SHA-256 itself.
That makes the CPU numbers a valid, reproducible measure of the
browser's crypto call path — which is what in-browser workloads actually
hit — but they are not native CPU throughput, and the single-core to
multi-core ratio should not be read as a hardware fact. Changing the
payload would rebase every score ever recorded here, so we document it
rather than quietly alter it.
v3.3 — July 28, 2026
Browsers without WebGPU can finally run the benchmark.
The test page refused to start at all unless WebGPU was present — so
every Android phone whose GPU vendor isn't on the browser's allowlist,
and every Linux Firefox user, hit a dead end reading "your browser does
not support WebGPU" even though the CPU and RAM tests would have run
perfectly. They now run, and produce a CPU + RAM score under the
renormalized formula in Section 3.
An unmeasured GPU is never reported as a zero. It shows
as "not measured" on the test page, the result page and the share card.
Previously a CPU+RAM-only result would also have been told
"Bottleneck identified: GPU" — about hardware we never touched — because
the missing measurement always sorted lowest. Fixed.
Local-AI predictions are suppressed rather than guessed
when there is no GPU measurement. They are all derived from GPU GFLOPS;
extrapolating them from a zero produced confident-looking claims like
"Llama 7B → yes, ~5-15 tokens/s". The live in-browser LLM test still
runs on those devices — it uses WebAssembly, not WebGPU — and remains a
real measurement.
Runs whose tab was backgrounded are no longer submitted.
Browsers throttle timers in hidden tabs, which makes the timing
meaningless. You still see your result; it just stays out of the
shared pool.
v3.1 — April 30, 2026
Calibrated scoring formulas for browser-API ceilings (GFLOPS × 3, SHA/s ÷ 300 and ÷ 600, bandwidth × 60).
10. The v4.0 gate — pass criteria, published before the data exists
The v4.0 CPU workload (two WebAssembly kernels: a SHA-256 integer chain and
a Mandelbrot f64 loop, both self-verifying against tables computed at build
time by independent implementations) replaces the current workload
only if all of the following hold. These criteria were
published before shadow collection began and do not move after we see the
numbers.
G1 — Paired references. At least 20 devices, each with
a 9bench v4 solo run (/test/?v4solo=1) and a public Geekbench
6 or 7 result link, spanning at least 4 CPU families with at least 3
devices each: Intel hybrid, AMD, Apple Silicon, ARM Android. GB6 and GB7
analysed as separate strata. (Why 20: the Spearman confidence interval has
Fisher-z SE ≈ 1.03/√(n−3) — below n≈20 no correlation claim survives its
own confidence interval.)
G2 — Rank correlation. Spearman ρ ≥ 0.90 observed AND
95% lower bound ≥ 0.75, for multi-vs-GB-multi and single-vs-GB-single.
The CI clause exists so the threshold cannot be gamed by keeping n small.
G3 — Close-pair inversions. Among device pairs whose GB
multi ratio is in [1.10, 1.50] — the zone where the current workload
failed — at least 8 pairs required, inversion rate ≤ 10%, zero inversions
at ratio ≥ 1.50. Named regression pair: i7-12650H vs i7-12700H
(GB multi ratio 1.12) must rank correctly. (Why ≤ 10% and not 0:
under the G5 noise bound, an unbiased measure still inverts ~9% of pairs
at the bottom edge of that zone — demanding 0 would reject a correct
workload on noise.)
G4 — Scaling. On devices with ≥ 8 threads:
(9bench multi/single) ÷ (GB multi/single) per device within [0.5, 1.5],
median within [0.7, 1.3]. Falsifiable prediction, recorded now: the
validation laptop pair's multi ratio moves from 1.14× to 0.85–0.95×
(Geekbench: 0.89×), and 16/20-thread scaling from 3.21×/2.47× to
≥ 4.5×/≥ 5.0×. If not, the shared-dispatch diagnosis was incomplete and
we investigate before shipping anything.
G5 — Repeatability. Five back-to-back solo runs on at
least 3 machines including one phone: CV ≤ 3% single / ≤ 5% multi on
mains power, ≤ 10% on the phone.
G6 — Cross-browser spread. Same machine, Chrome +
Firefox (+ Safari where possible), at least 3 machines: max/min ≤ 1.25
multi, ≤ 1.20 single, per kernel. Per-browser medians get published
regardless of outcome. Above ~25% the number is a browser property again
— the current workload's disease.
G7 — Self-verification. Fleet verify-failure rate
below 0.5% on the final candidate revision; failed rows excluded from
analysis but counted and published.
G8 — Volume. At least 1,000 shadow rows on the final
candidate revision, including at least 200 Android and 100 Apple.
Failure branch, precommitted: any gate fails → the
candidate is revised (revision counter bumps, the gate clock restarts for
affected metrics) or v4.0 is dropped; the failure is published here with
the numbers. Shadow collection hard-stops after 12 weeks
regardless — a permanent 12-second parasite on your time would be its own
honesty failure.
Anti-cherry-picking rule: every submitted pair meeting the
hygiene criteria enters the analysis, and the raw pair table ships as CSV
in the repository before the verdict. We cannot drop the pairs that make us
look bad.
We need your paired references. If you can run 9bench and
Geekbench 6/7 on the same machine: run
/test/?v4solo=1 (cold machine,
mains power, nothing heavy running), then file a
validation-pair
issue with the JSON block the solo run prints, your public Geekbench
result link and version, your /r/ link from a normal run
(same-machine provenance), the browser used, and the OS power mode.
Most wanted right now: Apple Silicon (any M-series),
Snapdragon/Dimensity Android, AMD Zen 4/5, and older 4-core machines.