CUDA stream的问题

仿照书上的例子写了多个流的程序,但是用profiler运行后发现并没有overlap,不知道是不是用的有问题,麻烦大家给看一下。程序如下:

#include <stdlib.h>
#include <stdio.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>

#define N (1024*1024)
#define FULL_DATASIZE (N*30)

__global__ void kernel(int *a,int *b,int *c)
{
	int tid=threadIdx.x+blockIdx.x*blockDim.x;
	if(tid < N)
	{
		int idx1 = (tid + 1) % 256;
		int idx2 = (tid + 2) % 256;
		float as = (a[tid] + a[idx1] + a[idx2]);
		float bs = (b[tid] + b[idx1] + b[idx2]);
		c[tid] = (as + bs) / 2;
	}
}

int main()
{
	cudaDeviceProp prop;
	int dev;
	cudaGetDevice(&dev);
	cudaGetDeviceProperties(&prop,dev);
	if(!prop.deviceOverlap)
	{
		printf("Device doesn't support overlap\n");
		return 0;
	}

	//host
	int *a,*b,*c;
	cudaHostAlloc((void**)&a, FULL_DATASIZE * sizeof(int), cudaHostAllocDefault);
	cudaHostAlloc((void**)&b, FULL_DATASIZE * sizeof(int), cudaHostAllocDefault);
	cudaHostAlloc((void**)&c, FULL_DATASIZE * sizeof(int), cudaHostAllocDefault);

	for(int i = 0; i < FULL_DATASIZE; i++)
	{
		a[i] = rand();
		b[i] = rand();
	}

	//device
	int *dev_a0, *dev_b0, *dev_c0, *dev_a1, *dev_b1, *dev_c1, *dev_a2, *dev_b2, *dev_c2;
	cudaMalloc((void**)&dev_a0, N * sizeof(int));
	cudaMalloc((void**)&dev_b0, N * sizeof(int));
	cudaMalloc((void**)&dev_c0, N * sizeof(int));

	cudaMalloc((void**)&dev_a1, N * sizeof(int));
	cudaMalloc((void**)&dev_b1, N * sizeof(int));
	cudaMalloc((void**)&dev_c1, N * sizeof(int));

	cudaMalloc((void**)&dev_a2, N * sizeof(int));
	cudaMalloc((void**)&dev_b2, N * sizeof(int));
	cudaMalloc((void**)&dev_c2, N * sizeof(int));

	cudaEvent_t start,stop;
	cudaEventCreate(&start);
	cudaEventCreate(&stop);
	cudaEventRecord(start,0);

	cudaStream_t stream0, stream1, stream2;
	cudaStreamCreate(&stream0);
	cudaStreamCreate(&stream1);
	cudaStreamCreate(&stream2);

	for(int i = 0; i < FULL_DATASIZE; i += 3 * N )
	{
		cudaMemcpyAsync(dev_a0, a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);
		cudaMemcpyAsync(dev_a1, a + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);
		cudaMemcpyAsync(dev_a2, a + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);
		cudaMemcpyAsync(dev_b0, b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);
		cudaMemcpyAsync(dev_b1, b + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);
		cudaMemcpyAsync(dev_b2, b + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);
		kernel<<<(N - 255) / 256, 256, 0, stream0>>>(dev_a0, dev_b0, dev_c0);
		kernel<<<(N - 255) / 256, 256, 0, stream1>>>(dev_a1, dev_b1, dev_c1);
		kernel<<<(N - 255) / 256, 256, 0, stream2>>>(dev_a2, dev_b2, dev_c2);
		cudaMemcpyAsync(c + i, dev_c0, N * sizeof(int), cudaMemcpyDeviceToHost, stream0);
		cudaMemcpyAsync(c + i + N, dev_c1, N * sizeof(int), cudaMemcpyDeviceToHost, stream1);
		cudaMemcpyAsync(c + i + 2 * N, dev_c2, N * sizeof(int), cudaMemcpyDeviceToHost, stream2);

		//cudaMemcpyAsync(dev_a0, a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);
		//cudaMemcpyAsync(dev_b0, b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);
		//kernel<<<(N - 255) / 256, 256, 0, stream0>>>(dev_a0, dev_b0, dev_c0);
		//cudaMemcpyAsync(c + i, dev_c0, N * sizeof(int), cudaMemcpyDeviceToHost, stream0);

		//cudaMemcpyAsync(dev_a1, a + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);
		//cudaMemcpyAsync(dev_b1, b + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);
		//kernel<<<(N - 255) / 256, 256, 0, stream1>>>(dev_a1, dev_b1, dev_c1);
		//cudaMemcpyAsync(c + i + N, dev_c1, N * sizeof(int), cudaMemcpyDeviceToHost, stream1);

		//cudaMemcpyAsync(dev_a2, a + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);
		//cudaMemcpyAsync(dev_b2, b + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);
		//kernel<<<(N - 255) / 256, 256, 0, stream2>>>(dev_a2, dev_b2, dev_c2);
		//cudaMemcpyAsync(c + i + 2 * N, dev_c2, N * sizeof(int), cudaMemcpyDeviceToHost, stream2);
	}

	cudaDeviceSynchronize();

	cudaEventRecord(stop,0);
	cudaEventSynchronize(stop);
	float elapsedtime;
	cudaEventElapsedTime(&elapsedtime, start, stop);

	printf("time used:%3.1fms",elapsedtime);

	cudaFree(dev_a0);
	cudaFree(dev_b0);
	cudaFree(dev_c0);

	cudaFree(dev_a1);
	cudaFree(dev_b1);
	cudaFree(dev_c1);

	cudaFree(dev_a2);
	cudaFree(dev_b2);
	cudaFree(dev_c2);

	cudaFreeHost(a);
	cudaFreeHost(b);
	cudaFreeHost(c);

	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	cudaStreamDestroy(stream0);
	cudaStreamDestroy(stream1);
	cudaStreamDestroy(stream2);

	cudaDeviceReset();

	return 0;
}

