CUDA çalışma zamanı API'sını kullanarak hataları kontrol etmenin standart yolu nedir?


258

CUDA sorularının cevaplarına ve yorumlarına baktığımızda ve CUDA etiketi wiki'sinde , her API çağrısının dönüş durumunun hatalar için kontrol edilmesi gerektiğini sık sık görüyorum. API belgeleri gibi fonksiyonları içerir cudaGetLastError, cudaPeekAtLastErrorve cudaGetErrorStringfakat ekstra kod birçok gerektirmeden güvenilir yakalamak ve rapor hataları bu buluşmanızı koymak için en iyi yolu nedir?


13
NVIDIA'nın CUDA örnekleri helper_cuda.h başlıklarını içeren getLastCudaErrorve çağrılan makrolarda çağrılan ve checkCudaErrorshemen hemen kabul edilen cevapta açıklananları içeren bir başlık içerir . Gösteriler için örneklere bakınız. Sadece araç kiti ile birlikte örnekleri yüklemeyi seçin ve sahip olacaksınız.
chappjc

@chappjc Bu soru ve cevabın orijinal olduğunu düşünmüyorum, eğer demek istediğiniz buysa, ama CUDA hata kontrolü kullanan eğitimli insanlara sahip olmanın avantajı var.
JackOLantern

@JackOLantern Hayır, bunu ima etmiyordum. Bu soru-cevap bana çok yardımcı oldu ve SDK'daki bazı başlıklardan bulmak kesinlikle daha kolay. NVIDIA'nın bunu nasıl ele aldığını ve daha fazlasını nerede arayacağınızı belirtmenin değerli olduğunu düşündüm. Yine de yorumumun tonunu yumuşatırdım. :)
chappjc

Hataların başladığı noktaya "yaklaşmanıza" izin veren hata ayıklama araçları, CUDA'da 2012'den bu yana önemli ölçüde iyileşti. GUI tabanlı hata ayıklayıcılarla çalışmadım ama CUDA etiketi wiki komut satırı cuda-gdb'den bahsediyor. Bu çok güçlü bir araçtır çünkü GPU'nun kendisinde gerçek çözgüleri ve iplikleri
atlamanıza

@bluefeet: Geri aldığınız düzenlemeyle ilgili anlaşma neydi? İşaretlemede aslında hiçbir şey değişmemiş gibi görünüyordu, ancak düzenleme olarak kabul edildi. İş yerinde hain bir şey var mıydı?
talonmies

Yanıtlar:


304

Muhtemelen çalışma zamanı API kodundaki hataları kontrol etmenin en iyi yolu, bir iddia tarzı işleyici fonksiyonu ve bunun gibi sarmalayıcı makro tanımlamaktır:

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

Daha sonra, her API çağrısını, sarmaladığı API çağrısının gpuErrchkdönüş durumunu işleyecek makro ile sarabilirsiniz , örneğin:

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

Çağrıda bir hata varsa, hatayı ve kodunuzda hatanın oluştuğu dosya ve satırı açıklayan bir metin mesajı verilir stderrve uygulamadan çıkılır. Gerekirse daha karmaşık bir uygulamada gpuAssertçağrı exit()yapmak yerine istisna oluşturmak için makul şekilde değişiklik yapabilirsiniz .

İkinci bir ilgili soru, standart çalışma zamanı API çağrıları gibi bir makro çağrısına doğrudan sarılamayan çekirdek başlatmalarındaki hataların nasıl kontrol edileceğidir. Çekirdekler için böyle bir şey:

kernel<<<1,1>>>(a);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaDeviceSynchronize() );

önce geçersiz başlatma bağımsız değişkenini kontrol eder, ardından ana bilgisayarı çekirdek duruncaya ve yürütme hatasını kontrol edene kadar beklemeye zorlar. Aşağıdaki gibi bir engelleme API çağrınız varsa senkronizasyon ortadan kaldırılabilir:

kernel<<<1,1>>>(a_d);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaMemcpy(a_h, a_d, size * sizeof(int), cudaMemcpyDeviceToHost) );

bu durumda cudaMemcpyçağrı, çekirdek yürütülmesi sırasında meydana gelen hataları veya bellek kopyasının kendisinden gelen hataları döndürebilir. Bu yeni başlayanlar için kafa karıştırıcı olabilir ve hataların nerede ortaya çıktığını anlamayı kolaylaştırmak için hata ayıklama sırasında bir çekirdek başlatmasından sonra açık senkronizasyon kullanmanızı öneririm.

Kullanıldığında bu Not CUDA Dinamik Paralellik , çok benzer bir yöntem ve cihaz çekirdekleri CUDA zamanı API bir kullanım uygulanacak olabilir, hem de herhangi bir aygıt çekirdek fırlatıldıktan sonra:

