吞吐优化概述:从“跑得快”到“跑得稳”
各位同学,咱们今天聊聊吞吐优化。说白了,就是让你的模型或者系统,在单位时间内处理更多的数据。我刚开始接触这行的时候,总觉得“优化”就是让代码跑快点。后来踩了不少坑才明白——吞吐量,才是衡量系统真实生产力的硬指标。
什么是吞吐量?
吞吐量,英文叫 Throughput。你可以把它想象成一条高速公路的车流量。同样一条路,每小时能过 1000 辆车,还是 10000 辆车?这就是吞吐量的差别。
在 AI 计算里,吞吐量通常用“每秒处理的样本数”或者“每秒处理的 Token 数”来衡量。举个例子:你训练一个图像分类模型,吞吐量就是每秒能喂给 GPU 多少张图片。推理场景下,就是每秒能返回多少个预测结果。
核心公式:
吞吐量 = 处理的总数据量 / 总耗时
单位:samples/s、tokens/s、images/s
我个人习惯把吞吐量拆成两个维度看:峰值吞吐和持续吞吐。峰值好看,但持续稳定才是真本事。我在项目中遇到过,有些算子跑 benchmark 时数据漂亮,一上生产环境就掉链子——就是因为没考虑内存带宽的竞争。
为什么需要算子调优?
你可能会问:“我直接用 PyTorch 或者 TensorFlow 的官方算子不就行了?为什么要自己调?”
嗯,这里要注意。官方算子追求的是通用性,它得照顾各种硬件、各种数据形状。但你的场景可能很特殊——比如输入尺寸固定、batch size 很大、或者用了某种特殊的激活函数。这时候,通用算子往往不是最优解。
我举个例子。有一次我在优化一个 Transformer 的推理服务,发现 softmax 算子占了 15% 的时间。换成手写的、针对特定 batch size 优化的版本,直接砍到 5%。这就是算子调优的价值——把通用工具变成趁手的兵器。
避坑指南:
我曾经花了两周调一个卷积算子,最后发现瓶颈根本不在计算,而在数据加载。所以调优前,先做 profiling,别凭感觉下手。
吞吐优化的核心指标
调优不能瞎调,得有指标。我一般盯着三个数:FLOPS、内存带宽、延迟。这三者互相制约,你得找到平衡点。
| 指标 | 含义 | 我的经验 |
|---|---|---|
| FLOPS | 每秒浮点运算次数,衡量计算能力 | 别只看理论峰值,实际利用率更重要 |
| 内存带宽 | 每秒从内存读取/写入的数据量 | 很多算子瓶颈在带宽,不在计算 |
| 延迟 | 单个请求从输入到输出的时间 | 吞吐和延迟往往此消彼长,要权衡 |
为什么会这样?你想想看,一个算子如果计算量很小,但需要频繁读写内存,那它就被内存带宽卡死了。反过来,如果计算量巨大,但数据复用率高,那 FLOPS 就是瓶颈。我建议你记住一句话:算力不够用 FLOPS,数据搬不动看带宽。
调优的整体流程
调优不是玄学,是有套路的。我总结了一个四步法,这些年一直在用:
- Profiling(性能剖析):先搞清楚时间花在哪。用 nvprof、Nsight 或者 PyTorch Profiler 跑一遍,看哪个算子最耗时。
- 瓶颈定位:是计算密集?还是访存密集?还是 kernel launch 开销太大?这一步决定了你下一步的策略。
- 算子替换/重写:针对瓶颈,要么换一个更高效的实现,要么手写 CUDA kernel。我一般先试试 cuDNN 或者 cutlass 有没有更好的选择。
- 验证与迭代:改完一定要测,而且要在真实数据上测。有时候微小的改动会带来意想不到的副作用。
注意:调优是迭代的过程。别指望一次搞定。我曾经为了一个算子调了三版,第一版快了 20%,第二版慢了 10%,第三版才稳定提升 35%。
下面这张图,是我自己画的调优流程。你可以把它当成路线图:
说白了,调优就是个“测量-分析-修改-再测量”的循环。别想着一步到位,也别被花里胡哨的技巧迷惑。我见过太多人一上来就写 CUDA 汇编,结果 profiling 一看,瓶颈在数据预处理——白忙活。
最后说一句:调优的终点不是代码跑得最快,而是系统跑得最稳。吞吐量上去了,延迟还稳定,这才是真本事。
想要更多资料,联系可可