【问题标题】:What is the proper way to use stride in cuda to do multiblock reduction?在 cuda 中使用 stride 进行多块减少的正确方法是什么?
【发布时间】:2020-03-31 13:10:58
【问题描述】:

大家好,我正在尝试使用网格步长方法和原子函数来进行多块缩减。
我知道执行此操作的通常方法是启动两个内核或按照this 说明中的指示使用lastblock 方法。(或this 教程)

但是,我认为这也可以通过使用带有原子代码的网格步幅来完成。
正如我测试的那样,它工作得很好..
直到 some 数字,它给出了错误的答案。 (这很奇怪)

我测试了一些“n”,发现 n = 1234565、1234566、1234567 的答案有误。
这是我做 n 和 1 的全部代码。所以答案应该是 n。
任何帮助或评论表示赞赏。

#include<iostream>

__global__ void stride_sum(const double* input,
                           const int size,
                           double* sumOut){
    extern __shared__ double sm[];

    unsigned int tid = threadIdx.x;
    unsigned int i = blockDim.x * blockIdx.x + tid;

    //doing grid loop using stride method.
    for(unsigned int s=i;
            s<size;
            s+=blockDim.x*gridDim.x){
        sm[tid] = input[i];
        __syncthreads();

        //doing parallel reduction.
        for(unsigned int ss = blockDim.x/2;ss>0;ss>>=1){
            if(tid<ss && tid+ss<size) sm[tid] += sm[tid+ss];
            __syncthreads();
        }

        //atomically add results to sumOut.
        if(tid==0) atomicAdd(sumOut, sm[0]);
    }
}

int main(){

    unsigned int n = 1234567;
    int blockSize = 4;
    int nBlocks = (n + blockSize - 1) / blockSize;
    int sharedMemory = sizeof(double)*blockSize;

    double *data, *sum;

    cudaMallocManaged(&data, sizeof(double)*n);
    cudaMallocManaged(&sum, sizeof(double));

    std::fill_n(data,n,1.);
    std::fill_n(sum,1,0.);

    stride_sum<<<nBlocks, blockSize, sharedMemory>>>(data,n,sum);

    cudaDeviceSynchronize();

    printf("res: 10.f \n",sum[0]);

    cudaFree(data);
    cudaFree(sum);

    return 0;
}

【问题讨论】:

    标签: cuda reduction stride


    【解决方案1】:

    您在实施过程中犯了很多错误。这将起作用:

    __global__ void stride_sum(const double* input,
                               const int size,
                               double* sumOut)
    {
        extern __shared__ volatile double sm[];
    
        unsigned int tid = threadIdx.x;
        unsigned int i = blockDim.x * blockIdx.x + tid;
    
        //doing grid loop using stride method.
        double val = 0.;
        for(unsigned int s=i; s<size; s+=blockDim.x*gridDim.x){
            val += input[i]; 
        }
    
        // Load partial sum to memory
        sm[tid] = val; 
        __syncthreads();
    
        //doing parallel reduction.
        for(unsigned int ss = blockDim.x/2;ss>0;ss>>=1){
            if(tid<ss && tid+ss<size) sm[tid] += sm[tid+ss];
            __syncthreads();
        }
    
       //atomically add results to sumOut.
       if(tid==0) atomicAdd(sumOut, sm[0]);
    }
    

    [从不编译运行,风险自负]

    简而言之——进行网格跨步求和,然后单个共享内存减少,然后单个原子更新。您的实现在一些地方有未定义的行为,尤其是有条件地执行的 __syncthreads 调用以及在某些线程退出求和循环时使用未初始化的共享内存。

    【讨论】:

    • 感谢您的帮助。您的代码运行良好!顺便说一句,您能否更具体地解决我的代码中的问题? 1.条件执行的__syncthreads是什么意思? 2.我认为 tid+ss
    • 所以我发现 1. 通过在 stride 循环中进行同步,一些 'size' 之外的线程可能会导致问题, 4. volatile 是为了确保代码,它不会造成太大的伤害在性能上。但是对于 2,3,我仍然需要一些帮助..
    • val 的 float/double 是一个错字——就像我在回答中所说的那样,我在浏览器中编写了该代码,可能存在错误,在这种情况下确实存在。至于3:如果warp上半部分的线程退出外循环,那么它们不会更新共享内存的内容并在共享内存中留下不正确的数据。然后经线下半部分的线程使用不正确的数据进行归约,事情就中断了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-21
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 1970-01-01
    • 2021-08-31
    相关资源
    最近更新 更多