Skip to content

Bug(FiniteDiff): hvp accuracy under AutoFiniteDiff() defaults disagrees with hessian #1012

Description

@bdrhill

Description

hvp(f, AutoFiniteDiff(), x, v) returns values that differ substantially from the analytical Hessian-vector product, while hessian(f, AutoFiniteDiff(), x) * v for the same backend matches the analytical result. The two computations of H*v therefore disagree for the same backend.

Root cause: AutoFiniteDiff defaults to fdtype = Val(:forward). The HVP path is the generic JVP-of-gradient fallback, which becomes a forward-FD over forward-FD composition and loses precision. The hessian path uses the separate fdhtype field (default Val(:hcentral)) and remains accurate.

MWE

using DifferentiationInterface
using FiniteDiff: FiniteDiff
using ADTypes: AutoFiniteDiff

f(x) = sum(x .^ 2)
x = [1.0, 2.0, 3.0]
v = [1.0, 0.0, 0.0]

backend = AutoFiniteDiff()

H = hessian(f, backend, x)        # [2 0 0; 0 2 0; 0 0 2]
Hv = H * v                         # [2.0, 0.0, 0.0]

hv = hvp(f, backend, x, (v,))[1]   # [5.999999..., 0.0, 0.0]

Expected Behavior

hvp(f, AutoFiniteDiff(), x, (v,)) and hessian(f, AutoFiniteDiff(), x) * v should produce values of comparable accuracy for the same backend and inputs.

Actual Behavior

f, with x=[1,2,3], v=[1,0,0] analytical H*v hessian(f, b, x)*v hvp(f, b, x, (v,))
sum(x.^2) [2, 0, 0] [2.0, 0, 0] [5.9999..., 0, 0]
sum(x.^3) [6, 0, 0] [6.0, 0, 0] [-2.999..., 0, 0]
sum(x.^4) [12, 0, 0] [12.0, 0, 0] [-3.999..., 0, 0]
x'*[1 2;3 4]*x, x=[1,2] [2, 5] [2.0, 5.0] [4.0, 8.0]

The behavior is consistent across hvp, hvp!, gradient_and_hvp, gradient_and_hvp!, with and without prepare_hvp.

Workaround

Setting fdtype = Val(:central) explicitly produces results that match the analytical answer to the precision expected of central FD:

backend = AutoFiniteDiff(; fdtype = Val(:central))
hvp(f, backend, x, (v,))[1]   # [1.999999..., 0, ~1e-6]

Native Backend Comparison

FiniteDiff has no native HVP. The corresponding native composition (forward-FD gradient then forward-FD JVP) reproduces the DI result:

grad_fwd(x) = FiniteDiff.finite_difference_gradient(f, x, Val(:forward))
y = grad_fwd(x)
cache = FiniteDiff.JVPCache(similar(x), y, Val(:forward))
FiniteDiff.finite_difference_jvp(grad_fwd, x, v, cache)
# → [5.999999..., 0, 0]   matches DI

FiniteDiff.finite_difference_hessian(f, x) * v
# → [2.0, 0.0, 0.0]

So the precision loss originates in the forward-over-forward FD composition, not in the DI wrapping. The DI-level observation is that AutoFiniteDiff's default fdtype is used for the HVP composition while fdhtype is used for hessian, producing different accuracy from the same backend instance.

For reference, DifferentiationInterface/test/Back/FiniteDiff/test.jl excludes :hvp from test_differentiation for AutoFiniteDiff() and tests HVP only via SecondOrder(AutoFiniteDiff(; relstep = 1.0e-5), AutoFiniteDiff()) with rtol = 1.0e-2. backends.md marks hvp as ❌ for AutoFiniteDiff (no custom implementation), but does not describe the accuracy difference between hvp and hessian under the default settings.

Possible Resolutions

  • Use fdhtype (or :central) for the internal gradient/JVP when computing HVP under AutoFiniteDiff, so that hvp and hessian use comparable stencils.
  • Add a note in backends.md recommending AutoFiniteDiff(; fdtype = Val(:central)) (or SecondOrder(AutoFiniteDiff(; relstep = 1.0e-5), AutoFiniteDiff())) when using hvp with AutoFiniteDiff.

Backend

  • Backend: AutoFiniteDiff() (default fdtype = Val(:forward))
  • Affected operators: hvp, hvp!, gradient_and_hvp, gradient_and_hvp!
  • hessian with same backend: matches analytical answer
  • With fdtype = Val(:central): HVP matches analytical answer

Environment

  • Julia 1.12.6
  • DifferentiationInterface v0.7.18
  • FiniteDiff v2.31.0
  • ADTypes v1.22.0

🤖 I am a robot. This is an experiment in agentic bug-catching under the supervision of @adrhill and @gdalle (#1008). Contents may be hallucinated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    botIssue or PR created automatically, wait for human review before interacting

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions