5. 模型转换及量化常见问题

开发文档:《 NNToolChain用户开发手册 》、《 量化工具用户开发手册

5.1. 基本使用

5.1.1. pytorch模型转换需要注意的事项?

  • 什么是JIT(torch.jit)?

    答:JIT(Just-In-Time)是一组编译工具,用于弥合PyTorch研究与生产之间的差距。它允许创建可以在不依赖Python解释器的情况下运行的模型,并且可以更积极地进行优化。

  • 支持什么格式的pytorch模型?

    答:Sophon的PyTorch模型编译工具BMNETP只接受PyTorch的JIT模型JIT模型(TorchScript模型)。

  • 如何得到JIT模型?

    答:在已有PyTorch的Python模型(基类为torch.nn.Module)的情况下,通过torch.jit.trace得到;traced_model=torch.jit.trace(python_model, torch.rand(input_shape)),然后再用traced_model.save(‘jit.pt’)保存下来。

  • 为什么不能使用torch.jit.script得到JIT模型?

    答:BMNETP暂时不支持带有控制流操作(如if语句或循环)的JIT模型,但torch.jit.script可以产生这类模型,而torch.jit.trace却不可以,仅跟踪和记录张量上的操作,不会记录任何控制流操作。

  • 为什么不能是GPU模型?

    答:BMNETP的编译过程不支持。

  • 如何将GPU模型转成CPU模型?

    答:在加载PyTorch的Python模型时,使用map_location参数:torch.load(python_model, map_location = ‘cpu’)。

5.1.2. paddle模型*.pdmodel*.pdpot如何转换为bmodel?

  • 静态图保存*.pdmodel和*.pdpot 与*.pdmodel和*.pdiparams的区别?

    答:其主要差别在于保存结果的应用场景:.pdmodel,.pdparams,.pdopt三种由save接口保存(2.0的paddle.static.save或者1.8的fluid.io.save)。.pdmodel和*.pdiparams由save_inference_model接口保存(2.0的paddle.static.save_inference_model或者1.8的fluid.io.save_inference_model)

    • save接口:

      该接口用于保存训练过程中的模型和参数,一般包括*.pdmodel,.pdparams,.pdopt三个文件。其中*.pdmodel是训练使用的完整模型program描述,区别于推理模型,训练模型program包含完整的网络,包括前向网络,反向网络和优化器,而推理模型program仅包含前向网络,.pdparams是训练网络的参数dict,key为变量名,value为Tensor array数值,.pdopt是训练优化器的参数,结构与*.pdparams一致

    • save_inference_model接口:

      该接口用于保存推理模型和参数,2.0的paddle.static.save_inference_model保存结果为*.pdmodel和*.pdiparams两个文件,其中*.pdmodel为推理使用的模型program描述,.pdiparams为推理用的参数,这里存储格式与.pdparams不同(注意两者后缀差个i),*.pdiparams为二进制Tensor存储格式,不含变量名。1.8的fluid.io.save_inference_model默认保存结果为__model__文件,和以参数名为文件名的多个分散参数文件,格式与2.0一致。

    详情请参考: 模型保存与载入-使用文档-PaddlePaddle深度学习平台

  • 如何生成*.pdmodel和*.pdiparams?

    答:

    _images/paddle_jit_save_load_2.1.png
    • 动态图存储模型结构和参数 根据训练模式不同,分为两种情况:

      1. 动转静训练 + 模型&参数存储

        动转静训练相比直接使用动态图训练具有更好的执行性能,训练完成后,直接将目标Layer传入 paddle.jit.save 存储即可。

      2. 动态图训练 + 模型&参数存储

        动态图模式相比动转静模式更加便于调试,如果仍需要使用动态图直接训练,也可以在动态图训练完成后调用paddle.jit.save 直接存储模型和参数。

    • 静态图推理模型&参数保存

      保存/载入静态图推理模型,可以通过 paddle.static.save/load_inference_model实现。 静态图导出推理模型需要指定导出路径、输入、输出变量以及执行器。save_inference_model 会裁剪Program的冗余部分,并导出两个文件: path_prefix.pdmodel 、path_prefix.pdiparams。 注意事项请参考: 模型保存与载入-使用文档-PaddlePaddle深度学习平台

    • checkpoint(persistable 模型)转换为inference模型保存

      在任务的训练阶段,通常我们会保存一些 checkpoint(persistable 模型),这些只是模型权重文件,不能直接被预测引擎直接加载预测,所以我们通常会在训练完之后,找到合适的 checkpoint 并将其转换为 inference 模型。需要构建训练引擎,之后保存 inference 模型即可。 在paddleclas模型库的 tools/export_model.py 中提供了完整的示例,只需执行下述命令即可完成转换:

      python tools/export_model.py \
          --m=模型名称 \
          --p=persistable 模型路径 \
          --o=model和params保存路径
      

      详情请参考: 分类预测框架 — PaddleClas 文档

  • bmpaddle转换Paddle Paddle模型

    答:

    BMPADDLE是针对Paddle Paddle的模型编译器,可以将模型文件(inference.pdmodel,inference.pdiparams)编译成 BMRuntime 所需的文件。而且在编译的同时,可选择将每一个操作的NPU模型计算结果和CPU的计算结果进行对比,保证正确性。 命令行格式:

    python3 -m bmpaddle [--model=<path>] \
                        [--input_names=<string>] \
                        [--shapes=<string>] \
                        [--descs=<string>] \
                        [--output_names=<string>] \
                        [--net_name=<name>] \
                        [--opt=<value>] \
                        [--dyn=<bool>] \
                        [--outdir=<path>] \
                        [--target=<name>] \
                        [--cmp=<bool>] \
                        [--mode=<string>] \
                        [--enable_profile=<bool>] \
                        [--list_ops]
    

    python模式:

    import bmpaddle
    ## compile fp32 model
    bmpaddle.compile(
       model = "/path/to/model(directory)", ## Necessary
       outdir = "xxx", ## Necessary
       target = "BM1684", ## Necessary
       shapes = [[x,x,x,x],[x,x,x]], ## Necessary
       net_name = "name", ## Necessary
       input_names=["name1","name2"], ## Necessary, when .h5 use None
       output_names=["out_name1","out_name2"], ## Necessary, when .h5 use None
       opt = 2, ## optional, if not set, default equal to 1
       dyn = False, ## optional, if not set, default equal to False
       cmp = True, ## optional, if not set, default equal to True
       enable_profile = True ## optional, if not set, default equal to False
       )
    

