CUDA 18 · 综合用例、能力速查与术语表
从“会写 Kernel”到“能交付系统”
一个完整 CUDA 项目不是单个快 kernel,而是一条可证明的链:
需求与误差预算
↓
CPU/高精度 reference + 数据集
↓
算法分解与数据布局
↓
库 / SIMT / Tile / 多 GPU 选型
↓
正确性测试 + Sanitizer
↓
端到端 benchmark + profiler
↓
瓶颈驱动的优化
↓
能力查询、fallback、部署矩阵与回归监控下面四个用例覆盖规则 stencil、随机模拟、稀疏图和大模型推理,展示如何把前 17 章的知识连起来。
用例一:实时图像处理流水线
目标:批量 RGB 图像经过灰度化、Gaussian blur、Sobel 和 threshold,输出边缘图。要求吞吐稳定,支持任意宽高与 batch。
数据流
CPU decode
│ pinned ring buffers
▼
H2D ─→ RGB→Gray ─→ Gaussian X/Y ─→ Sobel+threshold ─→ D2H/encoder
stream 0 image k
H2D ─→ RGB→Gray ─→ Gaussian X/Y ─→ Sobel+threshold ─→ D2H/encoder
stream 1 image k+1先用 NPP/nvJPEG 等库建立基线。若格式或融合需求不匹配,再写自定义 kernel。
映射与布局
- block 用二维 shape,例如
32×8或16×16; - 行主序图像让相邻
threadIdx.x访问相邻像素; - pitch/stride 是字节数,不可假定
width*sizeof(pixel); - blur/stencil 用 shared tile 加 halo;
- border mode 明确为 clamp、reflect、wrap 或 zero;
- RGB packed 与 planar 布局要分别 benchmark。
shared tile for radius=1
halo halo halo halo halo halo
halo x x x x halo
halo x output output x halo
halo x output output x halo
halo x x x x halo
halo halo halo halo halo haloKernel 骨架
template<int BX, int BY>
__global__ void sobel(const std::uint8_t* in, std::size_t in_pitch,
std::uint8_t* out, std::size_t out_pitch,
int width, int height) {
__shared__ std::uint8_t tile[BY + 2][BX + 2];
int x = blockIdx.x * BX + threadIdx.x;
int y = blockIdx.y * BY + threadIdx.y;
// 协作加载中心和 halo;所有线程必须到达 barrier。
load_clamped_tile(tile, in, in_pitch, x, y, width, height);
__syncthreads();
if (x < width && y < height) {
int gx = gradient_x(tile, threadIdx.x + 1, threadIdx.y + 1);
int gy = gradient_y(tile, threadIdx.x + 1, threadIdx.y + 1);
row_ptr(out, out_pitch, y)[x] = saturate(abs(gx) + abs(gy));
}
}重点不是复制辅助函数,而是 barrier 必须位于边界 if 外;否则边缘 block 的一部分线程提前退出会导致未定义行为。
优化次序
- 先把 decode/transfer/kernel/encode 分段计时;
- pinned buffers + 多 stream 重叠多张图;
- 消除 RGB→Gray 与 blur 间的中间 round trip;
- shared halo 降低重复 global load;
- 批量稳定后用 CUDA Graph 降低 launch overhead;
- 只有 profiler 显示算术热点,再考虑 vectorized load 或 fast math。
测试矩阵
| 类别 | 样例 |
|---|---|
| 尺寸 | 0×0、1×1、小于 block、奇数、4K |
| stride | 紧密、额外 padding、非默认 pitch |
| 内容 | 全黑、全白、单像素脉冲、棋盘、随机 |
| 边界 | 四角、最后一行/列、radius 大于尺寸 |
| 批次 | 1、2、并发 stream 数以上 |
用例二:Monte Carlo 期权定价
目标:模拟几何布朗运动终值,计算 discounted payoff 的均值和置信区间。
数据流与算法
sample id
↓ counter/subsequence RNG
normal Z
↓ path/payoff(可与 RNG 融合)
payoff
├─ sum(payoff)
└─ sum(payoff²)
↓
mean + variance + confidence interval大数组不是必需:每个线程可生成多个样本并做局部累加,再用 CUB reduction 合并 {sum, sumsq}。这样减少显存流量。
数值与统计正确性
- 累加采用
double或经过验证的分层float; variance = E[x²]-E[x]²在方差很小时可能发生消减,必要时用更稳定算法;- 固定 seed 只保证可复现实验,不证明随机质量;
- 每个逻辑样本拥有不重叠 sequence/counter;
- 与 Black-Scholes 闭式解对照,误差应随 统计下降;
- 报告置信区间而不是只报一个浮点值。
并行粒度
thread i:
local_sum = 0
repeat K samples:
z = rng(sample_id)
payoff = simulate(z)
local_sum += payoff
block_reduce(local_sum)
grid result → second-stage reduce / CUB每线程多个样本能摊销 RNG state 和 launch 成本,但增加寄存器及串行工作。扫描 K 并看 occupancy、instruction throughput 和总时间。
性能实验
| 版本 | 预期瓶颈 |
|---|---|
| 生成随机数组,再单独 payoff | global bytes 和额外 launch |
| RNG + payoff 融合 | exp/log/SFU 或 RNG 算术 |
| 原子累加一个全局标量 | atomic contention |
| block reduction + 少量原子 | 通常更平衡 |
| CUB transform-reduce | 高质量库基线 |
用例三:稀疏图 BFS
目标:在 CSR 图上从源点计算无权最短层级。它展示 GPU 不擅长的负载不均、随机内存与动态 frontier。
CSR 与 Frontier
row_offsets[v]..row_offsets[v+1] → v 的邻边
frontier level L: [v2, v8, v9]
│ expand neighbors
▼
candidate vertices
│ visited atomic / duplicate removal / compact
▼
frontier level L+1基线 Kernel
__global__ void expand(const int* frontier, int frontier_size,
const int* row, const int* col,
int* distance, int level,
int* next, int* next_size) {
for (int i = blockIdx.x * blockDim.x + threadIdx.x;
i < frontier_size;
i += blockDim.x * gridDim.x) {
int v = frontier[i];
for (int e = row[v]; e < row[v + 1]; ++e) {
int u = col[e];
if (atomicCAS(&distance[u], -1, level + 1) == -1) {
int pos = atomicAdd(next_size, 1);
next[pos] = u;
}
}
}
}它正确直观,但高 degree vertex 由一个线程串行处理,next_size 又形成全局原子热点。
逐步优化
- Warp-centric:一个 warp 合作处理高 degree vertex;
- 分级调度:小 degree 每线程、中 degree 每 warp、大 degree 每 block;
- Block queue:先写 shared queue,再批量 reserve global slots;
- Prefix-sum compaction:用 scan 替代逐元素 global atomic;
- Direction optimization:frontier 很大时从 unvisited 顶点检查 incoming 邻居;
- Persistent/work stealing:degree 极不均时动态分配边范围。
degree distribution
small vertices ─→ thread lanes
medium vertices ─→ whole warp
large vertices ─→ whole block / split edge ranges指标
BFS 的“有效工作”随图结构变化,不能只报 kernel 时间。至少报告:
- traversed edges per second;
- 每层 frontier size 与时间;
- 重复 candidate 比例;
- 原子次数与成功率;
- global load efficiency、cache hit、warp divergence;
- 与 CPU reference 的 distance 全量比较。
道路网、均匀随机图和幂律社交图表现完全不同,必须覆盖多种 degree 分布。
用例四:LLM Attention 推理片段
目标:解释 prefill 与 decode 下 attention 为什么需要不同 kernel,并把 GEMM、online softmax、KV cache 和多 GPU 串联起来。
朴素数据流
Q,K ─→ GEMM ─→ score[N,N] ─→ mask/softmax ─→ P[N,N]
│
P,V ───────────────────────────────────────────→ GEMM → O中间 score 与 P 的 显存流量是主要问题。FlashAttention 类算法按块计算 score,维护在线 softmax 统计量,不把完整矩阵写回 global memory。
Online Softmax 合并
对已经处理部分的最大值、指数和 和输出累加,加入新 tile 后:
最终输出为。这允许流式处理 K/V tiles,同时保持数值稳定。
Prefill 与 Decode
| 阶段 | 形状特征 | 常见瓶颈 | 设计方向 |
|---|---|---|---|
| Prefill | query 长,矩阵较大 | 计算与 HBM 流量 | Tensor Core、tile、并行 sequence |
| Decode | 每步 query 很少,读取长 KV cache | memory bandwidth、launch latency | 批处理、paged KV、融合、Graph |
Decode 中 KV cache 的布局和分页元数据往往比单个矩阵指令更重要。
logical sequence pages
token 0..15 → physical page 7
token 16..31 → physical page 2
token 32.. → physical page 9
block table → gather K/V tiles → attention工程组合
- QKV projection 和 output projection 优先用 cuBLASLt/CUTLASS;
- attention 核心可用优化框架、CUTLASS 或 Tile DSL,不从朴素 kernel 开始生产化;
- RoPE、bias、mask、dequant 可在带宽允许时融合;
- CUDA Graph 降低 decode 的重复 launch 开销;
- KV pages 用 memory pool/VMM 管理时要区分逻辑容量和物理驻留;
- tensor parallel 使用 NCCL collective,流水线并行还要处理 stage 间通信;
- 量化必须同时定义权重、activation、scale、accumulator 和输出 dtype。
评估指标
不要只报某个 attention kernel 的 TFLOP/s:
- time to first token;
- inter-token latency;
- tokens/s 与 batch/sequence 的关系;
- KV cache bytes/token 和最大并发序列;
- HBM bandwidth、Tensor Core utilization;
- 数值误差、困惑度或任务质量;
- 多 GPU scaling 与通信占比。
一个可复用的项目目录
cuda-project/
├─ CMakeLists.txt
├─ include/
│ ├─ error_check.hpp
│ ├─ device_buffer.hpp
│ └─ api.h # 稳定 C ABI
├─ src/
│ ├─ kernels.cu
│ ├─ dispatch.cpp
│ └─ reference.cpp
├─ tests/
│ ├─ correctness.cpp
│ ├─ edge_cases.cpp
│ └─ sanitizer_cases.cu
├─ benchmarks/
│ ├─ bench_kernel.cpp
│ └─ datasets.json
├─ tools/
│ ├─ profile_nsys.ps1
│ └─ profile_ncu.ps1
└─ docs/
├─ numerical_contract.md
└─ performance_report.md测试与 benchmark 分开:测试追求覆盖错误,benchmark 追求稳定测量;两者都不应把首次 context/JIT 开销意外混入或排除。
Feature Query 与 Dispatch
编译成功不等于当前 GPU 能执行全部路径。启动时建立 capability 表:
struct Capabilities {
int cc_major;
int cc_minor;
bool managed;
bool concurrent_managed;
bool memory_pools;
bool cooperative_launch;
bool clusters;
bool vmm;
};input problem + numerical policy + device capabilities
│
▼
dispatch
├─ optimized_sm100
├─ optimized_sm90 (cluster/TMA)
├─ generic_sm80 (async copy)
└─ portable fallback不要仅用 GPU 商品名 dispatch;使用 Compute Capability、API attribute 和库 heuristic。MIG 配置还可能改变可用 cluster/SM 资源。
性能回归门禁
每个基准保存:
- GPU UUID/型号、CC、功耗/时钟策略;
- Driver、Toolkit、库、编译器与 git revision;
- 输入 shape、dtype、分布和随机 seed;
- 编译参数、算法 ID、workspace;
- warmup、repeat、统计量;
- kernel time、端到端 time、bytes/FLOPs 和误差。
门禁用区间而非单次值。例如以稳定机器的 median 与 P95 比较,只有超过噪声阈值才失败。共享开发机上的自动频率和其他进程会制造假回归。
生产故障分层排查
程序无法启动
└─ Driver/Toolkit/架构/动态库/容器挂载 → 01、16
结果错误或偶现
└─ 越界/竞态/同步/生命周期/数值 → 05、09、11、12
Kernel 慢
└─ 算法/bytes/occupancy/stall/布局 → 04、06、08
端到端慢但 kernel 快
└─ launch/分配/传输/同步/通信 → 05、10、14、17
新架构能力未生效
└─ capability、编译目标、dispatch、生成指令 → 13、15、16CUDA 术语速查
| 术语 | 含义 | 不要混淆 |
|---|---|---|
| Thread | SIMT 中最小逻辑执行实例 | 不是独立 CPU core |
| Warp | 32 个线程的调度组 | 分支后仍是同一 warp 的 active masks |
| Block/CTA | 可共享 shared memory 并 block-sync 的线程组 | 默认不能与其他 block 同步 |
| Grid | 一次 kernel launch 的 blocks 集合 | 与 cooperative grid 保证不同 |
| SM | 执行 warp 的多处理器 | CUDA Core 只是其一种单元 |
| GPC | 包含多个 SM 等资源的更高硬件分组 | Cluster 通常在此范围协同 |
| Occupancy | active warps 相对上限的比例 | 不是利用率或性能本身 |
| Coalescing | warp 访问合并为高效内存事务 | 不等于 cache hit |
| Shared memory | block 作用域片上软件管理存储 | 与 L1 可能共享物理资源但语义不同 |
| Local memory | 每线程地址空间,常落在 device memory | 名字不表示片上 |
| UVA | 统一虚拟地址空间 | 不等于页面自动迁移 |
| Unified Memory | 可迁移/统一管理的内存机制 | 不等于始终零拷贝 |
| Pinned memory | 不可分页 host memory | 过量会伤害系统 |
| Stream | 有序工作队列 | 不保证不同 stream 必然并发 |
| Event | stream 时间点/依赖对象 | timing event 与同步需求要区分 |
| PTX | 虚拟 GPU ISA | 不是最终硬件指令 |
| SASS | 特定 GPU 机器指令 | 不具备跨架构可移植性 |
| Tensor Core | 矩阵/张量运算单元 | 不是所有 shape/dtype 自动使用 |
| TMA | descriptor 驱动的 tensor 搬运单元 | 不执行矩阵乘法 |
| DSM | cluster 内分布式 shared memory | 不是全 GPU 通用 shared memory |
| Tile | 多维值/工作分区抽象 | 不等于固定一个 warp |
| Memory pool | allocation backing 的缓存与复用策略 | reserved 不等于 used |
| VMM | 虚拟地址与物理 allocation 显式映射 | 不自动提供同步 |
API 速查
| 目标 | 常用 API/工具 |
|---|---|
| 查询设备 | cudaGetDeviceProperties、cudaDeviceGetAttribute |
| 分配/传输 | cudaMalloc、cudaMemcpyAsync、cudaHostAlloc |
| 池化分配 | cudaMallocAsync、cudaMemPool* |
| Unified Memory | cudaMallocManaged、prefetch、advice |
| Stream/Event | cudaStream*、cudaEvent* |
| Graph | cudaGraph*、stream capture |
| 高级 launch | cudaLaunchKernelEx |
| Driver module | cuModule*、cuLaunchKernel |
| VMM | cuMemAddressReserve/cuMemCreate/cuMemMap/cuMemSetAccess |
| JIT | NVRTC、nvJitLink |
| 正确性 | Compute Sanitizer、CPU reference、单元测试 |
| 系统时间线 | Nsight Systems、NVTX |
| Kernel 指标 | Nsight Compute |
| 反汇编 | cuobjdump、nvdisasm |
毕业检查表
学完后,应能对一个 CUDA 项目给出以下可验证答案:
- 工作如何映射到 grid/block/warp 或 tile program?
- 每个输出元素需要搬运多少字节、做多少运算?
- 哪些数据在 register/shared/L2/HBM/host/fabric 中流动?
- 所有跨线程、stream、device、进程依赖如何建立 happens-before?
- 数值误差预算与 reference 是什么?
- 为什么选择库、SIMT、Tile 或多 GPU?
- profiler 证据指向哪个瓶颈?
- 优化对端到端时间而非单 kernel 有何影响?
- 不支持某项能力的 GPU 走哪条 fallback?
- 构建、测试、部署与性能结果能否在另一台机器复现?