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