注意

  1. –model 参数指定到模型所在文件夹那一级,但要特别注意,PaddlePaddle模型有2种:组合式(combined model)和非复合式(uncombined model);组合式就是*model + 权重,__model__文件夹下有很多文件,每一个文件是一层,这种模型必须用 _model_ ;如果是非组合式,一定要把模型名称修改为*.pdmodel和*.pdiparams

  2. shapes和descs中的变量顺序、名称要和实际模型一致,不能写错。

  3. 对于模型中带nms操作的,并且cmp==true时descs参数必填;对于int32类型,范围不能填重复的值,比如608*608的输入,要填608,609,但生效的就是608;对于float类型,则没有这个限制;对于不填的输入,其取值范围默认都是0-1。

5.1.3. 模型转换失败怎么办?

答:

  1. 检查命令行输入参数有没有错误,这个一般会有打印提示;

  2. 不支持的算子需要使用BMLang或者OKKernel开发,也可以联系FAE来解决;

  3. 如果是pytorch模型,是不是没有做trace?

  4. 使用转换工具编译模型时,设置环境变量export BMCOMPILER_STAT_ERR=1,然后加上–v 4,保存更详细的日志,提供给我方技术人员进一步排查;

  5. 有时转换失败是因为误差比对超过了允许的阈值而导致编译过程中断,目前比对误差阈值设置为误差在0.01之内,但也不排除有些模型有很多的累加或除法操作,由于尾差累计导致超出这个范围;可以加上–cmp False关闭比对,最终到业务层面上验证转换后的模型精度是否符合要求;

  6. 2.7.0以后的sdk可以通过BMCOMPILER_STAT_ERR=1,来看每层的数据相似度,个别超过误差不会中断完整编译过程。

5.1.4. 如何使用BMLang开发自定义的算子?

