Files
2026-07-13 13:33:03 +08:00

838 lines
52 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mnncompress 基于训练的模型压缩工具
mnncompress 是基于主流训练框架TF/Pytorch的模型压缩工具,需要训练数据和对应的训练框架环境) ,提供了包括低秩分解、剪枝、量化等模型压缩算法的实现,并且MNN进一步实现了其中一些需要软件特殊实现的算法(如稀疏计算和量化)的底层计算过程,此工具箱需要配合MNN推理框架来使用。目前提供的能力如下表所示:
| 原始模型格式 | 提供的工具 | 支持的压缩算法类别 |
| --- | --- | --- |
| TensorFlow 1.X | python压缩算法插件(mnncompress | 低秩分解<br>线性过参数化<br>模型剪枝<br>离线量化<br>训练量化<br>训练权值量化 |
| Pytorch | python压缩算法插件(mnncompress | 低秩分解<br>模型剪枝<br>离线量化<br>训练量化<br>训练权值量化 |
各类压缩算法的特点:
| 压缩算法类别 | 特点 | 支持的算法 |
| --- | --- | --- |
| 低秩分解 | 将原始模型进行分解,降低模型计算量、存储量,分解之后的模型仍是一个规整的模型,不需要特殊的软件底层实现;需要进行finetune训练 | Tucker分解,SVD分解 |
| 线性过参数化 | 用于模型从0开始训练的过程中,设计一个小模型,然后使用此算法对模型进行过参数化扩展为一个大模型,提高模型的表达能力,然后基于此大模型进行训练,训练完成之后可以将大模型中的层合并得到原始小模型一样的结构,而且精度和大模型一致 | 改进的 linear over parameterization |
| 模型剪枝 | 将模型的权值矩阵进行稀疏,并进行finetune训练,利用稀疏编码(通道剪枝不需要)压缩模型大小,底层稀疏计算实现加速 | 1x1随机剪枝,1x4block剪枝,通道剪枝 |
| 离线量化 | 将float卷积转换为int8卷积计算,仅需少量校准图片,降低存储量到原始模型的四分之一,降低内存,加速计算(某些模型可能会比float模型慢,因为float的优化方法和int8不同) | EMAKLADMM |
| 训练量化 | 将float卷积转换为int8卷积计算,需要进行训练,可提高量化模型精度,降低存储量到原始模型的四分之一,降低内存,加速计算(某些模型可能会比float模型慢,因为float的优化方法和int8不同) | LSQOAQWAQ |
| 训练权值量化 | 特点同直接权值量化,但通过mnncompress压缩算法插件实现,因而可以提供更低比特的权值量化,以减少更多的存储量,并提高权值量化之后模型的精度,例如4bit量化情况下,模型大小减小到原来的1/8 | 对称量化 |
| FP16 | 将FP32的权重转换成FP16的类型,可在模型转换时一键完成,模型大小减小为原来的1/2,精度基本无损 | - |
### 怎么用?
1. 使用模型转换工具中的压缩功能无需额外数据,只要在模型转换时加对应参数即可,开启动态量化功能后也可以对卷积等计算量大的算子实现量化加速。
2. 使用离线量化可以使大部分算子支持量化加速,这个可以将模型转换为MNN模型之后使用离线量化工具进行压缩,需要少量测试数据,但不需要进行训练finetune,通常运行得很快。
3. 如果离线压缩方法的精度不满足要求,且能够进行训练finetune的话,可以使用**mnncompress**中提供的压缩算法插件将原始模型进行压缩,得到压缩之后的模型和压缩信息描述文件,然后将这两个文件输入到MNN模型转换工具得到最终的MNN压缩模型。需要训练的压缩算法可以提供更好的精度,但需要一定的时间进行finetune训练,此finetune训练需要的时间一般比模型从0开始训练要少很多。
4. 这些算法中有些是可以叠加使用的,以取得更好的压缩效果。推荐使用pipeline(**其中方框中的算法均为可选,叠加压缩算法若精度不好,可选择使用**):
![](../_static/images/tools/mnncompress.jpg)
### 安装
使用pip安装:pip install mnncompress
### 支持的算法
| 算法类别 | 算法名称 | TensorFlow1.X | Pytorch |
| --- | --- | --- | --- |
| 线性过参数化 | linear over parameterization 改进 | ❎ | ✅ |
| 低秩分解 | Tucker分解,SVD分解 | ✅ | ✅ |
| 剪枝 | TaylorFOChannelPruner 结构化通道剪枝 | ✅ | ✅ |
| | SIMDOCPruner 1*4半结构化剪枝 | ✅ | ✅ |
| | SNIPLevelPruner 1*1随机非结构化剪枝 | ✅ | ✅ |
| 训练量化(也可改做离线量化) | LSQ(学习量化参数) | ✅ | ✅ |
| | OAQoverflow aware quantization | ✅ | ❎ |
| | WAQwinograd aware quantization | ❎ | ✅ |
| 权值量化(也可不训练,查看直接权值量化精度) | 对称量化,非对称量化 | ✅ | ✅ |
### mnncompress技术路线
通过mnncompress中的模型压缩插件对原始模型进行压缩,然后得到一个原始的float模型,以及该压缩算法所需要的额外的压缩参数(例如对于量化算法来说,需要各个卷积层的scale和zeropoint等信息)。最后将这个float模型和模型压缩参数文件输入到MNN转换器中,得到最终的MNN压缩模型。
### 关于模型压缩参数文件
在mnncompress的文档中通常命名为“`compress_params.bin`”,是一个protobuf序列化之后的二进制文件。每执行一个压缩算法都需要保存一个对应的模型压缩参数文件,如果有算法上的叠加(例如剪枝+量化的叠加),那么需要将算法对应的API接口中的`append`参数设置为`True`(例如剪枝+量化的叠加,则需要在量化算法保存压缩参数文件时,将对应的接口`append`参数设置为`True`,将量化算法所需要的参数append到剪枝算法的模型压缩文件上去),这样MNN模型转换器才会知道你的模型经过了哪些优化,才能进行正确的转换。
### Benchmark
#### 离线量化
以下模型均来自torchvision预训练模型,采用mnncompress中LSQ的offline模式进行量化,batch size为64,用10个batch,640张训练图片进行量化,表中标*的模型表示量化时跳过了第一层或者前两层不量化,以提升精度。测速均采用**华为P20 Pro 单线程**。
| 模型 | 原始模型指标 | 压缩模型指标 | ARMV7 (ms) | ARMV8 (ms) |
| --- | --- | --- | --- | --- |
| ResNet-18 | 69.758%45M | 69.740%12M | 196.6 --> 208.8 | 187.6 --> 167.0 |
| ResNet-50 | 76.130%98M | 76.030%25M | 606.0 --> 470.5 | 550.8 --> 379.6 |
| SqueezeNet 1.0 | 58.092%4.8M | 57.800%1.3M | 122.1 --> 104.1 | 120.8 --> 88.3 |
| ShuffleNet V2 x1.0 | 69.362%8.7M | 68.616%2.3M | 33.3 --> 33.8 | 29.0 --> 26.0 |
| MobileNet V2 | 71.878%14M | 71.150%3.5M | 69.1 --> 50.2 | 62.2 --> 42.1 |
| *MobileNet V3 Large | 74.042%21M | 73.030%5.4M | 68.6 --> 63.1 | 64.9 --> 50.1 |
| MNASNet 1.0 | 73.456%17M | 72.692%4.3M | 70.6 --> 51.2 | 63.0 --> 42.9 |
| *EfficientNet-B0 | 77.692%21M | 70.486%5.3M | 134.3 --> 113.0 | 128.7 --> 100.4 |
| EfficientNet-B1 | 78.642%30M | 73.546%7.8M | 199.5 --> 166.9 | 185.2 --> 145.9 |
| regnet_x_400mf | 72.834%21.3M | 72.660%8.0M | 83.2 --> 67.6 | 75.1 --> 58.0 |
#### 训练量化
训练量化用来提升量化的精度,其速度和离线量化版本模型一致。建议优先使用离线量化方法,精度不够的情况下再使用训练量化方法。以下数据中的标准模型来自torchvision预训练模型,有些模型如efficientnet的训练成本较大,故未给出训练量化版本。用户自己训练的模型知道训练参数,结果一般可以更好。
| 模型 | 原始模型指标 | 压缩模型指标 | 备注 |
| --- | --- | --- | --- |
| ResNet-18 | 69.758%45M | 69.840%12M | 训练一个epoch即可 |
| MobileNet V2 | 71.878%14M | 71.762%3.5M | github: pytorch/vision reference/classification,复现命令:torchrun --nproc_per_node=8 train.py --model mobilenet_v2 --data-path /mnt/data/ --epochs 100 --lr 0.01 --wd 0.00004 --lr-step-size 30 --lr-gamma 0.1 --pretrained --quant --sync-bn -b 128 |
| *MobileNet V3 Large | 74.042%21M | 73.924%5.4M | 跳过第一层,github: pytorch/vision reference/classification,复现命令:torchrun --nproc_per_node=8 train.py --opt rmsprop --auto-augment imagenet --random-erase 0.2 --model mobilenet_v3_large --data-path /mnt/data/ --epochs 100 --batch-size 128 --lr 0.01 --wd 0.00001 --lr-step-size 30 --lr-gamma 0.1 --pretrained --quant --sync-bn |
#### 剪枝
| 模型 | 原始模型指标 | 压缩模型指标 | ARMV7 (ms) | ARMV8 (ms) |
| --- | --- | --- | --- | --- |
| MobileNet V2 | 71.878%14M | 71.272%2.8M50% SIMDOC稀疏 | 69.1 --> 64.9 | 62.2 --> 58.8 |
| *MobileNet V3 Large | 74.042%21M | 73.568%4.2M 50% SIMDOC稀疏 | 68.6 --> 66.5 | 64.9 --> 62.3 |
#### 低秩分解
| 模型 | 原始模型指标 | 压缩模型指标 | ARMV7 (ms) | ARMV8 (ms) |
| --- | --- | --- | --- | --- |
| MobileNet V2 | 71.878%14M | 69.874%11M | 69.1 --> 60.6 | 62.2 --> 54.6 |
| MobileNet V3 Large | 74.042%21M | 72.748%18M | 68.6 --> 59.7 | 64.9 --> 55.7 |
#### 用户案例
| 模型 | 压缩方案 | 原始模型指标 | 压缩模型指标 |
| --- | --- | --- | --- |
| 神经渲染relight模型 | 低秩分解 | 30.1138M238ms | 29.9858M169ms / 29.6817M116ms |
| 语音识别AOA V3 | EMA训练量化 | cer 18.350.6M | cer 18.2618.7M |
### Pytorch模型压缩工具
#### 线性超参数化工具
- 环境要求
1. python3
2. PyTorch >= 1.2.0
- 总体使用流程
1. 通过本工具得到训练好的float onnx模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的MNN稀疏模型
- 支持的op,使用建议
1. 目前支持普通卷积nn.Conv2dgroup=1,非1*1)的过参数化
2. 该算法应在模型从零开始训练时使用,因为其中的参数会重新进行初始化
3. 该算法的特点是:设计小模型 --> 训练线性过参数化大模型 --> 保存时合并为原始小模型结构。因此你可以设计一个小模型,然后用此算法训练提高小模型精度,最后推理部署仍使用小模型结构
4. 得到的小模型后续仍可叠加剪枝和量化等压缩算法
5. 参考论文:ExpandNets: Linear Over-parameterization to Train Compact Convolutional Networks
- 使用方法:(**注意**modelexpand_modelmerged_model均为深拷贝,有不同的内存空间,它们之间不互相影响)
```python
from mnncompress.pytorch import LOP
# 定义你的模型结构,并初始化
model = Net()
# 对模型进行线性过参数化
lop = LOP(model)
# 扩大8倍,指定模型压缩参数文件,更多参数查看api文档
expand_model = lop.linear_expand_layers(8, "compress_params.bin")
# 使用线性过参数化之后的模型进行训练
train_and_evaluate(expand_model, data)
# 保存模型之前,将过参数化的模型合并,然后保存合并之后的模型 merged_model
merged_model = lop.linear_merge_layers()
```
- 相关API
- LOP
```python
LOP(model) # model为原始模型nn.Module实例
```
方法`linear_expand_layers`
```python
linear_expand_layers(expand_rate, compress_params_file, add_batchnorm=True, add_bypass=True, append=False)
'''
将原始模型进行线性过参数化
参数:
expand_rate: int,线性过参数化的倍数,一般取2,4,8,16等
compress_params_file: str,模型压缩参数文件名
add_batchnorm: bool,是否在线性过参数化时使用BN
add_bypass: bool,是否在线性过参数化时添加bypass(残差连接)
append: bool,是否将线性过参数化算法的参数追加到compress_params_file中去,为False,则将覆盖compress_params_file
返回值:
线性过参数化之后的模型,深拷贝,和原始model不共享内存
'''
```
方法`linear_merge_layers`
```python
linear_merge_layers()
'''
将过参数化之后的模型合并为原始模型结构
参数:
无参数
返回值:
合并之后的模型,深拷贝,和过参数化模型不共享内存
'''
```
#### 低秩分解工具
- 环境要求
1. python3
2. PyTorch >= 1.2.0
- 总体使用流程
1. 通过本工具得到分解之后的float onnx模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的MNN压缩模型
- 支持的op,使用建议
1. 目前支持nn.Conv2dgroup>1不支持)和nn.Linear的分解
2. 建议从已经训练好的float模型开始finetune
3. 分解之后的模型调整好学习率,准确率一般会迅速恢复
- 使用方法
```python
from mnncompress.pytorch import low_rank_decompose
# 加载已经训练好的float模型
model = Net()
model.load_state_dict(torch.load("ori_float_model.pt"))
# 将原始模型分解,然后用分解之后的模型进行finetune训练即可
model = low_rank_decompose(model, "compress_params.bin")
```
- 相关API
- low_rank_decompose
```python
low_rank_decompose(model, compress_params_file, skip_layers=[""], align_channels=8,
in_place=False, tucker_minimal_ratio=0.25,
reserved_singular_value_ratio=0.5, append=False)
'''
参数:
model: nn.Module实例,训练好的float模型
compress_params_file: str, MNN模型压缩参数文件名
skip_layers: List[str], 跳过不分解层的名字,需要是nn.Conv2d或者nn.Linear类型,如["features.conv1",]
align_channels: int, 分解之后进行通道对齐的倍数
in_place: 分解时是否使用原模型内存空间,若为False,则分解之前会对原模型进行深拷贝
tucker_minimal_ratio: float 0~1, 卷积层tucker分解保留的最低通道数比例
reserved_singular_value_ratio: svd分解保留的特征值之和占总特征值之和的比例
append: bool, 是否将低秩分解算法的参数追加到compress_params_file中去,为False,则将覆盖compress_params_file
返回值:
分解之后的模型,nn.Module实例
'''
```
#### 自动剪枝工具
- 环境要求
1. python3
2. PyTorch >= 1.8.0
- 总体使用流程
1. 通过本工具得到训练好的float onnx模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的MNN稀疏模型**(如果直接部署稀疏的float模型,而不叠加量化,那么转换时需要使用MNNConvert的 --weightQuantBits 8 参数进行转换,才会进行稀疏编码,否则模型大小将不变)**
- 支持的op,使用建议
1. 目前支持torch.nn.Conv2dtorch.nn.Linear
2. 优化器超参使用模型收敛阶段的超参,学习率可以在收敛阶段学习率的基础上再调小
- 支持的剪枝算法
1. SNIPLevelPruner:最细粒度的随机剪枝算法,稀疏单位为单个权值。一般需要剪枝比例达到80%以上才有加速,此方法主要进行压缩。
2. SIMDOCPruner:稀疏单位为1*4的剪枝算法,一般需要30%以上剪枝比例才能加速,精度比通道剪枝好。
3. TaylorFOChannelPruner:通道剪枝算法,此算法是将conv2d的一整个filter剪掉,因此此filter对应的输出通道会失效,剪枝完模型是一个规整的模型,不需要后端进行特殊加速,但为了将剪掉的filter从模型中真正去掉,需要使用MNNConverter进行转换,转换过程中会分析剪枝通道之间的依赖关系,并完成最终的转换。
- 使用方法
1. 建议从训练好的float模型进行finetune
2. 创建Pruner对象,对原始模型进行转换,然后用转换之后的模型进行训练
3. 在train过程中,调用pruner的do_pruning方法进行剪枝
4. 导出MNN模型压缩参数文件,示例代码如下(关注其中pruner的用法):
```python
from mnncompress.pytorch.SNIP_level_pruner import SNIPLevelPruner
from mnncompress.pytorch.SIMD_OC_pruner import SIMDOCPruner
Pruner = SIMDOCPruner
# 你的模型代码
class Net(nn.Module):
pass
model = Net()
# 加载已经训练好的模型
model.load_state_dict(torch.load("ori_model.pt"))
model.to(device)
# 将模型进行转换,并使用转换后的模型进行训练,测试
# 更多配置请看API部分
pruner = SIMDOCPruner(model, total_pruning_iterations=1, sparsity=0.6, debug_info=False)
def train(model, data, optimizer, pruner)
model.train()
for d, t in data:
optimizer.zero_grad()
output = model(d)
loss = F.nll_loss(output, t)
loss.backward()
optimizer.step()
# step之后调用pruner的剪枝方法
pruner.do_pruning()
# 获取当前剪枝比例
print(pruner.current_prune_ratios())
for epoch in range(1, epochs + 1):
train(model, data, optimizer, pruner)
test(model, data)
# 保存模型
model.eval()
torch.save(model.state_dict(), "pruned_model.pt")
x = torch.randn(input_shape).to(device)
torch.onnx.export(model, x, "pruned_model.onnx")
# 保存MNN模型压缩参数文件,应在剪枝完毕之后进行,建议在保存模型时调用
pruner.save_compress_params("compress_params.bin", append=False)
```
5. 模型稀疏之后,可以进一步使用PyTorch训练量化工具进行量化,得到稀疏量化模型。也可以直接使用此float稀疏的模型进行推理,需要在MNN模型转换时指定剪枝得到的MNN模型压缩参数文件,并进行权值量化(才会进行稀疏编码):
```bash
mnnconvert --modelFile pruned_model.onnx --MNNModel pruned_model.mnn --framework ONNX --bizCode MNNTest --compressionParamsFile compress_params.bin --weightQuantBits 8
```
- 相关API
- SNIPLevelPruner / SIMDOCPruner / TaylorFOChannelPruner
```python
Pruner(model, sparsity=0.5, total_pruning_iterations=1, config_file=None, debug_info= False,
prune_finetune_iterations=0, max_prune_ratio=0.99,
align_channels=4 # only for 'TaylorFOChannelPruner'
):
'''
参数:
modelModulepytorch模型
sparsityfloat0~1,模型总体剪枝比例
total_pruning_iterationsint,指定多少个iteration将指定比例的连接剪掉
config_filestr,可先运行一次Pruner的do_pruning方法,得到搜索出来的剪枝比例yml文件之后,
然后微调此yml文件中的各层剪枝比例,将修改后的文件名通过此参数传入,可控制各层剪枝比例
debug_info:是否输出debug信息
prune_finetune_iterations: int, 指定剪枝进行一步,finetune多少步,如指定为99,则剪枝一次,
finetune99次,在finetune的99次过程中剪枝mask不变
max_prune_ratio: float, 指定各层最大剪枝比例
align_channels: int, 仅通道剪枝算法TaylorFOChannelPruner有此参数,用于指定剪枝之后对齐的通道数
方法和属性:
do_pruning(result_file = "found_prune_ratios.yml"):进行一次剪枝,在训练optimizer.step之后调用,搜索到的各层剪枝比例将保存到此文件中
current_prune_ratios():返回当前各层剪枝比例
save_compress_params(filename, append=False): 保存MNN模型压缩参数到filename文件
append 表示是否将剪枝参数信息append到传入的文件中,如果为false,则将新建或覆盖该文件,
append=True 表示剪枝之前还做了其他模型压缩操作,filename文件中已存在相关压缩信息
'''
```
#### 权值量化工具
- 环境要求
1. python3
2. PyTorch >= 1.8.0
- 总体使用流程
1. 通过本工具得到训练好的float onnx模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的MNN量化模型
- 支持的op,使用建议
1. 目前支持torch.nn.Conv2dtorch.nn.Linear的量化
2. **仅对权值进行量化,一般8bit权值量化并不需要finetune,直接用MNN的转换工具的“--weightQuantBits”进行转换即可**,但也可使用本工具进行测试精度,或者finetune到更低的bit数,如4bit2bit,推理速度和float一致
3. 可与剪枝工具叠加使用
- 使用方法
1. 建议从训练好的float模型进行finetune
2. 创建WeightQuantizer对象,对原始模型进行转换,然后用转换之后的模型进行训练
3. 保存onnx模型之前,去掉插入的节点
4. 保存onnx模型之后,导出MNN模型压缩参数文件,示例代码如下(关注其中quantizer的用法):
```python
from mnncompress.pytorch import WeightQuantizer
# 你的模型代码
class Net(nn.Module):
pass
model = Net()
# 加载已经训练好的模型,可以是剪枝之后的
model.load_state_dict(torch.load("pruned_model.pt"))
# 将模型进行转换,并使用转换后的模型进行训练,测试
# 更多配置请看API部分
quantizer = WeightQuantizer(model, bits=8)
quant_model = quantizer.convert()
quant_model.to(device)
for epoch in range(1, epochs + 1):
# 每次训练之前加上这一句,准备好量化训练图
quantizer.resume_wq_graph()
train(quant_model, data, optimizer)
test(quant_model, data)
if should_save_model:
# 保存模型之前去掉插入的节点,恢复原模型结构
quantizer.strip_wq_ops()
# 保存模型,注意index,即模型和保存MNN模型压缩参数文件是一一对应的
quant_model.eval()
torch.save(quant_model.state_dict(), "quant_model_index.pt")
x = torch.randn(input_shape).to(device)
torch.onnx.export(quant_model, x, "quant_model_index.onnx")
# 保存MNN模型压缩参数文件,如果进行量化的模型有剪枝,
# 请将剪枝时生成的MNN模型压缩参数文件 "compress_params.bin" 文件在下方传入,并将 append 设置为True
quantizer.save_compress_params("compress_params_index.bin", append=False)
```
5. 训练完之后,使用MNN转换器进行模型转换,提供上面得到的MNN压缩参数文件,并使用`--weightQuantBits`指定量化比特数:
```bash
mnnconvert --modelFile quant_model_index.onnx --MNNModel weight_quant_model.mnn --framework ONNX --bizCode MNNTest --compressionParamsFile compress_params_index.bin --weightQuantBits 8
```
- 相关API
- WeightQuantizer
```python
WeightQuantizer(model, bits = 8, debug_info = False, mode = 'symmetric')
'''
参数:
modelModulepytorch模型
bits:int,指定权值量化比特数
debug_infobool,是否输出debug信息
mode"symmetric"或"asymmetric",采用对称或者非对称量化,目前仅支持对称量化
方法和属性:
convert():返回用于训练量化的模型
strip_wq_ops():去掉插入的权值量化op,恢复原始模型结构
resume_wq_graph()strip_wq_ops保存模型之后,如果还要继续量化训练需加上这一句,以恢复量化训练图,由此支持边训练边保存
save_compress_params(filename, append=False)
用于保存MNN转换时需要用的模型压缩信息
filename:str,MNN模型压缩参数将保存到这个文件名指定的文件中
appendbool,是否将量化参数追加到filename文件中。如果进行量化的模型有剪枝,请将剪枝时通过save_compress_params生成的剪枝信息文件通过此参数传入,并将 append 设置为True
'''
```
#### 训练量化工具
- 环境要求
1. python3
2. PyTorch >= 1.8.0
- 总体使用流程
1. 通过本工具得到训练好的float onnx模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的MNN量化模型
- 支持的op,使用建议
1. 建议优先试验此工具的离线量化功能,精度如果可以则不需要训练量化,速度快,节省时间
2. 目前支持torch.nn.Conv2dtorch.nn.Linear的量化
3. 优化器超参使用模型收敛阶段的超参,学习率可以在收敛阶段学习率的基础上再调小
4. 模型的第一层或者前面几层对精度影响较大,可以尝试跳过不进行量化
5. relu6建议使用nn.ReLU6F.relu6使用opset 9导出到onnx会有问题
- 支持的训练量化算法
1. LSQQuantizer: 改进naive QAT算法,scale会在网络训练中学习更新
- 使用方法
1. 建议从训练好的float模型进行finetune
2. 创建Quantizer对象,对原始模型进行转换,然后用转换之后的模型进行训练
3. 保存onnx模型之前,去掉插入的节点
4. 保存onnx模型之后,导出MNN模型压缩参数文件,示例代码如下(关注其中quantizer的用法):
```python
from mnncompress.pytorch import LSQQuantizer
Quantizer = LSQQuantizer
# 你的模型代码
class Net(nn.Module):
pass
model = Net()
# 加载已经训练好的模型,可以是剪枝之后的
model.load_state_dict(torch.load("pruned_model.pt"))
# 将模型进行转换,并使用转换后的模型进行训练,测试
# retain_sparsity=True表示待量化的float模型是稀疏模型,希望叠加量化训练
# 更多配置请看API部分
# 注意在model还在cpu上,任何分布式还未生效前调用
quantizer = Quantizer(model, retain_sparsity=False)
quant_model = quantizer.convert()
# 单机或分布式,根据你已有的代码来
quant_model.to(device)
for epoch in range(1, epochs + 1):
# 每次训练之前加上这一句,准备好量化训练图
quantizer.resume_qat_graph()
train(quant_model, data, optimizer)
test(quant_model, data)
if should_save_model:
# 保存模型之前去掉插入的节点,恢复原模型结构
quantizer.strip_qat_ops()
# 保存模型,注意index,即模型和保存MNN模型压缩参数文件是一一对应的
quant_model.eval()
torch.save(quant_model.state_dict(), "quant_model_index.pt")
x = torch.randn(input_shape).to(device)
torch.onnx.export(quant_model, x, "quant_model_index.onnx")
# 保存MNN模型压缩参数文件,如果进行量化的模型有剪枝,
# 请将剪枝时生成的MNN模型压缩参数文件 "compress_params.bin" 文件在下方传入,并将 append 设置为True
quantizer.save_compress_params("quant_model_index.onnx", "compress_params_index.bin", append=False)
```
5. 将onnx模型和生成的 "compress_params.bin" 文件输入到MNN转换工具中进行转换,得到最终的MNN量化模型:
```bash
mnnconvert --modelFile quant_model.onnx --MNNModel quant_model.mnn --framework ONNX --bizCode MNNTest --compressionParamsFile compress_params.bin
```
- 作为离线量化工具使用
- 去掉训练过程中的参数更新部分,并将`LSQQuantizer`的`mode`设置为`offline`(查看下方API),其他步骤和上述一致,此时该工具即成为离线量化工具,直接灌入训练数据即可完成离线量化:
```python
# 注释掉训练部分中的这一句即可
# optimizer.step()
```
可以先尝试这个,如果精度可接受,可以节省训练时间。
- 相关API
- LSQQuantizer
```python
LSQQuantizer(model, skip_quant_layers = [], bits = 8, debug_info = False, mode = 'online', retain_sparsity=False)
'''
参数:
modelModulepytorch模型
skip_quant_layers[str,],不进行量化的module的名字,可通过dict(model.named_modules()).keys()获得,如['model.conv1', 'model.conv2'],需要是nn.Conv2d或者nn.Linear
bits:int,指定量化比特数
debug_infobool,是否输出debug信息
mode"online"或"offline",训练量化时选online,离线量化时选offline
retain_sparsity: bool,为True表示待量化的float模型是稀疏模型,希望叠加量化训练
方法和属性:
convert():返回用于训练量化的模型
strip_qat_ops():去掉插入的训练量化op,恢复原始模型结构
resume_qat_graph()strip_qat_ops保存模型之后,如果还要继续量化训练需加上这一句,以恢复量化训练图,由此支持边训练边保存
save_compress_params(onnx_inference_model_file, filename, append=False)
用于保存MNN转换时需要用的模型压缩信息
onnx_inference_model_filestr,去掉插入的训练量化节点之后保存的onnx推理模型名字
filename:str,MNN模型压缩参数将保存到这个文件名指定的文件中
appendbool,是否将量化参数追加到filename文件中。如果进行量化的模型有剪枝,请将剪枝时通过save_compress_params生成的剪枝信息文件通过此参数传入,并将 append 设置为True
'''
```
### Tensorflow 1.x模型压缩工具
#### 低秩分解工具
- 环境要求
1. 支持TensorFlow1.X,在TensorFlow1.X最后一个版本1.15.3上测试通过;TensorFlow2.0暂未测试
- 总体使用流程
1. 通过本工具得到分解训练之后的float pb模型,以及MNN模型压缩参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的压缩模型
- 支持的op,使用建议
1. 支持 **Conv2D, MatMul**两种op
2. 建议从已经训练好的float模型开始finetune
3. 优化器超参使用模型收敛阶段的超参
- 使用方法
```python
from mnncompress.tensorflow import get_op_weight_values, low_rank_decompose
# first, 构建前向网络模型
build_model()
# 新建一个临时session,此session会在分解之后关闭掉
temp_sess = tf.Session()
# 给原模型中的变量用checkpoint赋值
saver = tf.train.Saver()
saver.restore(sess, 'save/model.ckpt')
# 获得图中的权值numpy数值
get_op_weight_values(temp_sess, "model_weights.npy")
temp_sess.close()
# 将模型图进行分解
low_rank_decompose(tf.get_default_graph(), "model_weights.npy", "compress_params.bin")
# 此时图已经分解,构建optimizer,正常训练即可
optimizer = ...
# 恢复模型中未分解层的参数
saver = tf.train.Saver()
saver.restore(sess, 'save/model.ckpt')
# 初始化分解之后的层的参数
initializers = tf.global_variables_initializer()
```
- 相关API
- get_op_weight_values
```python
get_op_weight_values(sess, npy_file_name)
'''
参数:
sess: 临时tf.Session,获取到权值numpy数值之后会被关掉
npy_file_name: str, 模型中权值的数值会被保存到此npy文件中,用于low_rank_decompose分解
返回值:
'''
```
- low_rank_decompose
```python
low_rank_decompose(graph, weight_npy_file, compress_params_file, skip_layers=[""], align_channels=8,
tucker_minimal_ratio=0.25,
reserved_singular_value_ratio=0.5, append=False)
'''
参数:
graph: 模型前向图
weight_npy_file: str, get_op_weight_values函数得到的模型权值npy文件名
compress_params_file: str, MNN模型压缩参数文件名
skip_layers: List[str], 跳过不分解层的名字,需要是nn.Conv2d或者nn.Linear类型,如["features.conv1",]
align_channels: int, 分解之后进行通道对齐的通道数
tucker_minimal_ratio: float 0~1, 卷积层tucker分解保留的最低通道数比例
reserved_singular_value_ratio: svd分解保留的特征值之和占总特征值之和的比例
append: bool, 是否将低秩分解算法的参数追加到compress_params_file中去,为False,则将覆盖compress_params_file
返回值:
无返回值,此时当前graph已经被分解
'''
```
#### 自动剪枝工具
- 环境要求
1. 支持TensorFlow1.X,在TensorFlow1.X最后一个版本1.15.3上测试通过;TensorFlow2.0暂未测试
- 大致算法原理
- 使用权值mask梯度信息,权值mask梯度的绝对值为权值的重要性表示,全局对所有权值的重要性大小进行排序,将指定比例的重要性较小的权值剪掉,参考论文 [SNIP: Single-shot Network Pruning based on Connection Sensitivity](https://openreview.net/pdf?id=B1VZqjAcYX)
- 支持的op,使用建议
1. 目前支持**Conv2D,带可学习参数MatMul需要使用reshape+1*1 Conv2D卷积的组合来实现**
- 支持的剪枝算法
1. SNIPLevelPruner:最细粒度的随机剪枝算法,稀疏单位为单个权值。一般需要剪枝比例达到80%以上才有加速,此方法主要进行压缩。
2. SIMDOCPruner:稀疏单位为1*4的剪枝算法,一般需要30%以上剪枝比例才能加速,精度比通道剪枝好。
3. TaylorFOChannelPruner:通道剪枝算法,以卷积filter为单位进行剪枝,剪枝完仍然是整齐的模型,无须框架底层特殊实现,但需要用MNN转换工具进行转换才能获得最终剪枝模型。
- 使用方法
1. 前提:一个预先训练好的模型checkpoint(也可以从头开始训练稀疏模型)
2. 首先恢复出已经训练好的checkpoint,示例代码:
```python
import tensorflow as tf
from mnncompress.tensorflow import SNIPLevelPruner, SIMDOCPruner, TaylorFOChannelPruner
from mnncompress.tensorflow import SensitivityAnalyzer
# 构图
...
# 恢复checkpoint
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(sess, 'original_model/model.ckpt')
```
3. 获取网络中的(gradientvariablepair
```python
# 模型中用到的优化器,例如,optimizer = tf.train.AdamOptimizer(1e-4)
optimizer = ...
# 获取(gradientvariablepair
gradients_vars = optimizer.compute_gradients(loss)
# 模型更新op
train_step = optimizer.apply_gradients(gradients_vars, global_step=tf.train.get_or_create_global_step())
```
4. 计算模型中各网络连接的敏感度,并保存为npy文件。主要计算过程是喂几个batch数据进网络,计算得到各参数的梯度:
```python
from mnncompress.tensorflow import SensitivityAnalyzer
analyzer = SensitivityAnalyzer(gradients_vars)
for i in range(10):
batch_x, batch_y = mnist.train.next_batch(50)
batch_x = np.reshape(batch_x,(-1, 28, 28,1))
feed_dict={x_PH: batch_x, labels: batch_y}
# 计算各参数的梯度
gv_numpy = sess.run(gradients_vars, feed_dict=feed_dict)
analyzer.analyze(gv_numpy)
analyzer.save_sensitivity_npy("model_sens.npy")
```
5. 构建Pruner,示例代码:
```python
target_sparsity = 0.6
total_prune_iterations = 1
# 可选SNIPLevelPruner, SIMDOCPruner, TaylorFOChannelPruner等算法,更多配置参数参见下方api文档
pruner = SNIPLevelPruner("model_sens.npy", sess.graph, target_sparsity, total_prune_iterations, debug_info=True)
```
6. 正常训练,注意,训练时每次反向更新之后,执行一下剪枝步骤即可**(第6行)(测试时不需要)**,这样保存下来的模型就已经是稀疏的了,示例代码:
```python
for data in training_dataset:
feed_dict = {x: batch_x, labels: batch_y}
sess.run([train_step], feed_dict=feed_dict)
# 反向更新之后,执行剪枝,测试时不需要
pruner.do_pruning(sess)
```
7. 测试过程中保存一下剪枝相关的参数文件:
```python
pruner.save_compress_params("compress_params.bin", sess)
```
8. 训练完成保存frozen pb即可得到最终稀疏pb模型,示例代码:
```python
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=output_names)
f = open("frozen.pb", 'wb')
f.write(output_graph_def.SerializeToString())
```
9. 模型稀疏之后,可以进一步使用TF训练量化工具进行量化,得到稀疏量化模型。也可以直接使用此float稀疏的模型进行推理,需要在MNN模型转换时指定第6步中的bin文件,并进行权值量化(才会进行稀疏编码):
```bash
mnnconvert --modelFile frozen.pb --MNNModel quant_model.mnn --framework TF --bizCode AliNNTest --compressionParams compress_params.bin --weightQuantBits 8
```
- 相关API
- SNIPLevelPruner/SIMDOCPruner/TaylorFOChannelPruner
```python
Pruner(sensitivity_npy_file, graph=None, sparsity=0.5, total_pruning_iterations=1, config_file=None, debug_info= False,
prune_finetune_iterations=0, max_prune_ratio=0.99,
prune_ratio_result_file="算法名_found_prune_ratios.yml",
align_channels=4 # TaylorFOChannelPruner特有参数
)
'''
参数:
sensitivity_npy_file: 敏感度分析得到的npy文件
graph: tensorflow计算图,如为None,则默认使用tf.get_default_graph()来获取计算图
sparsity: float0~1 模型的总体剪枝比例
total_pruning_iterations: int 多少个iteration内将指定比例的参数稀疏掉
config_file: str,可先运行一次Pruner的do_pruning方法,得到搜索出来的剪枝比例yml文件,
然后微调此yml文件中的各层剪枝比例,将修改后的文件名通过此参数传入,可控制各层剪枝比例
debug_info: bool,是否输出debug信息
prune_finetune_iterations: int,剪枝一次,finetune此参数指定步数,然后再剪枝一次。
如果total_pruning_iterations设为10prune_finetune_iterations设为99,则1000个iteration之后才执行完毕剪枝过程
max_prune_ratio: 0~1,控制各层最大的剪枝比例
prune_ratio_result_file: str,指定搜索得到的剪枝比例保存的文件名
align_channels: int,指定通道剪枝之后对齐的通道数
方法和属性:
do_pruning(sess): 训练时每更新一次模型参数之后,执行剪枝步骤,测试时不需要
save_compress_params(filename, sess, append=False): 保存剪枝参数信息到filename文件,sess为维护训练量化图的session,
append 表示是否将剪枝参数信息append到传入的proto文件中,如果为false,则将新建或覆盖该文件,
append=True 表示剪枝之前还做了其他模型压缩操作,filename文件中已存在相关压缩信息
'''
```
#### 权值量化工具
- 环境要求
1. 支持TensorFlow1.X,在TensorFlow1.X最后一个版本1.15.3上测试通过;TensorFlow2.0暂未测试
- 大致算法原理
- **仅对权值进行int8量化,一般权值量化并不需要finetune,直接用MNN的转换工具的“--weightQuantBits”进行转换即可**,但也可使用本工具进行测试精度,或者finetune到更低的bit数;可与剪枝工具叠加使用;
- 支持的op,使用建议
1. 权值量化8bit情况下一般不会损失精度,不需要训练,而模型参数压缩4倍左右,推理速度和float一致
2. 目前支持**Conv2D, DepthwiseConv2dNative,带参数MatMul**
3. 可以结合剪枝一起使用,以获得更大的压缩倍数
- 使用方法
1. 读取已经训练好的float模型的checkpoint,然后插入权值量化节点,**注意添加第22行代码**,示例代码:
```python
from mnncompress.tensorflow.weight_quantizer import WeightQuantizer, strip_wq_ops
# 构建 前向 网络模型
build_model_architecture()
# 在定义反向计算之前构建weight quantizer,向图中插入相关节点
graph = tf.get_default_graph()
weight_quantizer = WeightQuantizer(graph, bits=4, debug_info=True)
# 定义optimizer
opt = ...
# 恢复原模型中的变量
saver = tf.train.Saver()
sess = tf.Session()
# 给原模型中的变量用checkpoint赋值
saver.restore(sess, 'save/model.ckpt')
# 训练之前初始化相关变量,放在restore之后
weight_quantizer.init(sess)
```
2. 正常训练,**注意添加第5行代码**,示例代码:
```python
for data in training_dataset:
feed_dict = {x: batch_x, labels: batch_y}
sess.run([train_step], feed_dict=feed_dict)
weight_quantizer.update(sess)
```
3. 训练完成,去掉插入的权值量化算子**(第2行代码)**,保存frozen pb,示例代码:
```python
# 去掉插入的权值量化算子
strip_wq_ops()
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=output_names)
f = open("frozen.pb", 'wb')
f.write(output_graph_def.SerializeToString())
```
4. 使用MNN转换工具的“--weightQuantBits numBits”选项将frozen pb转换成MNN模型,其中numBits为WeightQuantizer中的bit数,得到的MNN模型的精度和frozen.pb一致
```bash
mnnconvert --modelFile frozen.pb --MNNModel weight_quant_model.mnn --framework TF --bizCode MNNTest --compressionParams compress_params.bin --weightQuantBits 8
```
- 相关API
- WeightQuantizer
```python
WeightQuantizer(graph, bits=8, debug_info=False)
'''
参数:
graph: tensorflow模型图
bits: 权值量化的bit数
debug_info: bool,是否输出debug信息
方法和属性:
init(sess): 训练之前初始化内部用到的相关变量
update(sess): 更新内部状态
save_compress_params(filename, append=False)
用于保存MNN转换时需要用的模型压缩信息
filename:str,MNN模型压缩参数将保存到这个文件名指定的文件中
appendbool,是否将量化参数追加到filename文件中。如果进行量化的模型有剪枝,请将剪枝时通过save_compress_params生成的剪枝信息文件通过此参数传入,并将 append 设置为True
'''
```
- strip_wq_ops()
```python
'''
去掉权值量化相关算子
'''
```
#### 训练量化工具
- 环境要求
1. 支持TensorFlow1.X,在TensorFlow1.X最后一个版本1.15.3上测试通过;TensorFlow2.0暂未测试
- 总体使用流程
1. 通过本工具得到训练好的float pb模型,以及量化参数文件
2. 通过MNN转换工具,输入这两个文件,得到最终的量化模型
- 支持的op,使用建议
1. 建议从训练好的float模型进行finetune
2. 目前支持 **Conv2D, DepthwiseConv2dNative**,带参数**MatMulEMALSQ量化算法支持)**
3. 使用OAQ量化算法需要量化矩阵乘 **MatMul** ,请将其利用 **reshape** 和 **Conv2D1 * 1 卷积) **组合实现
4. 优化器超参使用模型收敛阶段的超参,学习率可以适当调节一下
5. 模型的第一层或者前面几层对精度影响较大,可以尝试跳过不进行量化
- 支持的训练量化算法
1. EMAQuantizer: naive QAT算法,scale采用滑动平均更新
2. LSQQuantizer: 改进QAT算法,scale会在网络训练中学习更新
3. OAQQuantizer: 改进LSQ算法,int8计算中累加到int16而不是通常的累加到int32,在低端手机上可以获得比普通量化30%~50%的加速,低端设备上加速更明显
- 使用方法
1. 搭建好网络之后,在session运行之前,构造 **Quantizer** 对象,指定相关配置项,构造此对象的同时会向前面搭建好的网络中插入fake quant节点,用于实现训练量化功能,示例代码:
```python
from mnncompress.tensorflow import EMAQuantizer, strip_MNN_QAT_ops, get_qat_state_placeholder, MNN_QAT_variables
from mnncompress.tensorflow import LSQQuantizer, strip_MNN_QAT_ops, get_qat_state_placeholder, MNN_QAT_variables
from mnncompress.tensorflow import OAQQuantizer, strip_MNN_QAT_ops, get_qat_state_placeholder, MNN_QAT_variables
QATQuantizer = EMAQuantizer # 也可以改为使用LSQQuantizer或者OAQQuantizer
# first, 构建前向网络模型
build_model()
# second, 插入fake quant节点,注意此句位置应在构建optimizer之前
# 更多配置详见API部分
quantizer = QATQuantizer(tf.get_default_graph())
# 构建optimizer,反向网络
opt = ...
# 只恢复原模型中的变量
saver = tf.train.Saver(var_list=[v for v in tf.all_variables() if v not in MNN_QAT_variables()])
sess = tf.Session()
# 初始化所有变量,包括MNN训练量化中用到的变量
sess.run(tf.global_variables_initializer())
# 给原模型中的变量用checkpoint赋值
saver.restore(sess, 'save/model.ckpt')
# 注意:该saver只维护了原来的模型的变量,要保存插入fake quant的模型,需要新建一个saver
new_saver = tf.train.Saver()
```
2. 正常训练,注意,训练和测试过程中需要给quantizer内部的 **MNN_QAT_is_training** 这个 **placeholder** 赋值,训练时赋值True,测试时赋值False;**注意添加第1行、第7行代码:**
```python
quantizer.init(sess)
quant_state = quantizer.is_training # 或者 quant_state = get_qat_state_placeholder()
for data in dataset:
feed_dict = {x: batch_x, labels: batch_y, quant_state: True}
sess.run(train_step, feed_dict=feed_dict)
quantizer.update(sess)
```
3. 测试过程中保存训练得到的量化参数文件,示例代码:
```python
# 如果先进行了剪枝训练,那么需要将剪枝中得到的 "compress_params.bin" 文件在下方传入,并设置 append=True
quantizer.save_compress_params("compress_params.bin", sess, append=False)
```
4. 训练完之后,我们得到了一些checkpoint文件和对应的量化参数文件,现在我们要来得到最终推理用的frozen pb了,示例代码:
```python
from mnncompress.tensorflow import EMAQuantizer, strip_MNN_QAT_ops
# 保存frozen pb之前去掉(绕过)插入的量化节点,传入 strip_MNN_QAT_ops 的graph参数
# 也可以是从checkpoint读取出来的graph
strip_MNN_QAT_ops(sess.graph)
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=output_names)
f = open("frozen.pb", 'wb')
f.write(output_graph_def.SerializeToString())
```
5. 使用MNN模型转换工具,输入frozen pb和量化参数文件,得到最终的量化模型,示例:
```shell
mnnconvert --modelFile frozen.pb --MNNModel quant_model.mnn --framework TF --bizCode AliNNTest --compressionParamsFile compress_params.bin
```
- 测试训练量化结果需注意的地方
1. **如果你是训练和测试分开两个文件**,**测试时用python代码重新构图**,在构图时给bn的is_training又直接赋值为**python bool False**,然后加载训练的checkpoint进行测试,那你重新构建的图里面是没有训练量化节点的,此时需要在你构图之后,创建session之前,恢复变量之前,你再新建一个和训练时候一样的Quantizer对象(复制粘贴),向测试图中插入训练量化节点即可,然后根据训练时候保存的checkpoint恢复训练得到的变量,就可以了,当然测试的时候也需要给 quant_state 赋值为False,即可。
2. **另外注意,有的使用者在训练和测试的时候给模型加了不同的scope,这时训练的到的compress_params.bin就不能用了,因为tensor名字对不上,此时,只需要在测试之后转pb之前加入下面两句就可以了:**
```python
quantizer.save_compress_params("compress_params.bin", sess)
strip_MNN_QAT_ops(sess.graph)
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=output_names)
f = open("frozen.pb", 'wb')
f.write(output_graph_def.SerializeToString())
```
- 相关API
- EMAQuantizer/OAQQuantizer/LSQQuantizer
```python
Quantizer(graph=None, is_training = None, skip_quant_layers = [], skip_quant_flag = [], bits = 8,
min_clamp_value = 0, # 此参数仅OAQQuantizer有
debug_info = False, retain_sparsity=False)
'''
参数:
graph: 需要进行训练量化的模型图
is_training: None or Python bool,为None时,Quantizer的is_training属性是一个placeholder,用于控制量化状态,需要在feed_dict中额外传值;为True或False时,则和用户代码中的训练或测试flag一样,is_training是一个constant,不需要在feed_dict中额外赋值
skip_quant_layers: [str], 指定图中跳过量化的op的名字
skip_quant_flag: [str],跳过op名字中含有指定字符串的op的量化,例如想要跳过模型中整个decoder1的量化,这些op的名字中都含有"decoder1",那么在此参数中指定["decoder1"]即可
bits: int, 指定量化比特数,目前仅支持8bit量化
min_clamp_value: int, OAQQuantizer特有参数,用于指定最小的量化范围,例如最低量化为6bit,则此数值设置为2^(6-1) - 1 = 31
debug_info: bool,是否输出debug信息
retain_sparsity: bool, 是否保持量化原始float模型的稀疏结构不变,剪枝之后叠加量化时,需要将此参数设为True
'''
# 为获得相关层的名字,我们提供了一个帮助函数:
import mnncompress.tensorflow.helpers as helper
helper.get_op_name_of_type(graph, ['Conv2D', 'DepthwiseConv2dNative', 'MatMul'])
'''
方法和属性:
is_training: property,返回Quantizer内部的 MNN_QAT_is_training 这个 placeholder
strip_qat_ops(): 保存froze pb前去掉(绕过)插入的训练量化节点
save_compress_params(filename, sess, append=False): 保存量化参数信息到filename文件,sess为维护训练量化图的session,
append 表示是否将量化参数信息append到传入的proto文件中,如果为false,则将新建或覆盖该文件,
append=True 表示量化之前还做了其他模型压缩操作,如剪枝,filename文件中已存在相关压缩信息
init(sess): 初始化Quantizer内部信息
update(sess): 更新内部需要的信息
'''
```
- strip_MNN_QAT_ops
```python
strip_MNN_QAT_ops(graph)
'''
此函数用于保存froze pb前去掉(绕过)插入的训练量化节点
如果训练和测试分开来做,意味着测试时是读取训练得到的checkpoint然后进行测试的
此时图中已经有了训练量化的fake quant节点,但无法使用Quantizer维护的原图信息进行
保存froze pb前去掉(绕过)插入的训练量化节点 的功能
此时可以使用该函数来达到去掉(绕过)fake quant节点的目的
'''
```
- get_qat_state_placeholder
```python
get_qat_state_placeholder(graph=None)
# 用于在无quantizer对象的情况下获取MNN训练量化工具内部标志训练或者测试的placeholder
```
- MNN_QAT_variables
```python
MNN_QAT_variables()
# 用于获取MNN训练量化特有的量化variable集合
```
#### Hooks
TensorFlow推荐使用[tf.train.MonitoredSession](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/train/MonitoredSession)[tf.train.MonitoredTrainingSession](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/train/MonitoredTrainingSession)进行训练,并使用[tf.train.SessionRunHook](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/train/SessionRunHook)进行训练流程控制。为方便此类用户使用,我们提供了相关压缩工具的hook,初始化hook之后,即可使用。
这些hook的构造方法和使用的压缩算法文档中一致,在此不再详述。
注意,有些hook需要在构造反向图之前创建,例如量化的hook。目前提供的hook有:
1. SNIPLevelPrunerHook
2. SIMDOCPrunerHook
3. TaylorFOChannelPrunerHook
4. LowRankDecompositionHook
5. EMAQuantizerHook
6. OAQQuantizerHook
7. LSQQuantizerHook