Enzyme reverse mode: `prod(ndrange)` fails for kernels with a construction-time ndrange
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 52/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- julia
- Domain
- backend, performance
Research direction
Read ext/EnzymeCore08Ext.jl and locate _create_tape_kernel for Kernel{<:GPU}, then check how its available iterspace determines the tape size when ndrange is nothing. Review the imports in ext/EnzymeExt.jl for the proposed workitems dependency. Done means reverse mode works when ndrange is fixed at kernel construction as well as when supplied at launch; the issue does not name a test file.
Written by the indexing model from the issue text.
Description
Enzyme reverse mode through KA kernels currently fails when those kernel are constructed with a constant ndrange. The bug appears to be in the Enzyme extension for KA.
The following is generated by an LLM 🤖 and verified by a human 👨
Summary
_create_tape_kernel sizes the per-thread tape with prod(ndrange). When a kernel is built with
its ndrange fixed at construction (k!(backend, workgroupsize, ndrange)) and launched without an
ndrange keyword, ndrange is nothing at that point, and the rule fails with:
MethodError: no method matching iterate(::Nothing)
Reproducer
using CUDA, Enzyme, KernelAbstractions
using KernelAbstractions: @kernel, @index, get_backend
@kernel function k!(out, @Const(c))
i = @index(Global)
@inbounds out[i] = c[i]^2
end
# ndrange supplied as a keyword at launch (the common case).
apply_dynamic!(out, c) = (k!(get_backend(out), 256)(out, c; ndrange = length(out)); nothing)
# ndrange baked into the kernel object at construction, launched with no keyword.
# This is what Oceananigans' `launch!` does (it builds a kernel with an `OffsetStaticSize`
# ndrange so the loop bounds are compile-time constants).
function apply_static!(out, c)
kernel = k!(get_backend(out), 256, size(out))
kernel(out, c)
return nothing
end
function check(name, thunk)
try
thunk(); CUDA.synchronize()
println(" $name: OK")
catch e
println(" $name: FAILED ", first(sprint(showerror, e), 100))
end
end
n = 1024
c = CuArray(rand(n))
out, dc, dout = CUDA.zeros(Float64, n), CUDA.zeros(Float64, n), CUDA.zeros(Float64, n)
seed() = (fill!(dc, 0); fill!(dout, 1))
println("Enzyme reverse mode on GPU:")
check("ndrange as a launch keyword", () ->
(seed(); Enzyme.autodiff(set_runtime_activity(Reverse), apply_dynamic!,
Duplicated(out, dout), Duplicated(c, dc))))
check("ndrange fixed at construction", () ->
(seed(); Enzyme.autodiff(set_runtime_activity(Reverse), apply_static!,
Duplicated(out, dout), Duplicated(c, dc))))
Output:
Enzyme reverse mode on GPU:
ndrange as a launch keyword: OK
ndrange fixed at construction: FAILED MethodError: no method matching iterate(::Nothing)
This is the launch style Oceananigans uses: launch! constructs kernels with an
OffsetStaticSize ndrange so the loop bounds are compile-time constants, so every Oceananigans
GPU kernel hits this under reverse mode.
Cause
In ext/EnzymeCore08Ext.jl, _create_tape_kernel for Kernel{<:GPU}:
# Allocate per thread
subtape = allocate(backend(kernel), TapeType, prod(ndrange))
The resolved iteration space is already available as iterspace in the same function, so the
thread count can be recovered from it.
Suggested fix
@inline function _ka_nthreads(ndrange, iterspace)
return ndrange === nothing ?
length(blocks(iterspace)) * length(workitems(iterspace)) :
prod(ndrange)
end
and use _ka_nthreads(ndrange, iterspace) in place of prod(ndrange). workitems needs adding
to the import KernelAbstractions: list in ext/EnzymeExt.jl.
Patched locally and confirmed to fix it. Note this fix is only reachable once the two CUDA.jl
cudaconvert rule bugs are fixed as well (see the sibling directories), since an Oceananigans
kernel fails earlier without those.
Status on current package and Julia versions (rechecked 2026-10-25)
| Julia 1.10.12 | Julia 1.12.7 | Julia 1.13.2 |
|---|---|---|
| reproduces | reproduces | reproduces |
Same MethodError: no method matching iterate(::Nothing) on all three.
Checked with the newest registered packages available offline: Oceananigans 0.114.1 (where used),
CUDA.jl 6.4.1, KernelAbstractions 0.9.45, EnzymeCore 0.8.26, and Enzyme 0.13.216 (0.13.215 on
Julia 1.12), on an NVIDIA A40.
Environment
Julia 1.10.12, KernelAbstractions 0.9.42, CUDA.jl 6.2.2, Enzyme 0.13.205, NVIDIA A40.
- Dominant language
- Julia
- Stars
- 532
- Forks
- 88
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 72
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from JuliaGPU/KernelAbstractions.jl
-
good first issue runtime
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
JuliaGPU/KernelAbstractions.jl#691 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
JuliaGPU/KernelAbstractions.jl#921 ·
Maintainers usually reply within 1 day
-
CPU backend: `Base.checked_mul` in a kernel fails to build (`__spirv_SMulExtended` missing in PoCL)Open
Difficulty 4/5 3-5 days Newbie friendliness 52/100
JuliaGPU/KernelAbstractions.jl#916 ·
Maintainers usually reply within 1 day
-
kernels pocl upstream
Difficulty 5/5 Over a week Newbie friendliness 12/100
JuliaGPU/KernelAbstractions.jl#894 ·
Maintainers usually reply within 1 day
-
kernels performance pocl upstream
Difficulty 5/5 Over a week Newbie friendliness 12/100
JuliaGPU/KernelAbstractions.jl#893 ·
Maintainers usually reply within 1 day
All issues in JuliaGPU/KernelAbstractions.jl
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
gridap/GridapPETSc.jl#129 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
io
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
osrep/Runes2P.jl#115 ·