通常是设置块大小以获得最佳性能,并根据工作总量设置网格大小。大多数内核每 Mp 都有一个“最佳点”数量的经纱,它们工作得最好,你应该做一些基准测试/分析来看看它在哪里。您可能仍然需要内核中的溢出逻辑,因为问题大小很少是块大小的整数倍。
编辑:
举一个具体的例子来说明如何对一个简单的内核执行此操作(在这种情况下,一个自定义的 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;
}
}
为了启动这个内核,执行参数计算如下:
- 我们允许每个块最多 4 个 warp(因此 128 个线程)。通常,您会以最佳数量来解决此问题,但在这种情况下,内核通常会在非常小的向量上调用,因此具有可变块大小是有意义的。
- 然后我们根据总工作量计算块数,最多 112 个块,这相当于在 14 MP Fermi Telsa 上每个 MP 8 个块。如果工作量超过网格大小,内核将进行迭代。
生成的包含执行参数计算和内核启动的包装函数如下所示:
// 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() );
}
也许这提供了一些关于如何设计一个“通用”方案来根据输入数据大小设置执行参数的提示。