我是靠谱客的博主 体贴酸奶,这篇文章主要介绍Pytorch 1.10.2 下模型量化踩坑前言一、动态量化二、静态量化,现在分享给大家,希望可以做个参考。

文章目录

  • 前言
  • 一、动态量化
    • 1. 动态量化针对哪些?
    • 2.如何使用?
  • 二、静态量化
    • 1. 静态量化针对哪些?
    • 2.如何使用?
    • 3. 你可能会遇到的问题
          • (1)算子不支持
          • (2)模型精度下降很多
          • (3)对于这样的网络,可以单独量化吗?


前言

本文主要记录自己在使用Pytorch==1.10.2下对模型进行量化的过程,以及量化过程中遇到的坑,如果想详细了解,可以去看知乎大佬的详细介绍。

首先介绍本文使用的大概模型结构,如下所示是一个3D CNN头部+4层2D CNN组成的前端模型,然后会输入到GRU中进行序列建模,最后通过一个全连接层获得输出结果。

下面会根据这种网络结果,分别对训练完成后的模型使用静态量化和动态量化。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class model: def __init__(self): self.front3D = nn.sequential( nn.Conv3d(), nn.BatchNorm3d(), nn.ReLU(), nn.MaxPool3d() ) self.front = ResNet18() self.back = nn.GRU() self.fc = nn.Linear() def forward(self, x): ... x = self.front3D(x) ... x = self.front(x) x, _ = self.back(x) x = self.fc(x) return x

一、动态量化

  • 动态量化那是真滴简单

1. 动态量化针对哪些?

复制代码
1
2
3
4
5
- RNN - LSTM - GRU - Linear

2.如何使用?

以实例代码为例

复制代码
1
2
3
4
5
6
# 首先定义模型 model = Model() # 使用动态量化接口进行量化,这里表示会将GRU和Linear量化 model = torch.quantization.quantize_dynamic( model, {nn.GRU, nn.Linear}, dtype=torch.qint8)

到这里就完了,然后可以直接进行推理,打印量化后的模型如下

复制代码
1
2
3
4
5
... (fc): DynamicQuantizedGRU(..., dtype=torch.qint8, ...) (fc): DynamicQuantizedLinear(..., dtype=torch.qint8, ...) ...

二、静态量化

  • 静态量化那是真滴麻烦

1. 静态量化针对哪些?

复制代码
1
2
3
4
5
6
7
- Conv2d, BatchNorm2d - Conv3d, BatchNorm3d - ReLU - Linear - ... 基本上大部分CNN的算子都支持了,但是有例外,后面会提到。

2.如何使用?

以实例代码为例

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
# 首先定义模型 model = Model() # 设置qconfig model.qconfig = torch.quantization.get_default_qconfig('fbgemm') # 融合网络层 listmix = [ ['front3D.0', 'front3D.1', 'front3D.2'], ['front.layer1.0.conv1', 'front.layer1.0.bn1'], ['front.layer1.0.conv2', 'front.layer1.0.bn2', 'front.layer1.0.relu'], ... ]

在这里,对于sequential封装的conv3d, bn3d和relu,直接使用索引即可,网络融合必须以下例子中:
- Convolution, Batch normalization
- Convolution, Batch normalization, Relu
- Convolution, Relu
- Linear, Relu
- Batch normalization, Relu

需要注意的是,如果是单独的ReLU和单独的BN是不能融合,此外单独的BN运行时会报错,后面会提。

复制代码
1
2
3
4
5
6
7
8
9
# 融合 torch.quantization.fuse_modules(model, listmix, inplace=True) # 使用实际样本,让量化模型观察数据分布 torch.quantization.prepare(model, inplace=True) data = torch.randn(1, 44, 44) # 读入的实际数据,这里是举个例子 data = data.unsequence(0) # 需要增加一维 model(data) torch.quantization.convert(model, inplace=True)

