YOLOv5中FLOPS计算解析
这里选择的版本是YOLOv5-v5.0,Ultralytics官方代码中计算FLOPS部分的路径在yolov5/utils/torch_utils.py
,代码如下:
from thop import profile
stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32
img = torch.zeros((1, model.yaml.get('ch', 3), stride, stride), device=next(model.parameters()).device) # input
flops = profile(deepcopy(model), inputs=(img,), verbose=False)[0] / 1E9 * 2 # stride GFLOPS
img_size = img_size if isinstance(img_size, list) else [img_size, img_size] # expand if int/float
fs = ', %.1f GFLOPS' % (flops * img_size[0] / stride * img_size[1] / stride) # 640x640 GFLOPS
解释官方FLOPS的计算过程:
第一步-确定输入尺寸:
stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32
这行代码确定了用于计算FLOPS的基准输入尺寸。YOLOv5模型有不同的下采样步长(stride),包括stride=8、stride=16、stride=32,不同的stride分别代表不同大小的特征图,代码取最大步长值和32的较大值作为基准量。
第二步-创建测试输入:
img = torch.zeros((1, model.yaml.get('ch', 3), stride, stride), device=next(model.parameters()).device)
创建一个全零张量作为测试输入:
- 批次大小为1
- 通道数从模型配置文件获取(默认为3,对应RGB图像)
- 宽高都是前面计算的stride值
例如通道数=3,stride=32时,张量大小为[1,3,32,32]
第三步-计算基准FLOPS:
flops = profile(deepcopy(model), inputs=(img,), verbose=False)[0] / 1E9 * 2
- 使用profile函数自动计算处理输入时的所有计算操作
- 乘2是因为大多数深度学习操作涉及乘加运算(multiply-add),每个乘加被计为2次浮点运算
该步计算后得到的是基准FLOPS(inputs为stride的大小)
第四步-计算实际输入尺寸下的FLOPS:
img_size = img_size if isinstance(img_size, list) else [img_size, img_size]
fs = ', %.1f GFLOPS' % (flops * img_size[0] / stride * img_size[1] / stride)
对于实际inputs,根据其与stride的比例进行缩放:
- 将基准FLOPS
flops
乘比例因子(实际宽度/基准宽度)*(实际高度/基准高度)
,得到实际FLOPS
总的来说,官方计算可以理解为使用profile函数自定义了一个全新的FLOPS计算规则,具更好的鲁棒性
直接使用profile函数计算FLOPS
thop库中的profile函数是一个成熟的计算FLOPS函数,可以直接使用进行FLOPS的计算:
# 创建随机输入张量
input_image = torch.randn(1, 3, img_size, img_size).to(device)
# 1: 批次大小为1
# 3: RGB三通道
# img_size: 输入图像的高度和宽度
# 使用thop计算FLOPS和参数量
flops, params = thop.profile(model, inputs=(input_image,), verbose=False)
# 统一单位
fs = '%.2f' % (flops / 1E9) # Convert to GFLOPS
与之前的方法相比,这种方式有以下特点:
- 新方法使用torch.randn创建随机数据,而不是之前使用的torch.zeros,但对FLOPS的计算结果实际上没有影响
- 新方法直接使用指定的img_size,没有考虑stride的影响。这可能带来潜在问题:如果img_size不是stride的整数倍,可能导致特征图尺寸出现小数
(例如,如果img_size=650而最大stride=32,那么650/32=20.3125会导致问题) - 没有乘以2计算实际运算次数
总的来说,直接使用thop计算FLOPS的方式更简洁,但需要注意处理输入尺寸的兼容性问题。
关于实际GFLOPS是否应该考虑*2的问题
在深度学习中,计算FLOPS(浮点运算次数)时,对是否将乘加运算(multiply-add operations,或称MAC)计为1次还是2次运算存在不同的观点。
两种计算方式:
- 将乘加看作一个原子操作(MAC方式):
flops, params = thop.profile(model, inputs=(input_image,), verbose=False)
flops_gflops = flops / 1E9 # 不乘以2,直接转换为GFLOPS
- 将乘法和加法分别计数(FLOPS方式):
flops, params = thop.profile(model, inputs=(input_image,), verbose=False)
flops_gflops = flops / 1E9 * 2 # 乘以2,考虑乘法和加法分别计数
实际上,现代处理器通常将乘加作为一个融合操作(Fused Multiply-Add,FMA)来执行,一个时钟周期内就能完成。因此,从硬件执行的角度来看,不乘以2可能更符合实际情况。
因此,在实际使用时,若要比较不同模型的计算量时,重要的是保持计算方式的一致性:
- 如果你正在与其他使用MAC计数的工作进行比较,就不要乘以2
- 如果你在与分别计数乘法和加法的工作比较,就需要乘以2
这就是为什么在进行模型计算量的比较时,总是需要明确说明FLOPS的具体计算方式,以确保比较的公平性和准确性。
通常情况下,使用MAC的计数方式(不乘以2)更符合硬件的实际执行情况,也更容易与大多数深度学习框架的默认统计方式保持一致。
Reference
【YOLOv5-6.x】模型参数量param及计算量FLOPs解析