1. 算子与库:GEMM、cuBLAS / cuDNN
GEMM (General Matrix Multiply)
- 定义: 通用矩阵乘法,数学形式为 C=α⋅A×B+β⋅CC = \alpha \cdot A \times B + \beta \cdot CC=α⋅A×B+β⋅C。它是深度学习中最基础、最耗时的算子。全连接层、卷积(im2col 之后)、Transformer 的 Attention 和 FFN,本质上都是 GEMM。
- 为什么重要: GEMM 是计算密集型操作的标杆。它的算术强度(FLOPs/Byte)随矩阵尺寸增大而增加,是最容易跑满 GPU Tensor Core 峰值算力的算子。后端优化的很多技术(Tiling、Tensor Core)都是以 GEMM 为原型发展出来的。
- 类比: 就像烹饪中的"炒"这个动作。几乎所有菜都包含"炒",把"炒"练到极致(火候、翻锅速度),大部分菜的出品速度和品质就有了保障。
cuBLAS / cuDNN
- 定义: NVIDIA 提供的两个官方高性能计算库。
- cuBLAS: 专注于线性代数(GEMM、TRSM 等),相当于 GPU 上的 BLAS 标准实现。
- cuDNN: 专注于深度学习原语(Conv、Pool、Norm、Activation 等),内部会根据输入形状自动选择最优算法(如 Winograd、FFT、Implicit GEMM)。
- 为什么重要: 它们是厂商调优的天花板 。对于标准算子、规整形状,自生成代码很难打赢它们。AI 编译器的后端在遇到标准算子时,首选策略就是 Lowering 到这些库,而不是自己从头写 kernel。
- 类比: 米其林餐厅的预制高汤底料。你自己熬可能也不错,但很难稳定达到大厨水准;对于标准菜品,直接用高汤底料是最高效的选择。只有当你做了一道"菜单上没有的融合菜"时,才需要自己从原料开始处理。
2. 内存与访存:Bank Conflict
Bank Conflict(存储体冲突)
- 定义: GPU 的 Shared Memory 被物理划分为多个独立的存储体(Bank,通常 32 个)。当同一个 Warp 内的多个线程在同一时钟周期访问同一个 Bank 的不同地址时,这些访问必须串行化执行,导致有效带宽下降。
- 关键点:
- 同一 Warp 内多线程访问同一 Bank 的同一地址 → 不冲突(硬件广播机制)。
- 不同 Warp 之间的访问 → 不冲突(Bank 可并行服务不同 Warp)。
- 只有"同 Warp + 同 Bank + 不同地址"才触发冲突。
- 解决方案: Padding(填充)。例如将 tile 宽度从 64 改为 65,使相邻行的起始地址落在不同 Bank 上,错开冲突。
- 类比: 银行有 32 个窗口(Bank)。一个旅行团(Warp)30 人同时去办业务:
- 如果每人去不同窗口 → 并行办理,最快。
- 如果 5 人都去了 3 号窗口办不同的事 → 必须排队,效率降为 1/5。
- 如果 5 人都去 3 号窗口查同一个余额 → 柜员喊一嗓子所有人都听到了(广播),无需排队。
3. 循环与调度:Tile、Tiling、库 Lowering
Tile(数据块 / 切片)
- 定义: 从大张量中切出的一个小矩形块,大小通常适配某一级缓存或寄存器容量。例如一个 1024×10241024 \times 10241024×1024 的矩阵,可以切成 64×6464 \times 6464×64 的 tile。
- 作用: Tile 是后端优化的基本调度单元。所有数据搬运、计算复用、双缓冲都以 tile 为粒度进行。
- 类比: 搬家时不会把整个房子一次性搬走,而是打包成一个个箱子(tile)。箱子大小要匹配货车的载重(缓存容量)和搬运工的体力(寄存器数量)。
Tiling(分块 / 切片变换)
- 定义: 一种循环变换技术,将原本遍历整个大矩阵的循环,改写为先遍历 tile、再在 tile 内部遍历的两层嵌套循环。
- 核心价值: 数据复用 。一个大矩阵乘法如果不分块,每个元素从 HBM 加载后只用一次就被丢弃;分块后,一个 tile 被加载到 Shared Memory / 寄存器后,会被反复使用
tile_size次,将算术强度从 O(1) 提升到 O(tile_size),从而把内存受限问题转化为计算受限问题。 - 类比: 读一本 1000 页的书做笔记。如果不分块,每写一个字就翻回目录找对应章节(频繁访问 HBM);如果按章分块,先把一章内容读到脑子里(加载到 Shared Memory),然后在这一章内集中做笔记(复用),做完再换下一章。
库 Lowering(库降级 / 库映射)
- 定义: "Lowering"在编译器术语中指从高阶抽象转换到低阶表示 。"库 Lowering"特指:后端在模式匹配阶段识别出某个子图等价于某个标准库函数(如 cuBLAS 的
cublasSgemm),于是不再生成自定义 kernel,而是直接插入一条库函数调用。 - 为什么叫"Lowering"而不是"Calling": 因为在编译器的 IR 层面,这确实是一次从高阶算子描述到低阶运行时 API 调用的语义降级。编译器放弃了自主代码生成的控制权,将性能责任委托给厂商库。
- 权衡:
- ✅ 优点:性能有保障(厂商调优)、开发成本低、稳定性高。
- ❌ 缺点:黑盒不可定制、无法与非标准算子融合、对特殊形状可能选不到最优算法。
- 类比: 装修房子时,水电工程外包给专业持证团队(库 Lowering),而自己只做定制家具(Codegen)。外包的部分你没法改管线走向,但质量和安全有保证;定制家具完全按你的需求来,但需要你具备木工手艺。现代 AI 编译器的后端就是这种 "外包 + 自制"的混合模式。
速查对照表
| 术语 | 一句话定义 | 解决的核心问题 | 所属层次 |
|---|---|---|---|
| GEMM | 通用矩阵乘法,DL 的计算基石 | 计算密集型的性能标杆 | 算子 |
| cuBLAS/cuDNN | NVIDIA 官方高性能算子库 | 标准算子的性能天花板 | 运行时库 |
| Bank Conflict | Shared Memory 同 Warp 同 Bank 不同地址的串行化 | 共享内存带宽浪费 | 内存硬件 |
| Tile | 适配缓存/寄存器的数据小块 | 调度的基本单元 | 数据结构 |
| Tiling | 将大循环切分为 tile 级嵌套循环 | 数据复用,提升算术强度 | 循环变换 |
| 库 Lowering | 将子图映射为厂商库调用而非自生成代码 | 标准算子的性能与开发效率权衡 | 后端代码生成 |