【发布时间】:2015-01-17 17:54:48
【问题描述】:
我想计算 CUDA 中矩阵的所有列的总和和所有行的总和。一种方法是使用 BLAS 中的SGEMV 子例程,将矩阵乘以 1 的向量。
但是,这会导致对矩阵进行 两次 扫描,假设它比 L1 缓存大得多:一次用于行,另一次用于列。此外,我计划进一步修改其他运算符的代码,所以这就是我编写自己的内核的原因。
到目前为止,我的方法是将矩阵分解为大小为32 x 32 的子矩阵。每个线程块将这样的子矩阵加载到共享内存中,计算子矩阵的行和列的总和,并将它们原子地添加到适当的输出(下面的row 和col)。这样,矩阵数据只需从 VRAM 中读取一次。
为简单起见,代码假设矩阵为n x n、n % 32 == 0,线程块为32 x 32
__global__ void sum_cols_and_rows(size_t n, const float* matrix, float* col, float* row)
{
__shared__ float sh[32][32];
size_t x = blockDim.x * blockIdx.x + threadIdx.x;
size_t y = blockDim.y * blockIdx.y + threadIdx.y;
float sum = matrix[x + n * y];
sh[threadIdx.x][threadIdx.y] = sum;
for(unsigned w = 16; w >= 1; w /= 2)
sum += __shfl_down(sum, w);
const size_t laneID = threadIdx.x & 0x1f; // 32-1
if(laneID == 0)
atomicAdd(row + y, sum);
__syncthreads();
sum = sh[threadIdx.y][threadIdx.x]; // swapped indexes
for(unsigned w = 16; w >= 1; w /= 2)
sum += __shfl_down(sum, w);
if(laneID == 0)
atomicAdd(col + blockDim.x * blockIdx.x + threadIdx.y, sum);
}
// launch :
sum_cols_and_rows<<<dim3(n/32, n/32), dim3(32, 32), 32*32*sizeof(float)>>>(n, matrix, col, row);
然而,表现相当令人失望。我在 GTX 980 上看到大约 20% 的理论 224GB/s 内存带宽,即使在大型矩阵上,例如 16384x16384。
有没有办法让这种方法达到理论带宽限制?
【问题讨论】:
-
您可以尝试
sh[32][33];- 这可能有助于解决共享内存库冲突。除此之外,我不确定你是否受益于每个 NxN 块拥有 N^2 个线程,你可以尝试使用 N(也许使用更大的 N),根本不需要共享内存。 -
@zch sh[32][33] 给了我 50% 的加速,尽管我仍然处于理论极限的 30%。谢谢!我认为我需要共享内存来将数据从线程 (x,y) 发送到线程 (y, x) 在一个块中,并避免从 VRAM 重新读取该值。
-
并非如此。我的建议是每个块有 N 个线程,并且每个线程计算一个垂直总和。类似于:
for(i 0..N-1) { float v = matrix[i][threadIdx]; vertical += v; horizontalShuffle(v); if(threadIdx==0) AtomicAdd(v); } AtomicAdd(vertical). -
或者你可以保留共享内存并在没有任何洗牌的情况下执行
for(i 0..N-1) { float v = matrix[i][threadIdx]; vertical += v; sh[i][threadIdx] = v; } AtomicAdd(vertical); sync(); for(i 0..N-1) { horizontal += sh[threadIdx][i]; }; AtomicAdd(horizontal);。对于足够大的矩阵,它可能是最有效的。 -
@zch 您的“无共享内存”建议让我获得了 71% 的带宽,这与我从 CUDA 样本中看到的“一维缩减”接近,所以这可能和它得到的一样好. 谢谢!(顺便说一句:起初我以为你在谈论列主要(FORTRAN)布局,它表现不佳——我猜是非合并访问)