ONNX Runtime 图优化能加速 AI 模型吗?比较原图、优化图和固定输入输出

用同一 CPU 小图比较关闭与基础图优化,核对节点减少、三个批次输出及保存副本,再记录五轮固定输入耗时,说明波动和跨硬件限制。

ONNX Runtime 图优化可能减少冗余计算,但不能预先保证任何 AI 模型都更快。需要用同一原图、输入、线程和计时范围比较关闭优化与开启优化的会话,再检查保存的优化图与输出数值;结构变化、结果一致和耗时变化应分别记录。

下面实际运行了一个含 Identity 和常量加法的教学图。环境为 Windows、Python 3.11.15、ONNX 1.23.1、ONNX Runtime 1.28.0、NumPy 2.4.3、CPU EP。权重和算子由人工构造,没有训练过程;没有测试真实业务模型、GPU、跨机器兼容或端到端服务性能。

ONNX Runtime 图优化能加速 AI 模型吗?比较原图、优化图和固定输入输出

本次控制什么,比较什么

ORT 图优化文档说明基础优化包含常量折叠和 Identity 消除。ORT_DISABLE_ALL 关闭图优化;ORT_ENABLE_BASIC 启用基础图优化。本例只比较这两个级别,不同时改量化、输入精度或提供程序。

  • 同一 original.onnx:输入 float32 [batch,2],输出同形状。
  • 同一 CPU EP:会话的 intra/inter 线程都设置为 1。
  • 同一数值检查:batch 1、8、64,并与 x+[4,6] 解析结果对照。
  • 同一计时输入:batch 64,预热 100 次;五轮每轮 100000 次 session.run,交替测试顺序。
  • 计时只含 session.run,不含图生成、会话初始化、HTTP、磁盘和样本准备。

五轮中每轮计算“100000 次总耗时除以 100000”,然后取五个轮均值的中位数。它不是单次请求耗时的 P50,也没有计算 P95;不能把这个口径与服务延迟指标混用。

运行完整图优化实验

新建空练习目录并进入它,执行 Windows PowerShell 安装命令:

py -3.11 -m venv .venv-opt
.\.venv-opt\Scripts\python.exe -m pip install "onnx==1.23.1" "onnxruntime==1.28.0" "numpy==2.4.3"

将下面代码保存为 compare_optimization.py,运行 .\.venv-opt\Scripts\python.exe -X utf8 compare_optimization.py。代码创建专用 original.onnx、optimized-basic.onnx 与 optimization-report.json,重复执行会覆盖这些练习文件。图构建依据ONNX helper,ORT Python API是会话选项、优化文件保存和 run 的依据。

from pathlib import Path
from time import perf_counter_ns
import json
import numpy as np
import onnx
from onnx import TensorProto, helper, numpy_helper
import onnxruntime as ort

graph = helper.make_graph(
    [helper.make_node('Identity', ['features'], ['same']),
     helper.make_node('Add', ['constant_a', 'constant_b'], ['offset']),
     helper.make_node('Add', ['same', 'offset'], ['result'])],
    'optimization-demo',
    [helper.make_tensor_value_info('features', TensorProto.FLOAT, ['batch', 2])],
    [helper.make_tensor_value_info('result', TensorProto.FLOAT, ['batch', 2])],
    [numpy_helper.from_array(np.array([1, 2], dtype=np.float32), 'constant_a'),
     numpy_helper.from_array(np.array([3, 4], dtype=np.float32), 'constant_b')],
)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid('', 17)])
model.ir_version = 10
onnx.checker.check_model(model)
onnx.save(model, 'original.onnx')
def make_session(level, save=None):
    options = ort.SessionOptions()
    options.intra_op_num_threads = 1
    options.inter_op_num_threads = 1
    options.graph_optimization_level = level
    if save:
        options.optimized_model_filepath = save
    return ort.InferenceSession('original.onnx', sess_options=options,
                               providers=['CPUExecutionProvider'])

raw = make_session(ort.GraphOptimizationLevel.ORT_DISABLE_ALL)
optimized = make_session(ort.GraphOptimizationLevel.ORT_ENABLE_BASIC,
                         'optimized-basic.onnx')
optimized_model = onnx.load('optimized-basic.onnx')
onnx.checker.check_model(optimized_model)
restore_options = ort.SessionOptions()
restore_options.intra_op_num_threads = 1
restore_options.inter_op_num_threads = 1
restore_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL
restored = ort.InferenceSession('optimized-basic.onnx', sess_options=restore_options,
                               providers=['CPUExecutionProvider'])
errors = []
for batch in [1, 8, 64]:
    x = np.arange(batch * 2, dtype=np.float32).reshape(batch, 2) / 10
    a = raw.run(None, {'features': x})[0]
    b = optimized.run(None, {'features': x})[0]
    c = restored.run(None, {'features': x})[0]
    np.testing.assert_allclose(a, b, rtol=1e-6, atol=1e-6)
    np.testing.assert_allclose(b, c, rtol=1e-6, atol=1e-6)
    np.testing.assert_allclose(b, x + np.array([4, 6], dtype=np.float32))
    errors.append(float(np.max(np.abs(a-b))))

x = np.ones((64, 2), dtype=np.float32)
feed = {'features': x}
for session in [raw, optimized]:
    for _ in range(100): session.run(None, feed)
timings = {'raw': [], 'basic': []}
runs_per_round = 100_000
for round_index in range(5):
    order = [('raw', raw), ('basic', optimized)]
    if round_index % 2: order.reverse()
    for name, session in order:
        started = perf_counter_ns()
        for _ in range(runs_per_round): session.run(None, feed)
        timings[name].append((perf_counter_ns()-started) / runs_per_round / 1000)
report = {'original_nodes': [n.op_type for n in model.graph.node],
          'optimized_nodes': [n.op_type for n in optimized_model.graph.node],
          'batch_sizes_checked': [1, 8, 64],
          'max_abs_error': max(errors),
          'saved_model_reload_output': 'matched',
          'runs_per_round': runs_per_round,
          'mean_us_per_run_by_round': timings,
          'median_of_round_means_us': {k: float(np.median(v)) for k,v in timings.items()},
          'timed_scope': 'session.run only; fixed batch64 float32, CPU, 1 thread; initialization excluded'}
Path('optimization-report.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(report,ensure_ascii=False,indent=2))

先看结构和数值,再看速度

original_nodes: ['Identity', 'Add', 'Add']
optimized_nodes: ['Add']
batch_sizes_checked: [1, 8, 64]
max_abs_error: 0.0
saved_model_reload_output: matched

这次保存的基础优化图剩一个 Add:Identity 被移除,两个常量的加法提前得到 [4,6]。三个 batch 的原图和优化会话输出最大绝对差为 0;重新加载优化文件的结果也与优化会话一致。它说明本次结构优化和保存副本的验证通过,不代表其他模型可以跳过数值与任务质量检查。

本次耗时记录

以下为这次 CPU 教学实验的实际轮均值,单位微秒;保留各轮而不是挑最好的一个数字。

轮次 关闭优化 基础优化
1 6.306 5.398
2 6.275 5.423
3 7.637 7.129
4 8.260 7.042
5 8.379 6.770
五轮均值的中位数 7.637 6.770

本次基础优化的记录较低,同时不同轮次的耗时波动明显。本次足以核对结构变化、输出和计时方法,尚不足以断言稳定的加速幅度。图很小,Python 调用开销、系统调度和测量抖动都可能影响差异;不能把该数字推算成真实大模型的提速比例,也不能保证你复跑得到相同数值。

优化图更少,为什么真实模型仍可能没变快

如果原模型已经优化、可优化部分很少、运行开销由数据准备或网络主导,图节点减少不一定明显改变整体延迟。先检查实际优化文件、目标 EP 与数值,再按自己的输入批次和端到端口径测量;没有速度优势时如实保留结果,不因为启用了开关就写“已加速”。

优化后的文件还可能依赖执行提供程序、优化选项和硬件能力。官方文档特别说明离线优化要匹配目标环境;即使本例仅用基础优化,也没有验证跨设备运行。换目标机器或 EP 后,应重新做加载、固定输入输出和性能检查。

图优化与量化是一回事吗?

不是。这里做图结构变换,仍使用 float32 输入和同一数值契约;INT8 量化改变数值表示,并需要另外检查输出与质量。若要做量化,可阅读ONNX 动态量化及输出比较,不能把本篇的零差异复制为量化结论。

读者下一步是先跑教学图并核对节点、三个 batch 的数值和保存副本,再用同样受控口径比较自己已经能运行的模型。任何实际加速结论都应同时给出模型、输入、环境、计时范围和测量记录。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/32911.html

赞 (0)
AI小管家的头像AI小管家
AI 训练与部署工具怎么选?PyTorch、ONNX Runtime 和 FastAPI 的分工
上一篇 1小时前
DeepSeek 写歌词后怎么用 Suno 作曲?从结构稿到试听核对
下一篇 1小时前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部