OpenPPL之二,优化器里面的算子融合

算子融合的执行时机

完整的时间线

复制代码
模型加载阶段(一次)          运行时阶段(多次推理)
    ↓                              ↓
┌─────────────────────┐      ┌─────────────┐
│ 1. 解析ONNX模型      │      │ 输入数据     │
│ 2. 构建IR Graph     │      │     ↓       │
│ 3. 图优化(融合)    │ ←──  │ 执行融合算子 │
│ 4. 图分割(多硬件)  │      │     ↓       │
│ 5. 编译成执行计划    │      │ 输出结果     │
└─────────────────────┘      └─────────────┘

关键点

  • 融合发生在模型加载时,只执行一次

  • 运行时直接执行融合后的算子,不再重新融合

  • 因此,融合的误差是固定的,不会随输入变化

OpenPPL 的算子融合功能主要通过以下优化器实现:

文件 功能说明
act_reorder_for_conv_optimizer.cc 激活函数重排序优化。将卷积后的激活函数(如ReLU)提前或调整顺序,以便与卷积核融合或减少中间内存开销
constant_node_optimizer.cc 常量折叠。将输入全为常量的节点在编译时预计算,替换为常量节点
engine_graph_partitioner.cc 引擎图分割器。根据硬件后端(如x86、CUDA)将计算图分割成多个子图,每个子图由对应引擎执行
fuse_bn_optimizer.cc Conv + BatchNorm 融合。将 BN 的参数融合到 Conv 的权重和偏置中,删除 BN 节点
fuse_constant_optimizer.cc Conv + Add/Mul 融合。将 Add 或 Mul 的常量值融合到 Conv 的 bias 中
fuse_parallel_node_optimizer.cc 并行节点融合。融合可以并行执行的无依赖节点
fuse_shape_optimizer.cc Shape 相关算子融合。将 Shape → Gather → Slice → Concat → Add/Div 等多个算子融合成一个 PPL.ShapeOperation 自定义算子
identity_node_optimizer.cc Identity 节点删除。Identity 算子(恒等映射)在推理时无实际作用,直接删除并重新连接输入输出
skip_dropout_optimizer.cc Dropout 删除。推理时 Dropout 无效,直接删除

这9个优化器可以分为几类:

优化器 实现方式 说明
fuse_bn_optimizer.cc 只改参数 将BN参数融合到Conv的weight/bias中,Conv节点类型不变
fuse_constant_optimizer.cc 只改参数 将Add/Mul的常量融合到Conv的bias中,Conv节点类型不变
constant_node_optimizer.cc 只改参数 常量折叠,预计算结果替换原节点,不产生新算子类型
skip_dropout_optimizer.cc 只改参数 删除Dropout节点,不产生新算子
identity_node_optimizer.cc 只改参数 删除Identity节点,不产生新算子
act_reorder_for_conv_optimizer.cc 只改参数 调整激活函数顺序,不产生新算子
engine_graph_partitioner.cc 非融合 图分割,不是算子融合
fuse_parallel_node_optimizer.cc 非融合 并行节点融合,实际是调度优化
fuse_shape_optimizer.cc 生成新算子 将Shape→Gather→Slice→Concat→Add/Div等多个算子融合成一个新的 自定义算子

在**fuse_shape_**optimizer.cc的****Optimize里

复制代码
node->SetType(ir::Node::Type{"pmx", "Shape", 1});

实际看到的是pmx.Shape算子

一、卷积相关融合(最核心)

1. Conv + BN 融合

  • 实现位置FuseBNOptimizer

  • 融合方式:将BatchNorm的参数融合到Conv的权重和偏置中

  • 官方记录 :2021年12月已支持 batchnorm+relu 融合

2. Conv + Add/Mul 融合

  • 实现位置FuseConstantOptimizer

  • 融合方式:将Conv后面的Add或Mul常量节点融合到Conv的bias中

3. Conv + ReLU/ReLU6 融合

  • 官方记录:x86后端所有卷积算法支持 ReLU/ReLU6 融合

  • 融合方式:卷积计算后直接执行激活函数,避免中间结果回写内存

4. Conv + DepthwiseConv 融合

  • 官方记录 :x86后端支持 Conv+DepthwiseConv 融合,使用AVX512/FMA指令集优化

5. Conv + Concat 融合

  • 修复记录:2022年12月修复了Conv+Concat融合时channel不能整除的计算问题

二、Shape相关融合(动态模型专用)

PPL.ShapeOperation 自定义算子

OpenPPL设计了专门的自定义算子 PPL.ShapeOperation 来处理Shape相关计算,支持融合以下算子 :

融合类型 具体算子
提取类 Gather, Slice
拼接类 Concat
数值计算类 Add, Div, Mul, Sub

