【问题标题】:Some child grids not being executed with CUDA Dynamic Parallelism某些子网格未使用 CUDA 动态并行执行
【发布时间】:2013-07-27 23:03:29
【问题描述】:

我正在试验 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;
}

程序以给定数量的块(第一个参数)启动内核,每个块具有给定数量的线程(第二个参数)。然后,该内核中的每个线程将使用单个线程启动另一个内核。该子内核将在其输出数组的一部分(初始化为 0)中写入 1。

在执行结束时,输出数组中的所有值都应为 1。但奇怪的是,对于某些块大小和网格大小,一些数组值仍然为零。这基本上意味着一些子网格没有执行。

只有在同时生成许多子网格时才会发生这种情况。在我的测试系统(Tesla K20x)上,10 个块包含 210 个线程。但是,具有 200 个线程的 10 个块可以提供正确的结果。但是每个具有 1024 个线程的 3 个块也会导致错误。 奇怪的是,运行时没有报告错误。调度程序似乎只是忽略了子网格。

还有其他人面临同样的问题吗?这种行为是否记录在某处(我没有找到任何东西),或者它真的是设备运行时中的错误?

【问题讨论】:

    标签: c++ cuda gpgpu gpu


    【解决方案1】:

    你没有做任何我能看到的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的文档

    【讨论】:

    • 这很有道理,感谢您的回答!我不知道可以在 inside 内核中使用cudaGetLastError()。我还发现可以使用cudaDeviceSetLimit(cudaLimitDevRuntimePendingLaunchCount, &lt;size&gt;) 增加挂起的启动次数。如果您可以将其添加到您的答案中,那就太好了。再次感谢!
    猜你喜欢
    • 2017-02-03
    • 1970-01-01
    • 2018-07-03
    • 2017-06-14
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    相关资源
    最近更新 更多