BMLang是算能科技面向用户推出的针对BM168x TPU的一套高级编程接口库,使得用户可以快速的基于TPU硬件开发自定义算子,甚至整个神经网络。BMLang的基本元素是:张量数据(bmlang::Tensor)和计算操作(bmlang::Operator)。用户需要使用bmlang::Tensor和bmlang::Operation来编写C++代码,然后在程序最后使用bmlang::compile或bmlang::compile_with_check来生成TPU可以运行的二进制文件bmodel,与普通网络编译产生的BMODEL文件一样,依赖于BMRuntime接口载入与执行。

5.1.5. 是否支持模型的在线编译?

答:不支持,也不推荐这样做,因为模型编译的过程是特别耗时的。因此我们采用了离线编译生成BModel,在线推理时直接加载BModel运行的方式。

5.2. fp32模型转换

5.2.1. fp32模型的输出和原始模型输出差异比较大怎么办?

答:

  1. 对于两个模型采用同样的输入,看看输出是否一致,比如输入都是用全部是0.1的矩阵,填充input tensor的内存空间,然后做推理,比较输出数据的差异;

  2. 设置 export BMRT_SAVE_IO_TENSORS=1,运行bmruntime的时候会输出两个文件 input_ref_data.dat.bmrt和output_ref_data.dat.bmrt,用这个和理想的输入输出作对比排查;比较数据时尽量使用二进制比较,不要通过打印的数据比较,因为打印的输入格式不一样,也会导致数据显示不一样。

5.2.2. 使用bmpaddle转换模型时应该如何填写参数?descs 参数是选填还是必填?

答:示例:

python3 -m bmpaddle --model=model/ --input_names="image,im_size" --shapes="[1, 3, 608, 608],[1, 2]" --target="BM1684" --cmp=true  --
descs="[1,int32,608,609]"

注意

  1. –model参数到模型所在文件夹那一级;paddle模型有2种:组合式(combined model)和非复合式(uncombined model);组合式就是__model__ + 权重,__model__文件夹下有很多文件,每一个文件是一层,这种模型名称必须用__model__;如果是非组合式,要用.pdmodel和.pdiparams;

  2. shapes和descs中的变量顺序、名称要和实际模型一致,不能写错;

  3. 关于descs 参数是选填还是必填?对于模型中带nms的目标检测网络,并且cmp==true时descs必填,并且对于多输入时的其他输入(比如paddle检测模型中通常都会有的im_size参数),必须写明。对于没有填的输入,默认会产生0-1的随机数,比如输入图像,可以不填;当类型为int32时,不能填重复的值,故写608,609,但生效的就是608;float类型没这个限制;

  4. paddle-ocr-detection开比对会有误差累计,报错。另外有的模型算子中有很多累加或除法,由于误差累计会导致超出允许的比对误差阈值,转换中断报错;还有的有排序操作,小误差会导致顺序不同。这些都会导致转换中断,可以关闭cmp,不进行数据比对,到业务层面验证转换后模型的精度。

5.3. int8模型量化

5.3.1. int8的输出和fp32模型输出差异比较大怎么办?

答:

  1. 检查前后处理是否有问题,int8网络输入输出一般需要做scale处理,看看是否遗漏?

  2. 通过量化可视化工具分析int8的输出和fp32的输出的精度差异,做个输出曲线对比;通过曲线查找出导致误差较大的层,通过更换该层的量化方式调整结果,找到最优量化方式,若仍然不能满足精度要求,则可将该层设置为float输出,不进行量化。

5.3.2. 关于模型量化工具 calibration_use_pb 指定校准参数的问题?

calibration_use_pb 这个工具和校准相关的参数:

-accuracy_opt
-per_channel
-th_method
  1. 这三个参数是互斥的存在,还是可以组合使用?

    答:可以组合使用,accuracy_opt针对depthwise的卷积,如果没有depthwise可以不选,跟th_method不冲突。

  2. per_channel 这个参数指定成 true 之后,还需要其他的参数配合吗? 比如: first_conv_enable_per_channel

    答:per_channel可以单独使用。

  3. th_method 这个参数可以同时指定多个吗? 比如同时指定:-th_method=SYMKL, -th_method=JSD

    答:th_method一次只能指定一种。

