Cách chính tắc để kiểm tra lỗi bằng API thời gian chạy CUDA là gì?


258

Nhìn qua các câu trả lời và nhận xét về các câu hỏi CUDA và trong wiki thẻ CUDA , tôi thấy rằng thường được đề xuất rằng trạng thái trả về của mỗi lệnh gọi API nên được kiểm tra lỗi. Tài liệu API chứa các hàm như cudaGetLastError, cudaPeekAtLastErrorcudaGetErrorString, nhưng cách tốt nhất để kết hợp chúng lại với nhau để bắt và báo cáo lỗi một cách đáng tin cậy mà không yêu cầu nhiều mã bổ sung là gì?


13
Các mẫu CUDA của NVIDIA chứa một tiêu đề, helper_cuda.h, có các macro được gọi getLastCudaErrorcheckCudaErrors, thực hiện khá nhiều những gì được mô tả trong câu trả lời được chấp nhận . Xem các mẫu cho các cuộc biểu tình. Chỉ cần chọn để cài đặt các mẫu cùng với bộ công cụ và bạn sẽ có nó.
chappjc

@chappjc Tôi không nghĩ câu hỏi và câu trả lời này giả vờ là nguyên bản, nếu đây là ý của bạn, nhưng nó có công để giáo dục mọi người sử dụng kiểm tra lỗi CUDA.
JackOLogio

@JackOLogio Không, đó không phải là điều tôi đang ám chỉ. Câu hỏi và trả lời này rất hữu ích với tôi và chắc chắn nó dễ tìm hơn một số tiêu đề trong SDK. Tôi nghĩ rằng nó có giá trị để chỉ ra đây cũng là cách NVIDIA xử lý nó và nơi để tìm kiếm thêm. Tôi sẽ làm dịu giai điệu bình luận của tôi nếu tôi có thể. :)
chappjc

Các công cụ gỡ lỗi cho phép bạn "tiếp cận" nơi các lỗi bắt đầu đã cải thiện rất nhiều kể từ năm 2012 trên CUDA. Tôi chưa làm việc với trình gỡ lỗi dựa trên GUI nhưng wiki CUDA đề cập đến dòng lệnh cuda-gdb. Đây là một công cụ RẤT mạnh mẽ vì nó cho phép bạn bước qua các sợi dọc và luồng thực tế trên chính GPU (yêu cầu kiến ​​trúc 2.0+ hầu hết thời gian)
opetrenko

@bluefeet: thỏa thuận với bản chỉnh sửa mà bạn đã khôi phục là gì? Có vẻ như không có gì thực sự thay đổi trong đánh dấu, nhưng nó đã được chấp nhận như là một chỉnh sửa. Có một cái gì đó bất chính trong công việc?
Talonmies

Câu trả lời:


304

Có lẽ cách tốt nhất để kiểm tra lỗi trong mã API thời gian chạy là xác định hàm xử lý kiểu xác nhận và macro trình bao bọc như thế này:

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

Sau đó, bạn có thể bao bọc từng lệnh gọi API bằng gpuErrchkmacro, sẽ xử lý trạng thái trả về của lệnh gọi API, ví dụ:

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

Nếu có lỗi trong cuộc gọi, một tin nhắn văn bản mô tả lỗi và tệp và dòng trong mã của bạn nơi xảy ra lỗi sẽ được phát ra stderrvà ứng dụng sẽ thoát. Bạn có thể sửa đổi gpuAssertmột cách có thể hình dung để đưa ra một ngoại lệ thay vì gọi exit()trong một ứng dụng phức tạp hơn nếu được yêu cầu.

Một câu hỏi liên quan thứ hai là làm thế nào để kiểm tra lỗi trong các lần khởi chạy kernel, không thể được gói trực tiếp trong một lệnh gọi macro như các lệnh gọi API thời gian chạy tiêu chuẩn. Đối với hạt nhân, một cái gì đó như thế này:

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

trước tiên sẽ kiểm tra đối số khởi chạy không hợp lệ, sau đó buộc máy chủ phải đợi cho đến khi kernel dừng lại và kiểm tra lỗi thực thi. Đồng bộ hóa có thể được loại bỏ nếu bạn có lệnh gọi API chặn tiếp theo như sau:

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

trong trường hợp đó, cudaMemcpycuộc gọi có thể trả về một trong hai lỗi xảy ra trong quá trình thực thi kernel hoặc những lỗi từ chính bộ nhớ sao chép. Điều này có thể gây nhầm lẫn cho người mới bắt đầu và tôi khuyên bạn nên sử dụng đồng bộ hóa rõ ràng sau khi khởi chạy kernel trong quá trình gỡ lỗi để dễ hiểu vấn đề có thể phát sinh ở đâu.

Lưu ý rằng khi sử dụng CUDA Dynamic Parallelism , một phương pháp rất giống nhau có thể và nên được áp dụng cho bất kỳ việc sử dụng API thời gian chạy CUDA nào trong các hạt nhân thiết bị, cũng như sau khi bất kỳ kernel thiết bị nào khởi chạy:

