JuliaDiff / JuliaDiff/DifferentiationInterface.jl

AutoEnzyme Jacobian preparation scalar-indexes Reactant traced arrays

Open
#1,066 10 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

backend wontfix
Dominant language
Julia
Stars
313
Forks
35
PR merge metrics
No merged PRs in 30d

Description

Problem

prepare_jacobian with AutoEnzyme() fails when called inside Reactant.@jit because Enzyme's chunked shadow preparation scalar-indexes a TracedRArray.

This is a focused instance of the broader Reactant support discussion in https://github.com/JuliaDiff/DifferentiationInterface.jl/issues/265.

Reproducer

This environment contains only DifferentiationInterface, Enzyme, and Reactant; it imports no SciML package.

using DifferentiationInterface: AutoEnzyme, jacobian, prepare_jacobian
using Enzyme: Enzyme
using Reactant: Reactant, @jit

f(x) = x .^ 2

function di_jacobian(x)
    backend = AutoEnzyme()
    prep = prepare_jacobian(f, backend, x)
    return jacobian(f, prep, backend, x)
end

x = Reactant.to_rarray(Float32[1, 2])
@jit di_jacobian(x)

The call fails during tracing:

ERROR: LoadError: Scalar indexing is disallowed.
Invocation of setindex!(::TracedRArray, v, ::Union{Int, TracedRNumber{Int}}) resulted in scalar indexing of a GPU array.
...
 [16] #onehot##2
    @ Enzyme/src/sugar.jl:153
 [19] #chunkedonehot##0
    @ Enzyme/src/sugar.jl:424
 [25] create_shadows
    @ Enzyme/src/sugar.jl:436
 [28] prepare_jacobian_nokwarg
    @ DifferentiationInterface/ext/DifferentiationInterfaceEnzymeExt/forward_onearg.jl:241

The equivalent public Enzyme primitive works inside the same Reactant kernel:

function enzyme_jacobian(x)
    derivatives = Enzyme.autodiff(
        Enzyme.Forward,
        f,
        Enzyme.BatchDuplicated(x, Enzyme.onehot(x)),
    )
    return stack(values(only(derivatives)))
end

@jit enzyme_jacobian(x)
# Reactant.ConcretePJRTArray(Float32[2 0; 0 4])

Delegating the traced path to this primitive would also avoid DI's scalar-indexing basis construction for in-place functions. The higher-level Enzyme.jacobian convenience function cannot currently be used directly: its forward implementation calls tupstack, which has no traced-array method, while its reverse implementation fails during its augmented forward pass.

Expected behavior

prepare_jacobian and jacobian with the default AutoEnzyme() should work inside Reactant.@jit and return a Reactant array.

Versions

Julia Version 1.12.6
Commit 15346901f00 (2026-04-09 19:20 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 128 × AMD EPYC 7502 32-Core Processor
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, znver2)
  GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 128 virtual cores)

DifferentiationInterface v0.7.21 (main at f0fb136d)
Enzyme v0.13.199
Reactant v0.2.283

Contributor guide

Open the contributing guide

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.

Research direction

Run the supplied Julia reproducer with Reactant.@jit, then inspect DifferentiationInterface/ext/DifferentiationInterfaceEnzymeExt/forward_onearg.jl around line 241 and Enzyme/src/sugar.jl around the reported shadow-preparation calls. Compare the traced path with the working Enzyme.autodiff primitive and verify that prepare_jacobian and jacobian return a Reactant array without scalar indexing.

Written by the indexing model from the issue text.

Assessment

Tech stack
julia
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.