Skip to content

Refactor index manipulation kernels around position-indexed subblocks - #526

Draft
lkdvos wants to merge 1 commit into
mainfrom
ld-adjoint
Draft

Refactor index manipulation kernels around position-indexed subblocks#526
lkdvos wants to merge 1 commit into
mainfrom
ld-adjoint

Conversation

@lkdvos

@lkdvos lkdvos commented Sep 3, 2026

Copy link
Copy Markdown
Member

Structural fix for #516 (adjoint permute! up to 80× slower than the plain path), superseding #519 and #520 and building on #518/#521.

What changes

  • StridedSubblocks: sector-independent, integer-indexed views into the flat data of a TensorMap in canonical fusion-tree order, with an optional lazy conjugation (op = identity/conj as type parameter). TreeSubblocks is the generic counterpart for any AbstractTensorMap, going through subblock.
  • TreeTransformers store only the mapping between subblock positions and recoupling coefficients (plus the two subblock structures), and are cached for every tensor type; the closure-based fallback and TrivialTreeTransformer are gone.
  • One kernel serves abelian and generic transformers, TensorMaps and other tensor types.
  • permute!/braid!/transpose! and TO.tensoradd! fold AdjointTensorMap sources and destinations (and conjA) into a conjsrc::Bool, relabeled p/levels and conjugated α/β; the flag is resolved into the view type only at the kernel entry, so everything stays type-stable.
  • BraidingTensor sources are converted before the transformer is chosen (the old overload could reach an untyped kernel).

Numbers (issue reproducer, adjoint vs plain permute!): fℤ₂ 1.30× → 1.09×, fℤ₂⊠U₁ 3.57× → 1.05×, SU₂ 4.44× → 1.01×, U₁ 2.23× → 1.11×; plain path unchanged within noise (+32 B from the extra Bool in the cache key).

Tests: adjoint source/destination/both for permute!/transpose!/braid! with accumulation, @tensor conj, BraidingTensor source, and a dot-based isometry check that would catch a wrongly conjugated recoupling matrix for complex sector scalar types. Benchmark suite gained adjoint = true permute variants.

Follow-ups (not here): keying transformers on sector structure only; passing conj into trace_permute!.

🤖 Generated with Claude Code

Index manipulations now run through a single kernel that operates on
subblocks addressed by position: `StridedSubblocks` (sector-independent
views into the flat data of a `TensorMap`) or `TreeSubblocks` (any
`AbstractTensorMap`, through `subblock`), both carrying an optional lazy
conjugation. `TreeTransformer`s store only the mapping between subblock
positions and recoupling coefficients, alongside the subblock structures,
and are cached for every tensor type.

Adjoint sources and destinations, as well as `conj` in `tensoradd!`, are
folded into a conjugation flag, relabeled permutation and levels, and
conjugated scalars, so that `AdjointTensorMap` wrappers no longer force
the uncached generic path (fixes #516, supersedes #519 and #520).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.58228% with 37 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/tensors/blockiterator.jl 20.83% 19 Missing ⚠️
src/tensors/tensor.jl 18.18% 9 Missing ⚠️
src/tensors/indexmanipulations.jl 90.32% 6 Missing ⚠️
src/tensors/braidingtensor.jl 0.00% 2 Missing ⚠️
ext/TensorKitEnzymeExt/utility.jl 0.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/spaces/homspace.jl 89.28% <100.00%> (-2.09%) ⬇️
src/tensors/tensoroperations.jl 91.81% <100.00%> (-5.57%) ⬇️
src/tensors/treetransformers.jl 100.00% <100.00%> (+4.21%) ⬆️
ext/TensorKitEnzymeExt/utility.jl 0.00% <0.00%> (-20.94%) ⬇️
src/tensors/braidingtensor.jl 72.18% <0.00%> (-14.75%) ⬇️
src/tensors/indexmanipulations.jl 78.75% <90.32%> (-11.69%) ⬇️
src/tensors/tensor.jl 57.28% <18.18%> (-25.79%) ⬇️
src/tensors/blockiterator.jl 11.20% <20.83%> (-32.92%) ⬇️

... and 49 files with indirect coverage changes

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

@lkdvos

lkdvos commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Benchmark: main vs ld-adjoint (rusty, dedicated rome node, --threads=4)

benchpkg TensorKit --rev=main,ld-adjoint --bench-on=ld-adjoint --filter=indexmanipulations, i.e. both revisions ran this branch's suite including the new adjoint = true variants (permute!(C, A', p)). Ratio is main / ld-adjoint, so > 1 means this branch is faster.

Adjoint sources (the #516 case): 1.03–1.69× faster than main, and at parity with the plain TensorMap path on this branch (e.g. SU₂ Float64 [[1,3],[2,4]]: 1.27 ms → 0.78 ms next to 0.82 ms plain; on main the adjoint variant was 2.2× slower than plain).

Plain path: unchanged within noise. Two entries show a < 1 ratio (SU₂ Float64 [[1,3],[2,4]] 0.72 ± 0.19 and ℤ₂ [7264, 7264] 0.82 ± 0.03); both were re-measured locally against main at 4 threads and are at parity (0.63–0.67 vs 0.67 ms, and 49–50 vs 50 ms). The ℤ₂ adjoint variant executes the identical kernel (real eltype ⇒ op = identity) and is at parity in the table as well, and the untouched Trivial path shows the same kind of scatter (0.83 ± 0.14), so these are between-process memory-placement effects on the 400 MB transposes rather than code differences.

Full table
main ld-adjoint main / ld-adjoint
indexmanipulations/permute/permute/("ComplexF64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[1, 3], [2, 4]]") 1.37 ± 0.22 ms 1.19 ± 0.23 ms 1.15 ± 0.29
indexmanipulations/permute/permute/("ComplexF64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[1, 3], [2, 4]]", "adjoint") 1.59 ± 0.085 ms 1.55 ± 0.32 ms 1.03 ± 0.22
indexmanipulations/permute/permute/("ComplexF64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[4, 2, 3], [1]]") 1.34 ± 0.25 ms 1.22 ± 0.22 ms 1.1 ± 0.28
indexmanipulations/permute/permute/("ComplexF64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[4, 2, 3], [1]]", "adjoint") 1.32 ± 0.12 ms 0.939 ± 0.15 ms 1.41 ± 0.26
indexmanipulations/permute/permute/("Float64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[1, 3], [2, 4]]") 0.59 ± 0.04 ms 0.822 ± 0.21 ms 0.717 ± 0.19
indexmanipulations/permute/permute/("Float64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[1, 3], [2, 4]]", "adjoint") 1.27 ± 0.19 ms 0.775 ± 0.12 ms 1.64 ± 0.35
indexmanipulations/permute/permute/("Float64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[4, 2, 3], [1]]") 0.811 ± 0.089 ms 0.665 ± 0.059 ms 1.22 ± 0.17
indexmanipulations/permute/permute/("Float64", "SU2Irrep", "[48, 48, 48, 48]", "[1.0, 1.0, 1.0, 1.0]", "Any[[4, 2, 3], [1]]", "adjoint") 0.684 ± 0.038 ms 0.5 ± 0.023 ms 1.37 ± 0.098
indexmanipulations/permute/permute/("Float64", "SU2Irrep", "[512, 512]", "[1.0, 1.0]", "Any[[2, 1], Any[]]") 0.144 ± 0.014 ms 0.0849 ± 0.0089 ms 1.69 ± 0.24
indexmanipulations/permute/permute/("Float64", "Trivial", "[43408, 1216]", "nothing", "Any[[2, 1], Any[]]") 0.0453 ± 0.0017 s 0.0545 ± 0.0086 s 0.832 ± 0.14
indexmanipulations/permute/permute/("Float64", "Trivial", "[7264, 7264]", "nothing", "Any[[2, 1], Any[]]") 0.0557 ± 0.0012 s 0.0548 ± 0.0011 s 1.02 ± 0.029
indexmanipulations/permute/permute/("Float64", "Z2Irrep", "[43408, 1216]", "[0.5, 0.5]", "Any[[2, 1], Any[]]") 27.6 ± 1.6 ms 27.3 ± 0.8 ms 1.01 ± 0.066
indexmanipulations/permute/permute/("Float64", "Z2Irrep", "[43408, 1216]", "[0.5, 0.5]", "Any[[2, 1], Any[]]", "adjoint") 27.7 ± 2.1 ms 27.8 ± 2.7 ms 0.995 ± 0.12
indexmanipulations/permute/permute/("Float64", "Z2Irrep", "[7264, 7264]", "[0.5, 0.5]", "Any[[2, 1], Any[]]") 22.9 ± 0.47 ms 28 ± 0.76 ms 0.819 ± 0.028
indexmanipulations/permute/permute/("Float64", "Z2Irrep", "[7264, 7264]", "[0.5, 0.5]", "Any[[2, 1], Any[]]", "adjoint") 23 ± 0.41 ms 23.1 ± 0.45 ms 0.994 ± 0.026

🤖 Generated with Claude Code

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