我正在尝试在CUDA中实现点积,并将结果与MATLAB返回的结果进行比较。我的CUDA代码(基于this tutorial)如下:
#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);
}
我用以下代码编译它:
/usr/local/cuda-5.0/bin/nvcc -m64 -I/usr/local/cuda-5.0/include -gencode arch=compute_20,code=sm_20 -o multi_dot_product.o -c multi_dot_product.cu
g++ -m64 -o multi_dot_product multi_dot_product.o -L/usr/local/cuda-5.0/lib64 -lcudart
有关我的NVIDIA显卡的信息,请访问http://pastebin.com/8yTzXUuK。我尝试使用以下简单代码在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
是一个大小为THREADS_PER_BLOCK
的数组,并在同一个块中的线程之间共享。它是否也在块之间共享,或者每个块在此变量的不同副本上运行?我应该将方法dot
视为每个块的指令吗?有人可以详细说明如何分割作业以及如何在此示例中共享变量
答案 0 :(得分:2)
在你的内核中注释掉这一行:
// *c = 0.00;
在内核调用之前(在dev_c
的cudaMalloc之后)将这些行添加到主机代码中:
num_t h_c = 0.0f;
cudaMemcpy(dev_c, &h_c, sizeof(num_t), cudaMemcpyHostToDevice);
我相信你会得到与matlab相匹配的结果,或多或少。
你的内核中的这一行没有受到任何同步的保护,这一事实让你感到困惑。每个块的每个线程,无论何时执行,都会在您编写它时将其归零{。{1}}。
顺便说一句,通过使用经典的并行约简方法,我们可以通过这种操作做得更好。基本(未优化)插图是here。如果将该方法与共享内存的使用和最后的单个atomicAdd(每个块一个atomicAdd)结合使用,您将获得显着改进的实现。虽然它不是点积,this example结合了这些想法。
编辑:回复评论中的以下问题:
内核函数是一组指令, the grid 中的所有线程(根据定义,与内核启动相关联的所有线程)都会执行。但是,将执行视为由threadblock管理是合理的,因为线程块中的线程在很大程度上一起执行。但是,即使在一个线程块中,执行也不是必须在所有线程上完美地锁定。通常,当我们考虑锁步执行时,我们会想到一个 warp ,它是一个32个线程在一个线程块中的一组。因此,由于块内的warp之间的执行可能会发生偏差,因此即使对于单个线程块也存在这种危险。但是,如果只有一个threadblock,我们可以使用适当的同步和控制机制(例如c
和__syncthreads()
等来消除代码中的危险。但是这些机制对于一般情况来说是无用的。控制跨多个线程块的执行。多个线程块可以按任何顺序执行。整个网格中唯一定义的同步机制是内核启动本身。因此,为了解决您的问题,我们必须在内核启动之前将(if threadIdx.x == 0)
清零。