操作步骤

示例代码说明

本章节操作步骤相关示例代码包括:

  • 示例A:lyngor_sample/basic/add_argmax.py

  • 示例B:lyngor_sample/tensorflow/inference/simplenet/inference_simplenet.py

  • 示例C:lyngor_sample/quant/inference_resnet50_quant.py

  • 示例D:lyngor_sample/quant/inference_resnet50_custom_quant.py

  • 示例E:lyngor_sample/dynamic_shape/resnet50_dynamic_shape.py

  • 示例F:lyngor_sample/train/finetune_ResNet50_cats_dogs.py

获取示例代码参见 获取Sample

导入Lyngor库

Lyngor库提供计算图相关操作的Python接口。

执行 import lyngor as lyn 命令,导入Lyngor库。

注意

  • 在使用Lyngor接口前,须确认已导入Lyngor库。

  • 如导入操作报错,解决办法参见 导入问题

生成计算图

生成计算图采用2种方式:

  • 构建计算图:适用于初次创建计算图。

  • 导入模型转换成计算图:适用于加载已有计算图。

构建计算图

使用Lyngor接口,依次构建输入节点、输出节点,以及计算图。Lyngor支持构建多输入多输出的计算图。

前提条件:已导入Lyngor库。具体操作参见 导入Lyngor库

  1. 使用 lyn.var() 接口,构建计算图的输入节点。例如, x1x2

    创建后的输入节点将被用于执行推理步骤,具体参见 执行推理

  2. 使用计算公式,或者Lyngor算子,构建计算图的输出节点。例如, (x1 + x2)*(x1 - x2)

    备注

    • 计算公式支持加(+)减(-)乘(*)除(/)运算符。

    • Lyngor算子说明参见《Lyngor算子说明书》。

  3. 使用 lyn.gen_graph() 接口,创建计算图。

    示例:构建一个计算图保存在 graph 变量里。完整代码参见 示例代码说明 的【示例A】。

    #使用lyn.var()接口,构建计算图的输入节点。
    shape = (1,1,10,2)
    x1 = lyn.var("in_x1", shape=shape, dtype="float16")# 第一个输入x1
    x2 = lyn.var("in_x2", shape=shape, dtype="float16")# 第二个输入x2
    
    #使用计算公式,或者Lyngor算子,构建计算图的输出节点。
    y = (x1-x2)*(x1+x2) # 支持+-\*/运算符重载
    z = lyn.math.reduce_max(x1*x2, axis=2)
    
    #使用lyn.gen_graph()接口,创建计算图。
    graph = lyn.gen_graph([x1,x2], [y,z]) # 支持多输入,多输出
    

导入模型转换成计算图

用户已在某个深度学习框架下训练得到了模型,使用Lyngor接口导入已有的模型, 提供模型路径、深度学习框架类型、输入、输出即可得到对应计算图和参数。

前提条件:已导入Lyngor库。具体操作参见 导入Lyngor库

  1. 指定待转换的模型的路径等参数。

    支持输入相对路径或绝对路径。

  2. 调用 DLModel 中的 load() 方法,将指定模型转换为计算图。

  3. 通过调用 model.graphmodel.param 方法获取转换后的计算图,以及部分不在计算图中的权重。例如, graphparam

    备注

    • param为部分不在计算图中的权重,如没有则为None。

    • 模型类型为Pytorch时,转换过程常见问题详见 Pytorch模型推理问题

    注意

    • load() 方法中的 in_typeout_typetranspose_axis 会改变生成的图信息,如 in_typetranspose_axis 两参数同时添加,模型先进行数据类型转换再进行转置操作,运行时输入数据应按照添加完前后处理的图的实际形状和类型来提供,而非导入模型的实际输入形状和类型。

    • 对于Tensorflow/Caffe/ONNX/MXNet框架可使用 load() 方法中的 split 参数传入节点名字的列表对图进行切分操作,将一张图切分成两张图或多张图,节点对应名字可使用可视化工具netron打开后,点击节点,在右侧outputs项目中获取。

    • 对于Keras框架可转换成Tensorflow框架后,使用 load() 方法中的 split 参数对图进行切分。

    • 对于Pytorch框架可转换成ONNX框架后,使用 load() 方法中的 split 参数对图进行切分。

示例:导入指定模型,转换为计算图。完整代码参见 示例代码说明 的【示例B】。

#指定待转换的模型的路径。
pb_file = './simplenet.pb'

#使用model.load()接口,将指定模型转换为计算图。
model = lyn.DLModel()
model.load(pb_file, 'Tensorflow', {'input_1':(1,32,32,3)}, ['dense_1/Softmax'])

