__match_any_sync 在计算能力 6 上的替代方案是什么?

Joh*_*ica 4 cuda gpu-warp

在 cuda 示例中,例如这里__match_all_sync __match_any_sync 用来。

这是一个例子,其中一个经线被分成多个(一个或多个)组,每个组都跟踪自己的原子计数器。

// increment the value at ptr by 1 and return the old value
__device__ int atomicAggInc(int* ptr) {
    int pred;
    //const auto mask = __match_all_sync(__activemask(), ptr, &pred); //error, should be any_sync, not all_sync
    const auto mask = __match_any_sync(__activemask(), ptr, &pred);
    const auto leader = __ffs(mask) - 1;  // select a leader
    int res;
    const auto lane_id = ThreadId() % warpSize;
    if (lane_id == leader) {                 // leader does the update
        res = atomicAdd(ptr, __popc(mask));
    }
    res = __shfl_sync(mask, res, leader);    // get leader’s old value
    return res + __popc(mask & ((1 << lane_id) - 1)); //compute old value
}
Run Code Online (Sandbox Code Playgroud)

__match_any_sync这里经线程分裂成具有相同的组ptr值,使每个组可以原子更新自己的PTR没有其他线程的方式获得。

我知道 nvcc 编译器(自 cuda 9 起)会自动在引擎盖下进行这种优化,但这只是关于 __match_any_sync

有没有办法做到这种预计算能力 7?

Rob*_*lla 5

编辑:博客文章现在已修改为反映__match_any_sync()而不是__match_all_sync(),因此应忽略以下对此效果的任何评论。下面的答案经过编辑以反映这一点。

根据你的说法:

这只是关于机械的 __match_any_sync

我们将专注于对__match_any_sync自身的替换,而不是任何其他形式的atomicAggInc函数重写。因此,我们必须提供一个掩码,其值与__match_any_sync()cc7.0 或更高架构上的返回值相同。

我相信这将需要一个循环来广播ptr值,在最坏的情况下,warp 中的每个线程都需要进行一次迭代(因为每个线程都可以有一个唯一的ptr值)并测试哪些线程具有相同的值。我们可以通过多种方式“优化”此函数的循环,以便根据ptr每个线程中的实际值将行程计数从 32 减少到某个较小的值,但在我看来,这种优化会带来相当大的复杂性,使最坏情况的处理时间更长(这是早期退出优化的典型特征)。所以我将演示一个没有这种优化的相当简单的方法。

另一个考虑是在warp没有收敛的情况下怎么办?为此,我们可以使用__activemask()来识别这种情况。

这是一个工作示例:

$ cat t1646.cu
#include <iostream>
#include <stdio.h>

// increment the value at ptr by 1 and return the old value
__device__ int atomicAggInc(int* ptr) {
    int mask;
#if __CUDA_ARCH__ >= 700
    mask = __match_any_sync(__activemask(), (unsigned long long)ptr);
#else
    unsigned tmask = __activemask();
    for (int i = 0; i < warpSize; i++){
#ifdef USE_OPT
      if ((1U<<i) & tmask){
#endif
        unsigned long long tptr = __shfl_sync(tmask, (unsigned long long)ptr, i);
        unsigned my_mask = __ballot_sync(tmask, (tptr == (unsigned long long)ptr));
        if (i == (threadIdx.x & (warpSize-1))) mask = my_mask;}
#ifdef USE_OPT
      }
#endif
#endif
    int leader = __ffs(mask) - 1;  // select a leader
    int res;
    unsigned lane_id = threadIdx.x % warpSize;
    if (lane_id == leader) {                 // leader does the update
        res = atomicAdd(ptr, __popc(mask));
    }
    res = __shfl_sync(mask, res, leader);    // get leader’s old value
    return res + __popc(mask & ((1 << lane_id) - 1)); //compute old value
}



__global__ void k(int *d){

  int *ptr = d + threadIdx.x/4;
  if ((threadIdx.x >= 16) && (threadIdx.x < 32))
    atomicAggInc(ptr);
}

const int ds = 32;
int main(){

  int *d_d, *h_d;
  h_d = new int[ds];
  cudaMalloc(&d_d, ds*sizeof(d_d[0]));
  cudaMemset(d_d, 0, ds*sizeof(d_d[0]));
  k<<<1,ds>>>(d_d);
  cudaMemcpy(h_d, d_d, ds*sizeof(d_d[0]), cudaMemcpyDeviceToHost);
  for (int i = 0; i < ds; i++)
    std::cout << h_d[i] << " ";
  std::cout << std::endl;
}
$ nvcc -o t1646 t1646.cu -DUSE_OPT
$ cuda-memcheck ./t1646
========= CUDA-MEMCHECK
0 0 0 0 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
========= ERROR SUMMARY: 0 errors
$
Run Code Online (Sandbox Code Playgroud)

(CentOS 7,CUDA 10.1.243,设备 0 是 Tesla V100,设备 1 是 cc3.5 设备)。

我为经线发散(即tmask不是0xFFFFFFFF)的情况添加了可选优化。这可以通过定义来选择USE_OPT