0

我有两个 GPU、一个内核、一个上下文和两个命令队列(每个 GPU 1 个)。我试图在每个命令队列都运行的循环中运行它们,然后我尝试了两者queue.finish(),并queue.flush()希望同时在 GPU 上运行工作。

但实际发生的情况是,数据首先发送到一个设备,GPU 执行其工作,然后另一个 GPU 开始工作。它花费的时间是单个 GPU 的两倍。这不是我打算实现的!

虽然我也在将缓冲区读回主机代码,但有人可能认为这可能是第二个 GPU 等待第一个结果的问题。但我也注释掉了结果的回读,没有任何运气。它仍然是一样的。

for (unsigned int iter = 0; iter < numberOfDevices; iter++) {
    // Load in kernel source, creating a program object for the context
     cl::Program programGA(context, stringifiedSourceCL, true);

    // Create the kernel functor
    auto kernelGA = cl::make_kernel<cl::Buffer,
                                    cl::Buffer,
                                    cl::Buffer>
                                    (programGA, "kernelGA");

    // CREATE THE BUFFERS.

    d_pop = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                      (Length * POP_SIZE * sizeof(double)),
                       pop);
    // And other buffers...

    // Enqueue the kernel.
    kernelGA(cl::EnqueueArgs(queue[iter],
                             cl::NDRange(POP_SIZE / numberOfDevices)),
                             d_integerParameters,
                             d_doubleParameters, ... and so on...);

    // Enqueue in the corresponding device.
    queue[iter].flush();

    // Get results from the queue.
    queue[iter].enqueueReadBuffer(buf_half_population,
                                        true,
                                        0,
                                        popSizeMD * sizeof(double),
                                        popMD[iter]);

    // Add up the results after every iteration.
    for (int in_iter = 0; in_iter < populationSizeMD; in_iter++, it_j++) {
         population[it_j] = populationMD[iter][in_iter];
    }
}

我的问题是:我应该怎么做才能实现真正的并发性并使 GPU 同时运行而不等待另一个结果?我应该创建两个上下文吗?我应该做点别的吗?

请记住,只有一个内核

4

1 回答 1

0

ClFinish 是一个阻塞命令。

在对所有队列的所有命令进行排队后,您需要主机端并发 + 多个上下文(每个设备 1 个)或延迟所有队列的刷新/完成。

对于主机端并发,

兑换

for (unsigned int iter = 0; iter < numberOfDevices; iter++) {...}

Concurrent.for(){} // if there is any for the language you working on

Parallel.For(0,n,i=>{...}); // C#

版本,因此每次迭代都是并发的。例如,Parallel.For 在 C# 中工作。然后确保在不同范围的数组上工作,这样缓冲区复制操作就不会重合。如果存在任何 pci-e 带宽不足,您可以在第一次迭代中复制到 gpu-1,在 gpu-1 上计算 + 在第二次迭代中复制到 gpu-2,从 gpu-1 获取结果并在第三次在 gpu-2 上计算迭代,在最后一次迭代中从 gpu-2 获取结果。如果没有饥饿,您可以在不同的循环中执行所有副本 + 所有计算 + 所有结果:

Parallel.For( ... copy to gpus)
sync_point() ---> because other gpus result can change some input arrays,
             need to be sure all gpus have their own copies/buffers updated
             but not needed if it is an embarrassingly parallel workload
Parallel.For( ... compute on gpus + get results)

对于延迟完成/冲洗:

 for(){...} // divide work into 4-8 parts per gpu, 
               so all gpu can have its turn without waiting much
               computing concurrently between mgpus
 flush1                        
 flush2
 finish1
 finish2

所以他们都开始同时向 gpus 发布作品。此代码的性能应取决于 gpu 驱动程序,而主机端并发性能取决于您的优化。

第一种类型对我来说更容易,因为我可以为每个设备获得更好的时序数据,以便在所有 gpu 上对工作进行负载平衡(不仅仅是将其分成两半,随着每个 gpu 上花费的时间、缓冲区副本和工作范围而相应地改变)。但是,如果驱动程序更好地管理副本,则第二种类型应该更快。特别是如果您正在执行 map/unmap 而不是 write/read,因为 map/map 在获取结果或复制到 gpu 时使用 dma 引擎而不是 cpu。

于 2016-10-14T15:40:22.620 回答