Skip to content

Look up the tuning hooks behind an inference barrier, so that loading a backend doesn't invalidate AK's operations - #189

Merged
maleadt merged 1 commit into
mainfrom
tb/tuning-invalidations
Oct 27, 2026
Merged

maleadt merged 1 commit into
mainfrom
tb/tuning-invalidations

Conversation

@maleadt

@maleadt maleadt commented Oct 27, 2026

Copy link
Copy Markdown
Member

AK's package extensions set the tuning of their backends (reduce_tuning(::MetalBackend, ::Type) etc.), so that it ships with AK rather than with the back ends. The back ends precompile AK's operations, e.g. Metal.jl's and CUDA.jl's reductions through GPUArrays, and that code calls the hooks directly, so it depends on their generic methods. Loading AK's extension for the backend then invalidates it. With the back ends defining their KernelAbstractions methods in src (JuliaGPU/Metal.jl#1118, JuliaGPU/CUDA.jl#3493) and JuliaGPU/KernelAbstractions.jl#937, which does the same for KernelAbstractions' own tuning, AK's reduce_tuning and scan_tuning are the largest invalidation trees when loading Metal, and reduce_tuning the largest one when loading CUDA.

This PR looks the tunings up through tuning(hook, backend, T), which calls the hook behind Base.inferencebarrier and asserts the result type that each hook declares with tuning_type. Every internal call goes through it, and the hooks and extensions are unchanged. Tunings can already differ per device, so no caller relied on their values being constants.

Unique method instances invalidated by using <backend>, KernelAbstractions, and the time a fresh process takes to load the back end and run its first broadcast, getindex, sum, sum(; dims), accumulate, sort, any and 4-d broadcast (Julia 1.13.2, with the PRs above):

main this PR first operations
Metal (M1) 1177 911 4.69–4.79 s → 3.93–4.00 s
CUDA (RTX 6000 Ada) 852 758 7.63 s → 7.49 s (median of 9)
oneAPI (Arc A770) 787 787 13.8–14.0 s either way

On Metal, the first broadcast takes 0.05 s instead of 0.72 s, as it no longer recompiles, and the first sum 0.13 s instead of 0.22 s. AK has no AMDGPU extension, and oneAPI.jl doesn't precompile AK's operations, so neither changes.

The values no longer constant-fold, so where a tuning selects between launches, such as reduce_then_scan and the sub-group width of scans, the host code for both is compiled. That makes the first accumulate 0.05–0.1 s slower (0.90 → 1.00 s on the M1). Each lookup also takes a dynamic dispatch, which is small next to the launches that follow it.

The tests pass on CUDA, AMDGPU and Metal.

Backend packages precompile AK's operations, e.g. Metal.jl's reductions
through GPUArrays, and that code depended on the generic methods of the
tuning hooks, so loading AK's extension for the backend invalidated it.
Look the tunings up through a `tuning` helper that hides the call from
inference. Tunings may already differ per device, so callers never
relied on their values being constants.
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