From 7498ffed4142062cf370954e9ff0b3e330987c30 Mon Sep 17 00:00:00 2001 From: Penelope Yong Date: Fri, 15 Aug 2025 00:33:44 +0100 Subject: [PATCH 1/6] Avoid batch size of 0 for empty inputs --- DifferentiationInterface/src/utils/batchsize.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DifferentiationInterface/src/utils/batchsize.jl b/DifferentiationInterface/src/utils/batchsize.jl index 054d5c9b9..754d464ce 100644 --- a/DifferentiationInterface/src/utils/batchsize.jl +++ b/DifferentiationInterface/src/utils/batchsize.jl @@ -22,7 +22,7 @@ struct BatchSizeSettings{B,singlebatch,aligned} end function BatchSizeSettings{B,singlebatch,aligned}(N::Integer) where {B,singlebatch,aligned} - B > N && throw(ArgumentError("Batch size $B larger than input size $N")) + B > N > 0 && throw(ArgumentError("Batch size $B larger than input size $N")) A = div(N, B, RoundUp) B_last = N % B return BatchSizeSettings{B,singlebatch,aligned}(N, A, B_last) @@ -123,7 +123,9 @@ Reproduces the heuristic from ForwardDiff to minimize Source: https://github.com/JuliaDiff/ForwardDiff.jl/blob/ec74fbc32b10bbf60b3c527d8961666310733728/src/prelude.jl#L19-L29 """ function reasonable_batchsize(N::Integer, Bmax::Integer) - if N <= Bmax + if N == 0 + return 1 + elseif N <= Bmax return N else A = div(N, Bmax, RoundUp) From 8f93335fa941dda2ad4bd68aab1ecb50edfcf9e6 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Sat, 16 Aug 2025 11:26:21 +0200 Subject: [PATCH 2/6] Add more support for zero batch size (incomplete Jacobian and Hessian) --- .../src/first_order/jacobian.jl | 28 +++++++++++++------ .../src/first_order/pullback.jl | 16 +++++++++-- .../src/first_order/pushforward.jl | 16 +++++++++-- .../src/utils/batchsize.jl | 16 ++++++----- .../test/Core/Internals/batchsize.jl | 3 ++ .../test/Core/ZeroBackends/test.jl | 13 +++++++++ 6 files changed, 73 insertions(+), 19 deletions(-) diff --git a/DifferentiationInterface/src/first_order/jacobian.jl b/DifferentiationInterface/src/first_order/jacobian.jl index 5e6a07280..fa57dd2e4 100644 --- a/DifferentiationInterface/src/first_order/jacobian.jl +++ b/DifferentiationInterface/src/first_order/jacobian.jl @@ -153,12 +153,14 @@ struct PullbackJacobianPrep{ S<:AbstractVector{<:NTuple}, R<:AbstractVector{<:NTuple}, E<:PullbackPrep, + Y, } <: StandardJacobianPrep{SIG} _sig::Val{SIG} batch_size_settings::BS batched_seeds::S batched_results::R pullback_prep::E + y_example::Y end function prepare_jacobian_nokwarg( @@ -212,7 +214,7 @@ function _prepare_jacobian_aux( ] batched_results = [ntuple(b -> similar(y), Val(B)) for _ in batched_seeds] pushforward_prep = prepare_pushforward_nokwarg( - strict, f_or_f!y..., backend, x, batched_seeds[1], contexts... + strict, f_or_f!y..., backend, x, ntuple(b -> zero(x), Val(B)), contexts... ) return PushforwardJacobianPrep( _sig, batch_size_settings, batched_seeds, batched_results, pushforward_prep @@ -237,10 +239,10 @@ function _prepare_jacobian_aux( ] batched_results = [ntuple(b -> similar(x), Val(B)) for _ in batched_seeds] pullback_prep = prepare_pullback_nokwarg( - strict, f_or_f!y..., backend, x, batched_seeds[1], contexts... + strict, f_or_f!y..., backend, x, ntuple(b -> zero(y), Val(B)), contexts... ) return PullbackJacobianPrep( - _sig, batch_size_settings, batched_seeds, batched_results, pullback_prep + _sig, batch_size_settings, batched_seeds, batched_results, pullback_prep, y ) end @@ -367,7 +369,7 @@ function _jacobian_aux( (; A, B_last) = batch_size_settings pushforward_prep_same = prepare_pushforward_same_point( - f_or_f!y..., pushforward_prep, backend, x, batched_seeds[1], contexts... + f_or_f!y..., pushforward_prep, backend, x, ntuple(b -> zero(x), Val(B)), contexts... ) jac = mapreduce(hcat, eachindex(batched_seeds)) do a @@ -419,11 +421,16 @@ function _jacobian_aux( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,aligned,C} - (; batch_size_settings, batched_seeds, pullback_prep) = prep + (; batch_size_settings, batched_seeds, pullback_prep, y_example) = prep (; A, B_last) = batch_size_settings pullback_prep_same = prepare_pullback_same_point( - f_or_f!y..., prep.pullback_prep, backend, x, batched_seeds[1], contexts... + f_or_f!y..., + pullback_prep, + backend, + x, + ntuple(b -> zero(y_example), Val(B)), + contexts..., ) jac = mapreduce(vcat, eachindex(batched_seeds)) do a @@ -487,11 +494,16 @@ function _jacobian_aux!( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,C} - (; batch_size_settings, batched_seeds, batched_results, pullback_prep) = prep + (; batch_size_settings, batched_seeds, batched_results, pullback_prep, y_example) = prep (; N) = batch_size_settings pullback_prep_same = prepare_pullback_same_point( - f_or_f!y..., pullback_prep, backend, x, batched_seeds[1], contexts... + f_or_f!y..., + pullback_prep, + backend, + x, + ntuple(b -> zero(y_example), Val(B)), + contexts..., ) for a in eachindex(batched_seeds, batched_results) diff --git a/DifferentiationInterface/src/first_order/pullback.jl b/DifferentiationInterface/src/first_order/pullback.jl index 6c6e6cc10..5512ebdc4 100644 --- a/DifferentiationInterface/src/first_order/pullback.jl +++ b/DifferentiationInterface/src/first_order/pullback.jl @@ -285,7 +285,13 @@ function _prepare_pullback_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f, backend, x, ty, contexts...; strict) - dx = x isa Number ? oneunit(x) : basis(x, first(CartesianIndices(x))) + dx = if x isa Number + oneunit(x) + elseif isempty(x) + zero(x) + else + basis(x, first(CartesianIndices(x))) + end pushforward_prep = prepare_pushforward_nokwarg( strict, f, backend, x, (dx,), contexts... ) @@ -303,7 +309,13 @@ function _prepare_pullback_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f!, y, backend, x, ty, contexts...; strict) - dx = x isa Number ? oneunit(x) : basis(x, first(CartesianIndices(x))) + dx = if x isa Number + oneunit(x) + elseif isempty(x) + zero(x) + else + basis(x, first(CartesianIndices(x))) + end pushforward_prep = prepare_pushforward_nokwarg( strict, f!, y, backend, x, (dx,), contexts... ) diff --git a/DifferentiationInterface/src/first_order/pushforward.jl b/DifferentiationInterface/src/first_order/pushforward.jl index 46d249d67..6349c1f68 100644 --- a/DifferentiationInterface/src/first_order/pushforward.jl +++ b/DifferentiationInterface/src/first_order/pushforward.jl @@ -290,7 +290,13 @@ function _prepare_pushforward_aux( ) where {F,C} _sig = signature(f, backend, x, tx, contexts...; strict) y = f(x, map(unwrap, contexts)...) - dy = y isa Number ? oneunit(y) : basis(y, first(CartesianIndices(y))) + dy = if y isa Number + oneunit(y) + elseif isempty(y) + zero(y) + else + basis(y, first(CartesianIndices(y))) + end pullback_prep = prepare_pullback_nokwarg(strict, f, backend, x, (dy,), contexts...) return PullbackPushforwardPrep(_sig, pullback_prep) end @@ -306,7 +312,13 @@ function _prepare_pushforward_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f!, y, backend, x, tx, contexts...; strict) - dy = y isa Number ? oneunit(y) : basis(y, first(CartesianIndices(y))) + dy = if y isa Number + oneunit(y) + elseif isempty(y) + zero(y) + else + basis(y, first(CartesianIndices(y))) + end pullback_prep = prepare_pullback_nokwarg(strict, f!, y, backend, x, (dy,), contexts...) return PullbackPushforwardPrep(_sig, pullback_prep) end diff --git a/DifferentiationInterface/src/utils/batchsize.jl b/DifferentiationInterface/src/utils/batchsize.jl index 620c6ac71..510dd502b 100644 --- a/DifferentiationInterface/src/utils/batchsize.jl +++ b/DifferentiationInterface/src/utils/batchsize.jl @@ -23,21 +23,25 @@ end function BatchSizeSettings{B,singlebatch,aligned}(N::Integer) where {B,singlebatch,aligned} B > N > 0 && throw(ArgumentError("Batch size $B larger than input size $N")) - A = div(N, B, RoundUp) - B_last = N % B + if B == N == 0 + A = B_last = 0 + else + A = div(N, B, RoundUp) + B_last = N % B + end return BatchSizeSettings{B,singlebatch,aligned}(N, A, B_last) end function BatchSizeSettings{B}(::Val{N}) where {B,N} singlebatch = B == N - aligned = N % B == 0 + aligned = (B == N == 0) || (N % B == 0) return BatchSizeSettings{B,singlebatch,aligned}(N) end function BatchSizeSettings{B}(N::Integer) where {B} # type-unstable singlebatch = B == N - aligned = N % B == 0 + aligned = (B == N == 0) || (N % B == 0) return BatchSizeSettings{B,singlebatch,aligned}(N) end @@ -123,9 +127,7 @@ Reproduces the heuristic from ForwardDiff to minimize Source: https://github.com/JuliaDiff/ForwardDiff.jl/blob/ec74fbc32b10bbf60b3c527d8961666310733728/src/prelude.jl#L19-L29 """ function reasonable_batchsize(N::Integer, Bmax::Integer) - if N == 0 - return 1 - elseif N <= Bmax + if N <= Bmax return N else A = div(N, Bmax, RoundUp) diff --git a/DifferentiationInterface/test/Core/Internals/batchsize.jl b/DifferentiationInterface/test/Core/Internals/batchsize.jl index 29c17eb24..05f39d2ff 100644 --- a/DifferentiationInterface/test/Core/Internals/batchsize.jl +++ b/DifferentiationInterface/test/Core/Internals/batchsize.jl @@ -25,11 +25,14 @@ BSS = BatchSizeSettings end @testset "SimpleFiniteDiff (adaptive)" begin + @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(0))) isa BSS{0,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(2))) isa BSS{2,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(6))) isa BSS{6,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(12))) isa BSS{12,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(24))) isa BSS{12,false,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(100))) isa BSS{12,false,false} + @test (@inferred pick_batchsize(AutoSimpleFiniteDiff(), @SVector(zeros(0)))) isa + BSS{0,true,true} @test (@inferred pick_batchsize(AutoSimpleFiniteDiff(), @SVector(zeros(2)))) isa BSS{2,true,true} @test (@inferred pick_batchsize(AutoSimpleFiniteDiff(), @SVector(zeros(6)))) isa diff --git a/DifferentiationInterface/test/Core/ZeroBackends/test.jl b/DifferentiationInterface/test/Core/ZeroBackends/test.jl index 6d56dabd4..235812183 100644 --- a/DifferentiationInterface/test/Core/ZeroBackends/test.jl +++ b/DifferentiationInterface/test/Core/ZeroBackends/test.jl @@ -1,6 +1,7 @@ using DifferentiationInterface using DifferentiationInterface: AutoZeroForward, AutoZeroReverse using DifferentiationInterfaceTest +using LinearAlgebra using ComponentArrays: ComponentArrays using JLArrays: JLArrays using SparseMatrixColorings @@ -50,3 +51,15 @@ end logging=LOGGING, ) end + +@testset "Empty arrays" begin + make_empty(t) = typeof(t)[] + make_empty!(y, t) = nothing + @test gradient(sum, AutoZeroForward(), Float64[]) == Float64[] + @test derivative(make_empty, AutoZeroReverse(), 1.0) == Float64[] + @test derivative(make_empty!, Float64[], AutoZeroReverse(), 1.0) == Float64[] + @test_broken jacobian(copy, AutoZeroForward(), Float64[]) == I(0) + @test_broken jacobian(copy, AutoZeroReverse(), Float64[]) == I(0) + @test_broken jacobian(copyto!, Float64[], AutoZeroForward(), Float64[]) == I(0) + @test_broken jacobian(copyto!, Float64[], AutoZeroReverse(), Float64[]) == I(0) +end From d0393fc5b09ae70a15fcbcfc8ae34cc4ad3eb80f Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 21 Aug 2025 08:43:44 +0200 Subject: [PATCH 3/6] test: add proper empty tests --- DifferentiationInterface/src/utils/batchsize.jl | 6 ++++-- .../test/Back/Enzyme/test.jl | 8 ++++++++ .../test/Core/Internals/batchsize.jl | 3 ++- .../test/Core/ZeroBackends/test.jl | 14 ++++---------- DifferentiationInterface/test/testutils.jl | 3 ++- .../src/DifferentiationInterfaceTest.jl | 3 ++- .../src/scenarios/empty.jl | 16 ++++++++++++++++ DifferentiationInterfaceTest/test/runtests.jl | 3 ++- DifferentiationInterfaceTest/test/standard.jl | 7 +++++++ 9 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 DifferentiationInterfaceTest/src/scenarios/empty.jl diff --git a/DifferentiationInterface/src/utils/batchsize.jl b/DifferentiationInterface/src/utils/batchsize.jl index 510dd502b..1e55721e9 100644 --- a/DifferentiationInterface/src/utils/batchsize.jl +++ b/DifferentiationInterface/src/utils/batchsize.jl @@ -6,7 +6,7 @@ Configuration for the batch size deduced from a backend and a sample array of le # Type parameters - `B::Int`: batch size - - `singlebatch::Bool`: whether `B == N` (`B > N` is not allowed) + - `singlebatch::Bool`: whether `B == N` (`B > N` is only allowed when `N == 0`) - `aligned::Bool`: whether `N % B == 0` # Fields @@ -127,7 +127,9 @@ Reproduces the heuristic from ForwardDiff to minimize Source: https://github.com/JuliaDiff/ForwardDiff.jl/blob/ec74fbc32b10bbf60b3c527d8961666310733728/src/prelude.jl#L19-L29 """ function reasonable_batchsize(N::Integer, Bmax::Integer) - if N <= Bmax + if N == 0 + return 1 + elseif N <= Bmax return N else A = div(N, Bmax, RoundUp) diff --git a/DifferentiationInterface/test/Back/Enzyme/test.jl b/DifferentiationInterface/test/Back/Enzyme/test.jl index 7e1b02451..ecc120718 100644 --- a/DifferentiationInterface/test/Back/Enzyme/test.jl +++ b/DifferentiationInterface/test/Back/Enzyme/test.jl @@ -201,3 +201,11 @@ end @test occursin("DifferentiationInterface", msg) end end + +@testset "Empty arrays" begin + test_differentiation( + [AutoEnzyme(; mode=Enzyme.Forward), AutoEnzyme(; mode=Enzyme.Reverse)], + empty_scenarios(); + excluded=[:jacobian], + ) +end; diff --git a/DifferentiationInterface/test/Core/Internals/batchsize.jl b/DifferentiationInterface/test/Core/Internals/batchsize.jl index 05f39d2ff..52cc24b91 100644 --- a/DifferentiationInterface/test/Core/Internals/batchsize.jl +++ b/DifferentiationInterface/test/Core/Internals/batchsize.jl @@ -13,6 +13,7 @@ using Test BSS = BatchSizeSettings @testset "Default" begin + @test (@inferred pick_batchsize(AutoZygote(), zeros(0))) isa BSS{1,false,true} @test (@inferred pick_batchsize(AutoZygote(), zeros(2))) isa BSS{1,false,true} @test (@inferred pick_batchsize(AutoZygote(), zeros(100))) isa BSS{1,false,true} @test_throws ArgumentError pick_batchsize(AutoSparse(AutoZygote()), zeros(2)) @@ -25,7 +26,7 @@ BSS = BatchSizeSettings end @testset "SimpleFiniteDiff (adaptive)" begin - @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(0))) isa BSS{0,true,true} + @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(0))) isa BSS{1,false,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(2))) isa BSS{2,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(6))) isa BSS{6,true,true} @test (pick_batchsize(AutoSimpleFiniteDiff(), zeros(12))) isa BSS{12,true,true} diff --git a/DifferentiationInterface/test/Core/ZeroBackends/test.jl b/DifferentiationInterface/test/Core/ZeroBackends/test.jl index 235812183..e79383d00 100644 --- a/DifferentiationInterface/test/Core/ZeroBackends/test.jl +++ b/DifferentiationInterface/test/Core/ZeroBackends/test.jl @@ -53,13 +53,7 @@ end end @testset "Empty arrays" begin - make_empty(t) = typeof(t)[] - make_empty!(y, t) = nothing - @test gradient(sum, AutoZeroForward(), Float64[]) == Float64[] - @test derivative(make_empty, AutoZeroReverse(), 1.0) == Float64[] - @test derivative(make_empty!, Float64[], AutoZeroReverse(), 1.0) == Float64[] - @test_broken jacobian(copy, AutoZeroForward(), Float64[]) == I(0) - @test_broken jacobian(copy, AutoZeroReverse(), Float64[]) == I(0) - @test_broken jacobian(copyto!, Float64[], AutoZeroForward(), Float64[]) == I(0) - @test_broken jacobian(copyto!, Float64[], AutoZeroReverse(), Float64[]) == I(0) -end + test_differentiation( + [AutoZeroForward(), AutoZeroReverse()], empty_scenarios(); excluded=[:jacobian] + ) +end; diff --git a/DifferentiationInterface/test/testutils.jl b/DifferentiationInterface/test/testutils.jl index 646f161f9..4c7366b40 100644 --- a/DifferentiationInterface/test/testutils.jl +++ b/DifferentiationInterface/test/testutils.jl @@ -10,7 +10,8 @@ using DifferentiationInterfaceTest: complex_sparse_scenarios, static_scenarios, component_scenarios, - gpu_scenarios + gpu_scenarios, + empty_scenarios function MyAutoSparse(backend::AbstractADType) return AutoSparse( diff --git a/DifferentiationInterfaceTest/src/DifferentiationInterfaceTest.jl b/DifferentiationInterfaceTest/src/DifferentiationInterfaceTest.jl index f4751bad9..8c2c99803 100644 --- a/DifferentiationInterfaceTest/src/DifferentiationInterfaceTest.jl +++ b/DifferentiationInterfaceTest/src/DifferentiationInterfaceTest.jl @@ -93,7 +93,7 @@ using DifferentiationInterface: Rewrap, Context, Constant, Cache, ConstantOrCach using DifferentiationInterface: PreparationMismatchError using DocStringExtensions: TYPEDFIELDS, TYPEDSIGNATURES using JET: @test_opt -using LinearAlgebra: Adjoint, Diagonal, Transpose, dot, parent +using LinearAlgebra: Adjoint, Diagonal, Transpose, I, dot, parent using ProgressMeter: ProgressUnknown, next! using Random: AbstractRNG, default_rng, rand! using SparseArrays: @@ -124,6 +124,7 @@ include("scenarios/default.jl") include("scenarios/sparse.jl") include("scenarios/complex.jl") include("scenarios/allocfree.jl") +include("scenarios/empty.jl") include("scenarios/extensions.jl") include("tests/correctness_eval.jl") diff --git a/DifferentiationInterfaceTest/src/scenarios/empty.jl b/DifferentiationInterfaceTest/src/scenarios/empty.jl new file mode 100644 index 000000000..f421abc0a --- /dev/null +++ b/DifferentiationInterfaceTest/src/scenarios/empty.jl @@ -0,0 +1,16 @@ +make_empty(t::Number) = typeof(t)[] +function make_empty!(y::AbstractArray, t::Number) + @assert isempty(y) + return nothing +end + +function empty_scenarios() + scens = Scenario[ + Scenario{:derivative,:out}(make_empty, 1.0; res1=Float64[]), + Scenario{:derivative,:out}(make_empty!, Float64[], 1.0; res1=Float64[]), + Scenario{:gradient,:out}(sum, Float64[]; res1=Float64[]), + Scenario{:jacobian,:out}(copy, Float64[]; res1=float.(I(0))), + Scenario{:jacobian,:out}(copyto!, Float64[], Float64[]; res1=float.(I(0))), + ] + return scens +end diff --git a/DifferentiationInterfaceTest/test/runtests.jl b/DifferentiationInterfaceTest/test/runtests.jl index 0a4e690f2..17ed63d9d 100644 --- a/DifferentiationInterfaceTest/test/runtests.jl +++ b/DifferentiationInterfaceTest/test/runtests.jl @@ -12,7 +12,8 @@ using DifferentiationInterfaceTest: complex_sparse_scenarios, static_scenarios, component_scenarios, - gpu_scenarios + gpu_scenarios, + empty_scenarios GROUP = get(ENV, "JULIA_DIT_TEST_GROUP", "All") diff --git a/DifferentiationInterfaceTest/test/standard.jl b/DifferentiationInterfaceTest/test/standard.jl index c4c351db4..24e64b0b0 100644 --- a/DifferentiationInterfaceTest/test/standard.jl +++ b/DifferentiationInterfaceTest/test/standard.jl @@ -28,6 +28,13 @@ test_differentiation( logging=LOGGING, ) +test_differentiation( + [AutoForwardDiff()], empty_scenarios(); excluded=[:gradient], logging=LOGGING +) +test_differentiation( + [AutoFiniteDiff()], empty_scenarios(); excluded=[:jacobian], logging=LOGGING +) + ## Sparse sparse_backend = AutoSparse( From 65d2efdf8e916f5d02f0a9359a4d23a3c2ad6523 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:08:56 +0200 Subject: [PATCH 4/6] chore: Bump DI, update changelogs --- DifferentiationInterface/CHANGELOG.md | 6 +++++- DifferentiationInterface/Project.toml | 2 +- DifferentiationInterfaceTest/CHANGELOG.md | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/DifferentiationInterface/CHANGELOG.md b/DifferentiationInterface/CHANGELOG.md index 3d96f34a2..c60369666 100644 --- a/DifferentiationInterface/CHANGELOG.md +++ b/DifferentiationInterface/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/JuliaDiff/DifferentiationInterface.jl/compare/DifferentiationInterface-v0.7.6...main) +## [Unreleased](https://github.com/JuliaDiff/DifferentiationInterface.jl/compare/DifferentiationInterface-v0.7.7...main) + +## [0.7.7](https://github.com/JuliaDiff/DifferentiationInterface.jl/compare/DifferentiationInterface-v0.7.6...DifferentiationInterface-v0.7.7) + + - Improve support for empty inputs (still not guaranteed) ([#835](https://github.com/JuliaDiff/DifferentiationInterface.jl/pull/835)) ## [0.7.6](https://github.com/JuliaDiff/DifferentiationInterface.jl/compare/DifferentiationInterface-v0.7.5...DifferentiationInterface-v0.7.6) diff --git a/DifferentiationInterface/Project.toml b/DifferentiationInterface/Project.toml index ca2055d1c..372a97de8 100644 --- a/DifferentiationInterface/Project.toml +++ b/DifferentiationInterface/Project.toml @@ -1,7 +1,7 @@ name = "DifferentiationInterface" uuid = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" authors = ["Guillaume Dalle", "Adrian Hill"] -version = "0.7.6" +version = "0.7.7" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" diff --git a/DifferentiationInterfaceTest/CHANGELOG.md b/DifferentiationInterfaceTest/CHANGELOG.md index 473bf916e..e25180179 100644 --- a/DifferentiationInterfaceTest/CHANGELOG.md +++ b/DifferentiationInterfaceTest/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added + - Improve support for empty inputs (still not guaranteed) ([#835](https://github.com/JuliaDiff/DifferentiationInterface.jl/pull/835)) - Compute Scenario results with a reference backend ([#839](https://github.com/JuliaDiff/DifferentiationInterface.jl/pull/839)) ### Fixed From 35d9a6048bb458109c8162575d661dcc4607c7db Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:21:49 +0200 Subject: [PATCH 5/6] fix: fix type stability --- .github/workflows/Test.yml | 2 +- .../src/first_order/jacobian.jl | 55 +++++++++++-------- .../test/zero_backends.jl | 4 +- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 388bbb3ff..729fe6ec5 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -25,7 +25,7 @@ jobs: actions: write contents: read strategy: - fail-fast: true # TODO: toggle + fail-fast: false # TODO: toggle matrix: version: - "1.10" diff --git a/DifferentiationInterface/src/first_order/jacobian.jl b/DifferentiationInterface/src/first_order/jacobian.jl index fa57dd2e4..9e55264b3 100644 --- a/DifferentiationInterface/src/first_order/jacobian.jl +++ b/DifferentiationInterface/src/first_order/jacobian.jl @@ -138,12 +138,14 @@ struct PushforwardJacobianPrep{ BS<:BatchSizeSettings, S<:AbstractVector{<:NTuple}, R<:AbstractVector{<:NTuple}, + SE<:NTuple, E<:PushforwardPrep, } <: StandardJacobianPrep{SIG} _sig::Val{SIG} batch_size_settings::BS batched_seeds::S batched_results::R + seed_example::SE pushforward_prep::E end @@ -152,15 +154,15 @@ struct PullbackJacobianPrep{ BS<:BatchSizeSettings, S<:AbstractVector{<:NTuple}, R<:AbstractVector{<:NTuple}, + SE<:NTuple, E<:PullbackPrep, - Y, } <: StandardJacobianPrep{SIG} _sig::Val{SIG} batch_size_settings::BS batched_seeds::S batched_results::R + seed_example::SE pullback_prep::E - y_example::Y end function prepare_jacobian_nokwarg( @@ -213,11 +215,17 @@ function _prepare_jacobian_aux( ntuple(b -> seeds[1 + ((a - 1) * B + (b - 1)) % N], Val(B)) for a in 1:A ] batched_results = [ntuple(b -> similar(y), Val(B)) for _ in batched_seeds] + seed_example = ntuple(b -> zero(x), Val(B)) pushforward_prep = prepare_pushforward_nokwarg( - strict, f_or_f!y..., backend, x, ntuple(b -> zero(x), Val(B)), contexts... + strict, f_or_f!y..., backend, x, seed_example, contexts... ) return PushforwardJacobianPrep( - _sig, batch_size_settings, batched_seeds, batched_results, pushforward_prep + _sig, + batch_size_settings, + batched_seeds, + batched_results, + seed_example, + pushforward_prep, ) end @@ -238,11 +246,17 @@ function _prepare_jacobian_aux( ntuple(b -> seeds[1 + ((a - 1) * B + (b - 1)) % N], Val(B)) for a in 1:A ] batched_results = [ntuple(b -> similar(x), Val(B)) for _ in batched_seeds] + seed_example = ntuple(b -> zero(y), Val(B)) pullback_prep = prepare_pullback_nokwarg( - strict, f_or_f!y..., backend, x, ntuple(b -> zero(y), Val(B)), contexts... + strict, f_or_f!y..., backend, x, seed_example, contexts... ) return PullbackJacobianPrep( - _sig, batch_size_settings, batched_seeds, batched_results, pullback_prep, y + _sig, + batch_size_settings, + batched_seeds, + batched_results, + seed_example, + pullback_prep, ) end @@ -365,11 +379,11 @@ function _jacobian_aux( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,aligned,C} - (; batch_size_settings, batched_seeds, pushforward_prep) = prep + (; batch_size_settings, batched_seeds, seed_example, pushforward_prep) = prep (; A, B_last) = batch_size_settings pushforward_prep_same = prepare_pushforward_same_point( - f_or_f!y..., pushforward_prep, backend, x, ntuple(b -> zero(x), Val(B)), contexts... + f_or_f!y..., pushforward_prep, backend, x, seed_example, contexts... ) jac = mapreduce(hcat, eachindex(batched_seeds)) do a @@ -421,16 +435,11 @@ function _jacobian_aux( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,aligned,C} - (; batch_size_settings, batched_seeds, pullback_prep, y_example) = prep + (; batch_size_settings, batched_seeds, seed_example, pullback_prep) = prep (; A, B_last) = batch_size_settings pullback_prep_same = prepare_pullback_same_point( - f_or_f!y..., - pullback_prep, - backend, - x, - ntuple(b -> zero(y_example), Val(B)), - contexts..., + f_or_f!y..., pullback_prep, backend, x, seed_example, contexts... ) jac = mapreduce(vcat, eachindex(batched_seeds)) do a @@ -458,11 +467,13 @@ function _jacobian_aux!( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,C} - (; batch_size_settings, batched_seeds, batched_results, pushforward_prep) = prep + (; + batch_size_settings, batched_seeds, batched_results, seed_example, pushforward_prep + ) = prep (; N) = batch_size_settings pushforward_prep_same = prepare_pushforward_same_point( - f_or_f!y..., pushforward_prep, backend, x, batched_seeds[1], contexts... + f_or_f!y..., pushforward_prep, backend, x, seed_example, contexts... ) for a in eachindex(batched_seeds, batched_results) @@ -494,16 +505,12 @@ function _jacobian_aux!( x, contexts::Vararg{Context,C}, ) where {FY,SIG,B,C} - (; batch_size_settings, batched_seeds, batched_results, pullback_prep, y_example) = prep + (; batch_size_settings, batched_seeds, batched_results, seed_example, pullback_prep) = + prep (; N) = batch_size_settings pullback_prep_same = prepare_pullback_same_point( - f_or_f!y..., - pullback_prep, - backend, - x, - ntuple(b -> zero(y_example), Val(B)), - contexts..., + f_or_f!y..., pullback_prep, backend, x, seed_example, contexts... ) for a in eachindex(batched_seeds, batched_results) diff --git a/DifferentiationInterfaceTest/test/zero_backends.jl b/DifferentiationInterfaceTest/test/zero_backends.jl index 935de0cd1..aeec03bc2 100644 --- a/DifferentiationInterfaceTest/test/zero_backends.jl +++ b/DifferentiationInterfaceTest/test/zero_backends.jl @@ -86,7 +86,9 @@ end logging=LOGGING, ), ) - @test all(iszero, data_allocfree[!, :allocs]) + @testset "$(collect(row[1:4]))" for row in collect(eachrow(data_allocfree)) + @test row[:allocs] == 0 + end end test_differentiation( From 22bd82eb4ab89ea55f8690166f5847fa1b7a2ab6 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:41:51 +0200 Subject: [PATCH 6/6] fix: use zero as example --- .../src/first_order/pullback.jl | 16 ++-------------- .../src/first_order/pushforward.jl | 16 ++-------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/DifferentiationInterface/src/first_order/pullback.jl b/DifferentiationInterface/src/first_order/pullback.jl index 5512ebdc4..15477bf9a 100644 --- a/DifferentiationInterface/src/first_order/pullback.jl +++ b/DifferentiationInterface/src/first_order/pullback.jl @@ -285,13 +285,7 @@ function _prepare_pullback_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f, backend, x, ty, contexts...; strict) - dx = if x isa Number - oneunit(x) - elseif isempty(x) - zero(x) - else - basis(x, first(CartesianIndices(x))) - end + dx = zero(x) pushforward_prep = prepare_pushforward_nokwarg( strict, f, backend, x, (dx,), contexts... ) @@ -309,13 +303,7 @@ function _prepare_pullback_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f!, y, backend, x, ty, contexts...; strict) - dx = if x isa Number - oneunit(x) - elseif isempty(x) - zero(x) - else - basis(x, first(CartesianIndices(x))) - end + dx = zero(x) pushforward_prep = prepare_pushforward_nokwarg( strict, f!, y, backend, x, (dx,), contexts... ) diff --git a/DifferentiationInterface/src/first_order/pushforward.jl b/DifferentiationInterface/src/first_order/pushforward.jl index 6349c1f68..b029f66e8 100644 --- a/DifferentiationInterface/src/first_order/pushforward.jl +++ b/DifferentiationInterface/src/first_order/pushforward.jl @@ -290,13 +290,7 @@ function _prepare_pushforward_aux( ) where {F,C} _sig = signature(f, backend, x, tx, contexts...; strict) y = f(x, map(unwrap, contexts)...) - dy = if y isa Number - oneunit(y) - elseif isempty(y) - zero(y) - else - basis(y, first(CartesianIndices(y))) - end + dy = zero(y) pullback_prep = prepare_pullback_nokwarg(strict, f, backend, x, (dy,), contexts...) return PullbackPushforwardPrep(_sig, pullback_prep) end @@ -312,13 +306,7 @@ function _prepare_pushforward_aux( contexts::Vararg{Context,C}; ) where {F,C} _sig = signature(f!, y, backend, x, tx, contexts...; strict) - dy = if y isa Number - oneunit(y) - elseif isempty(y) - zero(y) - else - basis(y, first(CartesianIndices(y))) - end + dy = zero(y) pullback_prep = prepare_pullback_nokwarg(strict, f!, y, backend, x, (dy,), contexts...) return PullbackPushforwardPrep(_sig, pullback_prep) end