Skip to content

SegmentedReduce with pluggable schedules (thread, workgroup, merge-path) and SpMV - #108

Open
jowens wants to merge 10 commits into
mainfrom
segmented-reduce
Open

jowens wants to merge 10 commits into
mainfrom
segmented-reduce

Conversation

@jowens

@jowens jowens commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

A test of the load-balancing programming model of Osama, Porumbescu and Owens (PPoPP 2023) on WebGPU: a load-balanced reduction of each tile (segment) of a CSR-style array, with the schedule separate from the work.

  • SegmentedReduce (src/segmentedreduce.mjs) holds the work, which the caller gives as JS functions that return WGSL: atom: (atom, tile) => expression, emit: (tile, value) => statement, and optionally tileStart: (t) => expression in place of an offsets buffer. These are Gridwise's stand-in for the paper's C++ lambdas.
  • Schedules (src/schedules.mjs) are classes that write the kernels and call the work through binop, tileStartOf, atomValue and emitTile. SegmentedReduce knows no schedule by more than its name, and a test defines one outside the library.
    • ThreadMapped: one invocation per tile.
    • WorkgroupMapped: one workgroup per tile (the paper's block-mapped).
    • MergePath (Merrill and Garland): tiles + atoms path steps split evenly. Split tiles are combined by a segmented scan within the workgroup, and between workgroups by a fixup that is itself a SegmentedReduce: its atoms are the workgroups' carries, its tiles are the workgroups. It runs merge-path, recursively until one workgroup suffices, so a tile spanning any number of workgroups costs nothing extra. Every tile is emitted once, with its whole value, with no float atomics.
  • SpMV (src/spmv.mjs): y = Ax (CSR, f32) as a SegmentedReduce.
  • Tests (segred, 32): u32 add, i32 max and SpMV against the CPU; all three schedules and an outside one; 1, 7 and 16 items per invocation; three levels of fixup; kernels repeated in place (trials); uniform, power-law, skewed, one-tile and all-empty shapes; sizes changing between executes.
  • misc/benchmark_spmv.mjs: the schedules on uniform, R-MAT and dense-row matrices or Matrix Market files, checked against the CPU.
  • docs/segmented-reduce.md: the model, the schedules, writing a schedule, a worked example of the fixup, measurements on an M1 Max, a V100 and an A100 (merge-path at 2^22: 2× thread-mapped on uniform, 8–15× on R-MAT, 1,500–2,800× with one dense row, at the same GFLOP/s on all three).

https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ


Generated by Claude Code

A load-balanced reduction of each tile of a CSR-style array, after Osama,
Porumbescu and Owens (PPoPP 2023): the schedule is separate from the work,
which the caller writes as JS functions that return WGSL (atom, emit),
Gridwise's stand-in for the paper's C++ lambdas. Merge-path combines split
tiles with a segmented scan in the workgroup and a fixup kernel between
workgroups, so each tile is emitted once with its whole value (no float
atomics needed). SpMV is a SegmentedReduce. Tests (segred) and
misc/benchmark_spmv.mjs (uniform, R-MAT, dense-row and Matrix Market).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
…rries

Combining the workgroups' carries is itself a segmented reduce: atoms are
the carries, tiles are the workgroups (each holding the run of carries in
the tile it ends, found by a binary search), and emit adds the run into the
workgroup's head value. It runs merge-path too, recursively until one
workgroup suffices, and binds only its own carry buffers; a last kernel
emits the heads. Replaces the walk-back fixup, which took half the time on
a matrix with one row of 2^25 nonzeros. Adds tileStart (a function in place
of the offsets buffer) and wgsl options; itemsPerThread defaults to 4, the
fastest on an M1 Max, a V100 and an A100. Test: three levels of fixup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
The fixup's emit added the run's carries into the head value in place, so
a repeated kernel (benchmark()'s trials) added them again: wrong results on
the M1 Max benchmark. It now writes to a third slot; every kernel can be
repeated. Tests with kernels repeated (trials 2 and 3), which fail without
the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
…sults

The fixup levels' kernels were labeled like the main kernel, so the
benchmark's fixup time left them out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
Each schedule (ThreadMapped, WorkgroupMapped, MergePath, in
src/schedules.mjs) writes its kernels and says what runs; SegmentedReduce
holds only the work (binop, tileStart, atom, emit) and helpers schedules use
(kernel, allocate, child, run). Schedules call the work through
binop/IDENTITY/tileStartOf/atomValue/emitTile and the sr uniform. A schedule
is given as an instance or by a name in SegmentedReduce.schedules; adding one
changes nothing in SegmentedReduce, as a test with a schedule defined in the
test file shows. MergePath's fixup is a child SegmentedReduce. Adds
WorkgroupMapped (the paper's block-mapped) to the tests and the benchmark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
@jowens jowens changed the title SegmentedReduce (thread-mapped and merge-path) and SpMV SegmentedReduce with pluggable schedules (thread, workgroup, merge-path) and SpMV Nov 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants