Я хотел бы рассчитать сумму всех столбцов и сумму всех строк матрицы в CUDA. Один из способов сделать это - использовать подпрограмму SGEMV
от BLAS, умножая матрицу на вектор 1s.Как эффективно вычислять сумму всех столбцов и строк матрицы в CUDA?
Однако это приводит к двум сканированию матрицы, если предположить, что она намного больше, чем кеш 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);
Однако производительность довольно неутешительная. Я вижу около 20% теоретической пропускной способности памяти 224 ГБ/с на GTX 980, даже на больших матрицах, , например. 16384x16384.
Есть ли способ сделать этот подход теоретическим пределом пропускной способности?
Вы можете попробовать 'ш [32] [33];' - это может помочь с разделяемыми банка памяти конфликтов. Кроме этого, я не уверен, что вам удастся получить N^2 потока на блок NxN, вы можете попробовать с N (возможно, с большим N), без необходимости использовать общую память. – zch
@zch sh [32] [33] дал мне 50% ускорение, хотя я все еще на 30% от теоретического предела. Благодаря! Мне кажется, мне нужна общая память для передачи данных из потока (x, y) в поток (y, x) в блоке и не перечитывать это значение из VRAM. – MaxB
Не совсем. Я предлагал иметь N потоков на блок и каждый поток, вычисляющий одну вертикальную сумму. Аналогично: 'for (i 0..N-1) {float v = matrix [i] [threadIdx]; вертикальный + = v; horizontalShuffle (v); if (threadIdx == 0) AtomicAdd (v); } AtomicAdd (вертикальный) '. – zch