LeetGPU | 02_Matrix_Multiplication

1810 字
9 分钟
LeetGPU | 02_Matrix_Multiplication

关注 LeetGPU repository,了解更多内容。

Problem Description#

Difficulty: Easy

LeetGPU Challenge

Write a program that multiplies two matrices of 32-bit floating point numbers on a GPU. Given matrix AA of dimensions M×NM \times N and matrix BB of dimensions N×KN \times K, compute the product matrix C=A×BC = A \times B, which will have dimensions M×KM \times K. All matrices are stored in row-major format.

Implementation Requirements#

  • Use only native features (external libraries are not permitted).
  • The solve function signature must remain unchanged.
  • The final result must be stored in matrix C.

Example 1#

Input:

Matrix AA (2×22 \times 2):

[1.02.03.04.0]\begin{bmatrix} 1.0 & 2.0 \\ 3.0 & 4.0 \end{bmatrix}

Matrix BB (2×22 \times 2):

[5.06.07.08.0]\begin{bmatrix} 5.0 & 6.0 \\ 7.0 & 8.0 \end{bmatrix}

Output:

Matrix CC (2×22 \times 2):

[19.022.043.050.0]\begin{bmatrix} 19.0 & 22.0 \\ 43.0 & 50.0 \end{bmatrix}

Example 2#

Input:

Matrix AA (1×31 \times 3):

[1.02.03.0]\begin{bmatrix} 1.0 & 2.0 & 3.0 \end{bmatrix}

Matrix BB (3×13 \times 1):

[4.05.06.0]\begin{bmatrix} 4.0 \\ 5.0 \\ 6.0 \end{bmatrix}

Output:

Matrix CC (1×11 \times 1):

[32.0]\begin{bmatrix} 32.0 \end{bmatrix}

Constraints#

  • 1 <= M, N, K <= 8192.
  • Performance is measured with M = 8192, N = 6144, K = 4096.

CUDA#

Approach#

CUDA 实现采用 16 x 16 的 shared-memory tiling。每个 block 包含 16 x 16 个 thread,负责计算输出矩阵 C 中一个 16 x 16 的 tile;每个 thread 最终负责一个输出元素。

矩阵乘法需要沿 N 维度完成内积。kernel 将这一维按 16 个元素分成多个 reduction tile。每轮中,block 内的 thread 协作完成两次加载:从 A 读取当前输出行对应的数据写入 A_shared,同时从 B 读取当前输出列对应的数据写入 B_shared。

数据进入 shared memory 后,第一次 __syncthreads() 确保整个 tile 已加载完成。随后,每个 thread 从 A_shared 的一行和 B_shared 的一列取数,执行 16 次乘加,并把结果累积到寄存器变量 S。第二次 __syncthreads() 确保所有 thread 都已使用完当前 tile,之后 shared memory 才能被下一轮数据覆盖。

这样,从 global memory 读取的每个输入值都可以被同一 block 中的多个 thread 复用,从而减少重复的 global-memory 访问。对于不能被 16 整除的矩阵尺寸,加载阶段把越界位置填充为 0.0f,最终写回时再检查 row < M && col < K,因此不会越界,也不会影响正确结果。

Solution#

#include <cuda_runtime.h>
#define BLOCK_SIZE 16
__global__ void matrix_multiplication_kernel(const float* A, const float* B, float* C, int M, int N,
int K) {
__shared__ float A_shared[BLOCK_SIZE][BLOCK_SIZE];
__shared__ float B_shared[BLOCK_SIZE][BLOCK_SIZE];
int row = threadIdx.y + blockIdx.y * blockDim.y;
int col = threadIdx.x + blockIdx.x * blockDim.x;
int tx = threadIdx.x;
int ty = threadIdx.y;
float S = 0.0f;
for (int i = 0 ; i < (N + BLOCK_SIZE - 1) / BLOCK_SIZE ; i++){
int col2 = tx + i * BLOCK_SIZE;
int row2 = ty + i * BLOCK_SIZE;
A_shared[ty][tx] = (row < M && col2 < N) ? A[row * N + col2] : 0.0f;
B_shared[ty][tx] = (row2 < N && col < K) ? B[row2 * K + col] : 0.0f;
__syncthreads();
for (int j = 0 ; j < BLOCK_SIZE ; j++){
S += A_shared[ty][j] * B_shared[j][tx];
}
__syncthreads();
}
if(row < M && col < K){
C[row * K + col] = S ;
}
}
extern "C" void solve(const float* A, const float* B, float* C, int M, int N, int K) {
dim3 threadsPerBlock(16, 16);
dim3 blocksPerGrid((K + threadsPerBlock.x - 1) / threadsPerBlock.x,
(M + threadsPerBlock.y - 1) / threadsPerBlock.y);
matrix_multiplication_kernel<<<blocksPerGrid, threadsPerBlock>>>(A, B, C, M, N, K);
}

Test Code#

