Skip to content
Merged
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
28 changes: 17 additions & 11 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,32 @@ jobs:
- macOS-latest
- windows-latest
arch:
- x64
- default
- x86
exclude:
# Test 32-bit only on Linux
- os: macOS-latest
arch: x86
- os: windows-latest
arch: x86
# No native Apple Silicon binaries for Julia 1.0, test it on an Intel runner instead
- os: macOS-latest
version: "1.0"
include:
# Add a 1.5 job because that's what Invenia actually uses
- os: ubuntu-latest
version: "1.5"
arch: x64
arch: default
- os: macos-15-intel
version: "1.0"
arch: default
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v2
- uses: actions/cache@v6
env:
cache-name: cache-artifacts
with:
Expand All @@ -50,19 +56,19 @@ jobs:
${{ runner.os }}-${{ matrix.arch }}-test-
${{ runner.os }}-${{ matrix.arch }}-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v7
with:
file: lcov.info
files: lcov.info

docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
with:
version: '1.6'
- run: |
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/IntegrationTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ jobs:
# - {user: Invenia, repo: Nabla.jl}

steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
with:
version: ${{ matrix.julia-version }}
arch: x64
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-buildpkg@v1
- name: Clone Downstream
uses: actions/checkout@v2
uses: actions/checkout@v7
with:
repository: ${{ matrix.package.user }}/${{ matrix.package.repo }}
path: downstream
Expand Down
15 changes: 7 additions & 8 deletions .github/workflows/JuliaNightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@ jobs:
name: Julia Nightly - Ubuntu - x64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
with:
version: nightly
arch: x64
- uses: actions/cache@v2
- uses: actions/cache@v6
env:
cache-name: julia-nightly-cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ env.cache-name }}-
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v7
with:
file: lcov.info
files: lcov.info
6 changes: 6 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ Packages like [ExprTools.jl](https://github.com/invenia/ExprTools.jl) can be hel
Its generally fairly simple, though can become complex if you need to handle complicated type-constraints.
Examples are shown below.

Make sure every generated overload requires at least one argument of your AD's type.
An overload such as `(op::typeof(+))(::Vararg{Union{Dual,Float64},2})` also matches
`+(::Float64, ::Float64)`, which is type piracy and is ambiguous with Base's methods.
One way to avoid this is to define one method per position of the first argument of your
AD's type, as the examples do.

The hook is automatically triggered whenever a package is loaded.
It can also be triggers manually using `refresh_rules`(@ref).
This is useful for example if new rules are define in the REPL, or if a package defining rules is modified.
Expand Down
26 changes: 17 additions & 9 deletions test/demos/forwarddiffzero.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,25 @@ function define_dual_overload(sig)
all(argT isa Type && Float64 <: argT for argT in argTs) || return # only handling purely Float64 ops.

N = length(sig.parameters) - 1 # skip the op
fdef = quote
# we use the function call overloading form as it lets us avoid namespacing issues
# as we can directly interpolate the function type into to the AST.
function (op::$opT)(dual_args::Vararg{Union{Dual, Float64}, $N}; kwargs...)
ȧrgs = (NoTangent(), partial.(dual_args)...)
args = (op, primal.(dual_args)...)
y, ẏ = frule(ȧrgs, args...; kwargs...)
return Dual(y, ẏ) # if y, ẏ are not `Float64` this will error.
names = [Symbol(:x, j) for j in 1:N]
# Define one method per position of the first `Dual` argument, so that every method
# requires at least one `Dual` and we never overload `op` for purely `Float64` arguments
# (which would be type piracy). In particular, nothing is defined if `op` takes no arguments.
for i in 1:N
Ts = [fill(Float64, i - 1); Dual; fill(Union{Dual, Float64}, N - i)]
fdef = quote
# we use the function call overloading form as it lets us avoid namespacing issues
# as we can directly interpolate the function type into the AST.
function (op::$opT)($(map((x, T) -> :($x::$T), names, Ts)...); kwargs...)
dual_args = ($(names...),)
ȧrgs = (NoTangent(), partial.(dual_args)...)
args = (op, primal.(dual_args)...)
y, ẏ = frule(ȧrgs, args...; kwargs...)
return Dual(y, ẏ) # if y, ẏ are not `Float64` this will error.
end
end
eval(fdef)
end
eval(fdef)
end

# !Important!: Attach the define function to the `on_new_rule` hook
Expand Down
34 changes: 21 additions & 13 deletions test/demos/reversediffzero.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,30 @@ function define_tracked_overload(sig)
all(argT isa Type && Float64 <: argT for argT in argTs) || return # only handling purely Float64 ops.

N = length(sig.parameters) - 1 # skip the op
fdef = quote
# we use the function call overloading form as it lets us avoid namespacing issues
# as we can directly interpolate the function type into to the AST.
function (op::$opT)(tracked_args::Vararg{Union{Tracked, Float64}, $N}; kwargs...)
args = (op, primal.(tracked_args)...)
y, y_pullback = rrule(args...; kwargs...)
the_tape = get_tape(tracked_args)
y_tracked = Tracked(y, the_tape) do ȳ
# pull this partial back and propagate it to the input's partial store
_, ārgs = Iterators.peel(y_pullback(ȳ))
accum!.(tracked_args, ārgs)
names = [Symbol(:x, j) for j in 1:N]
# Define one method per position of the first `Tracked` argument, so that every method
# requires at least one `Tracked` and we never overload `op` for purely `Float64` arguments
# (which would be type piracy). In particular, nothing is defined if `op` takes no arguments.
for i in 1:N
Ts = [fill(Float64, i - 1); Tracked; fill(Union{Tracked, Float64}, N - i)]
fdef = quote
# we use the function call overloading form as it lets us avoid namespacing issues
# as we can directly interpolate the function type into the AST.
function (op::$opT)($(map((x, T) -> :($x::$T), names, Ts)...); kwargs...)
tracked_args = ($(names...),)
args = (op, primal.(tracked_args)...)
y, y_pullback = rrule(args...; kwargs...)
the_tape = get_tape(tracked_args)
y_tracked = Tracked(y, the_tape) do ȳ
# pull this partial back and propagate it to the input's partial store
_, ārgs = Iterators.peel(y_pullback(ȳ))
accum!.(tracked_args, ārgs)
end
return y_tracked
end
return y_tracked
end
eval(fdef)
end
eval(fdef)
end

# !Important!: Attach the define function to the `on_new_rule` hook
Expand Down
Loading