4. 模型复杂度度量标准:FLOPs、Params、FPS、模型体积(MB)的计算与测量方法
做模型压缩和知识蒸馏,你得先知道你的模型到底有多「重」。我见过不少同学一上来就调参、剪枝,结果连自己模型有多少参数、跑一帧要多久都说不清楚。这就像你要减肥,却连体重秤都不上——那你怎么知道效果呢?
这一章,我们就来聊聊衡量模型复杂度的四个核心指标:FLOPs、Params、FPS 和 模型体积(MB)。我会带着你用 thop 和 ptflops 这两个工具,亲手把 YOLOv11 的「体检报告」打出来。
4.1 四个指标,各管一摊
先搞清楚每个指标到底在衡量什么。别搞混了,我见过有人把 FLOPs 和 FPS 当成一回事,那可就闹笑话了。
| 指标 | 全称 | 衡量什么 | 单位 | 越低越好? |
|---|---|---|---|---|
| FLOPs | Floating Point Operations | 模型执行一次前向传播所需的浮点运算次数 | GFLOPs(10⁹) | 通常越低越好 |
| Params | Parameters | 模型中可训练参数的总数 | M(百万) | 通常越少越好 |
| FPS | Frames Per Second | 模型每秒能处理的图像帧数 | fps | 越高越好 |
| 模型体积 | Model Size | 模型文件在磁盘上占用的空间 | MB | 通常越小越好 |
4.2 用 thop 计算 FLOPs 和 Params
thop 是我用得最多的工具。它轻量、直接,几行代码就能把 FLOPs 和 Params 打出来。说白了,它就是帮你把模型「过一遍」,然后统计运算量。
安装很简单:
pip install thop
然后,我们拿 YOLOv11 的检测头来试试。注意,这里要传入一个 dummy input,模拟一张图片的输入。
import torch
from thop import profile
from models.yolo import DetectionModel # 假设你的 YOLOv11 模型在这里
# 加载模型
model = DetectionModel(cfg='yolov11n.yaml', ch=3, nc=80)
model.eval()
# 构造 dummy 输入:batch=1, channel=3, height=640, width=640
dummy_input = torch.randn(1, 3, 640, 640)
# 计算 FLOPs 和 Params
flops, params = profile(model, inputs=(dummy_input,), verbose=False)
print(f"FLOPs: {flops / 1e9:.2f} GFLOPs")
print(f"Params: {params / 1e6:.2f} M")
输出大概长这样:
FLOPs: 8.23 GFLOPs
Params: 3.21 M
4.3 用 ptflops 做更细致的分析
ptflops 是另一个好用的库。它比 thop 多一个功能:可以按层打印 FLOPs 分布。我个人习惯在分析瓶颈层时用它。
安装:
pip install ptflops
用法类似,但多了一个 print_per_layer_stat=True 的参数:
from ptflops import get_model_complexity_info
model = DetectionModel(cfg='yolov11n.yaml', ch=3, nc=80)
model.eval()
flops, params = get_model_complexity_info(
model,
input_res=(3, 640, 640),
print_per_layer_stat=True, # 打印每一层的 FLOPs
as_strings=True,
verbose=False
)
print(f"FLOPs: {flops}")
print(f"Params: {params}")
输出会包含每一层的详细信息,比如:
Layer | FLOPs (G) | Params (M)
---------------------------------------------------
Conv_0 | 0.12 | 0.04
C2f_1 | 1.45 | 0.52
SPPF_2 | 0.89 | 0.31
Detect_3 | 2.10 | 0.78
... | ... | ...
---------------------------------------------------
Total | 8.23 | 3.21
看到这个表,你就能一眼看出哪个层是「吃计算量的大户」。比如 Detect 头占了 2.1 GFLOPs,那压缩时就可以优先考虑优化它。
4.4 测量 FPS:别只看理论值
FPS 是实际部署时最关键的指标。但测量 FPS 有个坑:你不能只跑一次就下结论。因为 GPU 有预热过程,前几次推理通常比较慢。
我一般这样测:
import time
import torch
def measure_fps(model, input_size=(1, 3, 640, 640), num_iterations=500, warmup=100):
device = next(model.parameters()).device
dummy_input = torch.randn(*input_size).to(device)
# 预热
for _ in range(warmup):
_ = model(dummy_input)
# 正式测量
torch.cuda.synchronize() # 确保 GPU 操作完成
start_time = time.time()
for _ in range(num_iterations):
_ = model(dummy_input)
torch.cuda.synchronize()
end_time = time.time()
avg_time = (end_time - start_time) / num_iterations
fps = 1.0 / avg_time
return fps, avg_time
fps, avg_time = measure_fps(model)
print(f"FPS: {fps:.2f}, 平均每帧耗时: {avg_time*1000:.2f} ms")
torch.cuda.synchronize()。否则你测到的时间可能是「提交任务的时间」,而不是「实际执行完的时间」。我刚开始做部署时吃过这个亏,测出来的 FPS 虚高 30%,上线后直接翻车。
4.5 模型体积:不只是 .pt 文件大小
模型体积通常指保存后的文件大小。但这里有个细节:PyTorch 的 .pt 文件包含的是整个模型结构 + 权重,而 .pth 文件通常只包含权重(state_dict)。
测量方法:
import os
# 保存完整模型
torch.save(model, 'yolov11n_full.pt')
size_full = os.path.getsize('yolov11n_full.pt') / (1024 * 1024)
print(f"完整模型体积: {size_full:.2f} MB")
# 只保存权重
torch.save(model.state_dict(), 'yolov11n_weights.pth')
size_weights = os.path.getsize('yolov11n_weights.pth') / (1024 * 1024)
print(f"权重文件体积: {size_weights:.2f} MB")
一般来说,权重文件比完整模型小 10%-20%。部署时,我们通常只加载权重,然后重新构建模型结构。
4.6 四个指标的综合解读
拿到这四个指标后,怎么判断模型好不好?我列一个简单的对照表:
| 场景 | FLOPs | Params | FPS | 模型体积 |
|---|---|---|---|---|
| 移动端部署 | < 5 G | < 2 M | > 30 | < 10 MB |
| 边缘端(Jetson等) | < 15 G | < 5 M | > 60 | < 25 MB |
| 云端(GPU) | < 50 G | < 20 M | > 200 | < 100 MB |
当然,这只是参考值。具体还要看你的任务和硬件。我记得有一次给客户做安防项目,要求 FPS 必须大于 25,但模型体积可以放宽到 50 MB。那我们就重点优化推理速度,而不是一味地压缩体积。
4.7 本章小结
好了,四个指标和两个工具都讲完了。总结一下:
- FLOPs 和 Params 用
thop或ptflops算,几行代码搞定。 - FPS 要实测,记得预热和同步。
- 模型体积 直接看文件大小,注意区分完整模型和权重文件。
- 四个指标要综合看,别只盯着一个。