I have a function that depends on two inputs, but I only want to differentiate with respect to the first. But I also want to compute the gradient for different values of the second input. Is it okay to use the same GradientConfig
object for the different calls? I’m asking because I have to disable tag checking if I want to avoid an Invalid Tag object
error, and the ForwardDiff docs scare me from disabling tag checking haha
Here’s a MWE:
# main.jl
using ForwardDiff, Random
function main(method)
Random.seed!(0)
x = ones(10)
data = [1:10; 2:2:20]
indexes = randperm(length(data))
f(x, i) = x' * data[i]
result = DiffResults.GradientResult(x)
config = ForwardDiff.GradientConfig(x -> f(x, 1:10), x, ForwardDiff.Chunk{length(x)}())
i = indexes[1:10]
if method === :config
ForwardDiff.gradient!(result, x -> f(x, i), x, config)
elseif method === :noconfig
ForwardDiff.gradient!(result, x -> f(x, i), x)
elseif method === :nocheck
ForwardDiff.gradient!(result, x -> f(x, i), x, config, Val{false}())
end
DiffResults.gradient(result) == data[i]
end
julia> include("main.jl")
main (generic function with 1 method)
julia> main(:config)
ERROR: Invalid Tag object:
Expected ForwardDiff.Tag{var"#2#7"{var"#f#5"{Vector{Int64}}, Vector{Int64}}, Float64},
Observed ForwardDiff.Tag{var"#1#6"{var"#f#5"{Vector{Int64}}}, Float64}.
Stacktrace:
[1] checktag(#unused#::Type{ForwardDiff.Tag{var"#1#6"{var"#f#5"{Vector{Int64}}}, Float64}}, f::var"#2#7"{var"#f#5"{Vector{Int64}}, Vector{Int64}}, x::Vector{Float64})
@ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/config.jl:34
[2] gradient!(result::DiffResults.MutableDiffResult{1, Float64, Tuple{Vector{Float64}}}, f::var"#2#7"{var"#f#5"{Vector{Int64}}, Vector{Int64}}, x::Vector{Float64}, cfg::ForwardDiff.GradientConfig{ForwardDiff.Tag{var"#1#6"{var"#f#5"{Vector{Int64}}}, Float64}, Float64, 10, Vector{ForwardDiff.Dual{ForwardDiff.Tag{var"#1#6"{var"#f#5"{Vector{Int64}}}, Float64}, Float64, 10}}}, ::Val{true}) (repeats 2 times)
@ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/gradient.jl:35
[3] main(method::Symbol)
@ Main ~/tmp/main.jl:15
[4] top-level scope
@ REPL[1]:1
julia> main(:noconfig)
true
julia> main(:nocheck)
true
Also, in this MWE the expected and observed tags differ not only in the anonymous function “name” (e.g., #2#7
), but also in its parameterization:
Expected ForwardDiff.Tag{var"#2#7"{var"#f#5"{Vector{Int64}}, Vector{Int64}}, Float64},
Observed ForwardDiff.Tag{var"#1#6"{var"#f#5"{Vector{Int64}}}, Float64}.
Does that matter for tag checking? (And why is the parameterization different?)
Am I missing anything important? Are nested differentiation calls the only situation where tag checking should not be disabled?