From 303d2934736e566e452fea570c1383ba9da20c06 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Tue, 17 Feb 2026 10:53:11 -0600 Subject: [PATCH 1/4] Robust options for Enzyme --- ext/ADNLPModelsEnzymeExt.jl | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/ext/ADNLPModelsEnzymeExt.jl b/ext/ADNLPModelsEnzymeExt.jl index 494280b5..e750d4d1 100644 --- a/ext/ADNLPModelsEnzymeExt.jl +++ b/ext/ADNLPModelsEnzymeExt.jl @@ -5,6 +5,10 @@ using ADNLPModels, NLPModels using SparseMatrixColorings using Enzyme +# Configure Enzyme for robustness +Enzyme.API.strictAliasing!(false) # handle Union types in Printf/@sprintf +Enzyme.API.looseTypeAnalysis!(true) # handle unresolved types in complex structs + function _gradient!(dx, f, x) Enzyme.make_zero!(dx) Enzyme.autodiff( @@ -70,12 +74,21 @@ end function ADNLPModels.gradient!(::ADNLPModels.EnzymeReverseADGradient, g, f, x) Enzyme.make_zero!(g) - Enzyme.autodiff(Enzyme.Reverse, Enzyme.Const(f), Enzyme.Active, Enzyme.Duplicated(x, g)) + Enzyme.autodiff( + Enzyme.set_runtime_activity(Enzyme.Reverse), + Enzyme.Const(f), + Enzyme.Active, + Enzyme.Duplicated(x, g), + ) return g end ADNLPModels.jacobian(::ADNLPModels.EnzymeReverseADJacobian, f, x) = - Enzyme.jacobian(Enzyme.Reverse, f, x) + Enzyme.jacobian( + Enzyme.set_runtime_activity(Enzyme.Reverse), + f, + x + ) function ADNLPModels.hessian(b::ADNLPModels.EnzymeReverseADHessian, f, x) T = eltype(x) @@ -96,7 +109,7 @@ function ADNLPModels.Jprod!(b::ADNLPModels.EnzymeReverseADJprod, Jv, c!, x, v, : copyto!(b.xbuf, x) copyto!(b.vbuf, v) Enzyme.autodiff( - Enzyme.Forward, + Enzyme.set_runtime_activity(Enzyme.Forward), Enzyme.Const(c!), Enzyme.Duplicated(b.cx, b.jvbuf), Enzyme.Duplicated(b.xbuf, b.vbuf), @@ -118,7 +131,7 @@ function ADNLPModels.Jtprod!(b::ADNLPModels.EnzymeReverseADJtprod, Jtv, c!, x, v copyto!(b.vbuf, v) Enzyme.make_zero!(b.jtvbuf) Enzyme.autodiff( - Enzyme.Reverse, + Enzyme.set_runtime_activity(Enzyme.Reverse), Enzyme.Const(_void_c!), Enzyme.Const(c!), Enzyme.Duplicated(b.cx, b.vbuf), @@ -261,7 +274,7 @@ function sparse_jac_coord!( # b.compressed_jacobian is just a vector Jv here # We don't use the vector mode Enzyme.autodiff( - Enzyme.Forward, + Enzyme.set_runtime_activity(Enzyme.Forward), Enzyme.Const(c!), Enzyme.Duplicated(b.cx, b.compressed_jacobian), Enzyme.Duplicated(b.xbuf, b.v), From c0a15ac0daa146e36f26dffbb75d5b8ea9a9ae46 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Wed, 18 Feb 2026 11:52:24 -0600 Subject: [PATCH 2/4] Fixes --- ext/ADNLPModelsEnzymeExt.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/ADNLPModelsEnzymeExt.jl b/ext/ADNLPModelsEnzymeExt.jl index e750d4d1..502157d9 100644 --- a/ext/ADNLPModelsEnzymeExt.jl +++ b/ext/ADNLPModelsEnzymeExt.jl @@ -5,15 +5,15 @@ using ADNLPModels, NLPModels using SparseMatrixColorings using Enzyme -# Configure Enzyme for robustness -Enzyme.API.strictAliasing!(false) # handle Union types in Printf/@sprintf -Enzyme.API.looseTypeAnalysis!(true) # handle unresolved types in complex structs +# Configure Enzyme for robustness. This should be in the user code. +# Enzyme.API.strictAliasing!(false) # handle Union types in Printf/@sprintf +# Enzyme.API.looseTypeAnalysis!(true) # handle unresolved types in complex structs function _gradient!(dx, f, x) Enzyme.make_zero!(dx) Enzyme.autodiff( Enzyme.set_runtime_activity(Enzyme.Reverse), - f, + Enzyme.Const(f), Enzyme.Active, Enzyme.Duplicated(x, dx), ) @@ -36,7 +36,7 @@ function _gradient!(dx, ℓ, x, y, obj_weight, cx) dcx = Enzyme.make_zero(cx) Enzyme.autodiff( Enzyme.set_runtime_activity(Enzyme.Reverse), - ℓ, + Enzyme.Const(ℓ), Enzyme.Active, Enzyme.Duplicated(x, dx), Enzyme.Const(y), From d2b8dd01ed9d6300adc2823274bc824ee5ef9c4f Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Wed, 18 Feb 2026 12:09:59 -0600 Subject: [PATCH 3/4] Use ForwardDiff over Enzyme Reverse for second-order --- ext/ADNLPModelsEnzymeExt.jl | 44 +++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/ext/ADNLPModelsEnzymeExt.jl b/ext/ADNLPModelsEnzymeExt.jl index 502157d9..af9b3b53 100644 --- a/ext/ADNLPModelsEnzymeExt.jl +++ b/ext/ADNLPModelsEnzymeExt.jl @@ -4,10 +4,7 @@ using SparseArrays using ADNLPModels, NLPModels using SparseMatrixColorings using Enzyme - -# Configure Enzyme for robustness. This should be in the user code. -# Enzyme.API.strictAliasing!(false) # handle Union types in Printf/@sprintf -# Enzyme.API.looseTypeAnalysis!(true) # handle unresolved types in complex structs +using ForwardDiff function _gradient!(dx, f, x) Enzyme.make_zero!(dx) @@ -20,14 +17,24 @@ function _gradient!(dx, f, x) return nothing end +# Helpers for ForwardDiff-over-Enzyme-reverse HVP. +# Enzyme reverse-differentiates these; the Active return is a Float64 scalar. +_dual_objective(f, x_d) = ForwardDiff.partials(f(x_d), 1) +_dual_lagrangian(ℓ, x_d, y, obj_weight, cx_d) = ForwardDiff.partials(ℓ(x_d, y, obj_weight, cx_d), 1) + function _hvp!(res, f, x, v) + x_d = ForwardDiff.Dual{Nothing}.(x, v) + dx_d = zero.(x_d) + Enzyme.autodiff( - Enzyme.set_runtime_activity(Enzyme.Forward), - _gradient!, - res, + Enzyme.set_runtime_activity(Enzyme.Reverse), + Enzyme.Const(_dual_objective), + Enzyme.Active, Enzyme.Const(f), - Enzyme.Duplicated(x, v), + Enzyme.Duplicated(x_d, dx_d), ) + + res.dval .= ForwardDiff.value.(dx_d) return nothing end @@ -47,17 +54,26 @@ function _gradient!(dx, ℓ, x, y, obj_weight, cx) end function _hvp!(res, ℓ, x, v, y, obj_weight, cx) - dcx = Enzyme.make_zero(cx) + D = ForwardDiff.Dual{Nothing, eltype(x), 1} + + x_d = ForwardDiff.Dual{Nothing}.(x, v) + dx_d = zero.(x_d) + + cx_d = fill!(similar(cx, D), zero(D)) + dcx_d = fill!(similar(cx, D), zero(D)) + Enzyme.autodiff( - Enzyme.set_runtime_activity(Enzyme.Forward), - _gradient!, - res, + Enzyme.set_runtime_activity(Enzyme.Reverse), + Enzyme.Const(_dual_lagrangian), + Enzyme.Active, Enzyme.Const(ℓ), - Enzyme.Duplicated(x, v), + Enzyme.Duplicated(x_d, dx_d), Enzyme.Const(y), Enzyme.Const(obj_weight), - Enzyme.Duplicated(cx, dcx), + Enzyme.Duplicated(cx_d, dcx_d), ) + + res.dval .= ForwardDiff.value.(dx_d) return nothing end From 5836266bda75904033679f5b97b6279304f20786 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 24 Feb 2026 20:36:05 -0600 Subject: [PATCH 4/4] Update sparse_hessian.jl --- test/sparse_hessian.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/sparse_hessian.jl b/test/sparse_hessian.jl index 5e76f5c3..c44f1a83 100644 --- a/test/sparse_hessian.jl +++ b/test/sparse_hessian.jl @@ -3,6 +3,24 @@ function sparse_hessian(backend, info, kw) Float32, Float64, ) + # When using ForwardDiff.Dual{Nothing,Float32,1} inside Enzyme reverse mode, + # LLVM may scalar-replace the Dual and pack its (value, partial) fields into + # a single i64. This packing is implemented using integer bit operations such + # as `shl`, `zext`, and `or disjoint`. + # + # Enzyme reverse mode does not support differentiating these low-level + # integer bitwise operations. As a result, it throws: + # + # "cannot handle unknown binary operator: or disjoint i64" + # + # This issue typically appears with Float32 (8-byte Dual → packed into i64), + # but not with Float64 (16-byte Dual → kept as two f64 values). + # + # In short: this is not a numerical precision issue, but a limitation of + # Enzyme when differentiating LLVM bit-manipulation code generated for + # packed Dual numbers + (backend == ADNLPModels.SparseEnzymeADHessian) && (T == Float32) && continue + c!(cx, x) = begin cx[1] = x[1] - 1 cx[2] = 10 * (x[2] - x[1]^2)