Как разделить код CUDA на несколько файлов
Я пытаюсь разделить программу CUDA на два отдельных файла .cu, пытаясь приблизиться к написанию реального приложения на С++. У меня есть простая небольшая программа, которая:
Выделяет память на хосте и устройстве.
Инициализирует массив хоста для серии чисел.
Копирует массив хоста в массив устройств
Находит квадрат всех элементов массива, используя ядро устройства
Копирует массив устройств обратно в массив хоста
Распечатывает результаты
Это отлично работает, если я поместил все это в один .cu файл и запустил его. Когда я разбиваю его на два отдельных файла, я начинаю получать ошибки связывания. Как и все мои недавние вопросы, я знаю, что это что-то маленькое, но что это такое?
KernelSupport.cu
#ifndef _KERNEL_SUPPORT_
#define _KERNEL_SUPPORT_
#include <iostream>
#include <MyKernel.cu>
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 <<< 4, 4 >>> (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 *)" ([email protected]@[email protected]) 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 ==========
Я запускаю Visual Studio 2008 на 64-разрядной версии Windows 7.
Edit:
Думаю, мне нужно немного разобраться в этом. Конечный результат, который я ищу здесь, состоит в том, чтобы иметь нормальное приложение на С++ с чем-то вроде Main.cpp с событием int main()
, и оттуда все будет работать. В точке certains в моем .cpp-коде я хочу иметь возможность ссылаться на биты CUDA. Итак, мое мышление (и исправьте меня, если есть более стандартное соглашение здесь) заключается в том, что я положу код ядра CUDA в их файлы .cu, а затем поддерживаю файл .cu, который позаботится о разговоре с устройством и вызове функции ядра, а что нет.
Ответы
Ответ 1
Вы включаете mykernel.cu
в kernelsupport.cu
, когда пытаетесь связать компилятор, он видит mykernel.cu дважды. Вам нужно будет создать заголовок, определяющий TestDevice, и включить его вместо этого.
re comment:
Что-то вроде этого должно работать
// MyKernel.h
#ifndef mykernel_h
#define mykernel_h
__global__ void TestDevice(int* devicearray);
#endif
а затем измените файл включения на
//KernelSupport.cu
#ifndef _KERNEL_SUPPORT_
#define _KERNEL_SUPPORT_
#include <iostream>
#include <MyKernel.h>
// ...
re your edit
До тех пор, пока заголовок, который вы используете в коде С++, не имеет каких-либо специфических свойств cuda (__kernel__
, __global__
и т.д.), вы должны быть хорошо связаны С++ и cuda-кодом.
Ответ 2
Если вы посмотрите примеры кода 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<<<grid, threads>>>(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.
Ответ 3
Получение разделения на самом деле довольно просто, ознакомьтесь с этим ответом о том, как его настроить. Затем вы просто помещаете свой хост-код в файлы .cpp и код вашего устройства в .cu файлы, правила сборки говорят Visual Studio, как связать их вместе с окончательным исполняемым файлом.
Непосредственная проблема в коде, которую вы определяете функцию __global__ TestDevice
дважды, один раз, когда вы #include
MyKernel.cu и один раз, когда вы самостоятельно компилируете MyKernel.cu.
Вам также нужно поместить оболочку в файл .cu - в тот момент, когда вы вызываете TestDevice<<<>>>
из своей основной функции, но когда вы переносите это в .cpp файл, он будет скомпилирован с помощью cl.exe, который не понимает синтаксис <<<>>>
. Поэтому вы просто вызываете TestDeviceWrapper(griddim, blockdim, params)
в .cpp файл и предоставляете эту функцию в вашем .cu файле.
Если вам нужен пример, образец SobolQRNG в SDK достигает хорошего разделения, хотя он все еще использует cutil, и я всегда рекомендую избегать cutil.
Ответ 4
Простое решение - отключить создание файла MyKernel.cu.
Свойства → Общие → Исключено из сборки
Лучшее решение imo состоит в том, чтобы разделить ваше ядро на cu и cuh файл и включить это, например:
//kernel.cu
#include "kernel.cuh"
#include <cuda_runtime.h>
__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;
}