Appearance
算子优化
状态:入门理解 · 更新于 2026-09-14
算子优化的目标不是让某个 Kernel 的指令数量最少,而是在固定输入、精度和正确性约束下,缩短端到端关键路径。一个可复用的流程是:先建立基线,再判断瓶颈,最后针对瓶颈改数据流和并行映射。
先把问题量化
优化前固定以下变量:输入 shape、dtype、布局、Batch、硬件型号、驱动与框架版本。至少记录:
- 延迟的中位数和 P95,而不是一次最快值;
- 吞吐、显存占用和启动次数;
- 与参考实现的最大误差、相对误差和边界样例;
- 端到端时间中该算子所占比例。
如果算子只占总耗时的很小部分,单独加速它未必能带来用户可见收益。
先判断瓶颈
| 类型 | 典型表现 | 优先方向 |
|---|---|---|
| Memory-bound | 带宽接近上限,计算单元未充分利用 | 合并访问、减少读写、复用数据、融合 |
| Compute-bound | 计算吞吐接近上限,访存不是主因 | Tensor Core、指令映射、Tile、精度 |
| Launch-bound | Kernel 很短但调用很多,间隙明显 | 融合、批处理、减少 Python/框架调度 |
| Synchronization-bound | 等待和屏障占比高 | 减少同步、改变流水线和通信安排 |
Roofline 模型可以用算术强度(FLOPs/Byte)初步判断 Compute-bound 与 Memory-bound,但它是上界模型,不能替代真实 Profile。
常用优化手段
访存与布局
让相邻线程访问相邻地址,减少非合并事务;对可复用数据使用 Shared Memory 或寄存器 Tile;避免不必要的 dtype 转换和中间张量。布局转换本身有成本,只有在后续多个算子能够复用时才值得做。
Tiling 与并行映射
以 GEMM 为例,可以把矩阵切成 Block Tile,再进一步做 Warp/Thread Tile,让加载的数据在计算阶段被多次复用。Tile 太小会降低复用,太大则可能增加寄存器和 Shared Memory 压力,需要按 shape 选择。
融合与流水线
把逐元素操作融合进前后主算子,可以减少中间结果的 Global Memory 往返。异步拷贝、双缓冲和计算/访存流水线有助于隐藏延迟,但会增加边界处理与资源占用,必须用 Profile 验证。
选择合适的实现层
优先比较成熟库(cuBLAS、cuDNN、CUTLASS)和框架已有实现;规则稳定、需要快速迭代时可用 Triton;需要精细控制硬件映射时再写 CUDA C++。自定义 Kernel 的维护、编译和多架构兼容成本也应计入方案。
一个安全的优化闭环
- 用参考实现生成多 shape、多 dtype 的正确性样本。
- 预热后测基线,固定 stream、同步方式和重复次数。
- 用 Nsight Systems 判断是否被调用间隙、拷贝或通信限制。
- 用 Nsight Compute 找到访存、指令、Occupancy 或分支问题。
- 只改一个因素,记录配置、结果和误差。
- 在目标 shape 集合、边界输入和不同 GPU 上做回归。
常见反效果
- 为提高 Occupancy 盲目减少寄存器,导致重复加载或指令增加;
- 融合过多操作,寄存器溢出或并行度下降;
- 只比较单个 shape,线上 shape 分布变化后反而变慢;
- 用不同精度或不同输入规模制造“性能提升”;
- 只测 Kernel,不测框架调度、数据搬运和后处理。
信息来源
延伸阅读
更新日志
- 2026-09-14:建立从基线、瓶颈判断到回归验证的优化闭环。