Skip to content

webasm: pipelined kernels always degrade to cpu — 17 of 30 real GPGPU workloads never reach WebAssembly #868

Description

@fuzzie360

Follows #866 (WebAssembly backend). Not a defect claim — that PR states plainly that anything the backend cannot take degrades to cpu. This is a measurement of what that costs on a real workload suite, because the fraction turned out much larger than I expected.

Measurement

I added a webasm column to the gpu.rocks benchmark suite — 30 GPGPU workloads, each cross-checked against a plain-JS reference before timing. Apple M1 Max, Chrome 150, branch build of #866 (11b4c0e).

13 of 30 ran on WebAssembly. 17 of 30 silently ran on cpu instead.

Every one of the 17 was rejected by the same guard, and none for any other reason:

// src/backend/web-assembly/kernel.js, build()
if (this.graphical)                              return this.requestFallback(arguments);
if (this.subKernels && this.subKernels.length)   return this.requestFallback(arguments);
if (this.pipeline)                               return this.requestFallback(arguments);

Minimal reproduction — the kernel body is irrelevant, only the setting matters:

new GPU({ mode: 'webasm' }).createKernel(function (a) { return a[this.thread.x] * 2; })
  .setOutput([8])(input);                        // mode 'webasm'

new GPU({ mode: 'webasm' }).createKernel(function (a) { return a[this.thread.x] * 2; })
  .setPipeline(true).setOutput([8])(input);      // mode 'cpu'

setImmutable(true), setPrecision('single'), 3D output, setDynamicOutput, setLoopMaxIterations, nested loops, while, Math.min/max, added functions, integer division and modulo all compile to WebAssembly fine. It is specifically pipeline (and graphical, and sub-kernels).

Why this bites harder than it looks

Pipelining is not an optimisation people sprinkle on — it is how any multi-pass GPGPU algorithm keeps its working set on the device between passes. In this suite the rejected set is essentially "everything iterative":

bitonic-sort, canny, erosion, fft, gray-scott, heat, ising, jacobi, launch-overhead, life, path-trace, reduction, residency, sobel, spectral-filter, topk, wavefront

An FFT is 14 dependent passes; Conway's Life is 96; a Jacobi solve is a stencil stepped hundreds of times. Those are exactly the shapes someone reaches for a compute backend to accelerate, and all of them get plain cpu.

There is an argument that pipelining is meaningless without device memory to pipeline into — wasm has no textures, and a Texture handle is not a thing the emitter could consume. But the observable behaviour is that a kernel written for GL, run under mode: 'webasm', produces correct results at cpu speed while reporting nothing. Two things would help even without supporting it:

  1. Make it visible. kernel.kernel.constructor.mode is the only way to discover the degradation. I only caught it because the benchmark asserts the backend it asked for is the backend that ran. A one-time console.warn naming the reason, or a queryable degradedReason, would stop this being silent.
  2. Consider accepting pipeline as a no-op. On this backend the output is already a plain typed array in linear memory; treating a pipelined kernel as an unpipelined one returning its array would let the 17 run, at the cost of the caller's .delete()/.toArray() expectations. That may well be the wrong trade — but silently halving the backend's reach is also a trade.

What the 13 that did run look like

Not the point of this issue, but for calibration — speed-up against plain JS, and against the cpu backend on the same kernel:

workload webasm vs plain JS vs cpu backend
monte-carlo 315 ms 6.95× 7.06×
ode-rk4 661 ms 4.06× 4.06×
gradient-descent 460 ms 3.98× 3.97×
sdf-march 385 ms 2.30× 2.60×
nbody 255 ms 2.25× 2.27×
matmul 857 ms 1.63× 3.62×
ncc-template 776 ms 1.33× 6.69×

Zero WRONG and zero errors across all 30 rows — where it runs, it is correct. Checksums matched the plain-JS reference within 1e-4 relative on every row.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions