Skip to content

@grad_from_chainrules macro fails when using multi-output functions #221

Open

Description

@ThummeTo

Dear team,

first: Thanks for developing this nice package :-)

I think there is an error with the macro @grad_from_chainrules when using it on multi-output functions (for example a function that outputs a tuple of two vectors). Note, that gradient/jacobian determination is not part of the current Github-tests, only the rrules are evaluated directly, but no gradient/jacobian is built for testing ReverseDiff with the corresponding rrule. However this works fine for single-output functions together with ReverseDiff.gradient.

See the following MWE:

using ForwardDiff, Zygote, ReverseDiff, ChainRulesCore

# SINGLE OUTPUT FUNCTION 

f(x) = sum(4x .+ 1)

function ChainRulesCore.rrule(::typeof(f), x)
    r = f(x)
    function back(d)
        return ChainRulesCore.NoTangent(), fill(3, size(x))
    end
    return r, back
end

ReverseDiff.@grad_from_chainrules f(x::AbstractVector{<:ReverseDiff.TrackedReal})

seed = rand(3)

# Everything ok, ForwardDiff computes the correct derivatives (no frule defined),
# ReverseDiff and Zygote use the new rrule as to expect
ForwardDiff.gradient(f, seed)
Zygote.gradient(f, seed)[1]
ReverseDiff.gradient(f, seed)

# MULTI OUTPUT FUNCTION 

f_multi(x, y) = (4x .+ 1, 3x .+ 1 .+ y)

function ChainRulesCore.rrule(::typeof(f_multi), x, y)
    r = f_multi(x, y)
    function back(d)
        y1, y2 = d
        return ChainRulesCore.NoTangent(), fill(2 , size(x)), fill(3 , size(y))
    end
    return r, back
end

ReverseDiff.@grad_from_chainrules f_multi(x::AbstractVector{<:ReverseDiff.TrackedReal}, y::AbstractVector{<:Real})

# ForwardDiff computes the correct derivatives (no frule defined),
# Zygote use the new rrule as to expect, ReverseDiff fails!
ForwardDiff.jacobian(x -> f_multi(x, ones(3))[1], seed)
Zygote.jacobian(x -> f_multi(x, ones(3))[1], seed)[1]
ReverseDiff.jacobian(x -> f_multi(x, ones(3))[1], seed) # this errors!

Tested in Julia 1.8.5, all used libraries up-to-date.

Thanks in advance & best regards!

Activity

  1. ThummeTo commented on Mar 17, 2023

    @ThummeTo
    Author

    Forgot to post the error message:

    ERROR: MethodError: no method matching track(::Tuple{Vector{Float64}, Vector{Float64}}, ::Vector{ReverseDiff.AbstractInstruction})
    Closest candidates are:
      track(::AbstractArray, ::Vector{ReverseDiff.AbstractInstruction}) at ...\ReverseDiff.jl\src\tracked.jl:469
      track(::Real, ::Vector{ReverseDiff.AbstractInstruction}) at ...\ReverseDiff.jl\src\tracked.jl:467
      track(::typeof(vcat), ::Union{Number, AbstractVecOrMat}...) at ...\ReverseDiff.jl\src\macros.jl:190       
      ...
    Stacktrace:
     [1] track(#unused#::typeof(f_multi), x::ReverseDiff.TrackedArray{Float64, Float64, 1, Vector{Float64}, Vector{Float64}}, y::Vector{Float64})
       @ Main ...\ReverseDiff.jl\src\macros.jl:329
     [2] f_multi(x::ReverseDiff.TrackedArray{Float64, Float64, 1, Vector{Float64}, Vector{Float64}}, y::Vector{Float64})
       @ Main ...\ReverseDiff.jl\src\macros.jl:324
     [3] (::var"#17#18")(x::ReverseDiff.TrackedArray{Float64, Float64, 1, Vector{Float64}, Vector{Float64}})
       @ Main ...\MWE_multi_reversediff.jl:44
     [4] ReverseDiff.JacobianTape(f::var"#17#18", input::Vector{Float64}, cfg::ReverseDiff.JacobianConfig{ReverseDiff.TrackedArray{Float64, Float64, 1, Vector{Float64}, Vector{Float64}}, Nothing})
       @ ReverseDiff ...\ReverseDiff.jl\src\api\tape.jl:229
     [5] jacobian(f::Function, input::Vector{Float64}, cfg::ReverseDiff.JacobianConfig{ReverseDiff.TrackedArray{Float64, Float64, 1, Vector{Float64}, Vector{Float64}}, Nothing}) (repeats 2 times)
       @ ReverseDiff ...\src\api\jacobians.jl:23
     [6] top-level scope
       @ ...\MWE_multi_reversediff.jl:44
    
  2. cortner commented on Jul 9, 2023

    @cortner

    +1 --- I've run into the same problem and my MWE is almost identical to the one above.

    For me this is a huge problem, because I am hoping to use RevDiff over Zygote to get second derivatives. But when you implement a pullback of a pullback then you will typically have multiple outputs to take care of.

    If anybody can suggest how to fix this or work around it, I'd be very grateful.

    CC @tjjarvinen

  3. added a commit that references this issue on May 27, 2024
  4. devmotion commented on Oct 5, 2026

    @devmotion
    Member

    @grad has the same limitation:

    using ReverseDiff
    
    f(x) = (sum(x), prod(x))
    f(x::ReverseDiff.TrackedArray) = ReverseDiff.track(f, x)
    ReverseDiff.@grad function f(x)
        xv = ReverseDiff.value(x)
        return f(xv), Δ -> (Δ[1] .+ Δ[2] .* prod(xv) ./ xv,)
    end
    ReverseDiff.gradient(x -> sum(f(x)), [1.0, 2.0])
    ERROR: MethodError: no method matching track(::Tuple{Float64, Float64}, ::ReverseDiff.InstructionTape)
    

    Both macros call track(output_value, tp) on the primal output (L196, L339), and track only handles Reals and AbstractArrays (ReverseDiff master, b796032).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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