Skip to content

Avoid boxing the BLAS-contractable inputs in _blas_contract! - #315

Merged
lkdvos merged 1 commit into
ncon-overheadfrom
issue-273
Oct 13, 2026
Merged

lkdvos merged 1 commit into
ncon-overheadfrom
issue-273

Conversation

@lkdvos

@lkdvos lkdvos commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Fixes #273, together with QuantumKitHub/Strided.jl#94, without inlining the call chain. Based on #314.

The 112 B per call in the reproducer of #273 (Julia 1.13.1) have two independent sources:

  • 64 B here: makeblascontractable returns either its input or a permuted copy. With an allocator such as Bumper's, the copy has a different parent type, so the result is a union of two StridedView types. That union is not isbits and was boxed on every contraction, even when no copy was made. _blas_contract! now makes the inputs BLAS-contractable one at a time, and passes them on to the next step (_blas_contract_B!, then _blas_contract_C!) within each branch rather than returning them, so the union is never materialized. The copy itself is made by a small @inline helper: inlining it lets the compiler elide the Array wrapper of the copy, which otherwise costs an extra allocation per copy.
  • 48 B in Strided.jl: variables captured by the Threads.@spawn closure in _threaded_blas_mul! were boxed, fixed in Avoid boxing the views captured by the threaded BLAS multiplication Strided.jl#94.

makeblascontractable itself is unchanged, since it is tested and possibly used elsewhere.

Benchmarks

Allocations in the reproducer of #273 (StridedBLAS, Bumper allocator), with the latest Strided and StridedViews main:

main this PR this PR + Strided.jl#94
Float64 112 B 48 B 0 B
ComplexF64 112 B 48 B 0 B
ComplexF64, conj 48 B 48 B 0 B

Runtime and compile time (minimum times, fresh processes):

main this PR
reproducer, Bumper 422–434 ns 423–431 ns
same contraction, default allocator 1.83 μs / 16 allocs 0.45 μs / 4 allocs
8⁴ contraction requiring permuted copies, default allocator 12.7–13.7 μs / 25 allocs 8.9–9.6 μs / 7 allocs
first call, rank-5 ComplexF32 contraction 5.9 s 5.65 s
first call, Bumper contraction 32.6 s 32.7 s

Part of the speedup with the default allocator overlaps with the scalar type specialization of makeblascontractable in #314, since withblascontractable also specializes on it.

🤖 Generated with Claude Code

`makeblascontractable` returns either its input or a permuted copy, whose types differ when
the copy is allocated by an allocator such as Bumper's. The resulting union is not an
`isbits` type and was therefore heap allocated on every contraction, even when no copy was
required.

`_blas_contract!` now makes the inputs BLAS-contractable one at a time, passing them on to
the next step in each branch rather than returning them, such that the union is never
materialized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lkdvos
lkdvos changed the base branch from main to ncon-overhead October 13, 2026 18:55
@lkdvos
lkdvos merged commit 42e1ae1 into ncon-overhead Oct 13, 2026
7 of 9 checks passed
@lkdvos
lkdvos deleted the issue-273 branch October 13, 2026 19:25
@lkdvos

lkdvos commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Folded into #314, since this fixes the Enzyme failures that the specialization of makeblascontractable in #314 triggered on Julia 1.13. Closing in favor of #314.

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.

StridedBLAS contraction path allocates 64 B on Julia 1.12.1; inlining appears to eliminate the allocation

1 participant