我在CUDA 5.0(GTK 110)中尝试新的动态并行功能。我遇到了一个奇怪的行为,我的程序没有返回某些配置的预期结果 - 不仅意外,而且每次启动都会产生不同的结果。
现在我想我找到了问题的根源:当有太多的子网格同时产生时,似乎有些子网格(由其他内核启动的内核)有时无法执行。。
我写了一个小测试程序来说明这种行为:
#include <stdio.h>
__global__ void out_kernel(char* d_out, int index)
{
d_out[index] = 1;
}
__global__ void kernel(char* d_out)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
out_kernel<<<1, 1>>>(d_out, index);
}
int main(int argc, char** argv) {
int griddim = 10, blockdim = 210;
// optional: read griddim and blockdim from command line
if(argc > 1) griddim = atoi(argv[1]);
if(argc > 2) blockdim = atoi(argv[2]);
const int numLaunches = griddim * blockdim;
const int memsize = numLaunches * sizeof(char);
// allocate device memory, set to 0
char* d_out; cudaMalloc(&d_out, memsize);
cudaMemset(d_out, 0, memsize);
// launch outer kernel
kernel<<<griddim, blockdim>>>(d_out);
cudaDeviceSynchronize();
// dowload results
char* h_out = new char[numLaunches];
cudaMemcpy(h_out, d_out, memsize, cudaMemcpyDeviceToHost);
// check results, reduce output to 10 errors
int maxErrors = 10;
for (int i = 0; i < numLaunches; ++i) {
if (h_out[i] != 1) {
printf("Value at index %d is %d, should be 1.\n", i, h_out[i]);
if(maxErrors-- == 0) break;
}
}
// clean up
delete[] h_out;
cudaFree(d_out);
cudaDeviceReset();
return maxErrors < 10 ? 1 : 0;
}
程序在给定数量的块(第一个参数)中启动内核,每个块具有给定数量的线程(第二个参数)。然后,该内核中的每个线程将使用单个线程启动另一个内核。这个子内核将在输出数组的部分写入1(用0初始化)。
在执行结束时,输出数组中的所有值都应为1.但奇怪的是,对于某些块大小和网格大小,某些数组值仍为零。这基本上意味着某些子网格不会被执行。
只有在同时生成许多子网格时才会发生这种情况。在我的测试系统(特斯拉K20x)上,每个包含210个线程的10个块就是这种情况。但是,有200个线程的10个块可以提供正确的结果。但是,每个包含1024个线程的3个块也会导致错误。 奇怪的是,运行时没有报告任何错误。调度程序似乎忽略了子网格。
还有其他人遇到同样的问题吗?这种行为是在某处记录的(我没有找到任何东西),还是设备运行时中的错误?
答案 0 :(得分:4)
你没有做任何我能看到的error checking。您可以而且应该对设备内核启动进行类似的错误检查。请参阅documentation这些错误不一定会冒泡到主机:
每个线程记录错误,以便每个线程可以识别它生成的最新错误。
您必须将它们捕获到设备中。文档中有大量此类设备错误检查的例子。
如果您要进行正确的错误检查,您会发现在内核无法启动的每种情况下,cuda设备运行时API都返回错误69,cudaErrorLaunchPendingCountExceeded
。
如果您扫描documentation此错误,您会发现:
cudaLimitDevRuntimePendingLaunchCount
控制由于未解决的依赖性或缺乏执行资源而为缓存尚未开始执行的内核启动而留出的内存量。当缓冲区已满时,启动会将线程的最后一个错误设置为 cudaErrorLaunchPendingCountExceeded。默认的待处理启动计数为2048次启动。
在10个块* 200个线程中,你正在启动2000内核,事情似乎有效。
在10个块* 210个线程中,您正在启动2100内核,这超过了上面提到的2048限制。
请注意,这在某种程度上是动态的;根据应用程序启动子内核的方式,您可以轻松启动超过2048个内核,而不会达到此限制。但是,由于您的应用程序几乎同时启动所有内核,因此您达到了极限。
如果您的CUDA代码没有按照预期的方式运行,建议进行正确的cuda错误检查。
如果您想获得上述内容的确认,可以在代码中修改主内核,如下所示:
__global__ void kernel(char* d_out)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
out_kernel<<<1, 1>>>(d_out, index);
// cudaDeviceSynchronize(); // not necessary since error 69 is returned immediately
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) d_out[index] = (char)err;
}
待处理的启动计数限制是可修改的。请参阅cudaLimitDevRuntimePendingLaunchCount