e01ddf1MATLAB-syntax scripts on WebGPU: fused kernels, tic/toc timing, CPU comparisonJeremy Magland 1# math-webgpu-sandbox
3Write a MATLAB script, run it on your GPU. The sandbox compiles MATLAB-syntax
4scripts to fused WebGPU compute kernels, times them with the script's own
5`tic`/`toc`, and — because everything you write is plain MATLAB — you can
6paste the same script into real MATLAB and compare the numbers. An optional
7in-browser CPU run through [numbl](https://numbl.org)'s normal engine gives a
8third column without leaving the page.
10**Live page:** https://concept-collection.github.io/math-webgpu-sandbox/
12```matlab
02f2376Scale examples to 1-4 s of GPU work so tic/toc comparisons are meaningfulJeremy Magland 13n = 8000000;
e01ddf1MATLAB-syntax scripts on WebGPU: fused kernels, tic/toc timing, CPU comparisonJeremy Magland 14x = rand(n, 1);
15y = zeros(n, 1);
16tic;
02f2376Scale examples to 1-4 s of GPU work so tic/toc comparisons are meaningfulJeremy Magland 17for k = 1:600
e01ddf1MATLAB-syntax scripts on WebGPU: fused kernels, tic/toc timing, CPU comparisonJeremy Magland 18 y = y + 0.1*sin(x + k) .* exp(-x) + x.^2;
19end
20toc
21fprintf('checksum %.4f\n', mean(y));
22```
02f2376Scale examples to 1-4 s of GPU work so tic/toc comparisons are meaningfulJeremy Magland 24That loop body is **one** GPU kernel, compiled once and replayed 600 times —
25about five billion element-updates, a few seconds on an integrated GPU.
e01ddf1MATLAB-syntax scripts on WebGPU: fused kernels, tic/toc timing, CPU comparisonJeremy Magland 26
27## How it works
29The compiler front end is numbl's own: the script is parsed and lowered by
30numbl's JIT pipeline (reached through a `numbl-src` vite alias, the same
31arrangement [turing-surface](https://github.com/concept-collection/turing-surface)
32uses), which fixes every type and shape at compile time — `n = 2048;
33A = rand(n);` pins static shapes via exact-value propagation. The back end is
34this repo's: a planner maps the typed IR onto WebGPU, and an executor replays
35the resulting op sequence.
37- **Fusion.** numbl's inline pass folds single-use temps back into their
38 consumer, and a sandbox-side pass (`src/mgpu/fuse.ts`) folds the rest —
39 transcendentals, comparisons, logicals — which numbl's C backend declines
40 but WGSL handles. One source line becomes one kernel. Generators fuse too:
41 `x = 2*rand(n,1) - 1` is a single kernel that hashes its way to uniform
42 variates per element, touching no other buffer.
43- **Reductions** (`sum`/`mean`/`prod`/`max`/`min`/`norm`/`dot`) take a fused
44 *loader*, so `sum(a.*b + c)` reads its operands exactly once. Vectors (and
45 `X(:)`) reduce fully in two passes; matrices reduce per column, which in
46 MATLAB's column-major layout is the contiguous direction.
47- **`A * B`** is a 16×16-tiled shared-memory GEMM (column-major, adapted from
48 [matmul-bench](https://github.com/concept-collection/matmul-bench));
49 matrix transpose is a tiled relayout; vector transpose, `X(:)` and
50 `reshape` are views of the same buffer (free) when the source is never
51 reassigned.
52- **`for` loops replay, they don't unroll.** The body is planned once; the
53 loop variable lives in a dynamic-offset uniform with one slot per
54 iteration, and all iterations are encoded into one submit. `rand` inside a
55 loop mixes the iteration counter into its stream, so every pass draws
56 fresh values.
57- **`tic`/`toc` are synchronization points**: pending GPU work is submitted
58 and awaited, then wall-clock time is taken on the host. That is the same
59 thing MATLAB's synchronous tic/toc measures, which is what makes the
60 number comparable when you paste the script there.
62## What is (and isn't) supported
64Supported: elementwise math on real arrays (including comparisons, `&`/`|`/`~`,
65two-arg `max`/`min`, `mod`/`rem`, `.^`), `zeros`/`ones`/`eye`/`rand`/`randn`/
66`linspace`/ranges, `A*B`, transpose, the reductions above, `A(:)`/`reshape`,
67counted `for` loops, `tic`/`toc`, `disp`/`fprintf`, semicolon-suppressed or
68echoed assignments, and literal row/matrix constants.
70Deliberately rejected, with a source-located error rather than a wrong
71answer: indexing/slicing beyond `(:)`, `if`/`while`, complex numbers,
72user-defined functions, variables that change size, and printing inside `for`
73loops (the body has nowhere to run host I/O — it replays on the GPU).
75## The comparison is honest, with two caveats
77- The GPU computes in **f32**; WebGPU has no f64. MATLAB defaults to double.
78 Timing comparisons are still meaningful; for the closest apples-to-apples,
79 use `single` arrays in MATLAB. Values agree to single precision, and the
80 scripts print checksums so you can see that they do.
81- `rand`/`randn` here are deterministic counter-based generators
82 (PCG-flavored hash of element index and call site). Statistics match
83 MATLAB's; individual draws do not, so checksums of random data are close
84 but not equal.
86## Development
88```
89npm install # numbl must be checked out as a sibling: ../../numbl
90npm run dev
91npm run test:node # correctness suite on desktop WebGPU (Dawn)
92npm run test:gpu # same suite in headless Chrome (SwiftShader fallback)
93```
95The CPU-comparison button needs numbl's `dist-browser` build
96(`npm run build:browser` in the numbl checkout); the GPU path runs from
97numbl's TypeScript sources directly and needs no numbl build.
99## License
101Apache-2.0