<!-- Canonical: https://lucioduran.com/projects/webgpu-local-inference
Language: en
Content-SHA256: d22af162d7906def6cd4e5780cb3e3aca005368784e5b8b65da4cc99895271e7
-->

[Projects](<https://lucioduran.com/projects>) / WebGPU Local Inference

# WebGPU Local Inference

Inspectable browser ticket classification with native WebGPU, real WebAssembly fallback and recorded cached startup during a local asset outage.

Author: [Lucio Durán](<https://lucioduran.com/>) · Node 24.8.0, Chromium 157.0.0.0, WebGPU and WebAssembly · License: [MIT](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/LICENSE>)

Page published: 2026-11-10 · Page updated: 2026-11-10

[Repository](<https://github.com/lucioduran/webgpu-local-inference>) · [Raw browser execution report](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-1794256500335.json>) · [Release v0.1.0](<https://github.com/lucioduran/webgpu-local-inference/releases/tag/v0.1.0>)

## Problem and relevance to agents

Browser inference examples can conceal CPU fallback, omit reproducible inputs or confuse allocated buffers with measured memory. Offline claims also need a clearly defined failure condition.

A small shared model and explicit backend choices make numerical behavior, fallback reasons, cancellation and cache dependence observable on the actual browser.

## Architecture

1. **Share one transparent model.** A multinomial naive Bayes classifier trained on 12 original synthetic tickets emits float32 weights for billing, access and delivery. Inputs with no vocabulary match abstain.
2. **Execute real browser backends.** Native WGSL dot products and a scalar WASM kernel use the same weights. Automatic mode reports initialization fallback; strict WebGPU mode fails visibly instead of switching to CPU.
3. **Cache explicitly and preserve evidence.** A service worker stores an explicit local asset list. The foreground harness records online execution, denies static assets, reloads the document from cache and captures a second backend comparison.

- [Backend and cache design](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/docs/design.md>)
- [Frozen browser protocol](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/docs/protocol.md>)
- [WebAssembly text kernel](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/src/kernel.wat>)

## What this demonstrates

- Actual browser WebGPU and WASM inference, with 12 evaluation tickets repeated three times per backend in both online and cached-reload phases.
- All 144 evaluation predictions matched expected labels and exact float32 reference scores, with maximum absolute error zero on this fixed task.
- Tests of abstention, malformed inputs, cancellation, strict backend errors, deliberate device destruction and injected adapter unavailability, plus offline artifact replay and package checks.

## Published results

One physical Mac Pro was tested: an eight-core Intel Xeon W, 48 GB RAM, AMD Radeon Pro W5700X with 16 GB VRAM and macOS 26.7.1. In-app Chromium 157.0.0.0 exposed an AMD RDNA 1 adapter; device and description fields were empty.

Each timing row covers three fixed rounds of 12 tickets, with no warm-up exclusion. Initialization is recorded separately. The median averages the two central samples, and p95 uses nearest rank. Training and evaluation texts are owned synthetic fixtures with distinct partitions and intentional vocabulary overlap.

### Recorded browser latency

| Phase and backend | Initialization, ms | First inference, ms | Median inference, ms | p95 inference, ms |
| --- | --- | --- | --- | --- |
| Online WebGPU | 49.2 | 7.3 | 0.4 | 1.5 |
| Online WASM | 0.6 | 0.2 | Below timer resolution | 0.1 |
| Cached reload WebGPU | 6.0 | 0.7 | 0.4 | 0.7 |
| Cached reload WASM | 0.3 | Below timer resolution | Below timer resolution | 0.1 |

### Downloads and logical allocations

| Observation | Recorded value |
| --- | --- |
| Model response body | 3,951 bytes / 3.5 ms |
| WASM response body | 158 bytes / 3.1 ms |
| Explicit GPU buffer allocation | 712 bytes |
| WASM linear memory allocation | 65,536 bytes |
| Maximum float32 reference error | 0 |

In each WASM phase, 34 of 36 durations were reported as zero by the browser timer. They are below its observable resolution, not instantaneous computation. These tiny dot products do not justify a backend speedup ratio or performance claims for larger models.

The GPU figure is an explicit buffer allocation and the WASM figure is linear memory size. Neither is a measurement of total process memory, GPU memory consumption or peak memory.

Model and WASM response-body sizes and download times came from loopback. They are not Internet transfer measurements. Runtime JavaScript resource timings are retained in the raw report. [Raw browser execution report](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-1794256500335.json>).

During cached reload, the server rejected eight static asset requests, including the model and WASM. The local report endpoint stayed available. This tests an origin asset outage, not operating-system network disconnection, browser restart or cache eviction.

Device destruction and unavailable-adapter behavior were explicitly injected. No spontaneous device loss was observed. Initial browser connection failures remain recorded; Safari and other devices were not tested. [Preserved initial connection failure](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-blocker.json>).

## Reproduce

Use the recorded Node 24.8.0 environment. The committed model and WASM binary make lightweight replay and tests independent of package installation, model downloads and external services. Rebuilding the WASM binary uses the pinned wabt 1.0.37 development dependency. Code, fixtures and trained model are MIT licensed.

The first command validates artifact hashes, executes the real WASM kernel and checks saved browser reports. The second runs the small Node regression suite, including local HTTP serving. Neither command repeats GPU measurements. Follow the README for a fresh browser run under the shared exclusive benchmark lock.

### Verify saved evidence

```
git clone https://github.com/lucioduran/webgpu-local-inference.git
cd webgpu-local-inference
git checkout 7279202a64b6a047ca5f2ad88efd0360e876b333
node scripts/replay.mjs
```

### Run regression tests

```
node --test
```

## Scope and limitations

- Only one physical device and one working browser environment were measured. There are no mobile, Safari, cross-browser or cross-device compatibility claims.
- ASCII vocabulary and bag-of-words features do not reliably handle negation, multilingual input, ambiguity or distribution shift. Scores are not calibrated probabilities or evidence of general language understanding.
- GPU predictions are sequential. Cancellation discards a pending result but cannot preempt submitted GPU work or a synchronous WASM call; a lost device requires engine recreation.
- Cached startup depends on an explicit prior cache action and retained browser storage. The simulated asset outage does not establish behavior after a browser restart or full network loss.
- The evidence does not measure total memory, electricity, energy, monetary operating cost or production support quality. Saved-report verification establishes consistency only.

- [Related WebGPU article](<https://lucioduran.com/blog/webgpu-compute-shaders-ml-inference>)

## Sources and revision

The measured browser workload used commit 615e4051094b1b9ec865d920503a1516947cae71. The locked harness began at 2026-11-09T20:34:26.446Z; browser work ran from 2026-11-09T20:35:00.011Z to 2026-11-09T20:35:00.330Z, and the report arrived at 20:35:00.334Z. The release retains the same model, fixtures, kernels and browser workload. Later changes cover reports, replay, packaging and Git metadata. Source and artifact manifests bind those files; consistency does not establish independent measurement authenticity.

- Reviewed revision: [` 7279202a64b6a047ca5f2ad88efd0360e876b333 `](<https://github.com/lucioduran/webgpu-local-inference/commit/7279202a64b6a047ca5f2ad88efd0360e876b333>)
- Revision recorded by the experiment: [` 615e4051094b1b9ec865d920503a1516947cae71 `](<https://github.com/lucioduran/webgpu-local-inference/commit/615e4051094b1b9ec865d920503a1516947cae71>)
- Report timestamp (UTC): 2026-11-09T20:35:00.011Z
- Release published: [v0.1.0](<https://github.com/lucioduran/webgpu-local-inference/releases/tag/v0.1.0>) · 2026-11-09T20:39:55Z
- [Raw browser execution report](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-1794256500335.json>) SHA-256: ` 47edab939f5456ab523926069cf0e2ae462df7015f8ad9516bb299a945bd40cd `
- [Derived browser timing summary](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/summary.json>) SHA-256: ` 2427850504700de4d00e6d53f73649fc1c4fa88d52ddeae7b5369bc46a2390ec `
- [Committed classifier model](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/public/model.json>) SHA-256: ` 4c92113f9dc216f16da3052b22520e7296058727be907f9246bc67eca364960b `
- [Training and evaluation fixtures](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/data/fixtures.json>) SHA-256: ` e1575dfcf3fcaacb6fdf853ae9382cbea8fcc990f0a727822a4d198eaf03adbd `

- [README and browser reproduction](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/README.md>)
- [Raw browser execution report](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-1794256500335.json>)
- [Derived browser timing summary](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/summary.json>)
- [Physical hardware inventory](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/hardware.json>)
- [Run timing and source provenance](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/run-provenance.md>)
- [MIT license](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/LICENSE>)
- [Backend and cache design](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/docs/design.md>)
- [Frozen browser protocol](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/docs/protocol.md>)
- [Training and evaluation fixtures](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/data/fixtures.json>)
- [Committed classifier model](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/public/model.json>)
- [Model and kernel hashes](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/public/manifest.json>)
- [Measured browser source hashes](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-source-manifest.json>)
- [WebAssembly text kernel](<https://github.com/lucioduran/webgpu-local-inference/blob/7279202a64b6a047ca5f2ad88efd0360e876b333/src/kernel.wat>)
- [Recorded Node regression results](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/node-tests.txt>)
- [Preserved initial connection failure](<https://raw.githubusercontent.com/lucioduran/webgpu-local-inference/7279202a64b6a047ca5f2ad88efd0360e876b333/evidence/browser-blocker.json>)
