searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

onnx计算图优化算子分析

2024-09-18 09:21:21
175
0

简介

这是一个用于神经网络模型推理的跨平台库,支持各种硬件加速器。它通过图优化技术,如操作融合、常量折叠和分布式计算,显著提高了大语言模型的推理效率。

模型推理速度测试

以ResNet50模型为例

Nvidia GPU:

PyTorch 0.11262585956603288 VS ONNX 0.025557354614138602

CPU:

PyTorch 0.04904477499992936 VS ONNX 0.015337402000004658

onnx-simplifier工具使用:python -m onnxsim model_ori.onnx model_sim.onnx

onnx-simplifier算法解读

基本流程

simplify的基本流程如下:

  1. 配置优化器,判断是否跳过某些优化器,选择适当的优化传递机制
  2. 利用onnxruntime推理计算图,得到各个节点的输入输出的张量形状(确保每个节点的输入输出形状已知,避免简化过程中出现形状不匹配。为模型优化提供形状信息,帮助识别哪些节点是冗余的或可优化的。)
  3. 基于参数指定的优化方法调用onnxoptimizer进行ONNX模型的优化(如fuse_bn_into_conv)
  4. 基于get_constant_nodes函数获取常量OP,对ONNX模型的常量OP进行折叠
  5. 将得到的常量OP从图中移除(断开连线),同时将其节点参数构建为其他节点的输入参数,并清理图中的孤立节点
  6. 重复2-5步,反复应用形状推断、优化器和常量折叠,直到简化模型不再变化或达到最大迭代次数
  7. 简化完成后,检查简化后的模型,确保其结构有效

函数接口定义

def simplify(
    model: Union[str, onnx.ModelProto],
    check_n: int = 0,
    perform_optimization: bool = True,
    skip_fuse_bn: bool = False,
    overwrite_input_shapes=None,
    test_input_shapes=None,
    skipped_optimizers: Optional[List[str]] = None,
    skip_constant_folding=False,
    skip_shape_inference=False,
    input_data=None,
    dynamic_input_shape: bool = False,
    custom_lib: Optional[str] = None,
    include_subgraph: bool = False,
    unused_output: Optional[Sequence[str]] = None,
    tensor_size_threshold: str = DEFAULT_TENSOR_SIZE_THRESHOLDHOLD,
    mutable_initializer: bool = False,
    *,
    input_shapes=None,
) -> Tuple[onnx.ModelProto, bool]:
    ”“”
:param model: onnx ModelProto对象或文件路径
:param check_n:简化模型将通过随机输入检查' check_n '次
:param perform_optimization:是否在模型上运行onnx优化器
:param skip_fuse_bn:跳过fuse_bn_into_conv onnx优化器
:param overwrite_input_shapes:如果模型有动态输入形状,用户必须传递一个固定的输入形状
用于生成随机输入和检查等式。
:param test_input_shapes:如果模型有动态输入形状,用户必须传递一个固定的输入形状
用于生成随机输入和检查等式。
:param skipped_optimizers:跳过一些特定的onnx优化器
:param skip_constant_folding:跳过常量折叠
:param skip_shape_inference:跳过形状推断(有时形状推断会崩溃)
:param input_data:提供自定义输入数据,以便在需要时进行检查
:param dynamic_input_shape:已弃用。不再需要了。
:param custom_lib: onnxruntime自定义ops的共享库
:param include_subgraph:简化子图而不仅仅是主图
:param unused_output:将从模型中消除的未使用输出的名称
:param input_shapes:已弃用。请使用' overwrite_input_shapes '和/或' test_input_shapes '代替。
:return:一个元组(简化模型,success(True)或failed(False))
”“”

具体功能分析

OnnxSimplifier是一个用于简化onnx模型的工具,主要是拥有折叠常量(FoldConstant)的功能、自动调度OnnxOptimizer,最为重要而且核心的是FixedPointFn这个简化调度算法。

OnnxOptimizer是一个onnx官方的一个onnx模型优化库,内部包含很多模型简化/优化的功能。用户也可直接通过python/c++/c api执行调用。

