diff --git a/Project.toml b/Project.toml index 460171c..46f65b1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PlatformAware" uuid = "e7c50b67-2c03-471e-9cf2-69e515d86ecf" authors = ["Francisco Heron de Carvalho Junior and contributors"] -version = "0.4.3" +version = "0.5.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/README.md b/README.md index 907c61f..89365f6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # PlatformAware.jl - [![TagBot](https://github.com/PlatformAwareProgramming/PlatformAware.jl/actions/workflows/TagBot.yml/badge.svg)](https://github.com/decarvalhojunior-fh/PlatformAware.jl/actions/workflows/TagBot.yml) [![CompatHelper](https://github.com/PlatformAwareProgramming/PlatformAware.jl/actions/workflows/CompatHelper.yml/badge.svg)](https://github.com/PlatformAwareProgramming/PlatformAware.jl/actions/workflows/CompatHelper.yml) @@ -37,11 +36,11 @@ This tutorial shows how to create _MyFFT.jl_, demonstrating the basics of how to In the Julia REPL, as shown in the screenshot below, run ```] generate MyFFT.jl``` to create a new project called _MyFFT.jl_, run ```🔙cd("MyFFT.jl")``` to move to the directory of the created project, and ```] activate .``` to enable the current project (_MyFFT.jl_) in the current Julia REPL session. -![f1](docs/src/images/f1.png) +![f1](https://raw.githubusercontent.com/PlatformAwareProgramming/PlatformAware.jl/master/docs/src/images/f1.png) These operations create a standard _"hello world"_ project, with the contents of the following snapshot: -![f2](docs/src/images/f2.png) +![f2](https://raw.githubusercontent.com/PlatformAwareProgramming/PlatformAware.jl/master/docs/src/images/f2.png) ## Installing _PlatformAware.jl_ @@ -52,7 +51,7 @@ Before coding the platform-aware package, it is necessary to add _PlatormAware.j ``` Now, load the _PlatfomAware.jl_ package (```using PlatformAware``` or ```import PlatformAware```) and read the output message: -![f3](docs/src/images/f3.png) +![f3](https://raw.githubusercontent.com/PlatformAwareProgramming/PlatformAware.jl/master/docs/src/images/f3.png) _Platform.toml_ is the _platform description file_, containing a set of key-value pairs, each describing a feature of the underlying platform. It must be created by the user running ```PlatformWare.setup()```, which performs a sequence of feature detection operations on the platform. @@ -69,10 +68,10 @@ module MyFFT import PlatformAware - # setup platorm parameters - @platform parameter clear - @platform parameter accelerator_count - @platform parameter accelerator_api + # setup platorm features (parameters) + @platform feature clear + @platform feature accelerator_count + @platform feature accelerator_api # Fallback kernel @platform default fft(X) = ... @@ -88,7 +87,7 @@ module MyFFT end ``` -The sequence of ```@platorm parameter``` macro declarations specifies the set of platform parameters that will be used by subsequent kernel method declarations, that is, the assumptions that will be made to distinguish them. You can refer to [this table](https://docs.google.com/spreadsheets/d/1n-c4b7RxUduaKV43XrTnt54w-SR1AXgVNI7dN2OkEUc/edit?usp=sharing) for a list of all supported _**platform parameters**_. By default, they are all included. In the case of ```fft```, the kernel methods are differentiated using only two parameters: ```accelerator_count``` and ```accelerator_api```. They denote, respectively, assumptions about the number of accelerator devices and the native API they support. +The sequence of ```@platorm feature``` macro declarations specifies the set of platform parameters that will be used by subsequent kernel method declarations, that is, the assumptions that will be made to distinguish them. You can refer to [this table](https://docs.google.com/spreadsheets/d/1n-c4b7RxUduaKV43XrTnt54w-SR1AXgVNI7dN2OkEUc/edit?usp=sharing) for a list of all supported _**platform parameters**_. By default, they are all included. In the case of ```fft```, the kernel methods are differentiated using only two parameters: ```accelerator_count``` and ```accelerator_api```. They denote, respectively, assumptions about the number of accelerator devices and the native API they support. The ```@platorm default``` macro declares the _default kernel method_, which will be called if none of the assumptions of other kernel methods declared using ```@platform aware``` macro calls are valid. The default kernel must be unique to avoid ambiguity. @@ -115,7 +114,7 @@ Also, you should add _CUDA.jl_, _OpenCL.jl_, _CLFFT.jl_, and _FFFT.jl_ as depend > **NOTE**: [_CLFFT.jl_](https://github.com/JuliaGPU/CLFFT.jl) is not available on JuliaHub due to compatibility issues with recent versions of Julia. We're working with the CLFFT.jl maintainers to address this issue. If you have an error with the CLFFT dependency, point to our _CLFFT.jl_ fork by running ```add https://github.com/JuliaGPU/CLFFT.jl#master```. -As a performance optimization, we can take advantage of platform-aware features to selectively load dependencies, speeding up the loading of _MyFFT.jl_. To do this, we first declare a kernel function called ```which_api``` in _src/MyFFT.jl_, right after the ```@platform parameter``` declaration: +As a performance optimization, we can take advantage of platform-aware features to selectively load dependencies, speeding up the loading of _MyFFT.jl_. To do this, we first declare a kernel function called ```which_api``` in _src/MyFFT.jl_, right after the ```@platform feature``` declaration: ```julia @platform default which_api() = :fftw @@ -145,9 +144,9 @@ module MyFFT using PlatformAware - @platform parameter clear - @platform parameter accelerator_count - @platform parameter accelerator_api + @platform feature clear + @platform feature accelerator_count + @platform feature accelerator_api @platform default which_api() = :fftw @platform aware which_api({accelerator_count::(@atleast 1), accelerator_api::(@api CUDA)}) = :cufft @@ -238,7 +237,7 @@ Therefore, we suggest the following general guideline for package developers who 5. Provide platform-aware methods for each kernel function using the ```@platform aware``` macro. -6. After implementing and testing all platform-aware methods, you have a list of platform parameters that were used to make assumptions about the target execution platform(s). You can optionally instruct the _PlatformAware.jl_ to use only that parameters by using the ``@platform parameter`` macro. +6. After implementing and testing all platform-aware methods, you have a list of platform parameters that were used to make assumptions about the target execution platform(s). You can optionally instruct the _PlatformAware.jl_ to use only that parameters by using the ``@platform feature`` macro. # Contributing diff --git a/src/PlatformAware.jl b/src/PlatformAware.jl index 3384d79..652972a 100644 --- a/src/PlatformAware.jl +++ b/src/PlatformAware.jl @@ -60,6 +60,9 @@ export @just, @unlimited, @api, + @assumption, + platform_feature, + platform_features, PlatformType, QuantifierFeature, QualifierFeature, diff --git a/src/features/features.jl b/src/features/features.jl index 60638eb..d3206c4 100644 --- a/src/features/features.jl +++ b/src/features/features.jl @@ -205,7 +205,7 @@ function identifyAPI_oldversion(api_string) end -function get_api_qualifier(api_string, platform_feature_default) +function get_api_qualifier(api_string) apis = split(api_string,';') @@ -233,16 +233,26 @@ function loadFeaturesSection!(dict, platform_feature, platform_feature_default) for (parameter_id, feature) in dict p = Meta.parse(parameter_id) - v0 = check_blank_feature(p, feature, platform_feature_default) + #=v0 = check_blank_feature(p, feature, platform_feature_default) v = if (isnothing(v0)) feature_type[p] == qualifier ? get_qualifier(feature) : feature_type[p] == api_qualifier ? get_api_qualifier(feature, platform_feature_default) : get_quantifier(feature) else v0 - end - platform_feature[p]= v + end=# + + platform_feature[p]= getFeature(p, feature, platform_feature_default) end end +function getFeature(p, feature, platform_feature_default) + v0 = check_blank_feature(p, feature, platform_feature_default) + return if (isnothing(v0)) + feature_type[p] == qualifier ? get_qualifier(feature) : feature_type[p] == api_qualifier ? get_api_qualifier(feature) : get_quantifier(feature) + else + v0 + end +end + function loadFeatures!(dict, platform_feature_default, platform_feature) loadDBs!() for key in ["node", "processor", "accelerator", "memory", "storage", "interconnection"] diff --git a/src/platform.jl b/src/platform.jl index 4c54af8..23344e9 100644 --- a/src/platform.jl +++ b/src/platform.jl @@ -135,16 +135,16 @@ end # load!() -function setplatform!(parameter_id, actual_type) +function update_platform_feature!(parameter_id, actual_type) state.platform_feature[parameter_id] = actual_type (parameter_id,actual_type) end -function getplatform(parameter_id) +function platform_feature(parameter_id) state.platform_feature[parameter_id] end -function getplatform() +function platform_features() state.platform_feature end @@ -157,7 +157,29 @@ function reset_platform_feature!() for (k,v) in state.platform_feature_all state.platform_feature[k] = v end - for (k,v) in platform_types_all + for (k,v) in state.platform_feature_default_all + state.platform_feature_default[k] = v + end + keys(state.platform_feature) +end + +function all_platform_feature!() + for (k,v) in state.platform_feature_all + if (!haskey(state.platform_feature, k)) + state.platform_feature[k] = v + end + end + for (k,v) in state.platform_feature_default_all + state.platform_feature_default[k] = v + end + keys(state.platform_feature) +end + +function default_platform_feature!() + for (k,v) in state.platform_feature_default_all + state.platform_feature[k] = v + end + for (k,v) in state.platform_feature_default_all state.platform_feature_default[k] = v end keys(state.platform_feature) @@ -169,15 +191,21 @@ function include_platform_feature!(f) keys(state.platform_feature) end -function platform_parameter_macro!(f) - +function platform_parameter_macro!(f, x) if (f == :clear) empty_platform_feature!() elseif (f == :all) + all_platform_feature!() + elseif (f == :reset) reset_platform_feature!() - else + elseif (f == :default) + default_platform_feature!() + elseif (isnothing(x)) check_all(f) include_platform_feature!(f) + else + check_all(f) + update_platform_feature!(f,getFeature(f, string(x), state.platform_feature_default_all)) end end @@ -233,18 +261,37 @@ macro platform(t,f) elseif (t == :aware) denyaddparameter!() return esc(build_kernel_function(f)) - elseif (t == :parameter && getaddparameter()) - platform_parameter_macro!(f) - elseif (t == :parameter && !getaddparameter()) + elseif ((t == :parameter || t == :feature) && getaddparameter()) + platform_parameter_macro!(f, nothing) + elseif ((t == :parameter || t == :feature) && !getaddparameter()) @info "cannot add parameters after including the first kernel method" elseif (t == :assumption) - return :($f) + assumptions_dict[][f.args[1]] = f.args[2] + return nothing + else + platform_syntax_message() + end + end + + macro platform(t,f,x) + if ((t == :parameter || t == :feature) && getaddparameter()) + platform_parameter_macro!(f,x) + elseif ((t == :parameter || t == :feature) && !getaddparameter()) + @info "cannot add parameters after including the first kernel method" else - @info "usage: platform [default | aware] " - @info " platform parameter :()" + platform_syntax_message() end end + const assumptions_dict = Ref(Dict{Symbol,Expr}()) + + function platform_syntax_message() + @info "usage: @platform [default | aware] " + @info " @platform feature [clear | all | reset]" + @info " @platform feature " + @info " @platform feature new_feature" + end + # build_entry_function function build_entry_function(f::Expr) @@ -300,17 +347,17 @@ end function build_kernel_signature(fsign::Expr) fsign_args = copy(fsign.args) (call_node_args, where_vars) = fsign.head == :where ? (popfirst!(fsign_args).args, fsign_args) : (fsign_args, []) + fname = popfirst!(call_node_args) keyword_parameters_node = length(call_node_args) > 0 && typeof(call_node_args[1]) == Expr && call_node_args[1].head == :parameters ? popfirst!(call_node_args) : nothing # takes the platform parameters of the kernel - #aware_parameters_args = length(call_node_args) > 0 && typeof(call_node_args[1]) == Expr && call_node_args[1].head == :braces ? popfirst!(call_node_args).args : [] aware_parameters_args = [] if length(call_node_args) > 0 if typeof(call_node_args[1]) == Expr && call_node_args[1].head == :braces aware_parameters_args = popfirst!(call_node_args).args - #elseif typeof(call_node_args[1]) == Symbol - # arg = popfirst!(call_node_args) - # aware_parameters_args = @eval arg + elseif typeof(call_node_args[1]) == Expr && call_node_args[1].head == :$ + aware_parameters_args = assumptions_dict[][call_node_args[1].args[1]].args + popfirst!(call_node_args) end end diff --git a/test/basics.jl b/test/basics.jl index 7b84ca2..f3cfead 100644 --- a/test/basics.jl +++ b/test/basics.jl @@ -1,24 +1,24 @@ @testset "Basics" begin - @platform parameter clear + @platform feature clear #= for the first 5 kernels =# - @platform parameter accelerator_count - @platform parameter accelerator_manufacturer - @platform parameter accelerator_api - @platform parameter node_count - @platform parameter processor - @platform parameter accelerator_architecture + @platform feature accelerator_count + @platform feature accelerator_manufacturer + @platform feature accelerator_api + @platform feature node_count + @platform feature processor + @platform feature accelerator_architecture #= for all kernels =# - @platform parameter node_memory_size - @platform parameter processor_count - @platform parameter processor_core_count - @platform parameter interconnection_bandwidth - @platform parameter interconnection_latency - @platform parameter accelerator_type - @platform parameter accelerator_memory_size - @platform parameter processor_simd + @platform feature node_memory_size + @platform feature processor_count + @platform feature processor_core_count + @platform feature interconnection_bandwidth + @platform feature interconnection_latency + @platform feature accelerator_type + @platform feature accelerator_memory_size + @platform feature processor_simd # define a kernel @platform default function kernel(x,y,args...; z=0, kwargs...) @@ -30,29 +30,34 @@ x,y,args...; z=1, kwargs...) println(z,": kernel for 1 accelerators of unspecified kind") end + @platform aware function kernel({accelerator_count::@atleast(1,C), accelerator_manufacturer::NVIDIA, accelerator_api::(@api CUDA 3.0)}, x::@atleast(1),y,args...; z=2, kwargs...) where C println(z,": kernel 1 for $C NVIDIA accelerators") end + @platform aware function kernel({accelerator_count::@atleast(1,C), accelerator_manufacturer::NVIDIA, accelerator_api::(@api CUDA 3.0)}, x::@atleast(16),y,args...; z=2, kwargs...) where C println(z,": kernel 2 for $C NVIDIA accelerators") end - @platform aware function kernel({node_count::(@atleast 32), - processor::IntelCore_i7_7500U}, - x,y,args...; z=3, kwargs...) + + @platform assumption some_cluster = {node_count::(@atleast 32), processor::IntelCore_i7_7500U} + + @platform aware function kernel($some_cluster, x,y,args...; z=3, kwargs...) println(z,": kernel optimized to the features of clusters with at least 32 nodes with Intel(R) Core(TM) i7-7500U processors") end + @platform aware function kernel({accelerator_count::(@just 4), accelerator_manufacturer::NVIDIA, accelerator_architecture::Turing}, x,y,args...; z=4, kwargs...) println(z,": kernel for exactly 4 accelerators of NVIDIA's Turing architecture") end + @platform aware function kernel({node_count::(@between 8 16), node_memory_size::(@atleast 16G), processor_count::(@atleast 2), @@ -64,6 +69,7 @@ println(z,": kernel tuned for a cluster of 8 to 16 nodes having at least 2 processors with at least 8 cores each,") println(z,": connected through an intereconnection having at most 32us latency and at least 128Gbs bandwidth.") end + @platform aware function kernel({accelerator_count::(@atleast 1), accelerator_type::FPGA, accelerator_memory_size::(@atleast 16G),