#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: Tôi không nghĩ vậy. Wiki cộng đồng dành cho các câu hỏi hoặc câu trả lời thường xuyên được chỉnh sửa. Đây không phải là một trong số đó
Talonmies 6/214

1
chúng ta không nên thêm cudaDeviceReset()trước khi thoát? Và một điều khoản cho giải quyết bộ nhớ?
Aurelius

2
@talonmies: Đối với các cuộc gọi thời gian chạy CUDA Async, chẳng hạn như cudaMemsetAsync và cudaMemcpyAsync, nó cũng yêu cầu đồng bộ hóa thiết bị gpu và luồng máy chủ thông qua cuộc gọi đến gpuErrchk (cudaDeviceSyn syncize ())?
Nurabha

2
Lưu ý rằng đồng bộ hóa rõ ràng sau khi khởi chạy kernel không sai nhưng có thể làm thay đổi nghiêm trọng hiệu năng thực thi và ngữ nghĩa xen kẽ. Nếu bạn đang sử dụng xen kẽ, thực hiện đồng bộ hóa rõ ràng để gỡ lỗi có thể ẩn cả một lớp lỗi có thể khó theo dõi trong bản dựng Phát hành.
masterxilo

Có cách nào để có được các lỗi cụ thể hơn cho việc thực thi kernel không? Tất cả các lỗi tôi nhận được chỉ cho tôi số dòng từ mã máy chủ, không phải từ kernel.
Azmisov

70

Câu trả lời của Talonmies ở trên là một cách tốt để hủy bỏ một ứng dụng asserttheo kiểu.

Đôi khi, chúng tôi có thể muốn báo cáo và phục hồi từ một điều kiện lỗi trong ngữ cảnh C ++ như là một phần của ứng dụng lớn hơn.

Đây là một cách hợp lý để làm điều đó bằng cách ném ngoại lệ C ++ bắt nguồn từ std::runtime_errorviệc sử dụng 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);
  }
}

Điều này sẽ kết hợp tên tệp, số dòng và mô tả ngôn ngữ tiếng Anh cudaError_tcủa .what()thành viên ngoại lệ bị ném :

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

Đầu ra:

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

Một khách hàng some_functioncó thể phân biệt các lỗi CUDA với các loại lỗi khác nếu muốn:

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

Bởi vì thrust::system_errorlà một std::runtime_error, chúng ta có thể xử lý thay thế nó theo cùng một cách của một loại lỗi rộng nếu chúng ta không yêu cầu độ chính xác của ví dụ trước:

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
Các tiêu đề lực đẩy dường như đã được sắp xếp lại. <thrust/system/cuda_error.h>bây giờ là hiệu quả <thrust/system/cuda/error.h>.
chappjc

Jared, tôi nghĩ rằng thư viện trình bao bọc của tôi bao gồm giải pháp được đề xuất của bạn - chủ yếu, và đủ nhẹ để có thể thay thế. (Xem câu trả lời của tôi)
einpoklum 22/03/2017

27

Cách C ++ - kinh điển: Đừng kiểm tra lỗi ... hãy sử dụng các ràng buộc C ++ để đưa ra ngoại lệ.

Tôi đã từng bị khó chịu bởi vấn đề này; và tôi đã từng có một giải pháp chức năng bao bọc vĩ mô giống như trong câu trả lời của Talonmies và Jared, nhưng, thành thật mà nói? Nó làm cho việc sử dụng CUDA Runtime API thậm chí còn xấu hơn và giống như C.

Vì vậy, tôi đã tiếp cận điều này theo một cách khác và cơ bản hơn. Đối với một mẫu kết quả, đây là một phần của vectorAddmẫu CUDA - với việc kiểm tra lỗi hoàn toàn cho mỗi lệnh gọi API thời gian chạy:

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

Một lần nữa - tất cả các lỗi tiềm ẩn đều được kiểm tra và một ngoại lệ nếu xảy ra lỗi (báo trước: Nếu kernel gây ra một số lỗi sau khi khởi chạy, nó sẽ bị bắt sau khi cố gắng sao chép kết quả, không phải trước đó; để đảm bảo kernel thành công, bạn sẽ cần kiểm tra lỗi giữa khởi chạy và sao chép bằng cuda::outstanding_error::ensure_none()lệnh).

Đoạn mã trên sử dụng của tôi

Các trình bao bọc mỏng Modern-C ++ cho thư viện API CUDA Runtime (Github)

Lưu ý rằng các ngoại lệ mang cả giải thích chuỗi và mã trạng thái API thời gian chạy CUDA sau cuộc gọi thất bại.

Một vài liên kết đến cách các lỗi CUDA được kiểm tra tự động với các trình bao bọc này:


10

Các giải pháp thảo luận ở đây làm việc tốt cho tôi. Giải pháp này sử dụng các chức năng cuda tích hợp và rất đơn giản để thực hiện.

Mã liên quan được sao chép dưới đây:

#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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.