Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Enzyme reverse mode: `prod(ndrange)` fails for kernels with a construction-time ndrange

Open
#924 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@bgroenks96 is already working on this.

Since Oct 25, 2026.

  • #925 by @bgroenks96 — open

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

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from JuliaGPU/KernelAbstractions.jl

All issues in JuliaGPU/KernelAbstractions.jl

Similar issues

More Julia issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.