msit debug surgeon功能使用指南

简介

surgeon(自动调优)工具使能ONNX模型在昇腾芯片的优化,并提供基于ONNX的改图功能。该工具在原auto-optimizer工具的基础上进行了优化、扩展,建议优先使用。

将surgeon工具优化、修改后的ONNX模型转换为可部署在NPU上的OM模型的操作方法可参见《CANN商用版快速入门》中的“将ONNX模型转换为OM模型”章节或《msit convert功能使用指南》

工具安装

功能介绍

surgeon工具包含两大功能模块--面向昇腾设备的ONNX模型自动改图优化和丰富易用的ONNX改图接口。

  • 工具的自动改图优化功能:基于graph_optimizer图优化组件,集成业界先进的可泛化性图优化策略,构建17个改图知识库,识别模型中对应模式或子图,从而进行自动改图优化。
  • 工具的ONNX改图接口:基于graph_refactor基础改图组件,提供简易的改图接口,提供用户对ONNX图进行“增删改查”等多种改图需求的支持。

1. 图优化工具命令行使用入口

surgeon的图优化功能可以直接通过msit命令行形式启动。启动方式如下:

msit debug surgeon COMMAND [OPTIONS] [REQUIRED]

COMMAND为surgeon工具提供的五个功能选项:listevaluateoptimizeextractconcatenate[OPTIONS][REQUIRED]为可选项和必选项参数,每个子任务下面的可选项和必选项不同。

安全注意事项: 在onnx模型传给图优化工具加载前,用户需要确保onnx模型是安全可信的,若onnx模型来源官方有提供SHA256等校验值,用户必须要进行校验,以确保onnx模型没有被篡改。

建议使用流程:

  1. 执行list命令列举当前支持自动调优的所有知识库。
  2. 执行evaluate命令搜索可以被指定知识库优化的ONNX模型。
  3. 执行optimize命令使用指定的知识库来优化指定的ONNX模型。
  4. (可选)执行extract命令对模型进行子图切分。
  5. (可选)执行concatenate命令对模型进行拼接。

list命令

命令示例如下:

msit debug surgeon list

输出示例如下:

Available knowledges:
   0 KnowledgeConv1d2Conv2d
   1 KnowledgeMergeConsecutiveSlice
   2 KnowledgeTransposeLargeInputConv
   3 KnowledgeMergeConsecutiveConcat
   4 KnowledgeTypeCast
   5 KnowledgeSplitQKVMatmul
   6 KnowledgeSplitLargeKernelConv
   7 KnowledgeResizeModeToNearest
   8 KnowledgeTopkFix
   9 KnowledgeMergeCasts
  10 KnowledgeEmptySliceFix 
  11 KnowledgeDynamicReshape
  12 KnowledgeGatherToSplit
  13 KnowledgeAvgPoolSplit
  14 KnowledgeBNFolding
  15 KnowledgeModifyReflectionPad
  16 KnowledgeBigKernel

列举的知识库按照“序号”+“知识库名称”的格式展示,evaluateoptimize命令通过knowledges参数指定知识库时,可指定知识库序号或名称。关于具体知识库的详细信息,请参见知识库文档

注意:序号是为了方便手动调用存在的,由于知识库可能存在被删除或修改等情况,序号可能会变化。

evaluate命令

命令格式如下:

msit debug surgeon evaluate [OPTIONS] [REQUIRED]

evaluate可简写为eva。

参数说明:

参数名 使用说明 是否必选
--path evaluate的搜索目标,可以是.onnx文件或者包含.onnx文件的文件夹。
-know/--knowledges 知识库列表。可指定知识库名称或序号,以英文逗号“,”分隔。默认启用除修复性质以外的所有知识库。
-r/--recursive 在PATH为文件夹时是否递归搜索。默认关闭。
-v/--verbose 打印更多信息,目前只有搜索进度。默认关闭。
-p/--processes 使用multiprocess并行搜索,指定进程数量。默认1。
-h/--help 工具使用帮助信息。

optimize命令

命令格式如下:

msit debug surgeon optimize [OPTIONS] [REQUIRED]

optimize可简写为opt。

参数说明:

