4. 模型复杂度度量标准:FLOPs、Params、FPS、模型体积(MB)的计算与测量方法

做模型压缩和知识蒸馏,你得先知道你的模型到底有多「重」。我见过不少同学一上来就调参、剪枝,结果连自己模型有多少参数、跑一帧要多久都说不清楚。这就像你要减肥,却连体重秤都不上——那你怎么知道效果呢?

这一章,我们就来聊聊衡量模型复杂度的四个核心指标:FLOPs、Params、FPS 和 模型体积(MB)。我会带着你用 thop 和 ptflops 这两个工具,亲手把 YOLOv11 的「体检报告」打出来。

核心观点: 这四个指标不是孤立的。Params 决定存储,FLOPs 决定理论计算量,FPS 决定实际速度,模型体积决定部署成本。做蒸馏和压缩时,你得盯着它们一起看。
模型复杂度四维评估框架 YOLOv11 模型 FLOPs Params FPS 模型体积 理论计算量 vs 实际速度:FLOPs 低不一定 FPS 高,还要看内存带宽和算子效率

4.1 四个指标,各管一摊

先搞清楚每个指标到底在衡量什么。别搞混了,我见过有人把 FLOPs 和 FPS 当成一回事,那可就闹笑话了。

指标 全称 衡量什么 单位 越低越好?
FLOPs Floating Point Operations 模型执行一次前向传播所需的浮点运算次数 GFLOPs(10⁹) 通常越低越好
Params Parameters 模型中可训练参数的总数 M(百万) 通常越少越好
FPS Frames Per Second 模型每秒能处理的图像帧数 fps 越高越好
模型体积 Model Size 模型文件在磁盘上占用的空间 MB 通常越小越好
我的经验: 做蒸馏时,我一般先看 Params 和 FLOPs 有没有降下来。如果这两个没变,那蒸馏可能只是「表面功夫」。真正有效的蒸馏,学生模型的 Params 应该明显少于老师。

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
注意: thop 统计的 FLOPs 是理论值,它假设所有操作都是理想状态。实际运行时,因为内存访问、算子调度等原因,真实耗时可能会差很多。我曾经在某个模型上,thop 报 5 GFLOPs,但实际跑起来比另一个 8 GFLOPs 的模型还慢——原因就是那个模型用了太多内存不连续的操作。

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,那压缩时就可以优先考虑优化它。

避坑指南: 我曾经用 ptflops 分析一个加了注意力机制的模型,发现某个 Attention 层 FLOPs 特别高。仔细一看,是因为输入分辨率太大,导致 QKV 矩阵乘法计算量爆炸。后来我把那个层的输入做了下采样,FLOPs 直接降了 40%。

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")
重要: 测量 FPS 时,一定要用 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%。部署时,我们通常只加载权重,然后重新构建模型结构。

关键点: 模型体积和 Params 是强相关的。一个 3.2M 参数的模型,如果用 float32 存储,理论体积是 3.2M × 4 bytes = 12.8 MB。如果实际文件比这个大很多,说明模型里可能包含了优化器状态、缓存等额外信息。

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 要实测,记得预热和同步。
  • 模型体积 直接看文件大小,注意区分完整模型和权重文件。
  • 四个指标要综合看,别只盯着一个。