一、算子吞吐压测概述
大家好,我是你们的老朋友。今天咱们聊聊算子吞吐压测。
说实话,我刚入行那会儿,对「压测」这事儿挺不屑的。觉得模型能跑通就行,管它快不快呢?直到有一次,我负责的推荐系统上线,线上延迟直接飙到 200ms,用户反馈炸了锅……嗯,从那以后,我再也不敢轻视压测了。
1.1 什么是算子吞吐
先问个问题:你写了一个 Conv2d 算子,跑一遍 forward 用了 0.5 毫秒。这算快还是慢?
光看延迟,其实说明不了什么。你得看它「单位时间能处理多少数据」——这就是吞吐量。
算子吞吐,说白了就是:
一个算子在给定硬件上,每秒能处理多少个输入样本(或多少数据量)。
举个例子:
一个矩阵乘法算子,输入是 [1024, 1024] 的矩阵,在 A100 上跑一次耗时 0.1ms。
那它的吞吐就是 1 / 0.0001 = 10000 次/秒。
但注意,这只是单次推理的吞吐。实际压测时,我们通常用 batch 模式,一次喂进去一批数据,看总吞吐。
吞吐量 = 处理的数据量 / 总耗时
常用单位:samples/s、tokens/s、GB/s
1.2 为什么需要压测
我见过太多团队,模型在单卡上跑得飞快,一上多卡就崩。或者离线测试延迟 1ms,线上变成 10ms。
为什么会这样?
因为「能跑」和「能扛」是两码事。压测就是为了回答三个问题:
- 这个算子到底能跑多快? —— 摸清硬件上限
- 在多并发下会不会崩? —— 验证稳定性
- 显存够不够用? —— 避免 OOM
我个人习惯,在开发新算子时,第一件事不是调精度,而是先压测。先知道它的性能天花板在哪,后面优化才有方向。
1.3 压测的核心指标
压测不是只看一个数字。我一般会盯三个指标:
1. 吞吐量(Throughput)
这是最直观的指标。单位时间内能处理多少数据。
注意区分:
- 峰值吞吐:理想状态下,硬件跑满时的最大吞吐
- 实际吞吐:考虑数据搬运、kernel launch 开销后的真实吞吐
我曾经优化过一个 LayerNorm 算子,峰值吞吐能到 800GB/s,但实际跑起来只有 200GB/s。一查,发现是数据从 CPU 到 GPU 的拷贝成了瓶颈。嗯,这就是典型的「理论很丰满,现实很骨感」。
2. 延迟(Latency)
延迟和吞吐是跷跷板。你压得越狠,延迟往往越高。
我一般会看两个延迟:
- P50 延迟:一半请求在这个时间内完成
- P99 延迟:99% 的请求在这个时间内完成
为什么看 P99?因为 P50 好看,但 P99 才是用户体验的真相。我遇到过 P50 只有 2ms,但 P99 飙到 50ms 的情况——说白了,就是偶尔有请求被卡住了。
3. 显存占用(Memory Usage)
这个容易被忽略。很多新手只盯着吞吐和延迟,结果一跑大 batch 直接 OOM。
显存占用分两部分:
- 静态显存:算子本身占用的参数、缓存
- 动态显存:运行时临时分配的中间结果
1.4 三个指标的关系
这三个指标不是孤立的。我画了张图,帮你理清它们的关系:
你看,这三个指标就像三根绳子拴在一起。你拉吞吐这根,延迟和显存也会跟着动。压测的目的,就是找到那个「既要马儿跑,又要马儿少吃草」的平衡点。
1.5 压测的基本流程
说了这么多,到底怎么压?我一般按这几步走:
- 确定压测目标:是测单算子,还是测整个模型?
- 选择压测工具:PyTorch Profiler、NVIDIA Nsight、还是自己写脚本?
- 设置压测参数:batch size、输入尺寸、数据类型(FP32/FP16)
- 运行并采集数据:至少跑 100 次,取稳定值
- 分析瓶颈:是计算瓶颈?访存瓶颈?还是通信瓶颈?
- 迭代优化:改完再测,直到达标
1.6 常见误区
最后,分享几个我踩过的坑:
- 只看平均延迟:平均延迟好看,但 P99 可能已经炸了。一定要看分布。
- 忽略数据搬运:算子本身跑得再快,数据从 CPU 搬来搬去也白搭。
- 一次压测就下结论:硬件有温度波动,多跑几次取中位数更靠谱。
好了,这一章就到这里。算子吞吐压测,说白了就是「用数据说话」。别凭感觉,别拍脑袋,跑一遍压测,什么都清楚了。
想要更多资料,联系可可