本文是参加2022 CUDA on Platform 线上训练营学习笔记,感谢NVIDIA各位老师的精彩讲解!
CUDA运行时的错误检测函数- 为什么要使用错误检测函数?
CUDA通常有两类错误:
1)CUDA操作后立刻返回的瞬时错误,常见的有cudaMalloc的参数错误(如要求分配100GB显存分配失败),和kernel的启动配置错误(如要求启动1025个线程的block),错误都会立刻返回的。例如下面代码的错误类型1处,kernel的启动配置错误应立即返回CPU;
2)CUDA操作正在进行中,未来将会发生的错误,例如下面代码的错误类型2处异步错误。前面提过,CUDA是异构计算,CPU和GPU各自独立执行任务,CPU端调用核函数后继续执行主线程的程序,无法确认GPU端是否执行正确,因此再下一次的同步调用的时候,返回之前的异步错误,这样CPU端可以了解CUDA核函数是否成功执行。
__global__void hello()
{
printf("thread_id (%d,%d) block_id (%d, %d) coordinate (%d,%d)n"
"global index %2d n", threadIdx.x, threadIdx.y, blockIdx.x, blockIdx.y, ix, iy, idx);
}
int main()
{
hello<<<1, 1025>>>(); //错误,这里不能大于1024
//错误检测函数,错误类型1
cudaError_t error = cudaGetLastError();
if (error != cudaSuccess)
{
printf("CUDA error:%sn",cudaGetErrorString(err));
exit(-1);
}
printf("I am the CPU:Hello World!n");
cudaError_t err = cudaDeviceSynchronize();
//错误类型2
if (err != cudaSuccess)
{
printf("cudaDeviceSynchronize error:%sn",cudaGetErrorString(err));
exit(-1);
}
}
如上面的示例代码,若没有中间的错误检测函数,主程序输出
I am the CPU:Hello World!,主程序并不知道CUDA出错,而增加错误检测后,主程序获知CUDA出错原因,并退出程序,方便调试。
2. 几种检测函数
CUDA有下面几个错误检测函数,其中第三个第四个都是返回最新的error,区别是对于一些可恢复的错误(例如切换参数重新调用一些API),第三个可以reset成success进行挽救,这样程序可以继续往下执行,第四个获取到error并不修改或者删除它,这样后面的其他错误处理过程还可以继续得到这个错误。
注意,对于一些不可恢复的错误(例如kernel访存原因挂掉,此时CUDA Context将会失效),此时立刻终止是最好的选择,不能reset
__host____device__const char* cudaGetErrorName (cudaError_t error) //Returns the string representation of an error code enum name //返回错误的名称 __host____device__const char* cudaGetErrorString (cudaError_t error) //Returns the description string for an error code //返回错误的描述 __host____device__cudaError_t cudaGetLastError (void) //Returns the last error from a runtime call //返回最新的error __host____device__cudaError_t cudaPeekAtLastError(void) //Returns the last error from a runtime call //返回最新的error
- 使用方法示例
下面是一个使用方法示例,需要时直接调用CHECK即可。注意,该宏检测到错误后打印错误信息就退出了,因此若想reset上述的可恢复错误,不能直接调用。
//这里是一个预处理命令,作用是确保当前文件在一个编译单元中不被重复包含 #pragma once #includeCUDA中的事件#define CHECK(call) do { const cudaError_t error_code = call; if (error_code != cudaSuccess) { printf("CUDA Error:n"); printf(" File: %sn",__FILE__); printf(" Line: %dn",__LINE__); printf(" Eror code: %dn",error_code); printf(" Eror text: %sn",cudaGetErrorString(error_code)); exit(1); } } while (0); CHECK(cudaMemcpy(d_b, h_b, sizeof(int)*n*k,cudaMemcpyHostToDevice));
使用CUDA时,通常我们希望对程序进行计时,以便对比加速前后效果,虽然CPU计时器也可以使用,但由于GPU异步执行,计时可能不准确,因此最好使用CUDA event。
CUDA event本质是一个GPU时间戳,这个时间戳是在用户指定的位置记录。由于GPU本身支持记录时间戳,因此就避免了当使用CPU定时器来统计GPU执行时间时可能遇到的诸多问题,结果更准确。
- 常用函数
__host__cudaError_t cudaEventCreate(cudaEvent_t* event) //Creates an event object. __host__device__cudaError_t cudaEventCreateWithFlags(cudaEvent_t* event, unsigned int flags) //Creates an event object with the specified flags. __host__device__cudaError_t cudaEventDestroy(cudaEvent_t event) //Destroys an event object. __host__cudaError_t cudaEventElapsedTime(float* ms, cudaEvent_t start, cudaEvent_t end) //Computes the elapsed time between events. __host__cudaError_t cudaEventQuery(cudaEvent_t event) //Queries an event's status. __host__device__cudaError_t cudaEventRecord(cudaEvent_t event, cudaStream_t stream=0) //Records an event. __host__cudaError_t cudaEventRecordWithFlags(cudaEvent_t event, cudaStream_t stream=0, unsigned int flags=0) //Records an event. __host__cudaError_t cudaEventSynchronize(cudaEvent_t event) //Waits for an event to complete.
- 使用方法
CUDA的event使用通常有以下步骤。
- 声明:
cudaEvent_t event; - 创建:
cudaEvent_t cudaEventCreate(cudaEvent_t * event); - 销毁:
cudaEvent_t cudaEventDestroy(cudaEvent_t event); - 添加事件到当前执行流:
cudastream相当于一条条执行队列,比如调用一次CUDA通常有数据拷贝,核函数调用,结果返回,这样一个完整的队列可以成为一个stream,可以将event插入到不同的流中。
cudaEvent_t cudaEventRecord(cudaEvent_t event, cudaStream_t stream=0);
cudaEventRecord()视为一条记录当前时间的语句,并且把这条语句放入GPU的未完成队列中。因为直到GPU执行完了在调用cudaEventRecord()之前的所有语句时,事件才会被记录下来。且仅当GPU完成了之前的工作并且记录了stop事件后,才能安全地读取stop时间值。 - 等待事件完成,设立flag:
cudaEvent_t cudaEventSynchronize(cudaEvent_t event);//阻塞
cudaError_t cudaEventQuery(cudaEvent_t event);//非阻塞
阻塞是调用可能被卡住,不能继续往下执行,直到特定的状态满足才能继续(例如事件已经完成记录),非阻塞是指调用者查询了一下event的状态(完成还是未完成),不会卡住影响执行。 - 对两个已经发生/完成的事件的时刻值start和end做差,得到时间间隔。
cudaError_t cudaEventElapsedTime(float* ms, cudaEvent_t start, cudaEvent_t stop);



