启动GPU内核在3D数据集上进行计算的最佳方法是什么?

时间:2012-06-08 00:21:32

标签: c multidimensional-array cuda gpu

我正在使用CUDA对潜在的大型3D数据集进行计算。我认为最好先看一个简短的代码段:

void launch_kernel(/*arguments . . . */){
    int bx = xend-xstart, by = yend-ystart, bz = zend-zstart;

    dim3 blocks(/*dimensions*/);
    dim3 threads(/*dimensions*/);
    kernel<<blocks, threads>>();
}

我有一组3D单元格,我需要启动一个内核来计算每个单元格。问题是输入大小可能超过GPU的能力,特别是线程。所以这样的代码:

void launch_kernel(/*arguments . . . */){
       int bx = xend-xstart, by = yend-ystart, bz = zend-zstart;

       dim3 blocks(bx,by,1);
       dim3 threads(bz);
       kernel<<blocks, threads>>();
   }

......效果不佳。因为尺寸是1000x1000x1000怎么办? - 我不能每块启动1000个线程。或者甚至更好,如果尺寸是5x5x1000怎么办? - 现在我几乎没有启动任何块,但内核需要以5x5x512 b / c的硬件启动,每个线程将进行2次计算。我也不能简单地混淆我的所有维度,将一些z放在块中,一些放在线程b / c中我需要能够识别内核中的维度。目前:

__global__ void kernel(/*arguments*/){
    int x = xstart + blockIdx.x;
    int y = ystart + blockIdx.y;
    int z = zstart + threadIdx.x;
    if(x < xend && y < yend && z < zend){
        //calculate
    }
}

我需要一种可靠有效的方法来找出这些变量:

块x维度,块y维度,线程x(和y?和z?),x,y,z,一旦我在内核中通过blockIdx和threadIdx,并且,如果输入超过硬件,我在内核计算中的for循环中为每个维度采取的“步骤”量。

如果您有任何疑问,请询问。这是一个棘手的问题,它一直困扰着我(特别是因为我发布的块/线程数量是性能的一个主要组成部分)。此代码需要在针对不同数据集的决策中实现自动化,我不确定如何有效地执行此操作。提前谢谢。

2 个答案:

答案 0 :(得分:3)

我认为你在这里过分复杂化了。基本问题似乎是您需要在1000 x 1000 x 1000计算域上运行内核。因此,您需要1000000000个线程,这完全在所有CUDA兼容硬件的功能范围内。所以只需使用一个标准的2D CUDA执行网格,至少需要进行计算所需的线程数(如果你不理解如何做这个留下评论,我会把它添加到答案中)然后在你的内核调用中一个像这样的小设置功能:

__device__ dim3 thread3d(const int dimx, const int dimxy)
{
    // The dimensions of the logical computational domain are (dimx,dimy,dimz)
    // and dimxy = dimx * dimy
    int tidx = threadIdx.x + blockIdx.x * blockDim.x;
    int tidy = threadIdx.y + blockIdx.y * blockDim.y;
    int tidxy = tidx + gridDim.x * tidy;

    dim3 id3d;
    id3d.z = tidxy / dimxy;
    id3d.y = tidxy / (id3d.z * dimxy);
    id3d.x = tidxy - (id3d.z * dimxy - id3d.y * dimx);

    return id3d;
}

[免责声明:用浏览器编写,从未编译,从不运行,从未测试过。使用风险自负]。

此函数将从CUDA 2D执行网格返回3D域(dimx,dimy,dimz)中的“逻辑”线程坐标。在内核的开头调用它,如下所示:

__global__ void kernel(arglist, const int dimx, const int dimxy)
{
    dim3 tid = thread3d(dimx, dimxy);

    // tid.{xyx} now contain unique 3D coordinates on the (dimx,dimy,dimz) domain
    .....
}

请注意,在设置网格时存在大量的整数计算开销,因此您可能需要考虑为什么您确实需要3D网格。你会惊讶于它实际上没有必要的次数,并且可以避免大部分设置开销。

答案 1 :(得分:1)

我首先使用cudaGetDeviceProperties()来查找GPU的计算能力,这样您就可以准确知道GPU允许每个块的线程数(如果您的程序需要通用化,以便它可以在任何CUDA上运行有能力的设备)。

然后,使用该数字,我会制作一个大的嵌套if语句来测试输入的维度。如果所有维度都足够小,则可以有一个(bx,by,bz)线程块(不太可能)。如果这不起作用,那么根据它找到可以放入一个块和分区的最大维度(或两个维度)。如果这不起作用,那么你必须对最小的维划分,使得它的一些块适合一个块 - 例如(MAX_NUMBER_THREADS_PER_BLOCK,1,1)线程和(bx/MAX_NUMBER)THREADS_PER_BLOCK,by,bz)块假设bx<by<bzbx>MAX_NUMBER_THREADS_PER_BLOCK

每个案例你都需要不同的内核,这有点痛苦,但最终它是一个可行的工作。