Skip to content

Type stability of gradient and jacobian #781

@KnutAM

Description

@KnutAM

I couldn't find any issues on this, but noticed that the return type of gradient and jacobian is not type stable without passing the config (or using a mutating function in the jacobian case).

Took some time to track this down today as it was nested deep, of course just pre-allocating the output and using the jacobian! function makes it fine again. Has there been some discussions on how to make this stable?

julia> @code_warntype ForwardDiff.gradient(sum, [1.])
MethodInstance for ForwardDiff.gradient(::typeof(sum), ::Vector{Float64})
  from gradient(f, x::AbstractArray) @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/gradient.jl:16
Arguments
  #self#::Core.Const(ForwardDiff.gradient)
  f::Core.Const(sum)
  x::Vector{Float64}
Body::Any
1%1 = ForwardDiff.GradientConfig(f, x)::ForwardDiff.GradientConfig{ForwardDiff.Tag{typeof(sum), Float64}, Float64, _A, Array{ForwardDiff.Dual{ForwardDiff.Tag{typeof(sum), Float64}, Float64, _A1}, 1}} where {_A, _A1}
│   %2 = (#self#)(f, x, %1)::Any
└──      return %2

julia> @code_warntype ForwardDiff.jacobian(identity, [1.0])
MethodInstance for ForwardDiff.jacobian(::typeof(identity), ::Vector{Float64})
  from jacobian(f, x::AbstractArray) @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/jacobian.jl:18
Arguments
  #self#::Core.Const(ForwardDiff.jacobian)
  f::Core.Const(identity)
  x::Vector{Float64}
Body::Union{Matrix{Any}, Matrix{Float64}, Matrix{E} where E<:(ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64})}
1%1 = ForwardDiff.JacobianConfig(f, x)::ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(identity), Float64}, Float64, _A, Array{ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64, _A1}, 1}} where {_A, _A1}
│   %2 = (#self#)(f, x, %1)::Union{Matrix{Any}, Matrix{Float64}, Matrix{E} where E<:(ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64})}
└──      return %2

Either passing cfg = JacobianConfig(...) or using a mutating residual in the jacobian case, solves the problem, i.e.

julia> @code_warntype ForwardDiff.jacobian((y,x) -> (y .= x), rand(1), [1.0])
MethodInstance for ForwardDiff.jacobian(::var"#3#4", ::Vector{Float64}, ::Vector{Float64})
  from jacobian(f!, y::AbstractArray, x::AbstractArray) @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/jacobian.jl:35
Arguments
  #self#::Core.Const(ForwardDiff.jacobian)
  f!::Core.Const(var"#3#4"())
  y::Vector{Float64}
  x::Vector{Float64}
Body::Matrix{Float64}
1%1 = ForwardDiff.JacobianConfig(f!, y, x)::ForwardDiff.JacobianConfig{ForwardDiff.Tag{var"#3#4", Float64}, Float64, _A, <:Tuple{Array{ForwardDiff.Dual{ForwardDiff.Tag{var"#3#4", Float64}, Float64, _A}, 1} where _A, Array{ForwardDiff.Dual{ForwardDiff.Tag{var"#3#4", Float64}, Float64, _A}, 1} where _A}} where _A
│   %2 = (#self#)(f!, y, x, %1)::Matrix{Float64}
└──      return %2

The only way I found to solve this, is limiting (at least in these cases when the cfg is automatically created leading to a type instability) the chunk size, e.g.

const ChunkUnions3 = Union{(ForwardDiff.Chunk{i} for i in (2, 4, 12))...}
function ForwardDiff.Chunk(input_length::Integer, threshold::Integer = ForwardDiff.DEFAULT_CHUNK_THRESHOLD)
    N = ForwardDiff.pickchunksize(input_length, threshold)
    chunk = Base.@nif 12 d->(N == d) d->(Chunk{d}()) d->(Chunk{N}())
    return chunk::ChunkUnions3
end

julia> @code_warntype ForwardDiff.jacobian(identity, [1.0])
MethodInstance for ForwardDiff.jacobian(::typeof(identity), ::Vector{Float64})
  from jacobian(f::F, x::AbstractArray) where F @ ForwardDiff ~/.julia/packages/ForwardDiff/X74OO/src/jacobian.jl:18
Static Parameters
  F = typeof(identity)
Arguments
  #self#::Core.Const(ForwardDiff.jacobian)
  f::Core.Const(identity)
  x::Vector{Float64}
Body::Matrix{Float64}
1%1 = ForwardDiff.JacobianConfig(f, x)::Union{ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 2, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 2}}}, ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 4, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 4}}}, ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 12, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(identity), Float64}, Float64, 12}}}}
│   %2 = (#self#)(f, x, %1)::Matrix{Float64}
└──      return %2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions