之前的OpenPPL有个章节讲到过优化器,onnx里面也有个优化器,相关介绍如下
一、优化器的本质
ONNX Core Optimizer 是在图级别工作的,与EP(Execution Provider)无关。
ONNX模型(计算图)→ Optimizer(图变换)→ 优化后的计算图 → EP执行
关键点:优化器在EP之前运行,它不关心后续是哪个EP执行。
二、优化器对两类EP的影响
1. 解释型EP(逐个算子调用)
如 CPUExecutionProvider、CUDAExecutionProvider(基础模式)
优化后的图 → EP逐个调用算子内核
优化器有效的原因:
-
虽然EP是逐个算子执行,但图结构已经改变
-
例如:
Conv+BN被融合成一个节点 → EP只需调用1次而不是2次 -
算子数量减少,内核调用次数减少
实际效果:
| 优化 | 对解释型EP的效果 |
|---|---|
| 常量折叠 | ✅ 减少计算量 |
| 算子融合 | ✅ 减少内核调用 |
| 死代码消除 | ✅ 减少执行节点 |
2. 编译型EP(如TensorRT、OpenVINO)
如 TensorrtExecutionProvider、OpenVINOExecutionProvider
优化后的图 → 编译型EP接收 → 进一步做硬件级优化 → 生成特定引擎
优化器仍然有效:
-
编译型EP虽然会做自己的优化,但接收的图越干净,编译效果越好
-
例如:ONNX Optimizer 先融合
Conv+BN,TensorRT 就不需要再处理这个模式 -
减少编译型EP需要处理的工作量
三、优化器的生效时机
时间线:
─────────────────────────────────────────────────────────→
1. 创建Session
↓
2. 加载模型
↓
3. 【优化器运行】 ← 时刻
↓
4. 图变换完成
↓
5. EP获取优化后的图
↓
6. EP执行(解释型)或编译(编译型)
代码层面
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession("model.onnx", sess_options)
# ↑ 在这一步,优化器已经运行完毕
四、为什么编译型EP也需要图优化
以 TensorRT 为例:
| 优化层级 | 谁做 | 作用 |
|---|---|---|
| 图级优化 | ONNX Optimizer | 减少节点数,简化结构 |
| 层融合 | TensorRT | 垂直融合(如Conv+BN+Relu) |
| 内核选择 | TensorRT | 选择最优CUDA内核 |
| 内存规划 | TensorRT | 显存复用 |
如果图级优化没做:
-
TensorRT需要处理更复杂的图结构
-
可能错过某些融合机会
-
编译时间更长
五、总结
| 问题 | 答案 |
|---|---|
| 优化器对解释型EP有用吗? | 有用,减少节点数,减少内核调用 |
| 优化器对编译型EP有用吗? | 有用,简化图结构,帮助编译型EP做更好优化 |
| 优化器什么时候运行? | EP之前,Session创建时 |
| 编译型EP会重复优化吗? | 会,但接收优化后的图效果更好 |
解释型EP是逐个算子调用,优化器通过减少算子数量 来加速;编译型EP有自己的优化层,但上游优化让它的工作更高效。两者是互补而非替代关系。