参数名 使用说明 是否必选
-in/--input 输入ONNX待优化模型,必须为.onnx文件。
-of/--output-file 输出ONNX模型名称,用户自定义,必须为.onnx文件。优化完成后在当前目录生成优化后ONNX模型文件。
-know/--knowledges 知识库列表。可指定知识库名称或序号,以英文逗号“,”分隔。默认启用除修复性质以外的所有知识库。
-bk/--big-kernel transform类模型大kernel优化的开关,当开关开启时会启用大kernel优化知识库。关于大kernel优化的介绍请参考示例
-as/--attention-start-node 第一个attention结构的起始节点,与-bk配合使用,当启用大kernel优化开关时,需要提供该参数。
-ae/--attention-end-node 第一个attention结构的结束节点,与-bk配合使用,当启用大kernel优化开关时,需要提供该参数。
-t/--infer-test 当启用这个选项时,通过对比优化前后的推理速度来决定是否使用某知识库进行调优,保证可调优的模型均为正向调优。启用该选项需要安装CANN
-soc/--soc-version 使用的昇腾芯片版本。仅当启用infer-test选项时有意义。
-d/--device NPU设备ID。默认为0。仅当启用infer-test选项时有意义。
--loop 测试推理速度时推理次数。仅当启用infer-test选项时有意义。默认为100。
--threshold 推理速度提升阈值。仅当知识库的优化带来的提升超过这个值时才使用这个知识库,可以为负,负值表示接受负优化。默认为0,即默认只接受推理性能有提升的优化。仅当启用infer-test选项时有意义。
-is/--input-shape 静态Shape图输入形状,ATC转换参数,可以省略。仅当启用infer-test选项时有意义。
--input-shape-range 动态Shape图形状范围,ATC转换参数。仅当启用infer-test选项时有意义。
--dynamic-shape 动态Shape图推理输入形状,benchmark推理时用的参数,含义同benchmark参数--dym-shape。仅当启用infer-test选项时有意义。
-outsize/--output-size 动态Shape图推理输出实际size,benchmark推理时用的参数。含义同benchmark参数--output-size。仅当启用infer-test选项时有意义。
-h/--help 工具使用帮助信息。

extract命令

命令格式如下:

msit debug surgeon extract [OPTIONS] [REQUIRED]

extract 可简写为ext

参数说明:

参数 使用说明 是否必选
-in/--input 输入ONNX待优化模型,必须为.onnx文件。
-of/--output-file 切分后的子图ONNX模型名称,用户自定义,必须为.onnx文件。
-snn/--start-node-names 起始算子名称。可指定多个输入算子名称,节点之间使用","分隔。
-enn/--end-node-names 结束算子名称。可指定多个输出算子名称,节点之间使用","分隔。
-ck/--is-check-subgraph 是否校验子图。启用这个选项时,会校验切分后的子图。
-sis/--subgraph-input-shape 额外参数。可指定截取子图之后的输入shape。多节点的输入shape指定按照以下格式,"input1:n1,c1,h1,w1;input2:n2,c2,h2,w2"。
-sit/--subgraph_input_dtype 额外参数。可指定截取子图之后的输入dtype。多节点的输入dtype指定按照以下格式,"input1:dtype1;input2:dtype2"。
-h/--help 工具使用帮助信息。

使用特别说明:为保证子图切分功能正常使用且不影响推理性能,请勿指定存在父子关系的输入或输出节点作为切分参数。

concatenate命令

命令格式如下:

msit debug surgeon concatenate [OPTIONS]

concatenate 可简写为 concat

参数说明:

参数 使用说明 是否必选
-g1/--graph1 输入的第一个ONNX模型,必须为.onnx文件。
-g2/--graph2 输入的第二个ONNX模型,必须为.onnx文件。
-io/--io-map 拼接时第一幅图的输出与第二幅图的输入的映射关系。例如“g1_out1,g2_in1;g1_out2,g2_in2”
-cgp/--combined-graph-path 拼接之后结构图的名称。默认为以下划线连接的两幅图的名称
-pref/--prefix 添加到第一幅ONNX图的前缀字符串,默认为"pre_"
-h/--help 工具使用帮助信息。

2. 改图工具API使用入口

简介

graph_refactor 是 surgeon 工具的一个基础组件,提供简易的改图接口,解决用户改图难度大、学习成本高的问题。目前支持 onnx 模型的以下改图功能:

安全注意事项: 改图工具使用时传入的onnx文件路径请务必确保文件内容未被篡改,工具会在改图过程中加载直接执行该文件,用户需自行保证该文件内容安全性。

  • 加载和保存模型
  • 查询和修改单个节点信息
  • 新增节点,根据条件插入节点
  • 删除指定节点
  • 选定起始节点和结束节点,切分子图

快速上手

构造用例。

import onnx
import numpy as np
from onnx import helper, TensorProto, numpy_helper

# 输入节点
input1 = helper.make_tensor_value_info('input1', TensorProto.FLOAT, [1, 3, 224, 224])
input2 = helper.make_tensor_value_info('input2', TensorProto.FLOAT, [1, 3, 64, 64])
output = helper.make_tensor_value_info('final_output', TensorProto.FLOAT, [1, 3, 64, 64])

# 添加常量
const1 = numpy_helper.from_array(np.ones((1, 3, 224, 224), dtype=np.float32), name='const1')
const2 = numpy_helper.from_array(np.ones((1, 3, 64, 64), dtype=np.float32), name='const2')

# start_node_name1: Add(input1 + const1)
start_node1 = helper.make_node(
    'Add', ['input1', 'const1'], ['out1'], name='start_node_name1'
)

