Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make alpaka_add_executable obsolete #1793

Open
bernhardmgruber opened this issue Sep 19, 2022 · 17 comments
Open

Make alpaka_add_executable obsolete #1793

bernhardmgruber opened this issue Sep 19, 2022 · 17 comments
Labels
Type:CMake Related to build system

Comments

@bernhardmgruber
Copy link
Member

For building executables using alpaka, we need to use alpaka_add_executable instead of add_executable. AFAIK, the reason is that in case the CUDA backend is active, we need to mark all source files as CUDA files instead of CXX files for CMake. While this is understandable, it still puts a burden on users and I wonder whether we cannot just make the normal add_executable work.

This issue has been mentioned a few times already in #919 and also briefly in #1779.

@bernhardmgruber bernhardmgruber added the Type:CMake Related to build system label Sep 19, 2022
@bernhardmgruber
Copy link
Member Author

The definition of alpaka_add_executable.

@SimeonEhrig
Copy link
Member

I checked already if it is possible via target_link_library. 9 months before, the answer was no.
https://gitlab.kitware.com/cmake/cmake/-/issues/22971

@bernhardmgruber
Copy link
Member Author

I checked already if it is possible via target_link_library. 9 months before, the answer was no. https://gitlab.kitware.com/cmake/cmake/-/issues/22971

This is the central comment for me:

target_link_libraries can propagate "usage requirements" from dependencies, but those are all target-wide. We have no model for affecting source file properties in consuming targets via usage requirements. We also don't have a model for per-target properties of a source file.

@psychocoderHPC
Copy link
Member

