Skip to content

Tiled shared-memory kernel for GPU index manipulations - #616

Draft
lkdvos wants to merge 1 commit into
mainfrom
gpu-tiled
Draft

lkdvos wants to merge 1 commit into
mainfrom
gpu-tiled

Conversation

@lkdvos

@lkdvos lkdvos commented Nov 6, 2026

Copy link
Copy Markdown
Member

Replaces the per-element unique/generic permute kernels of the GPUArrays extension with a single tiled KernelAbstractions kernel.

Design

  • One workgroup per (block, destination tree, tile). The tile is gathered, recoupled over the cols source trees, into @localmem in source order, then written out in destination order, so both reads and writes are coalesced.
  • Tiles have power-of-two extents, so tile coordinates are shifts and masks (no integer division) in Int32. Shared memory is padded once per dim-1 row to avoid bank conflicts.
  • The tile shape is chosen per transformer by greedily doubling extents under a cost model that counts 32 B sectors read and written, contiguous runs, per-tile overhead and idle lanes.
  • Unique blocks (rows = cols = 1) and recoupling blocks share one launch, which halves host overhead for non-abelian tensors. The diagonal paths use the same kernel with 1-D blocks.
  • Tile lengths are always one of {1, 32, 64, 128, 256}. They are branched on statically, and so is β ≠ 0. DeviceTreeTransformer therefore has a concrete type, and the @cached device constructors declare their return type (devicetransformertype).
  • Tiles of blocks with more source trees are scheduled first.
  • The extension is split into several files: truncation.jl, utility.jl, indexmanipulations.jl, trace.jl and diagonal.jl. Traces are unchanged.

Benchmarks (RTX A6000, old per-element kernels → tiled)

Throughput is back-to-back launches in µs. All outputs match the old kernels.

category examples speedup
small abelian / multiplicity-free (MPS, PEPS D ≤ 6) U1 MPS D=64: 7.3 → 5.9 1.0–1.3× (latency 1.05–1.45×)
small with recoupling SU2 PEPS D=4: 13.1 → 5.8, SU2 r4 D=8: 14.3 → 5.9 2.2–2.4×
many small blocks U1 r6 deg 2: 83.7 → 27.2, SU2 r4 j≤4: 21 → 12.3 1.7–3.1×
few large blocks Z2 7264² transpose: 1258 → 641 (copy 635), ℂ⁴⁸ r4: 629 → 129 1.5–4.9×, within 1.0–1.13× of copyto!
mixed U1 r4 D=128: 3029 → 1520, U1 large+tiny sectors: 9359 → 2278 1.2–4.1×

Known weak spots:

  • SU(2) rank-6 with tiny blocks and many recoupling coefficients: parity to about 7% slower. Gathering every source tree for each destination tree dominates here.
  • Tiny dense ℂ² rank-4 (16 elements): about 9% slower throughput, but slightly better latency.

These numbers are from before the last round of cleanup (static tile-length branching, typed caches, tile ordering). A rerun of the small-tensor cases after the cleanup matched them. The rerun of the larger cases ran while another job was using the GPU, so it still needs to be redone.

Possible follow-ups

  • Per-block tile shapes, for tensors that mix large and tiny sectors.
  • One workgroup per tile for all destination trees: read each source tile into shared memory once and apply U there. This should help the SU(2) rank-6 case.
  • A byte-based MAX_TILE_LENGTH, so Float32 can use larger tiles (not yet measured).

Tests

test/runtests.jl cuda: 125107 pass, 36 broken, 4 errors. The errors are IsingBimodule SpaceMismatchs raised while building the test spaces (test/cuda/planar.jl:80, test/cuda/tensors.jl:119), before any kernel runs. They are not caused by this PR, but I haven't confirmed they also fail on main.

🤖 Generated with Claude Code

Replace the per-element unique/generic permute kernels with a single tiled
KernelAbstractions kernel: each workgroup gathers one power-of-two tile of a
destination tree, recoupled over the source trees, into shared memory in source
order and writes it out in destination order, so reads and writes are both
coalesced. Unique and recoupling blocks share one launch, and the diagonal paths
use the same kernel with one-dimensional blocks.

- tile shapes are chosen per transformer with a sector-based cost model
- the tile length is branched on statically, so `DeviceTreeTransformer` has a
  concrete type and the device caches declare their return types
- tiles of blocks with more source trees are scheduled first
- split the GPUArrays extension into several files

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.11568% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ext/TensorKitGPUArraysExt/trace.jl 92.66% 8 Missing ⚠️
ext/TensorKitGPUArraysExt/TensorKitGPUArraysExt.jl 77.77% 6 Missing ⚠️
ext/TensorKitGPUArraysExt/indexmanipulations.jl 96.75% 5 Missing ⚠️
Files with missing lines Coverage Δ
ext/TensorKitGPUArraysExt/diagonal.jl 100.00% <100.00%> (ø)
ext/TensorKitGPUArraysExt/truncation.jl 100.00% <100.00%> (ø)
ext/TensorKitGPUArraysExt/utility.jl 100.00% <100.00%> (ø)
ext/TensorKitGPUArraysExt/indexmanipulations.jl 96.75% <96.75%> (ø)
ext/TensorKitGPUArraysExt/TensorKitGPUArraysExt.jl 77.77% <77.77%> (ø)
ext/TensorKitGPUArraysExt/trace.jl 92.66% <92.66%> (ø)

... and 7 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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