FixedPointFn迭代优化函数基本原理:通过两个优化函数,反复迭代优化中得到了最终无法继续优化的最终模型。函数逻辑如下:

while (_max_iters-- > 0) {

if (google::protobuf::util::MessageDifferencer::Equals(y1, y2)){return y2}

y1 = f1(y2);

if (google::protobuf::util::MessageDifferencer::Equals(y1, y2)){return y1}

y2 = f2(y1);

}

直到y1=y2或达到最大迭代次数max_iter跳出循环

优化函数有二:

OptAndShape:FixedPointFn(std::function{InferShapes}, std::function{Optimize})

OptAndShape.f1:_InferShapes形状推导(可选,不使用的时候为Identity)

OptAndShape.f2:OptimizeFixed优化,调用了OnnxOptimizer函数库进行优化

OptAndShape.max_iters:默认是50(可通过ONNXSIM_FIXED_POINT_ITERS设置)

 

OptAndShapeAndFold:FixedPointFn(std::function{OptAndShape}, std::function{FoldConstant})

OptAndShapeAndFold.f1:OptAndShape

OptAndShapeAndFold.f2: FoldConstant函数(可选,不使用的时候为Identity)

FoldConstant具体来说:

  • 基于GetConstantNodes函数得到所有的常量节点和非常量节点;(常量节点需同时判断是官方算子、是确定性的、不是QuantizeLinear(将浮点数张量转换为量化的整数张量)或DequantizeLinear(将量化的张量(即整数张量)还原为浮点数张量)、不含有子图、不会输出超过指定大小的张量、输入为常量)
  • 使用onnxruntime进行一次推理(RunOp:在给定的 ONNX 模型上执行单个操作节点(`NodeProto`),并返回该操作节点的输出结果。),得到常量节点的值,并将常量节点的值及节点名构造成initializer,存入model中
  • 清空model中所有的node
  • 加入非常量节点

这样得到一个新的model,可以表示为常量的节点都转换为initializer

OptAndShapeAndFold.max_iters:默认是50

 

sim_model = OptAndShapeAndFold(model)得到最终的优化模型

 

onnxsim 本身只提供 constant folding/propagation(即消除结果恒为常量的算子)的能力,而图变换(即合并 conv 和 bn 等等)的能力是由 onnxsim 调用 onnx optimizer 的各种 pass 实现的。constant folding 和图变换同时使用时,很多隐藏的优化机会会被挖掘出来,这也是 onnxsim 优化效果出色的原因之一。

onnx-optimizer算法解读

Pass
含义:所有优化项的基类,每个优化项必须拥有唯一的名字,用来注册和管理。
优化类型(PassType):

  • Fuse: 会融合算子的优化项
  • Nop: 会移除无用的算子的优化项
  • Separate:会分开某种形式的算子
  • Immutable:不可变优化
  • Replace: 会进行节点替换的优化项
  • Other: 其他

返回类型(PassAnalysisType):

  • Empty:空分析结果
  • CountBased:基于计数的分析结果

效率(PassEfficiency):

  • Partial:部分有效,不能保证连续两次运行的结果与运行一次相同
  • Complete:完全有效,连续两次运行的结果等于一次运行的结果

优化目标(PassOptimizationType):

  • None:不优化
  • Compute: 针对计算优化
  • Memory: 针对内存优化
  • ComputeMemory: 针对计算和内存优化
  • Stability: 优化稳定性(如log-sum-exp)

节点销毁类型(NodeDestroyType):

  • DestroyZero:不销毁节点。
  • DestroyOne:等同于调用 destroyCurrent() 一次。
  • DestroyTwo:等同于调用 destroyCurrent() 两次。

