Skip to content

Commit e87b42e

Browse files
yebaiclaude
andcommitted
Consolidate AD-backend tests via shared run_autograd_tests entry point
Move the shared AD-test helpers out of `test/ext/ad_tests.jl` to `test/autograd_tests.jl` (now used by both `test/ext/` and `test/integration/` suites) and add a top-level `run_autograd_tests` umbrella that runs the gradient, jacobian, and empty-input shared tests on a single adtype. The umbrella supports `namedtuple=true` for backends with a native NamedTuple path (ForwardDiff, Mooncake) and `extra_jacobian_modes=(...)` for backends that exercise the jacobian on multiple modes (Enzyme, Mooncake). Each per-backend test file now collapses to imports + adtype + a single `run_autograd_tests(adtype; ...)` call (plus genuinely backend-specific extras for Enzyme and ReverseDiff). Also rename test directories to match the rest of the codebase's no- underscore convention: `forward_diff` -> `forwarddiff` and `differentiation_interface` -> `differentiationinterface`. The CI ext matrix and `run_extra.jl` mapping are updated to match. Net effect: ReverseDiff coverage grows from 9 to 17 (umbrella adds jacobian and empty-input tests); Enzyme grows from 21 to 29 (empty- input added, jacobian calls deduplicated through the umbrella). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ef6fdf0 commit e87b42e

15 files changed

Lines changed: 206 additions & 221 deletions

File tree

.github/workflows/CI.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ jobs:
5353
matrix:
5454
label:
5555
- enzyme
56-
- differentiation_interface
57-
- forward_diff
56+
- differentiationinterface
57+
- forwarddiff
5858
- logdensityproblems
5959
- mooncake
6060
- reversediff

ext/AbstractPPLForwardDiffExt.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ function AbstractPPL.prepare(
6464
)
6565
raw = AbstractPPL.prepare(problem, x)
6666
evaluator = AbstractPPL.ADProblems.VectorEvaluator{check_dims}(raw, length(x))
67-
# Empty inputs short-circuit at the `value_and_*` level; ForwardDiff has no
68-
# configuration to build for length-zero arrays.
67+
# ForwardDiff has no configuration to build for length-zero arrays; short-circuit
68+
# in `value_and_gradient` / `value_and_jacobian` instead.
6969
length(x) == 0 &&
70-
return ForwardDiffPrepared(evaluator, evaluator, nothing, nothing, nothing, nothing)
70+
return ForwardDiffPrepared(evaluator, nothing, nothing, nothing, nothing, nothing)
7171
# Hand ForwardDiff an unchecked wrapper so the per-call dim check does not
7272
# land in the dual-number hot path; user-visible `prepared(x)` still goes
7373
# through `evaluator` (whose `check_dims` honors the caller's request).

ext/AbstractPPLMooncakeExt.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ function AbstractPPL.prepare(
4747
)
4848
raw = AbstractPPL.prepare(problem, x)
4949
evaluator = AbstractPPL.ADProblems.VectorEvaluator{check_dims}(raw, length(x))
50-
# Empty inputs short-circuit at the `value_and_*` level; Mooncake has no
51-
# tape to build for length-zero arrays.
50+
# Mooncake has no tape to build for length-zero arrays; short-circuit
51+
# in `value_and_gradient` / `value_and_jacobian` instead.
5252
length(x) == 0 && return MooncakePrepared(evaluator, nothing, nothing)
5353
y = evaluator(x)
5454
_assert_supported_output(y)

