第4章:模型基准测试与性能分析
模型剪枝量化之前,有一件事必须做——建立基准。说白了,你得知道原始模型到底跑多快、精度多高,才能判断压缩后的效果是好是坏。我见过不少同学上来就剪枝,剪完发现精度掉得一塌糊涂,结果回头一看,连原始模型的mAP都没记录过……嗯,这坑我踩过。
4.1 用官方脚本训练一个基准模型
YOLOv11官方仓库提供了完整的训练脚本。我个人习惯直接用train.py,参数通过命令行或配置文件传入。下面是我常用的训练命令:
python train.py \
--data coco.yaml \
--weights yolov11n.pt \
--batch-size 16 \
--epochs 300 \
--imgsz 640 \
--device 0 \
--project baseline \
--name yolov11n_coco
这里有几个关键点:
- --weights:用官方预训练权重做微调,收敛更快。我从不用随机初始化训YOLO,太慢了。
- --batch-size:根据显存调整。我习惯先设16,如果显存有余量再往上加。
- --imgsz:640是YOLOv11的默认输入尺寸。如果你要部署在边缘设备,可以试试320或416。
--cos-lr和--label-smoothing 0.1,前者让学习率更平滑,后者能提升一点泛化能力。这两个参数在剪枝后尤其有用。
4.2 导出模型:从PyTorch到部署格式
训练完成后,我们需要把模型导出成不同格式做测试。YOLOv11官方支持导出ONNX、TensorRT、OpenVINO等。我一般先导出ONNX,再转TensorRT做性能测试。
# 导出ONNX
python export.py \
--weights baseline/yolov11n_coco/weights/best.pt \
--include onnx \
--imgsz 640
# 导出TensorRT(需要先装好TensorRT)
python export.py \
--weights baseline/yolov11n_coco/weights/best.pt \
--include engine \
--imgsz 640 \
--device 0
导出时有个坑:动态batch和静态batch的选择。如果你部署的服务需要处理可变batch,用--dynamic;如果固定batch,用静态能快10%~15%。我在项目中一般先用静态batch做基准测试,因为结果更稳定、好对比。
4.3 评估mAP:精度说了算
模型导出来后,第一件事就是跑mAP。YOLOv11的val.py可以直接评估:
python val.py \
--data coco.yaml \
--weights baseline/yolov11n_coco/weights/best.pt \
--batch-size 32 \
--imgsz 640 \
--task val
输出结果类似这样:
| 指标 | 数值 | 说明 |
|---|---|---|
| mAP@0.5 | 0.674 | IoU阈值0.5时的平均精度 |
| mAP@0.5:0.95 | 0.487 | IoU从0.5到0.95步长0.05的平均精度 |
| Precision | 0.712 | 所有类别的平均精确率 |
| Recall | 0.643 | 所有类别的平均召回率 |
4.4 测FPS:速度是硬道理
精度达标了,还得看速度。FPS(每秒帧数)是衡量推理速度的核心指标。我通常用两种方式测:
- 纯PyTorch推理:方便调试,但速度偏慢,适合对比不同模型结构的相对差距。
- TensorRT推理:接近真实部署环境,速度最快,适合做最终验收。
下面是我写的一个简易测速脚本:
import torch
import time
from ultralytics import YOLO
model = YOLO('baseline/yolov11n_coco/weights/best.pt')
model.to('cuda')
dummy_input = torch.randn(1, 3, 640, 640).cuda()
# 预热
for _ in range(50):
model(dummy_input)
# 正式测试
num_iter = 500
start = time.time()
for _ in range(num_iter):
model(dummy_input)
end = time.time()
fps = num_iter / (end - start)
print(f'FPS: {fps:.2f}')
测速时要注意:一定要做预热。GPU在刚开始推理时处于冷启动状态,速度偏慢。我一般预热50~100次,然后取500次推理的平均值。另外,batch size设为1测的是单帧延迟,设为N测的是吞吐量,两者都要记录。
torch.no_grad(),结果梯度计算开着,速度慢了将近一倍。记得加上with torch.no_grad():,或者用model.eval()。
4.5 Profiling工具:找到瓶颈在哪
光知道FPS还不够,你得知道时间花在哪了。Profiling工具能帮你定位瓶颈——是卷积层太慢?还是后处理拖了后腿?
我常用的工具有三个:
| 工具 | 适用场景 | 输出内容 |
|---|---|---|
| PyTorch Profiler | PyTorch模型调试 | 每个算子的耗时、显存占用 |
| NVIDIA Nsight Systems | GPU整体性能分析 | GPU利用率、kernel启动开销 |
| TensorRT Profiler | TensorRT引擎优化 | 每层推理时间、显存带宽 |
PyTorch Profiler最简单,几行代码就能用:
from torch.profiler import profile, record_function, ProfilerActivity
with profile(activities=[ProfilerActivity.CUDA], record_shapes=True) as prof:
with record_function("model_inference"):
model(dummy_input)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
输出会告诉你每个算子花了多少时间。我一般重点关注Conv2d和NMS这两块——前者是计算主力,后者是后处理瓶颈。如果Conv2d占了80%以上的时间,说明模型计算量太大,剪枝空间很大;如果NMS占了20%以上,那就要考虑优化后处理了。
4.6 基准测试清单:一个都不能少
为了方便后续对比,我每次做基准测试都会记录以下信息:
- 模型信息:版本、输入尺寸、参数量、FLOPs
- 精度指标:mAP@0.5、mAP@0.5:0.95、Precision、Recall
- 速度指标:PyTorch FPS、TensorRT FPS、单帧延迟(ms)
- 资源占用:显存峰值、模型文件大小
- Profiling结果:各层耗时占比、瓶颈算子
把这些数据整理成表格,剪枝量化前后一对比,效果一目了然。我习惯用CSV存一份,方便后面画图展示。
4.7 本章知识结构图
下面这张图概括了基准测试的完整流程:
这张图展示了基准测试的完整链路:先训练模型,再导出,然后分别评估精度和速度,最后汇总数据。每一步都有对应的工具和方法,照着做就行。
4.8 小结
基准测试是模型压缩的起点,也是标尺。没有它,你后面做的所有优化都像是在黑箱里调参数——调了半天也不知道是好是坏。我建议你把本章的测试流程写成脚本,每次压缩前后一键运行,自动生成对比报告。这样既省时间,又不容易出错。
嗯,基准测试就聊到这。接下来你就可以拿着这份数据,开始真正的剪枝和量化了。