CUDA 런타임 API를 사용하여 오류를 확인하는 정식 방법은 무엇입니까?


258

CUDA 질문에 대한 답변과 의견과 CUDA 태그 위키 에서 모든 API 호출의 반환 상태에 오류가 있는지 확인하는 것이 좋습니다. API 설명서 같은 기능을 포함하고 cudaGetLastError, cudaPeekAtLastError하고 cudaGetErrorString있지만, 추가 코드를 많이 필요로하지 않고 안정적으로 캐치 및 보고서 오류로이 함께 넣어하는 가장 좋은 방법은 무엇인지?


13
NVIDIA의 CUDA 샘플 에는 헤더 helper_cuda.h가 포함되어 있으며,이 매크로에는 getLastCudaErrorand 라는 매크로 checkCudaErrors있으며 허용되는 답변에 설명 된 것과 거의 같습니다 . 데모 샘플을 참조하십시오. 툴킷함께 샘플설치하기 만하면 됩니다.
chappjc

@chappjc 나는이 질문에 답하지 않고 이것이 원래의 척한다고 생각합니다. 그러나 이것이 CUDA 오류 검사를 사용하여 사람들을 교육시키는 것이 장점입니다.
JackOLantern

@JackOLantern 아니오, 그것은 내가 암시하는 것이 아닙니다. 이 Q & A는 저에게 매우 도움이되었으며 SDK의 일부 헤더보다 쉽게 ​​찾을 수 있습니다. 엔비디아가 어떻게이를 처리하고 어디에서 더 많은 것을 찾을 수 있는지를 지적하는 것이 가치 있다고 생각했습니다. 그래도 가능하다면 내 의견의 톤을 부드럽게 할 것입니다. :)
chappjc

CUDA에서 2012 년부터 오류가 시작되는 위치에 "접근"할 수있는 디버깅 도구. GUI 기반 디버거로 작업하지는 않았지만 CUDA 태그 위키 에는 명령 줄 cuda-gdb가 언급되어 있습니다. 이것은 GPU 자체에서 실제 날실과 스레드를 단계별로 실행할 수있는 매우 강력한 도구입니다 (대부분 2.0+ 아키텍처 필요)
opetrenko

@bluefeet : 롤백 한 편집 내용은 무엇입니까? 마크 다운에서 실제로 변경된 것이없는 것처럼 보이지만 편집으로 받아 들여졌습니다. 직장에서 사악한 것이 있었습니까?
talonmies

답변:


304

런타임 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호출은 커널 실행 중 발생한 오류 또는 메모리 사본 자체의 오류를 리턴 할 수 있습니다. 초보자에게는 혼란 스러울 수 있으므로 디버깅 중 커널 시작 후 명시 적 동기화를 사용하여 문제가 발생한 위치를 쉽게 이해할 수 있도록하는 것이 좋습니다.

CUDA Dynamic Parallelism 을 사용할 때 장치 커널 에서뿐만 아니라 장치 커널이 시작된 후에도 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 : 나는 그렇게 생각하지 않습니다. 커뮤니티 위키는 자주 편집되는 질문이나 답변을위한 것입니다. 이 중 하나가 아닙니다
talonmies

1
cudaDeviceReset()종료하기 전에 추가해서는 안 됩니까? 그리고 메모리 할당 해제에 대한 조항?
Aurelius

2
@talonmies : cudaMemsetAsync 및 cudaMemcpyAsync와 같은 비동기 CUDA 런타임 호출의 경우 gpuErrchk (cudaDeviceSynchronize ()) 호출을 통해 GPU 장치와 호스트 스레드를 동기화해야합니까?
nurabha

2
커널 시작 후 명시적인 동기화는 틀리지 않지만 실행 성능과 인터리빙 의미를 심각하게 변경할 수 있습니다. 인터리빙을 사용하는 경우 디버깅을 위해 명시적인 동기화를 수행하면 릴리스 빌드에서 추적하기 어려운 전체 버그 클래스가 숨겨 질 수 있습니다.
masterxilo

커널 실행에 대해 더 구체적인 오류를 얻는 방법이 있습니까? 내가 얻는 모든 오류는 커널이 아닌 호스트 코드에서 줄 번호를 제공합니다.
Azmisov

70

위의 talonmies의 답변은 응용 프로그램을 assert스타일 방식으로 중단시키는 좋은 방법 입니다.

때때로 더 큰 응용 프로그램의 일부로 C ++ 컨텍스트에서 오류 조건을보고하고 복구 할 수 있습니다.

다음을 std::runtime_error사용하여 파생 된 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_errorstd::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

제러드, 나는 래퍼 라이브러리가 당신의 제안 된 솔루션을 가장 많이 생각하고 대체 할 수있을 정도로 가볍다 고 생각한다. (내 답변보기)
einpoklum

27

C ++ 표준 방법 : 오류를 확인하지 마십시오 ... 예외를 발생시키는 C ++ 바인딩을 사용하십시오.

나는이 문제에 짜증이났다. Talonmies 및 Jared의 답변과 마찬가지로 매크로 쿰 래퍼 기능 솔루션을 사용했지만 솔직히 말했습니까? CUDA Runtime API를 훨씬 추악하고 C와 비슷하게 사용합니다.

그래서 나는 다른 근본적인 방법으로 이것에 접근했습니다. 결과 샘플의 경우 모든 런타임 API 호출에 대한 완전한 오류 검사 vectorAdd와 함께 CUDA 샘플 의 일부가 있습니다 .

// (... 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 Runtime 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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.