#获取转换后的计算图及其权重。
graph = model.graph
param = model.param

量化校准

如需加速模型推理,减少模型的权重体积,则进行量化校准;无需量化校准,则跳过该步骤。

自动量化校准

前提条件

已构建计算图,或已导入模型转换成计算图。

操作说明

  1. 生成校准数据集dataset,并选择权重量化方式wscale和输入量化方式in_scale。

    备注

    • 校准数据集需根据模型前处理进行相应设置。

    • 校准集的形式为列表,每个列表成员类型为Python的字典格式。其中,字典的键为模型的输入名称;值为模型的输入数据。

    • 权重量化方式支持max、max_c、power2、power2_c、percentile、percentile_c六种,量化后如发生精度下降等问题可使用本参数调整量化方式。默认方式percentile_c。

    • 输入量化方式支持kl_divergence、percent两种,默认方式percent。

    • max模式下\(w_{\max} = max(abs(w))\),其中max是计算整个权重的最大值,max_c是逐通道计算最大值。

    • power2模式下\(w_{\max} = 2^{{ceil(\log_{2}}{(max(abs(w))})}\)

    • power2_c是在power2模式的基础上\(w_{\max} = 2^{{ceil(\log_{2}}{(max(abs(w))})}\)。per_channel的形式计算常量的\(w_{\max}\)

  2. 调用量化接口 lyn.qconf() ,并传递校准集,获取量化参数 qconf

示例代码

# 准备校准数据集
import cv2
dataset = []
for home, dirs, files in os.walk('./qimages/'):
for filename in files:
data = cv2.imread("./qimages/"+filename)
data4d = data[np.newaxis, :]
dataset.append({'input_1':data4d.astype(dtype)/255.})

# 获取量化参数
import lyngor as lyn
qconf = lyn.qconf(dataset, in_scale="percent", wscale='percentile_c')

手动量化校准

选择不量化的卷积层

lyn.qconf() 中设置 skip_conv_layers 参数,选择特定卷积层跳过量化流程。

例如:\(skip\_ conv\_ layers = \lbrack 0,1,2,3\rbrack\)表示第1,2,3,4个卷积层不进行量化。

该标号是计算图中中深度优先排序后的卷积顺序。

示例代码:

# 配置量化参数
import lyngor as lyn
qconf = lyn.qconf(dataset, wscale='max', skip_conv_layers=[0,1,2,3])

配置qconfig.json

前提条件

已构建计算图,或已导入模型转换成计算图。

操作说明

  1. 生成量化参数配置文件。

    备注

    配置文件中需包含high_speed_mode,weight_scale,quantized_params。

    • high_speed_mode参数:用于指定是否基于APU特性关闭量化。由于部分卷积层量化后速率可能会降低,设置该参数为true可以关闭这部分量化。

    • weight_scale参数:用于指定权重量化方式,支持max、max_c,power2和power2_c四种。

    • quantized_params参数:包含要量化的卷积层的标号,以及该层输入和权重的量化因子in_scale和w_scale。该标号是计算图中中深度优先排序后的卷积顺序。

    • quantized_params中如果设置w_scale为0,则按weight_scale传入的量化方式对权重进行量化,其中max、power2为per-layer量化,max_c为per-channel量化,具体介绍参考上一小节。

  2. 调用量化接口 lyn.qconf() ,并传递配置文件的路径,获取量化参数 qconf

示例代码

# 获取量化参数
import lyngor as lyn
qconf = lyn.qconf(qfile='./qconfig.json')

配置文件示例

{
   "high_speed_mode": true,
   "weight_scale": "max",
   "quantized_params":{
      "0": {
         "in_scale": 151.0,
         "w_scale": 0.9198472
      }
      "1": {
         "in_scale": 697.2465209,
         "w_scale": 1.3800132
      }
      "2": {
         "in_scale": 1588.21240234,
         "w_scale": 0.90982133
      }
   }
}

包含量化校准参数

s_quantize参数

qconf = lyn.qconf(dataset, wscale='power2_c', s_quantize=True)

开启s_quantize:

  • 反量化流程中,in_scale被提取到权重中进行融合,再对权重采用power2_c方式求取w_scale且反量化流程中只存在w_scale。底层优化中会将反量化的乘法融合进apu的卷积中,提升量化模型的性能。

  • 计算w_scale时候,会侦测权重的分布,当检测到权重分布,0元素占据90%以上,则判断为该层为量化不友好,不进行量化。

前提条件:已获取量化校准参数 qconf

在使用 builder() 编译计算图graph,生成推理引擎时,传入量化配置 qconf

qconf = lyn.qconf(dataset, wscale='power2_c', s_quantize=True)
out_path = offline_builder.build(graph, param, qconf=qconf)

weight_int4_quantize

若要开启权重INT4量化,激活INT8量化,则设置 weight_int4_quantize=True

示例:

qconf = lyn.qconf(dataset, wscale='max_c', weight_int4_quantize =True)

构建推理引擎

用户在使用Lyngor提供的API自行生成计算图时,有可能会同时生成一个携带参数信息的字典, 这个字典也可以单独独立构建。参数字典用于减少计算图的体积,方便计算图展示。

用户在使用第三方框架模型进行计算图转换时,除了计算图外,也会生成一个携带参数信息 的字典,字典用于存放部分不在计算图中的权重。

计算图转化后,如果参数字典不为空,可使用带参数的build方法构建引擎。也有可能参数字 典为空,只生成图,这时可使用不带参数的build方法构建引擎,具体示例如下。

构造无参数引擎

在使用Lyngor算子创建计算图后,使用Lyngor接口生成推理引擎。

前提条件:

  1. 使用 lyn.Builder() 接口创建 builder

  2. 使用 build() 方法编译计算图 graph ,并保存推理引擎。

示例:完整代码参见 示例代码说明 的【示例A】

#使用lyn.Builder()接口创建Builder。
builder = lyn.Builder(target='apu')

#使用builder()方法编译计算图graph,并保存。
out_path = builder.build(graph)

构建带参数引擎

在导入已有网络模型,转换为计算图和参数后,使用Lyngor接口构建带参数推理引擎。

前提条件:

  1. 使用 lyn.Builder() 接口创建Builder。

  2. 使用 builder() 编译计算图 graph ,并保存推理引擎。

示例:完整代码参见 示例代码说明 的【示例B】

#使用lyn.Builder()接口创建Builder。
offline_builder=lyn.Builder(target='apu')

#使用builder()方法编译计算图graph,并保存。
out_path = offline_builder.build(graph, param, out_path='./tmp_net/')

包含量化校准参数

前提条件:已获取量化校准参数 qconf 。具体操作参见 量化校准

在使用 builder() 编译计算图 graph ,生成推理引擎时,传入量化配置 qconf

out_path = offline_builder.build(graph, param, qconf=qconf)

完整代码参见 示例代码说明 的【示例C】

构造多分辨率引擎

前提条件:

  • 模型本身能支持多种不同的分辨率;

  • 模型只有单个输入层和单个输出层。

在使用 builder() 编译计算图graph,生成推理引擎时,传入多分辨率列表 dynamic_shapes

注意

当前版本最多支持3种不同的输入形状。

dyn_shapes = [(1,3,192,192), (1,3,224,224), (1,3,256,256)]
out_path = offline_builder.build(graph, param,
dynamic_shapes=dyn_shapes)

完整代码参见 示例代码说明 的【示例E】

构建训练引擎

前提条件:

  • 已导入Lyngor库。具体操作参见 导入Lyngor库

  • 导入训练所需接口。执行 from lyngor.lynbwd import * 命令,导入训练相关接口。

  1. 指定待训练的模型或者模型路径等参数。

  2. 模型转换。

    gragh = lynbwd_graph()
    gragh.load_pth(model_file, input_dict)
    

    参数说明:

    • model_file:修改全连接层类别数量后新模型文件地址;

    • input_dict:模型输入名字和形状字典,格式:{inputname: inputshape, …}。

  3. 设置学习率。

    【方法1】

    w_names = gragh.get_w_names()
    trained = [fc.weights, fc.bias]
    gragh.set_lr_vec([0.001 if name in trained else 0 for name in w_names])
    

    说明:

    • graph.get_w_names():获取模型权重名,返回权重名列表;

    • trained = [fc.weights, fc.bias, ]:设置需要训练权重的权重名;

    • gragh.set_lr_vec([0.001 if name in trained else 0 for name in w_names]):根据需要训练的权重名设置学习率为0.001,其余权重学习率为0;

    • 当某权重学习率为0时,权重冻结,此时不更新该权重,也不会在训练图中产生梯度更新过程。

    【方法2】

    graph.reset_lr(lr_function)
    

    备注

    lr_function,表示各个权重的学习率,接受两个参数(i, n)。

    【示例1】设置模型中所有权重都以0.001的学习率进行更新:

    graph.reset_lr(lambda i, n: 0.001)
    

    【示例2】模型最后2个权重以0.001的学习率进行更新,其余权重冻结:

    graph.reset_lr(lambda i, n: 0.001 if (n - i <= 2) else 0)
    
  4. 设置损失函数。

    loss_head = sigmoid_CE_head(gt_name, gt_shape)
    

    说明:

    • gt_name(ground truth name):模型输入标签名字;

    • gt_shape(ground truth shape):模型输入标签形状;

    • 该操作为设置损失函数,可选交叉熵损失函数sigmoid_CE_head,以及L2损失函数linear_L2_head。

  5. 构建训练引擎。

    engine = graph.build(loss_head, out_path)
    

    说明:

    • loss_head:上一步中设置的损失函数;

    • out_path:模型编译生成物输出路径,默认为 ./build_module

  6. 模型运行。

    engine.run(in_dict)
    

    说明:

    • in_dict:模型输入名字和形状字典;

    • 格式:{inputname: input, …}。

  7. 模型保存。

    engine.update_pth_weights(model_file, dts_file)
    

    说明:

    • model_file:修改全连接层类别数量后新模型文件地址;

    • dts_file:训练完成后的模型保存位置。

注意

  • 当模型已经构建训练引擎,不需要再次构建时,可使用以下命令直接加载已构建成功的编译生成物,此时可跳过【步骤1】至【步骤5】:

    engine = lynbwd_engine(out_path)
    

    out_path: 模型编译生成物输出路径,默认为”./build_module”。

  • 如需修改预训练模型全连接层类别数量生成新的模型文件进行Fine-tune,可使用以下接口:

    change_pth_fc_out_channels(pretrained, model_file, num_classes)
    
    • pretrained:预训练模型地址;

    • model_file:修改全连接层类别数量后新模型文件地址;

    • num_classes:新模型全连接层类别数量。

完整代码参见 示例代码说明 的【示例F】

备注

目前已支持反向算子如下:

Conv2d、BatchNorm2d、MaxPool2d/AvgPool2d(目前仅支持kernel_size=stride情况)、ReLu、Clip、Add、Transpose、Reshape、Expand_dims、Squeeze、Concatenate、Slice。

注意

当前功能正在开发中,功能尚不完善,未得到完全验证,只支持sampale中例程,如遇到问题请联系售后 技术支持 。当训练过程中遇到未支持算子,需要自行支持。

反序列化引擎

使用Lyngor接口对已序列化的引擎进行反序列化操作,加载已保存的推理引擎,节省重复编译时间。

前提条件:

使用Lyngor的load函数,加载已保存的推理引擎。

r_engine = lyn.load(path=out_path + '/Net_0/', device=0, PDT=False)

备注

  • device参数:用于多设备指定具体设备,单卡用户可直接指定0或不指定具体设备,可选设备可通过 lynxi-smi 命令获取,同一线程中只能将引擎load到同一Device中,多芯片使用例子可在lyngor_sample中获取。

  • PDT参数:用于指定是否采用数据预先传输(Pre Data Transfer),用于算传并行。

执行推理

前提条件:

  1. 提供推理引擎的输入数据。

  2. 使用 run() 方法执行推理引擎。其中为支持多输入,输入参数为输入名称和输入数据的字典。

  3. 使用 get_output() 方法获取推理结果。

    示例:完整代码参见 示例代码说明 的 【示例A】。

    import numpy as np
    
    # 提供推理引擎的输入数据。
    shape = (10,2)
    vx1 = np.random.random(shape).astype(np.float16)
    vx2 = np.random.random(shape).astype(np.float16)
    
    # 使用run()方法执行推理引擎。
    r_engine.run(in_x1=vx1, in_x2=vx2)
    result = r_engine.get_output()
    
    # 使用get_output()方法获取推理结果。
    print(result[0])
    print(result[1])
    print(result[0].shape)
    print(result[1].shape)
    

多分辨率推理:

采用多分辨率方式构建的引擎允许用户在推理时的输入张量形状具有一定灵活度,输入形状可以是构建引擎时传入的dynamic_shapes列表中任意一个。此外,调用的代码接口和单分辨率推理没有任何形式上的区别。

前提条件:构建引擎时启用了多分辨率参数dynamic_shapes。具体操作参见 构造多分辨率引擎

示例:完整代码参见 示例代码说明 的【示例E】

# 这里使用的输入形状必须是构建引擎时传入的dynamic_shapes的子集
input_shapes = [(1,3,192,192), (1,3,224,224), (1,3,256,256)]
for shape in input_shapes:

# 生成指定形状的输入数据
data = np.random.random(shape)

# 执行推理
r_engine.run(in=data)
results = r_engine.get_output()

# 打印输入形状以及对应的输出结果
print('shape = ', shape)
print('result = ', result)