2

我正在尝试 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 个块也会导致错误。奇怪的是,运行时没有报告错误。调度程序似乎只是忽略了子网格。

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

4

1 回答 1

4

你没有做任何我能看到的错误检查。您可以而且应该在设备内核启动时进行类似的错误检查。请参阅文档 这些错误不一定会冒泡到主机:

每个线程都会记录错误,以便每个线程都可以识别它最近生成的错误。

您必须将它们困在设备中。文档中有大量此类设备错误检查的示例。

如果您要进行适当的错误检查,您会发现在每次内核启动失败的情况下,cuda 设备运行时 API 都会返回错误 69 cudaErrorLaunchPendingCountExceeded,.

如果您扫描此错误的文档,您会发现:

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

于 2013-07-27T22:11:26.867 回答