第7章:高级主题
深入 GPU 高级特性,掌握专家级优化技术
本章定位
本章负责建立高级主题的地图:Tensor Core、PTX、低精度、多 GPU、算子融合、CUTLASS。初学阶段不要求直接手写最底层 PTX,重点是知道每个主题解决什么问题、何时该使用库或 DSL。
配套主文档:
学习目标
- 掌握 Tensor Core 高级用法
- 理解多 GPU 编程模型
- 了解量化与低精度计算
- 掌握算子融合技术
- 学习 CUTLASS 等高性能库的使用
7.1 Tensor Core 深度使用
7.1.1 Tensor Core 概述
Tensor Core 是专门用于矩阵运算的硬件单元:
- 单周期完成
D = A × B + C(4×4×4 或更大矩阵) - 支持 FP16、BF16、TF32、FP64、INT8 等多种精度
- 相比 CUDA Core 可获得 3-8 倍加速
支持的精度(A100):
| 输入精度 | 累加精度 | 峰值性能 |
|---|---|---|
| FP16 | FP32 | 312 TFLOPS |
| BF16 | FP32 | 312 TFLOPS |
| TF32 | FP32 | 156 TFLOPS |
| FP64 | FP64 | 19.5 TFLOPS |
| INT8 | INT32 | 624 TOPS |
7.1.2 WMMA API 详解
#include <mma.h>
using namespace nvcuda::wmma;
// 定义矩阵片段大小
const int WMMA_M = 16;
const int WMMA_N = 16;
const int WMMA_K = 16;
__global__ void wmma_gemm(half* a, half* b, float* c, int M, int N, int K) {
// 声明矩阵片段
fragment<matrix_a, WMMA_M, WMMA_N, WMMA_K, half, row_major> a_frag;
fragment<matrix_b, WMMA_M, WMMA_N, WMMA_K, half, col_major> b_frag;
fragment<accumulator, WMMA_M, WMMA_N, WMMA_K, float> c_frag;
// 获取当前 warp 的位置
int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;
int warpN = blockIdx.y * blockDim.y + threadIdx.y;
// 初始化累加器
fill_fragment(c_frag, 0.0f);
// 遍历 K 维度
for (int i = 0; i < K; i += WMMA_K) {
// 加载矩阵 A 的片段
load_matrix_sync(a_frag, a + warpM * WMMA_M * K + i, K);
// 加载矩阵 B 的片段
load_matrix_sync(b_frag, b + i * N + warpN * WMMA_N, N);
// 执行矩阵乘加
mma_sync(c_frag, a_frag, b_frag, c_frag);
}
// 存储结果
store_matrix_sync(c + warpM * WMMA_M * N + warpN * WMMA_N,
c_frag, N, mem_row_major);
}7.1.3 PTX 级别的 Tensor Core 操作
// 使用内联 PTX 直接控制 Tensor Core
__device__ void mma_m16n8k16_fp16(
uint32_t* d0, uint32_t* d1,
uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3,
uint32_t b0, uint32_t b1,
uint32_t c0, uint32_t c1
) {
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0, %1}, "
"{%2, %3, %4, %5}, "
"{%6, %7}, "
"{%8, %9};"
: "=r"(*d0), "=r"(*d1)
: "r"(a0), "r"(a1), "r"(a2), "r"(a3),
"r"(b0), "r"(b1),
"r"(c0), "r"(c1)
);
}7.2 多 GPU 编程
7.2.1 CUDA 多设备管理
// 获取可用 GPU 数量
int device_count;
cudaGetDeviceCount(&device_count);
printf("可用 GPU 数量: %d\n", device_count);
// 设置当前设备
cudaSetDevice(0); // 使用 GPU 0
// 获取设备属性
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
printf("设备名称: %s\n", prop.name);
printf("计算能力: %d.%d\n", prop.major, prop.minor);7.2.2 Peer-to-Peer 内存访问
// 检查并启用 P2P 访问
int can_access;
cudaDeviceCanAccessPeer(&can_access, 0, 1);
if (can_access) {
cudaSetDevice(0);
cudaDeviceEnablePeerAccess(1, 0);
cudaSetDevice(1);
cudaDeviceEnablePeerAccess(0, 0);
}
// 现在 GPU 0 可以直接访问 GPU 1 的内存
float* d_ptr_on_gpu1;
cudaSetDevice(1);
cudaMalloc(&d_ptr_on_gpu1, size);
cudaSetDevice(0);
kernel<<<blocks, threads>>>(d_ptr_on_gpu1); // 直接从 GPU 0 访问7.2.3 NCCL 多 GPU 通信
#include <nccl.h>
// 初始化 NCCL
ncclComm_t comm;
ncclUniqueId id;
if (rank == 0) {
ncclGetUniqueId(&id);
}
// 广播 id 给所有进程
MPI_Bcast(&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD);
ncclCommInitRank(&comm, nRanks, id, rank);
// AllReduce 操作(常用于分布式训练)
ncclAllReduce(sendbuff, recvbuff, count, ncclFloat, ncclSum, comm, stream);
// 销毁 NCCL 通信器
ncclCommDestroy(comm);7.3 量化与低精度计算
7.3.1 INT8 量化
// 量化:FP32 -> INT8
__device__ int8_t quantize(float x, float scale) {
float scaled = x / scale;
scaled = fmaxf(-128.0f, fminf(127.0f, scaled));
return (int8_t)roundf(scaled);
}
// 反量化:INT8 -> FP32
__device__ float dequantize(int8_t x, float scale) {
return x * scale;
}
// INT8 矩阵乘法(使用 DP4A 指令)
__global__ void int8_gemm(
const int8_t* A, const int8_t* B, int32_t* C,
int M, int N, int K,
float scale_a, float scale_b
) {
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < M && col < N) {
int32_t sum = 0;
// 使用 __dp4a 进行 4 元素点积
for (int k = 0; k < K; k += 4) {
int32_t a4 = *(int32_t*)(A + row * K + k);
int32_t b4 = *(int32_t*)(B + col * K + k);
sum = __dp4a(a4, b4, sum);
}
// 反量化结果
C[row * N + col] = sum;
}
}7.3.2 FP8 计算(Hopper 架构)
#include <cuda_fp8.h>
// FP8 矩阵乘法(H100)
__global__ void fp8_gemm(
__nv_fp8_e4m3* A, __nv_fp8_e4m3* B, float* C,
int M, int N, int K
) {
// 使用 Tensor Core 进行 FP8 计算
// 需要 CUDA 11.8+ 和 Hopper 架构
using FragA = nvcuda::wmma::fragment<
nvcuda::wmma::matrix_a, 16, 16, 16,
__nv_fp8_e4m3, nvcuda::wmma::row_major>;
using FragB = nvcuda::wmma::fragment<
nvcuda::wmma::matrix_b, 16, 16, 16,
__nv_fp8_e4m3, nvcuda::wmma::col_major>;
using FragC = nvcuda::wmma::fragment<
nvcuda::wmma::accumulator, 16, 16, 16, float>;
FragA a_frag;
FragB b_frag;
FragC c_frag;
nvcuda::wmma::fill_fragment(c_frag, 0.0f);
// 加载并计算
nvcuda::wmma::load_matrix_sync(a_frag, A, K);
nvcuda::wmma::load_matrix_sync(b_frag, B, N);
nvcuda::wmma::mma_sync(c_frag, a_frag, b_frag, c_frag);
nvcuda::wmma::store_matrix_sync(C, c_frag, N, nvcuda::wmma::mem_row_major);
}7.4 算子融合
7.4.1 为什么需要算子融合?
独立 Kernel(慢):
数据 -> [Kernel A] -> 内存 -> [Kernel B] -> 内存 -> [Kernel C]
融合 Kernel(快):
数据 -> [Kernel A + B + C] -> 结果
优势:
- 减少全局内存访问
- 减少 Kernel 启动开销
- 更好的数据局部性
7.4.2 典型的融合模式
// 融合 1:BiasAdd + ReLU
__global__ void bias_add_relu(float* input, float* bias, float* output,
int batch, int features) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total = batch * features;
if (idx < total) {
int f = idx % features;
float val = input[idx] + bias[f];
output[idx] = fmaxf(0.0f, val); // ReLU
}
}
// 融合 2:LayerNorm + Residual
__global__ void layernorm_residual(float* input, float* residual,
float* gamma, float* beta,
float* output, int hidden_size) {
// 计算 LayerNorm 的同时加上 residual
// ... 实现略
}
// 融合 3:GEMM + BiasAdd + Activation
__global__ void gemm_bias_act(float* A, float* B, float* bias,
float* C, int M, int N, int K) {
// 在写回结果时同时加 bias 和激活函数
// ... 实现略
}7.4.3 使用 CUDA Graph 减少开销
// 创建 CUDA Graph
cudaGraph_t graph;
cudaGraphExec_t instance;
// 开始捕获
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
// 录制一系列 Kernel 调用
kernel1<<<blocks1, threads1, 0, stream>>>(...);
kernel2<<<blocks2, threads2, 0, stream>>>(...);
kernel3<<<blocks3, threads3, 0, stream>>>(...);
// 结束捕获
cudaStreamEndCapture(stream, &graph);
// 实例化 Graph
cudaGraphInstantiate(&instance, graph, NULL, NULL, 0);
// 之后可以重复执行(低开销)
for (int i = 0; i < iterations; i++) {
cudaGraphLaunch(instance, stream);
}
// 清理
cudaGraphExecDestroy(instance);
cudaGraphDestroy(graph);7.5 CUTLASS 高性能库
7.5.1 CUTLASS 简介
CUTLASS 是 NVIDIA 开源的高性能线性代数库:
- 提供接近 cuBLAS 的性能
- 高度可配置的模板设计
- 支持各种精度和布局
7.5.2 使用 CUTLASS 进行 GEMM
#include <cutlass/gemm/device/gemm.h>
// 定义 GEMM 配置
using Gemm = cutlass::gemm::device::Gemm<
cutlass::half_t, // ElementA
cutlass::layout::RowMajor, // LayoutA
cutlass::half_t, // ElementB
cutlass::layout::ColumnMajor, // LayoutB
float, // ElementC
cutlass::layout::RowMajor, // LayoutC
float, // ElementAccumulator
cutlass::arch::OpClassTensorCore, // 使用 Tensor Core
cutlass::arch::Sm80 // 架构(A100 = Sm80)
>;
void run_gemm(half* A, half* B, float* C, int M, int N, int K) {
// 配置 GEMM 参数
typename Gemm::Arguments args(
{M, N, K}, // 问题大小
{A, K}, // A 指针和 leading dimension
{B, K}, // B 指针和 leading dimension
{C, N}, // C 指针(输入)
{C, N}, // D 指针(输出)
{1.0f, 0.0f} // alpha, beta
);
// 创建并运行 GEMM
Gemm gemm_op;
cutlass::Status status = gemm_op(args);
if (status != cutlass::Status::kSuccess) {
printf("GEMM 失败\n");
}
}7.6 高级调试和性能分析
7.6.1 使用 Nsight Compute 深入分析
# 详细的内存分析
ncu --metrics \
sm__sass_average_data_bytes_per_sector_mem_global_op_ld.pct,\
l1tex__t_sectors_pipe_lsu_mem_global_op_ld.sum,\
l1tex__t_sectors_pipe_lsu_mem_global_op_st.sum,\
dram__sectors_read.sum,\
dram__sectors_write.sum \
./my_kernel
# 计算利用率分析
ncu --metrics \
sm__cycles_active.avg,\
sm__warps_active.avg.pct_of_peak_sustained_active,\
sm__inst_executed_pipe_tensor.avg.pct_of_peak_sustained_elapsed \
./my_kernel7.6.2 使用 CUDA Sanitizer
# 检测内存错误
cuda-memcheck ./my_program
# 检测数据竞争
cuda-memcheck --tool racecheck ./my_program
# 检测越界访问
cuda-memcheck --tool initcheck ./my_program7.7 Blackwell:tcgen05.mma、Tensor Memory 与 FP4 / Block Scaling
Blackwell(B200 / B300)把 Tensor Core 主路径推进到第五代 tcgen05.mma,对应几个关键概念,初学阶段不要求手写 PTX,但应能听懂术语:
| 概念 | 含义 | 在哪里能用到 |
|---|---|---|
tcgen05.mma | Blackwell 第五代 Tensor Core 的 PTX 指令族,异步、围绕 descriptor 组织 | CUTLASS 4.x Blackwell GEMM 模板 |
| Tensor Memory (TMEM) | SM 内新增的片上存储,约 256KB/SM,专用于存放 MMA accumulator / operand | tcgen05.mma 的累加目的地、tcgen05.ld/tcgen05.st 与寄存器搬运 |
cta_group::2 | 让同 cluster 内两个 CTA 协作完成一次更大的 MMA | 提升单次 MMA 的 N 维等效 tile |
| FP4 (E2M1) / NVFP4 | 4-bit 浮点,主要用于推理;FP6 (E3M2/E2M3) 6-bit | LLM 权重量化 + Tensor Core 路径 |
| Block Scaling (MXFP / NVFP4) | 多个元素共享一个缩放因子,缩放因子格式为 E8M0(8-bit)或 FP8(E4M3),不是 FP16;MXFP4 块=32,NVFP4 块=16 | Blackwell low-precision GEMM 主路径 |
严格定义、代际算力(dense/sparse)、详细 PTX 形态:见 CUDA GEMM 矩阵乘法优化指南 §13 与 GPU 硬件架构背景与编程范式。Hopper 的 FP8 实战通常走 wgmma + Transformer Engine 路径,WMMA FP8 接口仅作概念示意,不推荐复制到生产代码。
💡 本章要点
- Tensor Core 可以带来 3-8 倍加速,支持多种精度
- 多 GPU 编程 使用 NCCL 进行高效通信
- 量化技术(INT8/FP8)可以显著提升推理速度
- 算子融合 减少内存访问,提升端到端性能
- CUDA Graph 减少 CPU 开销,适合小 Kernel 场景
- CUTLASS 提供灵活的高性能线性代数实现
- Blackwell 第五代 Tensor Core:
tcgen05.mma+ TMEM + FP4 / NVFP4 + block scaling 是新的主路径 - 使用 Nsight Compute 深入分析性能瓶颈