Skip to content

Commit 8a29b58

Browse files
authored
Add benchmark tutorial (#139)
1 parent b9d31ed commit 8a29b58

7 files changed

Lines changed: 139 additions & 27 deletions

File tree

DifferentiationInterface/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ We also provide some experimental backends ourselves:
4646
| [FastDifferentiation.jl](https://github.com/brianguenter/FastDifferentiation.jl) | `AutoFastDifferentiation()`, `AutoSparseFastDifferentiation()` |
4747
| [Tapir.jl](https://github.com/withbayes/Tapir.jl) | `AutoTapir()` |
4848

49+
## Installation
50+
51+
In a Julia REPL, run
52+
53+
```julia
54+
julia> using Pkg
55+
56+
julia> Pkg.add(url="https://github.com/gdalle/DifferentiationInterface.jl", subdir="DifferentiationInterface")
57+
```
58+
4959
## Example
5060

5161
```jldoctest readme

DifferentiationInterface/docs/src/tutorial.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,25 @@ f(x::AbstractArray) = sum(abs2, x)
2424
and a random input vector
2525

2626
```@repl tuto
27-
x = [1.0, 2.0, 3.0]
27+
x = [1.0, 2.0, 3.0];
2828
```
2929

3030
To compute its gradient, we need to choose a "backend", i.e. an AD package that DifferentiationInterface.jl will call under the hood.
31-
[ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) is very efficient for low-dimensional inputs, so we'll go with that one.
32-
Most backend types are defined by [ADTypes.jl](https://github.com/SciML/ADTypes.jl) and re-exported by DifferentiationInterface.jl:
31+
Most backend types are defined by [ADTypes.jl](https://github.com/SciML/ADTypes.jl) and re-exported by DifferentiationInterface.jl.
32+
[ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) is very generic and efficient for low-dimensional inputs, so it's a good starting point:
3333

3434
```@repl tuto
3535
backend = AutoForwardDiff()
3636
```
3737

38-
Now we can use DifferentiationInterface.jl to get our gradient:
38+
Now you can use DifferentiationInterface.jl to get the gradient:
3939

4040
```@repl tuto
4141
gradient(f, backend, x)
4242
```
4343

4444
Was that fast?
45-
We can use [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) to answer that question.
45+
[BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) helps you answer that question.
4646

4747
```@repl tuto
4848
@btime gradient($f, $backend, $x);
@@ -54,12 +54,12 @@ More or less what you would get if you just used the API from ForwardDiff.jl:
5454
@btime ForwardDiff.gradient($f, $x);
5555
```
5656

57-
Not bad, but we can do better.
57+
Not bad, but you can do better.
5858

5959
## Overwriting a gradient
6060

61-
Since we know how much space our gradient will occupy, we can pre-allocate that memory and offer it to AD.
62-
Some backends can get a speed boost from this trick.
61+
Since you know how much space your gradient will occupy, you can pre-allocate that memory and offer it to AD.
62+
Some backends get a speed boost from this trick.
6363

6464
```@repl tuto
6565
grad = zero(x)
@@ -72,9 +72,9 @@ Note the double exclamation mark, which is a convention telling you that `grad`
7272
@btime gradient!!($f, _grad, $backend, $x) evals=1 setup=(_grad=similar($x));
7373
```
7474

75-
For some reason the in-place version is not much better than our first attempt.
76-
However, as you can see, it has one less allocation: it corresponds to the gradient vector we provided.
77-
Don't worry, we're not done yet.
75+
For some reason the in-place version is not much better than your first attempt.
76+
However, it has one less allocation, which corresponds to the gradient vector you provided.
77+
Don't worry, you're not done yet.
7878

7979
## Preparing for multiple gradients
8080

@@ -86,15 +86,14 @@ We abstract away the preparation step behind a backend-agnostic syntax:
8686
extras = prepare_gradient(f, backend, x)
8787
```
8888

89-
You don't need to know what that is, you just need to pass it to the gradient operator.
89+
You don't need to know what this object is, you just need to pass it to the gradient operator.
9090

9191
```@repl tuto
9292
grad = zero(x);
9393
grad = gradient!!(f, grad, backend, x, extras)
9494
```
9595

96-
Why, you ask?
97-
Because it is much faster, and allocation-free.
96+
Preparation makes the gradient computation much faster, and (in this case) allocation-free.
9897

9998
```@repl tuto
10099
@btime gradient!!($f, _grad, $backend, $x, _extras) evals=1 setup=(
@@ -105,7 +104,7 @@ Because it is much faster, and allocation-free.
105104

106105
## Switching backends
107106

108-
Now the whole point of DifferentiationInterface.jl is that you can easily experiment with different AD solutions.
107+
The whole point of DifferentiationInterface.jl is that you can easily experiment with different AD solutions.
109108
Typically, for gradients, reverse mode AD might be a better fit.
110109
So let's try the state-of-the-art [Enzyme.jl](https://github.com/EnzymeAD/Enzyme.jl)!
111110

@@ -121,7 +120,7 @@ But once it is done, things run smoothly with exactly the same syntax:
121120
gradient(f, backend2, x)
122121
```
123122

124-
And we can run the same benchmarks:
123+
And you can run the same benchmarks:
125124

126125
```@repl tuto
127126
@btime gradient!!($f, _grad, $backend2, $x, _extras) evals=1 setup=(
@@ -130,7 +129,6 @@ And we can run the same benchmarks:
130129
);
131130
```
132131

133-
Have you seen this?
134-
It's blazingly fast.
135-
And you know what's even better?
136-
You didn't need to look at the docs of either ForwardDiff.jl or Enzyme.jl to achieve top performance with both, or to compare them.
132+
Not only is it blazingly fast, you achieved this speedup without looking at the docs of either ForwardDiff.jl or Enzyme.jl!
133+
In short, DifferentiationInterface.jl allows for easy testing and comparison of AD backends.
134+
If you want to go further, check out the [DifferentiationTest.jl tutorial](https://gdalle.github.io/DifferentiationInterface.jl/DifferentiationInterfaceTest/dev/tutorial/).

DifferentiationInterfaceTest/README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,14 @@ Make it easy to know, for a given function:
2020
- Type stability tests
2121
- Count calls to the function
2222
- Benchmark runtime and allocations
23-
- Weird array types (GPU, static, components)
23+
- Weird array types (GPU, static, components)
24+
25+
## Installation
26+
27+
In a Julia REPL, run
28+
29+
```julia
30+
julia> using Pkg
31+
32+
julia> Pkg.add(url="https://github.com/gdalle/DifferentiationInterface.jl", subdir="DifferentiationInterfaceTest")
33+
```

DifferentiationInterfaceTest/docs/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
44
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
55
DifferentiationInterfaceTest = "a82114a7-5aa3-49a8-9643-716bb13727a3"
66
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
7+
Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
78
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
9+
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
10+
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"

DifferentiationInterfaceTest/docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ makedocs(;
1515
format=Documenter.HTML(),
1616
pages=[
1717
"Home" => "index.md", #
18+
"Tutorial" => "tutorial.md", #
1819
"API reference" => "api.md",
1920
],
2021
)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
```@meta
2+
CurrentModule = Main
3+
```
4+
5+
# Tutorial
6+
7+
We present a typical workflow with DifferentiationInterfaceTest.jl, building on the [DifferentiationInterface.jl tutorial](https://gdalle.github.io/DifferentiationInterface.jl/DifferentiationInterface/dev/tutorial/) (which we encourage you to read first).
8+
9+
```@repl tuto
10+
using DifferentiationInterface, DifferentiationInterfaceTest
11+
import ForwardDiff, Enzyme
12+
import DataFrames
13+
```
14+
15+
## Introduction
16+
17+
The AD backends we want to compare are [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) and [Enzyme.jl](https://github.com/EnzymeAD/Enzyme.jl).
18+
19+
```@repl tuto
20+
backends = [AutoForwardDiff(), AutoEnzyme(Enzyme.Reverse)]
21+
```
22+
23+
To do that, we are going to take gradients of a simple function:
24+
25+
```@repl tuto
26+
f(x::AbstractArray) = sum(sin, x)
27+
```
28+
29+
Of course we know the true gradient mapping:
30+
31+
```@repl tuto
32+
∇f(x::AbstractArray) = cos.(x)
33+
```
34+
35+
DifferentiationInterfaceTest.jl relies with so-called "scenarios", in which you encapsulate the information needed for your test:
36+
37+
- the function `f`
38+
- the input `x` (and output `y` for mutating functions)
39+
- optionally a reference `ref` to check against
40+
41+
There is one scenario per operator, and so here we will use [`GradientScenario`](@ref).
42+
Let us experiment with various input types and sizes:
43+
44+
```@repl tuto
45+
scenarios = [
46+
GradientScenario(f; x=rand(Float64, 3), ref=∇f),
47+
GradientScenario(f; x=rand(Float32, 3, 4), ref=∇f),
48+
GradientScenario(f; x=rand(Float16, 3, 4, 5), ref=∇f),
49+
];
50+
```
51+
52+
## Testing
53+
54+
The main entry point for testing is the function [`test_differentiation`](@ref).
55+
It has many options, but the main ingredients are the following:
56+
57+
```@repl tuto
58+
test_differentiation(
59+
backends, # the backends you want to compare
60+
scenarios, # the scenarios you defined,
61+
correctness=true, # compares values against the reference
62+
type_stability=true, # checks type stability with JET.jl
63+
detailed=true, # prints a detailed test set
64+
)
65+
```
66+
67+
If you are too lazy to manually specify the reference, you can also provide an AD backend as the `correctness` keyword argument, which will serve as the ground truth for comparison.
68+
69+
## Benchmarking
70+
71+
Once you are confident that your backends give the correct answers, you probably want to compare their performance.
72+
This is made easy by the [`benchmark_differentiation`](@ref) function, whose syntax should feel familiar:
73+
74+
```@repl tuto
75+
benchmark_result = benchmark_differentiation(backends, scenarios);
76+
```
77+
78+
The resulting object is a `Vector` of structs, which can easily be converted into a `DataFrame` from [DataFrames.jl](https://github.com/JuliaData/DataFrames.jl):
79+
80+
```@repl tuto
81+
df = DataFrames.DataFrame(benchmark_result)
82+
```
83+
84+
Here's what the resulting `DataFrame` looks like with all its columns.
85+
Note that we only compare (possibly) in-place operators, because they are always more efficient.
86+
87+
```@example tuto
88+
import Markdown, PrettyTables # hide
89+
Markdown.parse(PrettyTables.pretty_table(String, df; backend=Val(:markdown), header=names(df))) # hide
90+
```

DifferentiationInterfaceTest/src/test_differentiation.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@ function test_differentiation(
6969
excluded,
7070
)
7171

72-
title =
73-
"Testing" *
74-
(correctness != false ? " correctness" : "") *
75-
(call_count ? " calls" : "") *
76-
(type_stability ? " types" : "") *
77-
(sparsity ? " sparsity" : "")
72+
title_additions =
73+
(correctness != false ? " + correctness" : "") *
74+
(call_count ? " + calls" : "") *
75+
(type_stability ? " + types" : "") *
76+
(sparsity ? " + sparsity" : "")
77+
title = "Testing" * title_additions[3:end]
7878

7979
prog = ProgressUnknown(; desc="$title", spinner=true, enabled=logging)
8080

0 commit comments

Comments
 (0)