PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
环境说明
- Ubuntu 18.04
- MLU270 加速卡一张
- 寒武纪Pytorch-Docker移植环境
前言
阅读本文前,请务必须知以下前置文章概念:
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(一)— 基本概念及相关介绍》 ( https://blog.csdn.net/u011728480/article/details/121194076 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(二)— 模型移植-环境搭建》 ( https://blog.csdn.net/u011728480/article/details/121320982 )
经过了前面两篇文章的介绍,我们也对寒武纪加速平台有了一个朴实的了解。为了加深我们对寒武纪平台的理解,我这里将会使用一个分割网络的实例来展示寒武纪平台整个模型移植和部署过程。
若文中引用部分存在侵权,请及时联系我删除。
实例基本介绍
这里对这个简单的分割网络做一个简介,这里训练使用的是CamVid数据集。输入是1*3*480*480。输出是480*480。
这里最终的效果就是分割出输入图片里面的汽车。最终网络效果测试如下图:
![rep_img](http://sky-x.gitee.io/image-bed0/blogs/blog_idx_113/seg_result.png)
这个时候,我们也得到了一个可以用于移植和测试的pth模型文件。
移植模型基本步骤
其实Pytorch的模型移植还是比较简单的,按照一定流程进行测试即可。我总结的基本流程如下:
- 在docker里面,跑cpu版本的模型推理代码。
- 在docker里面,跑cpu版本的量化模型生成代码,同时进行量化模型的测试。
- 在docker里面,将量化模型转换为离线模型。
在Docker里运行cpu推理代码
至今为止,根据寒武纪官方文档描述,现在的docker环境里面存在的是pytorch1.3环境,这个可能和主流模型支持的pytorch 1.7+有差异。所以,为了后续工作的顺利展开,我们不要一上来就开始量化模型,先保证模型能够在pytorch 1.3环境能够正常工作。
当我们训练好模型后,得到pth文件,然后在训练环境里面还会做一个测试pth文件的脚本,判断模型的效果。同理,我们应该将此测试脚本放到移植环境里面再跑一次,一般来说都会多多少少出点问题。
至今为止,我们遇到过两大类问题,一类为pytorch1.3某些算子不支持,可以更换为其他类似算子,或者自己实现这个算子。第二类为一些版本问题,比如模型保存的格式在pytorch1.6后使用的是zip格式(详情见torch.save api说明注释里面),旧版本要加载模型,需要使用_use_new_zipfile_serialization=False重新存储一下模型文件。
一般来说,大致的模型转换代码如下:
# 存在一个模型test.pth(zip格式)
# 存在一个获取的模型网络结构类:TestModel
import torch
model = TestModel()
state_dict = torch.load('test.pth', map_location=torch.device('cpu'))
model.load_state_dict(state_dict, strict=True)
torch.save(model, 'new_test.pth', _use_new_zipfile_serialization=False)
# 得到了旧版本的pth文件。方便pytorch 1.6以下进行加载
在Docker里处理量化模型
这里有两个步骤,首先是使用寒武纪的pytorch接口生成量化模型,然后对量化模型进行测试。注意,这里生成的量化模型有两种,一种是INT8,一种是INT16,具体怎么选择,根据实际情况。一般来说,分类、分割算法可以尝试直接使用INT8,目标检测需要测试再下结论。此外INT8由于运算量的减少,也意味着推理速度的提升。如果不特殊说明,后续默认采用的是INT8模式。
此外,还需要说明的是,量化一般是量化卷积、全连接等这些参数量较大的层,其他的模型参数依旧是FP16或者FP32存在。
首先生成量化模型:
# 存在一个模型new_test.pth(非zip格式)
# 存在一个获取的模型网络结构类:TestModel
import torch
import torch_mlu.core.mlu_quantize as mlu_quantize
model = TestModel()
state_dict = torch.load('new_test.pth', map_location=torch.device('cpu'))
model.load_state_dict(state_dict, False)
mean=[]
std=[]
# 注意此接口,这里不使用firstconv优化,它的作用是将归一化放到第一层去一起加速做,但是有些模型的前处理是不需要这样做的,具体信息,请参考寒武纪官方文档。
net_quantization = mlu_quantize.quantize_dynamic_mlu(model, {'mean':mean, 'std':std, 'firstconv':False}, dtype='int8', gen_quant=True)
torch.save(net_quantization.state_dict(), 'test_quantization.pth')
# 得到了INT8的量化模型文件test_quantization.pth
然后在量化模型上测试,此步骤的内容是验证模型量化之后,使用寒武纪定制的pytorch量化算子能否正常得到结果:
# 存在一个INT8的量化模型文件test_quantization.pth
# 存在一个获取的模型网络结构类:TestModel
import torch_mlu
import torch_mlu.core.mlu_model as ct
import torch_mlu.core.mlu_quantize as mlu_quantize
model = TestModel()
# step 1
net = mlu_quantize.quantize_dynamic_mlu(model)
# step 2
net.load_state_dict(torch.load('test_quantization.pth'))
# 这里是
input_data=torch.randn((1,3,480,480))
# step 3
net_mlu = net.to(ct.mlu_device())
input_mlu = input_data.to(ct.mlu_device())
# step 4
output=net_mlu(input_mlu)
print(output.cpu())
# output的shape是480*480
如果这里量化之后的推理结果都是准确无误的,那么基本证明了模型移植成功了。其实从这里可以看出,这里的mlu其实就可以类比cuda,就可以大致猜想mlu是什么样的存在了。
在Docker里生成离线模型
在之前的基础上,其实我们很快很方便的就生成了离线模型,不过这里的离线模型同样也有两种,还记得前文说的量化只会量化一些特殊层的参数,而模型中的其他层也是用的FP16或者,FP32,因此,离线模型也具备两种,一种是FP16,一种是FP32。通常来说,一个INT8版本的FP16离线模型是最佳的离线模型。
生成MLU220离线模型:
# 存在一个INT8的量化模型文件test_quantization.pth
# 存在一个获取的模型网络结构类:TestModel
import torch_mlu
import torch_mlu.core.mlu_model as ct
import torch_mlu.core.mlu_quantize as mlu_quantize
model = TestModel()
# step 1
net = mlu_quantize.quantize_dynamic_mlu(model)
# step 2
net.load_state_dict(torch.load('test_quantization.pth'))
#
input_data=torch.randn((1,3,480,480))
# step 3
net_mlu = net.to(ct.mlu_device())
input_mlu = input_data.to(ct.mlu_device())
# 详细查看文档,一般4
core_number = 4
ct.set_core_number(core_number)
ct.set_core_version('MLU220')
# torch_mlu.core.mlu_model.set_input_format(input_format)
ct.save_as_cambricon('test')
net_trace = torch.jit.trace(net_mlu, input_mlu, check_trace=False)
net_trace(input_mlu)
torch_mlu.core.mlu_model.save_as_cambricon("")
# 最终,我们得到了test.cambricon 和 test.cambricon_twins。test.cambricon_twins是离线模型的说明文件,包含输入数据格式通道等信息,也包含输出相关的信息。
到此,我们已经得到了离线模型,也完成了我们模型移植的前面一半的工作。
此外,如果想得到MLU270的离线模型,也可以将set_core_version参数改为MLU270。如果将模型和输入tensor调用half()之后,就会得到fp16的模型格式,具体参考寒武纪官方文档。
.cambricon_twins文件有一个重要的作用就是描述离线模型网络是输入输出格式及通道,毕竟我们的网络一般结果对不上,很大的原因都是预处理和后处理的毛病。下面我会给出.cambricon_twins的两个实例,一个是INT8FP32,一个是INT8FP16。
![rep_img](http://sky-x.gitee.io/image-bed0/blogs/blog_idx_113/offline_info_fp32.png)
![rep_img](http://sky-x.gitee.io/image-bed0/blogs/blog_idx_113/offline_info_fp16.png)
后记
模型的移植流程,基本上都是固定的,一旦熟悉之后,其实就不会改了。
最终一般有6个模型生成,两个INT8和INT16的量化模型。4个离线模型,INT8-FP32,INT8-FP16,INT16-FP32,INT16-FP16。不同的模型对应不同的速度和精度,根据模型实际情况酌情选择。
注意,寒武纪除了支持Pytorch模型移植外,还支持caffe和tensorflow,因此如果需要转换这些模型,请查看对应文档。
参考文献
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(一)— 基本概念及相关介绍》 ( https://blog.csdn.net/u011728480/article/details/121194076 )
- 《寒武纪加速平台(MLU200系列) 摸鱼指南(二)— 模型移植-环境搭建》 ( https://blog.csdn.net/u011728480/article/details/121320982 )
- https://www.cambricon.com/
- https://www.cambricon.com/docs/pytorch/index.html
- 其他相关保密资料。
![qrc_img](http://sky-x.gitee.io/image-bed0/blogs/qrcode_for_wx_official_account.jpg)
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。