Julia 1.11: forward-over-reverse dynamic loop fails in `nodecayed_phis!` GC analysis
- Dominant language
- Julia
- Stars
- 586
- Forks
- 108
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 44
Description
## Summary
On Julia 1.11, forward-over-reverse differentiation of a small Enzyme-only dynamic loop fails in `nodecayed_phis!` while analyzing garbage-collection behavior. The inner runtime-activity reverse gradient succeeds; only the outer runtime-activity forward pass fails.
With verbose errors enabled, the key diagnostic is:
```text
Could not analyze garbage collection behavior of
inst: %85 = phi {} addrspace(10)* addrspace(13)* ...
...
EnzymeInternalError: Enzyme compilation failed due to an internal error
getparent(...) @ Enzyme.Compiler/src/llvm/transforms.jl:1228
nodecayed_phis! @ Enzyme.Compiler/src/llvm/transforms.jl:1236
optimize! @ Enzyme.Compiler/src/compiler/optimize.jl:223
```
The same file succeeds on Julia 1.10 and 1.12. This is a minimized version of the dynamic mock-solver reduction suggested in #3299 while isolating the still-failing direct-through-solver forward-over-reverse path.
## Reproducer
```julia
import Enzyme
Enzyme.Compiler.VERBOSE_ERRORS[] = true
struct Problem
f::Any
jac::Any
u0::Vector{Float64}
p::Vector{Float64}
end
@noinline function solve(prob::Problem)
u = copy(prob.u0)
p = prob.p
f = prob.f
jac = prob.jac
for _ in 1:2
k1 = f(u[1], p[1])
u[1] += 0.1 * k1
jval = if jac !== nothing
Base.invokelatest(jac, u[1], p[1])::Float64
else
0.0
end
u[1] += 0.01 * jval
end
return u[1]
end
function loss(p)
f = (x, pval) -> -pval * x
y = solve(Problem(f, nothing, [1.0], p))
return y * y
end
const RA_R = Enzyme.set_runtime_activity(Enzyme.Reverse)
const RA_F = Enzyme.set_runtime_activity(Enzyme.Forward)
@noinline function grad(p)
dp = zero(p)
Enzyme.autodiff(RA_R, Enzyme.Const(loss), Enzyme.Active, Enzyme.Duplicated(p, dp))
return dp
end
@show grad([0.5])
@show Enzyme.autodiff(
RA_F,
Enzyme.Const(p -> sum(grad(p))),
Enzyme.Duplicated([0.5], [1.0]),
)
```
## Observed on Julia 1.11.9
The inner reverse succeeds:
```text
grad([0.5]) = [-0.34295]
```
The outer forward-over-reverse call then fails:
```text
ERROR: LoadError: EnzymeInternalError: Enzyme compilation failed due to an internal error.
getparent(...) @ Enzyme.Compiler/src/llvm/transforms.jl:1228
nodecayed_phis! @ Enzyme.Compiler/src/llvm/transforms.jl:1236
optimize! @ Enzyme.Compiler/src/compiler/optimize.jl:223
```
## Version matrix
Tested with Enzyme v0.13.182 / Enzyme_jll v0.0.282+0:
| Julia | Result |
|---|---|
| 1.10.11 | succeeds, outer result `(0.10830000000000001,)` |
| 1.11.9 | fails in `nodecayed_phis!` as above |
| 1.12.6 | succeeds, outer result `(0.10830000000000001,)` |
The failure also reproduces on Enzyme `main` at `23ff63088cd6532ea0d58bcdc279573bec6e2731` with Julia 1.11.9.
## Ablations
- One loop iteration succeeds on Julia 1.11; two are sufficient to fail.
- Removing the inactive `jac::Any` branch succeeds.
- Removing the `u0::Vector{Float64}` field and allocating `u` directly in `solve` succeeds.
- Julia 1.10 and 1.12 succeed without code changes.
## Context
The full `SensitivityADPassThrough` reproducer in #3299 still fails on the current registered stack (Enzyme 0.13.182, DiffEqBase 7.6.1, OrdinaryDiffEq 7.1.2, Julia 1.11.9) with the broken-module LLVM verifier error after its inner reverse gradient succeeds. The Enzyme-only reduction above exposes this smaller Julia-1.11-specific GC-analysis failure without SciML dependencies.
Contributor guide
Assessment
This issue has not been assessed yet.