我想要的是运行时“我有 GPU 吗?”切换,以便我可以采用一个代码路径或另一个
我相信这应该非常简单。
典型的方法是:
将您的代码静态链接到 CUDA 运行时库 (cudart) 库。如果你编译nvcc
,这是默认行为。
(大概)在代码开头附近,选择 CUDA Runtime API 调用,例如cudaGetDevice()
。使用某种形式的正确的 CUDA 错误检查(无论如何,总是一个好主意)。在这种情况下,我们将使用第一个运行时 API 调用返回的错误来做出路径决策(而不是简单地终止应用程序)。
如果上面步骤 2 中的运行时 API 调用返回cudaSuccess
(作为功能返回值,而不是设备索引),那么可以安全地假设至少有 1 个功能 CUDA GPU。在这种情况下,如果需要的话,可以对环境进行进一步检查,也许遵循类似于 CUDA 的顺序deviceQuery
示例代码。此状态可以存储在您的程序中,以便将来做出有关要遵循的代码路径的决策。
如果步骤 2 中的运行时 API 调用返回除cudaSuccess
,这几乎肯定意味着 CUDA 不起作用,可能是因为没有 CUDA GPU。在这种情况下,我建议不要进一步使用任何 CUDA API 或库,并且从那里您的代码应该使用仅主机代码路径。
这是一个完整的示例。如果找到可用的 CUDA 环境,它会使用 CUFFT 库执行简单的 FFT 操作。否则,它使用 FFTW 在主机代码中执行相同的操作。请注意,除了静态链接 cudart 库(默认使用nvcc
,所以不明显),我也静态链接 CUFFT 库。至少在 Linux 上(如此处的示例所示),这可以防止应用程序启动时因无法找到要链接的动态库而失败(这将阻止应用程序运行;而在这种情况下,我们的意图是应用程序运行但选择主机代码路径)。
$ cat t467.cu
#include <cufft.h>
#include <fftw.h>
#include <iostream>
int main(){
double data[] = {0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f};
int N = sizeof(data)/sizeof(data[0]);
int dev = 0;
if (cudaGetDevice(&dev) == cudaSuccess) {
// GPU code path
cufftDoubleComplex *din, *dout, *in, *out;
in = new cufftDoubleComplex[N];
out = new cufftDoubleComplex[N];
for (int i = 0; i < N; i++) in[i].x = data[i];
cudaError_t err = cudaMalloc(&din, sizeof(din[0]) * N);
err = cudaMalloc(&dout, sizeof(din[0]) * N);
cufftHandle plan;
cufftResult cstat = cufftPlan1d(&plan, N, CUFFT_Z2Z, 1);
cudaMemcpy(din, in, N*sizeof(din[0]), cudaMemcpyHostToDevice);
cstat = cufftExecZ2Z(plan, din, dout, CUFFT_FORWARD);
cudaMemcpy(out, dout, N*sizeof(din[0]), cudaMemcpyDeviceToHost);
for (int i = 0; i < N; i++) data[i] = out[i].x * out[i].x + out[i].y * out[i].y;
cudaFree(din); cudaFree(dout);
delete[] in; delete[] out;
cufftDestroy(plan);
std::cout << "GPU calculation: " << std::endl;
}
else {
// CPU code path
fftw_complex *in, *out;
fftw_plan p;
in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
for (int i = 0; i < N; i++) {in[i].re= data[i]; in[i].im = 0;}
p = fftw_create_plan(N, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_one(p, in, out);
fftw_destroy_plan(p);
for (int i = 0; i < N; i++) data[i] = out[i].re * out[i].re + out[i].im * out[i].im;
fftw_free(in); fftw_free(out);
std::cout << "CPU calculation: " << std::endl;
}
for (int i = 0; i < N; i++)
std::cout << data[i] << ", ";
std::cout << std::endl;
return 0;
}
$ nvcc t467.cu -o t467 -lcufft_static -lculibos -lfftw -lm
$ ./t467
GPU calculation:
0, 0, 16, 0, 0, 0, 16, 0,
$ CUDA_VISIBLE_DEVICES="" ./t467
CPU calculation:
0, 0, 16, 0, 0, 0, 16, 0,
$
请注意,上面的示例仍然动态链接 fftw,因此您的执行环境(CPU 和 GPU)需要有适当的 fftwX.so 库可用。如何使 Linux 可执行文件在各种设置(CUDA 依赖项之外)下工作的一般过程超出了本示例的范围或我打算回答的范围。在Linux上,ldd
是你的朋友。