4、模型性能基准测试:FPS、FLOPs、Params计算、使用Ultralytics进行基准测试、Profiling工具介绍
模型压缩做完了,效果好不好?不能光靠感觉。你得有数据说话。
我个人习惯,在动手压缩之前,先跑一遍完整的基准测试。这叫「留底」。压缩完再跑一遍,对比着看。你想想看,没有基线,你怎么知道你的量化、剪枝、蒸馏到底提升了多少?
这一章,我们就来聊聊怎么给YOLOv11做一次全面的「体检」。核心指标就三个:FPS、FLOPs、Params。外加两个实战工具:Ultralytics自带的基准测试,以及更细粒度的Profiling工具。
4.1 三个核心指标:FPS、FLOPs、Params
这三个词,做模型优化的应该天天见。但很多人只知其名,不知其意。我简单拆开讲一下。
| 指标 | 全称 | 衡量什么 | 单位 | 我的理解 |
|---|---|---|---|---|
| FPS | Frames Per Second | 推理速度(用户体验) | 帧/秒 | 用户感知到的「快不快」 |
| FLOPs | Floating Point Operations | 计算量(理论复杂度) | GFLOPs | 模型做了多少次浮点运算 |
| Params | Parameters | 模型大小(存储/内存) | M(百万) | 模型有多少个可学习参数 |
关键认知: FPS是最终结果,FLOPs和Params是中间解释。你不能只看FLOPs低就认为一定快。我在项目中遇到过,有些模型FLOPs很低,但因为内存访问不连续,实际跑起来FPS反而更差。所以,三个指标要一起看。
4.1.1 FPS——用户说了算
FPS是最直接的。你拿手机拍一段视频,模型一秒钟能处理多少帧?这就是FPS。
但要注意,FPS跟硬件强相关。同一模型,在A100上跑500 FPS,在Jetson Orin上可能只有50 FPS。所以,报告FPS时一定要注明硬件平台、推理框架(ONNX Runtime / TensorRT / PyTorch)、以及batch size。
我曾经在汇报时只说了FPS,没提用了TensorRT FP16,结果领导拿去跟别人的PyTorch FP32比,差点闹出笑话。嗯,这里要注意,基准测试的环境必须透明。
4.1.2 FLOPs——理论计算量
FLOPs衡量的是模型的计算复杂度。通常用GFLOPs(十亿次浮点运算)表示。YOLOv11n大概在6-7 GFLOPs左右,YOLOv11x可能超过100 GFLOPs。
计算FLOPs的工具很多,我常用的是thop库。但要注意,FLOPs的计算方式各家略有不同,有的算乘加(MACs),有的算浮点运算(FLOPs)。一般1 MAC = 2 FLOPs。看报告时先搞清楚对方用的是哪个口径。
4.1.3 Params——模型体积
Params就是模型文件里存了多少个权重。单位通常是M(百万)。YOLOv11n大约2.6M,YOLOv11x大约56M。
Params直接影响模型文件大小和内存占用。对于移动端或边缘设备,Params是硬约束。比如,你的设备只有256MB内存,一个100M参数的模型根本塞不进去。
4.2 使用Ultralytics进行基准测试
Ultralytics官方已经帮我们封装好了基准测试工具。你不需要自己写循环算FPS,一行命令就能搞定。
# 基准测试YOLOv11n,使用FP16精度,batch size=32
yolo benchmark model=yolo11n.pt imgsz=640 half=True device=0
这条命令会输出什么?我截个典型的输出给你看:
| 模型 | 精度 | 设备 | FPS | 耗时/张 (ms) |
|---|---|---|---|---|
| yolo11n.pt | FP16 | RTX 4090 | 1428 | 0.7 |
| yolo11s.pt | FP16 | RTX 4090 | 1020 | 0.98 |
| yolo11m.pt | FP16 | RTX 4090 | 625 | 1.6 |
小技巧: 如果你只想测FPS,不想跑完整验证集,可以加 --plots=False 和 --verbose=False,只保留速度数据。我经常在调参时这么干,省时间。
但Ultralytics的基准测试有个局限——它只告诉你最终速度,不告诉你瓶颈在哪。比如,为什么YOLOv11m比YOLOv11n慢了2倍?是卷积层慢了?还是后处理慢了?这就需要Profiling工具了。
4.3 Profiling工具介绍
Profiling,说白了就是「逐层计时」。看看模型里每一层、每一个算子花了多少时间。我常用的工具有三个:PyTorch Profiler、ONNX Runtime Profiling、以及NVIDIA Nsight Systems。
4.3.1 PyTorch Profiler
如果你还在PyTorch环境下调试,这个最方便。它能告诉你每个操作(conv2d、batchnorm、relu)的耗时和显存占用。
import torch
from torch.profiler import profile, record_function, ProfilerActivity
model = torch.load('yolo11n.pt', map_location='cuda')
input = torch.randn(1, 3, 640, 640).cuda()
with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True) as prof:
with record_function("model_inference"):
model(input)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
输出会告诉你,耗时最长的前10个操作是什么。我遇到过的情况是,有一次发现torch.stack操作占了15%的时间,后来改成预分配张量,直接省了10%的推理时间。
4.3.2 ONNX Runtime Profiling
模型导出到ONNX后,可以用ONNX Runtime的Profiling功能。它比PyTorch Profiler更接近部署环境。
import onnxruntime as ort
sess = ort.InferenceSession('yolo11n.onnx', providers=['CUDAExecutionProvider'])
# 开启Profiling
sess.set_providers(['CUDAExecutionProvider'], [{'enable_profiling': True}])
input = np.random.randn(1, 3, 640, 640).astype(np.float32)
sess.run(None, {'images': input})
# 关闭Profiling,生成json文件
prof_file = sess.end_profiling()
print(f"Profiling file: {prof_file}")
生成的json文件可以用Chrome的chrome://tracing打开,可视化地看每个算子的耗时。非常直观。
4.3.3 NVIDIA Nsight Systems
这是终极武器。它能看整个GPU的利用率、kernel启动开销、数据搬运时间。如果你发现GPU利用率不到80%,说明CPU在拖后腿,或者数据加载有瓶颈。
避坑指南: 我曾经在Nsight Systems里发现,模型推理只占了40%的时间,剩下的60%都在做NMS(非极大值抑制)的后处理。当时我用的Python实现的NMS,换成C++实现的Torchvision NMS后,FPS直接翻倍。所以,Profiling一定要做,别只盯着模型本身。
4.4 本章知识体系
下面这张图,帮你把这一章的核心逻辑串起来。从三个指标出发,到工具选择,再到分析流程。
4.5 实战建议
最后,给你一个我自己的测试流程,你可以直接拿去用:
- 先跑Ultralytics Benchmark,拿到基线FPS和mAP。这一步5分钟搞定。
- 用PyTorch Profiler 看模型内部,找出耗时Top 3的操作。记录下它们的名字和耗时占比。
- 导出ONNX后,用ONNX Runtime Profiling 再跑一次,确认瓶颈是否一致。有时候PyTorch里的瓶颈在ONNX里会自动优化掉。
- 如果GPU利用率低,上Nsight Systems,看数据加载和CPU预处理是不是在拖后腿。
- 记录所有环境信息:GPU型号、驱动版本、CUDA版本、推理框架版本、batch size、精度(FP32/FP16/INT8)。
记住: 没有基准测试的优化,都是耍流氓。你只有知道起点在哪,才能知道终点在哪。我见过太多人,压缩完模型说「感觉快了」,结果一测只快了5%。嗯,感觉不靠谱,数据才靠谱。