ONNX Runtime 分配算子时,会先取得执行提供程序的能力,再按注册优先级把模型图中的节点或子图交给合适的实现。执行提供程序简称 EP,例如 CPUExecutionProvider 和 CUDAExecutionProvider;它们告诉引擎哪些计算可以由自己处理。
“安装包里有 CUDA EP”“当前会话注册了 CUDA EP”“某个节点真的由 CUDA 执行”是三种不同证据。本篇解释这个差别,并在 Windows、Python 3.11.15、ONNX 1.23.1、ONNX Runtime 1.28.0、NumPy 2.4.3 中读取真实 CPU 节点记录。未测试 CUDA EP 或多设备性能;小图为人工构造的教学模型,不评价真实模型能力。

从模型图到实际执行,发生什么
ONNX Runtime 架构说明给出图表示、与提供程序无关的优化、图分区和执行流程。GetCapability 是运行时向 EP 查询可执行节点或子图的能力接口,不是一个“请求 GPU 加速”的用户按钮。
- 读取 ONNX 图,理解输入、输出、算子和常量。
- 执行相应图变换,再查询已注册 EP 能处理的节点或子图。
- 按优先级分配可处理部分;有些节点可由高优先级 EP 承接,其他部分交给后续 EP。
- 运行分配后的图,并处理分区间的数据交换。
官方 EP 文档用 ['CUDAExecutionProvider', 'CPUExecutionProvider'] 表示优先尝试 CUDA,能力不覆盖的节点再交 CPU。这个列表不是“所有计算必定放在 GPU”。如果没有任何可用实现支持目标算子、opset 或自定义节点,会话可能无法建立;CPU 后备不能把不存在的实现变出来。
三层证据分别回答什么
| 检查 | 能回答 | 不能据此推断 |
|---|---|---|
| ort.get_available_providers() | 当前 ORT 安装可提供哪些 EP 名称 | 所列加速器一定在本机成功初始化 |
| session.get_providers() | 该会话实际注册了哪些 EP | 每个 EP 都处理了节点,或所有节点都在首选 EP |
| 节点 profiling 事件的 provider | 这次执行中有记录的节点由哪个实现处理 | 其他模型或其他输入也有相同分区和性能 |
Python API提供会话、provider 和 profiling 入口;官方 profiling 说明解释记录文件用途。1.28.0 的官方执行器源码将 provider 写入节点事件参数,因此本例按该字段读取。
生成一个小图,并检查节点实际位置
在空练习目录用 Python 3.11 建立环境。以下 Windows PowerShell 命令安装 CPU 推理包,不需要 GPU:
py -3.11 -m venv .venv-ep
.\.venv-ep\Scripts\python.exe -m pip install "onnx==1.23.1" "onnxruntime==1.28.0" "numpy==2.4.3"
保存代码为 provider_check.py,运行 .\.venv-ep\Scripts\python.exe -X utf8 provider_check.py。它写入专用 provider-demo.onnx,并记录一条输入的 MatMul 和 Relu。ONNX helper 文档是图构建接口依据。为了易于观察两个节点,本例关闭图优化;真实优化后可能融合成更少节点,不能直接对比原图节点数量。
from pathlib import Path
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('MatMul', ['features', 'weights'], ['z']),
helper.make_node('Relu', ['z'], ['result'])],
'provider-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.eye(2, dtype=np.float32), 'weights')],
)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid('', 17)])
model.ir_version = 10
onnx.checker.check_model(model)
onnx.save(model, 'provider-demo.onnx')
options = ort.SessionOptions()
options.enable_profiling = True
options.intra_op_num_threads = 1
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL
session = ort.InferenceSession('provider-demo.onnx', sess_options=options,
providers=['CPUExecutionProvider'])
x = np.array([[-1, 2]], dtype=np.float32)
result = session.run(None, {'features': x})[0]
np.testing.assert_array_equal(result, np.array([[0, 2]], dtype=np.float32))
profile = Path(session.end_profiling())
events = json.loads(profile.read_text(encoding='utf-8'))
nodes = [e for e in events if e.get('cat') == 'Node'
and e.get('args', {}).get('provider')]
if not nodes:
raise RuntimeError('没有节点provider记录,不能判断节点设备')
providers = sorted({e['args']['provider'] for e in nodes})
assert providers == ['CPUExecutionProvider']
print('available:', ort.get_available_providers())
print('session:', session.get_providers())
print('node_events:', len(nodes))
print('node_providers:', providers)
print('output:', result.tolist())
print('profile:', profile.name)
本次记录证明了什么
available: ['AzureExecutionProvider', 'CPUExecutionProvider']
session: ['CPUExecutionProvider']
node_events: 2
node_providers: ['CPUExecutionProvider']
output: [[0.0, 2.0]]
本次会话只注册 CPU EP,两个节点事件的 provider 都为 CPUExecutionProvider;输入 [-1,2] 经过单位矩阵乘法和 Relu 得到 [0,2]。available 中还出现 Azure EP,并不表示本例使用它;更不能把这个 CPU 实验写成已经验证 GPU 回退。
profile 文件名包含时间,代码使用 end_profiling 返回的真实路径,避免猜文件名。若没有带 provider 的节点记录,脚本报错而不凭会话列表推断节点位置。版本、构建选项和图融合都可能影响事件;应保存实际日志并按当前版本核对。
实际模型出现 CPU 节点,怎么继续查
先确认目标 EP 在当前包及依赖环境中能初始化,再看会话注册列表和节点记录。若节点由 CPU 处理,核对目标 EP 对该算子、数据类型和形状的支持;不要仅凭模型文件能打开就断言全图受支持。跨设备数据交换也可能影响整体延迟,设备名称不是性能结论。
读者下一步是对自己的同一模型、输入和 EP 配置保留一次节点记录,再据此决定要核查哪项算子支持或依赖。普通 CPU 推理输入检查可参照ONNX Runtime CPU 推理教程;它与本文的引擎分图原理是不同检查。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/32902.html