CUDAランタイムAPIを使用してエラーをチェックする正規の方法は何ですか?


258

CUDAの質問に対する回答とコメント、およびCUDAタグのwikiを見ると、多くの場合、すべてのAPI呼び出しの戻りステータスでエラーを確認することが推奨されています。APIドキュメントは次のような機能が含まれているcudaGetLastErrorcudaPeekAtLastErrorcudaGetErrorString、余分なコードの多くを必要とせずに確実にキャッチし、レポートのエラーにこれらを一緒に置くための最良の方法は何ですか?


13
NVIDIAのCUDAのサンプルが呼ばれるマクロがあるヘッダー、helper_cuda.h、含まれているgetLastCudaErrorcheckCudaErrorsほとんどで説明されて何をすべきか、受け入れ答えを。デモンストレーションのサンプルを参照してください。ツールキット一緒にサンプルインストールすることを選択するだけで、それが手に入ります。
chappjc

@chappjc私がこの質問と回答がオリジナルであるとは思わないのであれば、これがあなたの意図するものであるとは言えませんが、CUDAエラーチェックを使用して人々を教育したというメリットがあります。
JackOLantern 2015

@JackOLanternいいえ、それは私が示唆していたことではありません。このQ&Aは非常に役に立ち、SDKのヘッダーよりも簡単に見つけることができます。これはNVIDIAがどのように処理するか、またどこを探すべきかについても指摘することは価値があると思いました。できればコメントの口調を和らげます。:)
chappjc 2015

エラーが始まる場所に「アプローチ」できるデバッグツールは、CUDAで2012年以来大幅に改善されています。私はGUIベースのデバッガーを使用していませんが、CUDAタグwikiにはコマンドラインcuda-gdbが記載されています。GPU自体で実際のワープとスレッドをステップスルーできるため、これは非常に強力なツールです(ただし、ほとんどの場合2.0+アーキテクチャが必要です)
opetrenko

@bluefeet:ロールバックした編集との取り決めは何でしたか?マークダウンでは実際には何も変更されていないように見えましたが、編集として受け入れられました。仕事で何か悪いことはありましたか?
talonmies

回答:


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

次に、各API呼び出しをgpuErrchkマクロでラップできます。これにより、ラップしたAPI呼び出しの戻りステータスが処理されます。次に例を示します。

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

呼び出しにエラーがある場合、エラーと、エラーが発生したコード内のファイルと行を説明するテキストメッセージが出力されstderr、アプリケーションが終了します。必要に応じて、より高度なアプリケーションをgpuAssert呼び出すのexit()ではなく、例外を発生させるように変更することが考えられます。

関連する2番目の質問は、カーネルの起動時にエラーを確認する方法です。これは、標準のランタイム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
@ハリスム:そうは思いません。コミュニティWikiは、頻繁に編集される質問または回答を対象としています。これはそれらの1つではありません
talonmies

1
cudaDeviceReset()終了する前に追加してはいけませんか?そして、メモリ割り当て解除の条項は?
Aurelius

2
@talonmies:cudaMemsetAsyncやcudaMemcpyAsyncなどの非同期CUDAランタイムコールの場合、gpuErrchk(cudaDeviceSynchronize())を呼び出してgpuデバイスとホストスレッドを同期する必要もありますか?
nurabha

2
カーネルの起動後の明示的な同期は問題ありませんが、実行パフォーマンスとインターリーブのセマンティクスを大幅に変更する可能性があることに注意してください。インターリーブを使用している場合、デバッグのために明示的な同期を行うと、リリースビルドで追跡するのが難しいバグのクラス全体が非表示になる可能性があります。
masterxilo 2016

カーネルの実行でより具体的なエラーを取得する方法はありますか?取得したすべてのエラーは、カーネルではなくホストコードからの行番号を示しています。
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_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;
}

1
スラストヘッダーが再配置されたようです。<thrust/system/cuda_error.h>効果的になりました<thrust/system/cuda/error.h>
chappjc 2015年

Jaredさん、私のラッパーライブラリは、提案されたソリューションを包括していると思います。ほとんどの場合、適切に置き換えることができるほど軽量です。(私の回答を参照してください)
einpoklum 2017年

27

C ++の標準的な方法:エラーをチェックしない...例外をスローするC ++バインディングを使用します。

私は以前この問題に腹を立てていました。かつては、TalonmiesとJaredの答えと同じように、マクロ兼ラッパー関数ソリューションを使用していましたが、正直なところ、これにより、CUDAランタイム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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.