具体pass实现(共39个):

  • adjust_add:交换add第一个常量输入到第二位置上
  • adjust_slice_and_matmul:调整slice和matmul之间的顺序,以便优化:Y = Matmul(Slice(data, start, end, axes) ,rhs) ==> Y = Slice(Matmul(data, rhs), start, end, axes)
  • eliminate_common_subexpression:消除掉那些属性/输入一致的节点,也就是公共子表达式
  • eliminate_deadend:移除掉output没有连接到其他节点的node
  • eliminate_duplicate_initializer:移除掉重复的initializer(可能导致意外)
  • eliminate_identity:移除掉Identity node
  • eliminate_if_with_const_cond:消除输入条件cond为常量的if算子
  • eliminate_nop_cast:消除那些目标输出数据类型等于输入数据类型的cast算子
  • eliminate_nop_concat:消除输入个数为1的Concat算子
  • eliminate_nop_dropout:消除那些ratio=0.0的dropout算子
  • eliminate_nop_expand:消除expand_dim可以广播到input_dim的Expand算子
  • eliminate_nop_flatten:消除那些对shape无影响的Flatten算子
  • eliminate_nop_monotone_argmax:消除那些正相关的激活函数到argmax函数上,减少计算
  • eliminate_nop_pad:消除pads=0的Pad算子
  • eliminate_nop_reshape:消除掉reshape dim == input_dim的reshape算子
  • eliminate_nop_split:消除输出个数为1、input_dim[axis]=split[0]的Split算子
  • eliminate_nop_transpose:消除掉不起作用的transpose算子
  • eliminate_nop_with_unit:消除掉同0并的And、同1乘的Mul、同0或的Or、同0加的Add、减0的Sub、除1的Div、方1的Pow、 无效的Concat
  • eliminate_shape_gather:融合掉indices=[indices_val,]、前面的节点是Shape的Gather算子
  • eliminate_shape_op:融合掉可以直接获取input_shape的Shape算子
  • eliminate_slice_after_shape:融合掉前面的节点是Shape的Slice算子
  • eliminate_unused_initializer:移除掉不被使用的initializer
  • extract_constant_to_initializer:移除Constant算子,放置到Initializer内
  • fuse_add_bias_into_conv:融合掉前面的节点是Conv2d、且input[1]是常量可配合dim的Add算子
  • fuse_bn_into_conv:融合BN算子到Conv2d上,同时修改Conv2d的权重
  • fuse_concat_into_reshape:融合reshape的shape输入的concat/cast算子,变成constant shape输入
  • fuse_consecutive_concats:融合前面的axis相同的Concat到该Concat节点上
  • fuse_consecutive_log_softmax:融合softmax+log成为LogSoftmax算子
  • fuse_consecutive_reduce_unsqueeze:当前面的Reduce算子的axes=Unsqueeze_axes、keepdims=0时,融合Unsqueeze算子到Reduce算子上
  • fuse_consecutive_slices:融合掉axes没有交集的连续的slice算子,合并为一个Slice
  • fuse_consecutive_squeezes:合并多个连续的Squeeze算子成为一个Squeeze算子
  • fuse_consecutive_transposes:合并多个连续的Transpose算子成为一个Transpose算子
  • fuse_consecutive_unsqueezes:融合连续的Unsqueezes算子
  • fuse_matmul_add_bias_into_gemm:合并MatMul+Add成为一个Gemm算子
  • fuse_pad_into_conv:合并Pad+Conv成为一个Conv算子,Pad操作合并到了Conv上
  • fuse_pad_into_pool:合并Pad+AveragePool/MaxPool成为一个Pool算子
  • fuse_qkv:合并qkv计算的三个matmul为只有一个matmul: A = matmul(X, Q), B = matmul(X, K), C = matmul(X, V) ==> A,B,C = split(matmul(X, concat(Q,K,V)))
  • fuse_transpose_into_gemm:融合前面的Transpose操作反转Gemm的transA/transB参数,从而融合掉transpose算子
  • replace_einsum_with_matmul:满足条件的einsum(爱因斯坦求和约定)变成matmul操作:"bhij,bhjd->bhid"变成matmul; "bhid,bhjd→bhij"变成transpose+matmul操作
