Skip to content

perf(nearfield): source-chunk grid axis for the leafpair kernel; batched self-P2P scan - #334

Merged
TobiBu merged 3 commits into
mainfrom
perf/nearfield-row-split-batched-self
Sep 7, 2026
Merged

perf(nearfield): source-chunk grid axis for the leafpair kernel; batched self-P2P scan#334
TobiBu merged 3 commits into
mainfrom
perf/nearfield-row-split-batched-self

Conversation

@TobiBu

@TobiBu TobiBu commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Why

Profiling one fused eval at N=200k, leaf 256, theta 1.0 (A100, 93 % device busy): 40 of 47 ms in nearfield_leafpair_t32_s1024_w256, 7.5 ms in the intra-leaf self term (782 x 5 launches of ~2 us; 15 ms at leaf 128 with the GPU 41 % idle). The kernel's wall time was bounded below by ONE single-warp program: the grid is (leaf, subtile) and each program loops its leaf's whole neighbour row; in a Plummer halo the longest row is num_leaves - 1, so one warp serially sums all N sources at ~100 ns per lane-step (19 ms at 200k, 38 at 400k, theta- and order-independent). Truncating rows 781 -> 150 slots removed 18 ms while dropping 21 % of entries.

What

  • nearfield_leafpair_pallas(source_chunk=): grid (leaf, subtile, chunk), each program sums <= chunk slots into a partial (kept in the wide dtype when the wide accumulator is on), partials reduced afterwards. JACCPOT_NEARFIELD_LEAFPAIR_SOURCE_CHUNK (default 64; 0 = the old single pass, bit-for-bit).
  • _self_contributions vmaps JACCPOT_NEARFIELD_SELF_BATCH (default 32; 1 = old) leaves per scan step.

Measured (shared card, so indicative; quiet numbers to follow)

theta 1.0: 46 -> 31 ms; theta 0.8: 62 -> 46 ms; theta 0.4: 124 -> 115 ms. aggL2 identical to 4 s.f. at theta 0.8/1.0; at theta 0.4 3.3065e-5 -> 3.2528e-5 (summation order, at the fp32 accumulation floor). tests/unit/operators/test_pallas_nearfield_fused.py and tests/unit/test_custom_vjp_parity.py green.

🤖 Generated with Claude Code


Update 2026-09-07. Merged main (which now carries #335's near-field axis contracts) and fixed the one CI failure: the batched self-P2P scan read JACCPOT_NEARFIELD_SELF_BATCH with a raw os.environ.get, which tests/unit/test_shared_env_switches.py rejects — the set of raw env readers outside runtime/ is closed by that test. It now goes through jaccpot._env.env_int (reads at call time, falls back to the default on a malformed value, where int(os.environ.get(...) or "1") silently turned a typo into batch=1). That one test was the whole of test-full (unit), test-smoke (3.11/3.12, unit) and test-runtime-typecheck.

Verified on this tree after the merge: the env-switch suite passes, and tests/unit/nearfield, tests/unit/pallas/test_fused_leaf_axis_contracts.py, tests/unit/operators/test_pallas_nearfield_fused.py and tests/unit/test_custom_vjp_parity.py all pass on CPU — so the new axis contracts accept the chunked-grid and batched-scan shapes. black/isort/flake8/pydoclint clean.

…hed self-P2P scan

Profiling one fused eval at N=200k, leaf 256, theta 1.0 on an A100 (93 % device
busy, so not launch-bound) put 40 of 47 ms in nearfield_leafpair and 7.5 ms in
the intra-leaf self term.  The kernel's wall time was bounded below by ONE
single-warp program: the grid is (leaf, 32-target subtile) and each program
loops over its leaf's whole neighbour row, and in a centrally concentrated
distribution the longest row is num_leaves-1 -- a halo leaf so extended that
the mutual MAC makes it near to every leaf -- so one warp serially sums all N
sources at ~100 ns per lane-step (19 ms at 200k, 38 ms at 400k, theta- and
order-independent).  Truncating rows 781->150 slots removed 18 ms while
dropping 21 % of the entries; a proportional model predicts 8.

nearfield_leafpair_pallas gains source_chunk: the grid becomes (leaf, subtile,
chunk), each program sums <= chunk source slots into a partial (kept in the
wide dtype when the wide accumulator is on) and the partials are reduced
afterwards.  JACCPOT_NEARFIELD_LEAFPAIR_SOURCE_CHUNK (default 64; 0 = the old
single pass, bit-for-bit) selects it.  Measured on a shared card: theta 1.0
46 -> 31 ms, theta 0.8 62 -> 46 ms, theta 0.4 124 -> 115 ms; aggL2 identical to
4 s.f. at theta 0.8/1.0 and 3.3065e-5 -> 3.2528e-5 at theta 0.4 (summation
order, at the fp32 accumulation floor).

_self_contributions scanned one leaf per step -- 782 x 5 launches of ~2 us at
leaf 256, 1563 x 5 at leaf 128 where the GPU sat 41 % idle.  It now vmaps
JACCPOT_NEARFIELD_SELF_BATCH leaves per step (default 32; 1 = old scan), which
keeps the remat argument per step and cuts the launch count 32x.  Forces
unchanged to 1e-7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@TobiBu
TobiBu force-pushed the perf/nearfield-row-split-batched-self branch from 3f4ad18 to 9b67434 Compare September 6, 2026 08:42
TobiBu and others added 2 commits September 7, 2026 09:23
tests/unit/test_shared_env_switches.py keeps the set of raw os.environ
readers outside runtime/ closed, and the batched self-P2P scan added one.
Route it through the sanctioned reader instead: env_int reads at call time
and falls back to the default on a malformed value, where the raw
int(os.environ.get(...) or "1") silently turned a typo into batch=1.

Fixes test-full (unit), test-smoke (3.11/3.12, unit) and
test-runtime-typecheck, which all run that suite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@TobiBu
TobiBu merged commit 845ba73 into main Sep 7, 2026
20 checks passed
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