我使用此(简化)代码得到了Cuda错误6(也称为cudaErrorLaunchTimeout
和CUDA_ERROR_LAUNCH_TIMEOUT
):
for(int i = 0; i < 650; ++i)
{
int param = foo(i); //some CPU computation here, but no memory copy
MyKernel<<<dimGrid, dimBlock>>>(&data, param);
}
Cuda错误6表明内核花了太多时间返回。单个MyKernel
的持续时间仅为~60 ms。块大小是经典的16×16。
现在,当我每次拨打cudaDeviceSynchronize()
,比如50次迭代时,就不会发生错误:
for(int i = 0; i < 650; ++i)
{
int param = foo(i); //some CPU computation here, but no memory copy
MyKernel<<<dimGrid, dimBlock>>>(&data, param);
if(i % 50 == 0) cudaDeviceSynchronize();
}
我想避免这种同步,因为它会大大减慢程序的速度。
由于内核启动是异步的,我猜错是因为看门狗从异步启动中测量内核的执行持续时间,而不是从实际开始执行。
我是Cuda的新手。这是错误6发生的常见情况吗?有没有办法在不改变性能的情况下避免这种错误?
答案 0 :(得分:3)
看门狗本身并不测量内核的执行时间。监视程序正在跟踪发送到GPU的命令队列中的请求,并确定GPU是否在超时期限内没有确认它们中的任何一个。
正如@talonmies在评论中指出的那样,我最好的猜测是(如果您确定没有内核执行超过超时时间),这种行为是由于CUDA驱动程序WDDM批处理机制,它试图通过批处理来减少平均延迟GPU一起命令并分批发送到GPU。
您无法直接控制批处理行为,因此通常,尝试在不禁用或修改Windows TDR机制的情况下解决此问题将是一项不精确的练习。
对于命令队列的低成本“刷新”的一般(有些未记录的)建议,您可以尝试进行试验,是使用cudaEventQuery(0);
(如建议here)代替cudaDeviceSynchronize();
,也许每50个内核启动一次。在某种程度上,具体细节可能取决于机器配置和正在使用的GPU。
我不确定你的情况会有多么有效。我认为,如果没有更多的实验,它可以作为避免TDR事件的“保证”。您的里程可能会有所不同。
答案 1 :(得分:3)
感谢talonmies和Robert Crovella(他提议的解决方案对我不起作用),我能够找到一个可接受的解决方法。
为了防止CUDA驱动程序一起批量启动内核,必须在每次内核启动之前或之后执行另一个操作。例如。虚拟副本可以解决问题:
void* dummy;
cudaMalloc(&dummy, 1);
for(int i = 0; i < 650; ++i)
{
int param = foo(i); //some CPU computation here, but no memory copy
cudaMemcpyAsync(dummy, dummy, 1, cudaMemcpyDeviceToDevice);
MyKernel<<<dimGrid, dimBlock>>>(&data, param);
}
此解决方案比包含cudaDeviceSynchronize()
调用的解决方案快8秒(50秒到42秒)(请参阅问题)。
此外,它更可靠,50
是一个任意的,特定于设备的时期。