test/autograd_tests.jl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Shared problem definitions and test helpers for AD backend integration tests.
2+
# Include this file after `using AbstractPPL, Test` and any backend-specific setup.
3+
4+
struct QuadraticProblem end
5+
struct QuadraticVecPrepared end
6+
7+
function AbstractPPL.prepare(::QuadraticProblem, x::AbstractVector{<:Real})
8+
return QuadraticVecPrepared()
9+
end
10+
11+
function (::QuadraticVecPrepared)(x::AbstractVector{<:Real})
12+
return sum(xi -> xi^2, x)
13+
end
14+
15+
struct VectorValuedProblem end
16+
struct VectorValuedPrepared end
17+
18+
function AbstractPPL.prepare(::VectorValuedProblem, x::AbstractVector{<:Real})
19+
return VectorValuedPrepared()
20+
end
21+
22+
# y = [x[1]*x[2], x[2]+x[3]] -> J = [x[2] x[1] 0; 0 1 1]
23+
function (::VectorValuedPrepared)(x::AbstractVector{<:Real})
24+
return [x[1] * x[2], x[2] + x[3]]
25+
end
26+
27+
"""
28+
run_shared_gradient_tests(adtype, x0, x; atol=0, rtol=1e-10)
29+
30+
Test the vector-input gradient path for `adtype` on `QuadraticProblem`.
31+
`x0` is the prototype (zeros), `x = [3.0, 1.0, 2.0]` is the test point.
32+
"""
33+
function run_shared_gradient_tests(adtype, x0, x; atol=0, rtol=1e-10)
34+
@testset "gradient path" begin
35+
problem = QuadraticProblem()
36+
prepared = AbstractPPL.prepare(adtype, problem, x0)
37+
38+
@test prepared(x) 14.0
39+
40+
val, grad = AbstractPPL.value_and_gradient(prepared, x)
41+
@test val 14.0 atol = atol rtol = rtol
42+
@test grad [6.0, 2.0, 4.0] atol = atol rtol = rtol
43+
44+
@test_throws DimensionMismatch prepared([3.0, 1.0, 2.0, 99.0])
45+
@test_throws r"floating-point" prepared([3, 1, 2])
46+
end
47+
end
48+
49+
"""
50+
run_shared_jacobian_tests(adtype, x0, xj; atol=0, rtol=1e-10)
51+
52+
Test the jacobian path for `adtype` on `VectorValuedProblem`.
53+
`x0` is the prototype (zeros(3)), `xj` is the test point.
54+
"""
55+
function run_shared_jacobian_tests(adtype, x0, xj; atol=0, rtol=1e-10)
56+
@testset "jacobian path" begin
57+
problem = VectorValuedProblem()
58+
prepared = AbstractPPL.prepare(adtype, problem, x0)
59+
60+
@test prepared(xj) [6.0, 7.0]
61+
62+
val, jac = AbstractPPL.value_and_jacobian(prepared, xj)
63+
@test val [6.0, 7.0] atol = atol rtol = rtol
64+
@test jac [3.0 2.0 0.0; 0.0 1.0 1.0] atol = atol rtol = rtol
65+
66+
@test_throws r"scalar-valued" AbstractPPL.value_and_gradient(prepared, xj)
67+
end
68+
end
69+
70+
"""
71+
run_shared_empty_input_tests(adtype)
72+
73+
Test the empty-input short-circuit for `adtype` on both scalar- and
74+
vector-valued evaluators.
75+
"""
76+
function run_shared_empty_input_tests(adtype)
77+
@testset "empty input" begin
78+
x_empty = Float64[]
79+
prepared = AbstractPPL.prepare(adtype, x -> 7.5, x_empty)
80+
val, grad = AbstractPPL.value_and_gradient(prepared, x_empty)
81+
@test val == 7.5
82+
@test grad == Float64[]
83+
84+
prepared_jac = AbstractPPL.prepare(adtype, x -> [2.0, 3.0], x_empty)
85+
valj, jac = AbstractPPL.value_and_jacobian(prepared_jac, x_empty)
86+
@test valj == [2.0, 3.0]
87+
@test size(jac) == (2, 0)
88+
end
89+
end
90+
91+
"""
92+
run_shared_namedtuple_tests(adtype)
93+
94+
Test the NamedTuple-input gradient path for AD backends that support it
95+
(currently the native ForwardDiff and Mooncake extensions).
96+
"""
97+
function run_shared_namedtuple_tests(adtype)
98+
@testset "NamedTuple input" begin
99+
prepared = AbstractPPL.prepare(
100+
adtype, vs -> vs.x^2 + sum(abs2, vs.y), (x=0.0, y=zeros(2))
101+
)
102+
@test prepared.evaluator isa AbstractPPL.ADProblems.NamedTupleEvaluator
103+
104+
val, grad = AbstractPPL.value_and_gradient(prepared, (x=3.0, y=[1.0, 2.0]))
105+
@test val 14.0
106+
@test grad.x 6.0
107+
@test grad.y [2.0, 4.0]
108+
109+
@test_throws r"same NamedTuple structure" AbstractPPL.value_and_gradient(
110+
prepared, (x=3.0, z=[1.0, 2.0])
111+
)
112+
end
113+
end
114+
115+
"""
116+
run_autograd_tests(adtype; namedtuple=false, extra_jacobian_modes=(), kwargs...)
117+
118+
Run the gradient, jacobian, and empty-input shared tests on `adtype`. Pass
119+
`namedtuple=true` for backends with a native NamedTuple-input path; pass
120+
`extra_jacobian_modes` to additionally exercise the jacobian path on those
121+
adtypes. `kwargs` (`atol`, `rtol`, …) are forwarded to the shared helpers.
122+
"""
123+
function run_autograd_tests(
124+
adtype; namedtuple::Bool=false, extra_jacobian_modes=(), kwargs...
125+
)
126+
run_shared_gradient_tests(adtype, zeros(3), [3.0, 1.0, 2.0]; kwargs...)
127+
run_shared_jacobian_tests(adtype, zeros(3), [2.0, 3.0, 4.0]; kwargs...)
128+
run_shared_empty_input_tests(adtype)
129+
for adtype_jac in extra_jacobian_modes
130+
run_shared_jacobian_tests(adtype_jac, zeros(3), [2.0, 3.0, 4.0]; kwargs...)
131+
end
132+
namedtuple && run_shared_namedtuple_tests(adtype)
133+
return nothing
134+
end