0条评论
0 / 1000
c****c
2文章数
0粉丝数
c****c
2 文章 | 0 粉丝
c****c
2文章数
0粉丝数
c****c
2 文章 | 0 粉丝
原创

onnx计算图优化算子分析

2024-09-18 09:21:21
175
0

简介

这是一个用于神经网络模型推理的跨平台库,支持各种硬件加速器。它通过图优化技术,如操作融合、常量折叠和分布式计算,显著提高了大语言模型的推理效率。

模型推理速度测试

以ResNet50模型为例

Nvidia GPU:

PyTorch 0.11262585956603288 VS ONNX 0.025557354614138602

CPU:

PyTorch 0.04904477499992936 VS ONNX 0.015337402000004658

onnx-simplifier工具使用:python -m onnxsim model_ori.onnx model_sim.onnx

onnx-simplifier算法解读

基本流程

simplify的基本流程如下:

  1. 配置优化器,判断是否跳过某些优化器,选择适当的优化传递机制
  2. 利用onnxruntime推理计算图,得到各个节点的输入输出的张量形状(确保每个节点的输入输出形状已知,避免简化过程中出现形状不匹配。为模型优化提供形状信息,帮助识别哪些节点是冗余的或可优化的。)
  3. 基于参数指定的优化方法调用onnxoptimizer进行ONNX模型的优化(如fuse_bn_into_conv)
  4. 基于get_constant_nodes函数获取常量OP,对ONNX模型的常量OP进行折叠
  5. 将得到的常量OP从图中移除(断开连线),同时将其节点参数构建为其他节点的输入参数,并清理图中的孤立节点
  6. 重复2-5步,反复应用形状推断、优化器和常量折叠,直到简化模型不再变化或达到最大迭代次数
  7. 简化完成后,检查简化后的模型,确保其结构有效

函数接口定义

def simplify(
    model: Union[str, onnx.ModelProto],
    check_n: int = 0,
    perform_optimization: bool = True,
    skip_fuse_bn: bool = False,
    overwrite_input_shapes=None,
    test_input_shapes=None,
    skipped_optimizers: Optional[List[str]] = None,
    skip_constant_folding=False,
    skip_shape_inference=False,
    input_data=None,
    dynamic_input_shape: bool = False,
    custom_lib: Optional[str] = None,
    include_subgraph: bool = False,
    unused_output: Optional[Sequence[str]] = None,
    tensor_size_threshold: str = DEFAULT_TENSOR_SIZE_THRESHOLDHOLD,
    mutable_initializer: bool = False,
    *,
    input_shapes=None,
) -> Tuple[onnx.ModelProto, bool]:
    ”“”
:param model: onnx ModelProto对象或文件路径
:param check_n:简化模型将通过随机输入检查' check_n '次
:param perform_optimization:是否在模型上运行onnx优化器
:param skip_fuse_bn:跳过fuse_bn_into_conv onnx优化器
:param overwrite_input_shapes:如果模型有动态输入形状,用户必须传递一个固定的输入形状
用于生成随机输入和检查等式。
:param test_input_shapes:如果模型有动态输入形状,用户必须传递一个固定的输入形状
用于生成随机输入和检查等式。
:param skipped_optimizers:跳过一些特定的onnx优化器
:param skip_constant_folding:跳过常量折叠
:param skip_shape_inference:跳过形状推断(有时形状推断会崩溃)
:param input_data:提供自定义输入数据,以便在需要时进行检查
:param dynamic_input_shape:已弃用。不再需要了。
:param custom_lib: onnxruntime自定义ops的共享库
:param include_subgraph:简化子图而不仅仅是主图
:param unused_output:将从模型中消除的未使用输出的名称
:param input_shapes:已弃用。请使用' overwrite_input_shapes '和/或' test_input_shapes '代替。
:return:一个元组(简化模型,success(True)或failed(False))
”“”

具体功能分析

OnnxSimplifier是一个用于简化onnx模型的工具,主要是拥有折叠常量(FoldConstant)的功能、自动调度OnnxOptimizer,最为重要而且核心的是FixedPointFn这个简化调度算法。

