はじめに
CUDAプログラミングを行う場合、たいていの場合はRuntime APIが利用されると思います。
Runtime APIより細かい制御をしたい場合や、ホストコードとデバイスコードをプロジェクト上分けたいときなど、Driver APIを利用したいときのため、基本的な実行方法を整理しておきます。
実行環境
Windows 10 Home
Microsoft Visual Studio Community 2017
CUDA 9.0
NVIDIA GeForce GPU
kernelコード
kernelコードはCUDAコンパイラ(nvcc)を使います。
kernelはNVIDIAプロジェクトに自動生成されるサンプル(int型配列同士の加算
)をそのまま利用します。
- 新規作成→プロジェクト→NVIDIA→CUDA 9.0→CUDA 9.0 Runtime
kernel.cu
extern "C"
{
__global__ void addKernel(int *c, const int *a, const int *b)
{
int i = threadIdx.x;
c[i] = a[i] + b[i];
}
}
プロジェクトのプロパティから、ptxファイルが出力されるようにしておきます。
コンパイルすると、kernel.ptxが出力されます。
ホストコード
ホストコードはVCのコンパイラを使います。
- 新規作成→プロジェクト→Visual C++→Win32→Win32コンソールアプリケーション
Driver APIでkernelを実行する場合は、cuModuleLoad
でptxファイルを外部モジュールとしてロードし、cuModuleGetFunction
で関数名をキーにエントリポイントを取得します。
その後、cuLaunchKernel
にgridやthreadblockの設定とともに渡すことで、kernelが実行されます。
ちなみに、CUDA 9からCUDA_LAUNCH_PARAMS
構造体が追加されており、cuLaunchKernel
の引数相当の値を構造体をしてまとめておくことができますが、cuLaunchKernel
には渡せません。
現状はマルチデバイス用のcuLaunchCooperativeKernelMultiDevice
にしか使えないようです。
main.cpp
#include "cuda.h"
#include <string>
#include <iostream>
//! CUresultエラーコード→エラーメッセージ
std::string GetErrorName(const CUresult& cuResult)
{
const char* pStr;
if (cuGetErrorName(cuResult, &pStr) != CUDA_ERROR_INVALID_VALUE)
{
return std::string(pStr);
}
return std::string();
}
int main()
{
CUresult result = CUDA_SUCCESS;
result = cuInit(0);
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
int deviceNum = 0;
result = cuDeviceGetCount(&deviceNum);
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
if (deviceNum < 1)
{
std::cout << "No Device."<< std::endl;
return 1;
}
CUdevice deviceID = 0;
CUcontext context;
result = cuCtxCreate(&context, CU_CTX_SCHED_AUTO, deviceID);
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
cuCtxPushCurrent(context);
//! kernel用入出力データ作成
constexpr size_t length = 10;
CUdeviceptr arrayAd, arrayBd, arrayCd;
cuMemAlloc(&arrayAd, sizeof(int)*length);
cuMemAlloc(&arrayBd, sizeof(int)*length);
cuMemAlloc(&arrayCd, sizeof(int)*length);
constexpr int arrayAh[length] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
constexpr int arrayBh[length] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int arrayCh[length] = {};
cuMemcpyHtoD(arrayAd, arrayAh, sizeof(int)*length);
cuMemcpyHtoD(arrayBd, arrayBh, sizeof(int)*length);
//! ptxファイルから関数のエントリポイントを取得
CUmodule module;
result = cuModuleLoad(&module, "kernel.ptx"); // 環境に合わせてパスを設定
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
CUfunction addFunc;
result = cuModuleGetFunction(&addFunc, module, "addKernel");
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
//! kernel実行
void* kernelArgs[] = { &arrayCd, &arrayAd, &arrayBd };
result = cuLaunchKernel(addFunc, 1, 1, 1, length, 1, 1, 0, NULL, kernelArgs, 0);
if (result != CUDA_SUCCESS)
{
std::cout << GetErrorName(result) << std::endl;
return 1;
}
//! 結果確認
cuMemcpyDtoH(arrayCh, arrayCd, sizeof(int)*length);
for (int i = 0; i < length; i++)
{
std::cout << arrayCh[i] << ",";
}
std::cout << std::endl;
cuModuleUnload(module);
cuMemFree(arrayAd);
cuMemFree(arrayBd);
cuMemFree(arrayCd);
cuCtxPopCurrent(&context);
cuCtxDestroy(context);
return 0;
}
実行結果
100,101,102,103,104,105,106,107,108,109,