omlins / ParallelStencil.jl

Package for writing high-level code for parallel high-performance stencil computations that can be deployed on both GPUs and CPUs
BSD 3-Clause "New" or "Revised" License
311 stars 31 forks source link

Creating modules that depend on `ParallelStencil.jl` #75

Closed smartalecH closed 3 weeks ago

smartalecH commented 1 year ago

Suppose I wanted to create a module that depends on ParallelStencil.jl (e.g. a self-contained finite-difference code). What's the best way to do this?

Normally this is rather straightforward... but the primary source of ambiguity for me is how to best handle the @init_parallel_stencil() macro.

Naively, I might just call that macro at the top of the script which calls the new module, but it gets hairy when you need to access the created Data class inside of your new module (there's a lot of redundant passing that occurs).

Perhaps a more elegant scenario is to call that macro within the module itself (but then you have a chicken-egg problem when trying to specify the parameters of that macro...)?

I'm curious to see how others tackle this.

albert-de-montserrat commented 1 year ago

Not very elegant, this is what I currently do now:

function environment!(model::PS_Setup{T,N}) where {T,N}
    gpu = model.device == :gpu ? true : false

    # call appropriate FD module
    Base.eval(@__MODULE__, Meta.parse("using ParallelStencil.FiniteDifferences$(N)D"))

    # start ParallelStencil
    if model.device == :gpu
        eval(:(@init_parallel_stencil(CUDA, $T, $N)))
        Base.eval(Main, Meta.parse("using CUDA"))
    else
        @eval begin
            @init_parallel_stencil(Threads, $T, $N)
        end
    end

    generate_myStruct!(N) 

    # includes and exports
    @eval begin
        include("myFile.jl"))
        export myfun
       # etc
    end

and at the top of my script I include:

# setup ParallelStencil.jl environment
model = PS_Setup(:cpu, Float64, 2) # :cpu or :gpu
environment!(model)

I am also curious to know if there's a more elegant wat to handle this.

smartalecH commented 1 year ago

Great! Thanks, @albert-de-montserrat!

It looks like the current strategy then is to "bootstrap" yourself through the loading process (which I guess is how ParallelStencil.jl itself works...)

I wonder if there's a cleaner (and more flexible) way to do this?

korbinian90 commented 1 year ago

I'm using simply this to automatically decide which version to use:

using CUDA, ParallelStencil, ParallelStencil.FiniteDifferences3D

if CUDA.functional()
    @eval @init_parallel_stencil(CUDA, Float32, 3)
else
    @eval @init_parallel_stencil(Threads, Float32, 3)
end

The user cannot decide which version to use, however.

omlins commented 4 weeks ago

In this issue has been fully solved meanwhile: all backend-related dependencies were moved into extensions and init_parallel_stencil is now simply to be called once per module after using ParallelStencil. The following test project illustrates how to create a package embracing the extensions feature: https://github.com/omlins/ParallelStencil.jl/tree/main/test/test_projects/Diffusion