Skip to content

Keep several loads in flight per thread in the columns scan kernel - #199

Open
maleadt wants to merge 1 commit into
tb/optional-op-selectfrom
tb/slice-columns-loads
Open

maleadt wants to merge 1 commit into
tb/optional-op-selectfrom
tb/slice-columns-loads

Conversation

@maleadt

@maleadt maleadt commented Oct 28, 2026

Copy link
Copy Markdown
Member

Stacked on #194.

A scan along dims whose slices are scanned one thread each (:columns) splits them into chunks when there are too few slices to fill the device, e.g. accumulate along dims=2 of an (8, 10^7) array. The split launches only target_blocks blocks, and each thread stepped through its chunk one element at a time. Its loads don't depend on each other, but with one in flight per thread the passes were bound by memory latency, not bandwidth.

On main, ptxas happened to unroll the totals pass 4x, so a few loads were in flight. That loop kept the running partial in stack slots, which #194 removes, and ptxas doesn't unroll #194's loop. That is the 1.11x of #194 on that scan (RTX 5080): its totals pass went from 1.22 ms to 1.62 ms.

This PR steps through split chunks eight elements at a time, loading them before combining and storing them (the scan may be in place), as the columns reduction kernel does. Unsplit slices keep the old loop: they launch enough threads, and the steps made them up to 1.5x slower on an RTX 6000 Ada. The (8, 10^7) max scan now takes 1.75 ms on the RTX 5080 (3.53 ms on main, 3.98 ms with #194 alone), and 1.66 ms instead of 4.73 ms on the RTX 6000 Ada. A test shape now splits slices into chunks that aren't a multiple of the step.

Time ratios against #194, accumulate of Float32 along dims=2, 10^6 / 10^7 / 10^8 elements (median of 3 runs, 5 for PoCL and oneAPI):

(8, n/8) max (8, n/8) + (64, n/64) max (64, n/64) + (n/8, 8), unsplit
CUDA (RTX 5080) 0.89 / 0.74 / 0.44 0.96 / 0.79 / 0.59 0.79 / 0.96 / 0.92 1.03 / 0.97 / 1.03 0.99–1.06
CUDA (RTX 6000 Ada) 0.94 / 0.90 / 0.35 0.91 / 0.81 / 0.50 0.88 / 0.96 / 0.99 0.97–1.08
AMDGPU (gfx1036) 0.51 / 0.68 / 0.73 0.39 / 0.71 / 0.69 0.65 / 0.51 / 0.53 0.35 / 0.52 / 0.54 0.98–1.01
oneAPI (A770) 1.02 / 0.98 / 1.01 1.01 / 0.99 / 1.00 1.01 / 1.00 / 1.03 0.99 / 1.00 / 1.00 1.00–1.01
Metal (M1) 0.98 / 0.98 / 0.73 0.99 / 0.97 / 0.58 0.94 / 0.91 / 0.93 1.00 / 1.08 / 0.97 1.00–1.08
PoCL 1.00 / 0.97 / 0.96 0.66 / 1.09 / 0.98 0.98 / 0.90 / 0.91 0.71 / 0.98 / 0.98 noise

The oneAPI ratios are of accumulate! into a preallocated array. With accumulate, allocating the result made the (8, n/8) max scan of 10^7 elements vary by up to 1.11x, while the per-pass kernel times were within 1%. PoCL and Metal (where other jobs ran) are noisy: the ratios above 1 lie within the spread of the runs, and unsplit slices run the same loop as before.

Tested accumulate on CUDA, AMDGPU, PoCL, oneAPI and Metal.

Separately, the tile scans (thread_scan!, tile_prefix!) access their tiles with a stride of items_per_thread elements, which gives about 11-way shared-memory bank conflicts on CUDA (95% of the shared-memory pipe in Nsight Compute), so padding or a register-based thread scan, as in CUB, could speed up every tile scan.

The one-thread-per-slice scan along `dims` steps through its chunk one
element at a time. When the slices are split into chunks, it launches
only `target_blocks` blocks, and with one load in flight per thread the
passes are bound by memory latency rather than bandwidth. On main, ptxas
happened to unroll the loop that kept the running partial in stack slots,
but not the select-based combine, which made the totals pass of an
(8, 10^7) `Float32` `max` scan along `dims=2` 1.34x slower on an RTX 5080.

Split slices are now stepped through eight elements at a time, loaded
before they are combined and stored (as the columns reduction kernel
does). That scan takes 1.82 ms instead of 3.57 ms on main and 3.98 ms
before this change (RTX 5080), and 1.67 ms instead of 4.74 ms on an
RTX 6000 Ada. Unsplit slices keep the old loop: they launch enough
threads, and the steps made them up to 1.5x slower on the Ada.

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.

1 participant