WebGPU FSS — the neighborhood score on the GPU

Fractions Skill Score computed by a WebGPU compute shader · checked against the CPU reference · MET-AL

The Fractions Skill Score is the one MET operator with no cheap closed form over partial sums — it needs a sliding window over the whole grid. That makes it the natural GPU target. This page runs FSS five ways — the trusted integral-image CPU reference and four WebGPU compute kernels (a naive per-cell one, an O(cells) separable one, a prefix-scan integral image, and a multi-block scan that lifts the single-workgroup line limit) — proves all agree, races them across grid size and neighborhood radius, then renders the fraction fields GPU-to-screen from the same integral image, with no readback. A pros/cons table weighs each level of the exploration.

1 · Compute device

WebGPU adapter in this browser (the shader runs here; if there's none, everything falls back to CPU).

checking…

2 · Parity — GPU vs the integral-image reference

Real AIGFS-vs-URMA case (8,051 cells). Same threshold and neighborhood fed to both paths; the scored-center count n must match exactly, the scores to floating-point tolerance.

side 5×5

3 · Benchmark — CPU integral image vs GPU shader

Warm median wall-clock across grid sizes, from the real case (8k cells) to 2048² (4.2 M cells), at the neighborhood set above. GPU time includes input upload + dispatch + readback (an honest end-to-end).

4 · The r-sweep — where algorithm beats brute force

Fixed 1024² grid, growing the neighborhood r. The naive kernel is O(cells·(2r+1)²) so it climbs steeply; the CPU integral image and the GPU separable kernel are O(cells) and stay flat. Watch the naive kernel fall behind even the CPU at large r while the separable kernel holds its lead.

5 · Beyond 2048 — the multi-block scan

The prefix-scan kernel above scans each grid line with a single workgroup, so a line can hold at most 256 threads × 8 = 2048 cells. The multi-block scan lifts that ceiling: each line is split into 2048-cell blocks scanned in parallel, a second pass scans the per-line block totals into offsets, and a third adds them back — the textbook parallel-prefix pattern. Below we run a grid wider than 2048, where the single-block kernel must bail out and only the multi-block kernel (and the CPU) can finish — both still bit-for-bit matching the reference.

6 · The neighborhood, made visible

Real case, at the threshold set in section 2. Drag the r slider here (mirrored with section 2) to resmooth and watch every panel — and the score — update live. Left of each pair: the raw binary exceedance (does this cell cross the threshold?). Right: the neighborhood fraction the FSS actually compares — each cell replaced by the share of exceedances within r cells. FSS rewards forecasts whose fraction fields agree even when the binary fields don't line up point-for-point; the last panel is the per-cell |Pf − Po| that the score integrates.

side 5×5 · FSS —

7 · GPU → screen — no readback

Same fraction fields as section 6, but now computed from the same integral image the prefix-scan kernel builds: a compute pass runs the multi-block SAT scan, a colorize pass box-queries it and writes turbo colors straight into a texture, then a render pass samples that texture onto the canvas. Nothing is read back to JavaScript — the whole scan → box-query → colormap → pixels path stays on the GPU. Move the r slider to recompute live.

8 · Trade-offs — pros, cons, benchmarks

Five levels of the same score, each a different point on the effort-vs-payoff curve. Toggle between what each buys you, what it costs you, and how it actually measures up.

MET-AL · WebGPU FSS prototype — four GPU kernels (naive · separable · prefix-scan · multi-block), checked against the integral-image CPU reference; the on-screen field shares the scan's integral image. De-identified real case. Verified live on this machine's GPU.