5.3.3. 如何提高模型的量化效率和精度?

答:推荐策略是:

  1. 对于检测和分类模型,使用自动量化工具,量化成功后精度测试;

  2. 对于其他类模型,手动指定量化参数,量化成功后精度测试;

  3. 自动量化失败 或 精度不够的,反馈给我们的技术支持。

5.3.4. 关于量化方法的问题?

问:手动指定量化参数时,需要尝试18种量化方法的组合,并逐个测试精度。是否可以提供相关量化参数的说明及推荐使用方法?我们希望能根据模型结构缩小量化方法的搜索范围。

答:kl和symkl和jsd算法比较相似可能差别不大,bert一般用max。多数网络max表现一般,但是各种算法和网络结构和用户训练结果都有关系,所以先默认kl量化,下降多的话再尝试其他组合可能省些时间。

问:这些量化方法 对量化后模型的性能有影响吗?

答:几种门限算法对性能没影响,accuracyopt会把depthwise卷积用浮点,一般会变慢,perchannel也会变慢,这两个是为了提高精度。

5.3.5. 可以使用已有的量化表(比如 TensorRT 量化后得到的量化表)作为输入 来完成BModel模型的量化吗?

答:目前不支持,主要是我们前端转换成umodel之后网络的layer的名字都对不上了。

5.3.6. YOLOv3的darknet模型先转为caffe模型后再转为fp32bmodel,模型输出和原始模型输出存在偏差?

答:darknet模型转caffe模型过程中可能存在偏差,建议直接用darknet模型转为fp32bmodel。将YOLOv3的darknet模型直接转为bmodel,推理结果没有偏差。

5.3.7. 使用bmnetd编译Darknet出现段错误Unknown error 27620053?

答:编译模型段错误时,检查xxx.cfg文件,如果在windows系统下保存或修改过,由于和linux系统对换行符表示不同,会导致解析出错引起段错误,可以用命令转换:

dos2unix xxx.cfg

5.3.8. 使用SDK2.7.0_20220316 auto_calib工具时报错base_conv_layer.cpp43] Check failed: num_spatial_axes_ == 2 (3 vs. 2) kernel_h & kernel_w can only be used for 2D convolution

_images/tool_chain_autocalib_check_failed.jpg

答:关于使用SDK 2.7.0_20220316自动量化工具时报错的问题:

  1. 使用auto_calib ufw.calib.cali_model 为了兼容之前就版本SDK生成的LMDB文件,要求LMDB必须是 3, 640, 640这种的,而不能是 1 3 640 640

  2. 删除examples/calibration/create_lmdb/convert_imageset.py中 cv_img = np.expand_dims(cv_img,axis=0) 这一句,重新生成lmdb。 新生成lmdb时,请先删除掉原先生成的lmdb那个文件夹,否则无法覆盖原先生成的。刚才您应该是已经有之前生成的文件了,所以修改后没生效。

  3. 如果 auto_calib ufw.calib.cali_model的preprocess参数可以满足模型预处理要求,那么使用时可以直接使用图片集,这个不会涉及LMDB文件数据的维度。

补充说明:使用手动分步方式量化模型时,也要注意,如果使用的是 1 3 640 640 这样的lmdb,那么修改使用ufw.tools.xx_to_umodel 生成的fp32umodel prototxt时,prototxt里要batch_size要设置为 0 (如果在使用ufw.tools.xx_to_umodel时,指定了-D=”${lmdb_dst_dir} 那么程序自动会在生成的protoxt里将batch size设置为0);如果使用的是3 640 640 这样的lmdb,那么fp32umodel prototxt里batch_size要设置为 1。 具体说明,请参考https://doc.sophgo.com/docs/docs_latest_release/calibration-tools/html/module/chapter4.html#using-lmdb

5.3.9. 一键量化会不会遍历KL、MAX这些量化策略,策略的细节有哪些?

答:autocalib会遍历所有量化策略,循环进行量化和测试精度的过程,叠加每次进行多次 iteration 的推理,选优提高量化精度。目前搜索策略有:

_images/tool_chain_autocalib_all_strategies.jpg

5.3.10. 量化策略里的MAX方法是指min-max吗?

答:MAX是用最大值作为量化阈值,不是min-max。