profiler运行的截图如下:
[attach]3179[/attach]

楼主可以尝试下这种写法:

for(i=0 to n)
{
memcpyAsync(,H2D,stream[i]);
kernel<<<,stream[i]>>>();
memcpyAsync(,D2H,stream[i]);
}

需要设备有2.x计算能力的支持,这样的话能够支持concurrent data transfer,具体的说明还请查阅programming guide.

至于你上面的代码为什么也没有并发,原因也在programming guide里有介绍。

LZ您好:

实现kernel和copy互相掩盖,基本上需要这几个因素:host端的page-locked memory,异步版的copy,写到不同的stream里面,不能使用默认stream,和代码的具体写法有关,以及和驱动版本有关。

前面几点您都注意到了,我说一下最后两点。

因为一些历史原因,programming guide和SDK sample里面的写法是不同的,以及同一个写法在不同的硬件上行为也是有所不同的。您可以看一下如下链接:
https://developer.nvidia.com/con … a-transfers-cuda-cc
以及需要指出的是,同时双向copy这个功能,只开放给计算能力2.0及以上的telsa品牌的GPU卡,并不开放给geforce品牌。

以及,如果您尝试了不同的写法,但依然不行,那么有可能是驱动的问题,不同版本的驱动,有时对geforce显卡的CUDA功能支持程度不一,据观察,有时会开放或者收回部分功能。有时老版本驱动可能效果更好。

那么,针对您的问题,建议您根据前面链接的内容,尝试改写您的代码,看看不同形式下行为有何变化。如果不行,可以尝试更换稍老的驱动和toolkit组合(如fermi核心的GPU可以考虑CUDA 4.2)。

当然,有一个终极解决方案,那就是更换Telsa K20卡,该卡完全无上述所有烦恼,不仅驱动会放开所有的CUDA功能的权限,而且新设计的硬件带有任务队列缓冲,能智能分离出您任务的依赖链,并予以高效执行,同时对代码书写的形式不敏感,两种写法都能正确识别。

如果您有相应的硬件条件,不妨一试。

祝您编码顺利~


修改了个别错别字。

感谢您的回答,您提供的方法除了驱动版本没试以外,其它的都试了,还是kernel的执行可以重叠,而数据传输和kernel的执行还是不能重叠。我用的是GTX 650TI+CUDA 5.0

感谢您的回答,这种写法也试过了,数据拷贝和kernel执行还是不能重叠。

LZ你可以做如下尝试:
将你主循环写成如下顺序:
cudaMemcpyAsync(a0);
cudaMemcpyAsync(b0);
cudaMemcpyAsync(a1);
cudaMemcpyAsync(b1);
cudaMemcpyAsync(a2);
cudaMemcpyAsync(b2);
kernel(0);
kernel(1);
kernel(2);
cudaMemcpyAsync(c0);
cudaMemcpyAsync(c1);
cudaMemcpyAsync(c2);
注意顺序~~~~然后说下结果(最好能上nvvp的图)。估计可能会传入和kernel并发。当然,对于geforce的卡,传入/传出的并发是是不被支持的——这个要想改善,就只能上tesla了。

