【问题标题】:CUDA generic dot productCUDA 通用点积
【发布时间】:2014-01-03 18:05:03
【问题描述】:

我是 C 和 CUDA 的新手,我正在编写点积函数,但是它没有给我正确的结果。有好心人帮我看看吗?

我还有两个问题,

  1. 为什么 dot() 不能正常工作,以及
  2. 在第 57 行,为什么是 product[threadIdx.x] 而不是 product[index]?我可以不写吗

    产品[索引] = a[索引] * b[索引]; ... 如果(索引==0){...} 并以这种方式将每个元素与第零个线程相加?

非常感谢。

设备查询:

  Device 0: "GeForce GTX 570"
  CUDA Driver Version / Runtime Version          6.0 / 5.5
  CUDA Capability Major/Minor version number:    2.0

生成文件:nvcc -arch=sm_20 cuda_test.cu -o cuda_test

在 cuda_test.cu 中:

#include <stdio.h> // printf, scanf, NULL etc.
#include <stdlib.h> // malloc, free, rand etc.

#define N (3) //Number of threads we are using (also, length of array declared in main)

#define THREADS_PER_BLOCK (1) //Threads per block we are using

#define N_BLOCKS (N/THREADS_PER_BLOCK)

/* Function to generate a random integer between 1-10 */
void random_ints (int *a, int n)
{
    int i;
    srand(time(NULL)); //Seed rand() with current time
    for(i=0; i<n; i++)
    { 
        a[i] = rand()%10 + 1; 
    }
    return;
}

/* Kernel that adds two integers a & b, stores result in c */
__global__ void add(int *a, int *b, int *c) {
//global indicates function that runs on 
//device (GPU) and is called from host (CPU) code

    int index = threadIdx.x + blockIdx.x * blockDim.x;

    //threadIdx.x : thread index
    //blockIdx.x  : block index
    //blockDim.x  : threads per block
    //hence index is a thread counter across all blocks
    c[index] = a[index] + b[index];

//note that pointers are used for variables
//add() runs on device, so they must point to device memory
//need to allocate memory on GPU
}

/* Kernel for dot product */
__global__ void dot(int *a, int *b, int *c)
{
    __shared__ int product[THREADS_PER_BLOCK]; //All threads in a block must be able 
                                               //to access this array

    int index = threadIdx.x + blockIdx.x * blockDim.x; //index

    product[threadIdx.x] = a[index] * b[index]; //result of elementwise
                                                //multiplication goes into product

    //Make sure every thread has finished
    __syncthreads();

    //Sum the elements serially to obtain dot product
    if( 0 == threadIdx.x ) //Pick one thread to sum, otherwise all will execute
    {
        int sum = 0;
        for(int j=0; j < THREADS_PER_BLOCK; j++) sum += product[j];
        //Done!
        atomicAdd(c,sum);
    }
}

int main(void)
{

    int *a, *b, *c, *dotProduct; //host copies of a,b,c etc
    int *d_a, *d_b, *d_c, *d_dotProduct; //device copies of a,b,c etc

    int size = N * sizeof(int); //size of memory that needs to be allocated

    int i=0; //iterator

    //Allocate space for device copies of a,b,c
    cudaMalloc((void **)&d_a, size);
    cudaMalloc((void **)&d_b, size);
    cudaMalloc((void **)&d_c, size);

    //Setup input values
    a = (int *)malloc(size); random_ints(a,N);
    b = (int *)malloc(size); random_ints(b,N);
    c = (int *)malloc(size);

    //Copy inputs to device
    cudaMemcpy(d_a, a, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, b, size, cudaMemcpyHostToDevice);

    //Launch add() kernel on GPU
    add<<<N_BLOCKS,THREADS_PER_BLOCK>>>(d_a, d_b, d_c);
    // triple angle brackets mark call from host to device
    // this is also known as a kernel launch
    // N/THREADS_PER_BLOCK = NO. OF BLOCKS

    //Copy result back to host
    cudaMemcpy(c, d_c, size, cudaMemcpyDeviceToHost);

    //Output results
    printf("a = {");
    for (i=0; i<N; i++) printf(" %d",a[i]);
    printf(" }\n");

    printf("b = {");
    for (i=0; i<N; i++) printf(" %d",b[i]);
    printf(" }\n");

    printf("c = {");
    for (i=0; i<N; i++) printf(" %d",c[i]);
    printf(" }\n");

    //Calculate dot product of a & b
    dotProduct = (int *)malloc(sizeof(int)); //Allocate host memory to dotProduct
    *dotProduct = 0; //initialise to zero
    cudaMalloc((void **)&d_dotProduct, sizeof(int)); //Allocate device memory to d_dotProduct
    dot<<<N_BLOCKS,THREADS_PER_BLOCK>>>(d_a, d_b, d_dotProduct); //Perform calculation
    cudaMemcpy(dotProduct, d_dotProduct, sizeof(int), cudaMemcpyDeviceToHost); //Copy result into dotProduct
    printf("\ndot(a,b) = %d\n", *dotProduct); //Output result

    //Cleanup
    free(a); free(b); free(c); free(dotProduct);
    cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); cudaFree(d_dotProduct);

    return 0;
} //End of main

