Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ADTypes"
uuid = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
authors = ["Vaibhav Dixit <[email protected]>, Guillaume Dalle and contributors"]
version = "1.17.0"
version = "1.18.0"

[deps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
Expand Down
10 changes: 6 additions & 4 deletions ext/ADTypesConstructionBaseExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ module ADTypesConstructionBaseExt
using ADTypes: AutoEnzyme, AutoForwardDiff, AutoPolyesterForwardDiff
using ConstructionBase: ConstructionBase

struct InternalAutoEnzymeReconstructor{A} end
struct InternalAutoEnzymeReconstructor{A, C} end

InternalAutoEnzymeReconstructor{A}(mode::M) where {M, A} = AutoEnzyme{M, A}(mode)
function InternalAutoEnzymeReconstructor{A, C}(mode::M) where {M, A, C}
AutoEnzyme{M, A, C}(mode)
end

function ConstructionBase.constructorof(::Type{<:AutoEnzyme{M, A}}) where {M, A}
return InternalAutoEnzymeReconstructor{A}
function ConstructionBase.constructorof(::Type{<:AutoEnzyme{M, A, C}}) where {M, A, C}
return InternalAutoEnzymeReconstructor{A, C}
end

function ConstructionBase.constructorof(::Type{<:AutoForwardDiff{chunksize}}) where {chunksize}
Expand Down
53 changes: 36 additions & 17 deletions src/dense.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,46 +39,65 @@ struct AutoDiffractor <: AbstractADType end
mode(::AutoDiffractor) = ForwardOrReverseMode()

"""
AutoEnzyme{M,A}
AutoEnzyme{M,A,C}

Struct used to select the [Enzyme.jl](https://github.com/EnzymeAD/Enzyme.jl) backend for automatic differentiation.

Defined by [ADTypes.jl](https://github.com/SciML/ADTypes.jl).

# Constructors

AutoEnzyme(; mode::M=nothing, function_annotation::Type{A}=Nothing)
AutoEnzyme(;
mode::Union{EnzymeCore.Mode,Nothing}=nothing,
function_annotation::Type{<:Union{EnzymeCore.Annotation,Nothing}}=Nothing,
chunksize::Union{Int,Float64,Nothing}=nothing,
)

# Type parameters
- `mode::M` determines the autodiff mode (forward or reverse). It can be:

- `A` determines how the function `f` to differentiate is passed to Enzyme. It can be:
+ a mode object from EnzymeCore.jl, like `EnzymeCore.Forward` or `EnzymeCore.Reverse` (possibly modified with additional settings like runtime activity)
+ `nothing` to choose the best mode automatically

+ a subtype of `EnzymeCore.Annotation` (like `EnzymeCore.Const` or `EnzymeCore.Duplicated`) to enforce a given annotation
+ `Nothing` to simply pass `f` and let Enzyme choose the most appropriate annotation
- `A=function_annotation` determines how the function `f` to differentiate is passed to Enzyme. It can be:

# Fields
+ a subtype of `EnzymeCore.Annotation` (like `EnzymeCore.Const` or `EnzymeCore.Duplicated`) to enforce a given annotation

- `mode::M` determines the autodiff mode (forward or reverse). It can be:
+ `Nothing` (the type, not the object) to simply pass `f` and let Enzyme choose the most appropriate annotation
- `C=chunksize` determines the number of derivatives evaluated simultaneously when computing operators like a Jacobian or a forward-mode gradient. It can be:

+ an object subtyping `EnzymeCore.Mode` (like `EnzymeCore.Forward` or `EnzymeCore.Reverse`) if a specific mode is required
+ `nothing` to choose the best mode automatically
+ a positive `Int` to fix a constant chunk size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of wonder if a chunk size of 0 here would be a good way to represent maximum chunk size

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get where you're coming from but I have two objections:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reading that, I still don't understand why a zero chunksize is semantically meaningful?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you denote by $N$ the dimension, $C$ the chunk size, $N_C$ the number of chunks, you have $N = N_C \cdot C$ (plus a remainder possibly). For $N = 0$, you can either pick $C = 0$ or $N_C = 0$. None of those means a lot to be honest, but different backends have different conventions, and ForwardDiff picks a zero chunk size in the zero-length case by default (JuliaDiff/DifferentiationInterface.jl#835 (comment)) while Enzyme doesn't. That's why I'd rather steer clear of this whole mess.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see where you're coming from vis-a-vis forward diff making a different design choice, but I'm not sure that is most critical here.

Alternatively, I kind of wonder, if it would be best to make an EnzymeCore.MaxChunk (which equally can be used by the Enzyme.gradient/jacobian wrappers), which would be the alternate here like there is for EnzymeCore.Mode

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna veto the zero but if you want to add the max chunk setting to EnzymeCore that's fine by me too, your call

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Billy, just following up on this, do you want to add that setting to EnzymeCore?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think thats the right move. if you have cycles before I feel free to open a PR on

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ `Inf` to pick the maximum chunk size, corresponding to the array length
+ `nothing` to choose a good chunk size automatically
"""
struct AutoEnzyme{M, A} <: AbstractADType
struct AutoEnzyme{M, A, C} <: AbstractADType
mode::M

function AutoEnzyme{M, A, C}(mode::M) where {M, A, C}
@assert C isa Union{Nothing, Int, Float64}
if C isa Int
@assert C > 0
elseif C isa Float64
@assert C == Inf
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should give a better error message here

end
return new{M, A, C}(mode)
end
end

function AutoEnzyme(;
mode::M = nothing, function_annotation::Type{A} = Nothing) where {M, A}
return AutoEnzyme{M, A}(mode)
mode::M = nothing,
function_annotation::Type{A} = Nothing,
chunksize::Union{Nothing, Int, Float64} = nothing
) where {M, A}
return AutoEnzyme{M, A, chunksize}(mode)
end