您好,按照您说的,我把循环改成了下面的形式:

 for(int i = 0; i < FULL_DATASIZE; i += 3 * N )
	{
		cudaMemcpyAsync(dev_a0, a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);
		cudaMemcpyAsync(dev_b0, b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream0);

		cudaMemcpyAsync(dev_a1, a + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);
		cudaMemcpyAsync(dev_b1, b + i + N, N * sizeof(int), cudaMemcpyHostToDevice, stream1);

		cudaMemcpyAsync(dev_a2, a + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);
		cudaMemcpyAsync(dev_b2, b + i + 2 * N, N * sizeof(int), cudaMemcpyHostToDevice, stream2);

		kernel<<<(N - 255) / 256, 256, 0, stream0>>>(dev_a0, dev_b0, dev_c0);
		kernel<<<(N - 255) / 256, 256, 0, stream1>>>(dev_a1, dev_b1, dev_c1);
		kernel<<<(N - 255) / 256, 256, 0, stream2>>>(dev_a2, dev_b2, dev_c2);

		cudaMemcpyAsync(c + i, dev_c0, N * sizeof(int), cudaMemcpyDeviceToHost, stream0);
		cudaMemcpyAsync(c + i + N, dev_c1, N * sizeof(int), cudaMemcpyDeviceToHost, stream1);		
		cudaMemcpyAsync(c + i + 2 * N, dev_c2, N * sizeof(int), cudaMemcpyDeviceToHost, stream2);
	}

profiler运行截图如下:
[attach]3180[/attach]
数据传输还是没能和kernel执行重叠。

从LZ反馈的图上看,以及LZ也应用了cudaMallocHost分配主机端空间,感觉像是不支持并发的感觉……
LZ可否尝试下打印prop.asyncEngineCount的值?如果此值等于0,说明设备本身不支持并发。
又或者LZ是否将CUDA LAUNCH BLOCKING全局变量设成了1,禁用了并发?
建议LZ先打印下asyncEngineCount看看。

asyncEngineCount的值是1的,我输出了下,如图所示:
[attach]3181[/attach]

另外,您说的CUDA LAUNCH BLOCKING这个变量是在哪设置的呢?我好像没有设置过。

没设置过应该是默认可以并行的说……
以及我用我的卡跑了下你的程序(按我8L说法改过的),nvvp结果是可以并行的,我想不到还有什么问题了……你可以等下午版主上来,看他们的建议。
[attach]3182[/attach]

ice斑竹说可能是驱动版本的原因,可否告知您使用的驱动版本?

我的驱动版本是311.06

我也是这个问题,请问最后这个问题解决了吗?怎么解决的

在楼主问题的基础上(正确使用了多个流,以及async版本的函数,以及page-locked memory), 依然不能传输和计算overlap的最大可能是您用的geforce卡,

在windows 7/vista下,从CUDA 5.0+(的配套版本驱动)开始,您将具有较低的可能让传输和计算同时进行。

您可以改用Linux或者购买tesla卡,或者降级到较老的驱动。

没有解决,不过我把显卡换到另外一台机器上,gtx650ti+win764位+cuda 5.0+最新显卡驱动,是可以实现kernel execution的重叠以及kernel execution和data transfer的重叠。另外,还在另外一台机器上,gt220+win732位+cuda5.0(这个显卡驱动我倒不清楚是什么版本的),也是可以实现的。总之,具体原因不是很清楚。

我的显卡和楼主一样,也是GTX650ti。用的是CUDA4.2+WIN7 64位。原来的驱动是314.07,现在换成较低版本的310.90刚试了一下好像不行。等我再换更低的试一下。另外我在CUDA编程指南里面发现了这句话:When an application is run via cuda-gdb, the Visual Profiler, or the Parallel Nsight CUDA Debugger, all launches are synchronous.
是不是因为我用nvvp或者nsight调试的时候程序本身就不能重叠呢

我目前也是GTX650ti+win7 64位。等回去再换一下CUDA5.0和更新的驱动试一下。另外请问您观察到成功实现重叠的时候是使用的nvvp或者nsight吗?我在CUDA编程指南里面发现了这句话:When an application is run via cuda-gdb, the Visual Profiler, or the Parallel Nsight CUDA Debugger, all launches are synchronous.不知道这是不是原因。

就是用nvvp看的,确实能看到重叠。我在64位机器上反而用低版本的驱动没有kernel执行和数据传输的重叠,只有kernel执行的重叠,更新到最新版本的才有的。在32位机器上最新的和旧一点的都用过,都是只有kernel执行的重叠,就是不知道是为什么。

好奇怪啊,看来原因确实不好找,不过感觉应该不是显卡的问题,可能还与主机有关。谢谢楼主的回答。我去换成最新版的驱动再试一试,有机会再找其他主机试一下。