Каков канонический способ проверки ошибок с использованием API-интерфейса CUDA?

Просматривая ответы и комментарии по вопросам CUDA и в вики-тегах CUDA, я вижу, что часто предлагается проверить статус возврата каждого вызова API для ошибок. Документация API содержит такие функции, как cudaGetLastError, cudaPeekAtLastError и cudaGetErrorString, но как лучше всего их собрать, чтобы надежно улавливать и сообщать об ошибках, не требуя большого количества дополнительного кода?

224
задан 26 дек. '12 в 12:35
источник поделиться
5 ответов

Вероятно, лучший способ проверить ошибки в API-интерфейсе времени выполнения - определить функцию-обработчик assert и макрос оболочки следующим образом:

#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);
   }
}

Затем вы можете обернуть каждый вызов API макросом gpuErrchk, который обработает статус возврата 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 может возвращать либо ошибки, которые произошли во время выполнения ядра, либо те из самой копии памяти. Это может ввести в заблуждение для новичков, и я бы рекомендовал использовать явную синхронизацию после запуска ядра во время отладки, чтобы было легче понять, где могут возникнуть проблемы.

256
ответ дан 26 дек. '12 в 12:35
источник
Ответ на

talonmies выше - прекрасный способ прервать приложение в стиле assert.

Иногда мы можем сообщать и восстанавливать из условия ошибки в контексте С++ как часть более крупного приложения.

Здесь достаточно сложный способ сделать это, выбросив исключение С++, полученное из std::runtime_error, используя 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 является 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;
}
63
ответ дан 27 дек. '12 в 10:31
источник

С++ - канонический путь: не проверяйте ошибки... используйте привязки С++, которые генерируют исключения.

Раньше меня раздражала эта проблема; и у меня было решение макро-cum-wrapper-функции, как в Talonmies, и Джаред ответил, но, честно? Это делает использование API CUDA Runtime еще более уродливым и C-like.

Итак, я подошел к этому другим и более фундаментальным способом. Для примера результата, здесь часть образца CUDA vectorAdd - с полной проверкой ошибок каждого вызова API runtime:

// (... 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...)

Опять же - все потенциальные ошибки проверяются и сообщаются через исключение. Этот код использует мой

Обертки Thin Modern-С++ для библиотеки API CUDA Runtime API (Github)

Обратите внимание, что исключения несут как объяснение строки, так и код состояния API-интерфейса CUDA после неудачного вызова

Несколько ссылок на то, как ошибки CUDA автоматически проверяются этими обертками:

17
ответ дан 09 дек. '13 в 21:58
источник

Решение, обсуждавшееся здесь, хорошо сработало для меня. Это решение использует встроенные функции 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;
}
5
ответ дан 19 дек. '15 в 18:35
источник

Я обычно делаю так:

#define CHK_ERROR if (erro != cudaSuccess) goto Error;  

...

erro = cudaMalloc((void**)&d_image,sizeof(unsigned char)*nBlocks); CHK_ERROR
...
erro = cudaDeviceSynchronize(); CHK_ERROR   

...
Error:  
    std::cerr << "Error on CUDA: " << cudaGetErrorString(erro);
    cudaFree(d_image);

Я думаю, что он более читается

-1
ответ дан 06 апр. '16 в 4:55
источник

Другие вопросы по меткам или Задайте вопрос