使用CUDA运行时API检查错误的规范方法是什么?


258

浏览关于CUDA问题的答案和评论,以及在CUDA标签Wiki中,我经常看到有人建议应检查每个API调用的返回状态是否有错误。API文档包括像功能cudaGetLastErrorcudaPeekAtLastError以及cudaGetErrorString,但什么是把这些结合在一起,以可靠地捕捉和报告错误,而不需要很多额外的代码的最佳方式?


13
NVIDIA的CUDA 示例包含一个名为helper_cuda.h的标头,该标头具有名为getLastCudaError和的宏checkCudaErrors,这些宏几乎可以执行公认的答案中所述的内容。请参阅样本进行演示。只需选择将示例与工具包一起安装,您将拥有它。
chappjc

@chappjc如果您要说的是这个问题,我不认为这个问题和答案是原始的,但是它具有让受过教育的人使用CUDA错误检查的优点。
JackOLantern

@JackOLantern不,那不是我的意思。这次问答对我非常有帮助,而且肯定比SDK中的某些标题更容易找到。我认为值得指出的是,这也是NVIDIA如何处理它以及在哪里可以找到更多东西的。如果可以的话,我会软化评论的语气。:)
chappjc

自2012年以来,调试工具使您可以“接近”错误的开始位置,自2012年以来,CUDA有了很大的改进。我尚未使用基于GUI的调试器,但CUDA标签Wiki提到了命令行cuda-gdb。这是一个非常强大的工具,它使您可以逐步了解GPU本身上的实际扭曲和线程(尽管大多数时候需要2.0+架构)
opetrenko 2016年

@bluefeet:您回滚的编辑处理了什么?在减价中,看起来没有任何实际变化,但被接受为修改。工作中有什么邪恶的东西吗?
talonmies

Answers:


304

检查运行时API代码中的错误的最佳方法可能是定义一个断言样式处理函数和包装宏,如下所示:

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
   if (code != cudaSuccess) 
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) exit(code);
   }
}

然后,您可以使用gpuErrchk宏包装每个API调用,该宏将处理包装的API调用的返回状态,例如:

gpuErrchk( cudaMalloc(&a_d, size*sizeof(int)) );

如果调用中存在错误,则描述该错误以及发生错误的代码中的文件和行的文本消息将被发送到stderr该应用程序,并且应用程序将退出。可以想象,您可以进行修改gpuAssert以引发异常,而不是exit()在需要时在更复杂的应用程序中调用。

第二个相关问题是如何检查内核启动中的错误,这些错误不能直接包装在宏调用中,例如标准运行时API调用。对于内核,如下所示:

kernel<<<1,1>>>(a);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaDeviceSynchronize() );

首先将检查无效的启动参数,然后强制主机等待直到内核停止并检查执行错误。如果您随后有这样的阻塞API调用,则可以消除同步:

kernel<<<1,1>>>(a_d);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaMemcpy(a_h, a_d, size * sizeof(int), cudaMemcpyDeviceToHost) );

在这种情况下,cudaMemcpy调用可以返回内核执行过程中发生的错误,也可以返回内存副本本身的错误。这对于初学者可能会造成混淆,我建议在调试过程中启动内核后使用显式同步,以使您更容易理解可能出现问题的位置。

请注意,在使用CUDA动态并行时,可以并且应该将非常相似的方法应用于设备内核中以及任何设备内核启动之后对CUDA运行时API的任何使用:

#include <assert.h>
#define cdpErrchk(ans) { cdpAssert((ans), __FILE__, __LINE__); }
__device__ void cdpAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
   if (code != cudaSuccess)
   {
      printf("GPU kernel assert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) assert(0);
   }
}

8
@harrism:我不这么认为。社区Wiki适用于经常编辑的问题或答案。这不是其中之一
talonmies 2014年

1
我们cudaDeviceReset()还应该在退出之前添加吗?还有用于内存释放的子句?
Aurelius

2
@talonmies:对于异步CUDA运行时调用,例如cudaMemsetAsync和cudaMemcpyAsync,是否还需要通过调用gpuErrchk(cudaDeviceSynchronize())来同步gpu设备和主机线程?
nurabha

2
请注意,内核启动后的显式同步并没有错,但是会严重改变执行性能和交错语义。如果使用交错,则进行显式同步以进行调试可能会隐藏一整类错误,这些错误可能很难在Release版本中找到。
masterxilo 2016年

有什么办法可以让内核执行更具体的错误?我遇到的所有错误都只是给我主机代码的行号,而不是内核的行号。
Azmisov '17

70

talonmies的上述回答是一种以assert-style方式中止应用程序的好方法。

有时我们可能希望报告C ++上下文中的错误情况并从中恢复,这是大型应用程序的一部分。

这是一种合理的简洁方法,通过抛出std::runtime_error使用using 派生的C ++异常来实现thrust::system_error

