并行运行多个流(而不是线程/块)

时间:2013-08-21 00:39:18

标签: cuda

我有一个内核,我想从配置“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在队列中没有阻塞(如果当时没有其他数据传输,但在我的情况下,数据传输并不耗时)。

1 个答案:

答案 0 :(得分:2)

在Linux上可以很容易地看到并发内核执行。

有关示例和简易测试的详细信息,请参阅concurrent kernels sample

内核之间良好的并发性通常需要几件事:

  • 支持并发内核的设备,因此支持cc 2.0或更新的设备
  • 内核在块数和其他资源使用(寄存器,共享内存)方面足够小,以便多个内核可以实际执行。通常会观察到具有较大资源要求的内核以串行方式运行。这是预期的行为。
  • 正确使用流以实现并发

此外,并发内核通常意味着复制/计算重叠。为了使复制/计算重叠起作用,您必须:

  • 使用具有足够复制引擎的GPU。有些GPU有一个引擎,有些有2个引擎。如果你的GPU有一个引擎,你可以将一个复制操作(即一个方向)与内核执行重叠。如果您有2个复制引擎(您的GeForce GPU有1个),您可以在内核执行时重叠两个复制方向。
  • 将固定(主机)内存用于将复制到GPU全局内存或从GPU全局内存复制的任何数据,这些内容将成为您要重叠的任何复制操作的目标(来回)
  • 正确使用流以及相关api调用的必要异步版本(例如cudaMemcpyAsync

关于您观察到较小的32x1024内核不同时执行,这可能是资源问题(块,寄存器,共享内存),防止了很多重叠。如果第一个内核中有足够的块来占用GPU执行资源,那么在第一个内核完成或大部分内核完成之前,期望其他内核开始执行是不明智的。

编辑:回复以下问题修改和其他评论。

是的,GTX480只有一个副本"队列" (我在答案中明确提到了这一点,但我把它称为副本"引擎")。您将只能在任何给定时间运行一个 cudaMemcpy ...操作,因此在任何给定时间只有一个方向(H2D或D2H)实际上可以移动数据,您将只看到一个 cudaMemcpy ...操作与任何给定内核重叠。 cudaStreamSynchronize导致流等待,直到 ALL 先前发布到该流的CUDA操作完成。

请注意,您最后一个示例中的cudaStreamSynchronize不一定是必要的,我不这么认为。 Streams有2个执行特性:

  1. 发布到同一个流的cuda操作(API调用,内核调用,所有内容)将始终按顺序执行 ,无论您使用Async API还是任何其他注意事项。
  2. cuda操作发布到单独的流,假设已满足所有必要的要求,将彼此异步执行。
  3. 由于第1项,在您的最后一种情况下,您的最终"复制数据GPU->主机"即使没有cudaStreamSynchronize调用,在向该流发出的上一个内核调用完成之前,操作将不会开始。所以我认为你可以摆脱那个电话,即你列出的第二个案例应该与最后一个案例没有什么不同,而在第二个案例中你也不需要睡眠操作。发布到同一个流的cudaMemcpy ...将不会开始,直到该流中的所有先前cuda活动完成。这是溪流的特征。

    EDIT2:我不确定我们是否在此取得任何进展。您在GTC preso here(幻灯片21)中指出的问题是一个有效的问题,但您可以通过插入其他同步操作来解决它,也不会阻止内核&#34 ;帮助你,也不是一个复制引擎或2的功能。如果你想在单独的流中发布内核,但没有其他干预cuda操作顺序发布,那么存在这种危险。正如下一张幻灯片所指出的那样,解决方案是不按顺序发出内核,这与第二种情况大致相当。我再说一遍:

    • 您已经确定您的案例2提供了良好的并发性
    • 数据完整性不需要这种情况下的睡眠操作

    如果您想提供一个演示该问题的简短示例代码,也许可以进行其他发现。