CUDA रनटाइम API का उपयोग करते हुए त्रुटियों की जांच करने के लिए विहित तरीका क्या है?


258

CUDA प्रश्नों पर उत्तर और टिप्पणियों के माध्यम से, और CUDA टैग विकि में , मैं देख रहा हूँ कि अक्सर यह सुझाव दिया जाता है कि त्रुटियों के लिए हर API कॉल की वापसी स्थिति की जाँच की जानी चाहिए। API दस्तावेज़ों जैसे कार्यों में शामिल है cudaGetLastError, cudaPeekAtLastErrorऔर cudaGetErrorStringहै, लेकिन सबसे अच्छा तरीका है अतिरिक्त कोड के बहुत सारे की आवश्यकता के बिना मज़बूती से पकड़ और रिपोर्ट त्रुटियों के इन को एक साथ रखा करने के लिए क्या है?


13
NVIDIA के CUDA नमूनों में एक हेडर, helper_cuda.h शामिल है, जिसमें मैक्रोज़ कहा जाता है getLastCudaErrorऔर checkCudaErrors, जो स्वीकृत उत्तर में वर्णित बहुत अधिक करते हैं । प्रदर्शनों के लिए नमूने देखें। बस टूलकिट के साथ नमूने स्थापित करने का चयन करें और आपके पास यह होगा।
चाप

@chappjc मुझे नहीं लगता कि यह प्रश्न और उत्तर मूल होने का दिखावा करता है, अगर यह आपका मतलब है, लेकिन इसमें CUDA त्रुटि जाँच का उपयोग कर शिक्षित लोगों की योग्यता है।
जैकोलटेन

@Jackolantern नहीं, यह वह नहीं है जो मैं लागू कर रहा था। यह क्यू एंड ए मेरे लिए बहुत मददगार था और एसडीके के कुछ हेडर की तुलना में यह निश्चित रूप से आसान है। मुझे लगा कि यह इंगित करना मूल्यवान था कि यह भी है कि NVIDIA इसे कैसे संभालता है और अधिक देखने के लिए कहां है। मैं अपनी टिप्पणी के स्वर को नरम अगर मैं हालांकि कर सकता था। :)
चपजक

डिबगिंग टूल आपको "एप्रोच" करने की अनुमति देता है जहां 2012 में CUDA के बाद से त्रुटियों की शुरुआत में बहुत सुधार हुआ है। मैंने GUI आधारित डिबगर्स के साथ काम नहीं किया है, लेकिन CUDA टैग विकी में कमांड लाइन cuda-gdb का उल्लेख है। यह एक बहुत शक्तिशाली उपकरण है क्योंकि यह आपको वास्तविक युद्ध और थ्रेड्स के माध्यम से GPU पर ही कदम रखने की अनुमति देता है (हालांकि अधिकांश समय 2.0+ आर्किटेक्चर की आवश्यकता होती है)
opetrenko

@bluefeet: आपके द्वारा वापस रोल किए गए संपादन के साथ सौदा क्या था? ऐसा लग रहा था कि वास्तव में मार्कडाउन में कुछ भी नहीं बदला गया था, लेकिन इसे एडिट के रूप में स्वीकार किया गया था। क्या काम में कुछ दकियानूसी था?
ताल

जवाबों:


304

रनटाइम एपीआई कोड में त्रुटियों के लिए जांच करने का सबसे अच्छा तरीका यह है कि एक मुखर स्टाइल हैंडलर फ़ंक्शन और रैपर मैक्रो को इस तरह परिभाषित किया जाए:

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

फिर आप प्रत्येक एपीआई कॉल को gpuErrchkमैक्रो के साथ लपेट सकते हैं , जो एपीआई कॉल की वापसी स्थिति की प्रक्रिया करेगा, उदाहरण के लिए:

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

यदि किसी कॉल में कोई त्रुटि है, तो एरर और आपके कोड में फाइल और लाइन का वर्णन करने वाला एक टेक्स्ट मैसेज जहां त्रुटि हुई है, उसे उत्सर्जित कर stderrदिया जाएगा और एप्लिकेशन निकल जाएगा। यदि आवश्यक हो तो आप एक अधिक परिष्कृत अनुप्रयोग में gpuAssertकॉल करने के बजाय अपवाद को संशोधित करने के लिए संशोधित कर सकते हैं exit()

एक दूसरा संबंधित सवाल यह है कि कर्नेल लॉन्च में त्रुटियों की जांच कैसे की जाए, जो सीधे मानक रनटाइम एपीआई कॉल जैसे मैक्रो कॉल में लपेटे नहीं जा सकते। गुठली के लिए, कुछ इस तरह से:

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

