我想要的是运行时“我有 GPU 吗?”切换,以便我可以采用一个代码路径或另一个
我相信这应该很简单。
典型的方法是:
将您的代码静态链接到 CUDA 运行时库 (cudart) 库。如果使用nvcc 编译,这是默认行为。
(大概)在代码开头附近,选择一个 CUDA 运行时 API 调用,例如 cudaGetDevice()。使用某种形式的proper CUDA error checking(无论如何总是一个好主意)。在这种情况下,我们将使用第一次运行时 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 是你的朋友。