我有一个内核,我想从配置“1 block x 32 threads”开始。为了增加并行性,我想启动几个流而不是运行比“1个块x 32个线程”更大的“工作包”。我想在数据来自网络的程序中使用GPU。我不想等到有更大的“工作包”可用。 代码如下:
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
真正的代码要复杂得多,但我想保持简单(15个CPU线程使用GPU)。
代码可以正常工作,但是流不会按预期同时运行。 GTX 480有15个SM,每个SM有32个着色器处理器。我希望如果我启动内核15次,所有15个流并行运行,但事实并非如此。我使用过Nvidia Visual Profiler,最多有5个并行运行的流。通常只有一个流运行。表现非常糟糕。
我使用“64块x 1024线程”配置获得最佳结果。如果我使用“32块x 1024线程”配置而是使用两个流,则一个接一个地执行流并且性能下降。我正在使用Cuda Toolkit 5.5和Ubuntu 12.04。
有人可以解释为什么会这样,可以给我一些背景资料吗?它应该在较新的GPU上运行得更好吗?在您不希望缓冲数据的时间关键应用程序中使用GPU的最佳方法是什么?可能这是不可能的,但我正在寻找能让我更接近解决方案的技术。
新闻:
我做了一些进一步的研究。问题是最后一次cudaMemcpyAsync(..)(GPU->主机拷贝)调用。如果我删除它,所有流都会并发运行。我认为这个问题在幻灯片21的http://on-demand.gputechconf.com/gtc-express/2011/presentations/StreamsAndConcurrencyWebinar.pdf中有说明。他们说费米有两个复制队列,但这只适用于特斯拉和四核卡,对吗?我认为问题在于GTX 480只有一个复制队列,并且所有复制命令(主机 - > GPU和GPU->主机)都放在这一个队列中。一切都是非阻塞的,第一个线程的GPU->主机memcopy阻止其他线程的主机 - > GPU memcopy调用。 这里有一些观察:
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
}
- >有效:流同时运行
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- sleep(10)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
- >有效:流同时运行
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- cudaStreamSynchronize(stream i)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
- >不起作用!!!也许cudaStreamSynchronize放在副本队列中?
有人知道这个问题的解决方案吗?像阻塞内核调用之类的东西会很酷。如果内核已经完成,应该调用最后一个cudaMemcpyAsync()(GPU->设备)。
EDIT2: 这是一个澄清我的问题的例子: 为了简单起见,我们有两个流:
Stream1:
------------
HostToGPU1
kernel1
GPUToHost1
Stream2:
------------
HostToGPU2
kernel2
GPUToHost2
启动第一个流。执行HostToGPU1,启动kernel1并调用GPUToHost1。 GPUToHost1因为kernel1正在运行而阻塞。同时启动Stream2。调用HostToGPU2,Cuda将其放入队列但由于GPUToHost1阻塞直到内核1完成,因此无法执行。目前没有数据传输。 Cuda只是等待GPUToHost1。所以我的想法是在kernel1完成时调用GPUToHost1。这种接缝是它与sleep(..)一起工作的原因,因为在内核完成时会调用GPUToHost1。自动阻止CPU线程的内核启动会很酷。 GPUToHost1在队列中没有阻塞(如果当时没有其他数据传输,但在我的情况下,数据传输并不耗时)。
答案 0 :(得分:2)
在Linux上可以很容易地看到并发内核执行。
有关示例和简易测试的详细信息,请参阅concurrent kernels sample。
内核之间良好的并发性通常需要几件事:
此外,并发内核通常意味着复制/计算重叠。为了使复制/计算重叠起作用,您必须:
cudaMemcpyAsync
关于您观察到较小的32x1024内核不同时执行,这可能是资源问题(块,寄存器,共享内存),防止了很多重叠。如果第一个内核中有足够的块来占用GPU执行资源,那么在第一个内核完成或大部分内核完成之前,期望其他内核开始执行是不明智的。
编辑:回复以下问题修改和其他评论。
是的,GTX480只有一个副本"队列" (我在答案中明确提到了这一点,但我把它称为副本"引擎")。您将只能在任何给定时间运行一个 cudaMemcpy ...操作,因此在任何给定时间只有一个方向(H2D或D2H)实际上可以移动数据,您将只看到一个 cudaMemcpy ...操作与任何给定内核重叠。 cudaStreamSynchronize
导致流等待,直到 ALL 先前发布到该流的CUDA操作完成。
请注意,您最后一个示例中的cudaStreamSynchronize
不一定是必要的,我不这么认为。 Streams有2个执行特性:
Async
API还是任何其他注意事项。 由于第1项,在您的最后一种情况下,您的最终"复制数据GPU->主机"即使没有cudaStreamSynchronize
调用,在向该流发出的上一个内核调用完成之前,操作将不会开始。所以我认为你可以摆脱那个电话,即你列出的第二个案例应该与最后一个案例没有什么不同,而在第二个案例中你也不需要睡眠操作。发布到同一个流的cudaMemcpy ...将不会开始,直到该流中的所有先前cuda活动完成。这是溪流的特征。
EDIT2:我不确定我们是否在此取得任何进展。您在GTC preso here(幻灯片21)中指出的问题是一个有效的问题,但您可以通过插入其他同步操作来解决它,也不会阻止内核&#34 ;帮助你,也不是一个复制引擎或2的功能。如果你想在单独的流中发布内核,但没有其他干预cuda操作顺序发布,那么存在这种危险。正如下一张幻灯片所指出的那样,解决方案是不按顺序发出内核,这与第二种情况大致相当。我再说一遍:
如果您想提供一个演示该问题的简短示例代码,也许可以进行其他发现。