# start_node_name2: Add(input2 + const2)
start_node2 = helper.make_node(
    'Add', ['input2', 'const2'], ['out2'], name='start_node_name2'
)

# end_node_name1: Mul(out1 * out2)
end_node = helper.make_node(
    'Mul', ['out1', 'out2'], ['final_output'], name='end_node_name1'
)

# 组装图
graph = helper.make_graph(
    [start_node1, start_node2, end_node],
    'DemoGraph',
    inputs=[input1, input2],
    outputs=[output],
    initializer=[const1, const2]
)

# 生成模型
model = helper.make_model(graph, producer_name='demo-model')
onnx.save(model, "layernorm.onnx")


# 输入和输出
input_tensor = helper.make_tensor_value_info('g1_input', TensorProto.FLOAT, [1, 3, 32, 32])
output_tensor = helper.make_tensor_value_info('g1_output', TensorProto.FLOAT, [1, 3, 32, 32])

# 常量
const = numpy_helper.from_array(np.ones((1, 3, 32, 32), dtype=np.float32), name='g1_const')

# 节点
add_node = helper.make_node(
    'Add', ['g1_input', 'g1_const'], ['g1_output'], name='g1_add'
)

# 图
graph = helper.make_graph(
    [add_node],
    'g1_graph',
    inputs=[input_tensor],
    outputs=[output_tensor],
    initializer=[const]
)

# g1.onnx
model = helper.make_model(graph, producer_name='g1-demo')
onnx.save(model, 'g1.onnx')

# g2.onnx
input_tensor = helper.make_tensor_value_info('g2_input', TensorProto.FLOAT, [1, 3, 32, 32])
output_tensor = helper.make_tensor_value_info('g2_output', TensorProto.FLOAT, [1, 3, 32, 32])

relu_node = helper.make_node(
    'Relu', ['g2_input'], ['g2_output'], name='g2_relu'
)

graph = helper.make_graph(
    [relu_node],
    'g2_graph',
    inputs=[input_tensor],
    outputs=[output_tensor]
)

model = helper.make_model(graph, producer_name='g2-demo')
onnx.save(model, 'g2.onnx')

动画演示

以下是一个简单的改图脚本示例,包括加载 -> 修改 -> 保存三个基本步骤:

import numpy as np
from auto_optimizer import OnnxGraph

# 加载 onnx 模型
g = OnnxGraph.parse('layernorm.onnx')

# 增加一个整网输入节点
dummy_input = g.add_input('dummy_input', 'int32', [2, 3, 4])

# 增加一个 add 算子节点和一个 const 常量节点
add = g.add_node('dummy_add', 'Add')
add_ini = g.add_initializer('add_ini', np.array([[2, 3, 4]]))
add.inputs = ['dummy_input', 'add_ini'] # 手动连边
add.outputs = ['add_out']
g.update_map() # 手动连边后需更新连边关系


# 在 add 算子节点前插入一个 argmax 节点
argmax = g.add_node('dummy_ArgMax',
                      'ArgMax',
                      attrs={'axis': 0, 'keepdims': 1, 'select_last_index': 0})
g.insert_node('dummy_add', argmax, mode='before') # 由于 argmax 为单输入单输出节点,可以不手动连边而是使用 insert 函数

# 保存修改好的 onnx 模型
g.save('layernorm_add.onnx', save_as_external_data=False, all_tensors_to_one_file=True)

# 切分子图
g.extract_subgraph(
    ["start_node_name1", "start_node_name2"],
    ["end_node_name1", "end_node_name1"],
    "sub.onnx", 
    input_shape="input1:1,3,224,224;input2:1,3,64,64",
    input_dtype="input1:float16;input2:int8"
)
g.save('layernorm_subgraph.onnx', save_as_external_data=False, all_tensors_to_one_file=True)

# 拼接子图
g1 = OnnxGraph.parse("g1.onnx")
g2 = OnnxGraph.parse("g2.onnx")
combined_graph = OnnxGraph.concat_graph(
  graph1=g1,
  graph2=g2,
  io_map=[("g1_output", "g2_input")]  # 两幅图的映射关系按照实际边的名称指定
)
g.save('layernorm_concat.onnx', save_as_external_data=False, all_tensors_to_one_file=True)

详细使用方法

使用示例

请移步surgeon使用示例

使用示例 使用场景
01_basic_usage 基础示例,介绍surgeon各功能
02_list_command 列举当前支持自动调优的所有知识库
03_evaluate_command 搜索可以被指定知识库优化的ONNX模型
04_optimize_command 使用指定的知识库优化ONNX模型
05_extract_command 对ONNX模型进行子图切分
06_big_kernel_optimize Transformer类模型大kernel优化
07_concatenate_command 对两幅ONNX图进行拼接
08_custom_op 添加自定义算子