CUDA点积

CUDA点积,cuda,dot-product,Cuda,Dot Product,我正在尝试为双精度阵列实现经典的点积内核,并对各个块的最终和进行原子计算。正如编程指南第116页所述,我使用atomicAdd实现双精度。可能是我做错了什么。每个块中线程的部分和计算正确,但事后原子操作似乎无法正常工作,因为每次我使用相同的数据运行内核,我收到不同的结果。如果有人能发现错误或提供替代解决方案,我将不胜感激! 这是我的内核: __global__ void cuda_dot_kernel(int *n,double *a, double *b, double *dot_res) {

我正在尝试为双精度阵列实现经典的点积内核,并对各个块的最终和进行原子计算。正如编程指南第116页所述,我使用atomicAdd实现双精度。可能是我做错了什么。每个块中线程的部分和计算正确,但事后原子操作似乎无法正常工作,因为每次我使用相同的数据运行内核,我收到不同的结果。如果有人能发现错误或提供替代解决方案,我将不胜感激! 这是我的内核:

__global__ void cuda_dot_kernel(int *n,double *a, double *b, double *dot_res)
{
    __shared__ double cache[threadsPerBlock]; //thread shared memory
    int global_tid=threadIdx.x + blockIdx.x * blockDim.x;
    int i=0,cacheIndex=0;
    double temp = 0;
    cacheIndex = threadIdx.x;
    while (global_tid < (*n)) {
        temp += a[global_tid] * b[global_tid];
        global_tid += blockDim.x * gridDim.x;
    }
    cache[cacheIndex] = temp;
    __syncthreads();
    for (i=blockDim.x/2; i>0; i>>=1) {
        if (threadIdx.x < i) {
            cache[threadIdx.x] += cache[threadIdx.x + i];
        }
        __syncthreads();
    }
    __syncthreads();
    if (cacheIndex==0) {
        *dot_res=cuda_atomicAdd(dot_res,cache[0]);
    }
}

使用特别的CUDA代码进行正确的缩减可能很棘手,因此这里有一个使用推力算法的替代解决方案,该算法包含在CUDA工具包中:

#include <thrust/inner_product.h>
#include <thrust/device_ptr.h>

double do_dot_product(int n, double *a, double *b)
{
  // wrap raw pointers to device memory with device_ptr
  thrust::device_ptr<double> d_a(a), d_b(b);

  // inner_product implements a mathematical dot product
  return thrust::inner_product(d_a, d_a + n, d_b, 0.0);
}
#包括
#包括
双dou点积(int n,double*a,double*b)
{
//使用设备\u ptr将原始指针包装到设备内存
推力:装置ptr d_a(a),d_b(b);
//内积实现了一个数学点积
返回推力:内积(d_a,d_a+n,d_b,0.0);
}

没有检查您的代码的深度,但这里有一些建议。
如果你只将GPU用于此类通用任务,我只建议使用推力,因为如果出现复杂问题,人们不知道如何在GPU上高效地并行编程

  • 启动一个新的并行归约内核来汇总点积。
    由于数据已经在设备上,因此启动新内核时不会出现性能下降

  • 在最新的GPU上,您的内核似乎无法扩展到可能的最大块数。如果是这样,并且内核能够计算数百万个值的点积,那么由于序列化的原子操作,性能将显著降低

  • 初学者错误:是否检查了输入数据和共享内存访问范围?或者您确定输入数据总是块大小的倍数吗?否则你会读垃圾。我大部分错误的结果都是由于这个错误

  • 优化你的平行减少。或

  • 未经测试,我只是把它写在记事本上:

    /*
     * @param inCount_s unsigned long long int Length of both input arrays
     * @param inValues1_g double* First value array
     * @param inValues2_g double* Second value array
     * @param outDots_g double* Output dots of each block, length equals the number of blocks
     */
    __global__ void dotProduct(const unsigned long long int inCount_s,
        const double* inValuesA_g,
        const double* inValuesB_g,
        double* outDots_g)
    {
        //get unique block index in a possible 3D Grid
        const unsigned long long int blockId = blockIdx.x //1D
                + blockIdx.y * gridDim.x //2D
                + gridDim.x * gridDim.y * blockIdx.z; //3D
    
    
        //block dimension uses only x-coordinate
        const unsigned long long int tId = blockId * blockDim.x + threadIdx.x;
    
        /*
         * shared value pair products array, where BLOCK_SIZE power of 2
         *
         * To improve performance increase its size by multiple of BLOCK_SIZE, so that each threads loads more then 1 element!
         * (outDots_g length decreases by same factor, and you need to range check and initialize memory)
         * -> see harris gpu optimisations / parallel reduction slides for more informations.
         */
        __shared__ double dots_s[BLOCK_SIZE];
    
    
        /*
         * initialize shared memory array and calculate dot product of two values, 
         * shared memory always needs to be initialized, its never 0 by default, else garbage is read later!
         */
        if(tId < inCount_s)
            dots_s[threadIdx.x] = inValuesA_g[tId] * inValuesB_g[tId];
        else
            dots_s[threadIdx.x] = 0;
        __syncthreads();
    
        //do parallel reduction on shared memory array to sum up values
        reductionAdd(dots_s, dots_s[0]) //see my thesis link
    
        //output value
        if(threadIdx.x == 0)
            outDots_g[0] = dots_s[0];
    
        //start new parallel reduction kernel to sum up outDots_g!
    }
    
    /*
    *@param inCount \u两个输入数组的无符号长整型长度
    *@param inValues1_g双*第一值数组
    *@param inValues2\u g双*秒值数组
    *@param outDots\u g double*每个块的输出点,长度等于块数
    */
    __全局\uuuuvoid点积(常量无符号长整型),
    常数双*无效,
    常数双*无效,
    双*输出点(g)
    {
    //在可能的三维网格中获取唯一的块索引
    const unsigned long long int blockId=blockIdx.x//1D
    +blockIdx.y*gridDim.x//2D
    +gridDim.x*gridDim.y*blockIdx.z;//3D
    //块标注仅使用x坐标
    const unsigned long long int tId=blockId*blockDim.x+threadIdx.x;
    /*
    *共享值对产品数组,其中块大小的幂为2
    *
    *为了提高性能,将其大小增加BLOCK_size的倍数,以便每个线程加载多于1个元素!
    *(outDots_g长度以相同的系数减小,您需要进行范围检查并初始化内存)
    *->有关更多信息,请参阅harris gpu优化/并行缩减幻灯片。
    */
    __共享双点[块大小];
    /*
    *初始化共享内存数组并计算两个值的点积,
    *共享内存总是需要初始化,默认情况下它永远不会为0,否则垃圾会在以后读取!
    */
    如果(tId

    编辑:删除不必要的点。

    您不正确地使用了
    cuda\u atomicAdd
    功能。内核的这一部分:

    if (cacheIndex==0) {
        *dot_res=cuda_atomicAdd(dot_res,cache[0]);
    }
    
    是罪魁祸首。在这里,您可以自动添加到
    dot\u res
    。然后使用返回的结果非原子地设置
    dot_res
    。此函数的返回结果是被原子更新的位置的前一个值,它仅用于“信息”或调用者的本地使用。您没有将它分配给原子更新的内容,这完全违背了最初使用原子内存访问的目的。改为这样做:

    if (cacheIndex==0) {
        double result=cuda_atomicAdd(dot_res,cache[0]);
    }
    

    感谢您的回复。由于全局变量*dot_res已初始化为0,因此我将使gridDim.x块具有一个局部变量“result”,其中包含与共享变量缓存[0]相同的值(result=cache[0]+*dot_res=cache[0])?如果我理解正确,这样就不会有最终的缩减。有没有办法在设备上完成缩减?我试着用cuda的互斥示例,但它似乎产生了死锁。我不确定我是否理解你的要求。如果你只是做了我展示的改变,我相信它应该像你想象的那样工作,并且减少应该完成。atomicCAS循环应该一直进行下去,直到每个调用线程的贡献都登记在全局总数中。因为你可能只跑了10到100个街区,对于
    dot_res
    应该没有太多争用,它应该可以正常工作。我正在询问变量结果。这个变量有局部作用域吗?只有cacheIndex=0的线程才能查看这个变量的独占副本并修改它?那么我如何全局地,跨所有块只生成一个包含所有块的部分和的结果变量?
    result
    不相关。你不需要它,让代码工作。如果需要,可以重写原子添加以不返回它
    if (cacheIndex==0) {
        double result=cuda_atomicAdd(dot_res,cache[0]);
    }