OnnxOptimizer是一个onnx官方的一个onnx模型优化库,内部包含很多模型简化/优化的功能。用户也可直接通过python/c++/c api执行调用。

FixedPointFn迭代优化函数基本原理:通过两个优化函数,反复迭代优化中得到了最终无法继续优化的最终模型。函数逻辑如下:

while (_max_iters-- > 0) {

if (google::protobuf::util::MessageDifferencer::Equals(y1, y2)){return y2}

y1 = f1(y2);

if (google::protobuf::util::MessageDifferencer::Equals(y1, y2)){return y1}

y2 = f2(y1);

}

直到y1=y2或达到最大迭代次数max_iter跳出循环

优化函数有二:

OptAndShape:FixedPointFn(std::function{InferShapes}, std::function{Optimize})

OptAndShape.f1:_InferShapes形状推导(可选,不使用的时候为Identity)

OptAndShape.f2:OptimizeFixed优化,调用了OnnxOptimizer函数库进行优化

OptAndShape.max_iters:默认是50(可通过ONNXSIM_FIXED_POINT_ITERS设置)

 

OptAndShapeAndFold:FixedPointFn(std::function{OptAndShape}, std::function{FoldConstant})

OptAndShapeAndFold.f1:OptAndShape

OptAndShapeAndFold.f2: FoldConstant函数(可选,不使用的时候为Identity)

FoldConstant具体来说:

  • 基于GetConstantNodes函数得到所有的常量节点和非常量节点;(常量节点需同时判断是官方算子、是确定性的、不是QuantizeLinear(将浮点数张量转换为量化的整数张量)或DequantizeLinear(将量化的张量(即整数张量)还原为浮点数张量)、不含有子图、不会输出超过指定大小的张量、输入为常量)
  • 使用onnxruntime进行一次推理(RunOp:在给定的 ONNX 模型上执行单个操作节点(`NodeProto`),并返回该操作节点的输出结果。),得到常量节点的值,并将常量节点的值及节点名构造成initializer,存入model中
  • 清空model中所有的node
  • 加入非常量节点

这样得到一个新的model,可以表示为常量的节点都转换为initializer

OptAndShapeAndFold.max_iters:默认是50

 

sim_model = OptAndShapeAndFold(model)得到最终的优化模型

 

onnxsim 本身只提供 constant folding/propagation(即消除结果恒为常量的算子)的能力,而图变换(即合并 conv 和 bn 等等)的能力是由 onnxsim 调用 onnx optimizer 的各种 pass 实现的。constant folding 和图变换同时使用时,很多隐藏的优化机会会被挖掘出来,这也是 onnxsim 优化效果出色的原因之一。

onnx-optimizer算法解读

Pass
含义:所有优化项的基类,每个优化项必须拥有唯一的名字,用来注册和管理。
优化类型(PassType):

  • Fuse: 会融合算子的优化项
  • Nop: 会移除无用的算子的优化项
  • Separate:会分开某种形式的算子
  • Immutable:不可变优化
  • Replace: 会进行节点替换的优化项
  • Other: 其他

返回类型(PassAnalysisType):

  • Empty:空分析结果
  • CountBased:基于计数的分析结果

效率(PassEfficiency):

  • Partial:部分有效,不能保证连续两次运行的结果与运行一次相同
  • Complete:完全有效,连续两次运行的结果等于一次运行的结果

优化目标(PassOptimizationType):

  • None:不优化
  • Compute: 针对计算优化
  • Memory: 针对内存优化
  • ComputeMemory: 针对计算和内存优化
  • Stability: 优化稳定性(如log-sum-exp)

节点销毁类型(NodeDestroyType):

  • DestroyZero:不销毁节点。
  • DestroyOne:等同于调用 destroyCurrent() 一次。
  • DestroyTwo:等同于调用 destroyCurrent() 两次。

