【问题标题】:Run-time GPU or CPU execution?运行时 GPU 或 CPU 执行?
【发布时间】:2019-10-02 22:31:50
【问题描述】:

我觉得必须有一种方法可以编写可以在 CPU 或 GPU 中运行的代码。也就是说,我想写一些具有(例如)的东西,一个 CPU FFT 实现,如果没有 GPU 可以执行,但当 GPU 存在时默认为 GPU FFT。我一直无法提出正确的问题来让互联网提供解决方案。

我的应用程序目标有可用的 GPU。我们想编写某些函数来使用 GPU。但是,我们的开发虚拟机是另一回事。能够运行代码/单元测试循环而不必跳转到 GPU 硬件似乎是非常可取的。

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

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

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

【问题讨论】:

  • “这样它就可以在 CPU 或 GPU 中运行”。我认为您需要详细说明您的意思
  • 已更新。我希望这更清楚
  • 肯定有足够的抽象,你可以得到一个不可知的接口,用于根据平台使用不同“后端”的功能?这当然意味着两个后端,而不是“可以在 CPU 或 GPU 中运行”的代码,并且意味着您的单元测试无法运行整个代码库
  • 我正在寻找有关如何进行抽象的信息。对 OpenCL 的简要了解意味着它提供了这种抽象,但我们有既得利益留在 CUDA
  • 你一直在写一些似乎暗示着“我如何在没有 GPU 的情况下运行 CUDA 代码”的东西。你不能那样做,抽象也不意味着那样

标签: cuda gpu


【解决方案1】:

我想要的是运行时“我有 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 是你的朋友。

【讨论】:

    猜你喜欢
    • 2020-04-28
    • 2019-01-11
    • 1970-01-01
    • 1970-01-01
    • 2015-12-27
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    • 2019-12-11
    相关资源
    最近更新 更多