Skip to content

Commit 8d29055

Browse files
authored
Merge pull request #199 from omlins/ad_deferred
Extend AD wrappers
2 parents d28753c + 8409bbb commit 8d29055

8 files changed

Lines changed: 126 additions & 67 deletions

File tree

src/AD.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Provides GPU-compatible wrappers for automatic differentiation functions of the
77
import ParallelStencil.AD
88
99
# Functions
10-
- `autodiff_deferred!(mode, f, args...)`: wraps function `Enzyme.autodiff_deferred`, calling it as `Enzyme.autodiff_deferred(mode, f, Const, args...)`, promoting all arguments of `args` and `f` that are not `Enzyme.Annotation` to `Enzyme.Const`. Important: the return type activity is automatically inserted as 3rd argument as it is always `Enzyme.Const`, given that `@parallel` and `@parallel_indices` functions must return nothing.
11-
- `autodiff_deferred_thunk!(mode, f, args...)`: wraps function `Enzyme.autodiff_deferred_thunk`, calling it as `Enzyme.autodiff_deferred_thunk(mode, f, Const, args...)`, promoting all arguments of `args` and `f` that are not `Enzyme.Annotation` to `Enzyme.Const`. Important: the return type activity is automatically inserted as 3rd argument as it is always `Enzyme.Const`, given that `@parallel` and `@parallel_indices` functions must return nothing.
10+
- `autodiff_deferred!`: wraps function `Enzyme.autodiff_deferred`, and supports the same arguments. Additionally, it promotes all arguments that are not `Enzyme.Annotation` to `Enzyme.Const` and automatically inserts the return type activity as 3rd argument if omitted; as a result, the function can be called also as, e.g., `autodiff_deferred!(Enzyme.Reverse, f!,...)`, besides the fully explicit form, e.g., `autodiff_deferred!(Enzyme.Reverse, Enzyme.Const(f!), Enzyme.Const,...)`.
11+
- `autodiff_deferred_thunk!`: wraps function `Enzyme.autodiff_deferred_thunk`, and supports the same arguments. Additionally, it applies the same argument promotion and return type activity insertion as `autodiff_deferred!` (see above).
1212
1313
# Examples
1414
const USE_GPU = true

