Что такое канонический способ проверить на наличие ошибок с помощью технологии CUDA API среды выполнения?
просматривая ответы и комментарии на вопросы CUDA, а также в CUDA tag wiki, Я вижу, что часто предлагается, чтобы состояние возврата каждого вызова API проверялось на наличие ошибок. Документация API содержит такие функции, как cudaGetLastError
,cudaPeekAtLastError
и cudaGetErrorString
, но каков наилучший способ собрать их вместе, чтобы надежно ловить и сообщать об ошибках, не требуя большого количества дополнительного кода?
4 ответа:
вероятно, лучший способ проверить ошибки в коде 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
вызов может возвращать либо ошибки, возникшие во время выполнения ядра, либо ошибки из самой копии памяти. Это может сбить с толку новичка, и я бы рекомендовал использовать явную синхронизацию после запуска ядра во время отладки, чтобы облегчить понимание того, где могут возникнуть проблемы.
ответ talonmies выше-это прекрасный способ прервать приложение в
assert
манер.иногда мы можем сообщать и восстанавливаться из состояния ошибки в контексте C++ как часть более крупного приложения.
вот достаточно краткий способ сделать это, бросив исключение C++, полученное из
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; }
C++-канонический способ: не проверяйте наличие ошибок...используйте привязки C++, которые вызывают исключения.
меня раздражала эта проблема; и у меня было решение макро-cum-wrapper-function, как в Talonmies и ответах Джареда, но, честно говоря? Это делает использование 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...)
снова-все потенциальные ошибки проверяются и сообщаются через брошенное исключение. Этот код использует мой
тонкие современные обертки C++ для библиотеки API среды выполнения CUDA (Github)
обратите внимание, что исключения несут как строковое объяснение, так и код состояния API среды выполнения CUDA после неудачного вызова.
несколько ссылок на то, как ошибки CUDA автоматически проверено с этими обертками:
решение обсуждается здесь работал хорошо для меня. Это решение использует встроенные функции 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; }