GPU系统拆解-11-高频 Kernel 设计:从自然并行到资源权衡
GPU系统拆解-11-高频 Kernel 设计:从自然并行到资源权衡
本文是「GPU系统拆解」系列第 11 篇。
系列导读:GPU系统拆解-00-导读:从架构认知到推理系统的学习路线
上一篇:GPU系统拆解-10-面试表达与系统思维:怎么把 GPU 理解讲成工程判断
下一篇:GPU系统拆解-12-LLM 推理的 GPU 主线:Prefill、Decode、KV Cache 与系统约束
这一篇不是再讲一遍 CUDA 语法,而是把前面的主线真正落到 4 类高频 kernel 上:
Reduction、Softmax、LayerNorm、MatMul。目标不是背模板,而是学会一套通用分析方式:这个 kernel 的自然并行边界是什么,瓶颈是计算还是访存,为什么这样切thread / warp / block,以及优化时到底在用什么换什么。
1. 先给结论
- 这 4 类 kernel 几乎覆盖了 GPU kernel 设计里最核心的几种矛盾:归约、同步、数值稳定性、数据复用、访存压力和吞吐上限。
Reduction的核心是把顺序依赖改写成分层并行归约。Softmax的核心不是公式,而是数值稳定性加两次归约,以及如何减少多轮访存。LayerNorm的核心是逐行统计量计算和高频读写,所以它经常更像 memory-bound 问题。MatMul的核心是多层tiling、复用、流水线和 Tensor Core 路径,它决定推理吞吐上限。- 对推理系统来说,
GEMM决定大头,softmax / norm / reduction这类小 kernel 决定尾部效率和系统细节性能。
2. 为什么这四类 kernel 特别重要
这四类 kernel 不是练手题,而是 GPU 思维的四个窗口。
Reduction让你真正理解并行归约、同步和 warp / block 分工Softmax让你把 reduction、数值稳定性和访存模式放到一起看LayerNorm让你意识到“计算不重,但读写很多”的算子为什么难做MatMul让你理解 GPU 吞吐主线为什么总是绕不开 tile、shared memory、register 和 Tensor Core
从推理角度看,它们也都非常直接:
MatMul覆盖线性层、投影层、MLP 和很多 attention 核心乘法Softmax是 attention 的关键归一化步骤LayerNorm / RMSNorm在 Transformer 里高频出现Reduction则潜伏在 softmax、norm、统计和 top-k 等很多算子里
3. 学 kernel 时最该先问的 5 个问题
以后看到一个 kernel,不要先想“要不要上 shared memory”,先问这 5 个问题。
3.1 自然并行边界是什么
是按:
- element
- row
- tile
- 一个 warp
- 一个 block
来切更自然?
并行边界如果选错,后面很多优化都只是补救。
3.2 主要瓶颈是什么
先分清:
- 是算得不够快
- 还是数据喂不过来
也就是先分 compute-bound 还是 memory-bound。
3.3 有没有真实的数据复用
如果没有复用,只是机械把数据搬进 shared memory,收益往往不高。
3.4 同步开销值不值得
block 级同步不是免费操作。很多时候,你在减少访存的同时,也在增加同步和资源占用。
3.5 优化是在用什么换什么
几乎所有 GPU kernel 优化都是 trade-off,例如:
- 少一次 global memory 往返,换更多寄存器
- 更大的 tile,换更低 occupancy
- fusion 减少 launch,换更复杂的寄存器和同步压力
4. Reduction:最经典的 GPU 思维题
4.1 它在解决什么问题
Reduction 的本质是把很多元素通过某种结合运算压成更少结果,例如:
summaxmin
在 CPU 上它常常是顺序累加,在 GPU 上它必须改写成分层归约。
4.2 为什么朴素写法通常很差
最差的一类写法是大量线程直接对同一个全局地址做原子更新。
问题很明显:
- 大量线程争抢同一地址
- global memory 上的原子冲突重
- 没有体现层次化并行
所以 GPU 并行的正确思路不是“让所有线程同时碰同一份状态”,而是:
先局部独立算,再逐层合并。
4.3 更合理的设计模式是什么
典型模式是:
- 每个线程先做局部累加
- warp 内先归约一次
- block 内再合并 warp 结果
- 必要时再做第二阶段归约
这背后其实就是把顺序依赖改写成树形依赖。
4.4 Reduction 真正考你的是什么
它真正考的不是“会不会写归约代码”,而是这些判断:
- 一个线程一次处理多少元素
- 什么时候该用
warp shuffle - 什么时候该用
shared memory - 什么时候要多阶段归约
- 同步次数和访存次数怎么平衡
4.5 这一类 kernel 必须记住的点
- 先做寄存器局部累加,通常比一上来就共享更划算
- warp 内优先用 shuffle,可以少走 shared memory
- shared memory 更适合做 warp 间汇总,而不是所有步骤都依赖它
- 归约越到后面,活跃线程越少,所以后半程常常是效率下降区
5. Softmax:数值稳定性 + 两次归约 + 多轮访存
5.1 Softmax 真正难在哪里
很多人以为 softmax 的难点在 exp。其实真正难点通常有三个:
- 数值稳定性
- 两次 reduction
- 重复读写
稳定写法通常是:
1 | 先求一行最大值 m |
这意味着 softmax 天然不是一步,而是一个复合算子。
5.2 为什么常按“行”并行
对 attention 里的 softmax 来说,最自然的数据边界通常就是一行。
所以常见设计是:
- 一个 warp 负责一行
- 或一个 block 负责一行
关键不是背哪种模板,而是让并行边界和数据依赖边界一致。
5.3 什么时候更适合 warp-level,什么时候更适合 block-level
如果一行比较短,warp-level 实现通常更自然:
- 同步少
- 延迟低
- shared memory 需求小
如果一行很长,往往要升到 block-level:
- 需要多个 warp 协作
- 需要 shared memory 保存中间结果
- 同步次数和资源压力都会上来
5.4 Softmax 的主要瓶颈通常是什么
不是单纯算得慢,而更常见的是:
- 同一行被读多次
- 有两次 reduction
- 同步多
- 中间结果管理重
所以 softmax 的优化主线通常不是“让公式更短”,而是:
减少重复访存、减少同步、尽量融合后续步骤。
5.5 为什么它和 attention 优化直接相关
在 attention 里,softmax 还经常和这些东西绑在一起:
maskscale- dropout
- 中间 score 管理
这也是为什么后面会自然走到 FlashAttention 一类 I/O-aware 设计。
6. LayerNorm:统计量计算 + 高频读写 + 融合思维
6.1 它在做什么
LayerNorm 的核心是对一行 hidden state 做:
- 均值统计
- 方差统计
- 归一化
- 再乘
gamma、加beta
所以它本质上是“带统计量的逐行归一化问题”。
6.2 为什么它比 softmax 更像工程题
因为它在真实模型里往往不是孤立出现的,而是邻接这些步骤:
- residual add
- bias
- dropout
- 量化 / 反量化
这意味着你不能只从公式看它,还要从数据流和融合空间看它。
6.3 它为什么经常偏 memory-bound
LayerNorm 的常见痛点是:
- 至少有多轮遍历
- 同一行数据容易被读多次
gamma / beta带来额外读写- 计算量不算特别重,但访存很多
所以它经常不像大 GEMM 那样是 pure compute problem,而更像 memory system problem。
6.4 常见优化方向有哪些
常见主线包括:
- 用更稳定也更适合并行的统计方法,例如
Welford - 用向量化加载改善访存效率
- 先 warp 内归约,再 block 内汇总
- 和 residual / bias / 量化步骤做融合
这些优化背后的核心逻辑其实是一致的:
尽量少读,尽量少写,尽量少同步。
6.5 为什么它在推理里特别值得关注
因为 LayerNorm 本身 FLOPs 不大,但出现频率高,而且很容易拖尾。
这类算子很典型地提醒你:
推理性能不只取决于大 GEMM,也取决于小而频繁的 memory-bound kernel。
7. MatMul:GPU 世界的主线问题
7.1 为什么它最重要
如果说前面三类 kernel 训练的是“局部 kernel 思维”,那 MatMul 训练的就是 GPU 吞吐主线。
在深度学习和推理里,大量核心计算最终都能落成矩阵乘:
- 全连接层
- QKV 投影
- 输出投影
- MLP
- 很多卷积变体
所以学 matmul,本质上是在学“如何让 GPU 真正把吞吐跑出来”。
7.2 朴素矩阵乘为什么一定不够快
朴素写法的问题不是数学不对,而是:
- 对 global memory 依赖太重
- 数据复用没有组织起来
- 没有形成
block / warp / instruction多层 tile - 走不到最强的矩阵硬件路径
所以高性能 matmul 的本质,从来不是公式变化,而是数据组织方式彻底变化。
7.3 为什么 matmul 天然适合 tiling
因为同一块输入数据一旦搬到更近的层级,就可以被重复使用很多次。
这直接带来三个收益:
- 减少慢内存往返
- 提高
arithmetic intensity - 更容易贴合 Tensor Core 路径
7.4 这类 kernel 真正考你的是什么
它真正考的是这些问题:
- block tile、warp tile、instruction tile 怎么分
- shared memory 和 register 如何配合
- 搬运和计算如何重叠
- 什么情况下值得走 Tensor Core
- 更大的 tile 会不会带来更高寄存器和 shared memory 压力
7.5 这一题和推理为什么最紧密
因为大模型推理的大头通常还是 dense matmul,所以:
- matmul 决定吞吐上限
- 小 kernel 决定尾部效率
这也是为什么 cuBLAS、CUTLASS、TensorRT-LLM 这些东西都离不开这条主线。
8. 把四类 kernel 放到同一张图里看
这四类 kernel 的共性其实很强:
- 都在选择自然并行边界
- 都在处理同步和协作
- 都在平衡寄存器、shared memory 和 occupancy
- 都在面对“少算一点”和“少搬一点”之间的取舍
但它们的主矛盾并不一样:
Reduction:顺序依赖如何改造成并行树Softmax:稳定性、两次归约和多轮访存LayerNorm:统计量计算与高频读写MatMul:多层复用和吞吐上限
这也是为什么不能用一套机械模板去理解所有 kernel。
9. 从推理视角看,这一篇真正要记住什么
9.1 GEMM 决定上限,小 kernel 决定尾部效率
很多人只盯大 matmul,这不够。真实推理里,小而频繁的 kernel 一样会吞掉很多性能。
9.2 很多小 kernel 的问题都更像 memory problem
尤其是:
- softmax
- layernorm
- 各类 elementwise / reduction
它们常常不是 FLOPs 不够,而是读写太多、同步太多或数据组织不顺。
9.3 decode 比 prefill 更容易暴露这些问题
因为 decode 的计算粒度更小,memory system 和 kernel 调度问题更容易暴露。
9.4 真正的 kernel 设计不是背模板,而是做取舍
这句话最重要:
你优化一个 kernel,通常不是“加一个技巧就变快”,而是在寄存器、shared memory、同步、occupancy、访存和实现复杂度之间做取舍。
10. 这一篇必须记住的几句话
- Reduction 的关键是分层归约,不要让大量线程直接争抢同一个全局状态。
- Softmax 的关键是数值稳定性加两次 reduction,优化主线是减少多轮访存和同步。
- LayerNorm 的关键是统计量和高频读写,它常常更像 memory-bound 问题。
- MatMul 的关键是 tiling、复用、流水线和矩阵路径,它决定吞吐上限。
- 学 kernel 设计时,最重要的不是“先上 shared memory”,而是先问自然并行边界和主瓶颈是什么。
- 对推理来说,大 GEMM 决定大头,小 kernel 决定尾部效率和系统细节表现。
11. 精简版面试表达
如果面试官问你怎么理解高频 CUDA kernel,可以这样答:
我会先看这个算子的自然并行边界和主要瓶颈是什么。比如 reduction 的核心是把顺序依赖改写成分层归约;softmax 的难点是数值稳定性和两次 reduction;layernorm 的难点是统计量计算和高频读写,所以常常更偏 memory-bound;matmul 的主线则是多层 tiling、复用和 Tensor Core 路径。真正的优化不是机械套模板,而是在寄存器、shared memory、同步、occupancy 和访存之间做取舍。