mode(::AutoEnzyme) = ForwardOrReverseMode() # specialized in the extension

function Base.show(io::IO, backend::AutoEnzyme{M, A}) where {M, A}
function Base.show(io::IO, backend::AutoEnzyme{M, A, C}) where {M, A, C}
print(io, AutoEnzyme, "(")
!isnothing(backend.mode) && print(io, "mode=", repr(backend.mode; context = io))
!isnothing(backend.mode) && !(A <: Nothing) && print(io, ", ")
!(A <: Nothing) && print(io, "function_annotation=", repr(A; context = io))
!isnothing(backend.mode) && print(io, "mode=", repr(backend.mode; context = io), ", ")
!(A <: Nothing) && print(io, "function_annotation=", repr(A; context = io), ", ")
!(C === nothing) && print(io, "chunksize=", repr(C; context = io))
print(io, ")")
end

Expand Down
19 changes: 19 additions & 0 deletions test/dense.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ end
@test mode(ad) isa ForwardOrReverseMode
end

get_chunksize(::AutoEnzyme{M, A, C}) where {M, A, C} = C

@testset "AutoEnzyme" begin
ad = AutoEnzyme()
@test ad isa AbstractADType
@test ad isa AutoEnzyme{Nothing, Nothing}
@test mode(ad) isa ForwardOrReverseMode
@test ad.mode === nothing
@test get_chunksize(ad) === nothing

ad = AutoEnzyme(; mode = EnzymeCore.Forward)
@test ad isa AbstractADType
Expand All @@ -50,6 +53,22 @@ end
@test ad isa AutoEnzyme{typeof(EnzymeCore.Reverse), EnzymeCore.Duplicated}
@test mode(ad) isa ReverseMode
@test ad.mode == EnzymeCore.Reverse

ad = AutoEnzyme(; chunksize = nothing)
@test get_chunksize(ad) === nothing

ad = AutoEnzyme(; chunksize = 3)
@test get_chunksize(ad) == 3

ad = AutoEnzyme(; chunksize = Inf)
@test get_chunksize(ad) == Inf

ad = AutoEnzyme(; chunksize = 3)
@test get_chunksize(ad) == 3

@test_throws TypeError AutoEnzyme(; chunksize = :big)
@test_throws AssertionError AutoEnzyme(; chunksize = 0)
@test_throws AssertionError AutoEnzyme(; chunksize = 1.3)
end

@testset "AutoFastDifferentiation" begin
Expand Down
1 change: 1 addition & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ for backend in [
ADTypes.AutoEnzyme(mode = :forward),
ADTypes.AutoEnzyme(function_annotation = Val{:forward}),
ADTypes.AutoEnzyme(mode = :reverse, function_annotation = Val{:duplicated}),
ADTypes.AutoEnzyme(chunksize = 2),
ADTypes.AutoFastDifferentiation(),
ADTypes.AutoFiniteDiff(),
ADTypes.AutoFiniteDiff(fdtype = :fd, fdjtype = :fdj, fdhtype = :fdh),
Expand Down
Loading