Skip to content

Segfault (signal 11, GC heap corruption): reverse-mode over Lux dense RHS repeated in an in-place ODE adjoint (Julia 1.10/1.12, Enzyme 0.13.171) #3260

Description

@ChrisRackauckas-Claude

Summary

Reverse-mode Enzyme.autodiff over an in-place ODE RHS that calls a Lux.Chain(Dense(2,50,tanh), Dense(50,2)) (i.e. reverse over LuxLib's fused_dense/dense layers), invoked repeatedly inside a SciMLSensitivity adjoint solve (BacksolveAdjoint/QuadratureAdjoint, EnzymeVJP), segfaults with signal 11 (GC heap corruption) on Julia 1.10 and 1.12 (1.11 is fine).

Versions

Julia 1.10 / 1.12, Enzyme v0.13.171, Lux v1.31.4, LuxLib, ComponentArrays. (From SciML/SciMLSensitivity.jl CI — Core5.)

CI crash signatures (signal 11)

  • Julia 1.12, BacksolveAdjoint(): crash in gc_mark_outrefs (GC) during an Enzyme CombinedAdjointThunk for the Lux RHS, called repeatedly from _vecjacobian! per reverse step.
  • Julia 1.10, QuadratureAdjoint(): Enzyme PrimalErrorThunk in vec_pjac!.

Reproduction status — NONDETERMINISTIC

The corruption is cumulative across many in-place reverse passes; a single reverse pass is always fine. I could not reproduce a deterministic local segfault (100 Zygote.gradient iterations × 4 sensealgs survived on both 1.10.11 and 1.12.6), but it crashes reliably on the SciMLSensitivity CI runners (heavier OptimizationProblem+Adam 200-iter training path, and repeated hybrid-callback adjoints). This is the classic "GC heap corruption that depends on allocation timing/pressure" signature.

Repro scaffold (exercises the exact path; segfault is environment-dependent)

using SciMLSensitivity, OrdinaryDiffEq, Lux, ComponentArrays, Zygote, Random
dudt2 = Chain(Dense(2,50,tanh), Dense(50,2))
ps, st = Lux.setup(Xoshiro(0), dudt2); psc = ComponentArray{Float32}(ps)
dudt(du,u,p,t) = (du .= first(dudt2(u,p,st)); nothing)
z0 = Float32[2.0,0.0]; tspan=(0.0f0,10.0f0); ts=range(tspan...,length=101)
prob = ODEProblem(dudt, z0, tspan)
loss(p) = sum(abs2, Array(solve(remake(prob;p=p,u0=z0), Tsit5();
            saveat=ts, sensealg=BacksolveAdjoint(autojacvec=SciMLSensitivity.EnzymeVJP()))))
for i in 1:200; Zygote.gradient(loss, psc); end   # CI: signal 11 on 1.10/1.12

The differentiated unit is Enzyme reverse over the Lux dense layers in an in-place RHS, run once per reverse-integration step. Happy to provide the exact failing SciMLSensitivity tests (test/Core5/HybridNODE.jl::test_hybridNODE2/3) and a Manifest. Filing primarily so there's an upstream tracking point; I realize the nondeterminism makes this hard — suggestions for getting a deterministic repro (e.g. forcing GC stress) welcome.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions