Skip to content

RealSpace kernel: coalesced IO + tunable launch; profiler: fused-path timing and --torch_profile - #13

Merged
alacour merged 5 commits into
mainfrom
perf/realspace-kernel-tuning
Sep 4, 2026
Merged

alacour merged 5 commits into
mainfrom
perf/realspace-kernel-tuning

Conversation

@alacour

@alacour alacour commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Performance work on the fused RealSpace nonlinearity plus profiler upgrades, measured on Perlmutter A100 (512-atom diamond box, 44k edges, float32 + TF32, all fusion flags on):

  • RealSpace Triton kernels (fwd+bwd): coalesced tile IO. The per-angular-column loads/stores (stride-3 scalar transactions) are replaced by one dense coalesced (BLOCK, N_ANG) tile load/store per operand, columns extracted in registers. Per-instance cost 2.32 → 1.38 ms across all 10 module instances; joint E+F step 174.1 → 159.6 ms.
  • Launch config env-tunable (ECENET_RS_BLOCK, ECENET_RS_WARPS); a 12-point sweep on the A100 showed the default (128/4) is at the plateau.
  • In-place m-major reads: tried, measured 4–6× slower, reverted — the einsum output layout degrades the tile load to strided scalar accesses. The gate in _rows() documents the measurement; per-operand stride plumbing retained.
  • profile_step: sub-components now time the path that actually runs. Reference ops are labeled (unfused ref) when a fused kernel replaces them; the fused ops (edge_frame_fused, pack_unrotate_fused, edge_frame_fused_single) get their own lines; nonlin lines tagged (fused).
  • profile_step: --torch_profile — per-op forward/backward/memory tables via torch.profiler; this is what identified the next optimization target (einsum layout-churn copies, ~29% of the joint step).

Test plan

  • tests/test_realspace_kernel.py passes on CPU (forward/backward equivalence, finite difference, conservativity, SO(3)); extended with an m-major-layout GPU case.
  • ⚠️ Run python tests/test_realspace_kernel.py once on a CUDA box before merging to exercise the triton vs fp64 ref and triton m-major layout tests (they skip on CPU).

🤖 Generated with Claude Code

https://claude.ai/code/session_019WyADHSVvKbQnYo1PAjGPK

alacour and others added 5 commits September 3, 2026 16:41
…ime fused paths

The RealSpace Triton kernels loaded/stored each angular column separately at
stride n_ang — N_ANG transactions per operand walking DRAM at one-third sector
utilization, which capped this bandwidth-bound op well below the A100's
bandwidth (measured ~15% at 44k edges x 256 features). Both kernels now do one
dense (BLOCK, N_ANG) coalesced tile load/store per operand and pull columns out
of the register tile with a multiply-mask reduction (free next to the loads it
replaces; no 3D intermediates, so no spill pressure). Launch config is
overridable per GPU via ECENET_RS_BLOCK / ECENET_RS_WARPS for a quick sweep.
Numerics unchanged: tests/test_realspace_kernel.py passes (CPU paths; run the
Triton test on a GPU box).

profile_step.py previously timed the *unfused reference* ops for the edge-frame
rotate and the MP pack/unrotate/rotate-back/unpack even when the fused flags
were on, so the sub-component lines neither summed to the real forward nor
showed the fused kernels' cost. The reference lines are now labeled
"(unfused ref)" when a fused path replaces them, and the fused ops
(edge_frame_fused, pack_unrotate_fused, edge_frame_fused_single) get their own
timing lines; nonlin lines are tagged "(fused)" when that path is engaged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WyADHSVvKbQnYo1PAjGPK
…us copies

The launch-config sweep on the A100 showed a ~1.4 ms floor that BLOCK/WARPS
could not move: the cost was outside the kernel. EquivariantLinear's einsum
emits (n_e, F, n_ang) with strides (F, 1, n_e*F), so the wrapper's
.contiguous() was paying a full read+write copy of both inputs every forward
call (and of inputs plus both incoming grads in the backward) before the
kernel launched. That layout is itself perfectly coalesced (rows are
unit-stride), so the copies bought nothing.

Each operand now carries its own (row, col) strides into the kernel, and the
_rows() helper flattens (e, f) to rows without copying whenever
stride(0) == F*stride(1) — covering both dense row-major and the einsum
m-major layout, with a contiguous fallback for anything else. GPU test
extended with an m-major case (forward and backward) against the contiguous
result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WyADHSVvKbQnYo1PAjGPK
…r on A100

The previous commit read EquivariantLinear's m-major einsum output in place
(strides (F, 1, R)) to skip the wrapper's contiguous copies. Profiling on the
A100 showed the opposite of the intent: the fused nonlinearity went from
1.38 ms to ~6.0 ms per instance (512-atom box, 44k edges), with the same
regression inside the MP trunk and receiver lines and a proportional hit in
the backward — the (BLOCK, N_ANG) tile load degrades to strided scalar
accesses across three ~45 MB-apart streams instead of one vectorized
coalesced read.

_rows() now skips the copy only for genuine row-major layouts (unit-stride
coefficient axis); the m-major layout goes back to the contiguous copy the
kernel is fast on. Per-operand stride plumbing stays for a future
transposed-tile experiment. Restores the prior 159.6 ms joint-step timing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WyADHSVvKbQnYo1PAjGPK
The per-section timers only see the forward, op by op; the backward (the
larger half of a force call — currently ~61% of the joint step) and the
kernel-level cost of composite blocks (receiver, trunk) are invisible to
them. --torch_profile reruns the same Totals closures under torch.profiler
and prints three aggregate tables: forward-only by self CUDA time, the joint
E+F step by self CUDA time (rows that grow between the two are the backward;
record_function tags mark the Triton regions), and the joint step by self
CUDA memory for the saved-tensor footprint behind large-box OOMs.
--profile_rows controls table length.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WyADHSVvKbQnYo1PAjGPK
@alacour
alacour merged commit b1686df into main Sep 4, 2026
1 check passed
@alacour
alacour deleted the perf/realspace-kernel-tuning branch September 4, 2026 03:33
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