ITADN

Segfault in Enzyme.gradient with ComponentArray active input + non-const captured global (1.10 & 1.11)

#3114OpenChrisRackauckas-Claude 创建于 2026-05-20
## Summary `Enzyme.gradient(Reverse, ...)` segfaults (SIGSEGV inside Julia's GC mark loop) when the closure being differentiated **captures a non-`const` global** *and* the active input is a **`ComponentArray`**. Making the captured global `const`, replacing the `ComponentArray` with a plain `Vector`, or — when called via DI — passing the global as a `DI.Constant`, each independently avoids the crash. The fault is hit during Enzyme's compile pipeline, before any AD actually happens. Reproduces on both Julia 1.10.11 and 1.11.9 with `Enzyme v0.13.147`, so this is not 1.11-specific. Likely related to #2396 (Lux + ComponentArrays segfault that you noted reproduces on 1.11 but not 1.10 — this one reproduces on both). Surfaced via JuliaDiff/DifferentiationInterface.jl#1006. ## MWE Four packages, ~10 lines: ```julia using OrdinaryDiffEq, SciMLSensitivity, Enzyme, ComponentArrays function lorenz!(du, u, p, t) du[1] = p[1] * (u[2] - u[1]) du[2] = u[1] * (p[2] - u[3]) - u[2] du[3] = u[1] * u[2] - p[3] * u[3] return nothing end u0 = ComponentVector(x=1.0, y=0.0, z=0.0) p_init = ComponentArray(sigma=5.0, rho=15.0, beta=1.0) prob = ODEProblem(lorenz!, u0, (0.0, 10.0), p_init) # <-- non-const global loss(p) = sum(abs2, Array(solve(prob, Tsit5(); p, saveat=0.1))) Enzyme.gradient(Reverse, loss, p_init) # SIGSEGV ``` **Change exactly one keyword** — `prob = ...` → `const prob = ...` — and the same script runs cleanly and returns ``` g = ((sigma = -153.74620246430592, rho = 3059.416632297161, beta = 1013.443993470765),) ``` ## What changes make the crash go away (any one suffices) | Change | Outcome | |---|---| | `prob = ODEProblem(...)` → `const prob = ODEProblem(...)` | ✅ works | | `p_init` → `Vector(p_init)` (plain `Vector{Float64}`) | ✅ works | | Refactor `loss(p, prob, data)` and call via `DI.gradient(loss, backend, p_init, DI.Constant(prob), ...)` | ✅ works | | `AutoEnzyme(; mode = Enzyme.set_runtime_activity(Enzyme.Reverse))` alone, still closing over non-`const` globals | ❌ still segfaults | So `set_runtime_activity` is not enough; the trigger is genuinely the interaction between (a) capturing a non-`const` binding via the closure and (b) the active input being a `ComponentArray`. ## Stack traces (top frames) ### Julia 1.11.9 ``` [3725614] signal 11 (1): Segmentation fault in expression starting at .../slim3.jl:24 gc_mark_obj16 at src/gc.c:2145 gc_mark_outrefs at src/gc.c:2895 [inlined] gc_mark_loop_serial_ at src/gc.c:2938 gc_mark_loop_serial at src/gc.c:2961 gc_mark_loop at src/gc.c:3143 [inlined] _jl_gc_collect at src/gc.c:3532 ijl_gc_collect at src/gc.c:3893 maybe_collect at src/gc.c:926 [inlined] jl_gc_pool_alloc_inner at src/gc.c:1319 jl_gc_alloc_ at src/julia_internal.h:523 [inlined] _new_genericmemory_ at src/genericmemory.c:56 [inlined] jl_alloc_genericmemory at src/genericmemory.c:99 ijl_array_grow_end at src/array.c:229 ijl_module_names at src/module.c:1002 unsorted_names at ./reflection.jl:96 [inlined] make_typealias at ./show.jl:624 show_typealias at ./show.jl:805 _show_type at ./show.jl:970 show at ./show.jl:965 print at ./strings/io.jl:35 print_to_string at ./strings/io.jl:148 string at ./strings/io.jl:189 [inlined] compile_unhooked at .../Enzyme/src/compiler.jl:5669 ... thunk_generator at .../Enzyme/src/compiler.jl:7181 ... loss at .../slim3.jl:21 [inlined] augmented_julia_loss_4595_inner_1wrap at .../slim3.jl:0 gradient at .../Enzyme/src/sugar.jl:274 ``` (The original `DI`-based version of this MWE hits the same fault but a few frames earlier — in `sroa_pass! → IncrementalCompact → primal_return_type_generator` — same root pattern: GC mark loop during an Enzyme-compile-pipeline allocation.) ### Julia 1.10.11 ``` [3944234] signal (11.1): Segmentation fault in expression starting at .../slim3.jl:24 gc_mark_outrefs at src/gc.c:2510 [inlined] gc_mark_loop_serial_ at src/gc.c:2690 gc_mark_loop_serial at src/gc.c:2713 gc_mark_loop at src/gc.c:2894 [inlined] _jl_gc_collect at src/gc.c:3227 ijl_gc_collect at src/gc.c:3524 maybe_collect at src/gc.c:937 [inlined] jl_gc_pool_alloc_inner at src/gc.c:1293 [inlined] ijl_gc_pool_alloc at src/gc.c:1341 jfptr_GetElementPtrInst_7809 at .../LLVM/...so Instruction at .../LLVM/src/core/instructions.jl:38 check_ir! at .../Enzyme/src/compiler/validation.jl:243 check_ir! at .../Enzyme/src/compiler/validation.jl:216 check_ir at .../Enzyme/src/compiler/validation.jl:185 [inlined] compile_unhooked at .../Enzyme/src/compiler.jl:5298 ... thunk_generator at .../Enzyme/src/compiler.jl:7181 ... gradient at .../Enzyme/src/sugar.jl:274 ``` ## Environment ``` Julia 1.11.9 (and 1.10.11) – Linux x86_64 Enzyme v0.13.147 ComponentArrays v0.15.39 OrdinaryDiffEq v7.0.0 SciMLSensitivity v7.109.0 ``` I can run further experiments if a more specific narrowing would help (e.g. trying to drop OrdinaryDiffEq entirely, or pinning specific Enzyme versions to bisect).
1 条评论