Skip to content

Browser client: WebGPU backend with a CPU/GPU selector - #105

Open
wasabipesto-bot wants to merge 2 commits into
wasabipesto:mainfrom
wasabipesto-bot:web-runner
Open

Browser client: WebGPU backend with a CPU/GPU selector#105
wasabipesto-bot wants to merge 2 commits into
wasabipesto:mainfrom
wasabipesto-bot:web-runner

Conversation

@wasabipesto-bot

Copy link
Copy Markdown
Contributor

Runs the browser client on the GPU through WebGPU, with a backend selector on the page, and adds an end-to-end test that exercises the whole browser stack in CI-shaped environments with no GPU hardware.

Stacked on #99 (merged); the core prep landed there, so this PR touches the backend only twice: one visibility change and one module registration.

The kernel

WebGPU has no shader-int64, so every existing kernel is rejected by a browser's wgpu device. common/src/cubecl_web.rs is the detailed algorithm with no u64 anywhere:

  • multi-precision values as 16-bit halves in u32 words; the schoolbook accumulator provably fits u32 ((2^16-1)^2 + 2*(2^16-1) < 2^32, pinned by a unit test)
  • digit masks as u32 quads instead of u64 pairs
  • the split16 chunk scan, which is what the wgpu targets already used

Structure, comments and naming track the native kernel line for line, so the two review side by side. Batches are 4M candidates, each flushed as its own submission — the same watchdog rationale as #99's ffe33a6, and doubly so in a browser tab.

Correctness

Exact CPU parity on lavapipe, which drives the identical naga→WGSL path a browser uses:

base candidates limbs / masks
10 1 000 000 1 limb, one mask
40 2 000 000 2 limbs
62 200 000 2 limbs
80 100 000 above 64: two-mask path
97 50 000 4 limbs, four-mask path

Base 97 is the widest supported base and no other device test in the tree exercises it.

The page

The existing page keeps its structure — same histogram, same rate/statistics, same claim→process→submit flow. What is new:

  • a Compute Backend dropdown. CPU keeps the worker pool; a GPU entry appears only when a WebGPU adapter initializes, labelled with the adapter's name. Browsers without WebGPU see the CPU option and a one-line explanation.
  • the GPU path runs in one dedicated worker — the device supplies the parallelism — slicing each claim ~32 ways so progress ticks and the stop button stay responsive.
  • results merge through the same server-format path as the CPU workers, so submission, histogram and stats code is shared.

Also fixes a real pre-existing bug: the CPU worker read chunkResult.distribution_updates, a field FieldResults has not serialized for some time. The checked-in page only worked against a stale pkg/ build; anyone rebuilding it got an empty histogram.

Testing it end to end

wasm-client/e2e/ (or just wasm-e2e) serves web/search, drives the real page in the Playwright container's headless Chromium, runs the offline benchmark on both backends, and asserts their histograms agree exactly:

=== CPU backend ===  Benchmark processing completed!
=== GPU backend ===  Benchmark processing completed!
CPU and GPU histograms agree exactly

The GPU arm is real: Chromium's SwiftShader supplies a software WebGPU adapter, so wasm → wasm-bindgen → CubeCL → WGSL → WebGPU all execute — no GPU, no host browser, nothing but docker. Between this and the lavapipe suite, three independent implementations agree on the same field: native CPU, browser CPU wasm, and browser WebGPU.

Known limitations

  • Real-GPU browser throughput is unmeasured. SwiftShader proves correctness, not speed. Worth a manual pass on real hardware before advertising the feature.
  • Detailed mode only. Niceonly's host pipeline is a std::thread::scope MSD filter, which is not browser-shaped; the mode selector is unaffected because the browser client only ever claims detailed fields.
  • pkg/ remains a build artifact (just wasm-build), unchanged from before.

Claude and others added 2 commits August 19, 2026 23:01
WebGPU has no shader-int64, so a browser's wgpu device rejects every
existing kernel. This one is the same detailed algorithm with no u64
anywhere: multi-precision values as 16-bit halves in u32 words
(schoolbook accumulation provably fits u32), digit masks as u32 quads,
and the split16 scan the wgpu targets already use. Batches are 4M
candidates and flushed per submission — sized for slow integrated GPUs
behind browser watchdogs.

The host path is async-first like the native backend, over the same
context, drain helper, and conservation check. Parity is anchored the
same way too: exact CPU agreement on lavapipe — which drives the
identical naga/WGSL path a browser uses — at bases 10/40/62/80 and 97,
the widest supported base (4 limbs, four-mask digit set), which no
other device test exercises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… end

The page gains a Compute Backend dropdown: CPU keeps the existing
worker pool; GPU appears when a WebGPU adapter initializes, named after
the adapter, and runs whole fields in one dedicated worker (the device
supplies the parallelism) through the u32 kernel via
process_chunk_gpu/gpu_init wasm bindings. Claims are sliced ~32 ways
for progress ticks and stop-responsiveness; results merge through the
same server-format path as the CPU workers.

Also fixes the CPU worker reading distribution_updates, a field name
FieldResults has not serialized for some time — the checked-in page
only worked against a stale pkg build.

wasm-client/e2e is the end-to-end harness: it serves web/search,
drives the real page in the playwright container's headless Chromium,
runs the offline benchmark on both backends, and asserts their
histograms agree exactly. SwiftShader supplies the WebGPU adapter, so
the whole browser GPU stack (wasm -> CubeCL -> WGSL -> WebGPU) runs in
CI-shaped environments with no GPU hardware. serving /repo/web/search at http://127.0.0.1:39397/
=== CPU backend ===
Benchmark processing completed!
=== GPU backend ===
Benchmark processing completed!
CPU and GPU histograms agree exactly runs it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant