Урок 26: Умножение матриц — наивное GPU-ядро
Умножение матриц C = A * B вычисляет каждый элемент C[row][col] как сумму произведений вдоль общего измерения K: C[row][col] = сумма по k произведений A[row][k] * B[k][col]. Прямой (наивный) способ распараллелить это на GPU — назначить по одному thread на каждый элемент C. Thread вычисляет свои row
Представьте огромную таблицу умножения, которую нужно заполнить. В наивной версии каждый работник отвечает лишь за одну клетку, но чтобы заполнить её, он идёт на далёкий склад и приносит целую строку и целый столбец — а работник рядом идёт на тот же склад и приносит ровно ту же строку снова. Множество лишних походов.
- отображение thread на элемент
- Каждый thread отвечает за один элемент C. Он вычисляет row и col из двумерных индексов и порождает C[row][col].
- цикл по k (скалярное произведение)
- Цикл по k, который накапливает sum += A[row*K + k] * B[k*N + col]. Это скалярное произведение строки A на столбец B.
- плоский индекс row-major
- Матрица размера rows x cols хранится строка за строкой, так что элемент [r][c] находится по адресу r*cols + c.
- избыточные чтения из глобальной памяти
- В наивной версии каждый элемент A и B читается из глобальной памяти снова и снова разными threads, сжигая пропускную способность.