OpenCL - 使用原子减少 double
OpenCL - using atomic reduction for double
我知道不推荐使用 OpenCL-1.x 的原子函数,但我只想了解一个原子示例。
以下内核代码运行不佳,它为计算所有数组值的总和(求和)生成随机最终值:
#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable
void atom_add_double(volatile __local double *val, double delta)
{
union {
double f;
ulong i;
} old, new;
do
{
old.f = *val;
new.f = old.f + delta;
}
while (atom_cmpxchg((volatile __local ulong *)val, old.i, new.i) != old.i);
}
__kernel void sumGPU ( __global const double *input,
__local double *localInput,
__global double *finalSum
)
{
uint lid = get_local_id(0);
uint gid = get_global_id(0);
uint localSize = get_local_size(0);
uint groupid = get_group_id(0);
local double partialSum;
local double finalSumTemp;
// Initialize sums
if (lid==0)
{
partialSum = 0.0;
finalSumTemp = 0.0;
}
barrier(CLK_LOCAL_MEM_FENCE);
// Set in local memory
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
// Compute atom_add into each workGroup
barrier(CLK_LOCAL_MEM_FENCE);
atom_add_double(&partialSum, localInput[lid]);
// See and Check if barrier below is necessary
barrier(CLK_LOCAL_MEM_FENCE);
// Final sum of partialSums
if (lid==0)
{
atom_add_double(&finalSumTemp, partialSum);
*finalSum = finalSumTemp;
}
}
带有 global id
策略的版本运行良好,但上面的版本通过使用 local memory
(共享内存),没有给出预期的结果([= 的值17=] 每次执行都是随机的)。
这里是我放入主机代码中的缓冲区和内核参数:
// Write to buffers
ret = clEnqueueWriteBuffer(command_queue, inputBuffer, CL_TRUE, 0,
nWorkItems * sizeof(double), xInput, 0, NULL, NULL);
ret = clEnqueueWriteBuffer(command_queue, finalSumBuffer, CL_TRUE, 0,
sizeof(double), finalSumGPU, 0, NULL, NULL);
// Set the arguments of the kernel
clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputBuffer);
clSetKernelArg(kernel, 1, local_item_size*sizeof(double), NULL);
clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&finalSumBuffer);
最后,我阅读了 finalSumBuffer
以获得总和值。
我认为我的问题来自内核代码,但我找不到错误所在。
如果有人能看出问题所在,请告诉我。
谢谢
更新 1:
我几乎成功完成了这个减少。按照 huseyin tugrul buyukisik 的建议,我修改了内核代码如下:
#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable
void atom_add_double(volatile __local double *val, double delta)
{
union {
double d;
ulong i;
} old, new;
do
{
old.d = *val;
new.d = old.d + delta;
}
while (atom_cmpxchg((volatile __local ulong *)val, old.i, new.i) != old.i);
}
__kernel void sumGPU ( __global const double *input,
__local double *localInput,
__local double *partialSum,
__global double *finalSum
)
{
uint lid = get_local_id(0);
uint gid = get_global_id(0);
uint localSize = get_local_size(0);
uint groupid = get_group_id(0);
// Initialize partial sums
if (lid==0)
partialSum[groupid] = 0.0;
barrier(CLK_LOCAL_MEM_FENCE);
// Set in local memory
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
// Compute atom_add into each workGroup
barrier(CLK_LOCAL_MEM_FENCE);
atom_add_double(&partialSum[groupid], localInput[lid]);
// See and Check if barrier below is necessary
barrier(CLK_LOCAL_MEM_FENCE);
// Compute final sum
if (lid==0)
*finalSum += partialSum[groupid];
}
如前所述 huseyin ,我不需要对所有部分和的最终求和使用原子函数。
最后我做了:
// Compute final sum
if (lid==0)
*finalSum += partialSum[groupid];
但不幸的是,最后的总和没有给出预期的值并且该值是随机的(例如,对于 nwork-items = 1024
和 size-WorkGroup = 16
,我得到的随机值顺序为 [1e+3 - 1e+4]
而不是预期的 5.248e+05
。
以下是主机代码中的参数设置:
// Set the arguments of the kernel
clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputBuffer);
clSetKernelArg(kernel, 1, local_item_size*sizeof(double), NULL);
clSetKernelArg(kernel, 2, nWorkGroups*sizeof(double), NULL);
clSetKernelArg(kernel, 3, sizeof(cl_mem), (void *)&finalSumBuffer);
你能看出我的内核代码哪里出错了吗?
谢谢
不是错误而是逻辑问题:
atom_add_double(&finalSumTemp, partialSum);
每个组只工作一次(通过 zero-local-indexed 线程)。
所以你只是在做
finalSumTemp = partialSum
所以这里不需要原子。
存在竞争条件
*finalSum = finalSumTemp;
工作组之间,每个 zero-index 本地线程写入相同的地址。 所以这应该是原子加法(用于学习目的)或者可以写在不同的单元格上以在主机端添加,例如sum_group1+sum_group2+。 .. = 总和。
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
这里使用 groupid 对 multi-device 求和是可疑的。因为每个设备都有自己的全局范围和工作组 ID 索引,所以两个设备可以对两个不同的组具有相同的组 ID 值。当使用多个设备时,应该使用一些设备相关的偏移量。如:
idx= get_global_id(0) + deviceOffset[deviceId];
此外,如果原子操作不可避免,并且正好操作N次,则可以将其移至单个线程(例如0索引线程)并在第二个内核中循环N次(可能更快),除非不能通过其他方式隐藏原子操作延迟。
我知道不推荐使用 OpenCL-1.x 的原子函数,但我只想了解一个原子示例。
以下内核代码运行不佳,它为计算所有数组值的总和(求和)生成随机最终值:
#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable
void atom_add_double(volatile __local double *val, double delta)
{
union {
double f;
ulong i;
} old, new;
do
{
old.f = *val;
new.f = old.f + delta;
}
while (atom_cmpxchg((volatile __local ulong *)val, old.i, new.i) != old.i);
}
__kernel void sumGPU ( __global const double *input,
__local double *localInput,
__global double *finalSum
)
{
uint lid = get_local_id(0);
uint gid = get_global_id(0);
uint localSize = get_local_size(0);
uint groupid = get_group_id(0);
local double partialSum;
local double finalSumTemp;
// Initialize sums
if (lid==0)
{
partialSum = 0.0;
finalSumTemp = 0.0;
}
barrier(CLK_LOCAL_MEM_FENCE);
// Set in local memory
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
// Compute atom_add into each workGroup
barrier(CLK_LOCAL_MEM_FENCE);
atom_add_double(&partialSum, localInput[lid]);
// See and Check if barrier below is necessary
barrier(CLK_LOCAL_MEM_FENCE);
// Final sum of partialSums
if (lid==0)
{
atom_add_double(&finalSumTemp, partialSum);
*finalSum = finalSumTemp;
}
}
带有 global id
策略的版本运行良好,但上面的版本通过使用 local memory
(共享内存),没有给出预期的结果([= 的值17=] 每次执行都是随机的)。
这里是我放入主机代码中的缓冲区和内核参数:
// Write to buffers
ret = clEnqueueWriteBuffer(command_queue, inputBuffer, CL_TRUE, 0,
nWorkItems * sizeof(double), xInput, 0, NULL, NULL);
ret = clEnqueueWriteBuffer(command_queue, finalSumBuffer, CL_TRUE, 0,
sizeof(double), finalSumGPU, 0, NULL, NULL);
// Set the arguments of the kernel
clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputBuffer);
clSetKernelArg(kernel, 1, local_item_size*sizeof(double), NULL);
clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&finalSumBuffer);
最后,我阅读了 finalSumBuffer
以获得总和值。
我认为我的问题来自内核代码,但我找不到错误所在。
如果有人能看出问题所在,请告诉我。
谢谢
更新 1:
我几乎成功完成了这个减少。按照 huseyin tugrul buyukisik 的建议,我修改了内核代码如下:
#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable
void atom_add_double(volatile __local double *val, double delta)
{
union {
double d;
ulong i;
} old, new;
do
{
old.d = *val;
new.d = old.d + delta;
}
while (atom_cmpxchg((volatile __local ulong *)val, old.i, new.i) != old.i);
}
__kernel void sumGPU ( __global const double *input,
__local double *localInput,
__local double *partialSum,
__global double *finalSum
)
{
uint lid = get_local_id(0);
uint gid = get_global_id(0);
uint localSize = get_local_size(0);
uint groupid = get_group_id(0);
// Initialize partial sums
if (lid==0)
partialSum[groupid] = 0.0;
barrier(CLK_LOCAL_MEM_FENCE);
// Set in local memory
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
// Compute atom_add into each workGroup
barrier(CLK_LOCAL_MEM_FENCE);
atom_add_double(&partialSum[groupid], localInput[lid]);
// See and Check if barrier below is necessary
barrier(CLK_LOCAL_MEM_FENCE);
// Compute final sum
if (lid==0)
*finalSum += partialSum[groupid];
}
如前所述 huseyin ,我不需要对所有部分和的最终求和使用原子函数。
最后我做了:
// Compute final sum
if (lid==0)
*finalSum += partialSum[groupid];
但不幸的是,最后的总和没有给出预期的值并且该值是随机的(例如,对于 nwork-items = 1024
和 size-WorkGroup = 16
,我得到的随机值顺序为 [1e+3 - 1e+4]
而不是预期的 5.248e+05
。
以下是主机代码中的参数设置:
// Set the arguments of the kernel
clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputBuffer);
clSetKernelArg(kernel, 1, local_item_size*sizeof(double), NULL);
clSetKernelArg(kernel, 2, nWorkGroups*sizeof(double), NULL);
clSetKernelArg(kernel, 3, sizeof(cl_mem), (void *)&finalSumBuffer);
你能看出我的内核代码哪里出错了吗?
谢谢
不是错误而是逻辑问题:
atom_add_double(&finalSumTemp, partialSum);
每个组只工作一次(通过 zero-local-indexed 线程)。
所以你只是在做
finalSumTemp = partialSum
所以这里不需要原子。
存在竞争条件
*finalSum = finalSumTemp;
工作组之间,每个 zero-index 本地线程写入相同的地址。 所以这应该是原子加法(用于学习目的)或者可以写在不同的单元格上以在主机端添加,例如sum_group1+sum_group2+。 .. = 总和。
int idx = groupid * localSize + lid;
localInput[lid] = input[idx];
这里使用 groupid 对 multi-device 求和是可疑的。因为每个设备都有自己的全局范围和工作组 ID 索引,所以两个设备可以对两个不同的组具有相同的组 ID 值。当使用多个设备时,应该使用一些设备相关的偏移量。如:
idx= get_global_id(0) + deviceOffset[deviceId];
此外,如果原子操作不可避免,并且正好操作N次,则可以将其移至单个线程(例如0索引线程)并在第二个内核中循环N次(可能更快),除非不能通过其他方式隐藏原子操作延迟。