/ concept-collection / matmul-bench
Sign in
concept-collection / matmul-bench
matmul-bench / CLAUDE.md
100 lines · 6.2 KBCodeBlameHistory
3Tips for future agents working in this repo.
5## Architecture
7```
8wasm/matmul.c naive + blocked/SIMD (f64x2) kernels, plus a pthreads
9 matmul_blocked_mt (rows split across threads, -DMATMUL_MT).
10 wasm/build-wasm.sh (emsdk) builds it twice:
11 - st ES module -> wasm/dist/matmul.{js,wasm}
12 - threaded -> public/matmul/matmul_mt.{js,wasm}
13blis/matmul_blis.c thin wrapper over libFLAME's dgemm_ (BLIS-backed); built
bfc4e78Build libFLAME/BLIS from source in CI instead of vendoring binariesJeremy Magland 14 st + mt (pthreads) by blis/build-blis-wasm.sh into
15 public/matmul/matmul_blis_{st,mt}.{js,wasm}. It links the
16 WASM .a libraries from a sibling concept-collection/
17 libflame2wasm checkout (LIBFLAME=..., default
18 ../../../libflame2wasm), which are built by that repo's
19 build-all-wasm.sh. NOT committed — CI builds them.
666db68matmul-bench: browser GEMM benchmark (JS, WebGPU, custom C WASM, libFLAME/BLIS WASM)Jeremy Magland 20native/bench_native.c same style dgemm benchmark, linked against OpenBLAS,
21 run standalone outside the browser
22src/methods/ one file per method, all implementing the MatmulMethod
23 interface in types.ts (id, precision, available(), run?())
24src/methods/benchWorker.ts + workerClient.ts
25 JS and both non-threaded custom WASM kernels run inside a
26 Vite module Web Worker — naive JS/WASM at n=2048 take ~10s
27 and would freeze the tab on the main thread. WebGPU runs on
28 the main thread (async). The worker regenerates inputs from
29 (n, seed) rather than receiving them over postMessage.
30src/methods/threadedClient.ts + public/matmul/worker.js
31 ALL pthread-capable modules (threaded custom C, BLIS st +
32 mt) run in ONE shared classic worker (not a Vite module
33 worker) — threaded builds spawn pthread workers from it
34 (nested workers), and importScripts of the emscripten glue
35 is the proven path (mirrors libflame2wasm's bench_worker.js).
36 worker.js has a kind->module REGISTRY; every module exports
37 a (a,b,c,n,nthreads) function (st builds ignore nthreads).
38 Served verbatim from public/, out of Vite's module graph.
39src/components/ BenchmarkRunner (orchestrates runs, thread selector) +
40 ResultsTable
41src/data/nativeReference.ts hardcoded numbers from native/bench_native,
42 hand-copied in — no runtime fetch
43public/coi-serviceworker.js vendored (gzuidhof, MIT) — adds COOP/COEP so
44 SharedArrayBuffer works on GitHub Pages (no header control)
45```
47## Key gotchas
49- **WASM .wasm URL resolution.** `wasm/dist/matmul.js` (emscripten glue) is
50 imported directly; its sibling `matmul.wasm` is resolved via Vite's `?url`
51 import and passed through `locateFile` — same pattern qhull-wasm-demo uses
52 for `qhull-wasm/dist/qhull.wasm`. Don't rely on emscripten's default
53 `import.meta.url`-relative lookup working under Vite's bundler.
54- **Two threaded builds, one classic worker.** The threaded custom kernel
55 (`matmul_mt`, from `wasm/matmul.c` with `-pthread -DMATMUL_MT`) and the
56 threaded BLIS build (`matmul_blis_mt`) both use pthreads/SharedArrayBuffer
57 and both dispatch through `public/matmul/worker.js`. The non-threaded custom
58 kernels (`matmul.c` st build) have no `-pthread` and run in the Vite module
59 worker instead.
60- **Cross-origin isolation for threaded methods.** `matmul_mt` and
61 `matmul_blis_mt` need `crossOriginIsolated === true` (COOP: same-origin,
62 COEP: require-corp). Vite dev/preview set these headers (see vite.config.ts).
63 GitHub Pages can't set headers, so `public/coi-serviceworker.js` installs
64 them via a service worker (page reloads once on first visit to gain control).
65 Their `available()` gates on `crossOriginIsolated`; without it the cell = n/a.
bfc4e78Build libFLAME/BLIS from source in CI instead of vendoring binariesJeremy Magland 66- **Everything under public/matmul/ except worker.js is CI-built** (gitignored).
67 The threaded custom module (`matmul_mt.*`) needs only `matmul.c` + emsdk. The
68 BLIS modules (`matmul_blis_*.*`) need the WASM libflame/BLIS `.a` files, which
69 CI produces by cloning the sibling **concept-collection/libflame2wasm** repo
70 and running its `build-all-wasm.sh` (result cached, keyed on that repo's SHA —
71 see `.github/workflows/deploy.yml`). `worker.js` is the only committed file
72 there. Locally: check out libflame2wasm beside this repo, run its
73 `build-all-wasm.sh` once, then `blis/build-blis-wasm.sh`.
666db68matmul-bench: browser GEMM benchmark (JS, WebGPU, custom C WASM, libFLAME/BLIS WASM)Jeremy Magland 74- **Row-major via operand swap.** dgemm_ is column-major; `matmul_blis` calls
75 it with operands swapped — `dgemm_(B, A)` computes row-major C = A*B in the
76 same flat buffer — so BLIS `c[0]` matches the other (row-major) methods.
77- **Deterministic seeded inputs, not transferred arrays.** Every method for
78 a given `n` multiplies bit-identical A/B (from `generateMatrix(n, seed)`);
79 the worker regenerates them from the same seed rather than receiving the
80 arrays over `postMessage`, since structured-cloning multi-megabyte
81 `Float64Array`s per run would be slower than regenerating.
82- Rebuilding `wasm/dist/*` requires emsdk on PATH or at `~/emsdk`
83 (`wasm/build-wasm.sh` sources `~/emsdk/emsdk_env.sh` if `emcc` isn't found).
84 CI installs it via `mymindstorm/setup-emsdk`.
86## Testing
88- `npm run dev`, click through each method's run button at a small size
89 (n=128) first and check the cross-check panel — the f64 methods (JS, the
90 three WASM kernels, both BLIS builds) should agree to ~1e-9, WebGPU (f32)
91 should be close but not identical. The "cross-origin isolated ✓" chip must
92 be green for the threaded methods to be available.
93- `wasm/build-wasm.sh && npm run build` to verify the CI path locally
94 (build-wasm.sh emits both the st module and the threaded matmul_mt).
bfc4e78Build libFLAME/BLIS from source in CI instead of vendoring binariesJeremy Magland 95- `blis/build-blis-wasm.sh` rebuilds the BLIS modules (needs emsdk + a sibling
96 libflame2wasm checkout already built via its `build-all-wasm.sh`; override the
97 location with `LIBFLAME=/path/to/libflame2wasm`).
666db68matmul-bench: browser GEMM benchmark (JS, WebGPU, custom C WASM, libFLAME/BLIS WASM)Jeremy Magland 98- `native/build.sh && native/bench_native` regenerates the values pasted into
99 `src/data/nativeReference.ts` (no automated round-trip — hand-copy after
100 running).
moveopenescclose