GPU编程模型
理解GPU编程模型是高效利用GPU算力的基础。 本文将介绍CUDA编程模型、内存层次和性能优化技术。
预计阅读时间:55分钟·难度:高级·更新时间:2024年4月
编程模型概述
GPU采用SIMT (Single Instruction Multiple Threads) 执行模型, 与CPU的SIMD模型有本质区别。理解这一差异是编写高效GPU代码的基础。
CPU vs GPU架构对比
CPU vs GPU架构特点:
┌──────────────────────────────────────────┐
│ CPU GPU │
├──────────────────────────────────────────┤
│ 核心数 少(8-128) 多(数千) │
│ 单核性能 高 低 │
│ 缓存大小 大(30-50MB) 小(4-6MB) │
│ 内存带宽 低(50GB/s) 高(2TB/s+) │
│ 延迟容忍度 低 高 │
│ 适用场景 复杂逻辑 并行计算 │
│ 编程模型 MIMD SIMT │
└──────────────────────────────────────────┘
SIMT执行模型:
├── Warp (线程束)
│ ├── 32个线程为一组
│ ├── 执行相同指令
│ └── 分支发散影响性能
│
├── 线程块 (Block)
│ ├── 多个Warp组成
│ ├── 共享内存可见
│ └── 可同步
│
└── 网格 (Grid)
├── 多个Block组成
├── Block间独立执行
└── 全局内存可见CUDA编程模型
Kernel函数
Kernel函数基础
// CUDA Kernel函数示例
__global__ void vectorAdd(float *a, float *b, float *c, int n) {
// 计算全局线程索引
int idx = blockIdx.x * blockDim.x + threadIdx.x;
// 边界检查
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
// Kernel调用
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;
vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, n);
// Kernel启动配置
// <<<gridDim, blockDim, sharedMemSize, stream>>>
// gridDim: 网格维度 (dim3)
// blockDim: 线程块维度 (dim3)
// sharedMemSize: 动态共享内存大小
// stream: CUDA流线程层次
CUDA线程层次结构
线程层次结构: ┌──────────────────────────────────────────┐ │ Grid (网格) │ │ ├── gridDim.x, gridDim.y, gridDim.z │ │ ├── blockIdx.x, blockIdx.y, blockIdx.z │ │ └── 包含多个Block │ │ │ │ └── Block (线程块) │ │ ├── blockDim.x, blockDim.y, .z │ │ ├── threadIdx.x, threadIdx.y, .z │ │ ├── 最大1024线程 │ │ └── 共享内存可见 │ │ │ │ └── Warp (线程束) │ │ ├── 32个连续线程 │ │ └── 执行相同指令 │ └──────────────────────────────────────────┘ 全局索引计算示例: // 1D Grid, 1D Block int idx = blockIdx.x * blockDim.x + threadIdx.x; // 2D Grid, 2D Block int idx = blockIdx.x * blockDim.x + threadIdx.x; int idy = blockIdx.y * blockDim.y + threadIdx.y; int index = idy * width + idx; // 3D Grid, 3D Block int idx = blockIdx.x * blockDim.x + threadIdx.x; int idy = blockIdx.y * blockDim.y + threadIdx.y; int idz = blockIdx.z * blockDim.z + threadIdx.z; int index = idz * width * height + idy * width + idx;
内存模型
全局内存
全局内存访问模式
全局内存特点:
├── 容量大 (40-80GB)
├── 延迟高 (~300-500 cycles)
├── 带宽高 (1-3TB/s)
└── 所有线程可见
内存合并访问:
├── 同一Warp线程访问连续内存
├── 对齐访问 (128B对齐)
└── 合并为少数内存事务
// 合并访问示例 (高效)
__global__ void copy(float *dst, float *src, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
dst[idx] = src[idx]; // 连续访问
}
}
// 跨步访问 (低效)
__global__ void copyStride(float *dst, float *src, int n, int stride) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx * stride < n) {
dst[idx] = src[idx * stride]; // 跨步访问
}
}
// 随机访问 (最差)
__global__ void gather(float *dst, float *src, int *indices, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
dst[idx] = src[indices[idx]]; // 随机访问
}
}共享内存
共享内存优化
共享内存特点:
├── 容量小 (48-228KB per SM)
├── 延迟低 (~20-30 cycles)
├── 带宽高 (~数TB/s)
└── Block内线程共享
// 矩阵乘法优化示例
__global__ void matmulShared(float *A, float *B, float *C,
int M, int N, int K) {
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
// 共享内存缓存
__shared__ float As[16][16];
__shared__ float Bs[16][16];
float sum = 0.0f;
// 分块计算
for (int t = 0; t < K; t += 16) {
// 加载到共享内存
As[threadIdx.y][threadIdx.x] = A[row * K + t + threadIdx.x];
Bs[threadIdx.y][threadIdx.x] = B[(t + threadIdx.y) * N + col];
__syncthreads();
// 块内矩阵乘
for (int k = 0; k < 16; k++) {
sum += As[threadIdx.y][k] * Bs[k][threadIdx.x];
}
__syncthreads();
}
C[row * N + col] = sum;
}
共享内存Bank冲突:
├── 32个Bank
├── 连续4字节映射到连续Bank
├── 多线程访问同一Bank导致冲突
└── 使用Padding避免冲突性能优化
GPU性能优化策略
GPU性能优化核心策略:
├── 最大化并行度
│ ├── 充分利用所有SM
│ ├── 足够的线程块数量
│ └── 避免线程束发散
│
├── 优化内存访问
│ ├── 合并全局内存访问
│ ├── 使用共享内存缓存
│ └── 避免Bank冲突
│
├── 隐藏延迟
│ ├── 增加线程级并行
│ ├── 使用多流
│ └── 指令级并行
│
└── 减少数据传输
├── 减少Host-Device传输
├── 使用Pinned Memory
└── 异步传输
常见优化技巧:
├── 循环展开 (#pragma unroll)
├── 使用快速数学函数 (__sinf, __expf)
├── 避免原子操作竞争
├── 使用常量内存
└── 使用纹理内存Tensor Core
Tensor Core编程
Tensor Core简介:
├── 专用矩阵计算单元
├── 单周期完成矩阵乘累加
├── 支持混合精度 (FP16/BF16/TF32/FP8)
└── 大幅提升AI计算性能
// 使用WMMA API (CUDA C++)
#include <mma.h>
using namespace nvcuda::wmma;
__global__ void tensorCoreGemm(half *A, half *B, float *C,
int M, int N, int K) {
// 定义片段
fragment<matrix_a, 16, 16, 16, half, row_major> a_frag;
fragment<matrix_b, 16, 16, 16, half, col_major> b_frag;
fragment<accumulator, 16, 16, 16, float> c_frag;
// 初始化累加器
fill_fragment(c_frag, 0.0f);
// 加载矩阵块
load_matrix_sync(a_frag, A, K);
load_matrix_sync(b_frag, B, K);
// Tensor Core矩阵乘
mma_sync(c_frag, a_frag, b_frag, c_frag);
// 存储结果
store_matrix_sync(C, c_frag, N, mem_row_major);
}
// 使用cuBLAS调用Tensor Core
cublasGemmEx(handle,
CUBLAS_OP_N, CUBLAS_OP_N,
M, N, K,
&alpha, A, CUDA_R_16F, K,
B, CUDA_R_16F, N,
&beta, C, CUDA_R_32F, N,
CUBLAS_COMPUTE_32F, CUBLAS_GEMM_DEFAULT_TENSOR_OP);高级主题
高级编程技术
高级GPU编程技术:
├── CUDA流 (Stream)
│ ├── 异步执行
│ ├── 多流并行
│ └── 事件同步
│
├── 动态并行
│ ├── Kernel内启动Kernel
│ └── 递归算法支持
│
├── Cooperative Groups
│ ├── 灵活的线程协作
│ ├── 跨Block同步
│ └── Grid级操作
│
├── CUDA Graph
│ ├── 图执行模型
│ ├── 减少启动开销
│ └── 适合重复工作流
│
└── Multi-Process Service (MPS)
├── 多进程GPU共享
├── 提高GPU利用率
└── 适合推理服务
// 多流示例
cudaStream_t streams[4];
for (int i = 0; i < 4; i++) {
cudaStreamCreate(&streams[i]);
}
// 异步执行多个Kernel
kernel1<<<grid, block, 0, streams[0]>>>();
kernel2<<<grid, block, 0, streams[1]>>>();
kernel3<<<grid, block, 0, streams[2]>>>();
kernel4<<<grid, block, 0, streams[3]>>>();
cudaDeviceSynchronize();