启动许多内核(> 1000)时,CUDA流被阻止

时间:2018-12-29 13:52:21

标签: cuda

我发现当我启动许多内核(超过1000个)时,CUDA流将阻塞。我想知道是否可以更改任何配置?

在我的实验中,我启动了一个小内核10000次。这个内核很快就运行了(大约190us)。启动前1000个内核时,内核启动非常快。启动内核需要4〜5us。但是之后,启动过程变慢了。启动新内核大约需要190个我们。 CUDA流似乎正在等待先前的内核完成,并且缓冲区大小约为1000个内核。 当我创建3个流时,每个流可以启动1000个内核异步。 我想增大此缓冲区。我尝试设置cudaLimitDevRuntimePendingLaunchCount,但不起作用。有什么办法吗?

#include <stdio.h>
#include "cuda_runtime.h"

#define CUDACHECK(cmd) do {                         \
    cudaError_t e = cmd;                              \
    if( e != cudaSuccess ) {                          \
        printf("Failed: Cuda error %s:%d '%s'\n",             \
        __FILE__,__LINE__,cudaGetErrorString(e));   \
        exit(EXIT_FAILURE);                             \
    }                                                 \
} while(0)

// a dummy kernel for test
__global__ void add(float *a, int n) {
    int id = threadIdx.x + blockIdx.x * blockDim.x;
    for (int i=0; i<n; i++) {
        a[id] = sqrt(a[id] + 1);
    }
}

int main(int argc, char* argv[])
{

    //managing 1 devices
    int nDev = 1;
    int nStream = 1;
    int size = 32*1024*1024;


    //allocating and initializing device buffers
    float** buffer = (float**)malloc(nDev * sizeof(float*));
    cudaStream_t* s = (cudaStream_t*)malloc(sizeof(cudaStream_t)*nDev*nStream);


    for (int i = 0; i < nDev; ++i) {
        CUDACHECK(cudaSetDevice(i));
        //CUDACHECK(cudaDeviceSetLimit(cudaLimitDevRuntimePendingLaunchCount, 10000));
        CUDACHECK(cudaMalloc(buffer + i, size * sizeof(float)));
        CUDACHECK(cudaMemset(buffer[i], 1, size * sizeof(float)));
        for (int j = 0; j<nStream; j++)
        CUDACHECK(cudaStreamCreate(s+i*nStream+j));
    }

    for (int i = 0; i < nDev; ++i) {
        CUDACHECK(cudaSetDevice(i));
        for (int j=0; j<10000; j++) {
            for (int k=0; k<nStream; k++)
            add<<<32, 1024, 0, s[i*nStream+k]>>>(buffer[i], 1000);
        }
    }

    for (int i = 0; i < nDev; ++i) {
        CUDACHECK(cudaSetDevice(i));
        cudaDeviceSynchronize();
    }


    //free device buffers
    for (int i = 0; i < nDev; ++i) {
        CUDACHECK(cudaSetDevice(i));
        CUDACHECK(cudaFree(buffer[i]));
    }

    printf("Success \n");
    return 0;
}

这是nvprof结果:

当我创建3个流时,前3000个内核快速启动,然后变慢

nvprof1.png

当我创建1个流时,前1000个内核快速启动,然后变慢

nvprof1.png

1 个答案:

答案 0 :(得分:1)

您正在见证的行为是预期的行为。如果在cuda标记中搜索“队列”或“启动队列”,则会发现许多其他与之相关的问题。 CUDA有一个内核启动进入的队列(显然是每个流)。只要未完成的启动计数小于队列深度,启动过程将是异步的。

但是,当未完成(即未完成)的启动超过队列深度时,启动过程将变为一种同步行为(尽管通常情况下不是同步的)。具体来说,当未完成的内核启动次数超过队列深度时,启动过程将阻塞正在执行下一次启动的CPU线程,直到启动插槽在队列中打开为止(有效地意味着内核已在另一端退出了)。队列)。

您对此没有任何了解(无法查询队列中打开的插槽数量),也无法查看或控制队列深度。我在这里引用的大多数信息都是通过检查获得的;据我所知,它尚未在CUDA文档中正式发布。

正如评论中已经讨论的那样,减轻您对多设备方案中的发射的担忧的一种可能方法是,首先发射广度优先而不是深度优先。我的意思是,您应该修改启动循环,以便先在设备0上启动下一个内核,然后再在设备0上启动内核,然后在设备1上启动设备2,依此类推。这样,您将获得最佳性能。所有GPU将在启动顺序中尽早进行处理。

如果您想查看CUDA行为或文档的变化,通常的建议是成为developer.nvidia.com上的注册开发人员,然后在此处登录您的帐户并提交错误,使用可访问的错误提交流程通过单击右上角的帐户名。