Skip to the content.

SGEMM Optimization: From Naive to Tensor Core

CI License: MIT CUDA C++

从零手写极致优化的 CUDA 矩阵乘法 — HPC 领域的 “Hello World”。五个渐进式优化的 kernel 变体,展示 GPU 核心优化技术,从最朴素的三层循环到 Tensor Core WMMA 达到 cuBLAS 40% 吞吐量


性能总览

在 NVIDIA GeForce RTX 3060 Laptop GPU (Ampere, sm_86) 上实测,矩阵维度 1024×1024×1024:

Kernel GFLOPS vs cuBLAS 耗时 核心技术
cuBLAS (参考) 5727 100% 0.375 ms NVIDIA 高度优化库
Tensor Core (WMMA) 2300 40.2% 0.934 ms FP16→FP32 混合精度,硬件矩阵单元
Tiled (32×32) 753 13.1% 2.853 ms 共享内存分块,数据复用
Double Buffer 701 12.2% 3.064 ms 双缓冲流水线,计算/访存重叠
Bank Conflict Free 673 11.8% 3.190 ms 共享内存 padding 消除 bank 冲突
Naive 604 10.6% 3.553 ms 每线程一个输出元素,基线

所有 kernel 均通过与 cuBLAS 的正确性验证 (allclose: rtol=1e-3, atol=1e-4; Tensor Core: rtol=5e-2)

优化演进路线

  ┌─────────┐     ┌──────────┐     ┌──────────────┐     ┌───────────────┐
  │  Naive  │────▶│  Tiled   │────▶│  Bank-Free   │────▶│ Double Buffer │
  │ 604 GF  │     │ 753 GF   │     │   673 GF     │     │   701 GF      │
  └─────────┘     └──────────┘     └──────────────┘     └───────┬───────┘
                                                                │
                                                                ▼
                                                    ┌───────────────────┐
                                                    │   Tensor Core     │
                                                    │   2300 GF (WMMA)  │
                                                    └───────────────────┘
阶段 变更内容 为什么有效
Naive → Tiled 将矩阵分块加载到共享内存 数据复用,全局内存流量降低 TILE_SIZE 倍
Tiled → Bank-Free 共享内存 padding [32][33] 消除 32 路 bank conflict,共享内存带宽恢复
Bank-Free → Double Buffer 两个共享内存缓冲区交替使用 下一块加载与当前块计算重叠,掩盖内存延迟
→ Tensor Core WMMA API mma_sync 专用矩阵计算单元,峰值性能 ~8× CUDA Core

核心优化技术

内存合并访问 (Memory Coalescing)

Naive 版本访问矩阵 B 的列为非合并访问(stride = N),同一 warp 内线程访问不连续地址。Tiled 加载阶段确保 warp 级合并读取(stride = 1),带宽利用率从 ~12.5% 提升至接近 100%。

共享内存分块 (Tiling)

每个 tile 从全局内存加载一次,在共享内存中被复用 TILE_SIZE 次。全局内存访问复杂度从 O(N³) 降至 O(N³/TILE_SIZE)。共享内存延迟比全局内存低约 100×。

Bank Conflict 消除

共享内存分为 32 个 bank。列访问时所有线程可能访问同一 bank,导致 32 路冲突串行化。通过 +1 padding([32][33])使列访问跨越不同 bank,带宽恢复 ~32×。

双缓冲流水线 (Double Buffering)

使用两个共享内存缓冲区交替进行加载和计算。当前 tile 计算的同时预取下一个 tile,掩盖全局内存访问延迟。

Tensor Core (WMMA API)

Roofline 模型分析

SGEMM 算术强度 AI ≈ N/3(方阵 M=N=K):

矩阵规模 算术强度 瓶颈类型 优化方向
N < 256 内存受限 优化访存模式
N = 512 过渡区域 兼顾两者
N > 1024 计算受限 优化计算效率

快速开始

# Makefile 构建(调整 GPU 架构)
make GPU_ARCH=sm_86

# 或 CMake 构建
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

# 运行基准测试
make benchmark
# 或
./build/bin/sgemm_benchmark

项目结构

sgemm-optimization/
├── src/
│   ├── kernels/
│   │   ├── naive_sgemm.cuh              # Naive: 基础三层循环
│   │   ├── tiled_sgemm.cuh              # Tiled: 共享内存分块
│   │   ├── bank_conflict_free_sgemm.cuh # 消除 bank 冲突
│   │   ├── double_buffer_sgemm.cuh      # 双缓冲流水线
│   │   └── tensor_core_sgemm.cuh        # Tensor Core (WMMA)
│   ├── utils/
│   │   ├── cuda_utils.cuh               # CUDA 错误检查与工具函数
│   │   ├── benchmark.cuh                # 性能测试框架 (CUDA Events)
│   │   └── verify.cuh                   # 正确性验证 (vs cuBLAS)
│   └── main.cu                          # 主程序入口
├── tests/
│   └── test_sgemm.cu                    # Google Test 属性测试
├── roofline_data_*.csv                  # Roofline 分析原始数据
├── CMakeLists.txt                       # CMake 构建 (推荐)
└── Makefile                             # Make 构建 (快速上手)

测试与验证

基于 Google Test 的属性测试覆盖:

属性 验证内容
数值正确性 所有 kernel 与 cuBLAS 结果一致 (allclose)
Tensor Core 容差 在放宽 FP16 容差下结果正确
错误检测 验证系统能正确捕获注入的计算错误
维度不变性 所有 kernel 支持任意对齐矩阵维度
# 构建并运行测试
make test
# 或
cmake --build build --target test_sgemm && ctest --test-dir build

技术栈

类别 技术
语言 CUDA C++17
构建 CMake 3.18+ / Makefile
依赖 cuBLAS, cuRAND, Google Test v1.14.0 (FetchContent)
GPU Compute Capability 7.0+ (Volta → Hopper)
质量 clang-format, GitHub Actions CI

GPU 架构参考

GPU 系列 架构 Compute Capability 构建参数
Tesla V100 Volta sm_70 GPU_ARCH=sm_70
RTX 2080 Turing sm_75 GPU_ARCH=sm_75
RTX 3090 / A100 Ampere sm_80 / sm_86 GPU_ARCH=sm_86
RTX 4090 / L40 Ada Lovelace sm_89 GPU_ARCH=sm_89
H100 Hopper sm_90 GPU_ARCH=sm_90

参考资料


View on GitHub · English README · 中文 README