test/ext/ad_tests.jl

Lines changed: 0 additions & 68 deletions
This file was deleted.

test/ext/differentiation_interface/differentiation_interface.jl

Lines changed: 0 additions & 73 deletions
This file was deleted.
File renamed without changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Pkg
2+
Pkg.activate(@__DIR__)
3+
Pkg.develop(; path=joinpath(@__DIR__, "..", "..", ".."))
4+
Pkg.instantiate()
5+
6+
using AbstractPPL
7+
using ADTypes: ADTypes
8+
using DifferentiationInterface: DifferentiationInterface as DI
9+
using Test
10+
11+
include(joinpath(@__DIR__, "..", "..", "autograd_tests.jl"))
12+
13+
# Stub backend without a native AbstractPPL extension; this exercises
14+
# the AbstractPPLDifferentiationInterfaceExt catch-all dispatch.
15+
struct DummyADType <: ADTypes.AbstractADType end
16+
const adtype = DummyADType()
17+
18+
DI.prepare_gradient(f, ::DummyADType, x, ::DI.Constant) = Val(:gradient)
19+
function DI.value_and_gradient(f, prep, ::DummyADType, x, ctx::DI.Constant)
20+
return (f(x, ctx.data), 2 .* x)
21+
end
22+
DI.prepare_jacobian(f, ::DummyADType, x, ::DI.Constant) = Val(:jacobian)
23+
function DI.value_and_jacobian(f, prep, ::DummyADType, x, ctx::DI.Constant)
24+
jac = [
25+
x[2] x[1] zero(eltype(x))
26+
zero(eltype(x)) one(eltype(x)) one(eltype(x))
27+
]
28+
return (f(x, ctx.data), jac)
29+
end
30+
31+
@testset "AbstractPPLDifferentiationInterfaceExt" begin
32+
run_autograd_tests(adtype; atol=1e-6, rtol=1e-6)
33+
end

test/ext/forward_diff/forward_diff.jl

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)