Skip to content

Unify CPU and GPU tree transformers - #584

Draft
lkdvos wants to merge 4 commits into
mainfrom
ld-unified-treetransformer
Draft

lkdvos wants to merge 4 commits into
mainfrom
ld-unified-treetransformer

Conversation

@lkdvos

@lkdvos lkdvos commented Oct 15, 2026

Copy link
Copy Markdown
Member

Closes #578.

This unifies the CPU and GPU tree transformers into a single data structure per fusion style, while keeping the kernel implementations themselves separate.

Design

Both transformers are structs of arrays of isbits elements, such that the host version is moved to the GPU with Adapt.adapt. This replaces DeviceUniqueTreeTransformer and DeviceGenericTreeTransformer.

  • UniqueTreeTransformer: coeffs, structs_dst, structs_src and n + 1 work_offsets. The transformation is a bijection between the fusion trees, so the entries are stored in the canonical order of the destination.
  • GenericTreeTransformer: reuses a UniqueTreeTransformer for the single-tree blocks, and stores the multi-tree blocks as concatenated (square) recoupling matrices and locators, described by tree_offsets, coeff_offsets and work_offsets.

The type of the locators depends on the SubblockLayout of either tensor, which is now part of the transformer cache key:

  • StridedLayout (TensorMap): (sizes, strides, offset) for the destination and (strides, offset) for the source, with the source strides permuted by p in advance. For unique transformers, the destination structure is shared with the space.
  • PositionalLayout (any other AbstractTensorMap, e.g. DiagonalTensorMap): subblock positions into subblocks(t), and p is applied in the kernel.

The CPU kernels now operate on the flat data vector of a TensorMap instead of StridedSubblocks, which is removed. For the generic transformer, the permutation is now applied when packing the source blocks, such that the unpacking is a trivial strided addition.

Benchmarks

Single-threaded permute!, main vs this branch, on an exclusive AMD EPYC 7742 (rome) node:

  • Unique (U1, Z2, U1×U1, fermion parity): 0.84–1.02×, where tensors with large subblocks gain 7–16%. This comes from visiting the blocks in the order of the destination (sequential writes), as confirmed by a separate diagnostic that compares both block orders on the same transformer data.
  • Generic (SU2, A4, fermion parity×SU2×U1): 0.91–1.04×.
  • Pre-permuting the source strides has no measurable effect.
  • Small-block cases (e.g. U1 with 231 subblocks of 16 elements) show ±10% differences that are stable within a job but switch between Float64 and ComplexF64 across nodes, and are entirely inside the kernel, which performs identical per-block work on both revisions. This is attributed to code layout rather than to the changes here.

The multi-threaded behaviour (32 threads) is still under investigation: there, the results vary between 0.66× and 1.65× for tensors with many small subblocks, where main also slows down with more threads.

Testing

  • Full (non---fast) CPU test files for index manipulations, diagonal tensors, braiding tensors, planar operations, contractions and construction, plus cuda/tensors and cuda/planar on an RTX A6000.
  • Additional checks for all combinations of strided and positional layouts on multi-tree blocks, with and without conjsrc.

🤖 Generated with Claude Code

lkdvos and others added 4 commits October 14, 2026 12:37
The `UniqueTreeTransformer` is now a single struct-of-arrays used by both the CPU and GPU
code paths: `coeffs`, `structs_dst`, `structs_src`, `work_offsets` and `nwork`, with the
entries in canonical destination order. The type of the locators is determined by the
`SubblockLayout` of either tensor, which is now part of the transformer cache key:
`StridedLayout` for `TensorMap`s, where the source strides are permuted by `p` in advance
and the destination structure is shared with the space, and `PositionalLayout` for other
tensor types, which index `subblocks` by position.

The CPU kernel now works on the flat data vector, and the GPU extension moves the host
transformer to the device through `Adapt`, replacing `DeviceUniqueTreeTransformer`. The
one-tree blocks of the `DeviceGenericTreeTransformer` reuse the same struct.

Part of #578.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Store `n + 1` work offsets instead of a separate `nwork`, computed with `cumsum`; the GPU kernel
reads the total on the device and launches over the destination length instead. The subblock
accessors are now plain `@inline` functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `GenericTreeTransformer` now reuses the `UniqueTreeTransformer` for its single-tree blocks
and stores the multi-tree blocks as concatenated recoupling matrices and locators, together with
`tree_offsets`, `coeff_offsets` and `work_offsets` describing each block. The same structure is
used on the CPU and, through `Adapt`, on the GPU, replacing `DeviceGenericTreeTransformer`.
The CPU kernels now operate on the flat data of a `TensorMap` instead of `StridedSubblocks`,
where the permutation is applied when packing the source blocks.

Part of #578.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`StridedSubblocks` is no longer used by the index manipulation kernels, which now operate on the
flat data of a `TensorMap` with the subblock locations stored in the transformers. Also fix the
dense strides of the GPU kernels for subblocks without indices.

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

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.

Unify CPU and GPU tree transformer code paths

1 participant