我有一个内核,我想从配置“1 块 x 32 线程”开始。为了增加并行度,我想启动多个流,而不是运行比“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->Host copy) 调用。如果我删除它,所有流都会同时运行。我认为问题在幻灯片 21 上的http://on-demand.gputechconf.com/gtc-express/2011/presentations/StreamsAndConcurrencyWebinar.pdf中有说明。他们说在 Fermi 上有两个复制队列,但这仅适用于特斯拉和quadro卡,对吧?我认为问题在于 GTX 480 只有一个复制队列,所有复制命令(主机->GPU 和 GPU->主机)都放在这个队列中。一切都是非阻塞的,第一个线程的 GPU->host memcopy 阻塞了其他线程的 host->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->device)。
Edit2: 这里有一个例子来澄清我的问题:为了简单起见,我们有 2 个流:
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 没有在队列中阻塞(如果当时没有其他数据传输,但在我的情况下,数据传输并不耗时)。