第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 所有类别的平均召回率
重点关注mAP@0.5:0.95,这个指标更严格,能反映模型在不同IoU阈值下的综合表现。剪枝后如果这个值掉得不多,说明压缩是成功的。

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 基准测试清单:一个都不能少

为了方便后续对比,我每次做基准测试都会记录以下信息:

  1. 模型信息:版本、输入尺寸、参数量、FLOPs
  2. 精度指标:mAP@0.5、mAP@0.5:0.95、Precision、Recall
  3. 速度指标:PyTorch FPS、TensorRT FPS、单帧延迟(ms)
  4. 资源占用:显存峰值、模型文件大小
  5. Profiling结果:各层耗时占比、瓶颈算子

把这些数据整理成表格,剪枝量化前后一对比,效果一目了然。我习惯用CSV存一份,方便后面画图展示。

4.7 本章知识结构图

下面这张图概括了基准测试的完整流程:

YOLOv11基准测试流程 YOLOv11基准测试流程 训练基准模型 导出ONNX/TensorRT 评估mAP精度 测FPS速度 Profiling瓶颈分析 汇总基准数据 完成 精度和速度都要测,缺一不可

这张图展示了基准测试的完整链路:先训练模型,再导出,然后分别评估精度和速度,最后汇总数据。每一步都有对应的工具和方法,照着做就行。

4.8 小结

基准测试是模型压缩的起点,也是标尺。没有它,你后面做的所有优化都像是在黑箱里调参数——调了半天也不知道是好是坏。我建议你把本章的测试流程写成脚本,每次压缩前后一键运行,自动生成对比报告。这样既省时间,又不容易出错。

嗯,基准测试就聊到这。接下来你就可以拿着这份数据,开始真正的剪枝和量化了。