【问题标题】:Strategy for doing final reduction最终减少的策略
【发布时间】:2016-08-21 02:57:42
【问题描述】:

我正在尝试实现一个 OpenCL 版本来减少浮点数组。

为了实现它,我拿了以下在网上找到的sn-p代码:

__kernel void sumGPU ( __global const double *input, 
                       __global double *partialSums,
               __local double *localSums)
 {
  uint local_id = get_local_id(0);
  uint group_size = get_local_size(0);

  // Copy from global memory to local memory
  localSums[local_id] = input[get_global_id(0)];

  // Loop for computing localSums
  for (uint stride = group_size/2; stride>0; stride /=2)
     {
      // Waiting for each 2x2 addition into given workgroup
      barrier(CLK_LOCAL_MEM_FENCE);

      // Divide WorkGroup into 2 parts and add elements 2 by 2
      // between local_id and local_id + stride
      if (local_id < stride)
        localSums[local_id] += localSums[local_id + stride];
     }

  // Write result into partialSums[nWorkGroups]
  if (local_id == 0)
    partialSums[get_group_id(0)] = localSums[0];
 }                  

此内核代码运行良好,但我想通过添加每个工作组的所有部分和来计算最终总和。 目前,我通过一个简单的循环和迭代nWorkGroups 完成 CPU 的最终总和这一步。

我还看到了另一种具有原子函数的解决方案,但它似乎是为 int 实现的,而不是为浮点数实现的。我认为只有 CUDA 提供了 float 的原子函数。

我还看到我可以使用另一个内核代码来执行 sum 的此操作,但我想避免使用此解决方案以保持简单可读的源代码。也许我不能没有这个解决方案......

我必须告诉你,我在 Radeon HD 7970 Tahiti 3GB 上使用 OpenCL 1.2(由 clinfo 返回)(我认为我的卡不支持 OpenCL 2.0)。

更一般地说,我想获得有关使用我的显卡模型和 OpenCL 1.2 执行最后最终求和的最简单方法的建议。

【问题讨论】:

  • 通常不需要在GPU中做最后一步。例如,如果您有 8M=2^23 个元素,并且每个工作组是 128=2^7。如果你运行你的内核两次,你最终会得到 512 个值,数量非常少。再一次迭代,结果只有 4 个值。 CPU可以轻松处理它。最好的方法是,将 GPU 用于好的、小尺寸和线性和不太适合的东西,所以最好用 CPU 来做(即使在 512 的情况下也可能)。
  • @DarkZeros 谢谢你的评论,问题是我想把这个减少到一个主循环中,在每次迭代中,我必须计算总和减少。为此,我需要在每次迭代时交换输入和输出缓冲区(将参数交换到 setKernelArg):这就是为什么我不想将 CPU 用于部分和的最终总和,因为我认为这种方法会破坏执行管道,即GPU处理的好处。
  • 您必须记住,GPU 没有无限量的工作项。很有可能,您可以并行处理数千个工作项。因此,对于 1M 元素,减少需要在 GPU 内部进行几次“迭代”。用不同的参数重新启动另一个迭代应该很便宜,而且开销最小。只要您不执行 clFinish() 来阻止管道。您可以阅读第 3 节,两级还原:developer.amd.com/resources/documentation-articles/…

标签: c arrays opencl reduction


【解决方案1】:

如果该浮点数的数量级小于exa 刻度,则:

代替

if (local_id == 0)
  partialSums[get_group_id(0)] = localSums[0];

你可以使用

if (local_id == 0)
{
    if(strategy==ATOMIC)
    {
        long integer_part=getIntegerPart(localSums[0]);
        atom_add (&totalSumIntegerPart[0] ,integer_part);
        long float_part=1000000*getFloatPart(localSums[0]);
         // 1000000 for saving meaningful 7 digits as integer
        atom_add (&totalSumFloatPart[0] ,float_part);
    }
}

这会溢出浮点部分,所以当你在另一个内核中将它除以 1000000 时,它可能有超过 1000000 的值,所以你得到它的整数部分并将其添加到实整数部分:

   float value=0;
   if(strategy==ATOMIC)
   {
       float float_part=getFloatPart_(totalSumFloatPart[0]);
       float integer_part=getIntegerPart_(totalSumFloatPart[0])
       + totalSumIntegerPart[0];
       value=integer_part+float_part;
   }

仅仅几个原子操作不应该在整个内核时间都有效。

其中一些get___part 可以使用 floor 和类似函数轻松编写。有些需要除以 1M。

【讨论】:

    【解决方案2】:

    抱歉之前的代码。 它也有问题。

    CLK_GLOBAL_MEM_FENCE 仅影响当前工作组。 我很困惑。 =[

    如果你想通过 GPU 减少总和,你应该在 clFinish(commandQueue) 之后通过 NDRangeKernel 函数将减少内核入队。

    请记住概念。

    __kernel void sumGPU ( __global const double *input,
                           __global double *partialSums,
                   __local double *localSums)
      {
     uint local_id = get_local_id(0);
     uint group_size = get_local_size(0);
    
      // Copy from global memory to local memory
      localSums[local_id] = input[get_global_id(0)];
    
      // Loop for computing localSums
      for (uint stride = group_size/2; stride>0; stride /=2)
         {
          // Waiting for each 2x2 addition into given workgroup
          barrier(CLK_LOCAL_MEM_FENCE);
    
          // Divide WorkGroup into 2 parts and add elements 2 by 2
          // between local_id and local_id + stride
          if (local_id < stride)
            localSums[local_id] += localSums[local_id + stride];
         }
    
      // Write result into partialSums[nWorkGroups]
      if (local_id == 0)
        partialSums[get_group_id(0)] = localSums[0];
    
        barrier(CLK_GLOBAL_MEM_FENCE);
    
          if(get_group_id(0)==0){
              if(local_id < get_num_groups(0)){  // 16384
                for(int n=0 ; n<get_num_groups(0) ; n+= group_size )
                   localSums[local_id] += partialSums[local_id+n];
                barrier(CLK_LOCAL_MEM_FENCE);
    
                for(int s=group_size/2;s>0;s/=2){
                   if(local_id < s)
                      localSums[local_id] += localSums[local_id+s];
                   barrier(CLK_LOCAL_MEM_FENCE);
                }
                if(local_id == 0)
                   partialSums[0] = localSums[0];
              }
           }
     }
    

    【讨论】:

    • 谢谢。在您的代码中,是否有一点错误?,您的意思是:“if(get_global_id(0)
    • 此代码不起作用。它假定全局项目 sincronization,这在 OpenCL 中是不存在的。当工作项 0,0 到达部分和部分时,应该计算 (0+s) 的工作组可能还没有开始
    • @DarkZeros,你的权利。它有全局内存同步问题。我会修改它。
    猜你喜欢
    • 2020-06-15
    • 1970-01-01
    • 2010-12-28
    • 1970-01-01
    • 1970-01-01
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    相关资源
    最近更新 更多