#include <cuda_runtime.h>
#include <array>
#include <cmath>
#include <iostream>
extern "C" void solve(const float* a, const float* b, float* c,
int m, int n, int k);
int main() {
constexpr int m = 2;
constexpr int n = 2;
constexpr int k = 2;
const std::array<float, 4> matrix_a = {1.0f, 2.0f, 3.0f, 4.0f};
const std::array<float, 4> matrix_b = {5.0f, 6.0f, 7.0f, 8.0f};
const std::array<float, 4> expected = {19.0f, 22.0f, 43.0f, 50.0f};
std::array<float, 4> actual = {};
constexpr size_t bytes = 4 * sizeof(float);
float* device_a = nullptr;
float* device_b = nullptr;
float* device_c = nullptr;
auto check_cuda = [](cudaError_t status, const char* operation) {
if (status == cudaSuccess) {
return true;
}
std::cerr << "Test failed. CUDA error in " << operation << ": "
<< cudaGetErrorString(status) << '\n';
return false;
};
bool success = check_cuda(cudaMalloc(&device_a, bytes), "cudaMalloc(device_a)") &&
check_cuda(cudaMalloc(&device_b, bytes), "cudaMalloc(device_b)") &&
check_cuda(cudaMalloc(&device_c, bytes), "cudaMalloc(device_c)") &&
check_cuda(cudaMemcpy(device_a, matrix_a.data(), bytes,
cudaMemcpyHostToDevice), "copy matrix_a") &&
check_cuda(cudaMemcpy(device_b, matrix_b.data(), bytes,
cudaMemcpyHostToDevice), "copy matrix_b");
if (success) {
solve(device_a, device_b, device_c, m, n, k);
success = check_cuda(cudaGetLastError(), "solve") &&
check_cuda(cudaDeviceSynchronize(), "synchronize") &&
check_cuda(cudaMemcpy(actual.data(), device_c, bytes,
cudaMemcpyDeviceToHost), "copy result");
}
cudaFree(device_a);
cudaFree(device_b);
cudaFree(device_c);
if (!success) {
return 1;
}
for (size_t index = 0; index < actual.size(); ++index) {
if (std::fabs(actual[index] - expected[index]) > 1e-5f) {
std::cerr << "Test failed at index " << index
<< ". Expected: " << expected[index]
<< ", Actual: " << actual[index] << '\n';
return 1;
}
}
std::cout << "Test passed.\n";
return 0;
}

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSM=8192, N=6144, K=409610250.884 ms224.301 ms302.463 ms1643.454 GFLOPS
NVIDIA GeForce RTX 4090PASSM=8192, N=6144, K=40961059.709 ms59.302 ms60.092 ms6905.488 GFLOPS

Triton#

Approach#

Triton 版本采用直接的二维分块方式。一个 program instance 负责 C 中一个 32 x 32 的输出 tile,二维 grid 的两个 program ID 分别确定输出 tile 的行位置和列位置。

在 reduction 维度上,kernel 每次处理 32 个元素。tl.arange 生成当前 A 子块和 B 子块所需的索引,tl.load 负责加载数据,越界位置通过 mask 按零处理。随后,tl.dot 完成两个 32 x 32 子块的乘法并累加结果。所有 reduction tile 处理完毕后,tl.store 使用输出 mask 将有效结果写入 C。

与 CUDA 版本相比,这里不需要显式编写 thread index、shared memory 和同步操作;Triton 直接用 tile 级张量操作描述分块矩阵乘法,再由编译器完成底层线程映射。

Solution#

