CUDAの質問に対する回答とコメント、およびCUDAタグのwikiを見ると、多くの場合、すべてのAPI呼び出しの戻りステータスでエラーを確認することが推奨されています。APIドキュメントは次のような機能が含まれているcudaGetLastError
、cudaPeekAtLastError
とcudaGetErrorString
、余分なコードの多くを必要とせずに確実にキャッチし、レポートのエラーにこれらを一緒に置くための最良の方法は何ですか?
CUDAの質問に対する回答とコメント、およびCUDAタグのwikiを見ると、多くの場合、すべてのAPI呼び出しの戻りステータスでエラーを確認することが推奨されています。APIドキュメントは次のような機能が含まれているcudaGetLastError
、cudaPeekAtLastError
とcudaGetErrorString
、余分なコードの多くを必要とせずに確実にキャッチし、レポートのエラーにこれらを一緒に置くための最良の方法は何ですか?
回答:
おそらく、ランタイム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);
}
}
cudaDeviceReset()
終了する前に追加してはいけませんか?そして、メモリ割り当て解除の条項は?
上記の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;
}
<thrust/system/cuda_error.h>
効果的になりました<thrust/system/cuda/error.h>
。
私は以前この問題に腹を立てていました。かつては、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エラーが自動的にチェックされる方法へのリンク:
ここで説明した解決策は私にとってはうまくいきました。このソリューションは組み込みの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;
}
getLastCudaError
とcheckCudaErrors
ほとんどで説明されて何をすべきか、受け入れ答えを。デモンストレーションのサンプルを参照してください。ツールキットと一緒にサンプルをインストールすることを選択するだけで、それが手に入ります。