具体pass实现(共39个):

  • adjust_add:交换add第一个常量输入到第二位置上
  • adjust_slice_and_matmul:调整slice和matmul之间的顺序,以便优化:Y = Matmul(Slice(data, start, end, axes) ,rhs) ==> Y = Slice(Matmul(data, rhs), start, end, axes)
  • eliminate_common_subexpression:消除掉那些属性/输入一致的节点,也就是公共子表达式
  • eliminate_deadend:移除掉output没有连接到其他节点的node
  • eliminate_duplicate_initializer:移除掉重复的initializer(可能导致意外)
  • eliminate_identity:移除掉Identity node
  • eliminate_if_with_const_cond:消除输入条件cond为常量的if算子
  • eliminate_nop_cast:消除那些目标输出数据类型等于输入数据类型的cast算子
  • eliminate_nop_concat:消除输入个数为1的Concat算子
  • eliminate_nop_dropout:消除那些ratio=0.0的dropout算子
  • eliminate_nop_expand:消除expand_dim可以广播到input_dim的Expand算子
  • eliminate_nop_flatten:消除那些对shape无影响的Flatten算子
  • eliminate_nop_monotone_argmax:消除那些正相关的激活函数到argmax函数上,减少计算
  • eliminate_nop_pad:消除pads=0的Pad算子
  • eliminate_nop_reshape:消除掉reshape dim == input_dim的reshape算子
  • eliminate_nop_split:消除输出个数为1、input_dim[axis]=split[0]的Split算子
  • eliminate_nop_transpose:消除掉不起作用的transpose算子
  • eliminate_nop_with_unit:消除掉同0并的And、同1乘的Mul、同0或的Or、同0加的Add、减0的Sub、除1的Div、方1的Pow、 无效的Concat
  • eliminate_shape_gather:融合掉indices=[indices_val,]、前面的节点是Shape的Gather算子
  • eliminate_shape_op:融合掉可以直接获取input_shape的Shape算子
  • eliminate_slice_after_shape:融合掉前面的节点是Shape的Slice算子
  • eliminate_unused_initializer:移除掉不被使用的initializer
  • extract_constant_to_initializer:移除Constant算子,放置到Initializer内
  • fuse_add_bias_into_conv:融合掉前面的节点是Conv2d、且input[1]是常量可配合dim的Add算子
  • fuse_bn_into_conv:融合BN算子到Conv2d上,同时修改Conv2d的权重
  • fuse_concat_into_reshape:融合reshape的shape输入的concat/cast算子,变成constant shape输入
  • fuse_consecutive_concats:融合前面的axis相同的Concat到该Concat节点上
  • fuse_consecutive_log_softmax:融合softmax+log成为LogSoftmax算子
  • fuse_consecutive_reduce_unsqueeze:当前面的Reduce算子的axes=Unsqueeze_axes、keepdims=0时,融合Unsqueeze算子到Reduce算子上
  • fuse_consecutive_slices:融合掉axes没有交集的连续的slice算子,合并为一个Slice
  • fuse_consecutive_squeezes:合并多个连续的Squeeze算子成为一个Squeeze算子
  • fuse_consecutive_transposes:合并多个连续的Transpose算子成为一个Transpose算子
  • fuse_consecutive_unsqueezes:融合连续的Unsqueezes算子
  • fuse_matmul_add_bias_into_gemm:合并MatMul+Add成为一个Gemm算子
  • fuse_pad_into_conv:合并Pad+Conv成为一个Conv算子,Pad操作合并到了Conv上
  • fuse_pad_into_pool:合并Pad+AveragePool/MaxPool成为一个Pool算子
  • fuse_qkv:合并qkv计算的三个matmul为只有一个matmul: A = matmul(X, Q), B = matmul(X, K), C = matmul(X, V) ==> A,B,C = split(matmul(X, concat(Q,K,V)))
  • fuse_transpose_into_gemm:融合前面的Transpose操作反转Gemm的transA/transB参数,从而融合掉transpose算子
  • replace_einsum_with_matmul:满足条件的einsum(爱因斯坦求和约定)变成matmul操作:"bhij,bhjd->bhid"变成matmul; "bhid,bhjd→bhij"变成transpose+matmul操作
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0