【问题讨论】:

  • 请发布没有嵌入行号的代码,如果他们愿意,其他人可以编译和运行。另外请解释一下“无法正常工作”的确切含义。
  • 嗨 talonmies,对不起!我已经清理了它,现在应该可以编译了。很抱歉之前浪费了您的时间!

标签: cuda dot-product


【解决方案1】:

正如 talonmies 所说,请制作它以便其他人可以运行您的代码。嵌入行号没有帮助。

没有其他信息的最佳猜测是您尚未将d_dotProduct 初始化为零。您可以使用 cudaMemset() 执行此操作 - 如果您想要不同的初始值,那么您可以 cudaMemcpy() 来自主机的初始值或启动单独的内核进行初始化,但在这种情况下 cudaMemset() (相当于 memset()在主机上)就足够了。

也可能是N_BLOCKS*THREADS_PER_BLOCK 不等于size

至于你的第二个问题,product 是一个大小为THREADS_PER_BLOCK 的每块数组,如果你用product[index] 访问它,你就会越界。

【讨论】:

  • 嗨,汤姆,感谢您回答 Q2!我已经清理了代码并将其全部包含在内。很抱歉之前浪费了您的时间!
【解决方案2】:

问题解决了!需要在对 'product' 数组的各个元素求和之前设置 "*c = 0"。

/* Kernel for dot product */
__global__ void dot(int *a, int *b, int *c)
{
    __shared__ int product[THREADS_PER_BLOCK]; //All threads in a block must be able 
                                               //to access this array

    int index = threadIdx.x + blockIdx.x * blockDim.x; //index

    product[threadIdx.x] = a[index] * b[index]; //result of elementwise
                                                //multiplication goes into product

    if(index==0) *c = 0; //Ask one thread to set c to zero.

    //Make sure every thread has finished
    __syncthreads();    

    //Sum the elements serially to obtain dot product
    if( 0 == threadIdx.x ) //Every block to do c += sum
    {
        int sum = 0;
        for(int j=0; j < THREADS_PER_BLOCK; j++) sum += product[j];
        //Done!
        atomicAdd(c,sum);
    }
}

【讨论】:

  • 这是错误的,它只有在你启动单个线程块时才会起作用。如果您启动多个块,则 每个 块的线程零会将最终结果重置为零。仅在块 0 中执行此操作也是不正确的,因为编程模型允许块以任何顺序执行。正确的解决方案是在启动内核之前将 d_dotProduct 设置为零。
  • 现在呢?我添加了 if(index==0) *c=0 行。当主机代码仅存在于 GPU 内存上时,如何将 d_dotProduct 设置为零?您肯定必须从内核中执行此操作吗?附言非常感谢你的帮助,汤姆。
  • 就像我上面说的,你不能只在块 0 中执行这个设置,因为块可以按任何顺序执行,例如块 1 可以在块 0 将结果(覆盖块 1 的结果)重置为零之前写入其结果。执行此操作的正确方法是将初始值设置为零在启动内核之前 - 最明显的方法是使用cudaMemset(),但您也可以使用cudaMemcpy() 复制一个来自主机的初始值(在这种情况下为零)。我将在我的答案中添加 how 部分。
猜你喜欢
  • 2016-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-01
  • 2018-11-08
相关资源
最近更新 更多