Karatsuba - 与CUDA的多项式乘法

时间:2018-04-25 20:17:47

标签: c++ parallel-processing cuda nvidia karatsuba

我正在使用CUDA进行迭代Karatsuba算法,我想问一下,为什么计算出的一行总是不同。

首先,我实现了这个函数,它总是正确计算结果:

__global__ void kernel_res_main(TYPE *A, TYPE *B, TYPE *D, TYPE *result, TYPE size, TYPE resultSize){
    int i = blockDim.x * blockIdx.x + threadIdx.x;

    if( i > 0 && i < resultSize - 1){

        TYPE start = (i >= size) ? (i % size ) + 1 : 0;


        TYPE end = (i + 1) / 2;


        for(TYPE inner = start; inner < end; inner++){
            result[i] += ( A[inner] + A[i - inner] ) * ( B[inner] + B[i - inner] );
            result[i] -= ( D[inner] + D[i-inner] );
        }
    }
}

现在我想使用2D网格并使用CUDA作为for循环,所以我将我的功能改为:

__global__ void kernel_res_nested(TYPE *A, TYPE *B, TYPE *D, TYPE *result, TYPE size, TYPE resultSize){

    int i = blockDim.x * blockIdx.x + threadIdx.x;
    int j = blockDim.y * blockIdx.y + threadIdx.y;

    TYPE rtmp = result[i];

    if( i > 0 && i < resultSize - 1){

        TYPE start = (i >= size) ? (i % size ) + 1 : 0;
        TYPE end = (i + 1) >> 1;

        if(j >= start && j <= end ){

           // WRONG 
           rtmp += ( A[j] + A[i - j] ) * ( B[j] + B[i - j] ) - ( D[j] + D[i - j] );
        }
    }

    result[i] = rtmp;
}

我这样称呼这个函数:

dim3 block( 32, 8 );
dim3 grid( (resultSize+1/32) , (resultSize+7/8) );
kernel_res_nested <<<grid, block>>> (devA, devB, devD, devResult, size, resultSize);

结果总是错误的,总是不同的。我无法理解为什么第二个实现错误并始终计算错误的结果。我看不出有任何与数据依赖相关的逻辑问题。有谁知道如何解决这个问题?

谢谢。

1 个答案:

答案 0 :(得分:0)

对于这样的问题,你应该提供一个MCVE。 (参见第1项here)例如,我不知道TYPE指示的是什么类型,并且我建议的解决方案的正确性也很重要。

在您的第一个内核中,整个网格中只有一个线程正在读取和写入位置result[i]。但是在你的第二个内核中,你现在有多个线程写入result[i]位置。他们彼此冲突。 CUDA没有指定线程运行的顺序,有些可能在其他线程之前,之后或同时运行。在这种情况下,某些线程可能会与其他线程同时读取result[i]。然后,当线程写出他们的结果时,它们将是不一致的。并且它可能因运行而异。你有一个竞争条件(执行顺序依赖,而不是数据依赖)。

对此进行排序的规范方法是采用reduction技术。

但为简单起见,我建议atomics可以帮助您解决问题。根据您显示的内容,这更容易实现,并有助于确认竞争条件。在那之后,如果你想尝试一种简化方法,那里有很多教程(一个在上面链接),关于它的cuda标签有很多问题。

您可以将内核修改为类似的内容,以解决竞争条件:

__global__ void kernel_res_nested(TYPE *A, TYPE *B, TYPE *D, TYPE *result, TYPE size, TYPE resultSize){

    int i = blockDim.x * blockIdx.x + threadIdx.x;
    int j = blockDim.y * blockIdx.y + threadIdx.y;

    if( i > 0 && i < resultSize - 1){

        TYPE start = (i >= size) ? (i % size ) + 1 : 0;
        TYPE end = (i + 1) >> 1;

        if(j >= start && j < end ){ // see note below

           atomicAdd(result+i, (( A[j] + A[i - j] ) * ( B[j] + B[i - j] ) - ( D[j] + D[i - j] )));
        }
    }

}

请注意,根据您的GPU类型以及您使用的TYPE的实际类型,这可能无法正常工作(可能无法编译)。但由于您以前使用TYPE作为循环变量,我假设它是一个整数类型,并且那些必要的atomicAdd应该可用。

其他一些评论:

  1. 这可能无法提供您期望的网格尺寸:

    dim3 grid( (resultSize+1/32) , (resultSize+7/8) );
    

    我认为通常的计算方法是:

    dim3 grid( (resultSize+31)/32, (resultSize+7)/8 );
    
  2. 我总是建议使用proper CUDA error checking并使用cuda-memcheck运行代码,以便在遇到CUDA代码时遇到问题,以确保没有运行时错误。

    < / LI>
  3. 它在我看来也像这样:

    if(j >= start && j <= end ){
    

    应该是这样的:

    if(j >= start && j < end ){
    

    匹配你的for循环范围。我也假设size小于resultSize(再次,MCVE会有所帮助)。