src/ParallelKernel/EnzymeExt/AD.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Provides GPU-compatible wrappers for automatic differentiation functions of the
77
import ParallelKernel.AD
88
99
# Functions
10-
- `autodiff_deferred!(mode, f, args...)`: wraps function `Enzyme.autodiff_deferred`, calling it as `Enzyme.autodiff_deferred(mode, f, Const, args...)`, promoting all arguments of `args` and `f` that are not `Enzyme.Annotation` to `Enzyme.Const`. Important: the return type activity is automatically inserted as 3rd argument as it is always `Enzyme.Const`, given that `@parallel` and `@parallel_indices` functions must return nothing.
11-
- `autodiff_deferred_thunk!(mode, f, args...)`: wraps function `Enzyme.autodiff_deferred_thunk`, calling it as `Enzyme.autodiff_deferred_thunk(mode, f, Const, args...)`, promoting all arguments of `args` and `f` that are not `Enzyme.Annotation` to `Enzyme.Const`. Important: the return type activity is automatically inserted as 3rd argument as it is always `Enzyme.Const`, given that `@parallel` and `@parallel_indices` functions must return nothing.
10+
- `autodiff_deferred!`: wraps function `Enzyme.autodiff_deferred`, and supports the same arguments. Additionally, it promotes all arguments that are not `Enzyme.Annotation` to `Enzyme.Const` and automatically inserts the return type activity as 3rd argument if omitted; as a result, the function can be called also as, e.g., `autodiff_deferred!(Enzyme.Reverse, f!,...)`, besides the fully explicit form, e.g., `autodiff_deferred!(Enzyme.Reverse, Enzyme.Const(f!), Enzyme.Const,...)`.
11+
- `autodiff_deferred_thunk!`: wraps function `Enzyme.autodiff_deferred_thunk`, and supports the same arguments. Additionally, it applies the same argument promotion and return type activity insertion as `autodiff_deferred!` (see above).
1212
1313
To see a description of a function type `?<functionname>`.
1414
"""

src/ParallelKernel/EnzymeExt/autodiff_gpu.jl

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,35 @@ function promote_to_const(args::Vararg{Any,N}) where N
2525
end
2626
end
2727

28-
function ParallelStencil.ParallelKernel.AD.autodiff_deferred!(mode, f, args::Vararg{Any,N}) where N # NOTE: minimal specialization is required to avoid overwriting the default method
29-
f = promote_to_const(f)[1]
30-
args = promote_to_const(args...)
31-
Enzyme.autodiff_deferred(mode, f, Enzyme.Const, args...)
32-
return
33-
end
34-
35-
function ParallelStencil.ParallelKernel.AD.autodiff_deferred_thunk!(mode, f, args::Vararg{Any,N}) where N # NOTE: minimal specialization is required to avoid overwriting the default method
36-
f = promote_to_const(f)[1]
37-
args = promote_to_const(args...)
38-
Enzyme.autodiff_deferred_thunk(mode, f, Enzyme.Const, args...)
39-
return
40-
end
28+
29+
function ParallelStencil.ParallelKernel.AD.autodiff_deferred!(mode, f, ::Type{T}, args::Vararg{Any,N}) where {T<:Enzyme.Annotation, N} # NOTE: minimal specialization is required to avoid overwriting the default method
30+
f = promote_to_const(f)[1]
31+
args = promote_to_const(args...)
32+
Enzyme.autodiff_deferred(mode, f, T, args...)
33+
return
34+
end
35+
36+
function ParallelStencil.ParallelKernel.AD.autodiff_deferred!(mode, f, args::Vararg{Any,N}) where N # NOTE: minimal specialization is required to avoid overwriting the default method
37+
f = promote_to_const(f)[1]
38+
args = promote_to_const(args...)
39+
Enzyme.autodiff_deferred(mode, f, Enzyme.Const, args...)
40+
return
41+
end
42+
43+
44+
function ParallelStencil.ParallelKernel.AD.autodiff_deferred_thunk!(mode, f, ::Type{T}, args::Vararg{Any,N}) where {T<:Enzyme.Annotation, N} # NOTE: minimal specialization is required to avoid overwriting the default method
45+
f = promote_to_const(f)[1]
46+
args = promote_to_const(args...)
47+
Enzyme.autodiff_deferred_thunk(mode, f, T, args...)
48+
return
49+
end
50+
51+
function ParallelStencil.ParallelKernel.AD.autodiff_deferred_thunk!(mode, f, args::Vararg{Any,N}) where N # NOTE: minimal specialization is required to avoid overwriting the default method
52+
f = promote_to_const(f)[1]
53+
args = promote_to_const(args...)
54+
Enzyme.autodiff_deferred_thunk(mode, f, Enzyme.Const, args...)
55+
return
56+
end
4157

4258

4359
## FUNCTIONS TO CHECK EXTENSIONS SUPPORT

test/ParallelKernel/test_kernel_language.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ eval(:(
5757
@test @prettystring(1, @sharedMem($FloatDefault, (2,3))) == "CUDA.@cuDynamicSharedMem $(nameof($FloatDefault)) (2, 3)"
5858
# @test @prettystring(1, @pk_show()) == "CUDA.@cushow"
5959
# @test @prettystring(1, @pk_println()) == "CUDA.@cuprintln"
60-
elseif $package == $AMDGPU
60+
elseif $package == $PKG_AMDGPU
6161
@test @prettystring(1, @gridDim()) == "AMDGPU.gridGroupDim()"
6262
@test @prettystring(1, @blockIdx()) == "AMDGPU.workgroupIdx()"
6363
@test @prettystring(1, @blockDim()) == "AMDGPU.workgroupDim()"
@@ -208,14 +208,16 @@ eval(:(
208208
Bshfl_up = similar(A)
209209
Bshfl_down = similar(A)
210210
Bshfl_xor = similar(A)
211+
Bwarpsize = Vector{Int}(undef, N)
212+
Blaneid = Vector{Int}(undef, N)
211213

212-
@parallel_indices (ix) function kernel_semantics!(Bout_any, Bout_all, Bout_ballot, Bshfl, Bshfl_up, Bshfl_down, Bshfl_xor, A, P)
214+
@parallel_indices (ix) function kernel_semantics!(Bout_any, Bout_all, Bout_ballot, Bshfl, Bshfl_up, Bshfl_down, Bshfl_xor, Bwarpsize, Blaneid, A, P)
213215
m = @active_mask()
214216
w = @warpsize()
215217
l = @laneid()
216-
# basic invariants under CPU model
217-
@test w == 1
218-
@test l == 1
218+
# store values for verification outside kernel
219+
Bwarpsize[ix] = w
220+
Blaneid[ix] = l
219221
# shuffle identities
220222
Bshfl[ix] = @shfl_sync(m, A[ix], l)
221223
Bshfl_up[ix] = @shfl_up_sync(m, A[ix], 1)
@@ -228,8 +230,11 @@ eval(:(
228230
Bout_ballot[ix] = @vote_ballot_sync(m, pa)
229231
return
230232
end
231-
@parallel (1:N) kernel_semantics!(Bout_any, Bout_all, Bout_ballot, Bshfl, Bshfl_up, Bshfl_down, Bshfl_xor, A, P)
233+
@parallel (1:N) kernel_semantics!(Bout_any, Bout_all, Bout_ballot, Bshfl, Bshfl_up, Bshfl_down, Bshfl_xor, Bwarpsize, Blaneid, A, P)
232234

235+
# basic invariants under CPU model
236+
@test all(Bwarpsize .== 1)
237+
@test all(Blaneid .== 1)
233238
@test all(Bshfl .== A)
234239
@test all(Bshfl_up .== A)
235240
@test all(Bshfl_down .== A)

test/ParallelKernel/test_parallel.jl

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@ eval(:(
103103
@test maxsize(BitstypeStruct(5, 6.0), 8, (x=[9 9; 9 9; 9 9], y=[9 9; 9 9; 9 9]), (x=[7 7 7; 7 7 7], y=[7 7 7; 7 7 7])) == (3, 3, 1)
104104
end;
105105
end;
106-
@testset "@parallel ∇" begin
107-
@test @prettystring(1, @parallel=B->f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.Const(A), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
108-
@test @prettystring(1, @parallel=(A->Ā, B->) f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
109-
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
110-
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward ad_annotations=(Duplicated=B) f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.Duplicated(B, B̄), Enzyme.Const(a))"
111-
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward ad_annotations=(Duplicated=(B,A), Active=b) f!(A, B, a, b)) == "@parallel configcall = f!(A, B, a, b) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.Duplicated(A, Ā), Enzyme.Duplicated(B, B̄), Enzyme.Const(a), Enzyme.Active(b))"
112-
@test @prettystring(1, @parallel=(V.x->.x, V.y->.y) f!(V.x, V.y, a)) == "@parallel configcall = f!(V.x, V.y, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.DuplicatedNoNeed(V.x, V̄.x), Enzyme.DuplicatedNoNeed(V.y, V̄.y), Enzyme.Const(a))"
113-
end;
114-
@testset "AD.autodiff_deferred! | @parallel ∇ (numerical)" begin
115-
@static if $package == $PKG_THREADS
106+
@static if $package != $PKG_POLYESTER # Enzyme does not support Polyester.
107+
@testset "@parallel ∇" begin
108+
@test @prettystring(1, @parallel=B->f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.Const(A), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
109+
@test @prettystring(1, @parallel=(A->Ā, B->B̄) f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
110+
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.DuplicatedNoNeed(B, B̄), Enzyme.Const(a))"
111+
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward ad_annotations=(Duplicated=B) f!(A, B, a)) == "@parallel configcall = f!(A, B, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.DuplicatedNoNeed(A, Ā), Enzyme.Duplicated(B, B̄), Enzyme.Const(a))"
112+
@test @prettystring(1, @parallel=(A->Ā, B->B̄) ad_mode=Enzyme.Forward ad_annotations=(Duplicated=(B,A), Active=b) f!(A, B, a, b)) == "@parallel configcall = f!(A, B, a, b) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Forward, f!, Enzyme.Duplicated(A, Ā), Enzyme.Duplicated(B, B̄), Enzyme.Const(a), Enzyme.Active(b))"
113+
@test @prettystring(1, @parallel=(V.x->.x, V.y->.y) f!(V.x, V.y, a)) == "@parallel configcall = f!(V.x, V.y, a) ParallelStencil.ParallelKernel.AD.autodiff_deferred!(Enzyme.Reverse, f!, Enzyme.DuplicatedNoNeed(V.x, V̄.x), Enzyme.DuplicatedNoNeed(V.y, V̄.y), Enzyme.Const(a))"
114+
end;
115+
@testset "ad numerical" begin
116116
N = 16
117117
a = 6.5
118118
A = @rand(N)
@@ -133,16 +133,30 @@ eval(:(
133133
end
134134
return
135135
end
136-
@parallel configcall=f!(A, B, a) AD.autodiff_deferred!(Enzyme.Reverse, f!, DuplicatedNoNeed(A, Ā), DuplicatedNoNeed(B, B̄), Const(a)) # NOTE: f! is automatically promoted to Const.
137136
Enzyme.autodiff_deferred(Enzyme.Reverse, Const(g!), Const, DuplicatedNoNeed(A_ref, Ā_ref), DuplicatedNoNeed(B_ref, B̄_ref), Const(a))
138-
@test Array(Ā) Ā_ref
139-
@test Array(B̄) B̄_ref
140-
= @ones(N)
141-
= @ones(N)
142-
@parallel=(A->Ā, B->B̄) f!(A, B, a)
143-
@test Array(Ā) Ā_ref
144-
@test Array(B̄) B̄_ref
145-
end
137+
@testset "AD.autodiff_deferred!" begin
138+
@parallel configcall=f!(A, B, a) AD.autodiff_deferred!(Enzyme.Reverse, f!, DuplicatedNoNeed(A, Ā), DuplicatedNoNeed(B, B̄), Const(a)) # NOTE: f! is automatically promoted to Const(f!) and the return type Const is inserted.
139+
@test Array(Ā) Ā_ref
140+
@test Array(B̄) B̄_ref
141+
= @ones(N)
142+
= @ones(N)
143+
@parallel configcall=f!(A, B, a) AD.autodiff_deferred!(Enzyme.Reverse, f!, Const, DuplicatedNoNeed(A, Ā), DuplicatedNoNeed(B, B̄), Const(a)) # NOTE: f! is automatically promoted to Const(f!)
144+
@test Array(Ā) Ā_ref
145+
@test Array(B̄) B̄_ref
146+
= @ones(N)
147+
= @ones(N)
148+
@parallel configcall=f!(A, B, a) AD.autodiff_deferred!(Enzyme.Reverse, Const(f!), Const, DuplicatedNoNeed(A, Ā), DuplicatedNoNeed(B, B̄), Const(a)) # NOTE: no automatic promotion or insertion here.
149+
@test Array(Ā) Ā_ref
150+
@test Array(B̄) B̄_ref
151+
end;
152+
@testset "@parallel ∇ (numerical)" begin
153+
= @ones(N)
154+
= @ones(N)
155+
@parallel=(A->Ā, B->B̄) f!(A, B, a) # NOTE: expands to the same as above
156+
@test Array(Ā) Ā_ref
157+
@test Array(B̄) B̄_ref
158+
end;
159+
end;
146160
end;
147161
@testset "@parallel_indices" begin
148162
@testset "inbounds" begin

test/runtests.jl

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ function runtests(testfiles=String[])
1515
istest(f) = endswith(f, ".jl") && startswith(basename(f), "test_")
1616
testfiles = isempty(testfiles) ? sort(filter(istest, vcat([joinpath.(root, files) for (root, dirs, files) in walkdir(testdir)]...))) : testfiles
1717

18-
nerror = 0
19-
errorfiles = String[]
18+
nabort = 0
19+
nfail = 0
20+
abortfiles = String[]
21+
failfiles = String[]
2022
printstyled("Testing package ParallelStencil.jl\n"; bold=true, color=:white)
2123

2224
if (PKG_CUDA in SUPPORTED_PACKAGES && !CUDA.functional())
@@ -38,25 +40,28 @@ function runtests(testfiles=String[])
3840
println("$f")
3941
continue
4042
end
41-
cmd = `$exename -O3 --startup-file=no $(joinpath(testdir, f))`
43+
cmd = `$exename --color=yes -O3 --startup-file=no $(joinpath(testdir, f))`
4244
stdout_path = tempname()
4345
stderr_path = tempname()
4446
stdout_content = ""
4547
stderr_content = ""
48+
proc = nothing
4649
try
4750
open(stdout_path, "w") do stdout_io
4851
open(stderr_path, "w") do stderr_io
49-
proc = run(pipeline(Cmd(cmd; ignorestatus=true), stdout=stdout_io, stderr=stderr_io); wait=false)
50-
wait(proc)
52+
proc = run(pipeline(Cmd(cmd; ignorestatus=true), stdout=stdout_io, stderr=stderr_io); wait=true)
5153
end
5254
end
5355
stdout_content = read(stdout_path, String)
5456
stderr_content = read(stderr_path, String)
5557
print(stdout_content)
5658
print(Base.stderr, stderr_content)
5759
catch ex
58-
println("Test Error: an exception occurred while running the test file $f :")
60+
println("Test Abort: a system-level exception occurred while running the test file $f :")
5961
println(ex)
62+
nabort += 1
63+
push!(abortfiles, f)
64+
continue
6065
finally
6166
if ispath(stdout_path)
6267
rm(stdout_path; force=true)
@@ -66,21 +71,32 @@ function runtests(testfiles=String[])
6671
end
6772
end
6873
if !occursin(r"(?i)test summary", stdout_content)
69-
nerror += 1
70-
push!(errorfiles, f)
74+
nabort += 1
75+
push!(abortfiles, f)
76+
elseif proc !== nothing && !success(proc)
77+
nfail += 1
78+
push!(failfiles, f)
7179
end
7280
end
7381
println("")
74-
if nerror == 0
75-
printstyled("Test suite: all selected test files executed (see above for results).\n"; bold=true, color=:green)
82+
if nabort == 0 && nfail == 0
83+
printstyled("Test suite: all selected test files executed and all tests passed.\n"; bold=true, color=:green)
7684
else
77-
printstyled("Test suite: $nerror test file(s) aborted execution due to error (see above for details); files aborting execution:\n"; bold=true, color=:red)
78-
for f in errorfiles
79-
println(" - $f")
85+
if nfail > 0
86+
printstyled("Test suite: $nfail test files(s) have tests that failed or errored (see above for results); files with failed/errored tests:\n"; bold=true, color=:red)
87+
for f in failfiles
88+
println(" - $f")
89+
end
90+
end
91+
if nabort > 0
92+
printstyled("Test suite: $nabort test file(s) aborted execution due to fatal error (see above for details); files aborting execution:\n"; bold=true, color=:red)
93+
for f in abortfiles
94+
println(" - $f")
95+
end
8096
end
8197
end
8298
println("")
83-
return nerror
99+
return nabort+nfail
84100
end
85101

86102
exit(runtests(ARGS))

0 commit comments

Comments
 (0)