import torch
import triton
import triton.language as tl
@triton.jit
def matrix_multiplication_kernel(
a, b, c,
M, N, K,
stride_am, stride_an,
stride_bn, stride_bk,
stride_cm, stride_ck,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
):
m = tl.program_id(0)
k = tl.program_id(1)
MAX_N = tl.cdiv(N, BLOCK_SIZE_N)
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_K), dtype=tl.float32)
m_start = m * BLOCK_SIZE_M
k_start = k * BLOCK_SIZE_K
a_m_offset = m_start + tl.arange(0, BLOCK_SIZE_M)
b_k_offset = k_start + tl.arange(0, BLOCK_SIZE_K)
for n in tl.range(0, MAX_N):
n_start = n * BLOCK_SIZE_N
a_n_offset = n_start + tl.arange(0, BLOCK_SIZE_N)
b_n_offset = n_start + tl.arange(0, BLOCK_SIZE_N)
# A block: (BM, BN)
a_mn_mask = (a_m_offset[:, None] < M) & (a_n_offset[None, :] < N)
a_mn_ptr = a + a_m_offset[:, None] * stride_am + a_n_offset[None, :] * stride_an
a_mn = tl.load(a_mn_ptr, mask=a_mn_mask, other=0.0)
# B block: (BN, BK)
b_nk_mask = (b_n_offset[:, None] < N) & (b_k_offset[None, :] < K)
b_nk_ptr = b + b_n_offset[:, None] * stride_bn + b_k_offset[None, :] * stride_bk
b_nk = tl.load(b_nk_ptr, mask=b_nk_mask, other=0.0)
acc = tl.dot(a_mn, b_nk, acc, allow_tf32=False)
c_mk_mask = (a_m_offset[:, None] < M) & (b_k_offset[None, :] < K)
c_mk_ptr = c + a_m_offset[:, None] * stride_cm + b_k_offset[None, :] * stride_ck
tl.store(c_mk_ptr, acc, mask=c_mk_mask)
# a, b, c are tensors on the GPU
def solve(a: torch.Tensor, b: torch.Tensor, c: torch.Tensor, M: int, N: int, K: int):
stride_am, stride_an = N, 1
stride_bn, stride_bk = K, 1
stride_cm, stride_ck = K, 1
BLOCK_SIZE_M = 32
BLOCK_SIZE_N = 32
BLOCK_SIZE_K = 32
grid = (triton.cdiv(M,BLOCK_SIZE_M), triton.cdiv(K,BLOCK_SIZE_K))
matrix_multiplication_kernel[grid](
a, b, c, M, N, K, stride_am, stride_an, stride_bn, stride_bk, stride_cm, stride_ck,
BLOCK_SIZE_M = BLOCK_SIZE_M,
BLOCK_SIZE_N = BLOCK_SIZE_N,
BLOCK_SIZE_K = BLOCK_SIZE_K,
)

Test Code#

import importlib.util
import sys
from pathlib import Path
import torch
def load_implementation():
source_path = Path(__file__).resolve().parents[2] / "src" / "triton" / "02_Matrix_Multiplication.py"
spec = importlib.util.spec_from_file_location("matrix_multiplication_triton_minimal", source_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load implementation: {source_path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
matrix_a = torch.tensor([[1.0, 2.0], [3.0, 4.0]], device="cuda")
matrix_b = torch.tensor([[5.0, 6.0], [7.0, 8.0]], device="cuda")
expected = torch.tensor([[19.0, 22.0], [43.0, 50.0]], device="cuda")
actual = torch.empty_like(expected)
load_implementation().solve(matrix_a, matrix_b, actual, 2, 2, 2)
torch.cuda.synchronize()
torch.testing.assert_close(actual, expected)
print("Test passed.")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"Test failed: {error}", file=sys.stderr)
raise

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSM=8192, N=6144, K=4096368.536 ms63.085 ms77.552 ms6016.063 GFLOPS
NVIDIA GeForce RTX 4090PASSM=8192, N=6144, K=40961017.057 ms16.829 ms17.418 ms24172.676 GFLOPS

PyTorch#

Approach#

PyTorch 版本直接调用 torch.matmul(A, B, out=C)。A 和 B 分别是形状为 M x N 与 N x K 的二维 GPU tensor,计算结果直接写入调用方提供的输出 tensor C。

该实现不显式管理 grid、thread、tile 或边界条件,这些工作由 PyTorch 的 CUDA backend 完成。参数 M、N 和 K 为保持题目要求的 solve 接口而保留,实际矩阵尺寸由 tensor shape 决定。

Solution#

import torch
# A, B, C are tensors on the GPU
def solve(A: torch.Tensor, B: torch.Tensor, C: torch.Tensor, M: int, N: int, K: int):
torch.matmul(A,B,out=C)

Test Code#

import importlib.util
import sys
from pathlib import Path
import torch
def load_implementation():
source_path = Path(__file__).resolve().parents[2] / "src" / "pytorch" / "02_Matrix_Multiplication.py"
spec = importlib.util.spec_from_file_location("matrix_multiplication_pytorch_minimal", source_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load implementation: {source_path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
matrix_a = torch.tensor([[1.0, 2.0], [3.0, 4.0]], device="cuda")
matrix_b = torch.tensor([[5.0, 6.0], [7.0, 8.0]], device="cuda")
expected = torch.tensor([[19.0, 22.0], [43.0, 50.0]], device="cuda")
actual = torch.empty_like(expected)
load_implementation().solve(matrix_a, matrix_b, actual, 2, 2, 2)
torch.cuda.synchronize()
torch.testing.assert_close(actual, expected)
print("Test passed.")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"Test failed: {error}", file=sys.stderr)
raise

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSM=8192, N=6144, K=40961029.305 ms25.729 ms31.689 ms14069.752 GFLOPS
NVIDIA GeForce RTX 4090PASSM=8192, N=6144, K=4096107.258 ms7.151 ms7.410 ms56810.183 GFLOPS

References#

关注 LeetGPU repository,了解更多内容。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LeetGPU | 02_Matrix_Multiplication
https://github.com/HaoyangPing0324/LeetGPU
作者
平昊阳
发布于
2026-07-25
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
「社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
212
分类
26
标签
291
总字数
2,407,536
运行时长
0 天
最后活动
0 天前

文章目录