From e10f9a03133298c312bf5f490dfd031d9405ac89 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Fri, 16 Jun 2023 13:42:51 -0400 Subject: [PATCH 1/2] Add implementation notes to host functionality --- src/KernelAbstractions.jl | 87 ++++++++++++++++++++++++++++++++------- test/runtests.jl | 27 ++++++++++++ 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/KernelAbstractions.jl b/src/KernelAbstractions.jl index 756f40f94..573a02bf8 100644 --- a/src/KernelAbstractions.jl +++ b/src/KernelAbstractions.jl @@ -66,7 +66,6 @@ KernelAbstractions primitives can be used in non-kernel functions. !!! warn This is an experimental feature. - """ macro kernel(config, expr) if config isa Expr && config.head == :(=) && @@ -97,6 +96,9 @@ macro Const end copyto!(::Backend, dest::AbstractArray, src::AbstractArray) Perform a `copyto!` operation that execution ordered with respect to the backend. + +!!! note + Backend implementations **must** implement this function. """ function copyto! end @@ -104,6 +106,9 @@ function copyto! end synchronize(::Backend) Synchronize the current backend. + +!!! note + Backend implementations **must** implement this function. """ function synchronize end @@ -114,12 +119,13 @@ Release the memory of an array for reuse by future allocations and reduce pressure on the allocator. After releasing the memory of an array, it should no longer be accessed. -This function is optional both to implement and call. -If not implemented for a particular backend, default action is a no-op. -Otherwise, it should be defined for backend's array type. - !!! note On CPU backend this is always a no-op. + +!!! note + Backend implementations **may** implement this function. + If not implemented for a particular backend, default action is a no-op. + Otherwise, it should be defined for backend's array type. """ function unsafe_free! end @@ -393,9 +399,17 @@ constify(arg) = adapt(ConstAdaptor(), arg) ### """ - Abstract type for all KernelAbstractions backends. + +Abstract type for all KernelAbstractions backends. """ abstract type Backend end + +""" +Abstract type for all GPU based KernelAbstractions backends. + +!!! note + New backend implementations **must** sub-type this abstract type. +""" abstract type GPU <: Backend end """ @@ -412,6 +426,11 @@ struct CPU <: Backend CPU(;static::Bool=false) = new(static) end +""" + isgpu(::Backend)::Bool + +Returns true for all [`GPU`](@ref) backends. +""" isgpu(::GPU) = true isgpu(::CPU) = false @@ -420,6 +439,10 @@ isgpu(::CPU) = false get_backend(A::AbstractArray)::Backend Get a [`Backend`](@ref) instance suitable for array `A`. + +!!! note + Backend implementations **must** provide `get_backend` for their custom array type. + It should be the same as the return type of [`allocate`](@ref) """ function get_backend end @@ -438,39 +461,61 @@ get_backend(::Array) = CPU() Adapt.adapt_storage(::CPU, a::Array) = a """ - allocate(::Backend, Type, dims...) + allocate(::Backend, Type, dims...)::AbstractArray Allocate a storage array appropriate for the computational backend. + +!!! note + Backend implementations **must** implement `allocate(::NewBackend, T, dims::Tuple)` +""" +allocate(backend::Backend, T, dims...) = allocate(backend, T, dims) +allocate(backend::Backend, T, dims::Tuple) = throw(MethodError(allocate, (backend, T, dims))) + """ -allocate(backend, T, dims...) = return allocate(backend, T, dims) + zeros(::Backend, Type, dims...)::AbstractArray -zeros(backend, T, dims...) = zeros(backend, T, dims) -function zeros(backend, ::Type{T}, dims::Tuple) where T +Allocate a storage array appropriate for the computational backend filled with zeros. +""" +zeros(backend::Backend, T, dims...) = zeros(backend, T, dims) +function zeros(backend::Backend, ::Type{T}, dims::Tuple) where T data = allocate(backend, T, dims...) fill!(data, zero(T)) return data end -ones(backend, T, dims...) = ones(backend, T, dims) -function ones(backend, ::Type{T}, dims::Tuple) where T +""" + ones(::Backend, Type, dims...)::AbstractArray + +Allocate a storage array appropriate for the computational backend filled with ones. +""" +ones(backend::Backend, T, dims...) = ones(backend, T, dims) +function ones(backend::Backend, ::Type{T}, dims::Tuple) where T data = allocate(backend, T, dims) fill!(data, one(T)) return data end """ - supports_atomics(::Backend) + supports_atomics(::Backend)::Bool Returns whether `@atomic` operations are supported by the backend. + +!!! note + Backend implementations **must** implement this function, + only if they **do not** support atomic operations with Atomix. """ -supports_atomics(backend) = true +supports_atomics(backend::Backend) = true """ - supports_float64(::Backend) + supports_float64(::Backend)::Bool Returns whether `Float64` values are supported by the backend. + +!!! note + Backend implementations **must** implement this function, + only if they **do not** support `Float64`. """ -supports_float64(backend) = true +supports_float64(backend::Backend) = true """ priority!(::Backend, prio::Symbol) @@ -479,6 +524,9 @@ Set the priority for the backend stream/queue. This is an optional feature that backends may or may not implement. If a backend shall support priorities it must accept `:high`, `:normal`, `:low`. Where `:normal` is the default. + +!!! note + Backend implementations **may** implement this function. """ function priority!(::Backend, prio::Symbol) if !(prio in (:high, :normal, :low)) @@ -501,6 +549,13 @@ import .NDIteration: get Kernel closure struct that is used to represent the backend kernel on the host. `WorkgroupSize` is the number of workitems in a workgroup. + +!!! note + Backend implementations **must** implement: + ``` + (kernel::Kernel{<:NewBackend})(args...; ndrange=nothing, workgroupsize=nothing) + ``` + As well as the on-device functionality. """ struct Kernel{Backend, WorkgroupSize<:_Size, NDRange<:_Size, Fun} backend::Backend diff --git a/test/runtests.jl b/test/runtests.jl index c961aef4d..d287d8b4d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,3 +20,30 @@ kern_static(CPU(static=true), (1,))(A, ndrange=length(A)) @kernel cpu=false function my_no_cpu_kernel(a) end @test_throws ErrorException("This kernel is unavailable for backend CPU") my_no_cpu_kernel(CPU()) + +struct NewBackend <: KernelAbstractions.GPU end +@testset "Default host implementation" begin + backend = NewBackend() + @test KernelAbstractions.isgpu(backend) == true + + @test_throws MethodError KernelAbstractions.synchronize(backend) + + @test_throws MethodError KernelAbstractions.allocate(backend, Float32, 1) + @test_throws MethodError KernelAbstractions.allocate(backend, Float32, (1,)) + @test_throws MethodError KernelAbstractions.allocate(backend, Float32, 1, 2) + + @test_throws MethodError KernelAbstractions.zeros(backend, Float32, 1) + @test_throws MethodError KernelAbstractions.ones(backend, Float32, 1) + + @test KernelAbstractions.supports_atomics(backend) == true + @test KernelAbstractions.supports_float64(backend) == true + + @test KernelAbstractions.priority!(backend, :high) === nothing + @test KernelAbstractions.priority!(backend, :normal) === nothing + @test KernelAbstractions.priority!(backend, :low) === nothing + + @test_throws ErrorException KernelAbstractions.priority!(backend, :middle) + + kernel = my_no_cpu_kernel(backend) + @test_throws MethodError kernel() +end From a1e3f8ad2a70ee0ca4efd3738a3218c398ff8abd Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 20 Jun 2023 18:25:13 -0400 Subject: [PATCH 2/2] Fix test --- src/KernelAbstractions.jl | 4 ++-- test/convert.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/KernelAbstractions.jl b/src/KernelAbstractions.jl index 573a02bf8..de8098edf 100644 --- a/src/KernelAbstractions.jl +++ b/src/KernelAbstractions.jl @@ -504,7 +504,7 @@ Returns whether `@atomic` operations are supported by the backend. Backend implementations **must** implement this function, only if they **do not** support atomic operations with Atomix. """ -supports_atomics(backend::Backend) = true +supports_atomics(::Backend) = true """ supports_float64(::Backend)::Bool @@ -515,7 +515,7 @@ Returns whether `Float64` values are supported by the backend. Backend implementations **must** implement this function, only if they **do not** support `Float64`. """ -supports_float64(backend::Backend) = true +supports_float64(::Backend) = true """ priority!(::Backend, prio::Symbol) diff --git a/test/convert.jl b/test/convert.jl index 9103c9cee..95a9a822f 100644 --- a/test/convert.jl +++ b/test/convert.jl @@ -44,7 +44,7 @@ using KernelAbstractions, Test end function convert_testsuite(backend, ArrayT) - ET = KernelAbstractions.supports_float64(backend) ? Float64 : Float32 + ET = KernelAbstractions.supports_float64(backend()) ? Float64 : Float32 N = 32 d_A = ArrayT([rand(ET)*3 for i = 1:N])