4. 模型性能基准测试

模型压缩做完了,效果好不好?不能光靠感觉。你得有数据说话。

我个人习惯,在动手压缩之前,先跑一遍完整的基准测试。这叫「留底」。压缩完再跑一遍,对比着看。你想想看,没有基线,你怎么知道你的量化、剪枝、蒸馏到底提升了多少?

这一章,我们就来聊聊怎么给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 本章知识体系

下面这张图,帮你把这一章的核心逻辑串起来。从三个指标出发,到工具选择,再到分析流程。

模型性能基准测试知识体系 FPS(推理速度) FLOPs(计算量) Params(参数量) 工具选择 Ultralytics Benchmark PyTorch / ONNX Profiler Nsight Systems 分析流程:逐层耗时 → 定位瓶颈 → 针对性优化 输出:优化前后对比报告

4.5 实战建议

最后,给你一个我自己的测试流程,你可以直接拿去用:

  1. 先跑Ultralytics Benchmark,拿到基线FPS和mAP。这一步5分钟搞定。
  2. 用PyTorch Profiler 看模型内部,找出耗时Top 3的操作。记录下它们的名字和耗时占比。
  3. 导出ONNX后,用ONNX Runtime Profiling 再跑一次,确认瓶颈是否一致。有时候PyTorch里的瓶颈在ONNX里会自动优化掉。
  4. 如果GPU利用率低,上Nsight Systems,看数据加载和CPU预处理是不是在拖后腿。
  5. 记录所有环境信息:GPU型号、驱动版本、CUDA版本、推理框架版本、batch size、精度(FP32/FP16/INT8)。

记住: 没有基准测试的优化,都是耍流氓。你只有知道起点在哪,才能知道终点在哪。我见过太多人,压缩完模型说「感觉快了」,结果一测只快了5%。嗯,感觉不靠谱,数据才靠谱。