融合示例(Shufflenet网络):

  • 融合前:Shape → Gather → Div → Concat 等多个算子

  • 融合后:单个 PPL.ShapeOperation 算子,维护系数矩阵表示完整计算逻辑

  • 效果:融合共计20个Shape相关算子

三、其他算子融合

1. Identity 算子融合

  • 官方记录:支持 identity 算子的融合

2. QuickGelu 算子融合

  • 官方记录:Onnxruntime后端已支持 QuickGelu 算子融合

3. Add/Sub/Mul/Div 就地计算(Inplace)

  • 官方记录:x86后端支持 Add/Sub/Mul/Div 就地计算,减少内存分配

四、各后端支持汇总

融合类型 x86 CUDA 说明
Conv + BN CUDA支持batchnorm+relu融合
Conv + ReLU x86所有卷积算法支持
Conv + Add FuseConstantOptimizer实现
Conv + DepthwiseConv AVX512/FMA指令集优化
Shape融合 PPL.ShapeOperation自定义算子
Identity融合 2022年12月加入
QuickGelu Onnxruntime后端支持

算子融合在大多数情况下确实只是修改节点信息而不生成新算子类型,但在某些情况下也会生成全新的算子类型。

两种融合模式

模式1:修改节点信息(最常见)

这种模式不生成新算子类型,只是修改现有节点的属性或删除冗余节点。

典型例子:Conv + BN 融合

cpp

复制代码
// 融合前
Conv节点:weight=W, bias=b
BN节点:gamma=γ, beta=β, mean=μ, var=σ²

// 融合后
Conv节点:weight=W', bias=b'  ← 只修改了属性
BN节点:被删除

发生了什么

  • 没有创建新节点类型

  • Conv节点的类型仍然是 "Conv"

  • 只是更新了它的权重和偏置

  • 删除了BN节点


模式2:生成新算子类型(少见但有)

这种模式会创建全新的节点类型,通常用于:

  1. 融合模式无法用现有算子表达

  2. 后端有专门优化的融合Kernel

典型例子:Conv + Add + Relu 融合(有专门优化时)
复制代码
// 融合前
Conv → Add → Relu

// 融合后(如果有专门优化的Kernel)
FusedConvAddRelu  ← 新类型

为什么需要新类型

  • Conv + Add + Relu 融合后,数据流从 ConvAddRelu 变成了一个端到端的计算

  • 如果后端有专门的手工汇编优化版本(如CUDA的cudnnConvolutionBiasActivationForward),就需要一个新的算子类型来调用它

为什么大多数融合不生成新算子

1. 数学等价性

很多融合(如Conv+BN)可以完全用修改参数来表达,不需要改变计算结构。

2. Kernel复用

修改属性后,现有的Conv Kernel仍然可以正确执行,只是参数不同。

3. 避免组合爆炸

如果为每种组合都创建新算子:

复制代码
ConvBN
ConvBNRelu
ConvAdd
ConvAddRelu
ConvBNAdd
ConvBNAddRelu
... 组合数 = 2^n 爆炸增长

4. 保持图结构简单

算子类型越少,图越简单,后续优化和调试越容易。


什么时候需要生成新算子

场景 原因 例子
后端有专门优化 利用硬件特性(如CUDA的Fused Conv) ConvReluFused
数据布局改变 融合后需要特殊的内存排布 NC4HW4Conv
无法用原算子表达 融合后的计算模式与原算子不同 DepthwiseConvBN
性能关键路径 为特定融合模式手写汇编优化 ConvAddReluInt8
相关推荐
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【69】Token 用量统计
java·人工智能·spring
十三画者1 小时前
【AI学习笔记】:DeepSeek 大模型本地部署与调用实战指南
人工智能
丁常彦-自媒体-常言道1 小时前
从首发4nm智驾芯片到兜底城市领航安全,比亚迪开启AI新征程
人工智能
小杨在厦门3 小时前
从AI验布到智能质检:纺织企业智能化升级的三个台阶
人工智能·服装·服装厂·服装机械·铺布机
达之云*驭影3 小时前
解锁流量密码:详解抖音AI智能推荐封面功能
人工智能
火山引擎开发者社区3 小时前
ArkClaw 投研助理 —— 零门槛做投研,从一句话开始产出你的第一份深度研报
人工智能
码农小白AI3 小时前
AI报告审核加速融入自动化实验室:IACheck破解智能设备时代报告管理新挑战
运维·人工智能·自动化
xingyuzhisuan3 小时前
自建聚合网关VS第三方聚合平台,适配场景与数据实测
人工智能·ai·云计算·oneapi
tedcloud1233 小时前
DeepSeek-TUI部署教程:打造CLI AI助手环境
服务器·人工智能·word·excel·dreamweaver