#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: Sanmıyorum. Topluluk Wiki, sık sık düzenlenen sorular veya cevaplar için tasarlanmıştır. Bu onlardan biri değil
talonmies

1
cudaDeviceReset()ayrılmadan önce eklememiz gerekmez mi? Peki bellek ayrılığı için bir madde mi?
Aurelius

2
@talonmies: cudaMemsetAsync ve cudaMemcpyAsync gibi Async CUDA çalışma zamanı çağrıları için, gpuErrchk (cudaDeviceSynchronize ()) çağrısı yoluyla gpu aygıtının ve ana bilgisayar iş parçacığının eşitlenmesini de gerektirir mi?
nurabha

2
Çekirdek başlatıldıktan sonra açık senkronizasyonun yanlış olmadığını, ancak yürütme performansını ve serpiştirme semantiğini ciddi şekilde değiştirebileceğini unutmayın. Serpiştirme kullanıyorsanız, hata ayıklama için açık senkronizasyon yapmak Release derlemesinde izlenmesi zor olabilecek bir hata sınıfının tamamını gizleyebilir.
masterxilo

Çekirdek yürütmeleri için daha spesifik hatalar almanın bir yolu var mı? Aldığım tüm hatalar bana çekirdekten değil, ana bilgisayar kodundan satır numarasını veriyor.
Azmisov

70

talonmies'in cevabı, bir uygulamayı asserttarz tarzında iptal etmenin iyi bir yoludur .

Bazen daha büyük bir uygulamanın parçası olarak bir C ++ bağlamında bir hata koşulunu bildirmek ve kurtarmak isteyebiliriz.

İşte bunu std::runtime_errorkullanarak bir C ++ istisnası atarak bunu yapmak için oldukça kısa bir yol 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);
  }
}

Bu cudaError_t, atılan kural dışı durumun .what()üyesine dosya adını, satır numarasını ve İngilizce bir açıklama ekleyecektir :

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

Çıktı:

$ nvcc exception.cu -run
CUDA error after cudaSetDevice: exception.cu(23): invalid device ordinal

İstemcisi, some_functionCUDA hatalarını, istenirse diğer hata türlerinden ayırt edebilir:

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

A thrust::system_errorolduğu için std::runtime_error, önceki örneğin doğruluğunu gerektirmezsek, alternatif olarak geniş bir hata sınıfıyla aynı şekilde işleyebiliriz:

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
Baskı başlıkları yeniden düzenlenmiş gibi görünüyor. <thrust/system/cuda_error.h>şimdi etkili <thrust/system/cuda/error.h>.
chappjc

Jared, sanırım sarıcı kütüphanem önerilen çözümünüzü kullanıyor - çoğunlukla ve muhtemelen değiştirilebilecek kadar hafif. (Cevabımı gör)
einpoklum

27

C ++ - kurallı yol: Hataları denetlemeyin ... istisnalar atan C ++ bağlarını kullanın.

Eskiden bu sorundan rahatsız oluyordum; ve eskiden Talonmies ve Jared'ın cevaplarında olduğu gibi bir makro-cum-sarıcı fonksiyon çözümü vardı, ama dürüstçe? CUDA Runtime API'sinin kullanımını daha çirkin ve C benzeri hale getirir.

Bu yüzden buna farklı ve daha temel bir şekilde yaklaştım. Sonuçların bir örneği için, CUDA vectorAddörneğinin bir parçası - her çalışma zamanı API çağrısının tam hata kontrolüyle:

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

Yine - tüm olası hatalar kontrol edilir ve bir hata oluşursa bir istisna (uyarı: Çekirdek başlatmadan sonra bir hataya neden olduysa, sonucu daha önce değil, kopyalamayı denedikten sonra yakalanır; çekirdeğin başarılı olduğundan emin olmak için başlatma ve kopya arasında bir hata olup olmadığını kontrol etmeniz gerekircuda::outstanding_error::ensure_none() komutla ).

Yukarıdaki kod benim

CUDA Çalışma Zamanı API kitaplığı (Github) için ince Modern-C ++ sarmalayıcıları

Özel durumların, başarısız bir çağrıdan sonra hem dize açıklaması hem de CUDA çalışma zamanı API durum kodu içerdiğini unutmayın.

CUDA hatalarının bu sarmalayıcılarla otomatik olarak nasıl kontrol edildiğine ilişkin birkaç bağlantı:


10

Burada tartışılan çözüm benim için iyi çalıştı. Bu çözüm yerleşik cuda işlevlerini kullanır ve uygulanması çok kolaydır.

İlgili kod aşağıda kopyalanmıştır:

#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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.