Skip to content

Re-enable precompilation - #286

Merged
lkdvos merged 8 commits into
masterfrom
ld-compilation
Aug 3, 2026
Merged

Re-enable precompilation#286
lkdvos merged 8 commits into
masterfrom
ld-compilation

Conversation

@lkdvos

@lkdvos lkdvos commented Jul 21, 2026

Copy link
Copy Markdown
Member

This closes #226.

The idea is that with the latest versions of Strided, it might be reasonable to just enable precompilation always, hopefully helping out with the CI runtimes in downstream packages like TensorKit and MPSKit.

I am not entirely sure if #208 is still happening, but I'd be willing to give it another try?

Additionally, I've slightly reorganized the tensoradd/tensortrace/tensorcontract code to reduce the total number of Strided kernels that have to be generated by canonicalizing the alpha and beta number types, and no longer specializing on unused backend or allocator arguments.

I've also found that the @tensor C[i; k] := A[i; j] * B[j; k] always yields pAB = ((1,), (2,)), and therefore the fast path for matrix multiplication was actually never taken.

@lkdvos lkdvos changed the title Ld compilation Re-enable precompilation Jul 21, 2026
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.11111% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/implementation/diagonal.jl 87.27% 7 Missing ⚠️
Files with missing lines Coverage Δ
src/implementation/abstractarray.jl 90.90% <100.00%> (+1.62%) ⬆️
src/implementation/blascontract.jl 100.00% <100.00%> (ø)
src/implementation/strided.jl 96.73% <100.00%> (-3.27%) ⬇️
src/indices.jl 88.46% <100.00%> (-2.85%) ⬇️
src/interface.jl 50.84% <100.00%> (+2.63%) ⬆️
src/precompile.jl 100.00% <100.00%> (+63.07%) ⬆️
src/implementation/diagonal.jl 94.26% <87.27%> (-0.19%) ⬇️

... and 1 file with indirect coverage changes

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

`standardize_scalartype` converts `β` to `scalartype(C)`, which turns
`VectorInterface.Zero()` into a plain zero and thereby drops its strong-zero
semantics. All kernels but one funnel `β` through `scale`/`scale!`/`Scaler`,
which are strong-zero by construction; the `C::Diagonal, A::Diagonal,
B::Diagonal` method instead used a raw broadcast, so `0 * C` propagated NaNs
from uninitialized output memory.

Resolve `conj` into the `StridedView`s at the top level, as the other methods
in this file already do, and add `_diagdiagdiagcontract!` with an explicit
`iszero(β)` branch following `stridedtensoradd!`. There is no reduction here,
so the zero branch passes `nothing` for both `op` and `initop`, which assigns
into `C` without ever reading it. This also replaces the broadcast with
`Strided._mapreducedim!`, matching the other two diagonal kernels.

Document the hazard on `standardize_scalartype` and add regression tests that
poison the output with NaNs for all four diagonal `tensorcontract!` methods.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lkdvos
lkdvos requested a review from kshyatt August 2, 2026 15:25
@lkdvos
lkdvos enabled auto-merge (squash) August 2, 2026 21:12
## Defaults

By default, precompilation is disabled, but can be enabled for "tensors" of type `Array{T,N}`, where `T` and `N` range over the following values:
By default, precompilation is enabled for "tensors" of type `Array{T,N}`, where `T` and `N` range over the following values:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to precompile for GPU arrays too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I understood yes and no, we can add a precompilation module to the package extensions to compile the "bookkeeping code", but from what I understood the actual GPU kernels themselves not yet (although it seemed like Tim was working on this). For cuTENSOR this is probably worth it anyways, as that library is compiled

@kshyatt kshyatt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo my doc question (feel free to punt on this)

@lkdvos
lkdvos merged commit 8fd0f8f into master Aug 3, 2026
13 checks passed
@lkdvos
lkdvos deleted the ld-compilation branch August 3, 2026 08:09
function argcheck_index2tuple(C::AbstractArray, pC::Index2Tuple)
return ndims(C) == numind(pC) && isperm(linearize(pC)) ||
argcheck_index2tuple(C::AbstractArray, pC::Index2Tuple) = argcheck_indextuple(C, linearize(pC))
function argcheck_indextuple(C::AbstractArray, pC::IndexTuple)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if such functions need a @noinline, to avoid them being inlined in the Index2Tuple method body, and then yielding the same compilation cost again (as far as I understand how compilation works).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the other argchecks below

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is probably hard to reason about this, since the alternative is that the Index2Tuple version gets inlined because it is small. From what I can measure this is anyways not really the dominant contribution, so I wanted to avoid doing too much compiler forcing

# note: `pAB` is only ever consumed through `linearize`/`invperm` from here on, so it is
# canonicalized to an `IndexTuple` and the reversed permutation does not need to be
# repartitioned the way `pAB` is
pAB = linearize(pAB)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have been nice to maybe also denote this one with a prime, i.e.

Suggested change
pAB = linearize(pAB)
pAB = linearize(pAB)

(Then also needs to be changed below)

Comment thread src/precompile.jl

tensoradd!(C, A, pA, false, One(), Zero(), backend, allocator)
tensoradd!(C, A, pA, false, one(T), Zero(), backend, allocator)
tensoradd!(C, A, pA, false, one(T), zero(T), backend, allocator)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if doing conj=true also triggers additional compilation paths, since this will at some point make a StridedView where the conjugation flag is encoded as a type parameter, and these stridedviews go all the way down to blas_contract!.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for the tensoradd case I'm sure that most of this is already compiled, the main reason being that we manually union-split

if conjA
stridedtensoradd!(SV(C), conj(SV(A)), p, α′, β′)
else
stridedtensoradd!(SV(C), SV(A), p, α′, β′)
end

As a result, both branches are already precompiled because everything is type stable (I think, and while I was playing around with this I did indeed find the method count to be in line with that)

@Jutho

Jutho commented Aug 3, 2026

Copy link
Copy Markdown
Member

Sorry, left some comments after merging.

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.

3 participants