运行时GPU或CPU执行?

时间:2019-05-15 17:54:14

标签: cuda gpu

我觉得必须有一种方法来编写代码,使其可以在CPU或GPU中运行。也就是说,我想编写一种具有(例如)CPU FFT实现的东西,该实现可以在没有GPU的情况下执行,但是当存在GPU时默认为GPU FFT。我没有能够提出正确的问题来使互连网提供解决方案。

我的应用程序目标具有可用的GPU。我们要编写某些功能来使用GPU。但是,我们的开发VM则不同。能够运行代码/单元测试周期而不必跳到GPU硬件似乎是非常可取的。

如果我需要进行一些聪明的运行时检查/库加载,我可以接受;我只需要一本食谱。

人们如何持续集成支持GPU的代码?

目标环境为nVidia / CUDA。我是GPU代码的新手,所以也许这是一个常见问题解答(但我还没有找到)。

1 个答案:

答案 0 :(得分:2)

  

我想要的是运行时“我有GPU吗?”切换,这样我就可以采用一个代码路径或另一个

我认为这应该很简单。

典型的方法是:

  1. 与CUDA运行时库(cudart)库静态链接代码。如果您使用nvcc进行编译,这是默认行为。

  2. (大概)在代码开头附近,选择一个CUDA运行时API调用,例如cudaGetDevice()。使用某种形式的proper CUDA error checking(无论如何总是一个好主意)。在这种情况下,我们将使用从第一个运行时API调用返回的错误来确定路径(而不是仅仅终止应用程序)。

  3. 如果以上步骤2中的运行时API调用返回cudaSuccess(作为功能返回值,而不是设备索引),则可以安全地假定至少有1个功能CUDA GPU。在这种情况下,如果需要/需要,可以按照类似于CUDA deviceQuery示例代码的顺序对环境进行进一步检查。此状态可以存储在您的程序中,以供将来做出有关遵循的代码路径的决策。

  4. 如果步骤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是您的朋友。