From a515f5a30f8b1c81e9f04a317464cd96bfd1ebd4 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 01:42:38 -0400 Subject: [PATCH 1/4] Add `SmallTag` type This is an alternative to `Tag` that provides largely the same functionality, but carries around only the hash of the function / array types instead of the full types themselves. This can make these types much less bulky to print and easier to visually scan for. --- src/config.jl | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/config.jl b/src/config.jl index d561b4fd..11442447 100644 --- a/src/config.jl +++ b/src/config.jl @@ -20,11 +20,46 @@ end Tag(::Nothing, ::Type{V}) where {V} = nothing - @inline function ≺(::Type{Tag{F1,V1}}, ::Type{Tag{F2,V2}}) where {F1,V1,F2,V2} tagcount(Tag{F1,V1}) < tagcount(Tag{F2,V2}) end +# SmallTag is similar to a Tag, but carries just a small UInt64 hash, instead +# of the full type, which makes stacktraces / types easier to read while still +# providing good resilience to perturbation confusion. +struct SmallTag{H} +end + +@generated function tagcount(::Type{SmallTag{H}}) where {H} + :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) +end + +function SmallTag(f::F, ::Type{V}) where {F,V} + H = if F <: Tuple + # no easy way to check Jacobian tag used with Hessians as multiple functions may be used + # see checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT<:Tuple,VT,F,V} + nothing + else + hash(F) ⊻ hash(V) + end + tagcount(SmallTag{H}) # trigger generated function + SmallTag{H}() +end + +SmallTag(::Nothing, ::Type{V}) where {V} = nothing + +@inline function ≺(::Type{SmallTag{H1}}, ::Type{Tag{F2,V2}}) where {H1,F2,V2} + tagcount(SmallTag{H1}) < tagcount(Tag{F2,V2}) +end + +@inline function ≺(::Type{Tag{F1,V1}}, ::Type{SmallTag{H2}}) where {F1,V1,H2} + tagcount(Tag{F1,V1}) < tagcount(SmallTag{H2}) +end + +@inline function ≺(::Type{SmallTag{H1}}, ::Type{SmallTag{H2}}) where {H1,H2} + tagcount(SmallTag{H1}) < tagcount(SmallTag{H2}) +end + struct InvalidTagException{E,O} <: Exception end @@ -36,13 +71,22 @@ checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT,VT,F,V} = checktag(::Type{Tag{F,V}}, f::F, x::AbstractArray{V}) where {F,V} = true +# SmallTag is a smaller tag, that only confirms the hash +function checktag(::Type{SmallTag{HT}}, f::F, x::AbstractArray{V}) where {HT,F,V} + H = hash(F) ⊻ hash(V) + if HT == H || HT === nothing + true + else + throw(InvalidTagException{SmallTag{H},SmallTag{HT}}()) + end +end + # no easy way to check Jacobian tag used with Hessians as multiple functions may be used checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT<:Tuple,VT,F,V} = true # custom tag: you're on your own. checktag(z, f, x) = true - ################## # AbstractConfig # ################## From 438fe5f149b918d466a643750285ba4ac5f7f780 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 01:45:36 -0400 Subject: [PATCH 2/4] Support `tag = :small` in `Config(...)` constructors This provides a convenient interface to ask for a SmallTag. --- src/config.jl | 150 ++++++++++++++++++++++++++++++++++--------- test/GradientTest.jl | 14 +++- test/HessianTest.jl | 8 ++- test/JacobianTest.jl | 19 ++++-- 4 files changed, 148 insertions(+), 43 deletions(-) diff --git a/src/config.jl b/src/config.jl index 11442447..5190a19d 100644 --- a/src/config.jl +++ b/src/config.jl @@ -99,6 +99,18 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N +function maketag(kind::Union{Symbol,Nothing}, f, X) + if kind === :default + return Tag(f, X) + elseif kind === :small + return SmallTag(f, X) + elseif kind === nothing + return nothing + else + throw(ArgumentError("tag may be :default, :small, or nothing")) + end +end + #################### # DerivativeConfig # #################### @@ -108,7 +120,7 @@ struct DerivativeConfig{T,D} <: AbstractConfig{1} end """ - ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real) + ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real; tag::Union{Symbol,Nothing} = :default) Return a `DerivativeConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input value `x`. @@ -121,12 +133,24 @@ If `f!` is `nothing` instead of the actual target function, then the returned in be used with any target function. However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. + This constructor does not store/modify `y` or `x`. """ +@inline function DerivativeConfig(f::F, + y::AbstractArray{Y}, + x::X; + tag::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} + # @inline ensures that, e.g., DerivativeConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, X) + return @noinline DerivativeConfig(f,y,x,T) +end + function DerivativeConfig(f::F, y::AbstractArray{Y}, x::X, - tag::T = Tag(f, X)) where {F,X<:Real,Y<:Real,T} + tag::T) where {F,X<:Real,Y<:Real,T} duals = similar(y, Dual{T,Y,1}) return DerivativeConfig{T,typeof(duals)}(duals) end @@ -144,7 +168,7 @@ struct GradientConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `GradientConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -152,16 +176,28 @@ vector `x`. The returned `GradientConfig` instance contains all the work buffers required by `ForwardDiff.gradient` and `ForwardDiff.gradient!`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function GradientConfig(f::F, + x::AbstractArray{V}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., GradientConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline GradientConfig(f,x,c,T) +end + function GradientConfig(f::F, x::AbstractArray{V}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, V)) where {F,V,N,T} + ::Chunk{N}, + ::T) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return GradientConfig{T,V,N,typeof(duals)}(seeds, duals) @@ -180,7 +216,7 @@ struct JacobianConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -189,23 +225,35 @@ The returned `JacobianConfig` instance contains all the work buffers required by `ForwardDiff.jacobian` and `ForwardDiff.jacobian!` when the target function takes the form `f(x)`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function JacobianConfig(f::F, + x::AbstractArray{V}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline JacobianConfig(f,x,c,T) +end + function JacobianConfig(f::F, x::AbstractArray{V}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, V)) where {F,V,N,T} + ::Chunk{N}, + ::T) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return JacobianConfig{T,V,N,typeof(duals)}(seeds, duals) end """ - ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input vector `x`. @@ -214,17 +262,30 @@ The returned `JacobianConfig` instance contains all the work buffers required by `ForwardDiff.jacobian` and `ForwardDiff.jacobian!` when the target function takes the form `f!(y, x)`. -If `f!` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f!` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `y` or `x`. """ +@inline function JacobianConfig(f::F, + y::AbstractArray{Y}, + x::AbstractArray{X}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,Y,X,N} + # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, X) + return @noinline JacobianConfig(f,y,x,c,T) +end + function JacobianConfig(f::F, y::AbstractArray{Y}, x::AbstractArray{X}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, X)) where {F,Y,X,N,T} + ::Chunk{N}, + ::T) where {F,Y,X,N,T} seeds = construct_seeds(Partials{N,X}) yduals = similar(y, Dual{T,Y,N}) xduals = similar(x, Dual{T,X,N}) @@ -245,7 +306,7 @@ struct HessianConfig{T,V,N,DG,DJ} <: AbstractConfig{N} end """ - ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -256,23 +317,35 @@ configured for the case where the `result` argument is an `AbstractArray`. If it is a `DiffResult`, the `HessianConfig` should instead be constructed via `ForwardDiff.HessianConfig(f, result, x, chunk)`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function HessianConfig(f::F, + x::AbstractArray{V}, + chunk::Chunk = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, x, chunk, T) +end + function HessianConfig(f::F, x::AbstractArray{V}, - chunk::Chunk = Chunk(x), - tag = Tag(f, V)) where {F,V} + chunk::Chunk, + tag) where {F,V} jacobian_config = JacobianConfig(f, x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals, chunk, tag) return HessianConfig(jacobian_config, gradient_config) end """ - ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f`, types/storage in `result`, and type/shape of the input vector `x`. @@ -280,17 +353,30 @@ type/shape of the input vector `x`. The returned `HessianConfig` instance contains all the work buffers required by `ForwardDiff.hessian!` for the case where the `result` argument is an `DiffResult`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function HessianConfig(f::F, + result::DiffResult, + x::AbstractArray{V}, + chunk::Chunk = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, result, x, chunk, T) +end + function HessianConfig(f::F, result::DiffResult, x::AbstractArray{V}, - chunk::Chunk = Chunk(x), - tag = Tag(f, V)) where {F,V} + chunk::Chunk, + tag) where {F,V} jacobian_config = JacobianConfig((f,gradient), DiffResults.gradient(result), x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals[2], chunk, tag) return HessianConfig(jacobian_config, gradient_config) diff --git a/test/GradientTest.jl b/test/GradientTest.jl index 4f46c167..d9c67a46 100644 --- a/test/GradientTest.jl +++ b/test/GradientTest.jl @@ -6,7 +6,7 @@ import NaNMath using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, Tag +using ForwardDiff: Dual, maketag using StaticArrays using DiffTests @@ -21,7 +21,8 @@ x = [0.1, 0.2, 0.3] v = f(x) g = [-9.4, 15.6, 52.0] -@testset "Rosenbrock, chunk size = $c and tag = $(repr(tag))" for c in (1, 2, 3), tag in (nothing, Tag(f, eltype(x))) +@testset "Rosenbrock, chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in (1, 2, 3), tag in (nothing, :default, :small) + tag = maketag(tag, f, eltype(x)) cfg = ForwardDiff.GradientConfig(f, x, ForwardDiff.Chunk{c}(), tag) @test eltype(cfg) == Dual{typeof(tag), eltype(x), c} @@ -60,7 +61,8 @@ cfgx = ForwardDiff.GradientConfig(sin, x) v = f(X) g = ForwardDiff.gradient(f, X) @test isapprox(g, Calculus.gradient(f, X), atol=FINITEDIFF_ERROR) - @testset "... with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f, eltype(x))) + @testset "... with chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, f, eltype(x)) cfg = ForwardDiff.GradientConfig(f, X, ForwardDiff.Chunk{c}(), tag) out = ForwardDiff.gradient(f, X, cfg) @@ -140,6 +142,12 @@ end # make sure this is not a source of type instability @inferred ForwardDiff.GradientConfig(f, sx) + if VERSION ≥ v"1.11" + # make sure that `GradientConfig(...; tag = compile-time-constant)` also + # infers well (requires that Base.hash(::Type) is foldable, which is true + # in Julia ≥ 1.11) + @inferred ((f, sx)->ForwardDiff.GradientConfig(f, sx; tag=:small))(f, sx) + end end @testset "exponential function at base zero" begin diff --git a/test/HessianTest.jl b/test/HessianTest.jl index 4c667e5e..68b6d103 100644 --- a/test/HessianTest.jl +++ b/test/HessianTest.jl @@ -5,7 +5,7 @@ import Calculus using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, Tag +using ForwardDiff: Dual, maketag using StaticArrays using DiffTests @@ -23,7 +23,8 @@ h = [-66.0 -40.0 0.0; -40.0 130.0 -80.0; 0.0 -80.0 200.0] -@testset "running hardcoded test with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) +@testset "running hardcoded test with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) cfg = ForwardDiff.HessianConfig(f, x, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(x), x, ForwardDiff.Chunk{c}(), tag) @@ -68,7 +69,8 @@ for f in DiffTests.VECTOR_TO_NUMBER_FUNCS h = ForwardDiff.hessian(f, X) # finite difference approximation error is really bad for Hessians... @test isapprox(h, Calculus.hessian(f, X), atol=0.02) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) + @testset "$f with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) cfg = ForwardDiff.HessianConfig(f, X, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(X), X, ForwardDiff.Chunk{c}(), tag) diff --git a/test/JacobianTest.jl b/test/JacobianTest.jl index 1e52f7fa..5b319af2 100644 --- a/test/JacobianTest.jl +++ b/test/JacobianTest.jl @@ -4,7 +4,7 @@ import Calculus using Test using ForwardDiff -using ForwardDiff: Dual, Tag, JacobianConfig +using ForwardDiff: Dual, Tag, SmallTag, JacobianConfig, maketag using StaticArrays using DiffTests using LinearAlgebra @@ -31,8 +31,8 @@ j = [0.8242369704835132 0.4121184852417566 -10.933563142616123 0.169076696546684 0.084538348273342 -2.299173530851733 0.0 0.0 1.0] -for c in (1, 2, 3), tags in ((nothing, nothing), - (Tag(f, eltype(x)), Tag(f!, eltype(x)))) +for c in (1, 2, 3), tag in (nothing, :small, :default) + tags = (maketag(tag, f, eltype(x)), maketag(tag, f!, eltype(x))) println(" ...running hardcoded test with chunk size = $c and tag = $(repr(tags))") cfg = JacobianConfig(f, x, ForwardDiff.Chunk{c}(), tags[1]) ycfg = JacobianConfig(f!, fill(0.0, 4), x, ForwardDiff.Chunk{c}(), tags[2]) @@ -103,9 +103,11 @@ for f in DiffTests.ARRAY_TO_ARRAY_FUNCS v = f(X) j = ForwardDiff.jacobian(f, X) @test isapprox(j, Calculus.jacobian(x -> vec(f(x)), X, :forward), atol=1.3FINITEDIFF_ERROR) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag) + @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag, SmallTag) if tag == Tag tag = Tag(f, eltype(X)) + elseif tag == SmallTag + tag = SmallTag(f, eltype(X)) end cfg = JacobianConfig(f, X, ForwardDiff.Chunk{c}(), tag) @@ -128,7 +130,8 @@ for f! in DiffTests.INPLACE_ARRAY_TO_ARRAY_FUNCS f!(v, X) j = ForwardDiff.jacobian(f!, fill!(similar(Y), 0.0), X) @test isapprox(j, Calculus.jacobian(x -> (y = fill!(similar(Y), 0.0); f!(y, x); vec(y)), X, :forward), atol=FINITEDIFF_ERROR) - @testset "$(f!) with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f!, eltype(X))) + @testset "$(f!) with chunk size = $c and tag = $(repr(maketag(tag, f!, eltype(X))))" for c in CHUNK_SIZES, tag in (nothing, :small, :default) + tag = maketag(tag, f!, eltype(X)) ycfg = JacobianConfig(f!, fill!(similar(Y), 0.0), X, ForwardDiff.Chunk{c}(), tag) y = fill!(similar(Y), 0.0) @@ -225,6 +228,12 @@ for T in (StaticArrays.SArray, StaticArrays.MArray) # make sure this is not a source of type instability @inferred ForwardDiff.JacobianConfig(f, sx) + if VERSION ≥ v"1.11" + # make sure that `JacobianConfig(...; tag = compile-time-constant)` also + # infers well (requires that Base.hash(::Type) is foldable, which is true + # in Julia ≥ 1.11) + @inferred ((f, sx)->ForwardDiff.JacobianConfig(f, sx; tag=:small))(f, sx) + end end ######### From 73e8b145dd6a2914bee69feb08e4af65fee0ab45 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 03:08:11 -0400 Subject: [PATCH 3/4] Mark allocation tests as fixed on 1.10 Latest patch release seems to have tightened this up. --- test/AllocationsTest.jl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/AllocationsTest.jl b/test/AllocationsTest.jl index ad1832d0..9b20fb60 100644 --- a/test/AllocationsTest.jl +++ b/test/AllocationsTest.jl @@ -24,20 +24,12 @@ convert_test_574() = convert(ForwardDiff.Dual{Nothing,ForwardDiff.Dual{Nothing,F index = 1 alloc = @allocated ForwardDiff.seed!(duals, x, index, seeds) alloc = @allocated ForwardDiff.seed!(duals, x, index, seeds) - if VERSION < v"1.9" || VERSION >= v"1.11" - @test alloc == 0 - else - @test_broken alloc == 0 - end + @test alloc == 0 index = 1 alloc = @allocated ForwardDiff.seed!(duals, x, index, seed) alloc = @allocated ForwardDiff.seed!(duals, x, index, seed) - if VERSION < v"1.9" || VERSION >= v"1.11" - @test alloc == 0 - else - @test_broken alloc == 0 - end + @test alloc == 0 alloc = @allocated convert_test_574() alloc = @allocated convert_test_574() From d88bf58768e127e9a97a642deadbcd45163662e1 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 09:35:40 -0400 Subject: [PATCH 4/4] Fix pre-compilation on Julia 1.6 This old version of Julia doesn't have call-site `@inline` / `@noinline` so version-guard against this trick for now. --- src/config.jl | 54 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/config.jl b/src/config.jl index 5190a19d..4ec3c50f 100644 --- a/src/config.jl +++ b/src/config.jl @@ -143,8 +143,13 @@ This constructor does not store/modify `y` or `x`. x::X; tag::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} # @inline ensures that, e.g., DerivativeConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, X) - return @noinline DerivativeConfig(f,y,x,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, X) + return @noinline DerivativeConfig(f,y,x,T) + else + T = maketag(tag, f, X) + return DerivativeConfig(f,y,x,T) + end end function DerivativeConfig(f::F, @@ -190,8 +195,13 @@ This constructor does not store/modify `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V,N} # @inline ensures that, e.g., GradientConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline GradientConfig(f,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline GradientConfig(f,x,c,T) + else + T = maketag(tag, f, V) + return GradientConfig(f,x,c,T) + end end function GradientConfig(f::F, @@ -239,8 +249,13 @@ This constructor does not store/modify `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V,N} # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline JacobianConfig(f,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline JacobianConfig(f,x,c,T) + else + T = maketag(tag, f, V) + return JacobianConfig(f,x,c,T) + end end function JacobianConfig(f::F, @@ -277,8 +292,13 @@ This constructor does not store/modify `y` or `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,Y,X,N} # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, X) - return @noinline JacobianConfig(f,y,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, X) + return @noinline JacobianConfig(f,y,x,c,T) + else + T = maketag(tag, f, X) + return JacobianConfig(f,y,x,c,T) + end end function JacobianConfig(f::F, @@ -331,8 +351,13 @@ This constructor does not store/modify `x`. chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V} # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline HessianConfig(f, x, chunk, T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, x, chunk, T) + else + T = maketag(tag, f, V) + return HessianConfig(f, x, chunk, T) + end end function HessianConfig(f::F, @@ -368,8 +393,13 @@ This constructor does not store/modify `x`. chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V} # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline HessianConfig(f, result, x, chunk, T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, result, x, chunk, T) + else + T = maketag(tag, f, V) + return HessianConfig(f, result, x, chunk, T) + end end function HessianConfig(f::F,