I've been trying to create a Makefile
that does the following. I have a C++
program that calls CUDA
functions, so I've got a set of .cpp
and .cu
files, with all CUDA
kernels in the .cu
files.
For every kernel call, I also have a CPU version of that function, so I have code in my .cpp
files that looks like this
if (with_cuda)
{
call_kernel(parameters);
}
else
{
call_cpu_function(parameters);
}
The call_kernel
function is defined in the .cu
files and then calls the actual kernel kernel<<<X,X>>>(...)
.
I want to be able to compile my program in a system without either a GPU
or the CUDA
toolkit. I can easily encapsulate the functions that will call kernels with some macro, for instance, the code above might look like
if (with_cuda)
{
#ifdef WITH_CUDA
call_kernel(parameters);
#endif
}
else
{
call_cpu_function(parameters);
}
And then compile either by g++ files.cpp
without CUDA
support, or nvcc files.cpp other_files.cu -DWITH_CUDA
And I should encapsulate all other includes to CUDA
libraries and so on with the same macro #ifdef...
How would I make a Makefile
that looks for the CUDA
toolkit installed and then does a compilation with nvcc
, or with g++
if it's not installed? I've looked into either writing a Makefile
directly, using cmake, or autoconf with a configure script, but I'm quite lost, so I'm open to using any other method that might be preferable.
Additionally, if that macro encapsulation of every call to a kernel is not the standard or the best way to do this kind of thing, I'm also happy to change how I'm approaching this. I can see a potential issue of having to do an if
statement for each call as in the code above besides the macro ifdef
, so I might compile without CUDA
. Still, then the bool with_cuda
is set to true for whatever reason, and no function is called, so I can see why this might not be a good solution.
You don't need to jump through all of these hoops, like conditionally-compiling the same file with different compilers, or spelling out an if-cuda-else condition in many locations. Instead, consider using linking mechanisms.
So, when in your C++ file, you have a:
do_stuff(params);
call - only provide a declaration of this function, but no definition.
Now, either manually in your Makefile
- or better yet, or auto-generated Makefile
, see below - you choose between one of two possibilities:
.cu
file with the kernel and a do_stuff()
host-side wrapper..cpp
file with a CPU-based implementation of do_stuff()
... and link the object file with calls do_stuff()
with either one implementation or the other.
Here's how you would do this with CMake: You write this CMakeLists.txt
file in your project directory:
cmake_minimum_required(VERSION 3.8)
include(CheckLanguage)
project(myapp LANGUAGES CXX)
add_executable(myapp calls_dostuff.cpp)
check_language(CUDA)
if (CMAKE_CUDA_COMPILER)
enable_language(CUDA)
add_dependencies(myapp dostuff_impl_calling_gpu_kernel.cu)
else()
add_dependencies(myapp dostuff_cpu_impl.cpp)
endif()
... and then invoke:
cmake -B my_build_path -S project_dir_path -G "Unix Makefiles"
to generate a Makefile
into my_build_path
for a project locate at project_dir_path
.