We need to tag files which language should be used. This is what alpaka_add_executable currently is doing. I thought a cmake target is enough but maybe not :-(

@bernhardmgruber
Copy link
Member Author

I quickly tried the idea presented here to run a cmake function after all targets have been created and find the user's targets which link against alpaka, so I can change the source file language of these targets. It seem however, that the solution on stackoverflow only works with generator expressions, because the MAGIC property was not propagated yet before generation time. But we would need to know this before generation time to change the source language so CMake can generate accordingly.

@bernhardmgruber
Copy link
Member Author

We discussed this in the alpaka VC today: we could add a separate function to annotate the source files of a given target and then use the plain add_executable.

@SimeonEhrig
Copy link
Member

We discussed this in the alpaka VC today: we could add a separate function to annotate the source files of a given target and then use the plain add_executable.

I think it could looks like:

add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
# link with internal target, other libraries ...
target_link_library(mainTgt PRIVATE utils)
alpaka_enable_cuda(mainTgt)

@psychocoderHPC
Copy link
Member

psychocoderHPC commented Sep 20, 2022

@SimeonEhrig You missed to link an alpaka target. If we add an target for any backend anyway I thought you snipped should looks like:

add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
# this function is internally using e.g. SET_SOURCE_FILES_PROPERTIES to set the language if required
alpaka_source_file(main.cpp alpaka::cuda)
target_link_library(mainTgt PRIVATE alpaka::cuda)
# link with internal target, other libraries ...
target_link_library(mainTgt PRIVATE utils)

@bernhardmgruber
Copy link
Member Author

alpaka_enable_cuda(mainTgt)

I would have called it something that does not involve the name CUDA, like target_using_alpaka(target) or sources_using_alpaka(files...). Because in principle we could need more handling than just CUDA.

Also, we would only need to declare source files which can contain device/kernel code. I think we added the host/device API separation at some point, so people could still use and link alpaka, but not use any device functionality. So maybe the function should be called alpaka_declare_kernel_sources(files...).

alpaka_source_file(main.cpp alpaka::cuda)

Same argument here.

target_link_library(mainTgt PRIVATE alpaka::cuda)

Please discuss having targets for specific backends in a separate ticket, or continue at: #919.

@psychocoderHPC
Copy link
Member

@bernhardmgruber I am mixing the discussion from #919 and this because IMO these are not orthogonal topics. The problem is that the current alpaka CMake is based on old CMake workflows mixed with targets. If we talk about modernizing what removing alpaka_add_executale IMO is, we need to see the full picture.

  • target_using_alpaka is not required because there is the cmake way to handle targets target_link_library(myTarget alpaka)
  • sources_using_alpaka(file...) this is what we need but with an additional parameter where we can express which backends should be used.

Also, we would only need to declare source files which can contain device/kernel code. I think we added the host/device API separation at some point, so people could still use and link alpaka, but not use any device functionality. So maybe the function should be called alpaka_declare_kernel_sources(files...).

As I know, currently alpaka is not providing a way to include only host API or device API functionality..

@SimeonEhrig
Copy link
Member

@psychocoderHPC In my opinion, your implementation is to complicated for the default use case. I suggest the following implementation:

enable a specific backend, e.g. CUDA:

function(alpaka_enable_cuda)
  # foreach source in target, mark as CUDA file
  # ...
  target_link_library(tgt PRIVATE alpaka::alpaka)
  target_link_library(tgt PRIVATE alpaka::cuda)
  # ...
endfunction()
add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
# link with internal target, other libraries ...
target_link_library(mainTgt PRIVATE utils)
alpaka_enable_cuda(mainTgt)

add all enabled backends, like the current behavior:

function(alpaka_add_enabled_backends)
  # ...
  if(alpaka_ACC_GPU_CUDA_ENABLED)
    alpaka_enable_cuda(tgt)
  endif()
  if(alpaka_ACC_GPU_HIP_ENABLED)
  # ...
  endif()
endfunction()
add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
# link with internal target, other libraries ...
target_link_library(mainTgt PRIVATE utils)
alpaka_add_enabled_backends(mainTgt)

@psychocoderHPC
Copy link
Member

@SimeonEhrig You missed that we need to annotate source files and not the target. How to link alpaka to the target does not require a new function, we have already target_link_library(mainTgt PRIVATE alpaka).
We need "only" one new function e.g. what @bernhardmgruber suggested sources_using_alpaka() but need a way to express which backends should be used in the annotated source files.

@bernhardmgruber
Copy link
Member Author

You missed that we need to annotate source files and not the target. How to link alpaka to the target does not require a new function, we have already target_link_library(mainTgt PRIVATE alpaka).

No, if you have the target, you can query it for all the source files and annotate those. It is actually the API I would prefer. This is not about how to link against alpaka, which, as you said, is solved by target_link_library(mainTgt PRIVATE alpaka)

@psychocoderHPC
Copy link
Member

psychocoderHPC commented Sep 21, 2022

No, if you have the target, you can query it for all the source files and annotate those.

Ok then I understand it wrong, I thought selecting the files from a target is creating problems.
If you use the files from a target is the order of the commands important?

Is

add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
add_sources(mainTgt PRIVATE  foo.cpp)
alpaka_add_enabled_backends(mainTgt)

equal to

add_executeable(mainTgt)
add_sources(mainTgt PRIVATE main.cpp)
alpaka_add_enabled_backends(mainTgt)
add_sources(mainTgt PRIVATE  foo.cpp)

?

@bernhardmgruber
Copy link
Member Author

Ah, now I get your point! The two examples make a difference, because alpaka_add_enabled_backends would only annotate the files of the passed target at the point of the call.

But both approaches do not solve my original issue to have a standard cmake file without anything alpaka specific. So my conclusion for now is that this cannot be solved in the near future.

@SimeonEhrig
Copy link
Member

I developed a prototype:

alpaka.cmake

option(alpaka_ACC_SERIAL_CPU "serial cpu accelerator" OFF)
option(alpaka_ACC_CUDA "serial cpu accelerator" OFF)

set(alpaka_active_acc "")

if(alpaka_ACC_SERIAL_CPU)
    add_library(alpaka_acc_serial_cpu INTERFACE)
    target_compile_definitions(alpaka_acc_serial_cpu INTERFACE "ALPAKA_SERIAL_CPU")
    add_library(alpaka::acc_serial_cpu ALIAS alpaka_acc_serial_cpu)
    list(APPEND alpaka_active_acc alpaka::acc_serial_cpu)
endif()

if(alpaka_ACC_CUDA)
    enable_language(CUDA)
    add_library(alpaka_acc_cuda INTERFACE)
    target_compile_definitions(alpaka_acc_cuda INTERFACE "ALPAKA_ACC_CUDA")
    add_library(alpaka::acc_cuda ALIAS alpaka_acc_cuda)
    list(APPEND alpaka_active_acc alpaka::acc_cuda)
endif()

function(alpaka_link_library)
    set(tgt ${ARGV0})
    set(alpaka_tgt ${ARGV1})

    if(NOT TARGET ${alpaka_tgt})
            message(FATAL_ERROR "target ${alpaka_tgt} is not enabled")
    endif()

    if(${alpaka_tgt} STREQUAL "alpaka::acc_serial_cpu")
        message(DEBUG "alpaka::acc_serial_cpu used")
    endif()

    if(${alpaka_tgt} STREQUAL "alpaka::acc_cuda") 
        message(DEBUG "alpaka::acc_cuda used")
        get_target_property(cuda_sources ${tgt} SOURCES)
        list(FILTER cuda_sources INCLUDE REGEX "(\\.cpp|\\.cxx)$")

        if("${cuda_sources}" STREQUAL "")
            message(WARNING "alpaka_link_library(${tgt} ${alpaka_tgt}): source list of target ${tgt} is empty")
        endif()

        foreach(cuda_source in ${cuda_sources})
            SET_SOURCE_FILES_PROPERTIES(${cuda_source} PROPERTIES LANGUAGE CUDA)
        endforeach()
    endif()

    target_link_libraries(${tgt} PUBLIC ${alpaka_tgt})
endfunction()

function(alpaka_link_enabled_accs)
    cmake_parse_arguments(PARSE_ARGV 1
        "ARG" # prefix
        "" #options
        "" # singlevalue 
        "EXCLUDE" # multivalue -> exclude target manually
    )

    set(local_alpaka_active_acc ${alpaka_active_acc})
    if(NOT "${ARG_EXCLUDE}" STREQUAL "")
        message(DEBUG "exclude tragets: ${ARG_EXCLUDE}")
        list(REMOVE_ITEM local_alpaka_active_acc ${ARG_EXCLUDE})
    endif()

    foreach(acc ${local_alpaka_active_acc})
        alpaka_link_library(${ARGV0} ${acc})
    endforeach()
endfunction()

CMakeLists.txt

cmake_minimum_required(VERSION 3.18)

# set project name and meta information
project(main
  VERSION 1.0
  LANGUAGES CXX)

  
set(CMAKE_CUDA_ARCHITECTURES 60)

include(alpaka.cmake)

###########################################################
# automatic select, which acc is uesed
###########################################################

add_executable(auto_all)
target_sources(auto_all PRIVATE main.cpp hello_cpu.cpp hello_cuda.cpp)
alpaka_link_enabled_accs(auto_all)

add_executable(auto_exclude_cuda)
target_sources(auto_exclude_cuda PRIVATE main.cpp hello_cpu.cpp)
alpaka_link_enabled_accs(auto_exclude_cuda EXCLUDE alpaka::acc_cuda)

add_executable(auto_exclude_serial_cpu)
target_sources(auto_exclude_serial_cpu PRIVATE main.cpp hello_cuda.cpp)
alpaka_link_enabled_accs(auto_exclude_serial_cpu EXCLUDE alpaka::acc_serial_cpu)

add_executable(auto_exclude_all)
target_sources(auto_exclude_all PRIVATE main.cpp hello_cpu.cpp hello_cuda.cpp)
alpaka_link_enabled_accs(auto_all EXCLUDE alpaka::acc_serial_cpu alpaka::acc_cuda)

###########################################################
# compile source file for specific acc's
###########################################################

if(alpaka_ACC_SERIAL_CPU)
    add_library(hello_cpu OBJECT)
    target_sources(hello_cpu PRIVATE hello_cpu.cpp)
    alpaka_link_library(hello_cpu alpaka::acc_serial_cpu)
endif()

if(alpaka_ACC_CUDA)
    add_library(hello_cuda OBJECT)
    target_sources(hello_cuda PRIVATE hello_cuda.cpp)
    alpaka_link_library(hello_cuda alpaka::acc_cuda)
endif()

###########################################################
# manuall select, which acc is uesed
###########################################################

if(alpaka_ACC_SERIAL_CPU)
    add_executable(manuell_cpu main.cpp)
    target_link_libraries(manuell_cpu PRIVATE hello_cpu)
endif()

if(alpaka_ACC_CUDA)
    add_executable(manuell_cuda main.cpp)
    target_link_libraries(manuell_cuda PRIVATE hello_cuda)
endif()

if(alpaka_ACC_SERIAL_CPU AND alpaka_ACC_CUDA)
    add_executable(manuell_cpu_cuda main.cpp)
    target_link_libraries(manuell_cpu_cuda PRIVATE hello_cpu hello_cuda)
endif()

full project: cmake_prototyp.zip

@ichinii
Copy link
Contributor

ichinii commented Apr 24, 2024

Although taking the burden from the user is great. i found that compilation times can be reduced within a target, by explicitly marking only the files, that make use of alpaka.
I split the contents of the function alpaka_add_excutable into functions for the target and sources.
As a little bonus, this way, the standard add_executable and add_library can be used and multiple calls can appear to mark source files.
Wanted to share my experience, just in case.

function(alpaka_target In_Name)
    if(alpaka_ACC_GPU_CUDA_ENABLE)
        enable_language(CUDA)
        # We have to set this here since CUDA_SEPARABLE_COMPILATION is not propagated by the alpaka::alpaka target.
        if(alpaka_RELOCATABLE_DEVICE_CODE STREQUAL ON)
            set_property(TARGET ${In_Name} PROPERTY CUDA_SEPARABLE_COMPILATION ON)
        elseif(alpaka_RELOCATABLE_DEVICE_CODE STREQUAL OFF)
            set_property(TARGET ${In_Name} PROPERTY CUDA_SEPARABLE_COMPILATION OFF)
        endif()
    endif()
endfunction()

function(alpaka_source)
    if(alpaka_ACC_GPU_CUDA_ENABLE)
        enable_language(CUDA)
        foreach(_file ${ARGN})
            if((${_file} MATCHES "\\.cpp$") OR
               (${_file} MATCHES "\\.cxx$") OR
               (${_file} MATCHES "\\.cu$")
            )
                set_source_files_properties(${_file} PROPERTIES LANGUAGE CUDA)
            endif()
        endforeach()
    endif()
endfunction()

# usage:
add_executable(my_tgt ${all_my_sources})
alpaka_target(my_tgt)
alpaka_source(${my_sources_that_use_alpaka})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type:CMake Related to build system
Projects
None yet
Development

No branches or pull requests

4 participants