OpenCL - 使用原子缩减双

时间:2017-02-04 06:05:12

标签: opencl atomic reduction

我不推荐使用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(共享内存),但没有给出预期的结果({{1}的值每次执行都是随机的。)

这里是我在主机代码中添加的Buffers和内核args:

*finalSum

最后,我读了 // 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); 来获得总和值。

我认为我的问题来自内核代码,但我无法找到错误的位置。

如果有人能看出什么是错的,那就告诉我这个很好。

由于

更新1:

我几乎设法完成这个减少。按照 huseyin tugrul buyukisik 提出的建议,我修改了这样的内核代码:

finalSumBuffer

如上所述 huseyin ,我不需要将原子函数用于所有部分和的最终总和。

所以我最后做了:

#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]; 

}                   

但不幸的是,最终的总和没有给出预期的值,而且值是随机的(例如,使用// Compute final sum if (lid==0) *finalSum += partialSum[groupid]; nwork-items = 1024,我得到{{1}的顺序的随机值而不是预期的size-WorkGroup = 16

以下是主机代码中参数的设置:

[1e+3 - 1e+4]

你能看到内核代码中的错误在哪里吗?

由于

1 个答案:

答案 0 :(得分:0)

不是错误而是逻辑问题:

atom_add_double(&finalSumTemp, partialSum);

每个组只工作一次(通过零本地索引线程)。

所以你只是在做

finalSumTemp = partialSum

所以不需要这里的原子。

有种族条件
*finalSum = finalSumTemp;
每个零索引本地线程写入同一地址的工作组之间的

所以这应该是原子加法(用于学习目的)或者可以写在不同的单元格上,以便在主机端添加,例如sum_group1 + sum_group2 + ... =总和。

int idx = groupid * localSize + lid;
localInput[lid] = input[idx];

这里使用groupid对于多设备求和是可疑的。因为每个设备都有自己的全局范围和工作组ID索引,所以两个设备可以为两个不同的组具有相同的组ID值。当使用多个设备时,应使用某些与设备相关的偏移。如:

idx= get_global_id(0) + deviceOffset[deviceId];

此外,如果原子操作不可避免,并且如果完全N次操作,则可以将其移动到单个线程(例如0索引线程)并在第二个内核中循环N次(可能更快),除非该原子操作延迟不能通过其他方式隐藏。