如何同时在多个GPU(2)上运行OpenCL?

时间:2016-10-14 11:30:32

标签: multithreading concurrency kernel opencl multi-gpu

我有两个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同时运行而不必等待另一个的结果?我应该创建两个上下文吗?我应该做别的吗?

请记住,有一个内核

1 个答案:

答案 0 :(得分: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 + copy到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驱动程序,而主机端并发性能取决于您的优化。

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