想象你经营着一家大型物流调度中心。每天清晨,形形色色的包裹从传送带上涌来,而你面前摆着几家承运商的窗口:一家陆运车队什么都肯运,速度却普普通通;一条高铁专线又快又稳,可惜只有轨道经过的地方才能送达;还有一家特快空运,速度快得惊人,却对包裹的尺寸、重量、批次有严格限制。你的工作不是亲自开车,也不是造飞机,而是根据每个包裹的目的地和特性,把它分给最合适的那家承运商,再按一套优先级规则调度它们协同工作。这套“调度中心与承运商”的协作方式,正是 ONNX Runtime 中“执行提供者(Execution Provider,EP)”架构的生动写照:模型的计算图是包裹流水线,EP 是各硬件后端的专门承运商,而 ONNX Runtime 就是那位聪明的调度员,它不亲自计算,却决定了每一份计算由谁来执行、以多快的速度完成。
掌握执行提供者架构,你就能看懂并掌控 ONNX Runtime 的跨硬件调度机制,在实际推理优化中精准选择后端、快速定位瓶颈,成为团队里那个“任何模型都跑得起来”的推理平台专家。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
运行时整体架构
物流园调度中心的“总控台”上有三块屏幕:第一块显示传送带上不断涌来的包裹清单,对应 ONNX Runtime 解析模型后得到的计算图;第二块显示入驻承运商的资质档案,每家能运什么、线路覆盖到哪,对应执行提供者注册表;第三块显示当前任务的路由规则,对应会话配置。调度员小O本人不搬货、不开车,但他掌握着每个包裹从进站到签收的完整路径——这就是运行时架构的缩影。
把视野拉远一点:一个完整的 ONNX Runtime 会话,前端吃掉 ONNX 模型并构建内部计算图;中间经过图优化、图切分;后端挂着多个执行提供者,各自负责把算子在特定硬件上真正跑起来。前端的“模型是什么”与后端的“在哪跑”被彻底解耦,模型的部署边界第一次变得可插拔。会话创建时,ORT 会评估每个 EP 与当前图的匹配程度——就像调度员评估每家承运商能接下多大比例的包裹。
回到总控台:小O扫描一遍包裹清单,就能在承运商档案旁标出“可接单 90%、60%、30%”的分数。分数高的承运商自然获得更多调度机会——但在真正派单前,还得等分拣区把包裹按能力分堆,这就是后面要讲的图切分。
- 图算子集合 $V(G)$$G$ 是 ONNX 计算图,$V(G)$ 是图中全部算子节点的集合。例如一个含 Conv、Relu、Gemm 三个算子的图,它的规模就是 3。
- EP 能力集合 $\mathcal{O}_i$第 $i$ 个执行提供者声明支持的算子集合。比如 CPU EP 支持全套算子,而 TensorRT EP 只认 Conv、Relu 这类基础算子。
- 交集计数 $|\{v \in V(G)v \in \mathcal{O}_i\}|$:分子统计“图里有、EP 也支持”的算子个数,也就是两边重叠的算子数。
- 比率计算 $C_i$用重叠数除以总算子数,得到 0 到 1 之间的覆盖率。覆盖率越高,说明该 EP 越可能接管当前模型的大部分计算。
执行提供者概览
在物流园里,一家承运商远不止“一辆车”。高铁承运商要维护自己的轨道信号、站台和排班体系;空运承运商拥有冷库、安检线和装机队。执行提供者也是一样:它不是一个算子一个函数,而是一整套面向特定硬件的执行环境。当你把模型交给 CUDA EP 时,得到的是一整套在 NVIDIA GPU 上运转的“承运体系”。
执行提供者封装了三层能力:算子内核层,提供算子在硬件上的具体实现,常常借助 cuDNN、oneDNN 等计算库或自研 CUDA 内核;内存管理层,负责显存分配、缓存复用与零拷贝策略;执行调度层,管理计算流、队列以及数据布局(layout)和数据类型(dtype)的偏好。每个 EP 还拥有自己的算子内核注册表(Kernel Registry),并作为“独立执行域”被编译、加载和替换——这就像调度中心可以整体更换一家承运商而不必改动传送带。
调度中心与承运商签约时,入驻合同上的四栏档案正是这个四元组:能运什么、用什么运、怎么运、排第几顺位。看懂了这份档案,就看清了 EP 的本质。
- 算子能力集 $\mathcal{O}_i$EP 声明支持的算子集合,决定它能“接什么样的货”。CPU EP 覆盖面最广,专用 EP 覆盖面较窄。
- 资源绑定 $\mathcal{R}_i$EP 持有的硬件资源,包括设备内存分配器、计算流、算子句柄等,确保计算发生在正确的硬件上下文里。
- 执行策略 $\mathcal{S}_i$EP 的内部调度偏好,例如 TensorRT EP 偏好固定 batch 以触发内核自动调优,CPU EP 偏好内存池复用。
- 优先级 $P_i$调度中心给该 EP 排的“派单优先位”,数值越大越先被考虑,对应注册时的提供者顺序。
EP核心接口
承运商入驻调度中心,必须签一份“入驻合同”。合同核心条款规定:承运商必须公布三样东西——能运什么货物(能力声明)、用什么车辆设备(资源绑定)、如何完成交接与回执(状态管理)。调度中心只按合同办事,从不窥探承运商内部怎么培训司机、怎么保养车辆。执行提供者的接口设计正是如此。
在 C++ 层面,每个 EP 都实现 IExecutionProvider 接口。最关键的方法是 GetCapability:给定当前计算图和已注册的内核表,它返回本 EP 有能力执行哪些节点,甚至可以主动提出算子融合建议。GetKernelRegistry 则交出真正的算子实现注册表,ORT 在执行阶段从中取内核。此外还有资源绑定与状态管理方法:分配设备内存、创建计算流、维护推理过程的状态。ORT 对每个 EP 只做“接口级对话”,完全不关心实现方是谁——面向接口而非面向实现,让第三方硬件厂商也能轻松接入。
这份合同条款被 ORT 在会话构建时逐个算子询问一遍,得到的“支持/不支持”清单,就成为了分拣区(图切分)的首要依据。可以说,接口是调度中心的“问话清单”,而切分是照着回答动手分拣。
- 判断算子归属 $v \in \mathcal{O}_i$把图中算子 $v$ 与 EP 的能力集合比对,若命中则进入支持分支,否则进入不支持分支。
- 支持分支取 1当 $\Phi_i(v) = 1$,说明该 EP 能为 $v$ 提供内核实现,调度中心可考虑把它划入这个 EP 的子图。
- 不支持分支取 0当 $\Phi_i(v) = 0$,说明该 EP 对这一单“无能为力”,算子将被划给其他 EP 或由 CPU EP 兜底。
图切分机制
包裹过了资质审核,接下来是分拣区的工作。分拣员按各承运商的货物清单把包裹分堆:高铁能运的进高铁堆,空运能运的进空运堆,谁都不认的临时件进陆运堆。更妙的是“打包”:把彼此相邻、又要送给同一家承运商的小盒子捆成一个大托盘,到了转运口一次装卸搞定——这就是算子融合。两个堆之间的交接点越少,中转成本就越低。
会话创建时,ORT 的图分拣分四步走:第一步,对图中每个算子询问各 EP 的 $\Phi_i(v)$,得到“节点-EP 支持矩阵”;第二步,基于支持矩阵划分连通子图,尽量让每个子图只归属单一 EP;第三步,融合器把子图内相邻算子合并成融合节点,例如 Conv 和 Relu 合成 FusedConv;第四步,在子图边界插入输出/输入张量的拷贝与内存注册,保证数据能跨 EP 流转。划分的目标,是让“运行开销 + 传输开销”的总和最小。
分拣员小O的原则被写成了公式:既要让每个堆跑得快,又要让堆与堆之间少倒腾。于是,一个 8 算子的小模型可能被分成“2 个走 CPU、3 个走 CUDA、3 个走 TensorRT”的三堆,各自出发。
- 子图拆分 $G_k$把整张图 $G$ 切成 $m$ 个互不重叠的子图,每个子图是一批相邻算子。
- 子图绑定 EP $T_{\text{run}}(G_k, \mathcal{EP}_{i_k})$子图 $G_k$ 由执行提供者 $\mathcal{EP}_{i_k}$ 负责,运行时间取决于该 EP 的硬件速度与算子实现。
- 边界 IO 开销 $T_{\text{io}}(G_k)$子图边界上的张量要在不同 EP 之间拷贝,例如 GPU 显存与 CPU 内存的往返,这部分是切分引入的额外代价。
- 求和取最小 $\min$在合法切分方案中挑选总代价最小者,即“分堆方案越省时越好”。
调度执行流程
分堆完成,调度进入执行阶段。每堆包裹上了自己承运商的车,车按依赖关系先后出发:先出发的堆到了转运口,后出发的堆才能接续。能并行发车的路线同时发车,互不干扰。执行提供者架构的运行期调度,正是这样一趟有秩序的多车队协同运输。
一次推理的完整生命线分为两段。会话创建阶段:读取 ONNX 模型、做图级优化(如常量折叠)、执行图切分、为每个子图绑定 EP,并准备好内存与内核资源。Run 阶段:按拓扑序依次执行各子图;子图内的节点由该 EP 的内核按序执行;跨 EP 边界的张量通过预注册的内存副本传递;相互独立的子图则由线程池并行推进。总延迟由三部分构成:预处理、各子图执行、边界拷贝。
调度员小O看着监控大屏:三辆“车”各自在泳道上奔跑,进度条的速度差异一目了然——这正是可视化演示里四个阶段的真实写照。执行流程的设计目标,就是让每一段路都最短、每一次交接都最少。
- 预处理时间 $T_{\text{pre}}$模型加载、图优化、切分与资源分配的固定开销,通常在会话创建时一次性支付。
- 子图执行时间 $T_{\text{run}}^{(k)}$第 $k$ 个子图在其绑定 EP 上的实际计算耗时,由算子数量、硬件峰值与内核质量共同决定。
- 边界拷贝时间 $T_{\text{copy}}(e)$每条跨 EP 边 $e$ 上的张量搬运开销,属于切分引入的“转运成本”。
- 总延迟相加 $T_{\text{total}}$三个分量线性求和。想要又快又稳,就要让每个分量都小:少切不必要边界、选更快的 EP、减少拷贝次数。
注册与优先级
新承运商入驻要办登记,调度中心的“优先选择表”则规定派单顺序:特快空运永远第一个被问,接不了的货才问高铁,再不行才轮到陆运。如果今天空运停飞,系统自动降级问高铁——绝不让包裹堆在传送带上无人接手。执行提供者的注册与优先级机制,就是这张派单表。
技术上有两个环节。注册环节:ORT 内置一份“承运商名册”,各 EP 通过 Provider Factory 或注册宏在编译期、运行期登记,get_available_providers() 可以随时列出名册内容。优先级环节:用户在创建 InferenceSession 时通过 providers=[...] 指定顺序,这个顺序就是派单优先级;ORT 从前往后寻找第一个“可用且支持目标子图”的 EP 接管执行。若高优先级 EP 初始化失败(比如显卡驱动异常),就自动滑到下一个可用者——降级回退保证了推理链路永不中断。
派单表的魅力在于确定性:同样的环境、同样的模型,每次都会选中同样的最优 EP,行为可预期。而就算最优者突然停运,降级链路也会让包裹照常上路——这正是生产环境最看重的那份“稳妥”。
- 优先级系数 $\text{Priority}_i$来自 providers 列表的先后位置,位置越靠前数值越大,代表调度中心越优先尝试它。
- 可用性开关 $\text{Avail}_i$EP 能否在当前环境成功初始化。显卡缺失、驱动不匹配、库版本错误都会让该值为 0,等价于停运。
- 覆盖率 $C_i$EP 对当前图的支持程度,前面已定义。三项相乘后,任何一个因子为 0,该 EP 就出局。
- 取最大值 $\arg\max$在所有 EP 中挑出乘积最大者,即“第一个既可用、又能接住单的承运商”。
后端生态与价值
几年过去,调度中心的承运商越来越多:Intel 区域的专线车队(OpenVINO)、手机上的同城快送(CoreML / NNAPI)、精准冷链空运(TensorRT)。小O的调度规矩一条没改,但能到的城市翻了几倍,运费反而更低了——这就是架构的生态红利:规则稳定,生态繁荣。
主流的执行提供者各有所长。CPU EP 通用保底,算子覆盖最全,任何环境都能跑;CUDA EP 全面利用 NVIDIA GPU 的并行能力,是深度学习推理的默认主力;TensorRT EP 面向 NVIDIA 硬件做深度图融合与内核自动调优,固定 shape、固定 batch 时吞吐极高,但约束也多;OpenVINO 面向 Intel CPU/GPU/VPU 全面优化;此外还有 ROCm、CoreML、NNAPI、XNNPACK 等面向 AMD、手机、边缘场景的后端。架构带来的核心价值是:模型只导出一次 ONNX,部署时按环境挑选 EP,新硬件出现时只需新增一个 EP,前端和模型完全不动。
调度中心的故事到这里完整了:从总控台(架构)、到承运商档案(EP 概念与接口)、再到分拣与发车(切分与调度)、派单表(注册优先级)、直至日益壮大的承运商生态(后端价值)。这套架构让 AI 推理从“为硬件定制模型”进化为“为模型选择硬件”,而理解它的每一个零件,正是你驾驭跨硬件推理的第一步。
- 基线时间 $T_{\text{CPU}}(G)$整张图由 CPU EP 串行执行所需时间,作为性能对比的“陆运基准价”。
- 加速时间 $T_{\text{EP}}(G)$同一张图由目标 EP(如 TensorRT)执行所需时间,通常远小于基线。
- 求加速比 $S$两者相除得到无量纲倍数。8 倍加速意味着原来 80ms 的任务现在只要 10ms。
- 选型依据对同一个模型分别实测多个 EP 的 $T_{\text{EP}}$,挑加速比最大且约束可接受者,而不是凭名气拍脑袋。
AI 应用场景
智能安防多硬件统一部署
阶段:模型部署与推理优化
解决的问题:智能安防公司同一套目标检测模型要部署到云端 A100 GPU、机房地 CPU 服务器与边缘盒子三类硬件。传统做法为每类硬件单独转换、维护一份模型和推理代码,训练一次、部署三次,版本极易漂移。借助执行提供者架构,模型只导出一次 ONNX,部署脚本通过 providers 优先级在运行时自动选择 TensorRT EP、CUDA EP 或 CPU EP,一套权重处处可用。
具体案例:算法团队交付检测模型后,部署工程师在三个环境分别配置 providers=["TensorrtExecutionProvider","CUDAExecutionProvider","CPUExecutionProvider"]、providers=["CUDAExecutionProvider","CPUExecutionProvider"] 和 providers=["CPUExecutionProvider"]。云端吞吐提升 4 倍,机房无需 GPU 也能跑,边缘盒子单帧延迟从 200ms 优化到 80ms。
OCR 服务的性能调优
阶段:推理性能优化
解决的问题:线上 OCR 服务图片长短不一,动态 shape 与固定 batch 需求混杂。若全部算子固定在 TensorRT EP,遇到不支持的动态形状算子会导致整图回退;若全部走 CUDA EP,又浪费了 TensorRT 的图融合加速。EP 架构允许 ORT 按算子支持度自动切分,让“该固定的固定、该动态的动态”。
具体案例:把 batch=8 的批处理分支交给 TensorRT EP,动态 shape 的检测头留在 CUDA EP,ORT 在两个 EP 间自动调度。压测显示吞吐提升约 3 倍,GPU 利用率从 40% 升至 75%,服务端单卡能扛住原先三倍的请求量。
边缘网关弹性降级
阶段:线上运维与容灾
解决的问题:边缘网关推理服务最怕硬件或驱动异常导致全线不可用。传统方案需人工切换备用链路,分钟级故障时间不可接受。EP 的优先级加降级回退让服务在 GPU 不可用时自动落到 CPU EP,实现“降速不降级”。
具体案例:某工厂网关运行中 CUDA 驱动被系统更新破坏,ORT 检测到 CUDAExecutionProvider 初始化失败,自动使用 CPUExecutionProvider 继续服务,模型在线率保持 100%,运维人员从容修复驱动后服务自动恢复加速。
Python 演示
# -*- coding: utf-8 -*-# Simulate ONNX Runtime Execution Provider (EP) architecture# graph partitioning + priority based dispatch class ExecutionProvider: # 一个EP: 算子能力集合 + 优先级 + 执行速度 def __init__(self, name, ops, priority, speed): self.name = name self.ops = set(ops) self.priority = priority self.speed = speed self.available = True def can_run(self, op): # 只有声明支持且可用的EP才能接单 return op in self.ops and self.available def partition(graph, eps): # 图切分: 为每个算子挑选优先级最高且支持它的EP assignment = [] for op in graph: best = None for ep in sorted(eps, key=lambda e: -e.priority): if ep.can_run(op): best = ep break assignment.append((op, best.name if best else "None")) return assignment def estimate_latency(assignment, eps): # 估算总延迟: 每个EP串行执行其负责的算子 grouped = {} for op, ep_name in assignment: grouped.setdefault(ep_name, []).append(op) total = 0.0 for ep_name, ops in grouped.items(): ep = next(e for e in eps if e.name == ep_name) total += len(ops) / ep.speed return total def real_ort_providers(): # 真实环境: 列出本机ONNX Runtime实际注册的EP try: import onnxruntime as ort print("Real available providers:", ort.get_available_providers()) except ImportError: print("onnxruntime not installed, skip real check") def main(): eps = [ ExecutionProvider("TensorRT", ["Conv", "Relu", "Pool"], 5, 12.0), ExecutionProvider("CUDA", ["Conv", "Relu", "Pool", "Add"], 3, 6.0), ExecutionProvider("CPU", ["Conv", "Relu", "Pool", "Add", "Gemm"], 1, 1.0), ] graph = ["Conv", "Relu", "Pool", "Conv", "Add", "Gemm", "Relu"] assignment = partition(graph, eps) print("Partition result (op -> EP):") for op, ep_name in assignment: print(" %-6s -> %s" % (op, ep_name)) print("Estimated total latency: %.3f ms" % estimate_latency(assignment, eps)) real_ort_providers() if __name__ == "__main__": main()
self.ops = set(ops)EP 的能力声明:把自己支持的算子登记成集合,对应接口里的能力清单。def can_run(self, op):接单判定:只有支持该算子且当前可用的 EP 才被允许运行。for ep in sorted(eps, key=lambda e: -e.priority):按优先级降序尝试,等价于 ORT 从前往后遍历 providers 列表。assignment.append((op, best.name if best else "None"))图切分结果:每个算子归属一个最优 EP,无人认领则标记 None。grouped.setdefault(ep_name, []).append(op)把归属于同一 EP 的算子聚拢成子图,对应图切分后的子图构建。total += len(ops) / ep.speed估算该 EP 的执行耗时:算子越多越慢,EP 速度越大越快。ort.get_available_providers()真实注册表查询,等价于调度中心的“承运商名册”。
常见误区
误区我一直以为 EP 就是把 cuDNN 或 OpenVINO 的接口包装一下,让 ONNX Runtime 能调用这些库而已,根本不复杂。
正解EP 是一个完整的执行域:算子内核实现、内存分配与管理、图内优化、执行状态与资源生命周期都由 EP 负责。它打包的是“整套执行能力”,而非一个函数库。类比来说,承运商不只是货车,而是司机、车队、油库、维修站的综合体。只把它当成 API 包装,会低估它在内存管理和图优化上的作用。
为何常见:从外部 API 看,EP 不过是 providers 里的一个字符串,接触不到内部尺度,自然容易低估。
误区我把所有能想到的 EP 都塞进 providers 列表,这样总有一个能用,性能也一定最好。
正解ORT 按列表顺序优先选择,注册顺序就是优先级。无顺序地堆砌 EP,可能让高优先级但非最优的 EP 抢走本应属于更合适后端的算子,还会增加会话构建时的检查开销。正确做法是按目标硬件精配:GPU 机器配 TensorRT/CUDA/CPU,纯 CPU 机器配 CPU/OpenVINO。
为何常见:把“可用性”误解为“越多越稳”,忽略了 providers 列表是线性尝试而非并行择优。
误区既然有 GPU 了,就不需要 CPU EP,留着它只会拖慢推理速度。
正解CPU EP 是所有 EP 的保底通道:图中任何算子如果 GPU EP 不认,最终都由 CPU EP 兜住。而且小模型、低并发、短流水场景里,CPU EP 因为免去设备间拷贝,反而可能更快。没有它,跨硬件部署的“降级回退”就是空中楼阁。
为何常见:拿大模型 benchmark 以偏概全,忽略了 CPU EP 在覆盖度和容灾上的架构性作用。
误区TensorRT 是 NVIDIA 官方极致优化,任何模型只要上了 TensorRT EP,就一定比 CUDA EP 快。
正解TensorRT 对固定 shape、固定 batch 和常见 CV 算子确实极快,但对动态 shape、冷门算子可能不接单或效果平平,此时 CUDA EP 反而更稳定更快。选型必须用真实业务输入实测延迟与吞吐,用加速比说话,而不是被宣传印象带着走。
为何常见:被基准测试的宣传误导,忽略了“支持度与约束”这两个 EP 架构中最核心的变量。
面试题
Q1. 执行提供者(EP)在 ONNX Runtime 中最核心的职责是什么?
- A.封装特定硬件的算子实现与执行逻辑← 正确答案
- B.负责解析并保存用户的 ONNX 模型文件
- C.为 Python 代码提供命令行调试工具
- D.替代前端框架完成模型训练与调参
答案:A。EP 就像承运商,核心职责是把算子在特定硬件上真实跑起来,包括内核、内存与调度。解析模型是前端的工作,调试工具与训练调参都和 EP 执行无关,因此 B、C、D 均错。
Q2. 下列哪个执行提供者通常作为系统保底通道存在?
- A.TensorRTExecutionProvider
- B.CUDAExecutionProvider
- C.CPUExecutionProvider← 正确答案
- D.OpenVINOExecutionProvider
答案:C。CPU EP 像万能陆运车队,任何环境都能运行、算子覆盖最全,是其他 EP 都不可用时的兜底通道。TensorRT、CUDA、OpenVINO 都依赖特定硬件,不能作保底。
Q3. 创建 InferenceSession 时传入 providers 列表,其语义是什么?
- A.按列表顺序尝试,优先用靠前者,不可用则后移← 正确答案
- B.随机选择一个可供执行的硬件后端执行
- C.同时启动所有提供者并取平均推理结果
- D.只允许列表中第一个提供者执行计算
答案:A。providers 列表就是派单优先级表,ORT 从前往后找第一个可用且支持目标子图的 EP。它不是随机选择,也不是并行取平均,更不是只认第一个——若第一个不可用会自动尝试后面的。
Q4. ONNX Runtime 图切分(Partition)主要依据什么标准?
- A.各执行提供者对算子的支持程度← 正确答案
- B.算子在计算图中出现的先后顺序
- C.算子名称字典序的字母大小比较
- D.模型文件在磁盘上的存储大小
答案:A。图切分是分拣员按承运商能力分堆,依据就是 $\Phi_i(v)$ 能力声明:每个 EP 支持哪些算子、覆盖多少节点。出现顺序、名称字典序和文件大小都只是表象,与硬件能力无关。
Q5. 将多个相邻算子融合成一个融合算子的主要收益包括哪些?
- A.减少子图边界的张量传输开销← 正确答案
- B.减少内核启动与调度开销← 正确答案
- C.合并算子使图结构更加紧凑← 正确答案
- D.让模型训练损失快速归零
答案:ABC。算子融合相当于把零散小包裹捆成大托盘,减少转运口装卸次数和内核启动次数,图也更紧凑。训练损失是训练阶段的事,与推理期融合无关,D 错。
Q6. 以下哪些因素会影响最终生效的执行提供者选择?
- A.提供者的注册优先级与运行时可用性← 正确答案
- B.提供者对图中算子的支持覆盖程度← 正确答案
- C.模型训练时采用的优化器名称
- D.模型导出时选择的具体文件名称
答案:AB。EP 选择由派单表决定:优先级(providers 顺序)、可用性(能否初始化)和支持度(覆盖率)三者共同作用。训练用的优化器和模型文件名与推理执行环境无关,不会影响 EP 选择。
Q7. 当高优先级 EP 不支持图中某个算子时,ONNX Runtime 会如何处理?
- A.切分子图,将该算子划分给支持它的 EP← 正确答案
- B.直接抛出异常并终止整个推理过程
- C.忽略该算子并继续执行剩余的计算图
- D.将该算子转换为训练模式重新训练
答案:A。这正是图切分的价值:高优先级 EP 不接的算子,会被划分给支持它的 EP(通常是 CPU 兜底),并在子图边界插入拷贝。推理不会终止、不能忽略算子导致语义错误,更不会在推理期训练。
Q8. 多次在 CPU 与 GPU 之间传递张量,会产生哪些额外开销?
- A.跨设备内存拷贝带来的时间延迟← 正确答案
- B.设备间同步等待与通信开销← 正确答案
- C.模型精度下降导致的必然误差
- D.模型文件体积的自动膨胀
答案:AB。跨 EP 边界的张量流转,就像包裹在转运口交接:内存拷贝耗时、CPU 与 GPU 之间的同步等待都是真实开销,所以要尽量减少子图边界。精度误差不是必然结果,文件体积更是与运行时无关。
Q9. 执行提供者实现的核心入口接口是哪一个?
- A.IExecutionProvider 接口及其方法集← 正确答案
- B.IAnnotationProvider 注解接口
- C.IDatasetLoader 数据集加载接口
- D.IOptimizerSchedule 调度器接口
答案:A。EP 的入驻合同就是 IExecutionProvider 接口,通过 GetCapability 声明能力、GetKernelRegistry 提供内核。后三个都是虚构的接口名,不是 ONNX Runtime 中的真实组件。
Q10. 某模型同时使用 TensorRT 与 CUDA 两个 EP,而业务存在动态 batch 输入,TensorRT 要求固定 batch。下列做法最合理?
- A.由 ONNX Runtime 自动切分,动态形状部分走 CUDA EP← 正确答案
- B.删除模型中的动态形状维度定义并改为固定 batch
- C.强制所有算子都使用 CPU 执行提供者推理
- D.为动态 batch 输入单独重写一个推理引擎
答案:A。EP 架构的优雅之处就在于自动切分:TensorRT 能接的固定形状子图交给它,动态形状部分由 CUDA EP 承接,调度中心按能力分单。改固定 batch 牺牲灵活性,全走 CPU 放弃加速,重写引擎更是重复造轮子。