如何将CUDA代码分成多个文件

我正在尝试将一个CUDA程序分成两个独立的.cu文件,以便更接近于在C ++中编写一个真正的应用程序。 我有一个简单的小程序:

在主机和设备上分配内存。
将主机arrays初始化为一系列数字。 将主机arrays复制到设备arrays使用设备内核查找arrays中所有元素的平方将设备arrays复制回主机arrays打印结果

如果我把它全部放在一个.cu文件中并运行它,这很有效。 当我将它分成两个单独的文件时,我开始得到链接错误。 像我最近的所有问题一样,我知道这很小,但它是什么?

KernelSupport.cu

#ifndef _KERNEL_SUPPORT_ #define _KERNEL_SUPPORT_ #include  #include  int main( int argc, char** argv) { int* hostArray; int* deviceArray; const int arrayLength = 16; const unsigned int memSize = sizeof(int) * arrayLength; hostArray = (int*)malloc(memSize); cudaMalloc((void**) &deviceArray, memSize); std::cout << "Before device\n"; for(int i=0;i<arrayLength;i++) { hostArray[i] = i+1; std::cout << hostArray[i] << "\n"; } std::cout << "\n"; cudaMemcpy(deviceArray, hostArray, memSize, cudaMemcpyHostToDevice); TestDevice <<>> (deviceArray); cudaMemcpy(hostArray, deviceArray, memSize, cudaMemcpyDeviceToHost); std::cout << "After device\n"; for(int i=0;i<arrayLength;i++) { std::cout << hostArray[i] << "\n"; } cudaFree(deviceArray); free(hostArray); std::cout << "Done\n"; } #endif 

MyKernel.cu

 #ifndef _MY_KERNEL_ #define _MY_KERNEL_ __global__ void TestDevice(int *deviceArray) { int idx = blockIdx.x*blockDim.x + threadIdx.x; deviceArray[idx] = deviceArray[idx]*deviceArray[idx]; } #endif 

构建日志:

 1>------ Build started: Project: CUDASandbox, Configuration: Debug x64 ------ 1>Compiling with CUDA Build Rule... 1>"C:\CUDA\bin64\nvcc.exe" -arch sm_10 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin" -Xcompiler "/EHsc /W3 /nologo /O2 /Zi /MT " -maxrregcount=32 --compile -o "x64\Debug\KernelSupport.cu.obj" "d:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\CUDASandbox\KernelSupport.cu" 1>KernelSupport.cu 1>tmpxft_000016f4_00000000-3_KernelSupport.cudafe1.gpu 1>tmpxft_000016f4_00000000-8_KernelSupport.cudafe2.gpu 1>tmpxft_000016f4_00000000-3_KernelSupport.cudafe1.cpp 1>tmpxft_000016f4_00000000-12_KernelSupport.ii 1>Linking... 1>KernelSupport.cu.obj : error LNK2005: __device_stub__Z10TestDevicePi already defined in MyKernel.cu.obj 1>KernelSupport.cu.obj : error LNK2005: "void __cdecl TestDevice__entry(int *)" (?TestDevice__entry@@YAXPEAH@Z) already defined in MyKernel.cu.obj 1>D:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\x64\Debug\CUDASandbox.exe : fatal error LNK1169: one or more multiply defined symbols found 1>Build log was saved at "file://d:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\CUDASandbox\x64\Debug\BuildLog.htm" 1>CUDASandbox - 3 error(s), 0 warning(s) ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== 

我在Windows 7 64bit上运行Visual Studio 2008。


编辑:

我想我需要详细说明一点。 我在这里寻找的最终结果是使用一个正常的C ++应用程序,例如Main.cpp和int main()事件,并从那里运行。 在我的.cpp代码的某些点上,我希望能够引用CUDA位。 所以我的想法(并纠正我,如果有更标准的约定)是我将CUDA内核代码放入他们的.cu文件,然后有一个支持.cu文件,将负责与设备通话和调用内核函数和什么不是。

您在kernelsupport.cu中包含mykernel.cu ,当您尝试链接编译器时会看到mykernel.cu两次。 您必须创建一个定义TestDevice的标头并将其包含在内。

评论:

这样的事情应该有效

 // MyKernel.h #ifndef mykernel_h #define mykernel_h __global__ void TestDevice(int* devicearray); #endif 

然后将包含文件更改为

 //KernelSupport.cu #ifndef _KERNEL_SUPPORT_ #define _KERNEL_SUPPORT_ #include  #include  // ... 

重新编辑

只要您在c ++代码中使用的标头没有任何cuda特定的东西( __global__ __kernel____global__等),您应该很好地链接c ++和cuda代码。

如果你看一下CUDA SDK代码示例,他们有extern C定义了从.cu文件编译的引用函数。 这样,.cu文件由nvcc编译,只链接到主程序,而.cpp文件正常编译。

例如,在marchingCubes_kernel.cu中有函数体:

 extern "C" void launch_classifyVoxel( dim3 grid, dim3 threads, uint* voxelVerts, uint *voxelOccupied, uchar *volume, uint3 gridSize, uint3 gridSizeShift, uint3 gridSizeMask, uint numVoxels, float3 voxelSize, float isoValue) { // calculate number of vertices need per voxel classifyVoxel<<>>(voxelVerts, voxelOccupied, volume, gridSize, gridSizeShift, gridSizeMask, numVoxels, voxelSize, isoValue); cutilCheckMsg("classifyVoxel failed"); } 

在marchingCubes.cpp(main()所在的位置)只有一个定义:

 extern "C" void launch_classifyVoxel( dim3 grid, dim3 threads, uint* voxelVerts, uint *voxelOccupied, uchar *volume, uint3 gridSize, uint3 gridSizeShift, uint3 gridSizeMask, uint numVoxels, float3 voxelSize, float isoValue); 

您也可以将它们放在.h文件中。

获得分离实际上非常简单,请查看此答案以了解如何进行设置。 然后,您只需将主机代码放在.cpp文件中,将设备代码放在.cu文件中,构建规则就会告诉Visual Studio如何将它们链接到最终的可执行文件中。

您定义__global__ TestDevice函数的代码中的直接问题是两次,一次是#include MyKernel.cu,一次是在独立编译MyKernel.cu时。

您需要将一个包装器放入.cu文件中 – 当您从主函数调用TestDevice<<<>>>时,但当您将其移动到.cpp文件时,它将使用cl.exe进行编译,其中不了解<<<>>>语法。 因此,您只需在.cpp文件中调用TestDeviceWrapper(griddim, blockdim, params) ,并在.cu文件中提供此函数。

如果你想要一个例子,SDK中的SobolQRNG示例实现了很好的分离,尽管它仍然使用cutil,我总是建议避免使用cutil。

简单的解决方案是关闭MyKernel.cu文件的构建。

属性 – >常规 – >从构建中排除

更好的解决方案是将内核拆分为cu和cuh文件,并将其包括在内,例如:

 //kernel.cu #include "kernel.cuh" #include  __global__ void increment_by_one_kernel(int* vals) { vals[threadIdx.x] += 1; } void increment_by_one(int* a) { int* a_d; cudaMalloc(&a_d, 1); cudaMemcpy(a_d, a, 1, cudaMemcpyHostToDevice); increment_by_one_kernel<<<1, 1>>>(a_d); cudaMemcpy(a, a_d, 1, cudaMemcpyDeviceToHost); cudaFree(a_d); } 

 //kernel.cuh #pragma once void increment_by_one(int* a); 

 //main.cpp #include "kernel.cuh" int main() { int a[] = {1}; increment_by_one(a); return 0; }