带有CUDA内核的大型矢量大小的Dot Product会返回错误的结果

时间:2017-03-11 16:51:54

标签: c++ cuda cublas

我正在尝试实现一个CUDA内核,即计算两个向量的点积。对于小矢量大小,代码正常工作,我得到了正确的结果,但对于较大的结果,它只是计算错误。 我正在实施三种不同的方法来计算点积:

  • 序列版本(直接在c ++中没有任何优化)
  • CUDA 内核
  • CUBLAS 版本

我在cpp-File中的主要内容如下:

float *h_x,*h_y;
float res1=0.0, res2=0.0, res3=0.0;

h_x=(float*)malloc(Main::N*sizeof(float)); random_ints_Vec(h_x);
h_y=(float*)malloc(Main::N*sizeof(float)); random_ints_Vec(h_y);

double serialTimer;
double cublasTimer;
double cudaTimer;

res1=serial_dotProd(h_x,h_y,&serialTimer);  
res2=cublas_dotProd(h_x,h_y,&cublasTimer);
res3=cuda_dotProd(h_x,h_y,&cudaTimer);      

free(h_x); free(h_y);

序列版本:

float Main::serial_dotProd(float* x, float* y, double* time){
std::clock_t start;
start = std::clock();

float res1=0.0;
for (int i=0;i<Main::N;++i) {
    res1+=x[i]*y[i];
}

*time= ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
return res1;}

CUDA 版本:

float Main::cuda_dotProd(float *h_x,float *h_y,double* time){
float *d_x,*d_y,*d_res, *h_res;
h_res=(float*)malloc(Main::BLOCKS_PER_GRID*sizeof(float));

size_t bfree, afree, total;
cudaMemGetInfo(&bfree,&total);

cudaMalloc((void**) &d_x,Main::N*sizeof(float));
cudaMalloc((void**) &d_y,Main::N*sizeof(float));
cudaMalloc((void**) &d_res,Main::BLOCKS_PER_GRID*sizeof(float));
cudaCheckErrors("cuda malloc fail");

cudaMemGetInfo(&afree,&total);
std::cout<<" > memory used for cuda-version:"<< (bfree -afree)/1048576<< "MB ("<<total/1048576 <<"MB)" <<"\n";

cudaMemcpy(d_x,h_x,Main::N*sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(d_y,h_y,Main::N*sizeof(float),cudaMemcpyHostToDevice);   

std::clock_t start;
start = std::clock();   
DotProdWrapper(d_x,d_y,d_res,(Main::N+Main::THREADS_PER_BLOCK-1)/Main::THREADS_PER_BLOCK,Main::THREADS_PER_BLOCK,Main::N);

*time= ( std::clock() - start ) / (double) CLOCKS_PER_SEC;

cudaMemcpy(h_res,d_res,Main::BLOCKS_PER_GRID*sizeof(float),cudaMemcpyDeviceToHost);

float c= 0;
for (int i=0;i<Main::BLOCKS_PER_GRID;++i){
  c+=h_res[i];
}
cudaFree(d_x);
cudaFree(d_y);
cudaFree(d_res);    

free(h_res);
return c;}

CUDA 内核:

__global__ void DotProd(float* x, float* y, float* scalar,unsigned long int N){
    extern __shared__ float cache[];

    int tid = threadIdx.x+ blockIdx.x*blockDim.x;
    int cacheIndex = threadIdx.x;

    float temp=0;
    while (tid<N){
        temp+=x[tid]*y[tid];
        tid +=blockDim.x*gridDim.x; 
    }
    cache[cacheIndex]=temp;
    __syncthreads();

    int i=blockDim.x/2;
    while(i!=0){
        if (cacheIndex<i)
            cache[cacheIndex]+=cache[cacheIndex+i];
        __syncthreads();
        i/=2;
    }
    if(cacheIndex==0)
        scalar[blockIdx.x]=cache[cacheIndex];
}

CUBLAS 版本:

float Main::cublas_dotProd(float *h_x,float *h_y, double* time){
    float *d_x,*d_y;
    float *res;
    float result=0.0;
    cublasHandle_t h;
    cublasCreate(&h);
    cublasSetPointerMode(h, CUBLAS_POINTER_MODE_DEVICE);

    size_t bfree, afree, total;
    cudaMemGetInfo(&bfree,&total);

    cudaMalloc((void**) &d_x,Main::N*sizeof(float));
    cudaMalloc((void**) &d_y,Main::N*sizeof(float));
    cudaMalloc( (void **)(&res), sizeof(float) );
    cudaCheckErrors("cublas malloc fail");

    cudaMemGetInfo(&afree,&total);

     cudaMemcpy(d_x, h_x, Main::N*sizeof(float), cudaMemcpyHostToDevice);
     cudaMemcpy(d_y, h_y, Main::N*sizeof(float), cudaMemcpyHostToDevice);

    cublasSetVector(Main::N,sizeof(float),h_x,1,d_x,1);
    cublasSetVector(Main::N,sizeof(float),h_y,1,d_y,1);


    std::clock_t start;
    start = std::clock();

    cublasSdot(h,Main::N,d_x,1,d_y,1,res);

    *time= ( std::clock() - start ) / (double) CLOCKS_PER_SEC;

    cudaMemcpy(&result, res, sizeof(float), cudaMemcpyDeviceToHost);

    cudaFree(d_x);
    cudaFree(d_y);
    cudaFree(res);
    return result;
}

列出了使用不同设置进行计算后得到的结果

  • N = 204800,THREADS_PER_BLOCK:512,BLOCKS_PER_GRID:400 serial_dotProd = 4.15369e + 06 ; cublas_dotProd = 4.15369e + 06 ; cuda_dotProd = 4.15369e + 06
  • N = 20480000,THREADS_PER_BLOCK:512,BLOCKS_PER_GRID:40000 serial_dotProd = 4.04149e + 08 ; cublas_dotProd = 4.14834e + 08 ; cuda_dotProd = 4.14833e + 08

我不知道为什么,但是在我的矢量一定大小之后我才得到错误的结果。向量确实适合SDRAM,每个块的共享内存也有足够的空间来分配内存。 非常感谢。

1 个答案:

答案 0 :(得分:1)

这个问题经常出现,Nvidia专门致力于an entire section of the CUDA Floating Point and IEEE 754指南。它也在CUDA C Best Practices Guide中简要提到过。

简短的解释是双重的:

  • 与相应的精确数学运算不同,由于涉及舍入误差,浮点算术运算不是关联的。这意味着将来自直接序列和的求和重新排序为适合于并行执行的树结构将稍微改变结果(随着总和的值的增加而更多)。巧合的是,在大多数情况下,树排列的结果也比顺序总和更接近精确的数学和。

  • CUDA编译器在使用融合乘法 - 加法(FMA,乘法 - 然后 - 加法 - 运算,其中省略中间舍入阶段)时往往更积极。同样,数学上正确的结果往往更接近使用FMA获得的结果。

所以可能的答案是,使用CUDA获得的结果可能比简单的串行CPU版本更接近确切的结果(这就是为什么我要求您使用更高的精度再次执行实验)。