这个时候模型就转换好了,但是还需要再模型内添加量化和解量化的模块,分别是QuantStub()和DeQuantStub(),通过QuantStub()后,会从CPU后端变成QuantizeCPU,这样才能跑量化后的一些算子。代码修改如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from torch.quantization import DeQuantStub, QuantStub class model: def __init__(self): self.front3D = nn.sequential( nn.Conv3d(), nn.BatchNorm3d(), nn.ReLU(), nn.MaxPool3d() ) self.front = ResNet18() self.back = nn.GRU() self.fc = nn.Linear() self.quant = QuantStub() self.dequant = DeQuantStub() def forward(self, x): x = self.quant(x) ... x = self.front3D(x) ... x = self.front(x) x = self.dequant(x) # 注意解量化的位置,后端已经被动态量化,所以无法走在QuantizeCPU中 x, _ = self.back(x) x = self.fc(x) return x

注意解量化添加的位置是在后端网络之前,这是因为后端网络已经变成了DynamicQuantizedGRU,无法在QuantizeCPU中跑。
到这里,我们对这个网络的静态量化和动态量化的操作看起来是完成了。

3. 你可能会遇到的问题

(1)算子不支持

e.g. Runtime error: “MaxPool3d” , “add_()”, “mul()_” , “torch.zeros_like()” …QuantizeCPU…

这是由于静态量化不支持上述算子,解决办法之前提过,可以用解量化的思路。我被MaxPool3d恶心了好久,因为是sequential封装好的,所以我想了好久才用下面这种笨方法解决:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
def forward(self, x): x = self.quant1(x) # 模型最开始的量化 for m in self.front3D: if isinstance(m, nn.MaxPool3d): x = self.dequant(x) x = m(x) x = self.quant2(x) # 经过maxpool3d后再量化回来 else: x = m(x)

这里需要注意的是,我使用了两个quant(),这是因为quant()中会保存scale, zero_point等信息,他是将FP32映射到int8的关键信息,所以模型内有多少次量化操作,就需要定义多少个QuantStub(),而DeQuantStub()只需定义一个就好,因为它内部没有储存什么信息。

对于原位操作add_()和mul()_,也可以用同样的思路解决。觉得麻烦也可以用torch.nn.quantized.FloatFunctional(),具体如下:

复制代码
1
2
3
4
5
6
7
8
9
10
class basicblock(nn.Module): def __init__(self, ...): ... self.func = torch.nn.quantized.FloatFunctional() def forward(self, x): ... # out = out + residual out = self.func.add(out, residual) return out

e.g. Runtime error: "nai” , “torch.zeros_like()” …QuantizeCPU…

一些其他的算子,比如说batchnorm类,如果在模型出现单独的BN,而又无法融合,此时同样无法跑在QuantizedCPU上,需要解量化。

(2)模型精度下降很多

对于这个问题就比较玄学了,我认为的解决办法是对网络层逐个逐个的对比。那怎么做呢?
直接将不想量化的层的qconfig设置为None就行,对于本网络

复制代码
1
2
3
model.front.layer1.qconfig = None model.front.layer2.qconfig = None

经过这样的设置后,ResNet()中的layer1和layer2中的算子就没有被量化,所以在跑这些网络之前记得解量化。对于我的网络,消融后得到的结果,挺玄学的。

(3)对于这样的网络,可以单独量化吗?

我的探索是,对于单独动态量化,但是不能单独静态量化。如果对于CNN+RNN类的网络,单独静态量化的话,在observe阶段,会报错,大概是叫

Runtime error: “tuple” has no attribute “numel”…

这是因为RNN会有多个输出,模型无法直接观察,所以在本例中,我是先对模型做动态量化,再对模型做静态量化

最后简单总结一下我遇到的问题以及解决:

  1. 对于不支持的算子、操作,直接解量化绕过,走FP32计算,也就是CPU后端
  2. 如果有多个解量化操作,QuantStub()同样需要多个,而DeQuantStub()只需要定义一个。
  3. 部分网络静态量化
  4. tuple has no attribute numel
  5. 精度下降明显

最后

以上就是体贴酸奶最近收集整理的关于Pytorch 1.10.2 下模型量化踩坑前言一、动态量化二、静态量化的全部内容,更多相关Pytorch内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(61)

评论列表共有 0 条评论

立即
投稿
返回
顶部