#include <thrust/system_error.h>
#include <thrust/system/cuda/error.h>
#include <sstream>

void throw_on_cuda_error(cudaError_t code, const char *file, int line)
{
  if(code != cudaSuccess)
  {
    std::stringstream ss;
    ss << file << "(" << line << ")";
    std::string file_and_line;
    ss >> file_and_line;
    throw thrust::system_error(code, thrust::cuda_category(), file_and_line);
  }
}

它将把文件名,行号和的英语描述cudaError_t合并到引发的异常.what()成员中:

#include <iostream>

int main()
{
  try
  {
    // do something crazy
    throw_on_cuda_error(cudaSetDevice(-1), __FILE__, __LINE__);
  }
  catch(thrust::system_error &e)
  {
    std::cerr << "CUDA error after cudaSetDevice: " << e.what() << std::endl;

    // oops, recover
    cudaSetDevice(0);
  }

  return 0;
}

输出:

$ nvcc exception.cu -run
CUDA error after cudaSetDevice: exception.cu(23): invalid device ordinal

的客户 some_function可以需要将CUDA错误与其他类型的错误区分开:

try
{
  // call some_function which may throw something
  some_function();
}
catch(thrust::system_error &e)
{
  std::cerr << "CUDA error during some_function: " << e.what() << std::endl;
}
catch(std::bad_alloc &e)
{
  std::cerr << "Bad memory allocation during some_function: " << e.what() << std::endl;
}
catch(std::runtime_error &e)
{
  std::cerr << "Runtime error during some_function: " << e.what() << std::endl;
}
catch(...)
{
  std::cerr << "Some other kind of error during some_function" << std::endl;

  // no idea what to do, so just rethrow the exception
  throw;
}

因为thrust::system_error是a std::runtime_error,如果我们不需要上一个示例的精度,则可以用与广泛的错误类别相同的方式来处理它:

try
{
  // call some_function which may throw something
  some_function();
}
catch(std::runtime_error &e)
{
  std::cerr << "Runtime error during some_function: " << e.what() << std::endl;
}

1
推力头管似乎已重新布置。<thrust/system/cuda_error.h>现在有效<thrust/system/cuda/error.h>
chappjc

Jared,我认为我的包装器库包含了您建议的解决方案-大多数情况下,它的重量轻到足以被替换。(请参阅我的回答)
einpoklum

27

C ++规范方法:不要检查错误...使用引发异常的C ++绑定。

我以前对这个问题很生气;和Talonmies和Jared的答案一样,我曾经有一个宏兼包装函数函数解决方案,但是,老实说?它使使用CUDA Runtime API变得更加丑陋且类似于C。

因此,我以另一种更基本的方式来解决这个问题。有关结果的示例,这是CUDA vectorAdd示例的一部分- 对每个运行时API调用进行完整的错误检查:

// (... prepare host-side buffers here ...)

auto current_device = cuda::device::current::get();
auto d_A = cuda::memory::device::make_unique<float[]>(current_device, numElements);
auto d_B = cuda::memory::device::make_unique<float[]>(current_device, numElements);
auto d_C = cuda::memory::device::make_unique<float[]>(current_device, numElements);

cuda::memory::copy(d_A.get(), h_A.get(), size);
cuda::memory::copy(d_B.get(), h_B.get(), size);

// (... prepare a launch configuration here... )

cuda::launch(vectorAdd, launch_config,
    d_A.get(), d_B.get(), d_C.get(), numElements
);    
cuda::memory::copy(h_C.get(), d_C.get(), size);

// (... verify results here...)

再次-检查所有可能的错误,并检查是否发生错误(异常:注意:如果内核启动导致某些错误,则将在尝试复制结果后而不是在捕获之前捕获该错误;要确保内核成功,您将需要使用命令检查启动和副本之间的错误cuda::outstanding_error::ensure_none())。

上面的代码使用了我的

CUDA运行时API库(Github)的 Thin Modern-C ++包装器

请注意,异常在调用失败后包含字符串说明和CUDA运行时API状态代码。

这些包装程序如何自动检查CUDA错误的一些链接:


10

这里讨论的解决方案对我来说效果很好。该解决方案使用内置的cuda函数,实现起来非常简单。

相关代码复制如下:

#include <stdio.h>
#include <stdlib.h>

__global__ void foo(int *ptr)
{
  *ptr = 7;
}

int main(void)
{
  foo<<<1,1>>>(0);

  // make the host block until the device is finished with foo
  cudaDeviceSynchronize();

  // check for error
  cudaError_t error = cudaGetLastError();
  if(error != cudaSuccess)
  {
    // print the CUDA error message and exit
    printf("CUDA error: %s\n", cudaGetErrorString(error));
    exit(-1);
  }

  return 0;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.