पहले अमान्य लॉन्च तर्क की जांच करेंगे, फिर होस्ट को तब तक इंतजार करने के लिए मजबूर करेंगे जब तक कर्नेल बंद न हो जाए और निष्पादन त्रुटि की जांच न हो जाए। यदि आपके पास इस तरह बाद में अवरुद्ध एपीआई कॉल है, तो सिंक्रनाइज़ेशन को समाप्त किया जा सकता है:

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

किस मामले में cudaMemcpyकॉल या तो त्रुटियों को वापस कर सकता है जो कर्नेल निष्पादन के दौरान हुई या जो मेमोरी से ही कॉपी होती हैं। यह शुरुआत के लिए भ्रामक हो सकता है, और मैं डिबगिंग के दौरान कर्नेल लॉन्च के बाद स्पष्ट सिंक्रनाइज़ेशन का उपयोग करने की सिफारिश करूंगा ताकि यह समझना आसान हो सके कि समस्याएं कहां उत्पन्न हो रही हैं।

ध्यान दें कि CUDA डायनामिक पैरेललिज़्म का उपयोग करते समय , एक बहुत ही समान कार्यप्रणाली को डिवाइस कर्नेल में 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: मुझे ऐसा नहीं लगता। सामुदायिक विकी को उन प्रश्नों या उत्तरों के लिए अभिप्रेत किया जाता है जिन्हें अक्सर संपादित किया जाता है। यह उन लोगों में से एक नहीं है
ताल

1
क्या हमें cudaDeviceReset()बाहर निकलने से पहले नहीं जोड़ना चाहिए ? और स्मृति डीलोकेशन के लिए एक खंड?
ऑरेलियस

2
@talonmies: Async CUDA रनटाइम कॉल के लिए, जैसे कि cudaMemsetAsync और cudaMemcpyAsync, क्या इसे gpuErrchk (cudaDeviceSynchronize ()) के लिए कॉल के माध्यम से gpu डिवाइस और होस्ट थ्रेड को सिंक्रनाइज़ करने की भी आवश्यकता है?
नर्सरी

2
ध्यान दें कि कर्नेल लॉन्च के बाद स्पष्ट तुल्यकालन गलत नहीं है, लेकिन निष्पादन प्रदर्शन और सिमेंटिंग सिमेंटिक्स को गंभीर रूप से बदल सकता है। यदि आप इंटरलाकिंग का उपयोग कर रहे हैं, तो डिबगिंग के लिए स्पष्ट सिंक्रनाइज़ेशन करने से बग की एक पूरी कक्षा छिप सकती है जो रिलीज बिल्ड में नीचे ट्रैक करना मुश्किल हो सकता है।
मास्टरएक्सिलो

क्या कर्नेल निष्पादन के लिए अधिक विशिष्ट त्रुटियां प्राप्त करने का कोई तरीका है? मुझे मिलने वाली सभी त्रुटियां केवल होस्ट कोड से मुझे लाइन नंबर देना है, न कि कर्नेल से।
आज़मिसोव

70

ऊपर दिए गए टैलोनमियों का उत्तर एक 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_errora है 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>
चापजेक

Jared, मुझे लगता है कि मेरा आवरण पुस्तकालय आपके सुझाए गए समाधान को पूरा करता है - ज्यादातर, और हल्के से पर्याप्त रूप से प्रतिस्थापन हो सकता है। (मेरा उत्तर देखें)
ईनपोकलम

27

C ++ - कैनोनिकल तरीका: त्रुटियों की जांच न करें ... अपवादों को फेंकने वाले C ++ बाइंडिंग का उपयोग करें।

मैं इस समस्या से परेशान रहा करता था; और मैं तालोनमी और जारेड के जवाबों की तरह ही मैक्रो-कम-रैपर-फ़ंक्शन समाधान करता था, लेकिन ईमानदारी से? यह CUDA रनटाइम एपीआई को और भी बदसूरत और सी-लाइक का उपयोग करता है।

इसलिए मैंने इसे एक अलग और अधिक मौलिक तरीके से अपनाया है। परिणाम के नमूने के लिए, यहाँ 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) के लिए पतली मॉडर्न-सी ++ रैपर

ध्यान दें कि अपवाद एक स्ट्रिंग स्पष्टीकरण और CUDA रनटाइम API स्थिति कोड दोनों को विफल कॉल के बाद ले जाते हैं।

CUDA त्रुटियों को इन आवरणों के साथ स्वचालित रूप से जाँचने के कुछ लिंक:


10

यहां चर्चा किए गए समाधान ने मेरे लिए अच्छा काम किया। यह समाधान अंतर्निर्मित कोडा कार्यों का उपयोग करता है और लागू करने के लिए बहुत सरल है।

प्रासंगिक कोड नीचे कॉपी किया गया है:

#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;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.