在CUDA中使用原子运算的点积-得到错误的结果

在CUDA中使用原子运算的点积-得到错误的结果,cuda,gpu,gpgpu,Cuda,Gpu,Gpgpu,我试图在CUDA中实现点积,并将结果与MATLAB返回的结果进行比较。我的CUDA代码(基于)如下所示: #include <stdio.h> #define N (2048 * 8) #define THREADS_PER_BLOCK 512 #define num_t float // The kernel - DOT PRODUCT __global__ void dot(num_t *a, num_t *b, num_t *c) { __shared__ num_t

我试图在CUDA中实现点积,并将结果与MATLAB返回的结果进行比较。我的CUDA代码(基于)如下所示:

#include <stdio.h>

#define N (2048 * 8)
#define THREADS_PER_BLOCK 512
#define num_t float

// The kernel - DOT PRODUCT
__global__ void dot(num_t *a, num_t *b, num_t *c) 
{
  __shared__ num_t temp[THREADS_PER_BLOCK];
  int index = threadIdx.x + blockIdx.x * blockDim.x;
  temp[threadIdx.x] = a[index] * b[index];
  __syncthreads(); //Synchronize!
  *c = 0.00;
  // Does it need to be tid==0 that
  // undertakes this task?
  if (0 == threadIdx.x) {
    num_t sum = 0.00;
    int i;
    for (i=0; i<THREADS_PER_BLOCK; i++)
      sum += temp[i];
    atomicAdd(c, sum);        
    //WRONG: *c += sum; This read-write operation must be atomic!
  }
}


// Initialize the vectors:
void init_vector(num_t *x)
{
  int i;
  for (i=0 ; i<N ; i++){
    x[i] = 0.001 * i;
  }
}

// MAIN
int main(void)
{
  num_t *a, *b, *c;
  num_t *dev_a, *dev_b, *dev_c;
  size_t size = N * sizeof(num_t);

  cudaMalloc((void**)&dev_a, size);
  cudaMalloc((void**)&dev_b, size);
  cudaMalloc((void**)&dev_c, size);

  a = (num_t*)malloc(size);
  b = (num_t*)malloc(size);
  c = (num_t*)malloc(size);

  init_vector(a);
  init_vector(b);

  cudaMemcpy(dev_a, a, size, cudaMemcpyHostToDevice);
  cudaMemcpy(dev_b, b, size, cudaMemcpyHostToDevice);

  dot<<<N/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(dev_a, dev_b, dev_c);

  cudaMemcpy(c, dev_c, sizeof(num_t), cudaMemcpyDeviceToHost);

  printf("a = [\n");
  int i;
  for (i=0;i<10;i++){
    printf("%g\n",a[i]);
  }
  printf("...\n");
  for (i=N-10;i<N;i++){
    printf("%g\n",a[i]);
  }
  printf("]\n\n");
  printf("a*b = %g.\n", *c);


  free(a); free(b); free(c);

  cudaFree(dev_a);
  cudaFree(dev_b);
  cudaFree(dev_c);

}
有关我的NVIDIA卡的信息,请访问。我尝试使用以下简单代码在MATLAB中验证结果:

N = 2048 * 8;
a = zeros(N,1);
for i=1:N
    a(i) = 0.001*(i-1);
end

dot_product = a'*a;
但随着N的增加,我得到的结果会明显不同(例如,对于N=2048*32 CUDA reutrns 6.73066e+07,而MATLAB返回9.3823e+07。对于N=2048*64 CUDA,则得到3.28033e+08,而MATLAB给出7.5059e+08)。我倾向于认为这种差异源于在我的C代码中使用了
float
,但是如果我用
double
替换它,编译器会抱怨
atomicAdd
不支持双参数。我应该如何解决这个问题

更新:另外,对于
N
的高值(例如2048*64),我注意到CUDA返回的结果在每次运行时都会发生变化。如果
N
较低(例如2048*8),则不会发生这种情况


与此同时,我还有一个更基本的问题:变量
temp
是每个块的大小
线程的数组,并且在同一块中的线程之间共享。它是否也在块之间共享,或者每个块都在该变量的不同副本上运行?我应该把方法
dot
看作是每个块的指令吗?在本例中,有人能详细说明作业是如何分割的,变量是如何共享的吗

//   *c = 0.00;
并在内核调用之前(在
dev_c
的cudamaloc之后)将这些行添加到主机代码中:

我相信你会得到与matlab相匹配的结果,或多或少


内核中的这一行不受任何同步的保护,这一事实正在把你搞砸。每个块的每个线程,无论何时执行,都会像您所写的那样将
c
归零

顺便说一句,通过使用经典的并行归约方法,我们通常可以更好地执行此操作。下面是一个基本(未优化)说明。如果您将该方法与共享内存的使用以及最后的单个atomicAdd(每个块一个atomicAdd)相结合,您的实现将得到显著改进。虽然它不是一个点产品,但结合了这些想法

编辑:在评论中回答以下问题:


内核函数是网格中所有线程(定义为与内核启动相关联的所有线程)执行的一组指令。但是,将执行视为由threadblock管理是合理的,因为threadblock中的线程在很大程度上是一起执行的。然而,即使在threadblock中,执行也不一定在所有线程中都处于完美的锁定状态。通常当我们考虑lockstep执行时,我们会想到一个扭曲,它是一个线程块中32个线程组。因此,由于一个块内的翘曲之间的执行可能会发生倾斜,因此即使是单个threadblock,也存在这种危险。但是,如果只有一个threadblock,我们可以使用适当的同步和控制机制(如
\uu syncthreads()
(如果threadIdx.x==0)
等)来消除代码中的危险。但是这些机制对于控制多个Threadsblock之间的执行的一般情况是无用的。多个线程块可以按任意顺序执行。整个网格中唯一定义的同步机制是内核启动本身。因此,为了解决您的问题,我们必须在内核启动之前将
c
归零。

您能量化“显著不同的结果”是什么吗?什么是相对误差和绝对误差?@Talonmes是的,我更新了我的问题。每个块都在任何给定的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。您可以使用概述的方法执行双原子添加不受内核代码中任何同步的保护会把你搞砸。在调用内核之前,将
c
初始化为零,并从内核中删除该行。每个块的每个线程,无论何时执行,都会像您编写的那样将
c
归零。该死,对!因此,变量的初始化应该以受保护/同步的方式进行(就像我对dev_a和dev_b所做的那样)。还有一个问题要确保我的回答是正确的:内核函数应该被视为在一个块中执行的一组指令,对吗?所以,这就是为什么我们使用
temp[threadIdx.x]
。在上面的回答中回答了这个问题
//   *c = 0.00;
num_t h_c = 0.0f;
cudaMemcpy(dev_c, &h_c, sizeof(num_t), cudaMemcpyHostToDevice);