Skip to content

Avoid load-order invalidation of the compilation pipeline - #1052

Merged
maleadt merged 4 commits into
mainfrom
tb/invalidations
Oct 24, 2026
Merged

maleadt merged 4 commits into
mainfrom
tb/invalidations

Conversation

@maleadt

@maleadt maleadt commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

#1048 reports that loading ChainRulesCore before KernelAbstractions makes the first CPU kernel launch about 6× slower. The cause is load-order invalidation: ChainRulesCore adds methods like promote_rule(::Type{<:Number}, ::Type{<:AbstractZero}) and *(::NotImplemented, ::Any), which invalidate Base method instances with abstract signatures (Colon(::Int64, ::Real), *(::Any, ::String), ...). Any precompiled GPUCompiler code with an edge to one of those fails verification when GPUCompiler is loaded, and so does everything that calls it, so most of the compilation pipeline (emit_llvm, compile, finish_ir!, check_ir!, build_runtime) gets recompiled for the first kernel. precompile_blockers only shows the part that's invalidated when ChainRulesCore is loaded after GPUCompiler, which is why the issue only found julia_datalayout.

This PR keeps the GPUCompiler code involved concretely typed: it asserts the result of llvm_datalayout (the suggestion in the issue), the method instance's signature in check_invocation and the Vararg count in the mangler, narrows statements before slicing their arguments, and uses typed array literals for rewritten calls. The rest of the chains are fixed in the dependencies: JuliaLLVM/LLVM.jl#702 and maleadt/CompilerCaching.jl#33 (the latter is the biggest single contributor). A separate commit imports | explicitly in the runtime module, which only removes spurious invalidation entries that Julia 1.13 reports for lazily created bindings (JuliaLang/julia#59135).

JuliaGPU/KernelAbstractions.jl#914 and JuliaGPU/CUDA.jl#3440 fix similar chains in those packages. Other packages invalidate the same pipeline through different methods, e.g. DataFrames' dependencies through LLVM.jl's @nospecialize C API layer. That needs further work in LLVM.jl, so this doesn't eliminate the problem for every package, but ChainRulesCore (loaded by most of the AD and ML ecosystem) is covered.

Tested with the full test suite on Julia 1.13. With all three PRs, first-kernel times on Julia 1.13.2 with ChainRulesCore loaded first:

before after without ChainRulesCore
KernelAbstractions CPU 2.51 s 1.09 s 0.78 s
CUDA (@cuda + broadcast + sum) 4.25 s 2.76 s 2.52 s

Fixes #1048

`llvm_datalayout` is called on an abstractly-typed target, so its result was
inferred as `Any`, and the string concatenation in `julia_datalayout` was
compiled with an edge to `*(::Any, ::String)`. Packages that add methods to
`*`, like ChainRulesCore, invalidated it and the `compile_method_instance`
closure that calls it. Part of #1048.
Code that calls Base functions on abstractly-typed values is compiled with edges
to their abstract signatures, e.g. `Colon(::Int64, ::Real)` for `x[2:end]` on a
value of unknown type. Packages loaded before GPUCompiler that extend those
functions (`promote_rule` by ChainRulesCore, `Colon` by Static) then make Julia
discard GPUCompiler's precompiled code when it is loaded, and recompile it for
the first kernel. Assert types that are known, narrow before indexing, and use
typed array literals.
The generated boxing functions use `|` from Base. Left implicit, its binding is
only created when a downstream package (e.g. KernelAbstractions) precompiles code
that calls them, and Julia 1.13 then reports the edges to that binding as
invalidations every time that package is loaded. The code is not actually
recompiled, but the spurious entries obscure real invalidations in SnoopCompile.
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.41%. Comparing base (4939c9d) to head (8edcfd1).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/driver.jl 75.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1052   +/-   ##
=======================================
  Coverage   92.41%   92.41%           
=======================================
  Files          31       31           
  Lines        7261     7266    +5     
=======================================
+ Hits         6710     6715    +5     
  Misses        551      551           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

@maleadt
maleadt merged commit 2114bf6 into main Oct 24, 2026
36 checks passed
@maleadt
maleadt deleted the tb/invalidations branch October 24, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

latency Compile time, load time, time-to-first-execution, invalidations.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

julia_datalayout is invalidated by ChainRulesCore, adding ~2 s to the first kernel compile

1 participant