CUDA块和网格尺寸效率

时间:2011-04-27 20:54:18

标签: optimization cuda gpgpu

在cuda中处理动态大小的数据集的建议方法是什么?

是否“根据问题集设置块和网格大小”或者是否值得将块维度指定为2的因子并且有一些内核逻辑来处理溢出?

我可以看到这对块尺寸有多重要,但这对网格尺寸有多大影响?据我了解,实际的硬件限制在块级别停止(即分配给具有一定数量SP的SM的块,因此可以处理特定的warp大小)。

我已经仔细阅读过柯克的“编程大规模并行处理器”,但它并没有触及这个领域。

4 个答案:

答案 0 :(得分:14)

通常情况下,设置块大小以获得最佳性能,并根据工作总量设置网格大小。大多数内核在每个Mp上都有一个“最佳位置”的warp数,它们效果最好,你应该做一些基准测试/分析,看看它在哪里。您可能仍需要内核中的溢出逻辑,因为问题大小很少是块大小的倍数。

编辑: 给出一个具体的例子,说明如何对一个简单的内核进行这种操作(在这种情况下,自定义BLAS 1级dscal类型操作是作为打包对称带状矩阵的Cholesky分解的一部分完成的):

// Fused square root and dscal operation
__global__ 
void cdivkernel(const int n, double *a)
{
    __shared__ double oneondiagv;

    int imin = threadIdx.x + blockDim.x * blockIdx.x;
    int istride = blockDim.x * gridDim.x;

    if (threadIdx.x == 0) {
        oneondiagv = rsqrt( a[0] );
    }
    __syncthreads();

    for(int i=imin; i<n; i+=istride) {
        a[i] *= oneondiagv;
    }
}

要启动此内核,执行参数计算如下:

  1. 每个块允许最多4个warp(所以128个线程)。通常你会把它固定在一个最佳数字,但是在这种情况下,内核通常在非常小的向量上调用,因此具有可变块大小是有意义的。
  2. 然后,我们根据总工作量计算块计数,最多112个块,相当于14 MP Fermi Telsa上每个MP 8个块。如果工作量超过网格大小,内核将迭代。
  3. 包含执行参数计算和内核启动的结果包装函数如下所示:

    // Fused the diagonal element root and dscal operation into
    // a single "cdiv" operation
    void fusedDscal(const int n, double *a)
    {
        // The semibandwidth (column length) determines
        // how many warps are required per column of the 
        // matrix.
        const int warpSize = 32;
        const int maxGridSize = 112; // this is 8 blocks per MP for a Telsa C2050
    
        int warpCount = (n / warpSize) + (((n % warpSize) == 0) ? 0 : 1);
        int warpPerBlock = max(1, min(4, warpCount));
    
        // For the cdiv kernel, the block size is allowed to grow to
        // four warps per block, and the block count becomes the warp count over four
        // or the GPU "fill" whichever is smaller
        int threadCount = warpSize * warpPerBlock;
        int blockCount = min( maxGridSize, max(1, warpCount/warpPerBlock) );
        dim3 BlockDim = dim3(threadCount, 1, 1);
        dim3 GridDim  = dim3(blockCount, 1, 1);
    
        cdivkernel<<< GridDim,BlockDim >>>(n,a);
        errchk( cudaPeekAtLastError() );
    }
    

    或许这提供了一些关于如何设计“通用”方案以根据输入数据大小设置执行参数的提示。

答案 1 :(得分:3)

好的我想我们在这里处理两个问题。

1)分配块大小(即线程数)的好方法 这通常取决于您正在处理的数据类型。你在处理矢量吗?你在处理矩阵吗?建议的方法是保持线程数为32的倍数。因此,当处理向量时,启动256 x 1,512 x 1块可能没问题。在处理矩阵时类似于32 x 8,32 x 16。

2)分配网格大小的好方法(即块数) 这里有点棘手。只需启动10,000个区块,因为我们通常不是最好的办法。切换进出硬件的块是昂贵的。需要考虑的两件事是每个块使用的共享内存,以及可用SP的总数,并求解最佳数量。

你可以从thrust找到一个非常好的实现方法。可能需要一段时间才能弄清楚代码中发生了什么。

答案 2 :(得分:1)

我认为通常最好根据问题集设置块和网格大小,尤其是出于优化目的。拥有无效的额外线程并不是真正有意义,并且可能会恶化程序的性能。

答案 3 :(得分:1)

如果您有动态大小的数据集,那么您可能会遇到一些延迟问题,而某些线程和块会等待其他人完成。

site有一些很好的启发式方法。一些一般亮点:

每格选择块

  • 每个网格的块数应为> =多个处理器的数量。
  • 内核中__syncthreads()的使用越多,块越多(一个块可以运行而另一个块等待同步)

每个块选择线程

  • 以经线大小的倍数(即一般为32)的线程

  • 通常可以选择线程数,这样每个块的最大线程数(基于硬件)是线程数的倍数。例如。最大线程数为768,每块使用256个线程往往优于512,因为多个线程可以在块上同时运行。