2012-02-26 80 views
6

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

__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]); 
    } 
} 

这里是我的设备功能atomicAdd:

__device__ double cuda_atomicAdd(double *address, double val) 
{ 
    double assumed,old=*address; 
    do { 
     assumed=old; 
     old= __longlong_as_double(atomicCAS((unsigned long long int*)address, 
        __double_as_longlong(assumed), 
        __double_as_longlong(val+assumed))); 
    }while (assumed!=old); 

    return old; 
} 
+0

共享内存原子很慢。这不是实现点积的好方法。 Jared指出,你最好使用Thrust。如果你坚持编写你自己的代码,并且你真的想在单个内核中完成它,请参阅CUDA SDK代码示例中的threadFenceReduction示例。它应该更有效率(它不是一个点积,只是一个总和减少,但添加最初的元素乘法应该是微不足道的。) – harrism 2012-02-26 11:25:35

+0

@harrism:这个代码中共享内存原子的地方在哪里?这只是一个标准的共享内存减少与全局内存原子操作来完成块部分减少值的总和。 – talonmies 2012-02-26 11:52:47

+0

对不起,我把原子论点转移到了我的脑海中!无论如何,如果你使用threadfence,你不应该需要原子来在单个内核中实现减少。 – harrism 2012-02-26 22:17:51

回答

3

您正在使用不正确的cuda_atomicAdd功能。内核的这一部分:

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

是罪魁祸首。在这里,你自动添加到dot_res。然后非原子地设置dot_res与它返回的结果。该函数的返回结果是之前的值,该位置被原子更新,并且它仅提供给“信息”或本地使用调用者。你不会将它分配给你原子更新的东西,这完全违背了使用原子内存访问的目的。代之以这样做:

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

谢谢您的回复。由于全局变量* dot_res初始化为0,我将有gridDim.x块,其中包含与共享变量cache [0]右侧相同的值(结果=缓存[0] + * dot_res =缓存[0])?如果我理解正确,将不会有最终减少这种方式..是否有办法完成减少设备?我尝试使用互斥体的例子cuda的例子,但它似乎产生了一个僵局。 – 2012-02-26 09:59:29

+0

我不确定我明白你在问什么。如果你只是做了我所展示的改变,我相信它应该如你所想象的那样工作,并且减少应该完成。 atomicCAS循环应该消除,直到每个调用线程的贡献已在全局总数中注册为止。因为你可能只运行10到100个块之间的东西,所以'dot_res'不应该有太多的争用,它应该可以正常工作。 – talonmies 2012-02-26 10:12:12

+0

我在询问变量的结果,这个变量有局部范围的权限吗?只有cacheIndex = 0的线程才能查看这个变量的独占副本并修改它?那么我如何在全局范围内跨所有块产生1个结果包含所有块的部分和的变量? – 2012-02-26 10:19:49

6

获取使用ad hoc 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); 
} 
+0

感谢您的回复和您的工作,但我会尝试实现我自己的dot-product版本!保持良好的工作 – 2012-02-26 10:35:30

-1

未检查您的代码深度,但这里有一些建议。
如果您仅将GPU用于这样的通用任务,那么我只会建议使用Thrust,因为如果出现复杂的问题,人们不知道要在GPU上高效地编程并行。

  1. 启动一个新的并行压缩内核来总结点积。
    由于数据已经存在于设备上,因此启动新内核时性能不会下降。

  2. 你的内核看起来并没有在最新GPU上的可能块的最大数量上进行缩放。如果它和你的内核能够计算出数百万个数值的点积,那么由于序列化的原子操作,性能会显着降低。

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

  4. 优化您的并行减少。 My ThesisOptimisations Mark Harris

未经检验的,我只是把它写下来,在记事本:

/* 
* @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! 
} 

编辑:删除不必要的点。

+0

1.“内核应该运行足够的块来填充GPU中的每个SM “。谁说它不应该只有足够的块运行?我说内核本身应该可以跨越最大块数进行扩展! 2.关于这个简单的内核,不需要任何跨步。最简单的合并读取模式适用于此处:http://developer.download.nvidia.com/compute/cuda/2_0/docs/NVIDIA_CUDA_Programming_Guide_2.0.pdf图5-1 – djmj 2012-02-26 15:59:39

+0

2.“第5点也是错误的。”基本的c知识。不要在你的指针长度之外阅读。你只需要阅读那个内存地址。对于共享内存:http://stackoverflow.com/questions/6478098/is-there-a-way-of-setting-default-value-for-shared-memory-array – djmj 2012-02-26 16:01:46

+0

点#3仍然不适用。也许你不明白代码的作用,但是它在累积循环中隐含的全局内存范围检查。 – talonmies 2012-02-26 16:09:57