我发现当我启动许多内核(超过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个内核快速启动,然后变慢
当我创建1个流时,前1000个内核快速启动,然后变慢
答案 0 :(得分:1)
您正在见证的行为是预期的行为。如果在cuda
标记中搜索“队列”或“启动队列”,则会发现许多其他与之相关的问题。 CUDA有一个内核启动进入的队列(显然是每个流)。只要未完成的启动计数小于队列深度,启动过程将是异步的。
但是,当未完成(即未完成)的启动超过队列深度时,启动过程将变为一种同步行为(尽管通常情况下不是同步的)。具体来说,当未完成的内核启动次数超过队列深度时,启动过程将阻塞正在执行下一次启动的CPU线程,直到启动插槽在队列中打开为止(有效地意味着内核已在另一端退出了)。队列)。
您对此没有任何了解(无法查询队列中打开的插槽数量),也无法查看或控制队列深度。我在这里引用的大多数信息都是通过检查获得的;据我所知,它尚未在CUDA文档中正式发布。
正如评论中已经讨论的那样,减轻您对多设备方案中的发射的担忧的一种可能方法是,首先发射广度优先而不是深度优先。我的意思是,您应该修改启动循环,以便先在设备0上启动下一个内核,然后再在设备0上启动内核,然后在设备1上启动设备2,依此类推。这样,您将获得最佳性能。所有GPU将在启动顺序中尽早进行处理。
如果您想查看CUDA行为或文档的变化,通常的建议是成为developer.nvidia.com上的注册开发人员,然后在此处登录您的帐户并提交错误,使用可访问的错误提交流程通过单击右上角的帐户名。