设想你是一位写信人,手里握着一本不断再版的《现代汉语词典》。第 9 版收录了“给力”,第 11 版给“粉丝”补上了引申义,第 13 版删掉了过时的词条……你每写一封信,都得在信封上注明“本文按第 X 版词典书写”,否则收信人要么读不懂新词,要么按旧义误解了新用法。 ONNX 的世界里,这本词典叫“算子集”(Operator Set),它的版次编号就叫 Opset。你用 PyTorch 训练好的模型,导出成 ONNX 文件时,也会在文件头写下一行小字:本模型按第 13 版算子集书写。推理引擎(ONNX Runtime、TensorRT)就是收信人,各有各能读懂的版次范围。版次选高了,老引擎拒收;版次选低了,Gelu 这类新算子又写不进去。更麻烦的是,词典每次改版都会修订词义——Squeeze 的 axes 从“属性”挪到了“输入”,Upsample 被 Resize 彻底取代——你照着旧版写好的信,升级时就得逐字校对。 这篇文章就是一部“选版与改版指南”:从版次编号规则讲起,盘点二十几次再版的里程碑与弃用词条,再讲导出时怎么选版、旧信怎么迁移、引擎怎么兼容,最后给出一份可落地的迁移检查清单。读完之后,你不仅能看懂任意 ONNX 模型文件头那行小字,还能从容回答面试官:“opset 13 的 Squeeze 语义,我很清楚。”
对 AI 从业者而言,Opset 是模型部署的第一道门槛:选错版本引擎拒载,迁错语义结果漂移。掌握它,你就能在模型上线的最后一公里把好关,这是推理框架维护与跨版本迁移工程师的核心基本功。
目录
可视化演示
动态演示ONNX Opset版本演进的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
ONNX Opset版本演进:Opse Opset各主要版本演进历程与 算子新增与弃用清单的版本对应管 核心机制可视化 ONNX Opset版本演进整体架构与关键组件
Opset 基本概念:这本字典怎么编号
假设你有一本每几年修订一次的《现代汉语词典》。第 7 版给“晒”字增加了“炫耀”的义项,第 12 版删掉某个生僻字。你写好一篇文章送给读者时,必须提前声明:“本文按第 11 版词典书写”,对方才知道该用哪一套释义去理解你笔下的每个词。ONNX 的 Opset 就是这样的“版次声明”——模型导出时,文件头会写下一行小字:本模型图里的算子,请按第几版算子词典来解读。
ONNX 模型并不是一堆散装文件,而是由一个标准化的 protobuf 消息封装起来的整体。它既装着计算图(graph),也装着自己的“自述信息”。其中 opset_import 字段正是那句版次声明,它告诉任何打开文件的引擎:图中出现的 Conv、MatMul、Relu 这些算子,其语义要按照第几版词典来查。千万别把这句话当成可有可无的注释——它是引擎解释整个模型的“总开关”。
进一步看,每个算子并不是每一版都会变化。ONNX 为每个算子单独追踪“自上次变更以来的版本号”,Opset 则是所有算子版本号的集合快照:第 13 版词典=当时所有算子最新版本的合集。当某个算子行为改变,ONNX 会为它单独立一个新版本,同时整体 Opset 号随之推进。因此,Opset 既是“词典版次”,也是“算子变更的推进器”。
所以引擎读模型的第一步不是急着看正文,而是先看“版次声明”。就像读者先确认信封上的词典版次,再决定用哪一组释义去理解每个字——这就是 Opset 存在的全部意义。
- Model(整封信)整个 ONNX 模型文件,一个序列化后的 protobuf 对象,装着全部自述与正文。
- ir_version(信封版式)消息结构的版本号,决定 protobuf 按什么版式解析字段,与算子语义无关,相当于信封的印刷格式。
- opset_import(版次声明)明确写着 domain 与 version,如
ai.onnx第 13 版,是引擎查词典的索引。 - graph(信纸正文)计算图本体,节点、张量、初始值全在这里,所有算子的参数与连接关系。
版本演进历程:词典的二十几次再版
这本算子词典不是一天写成的。1978 年《现代汉语词典》初版收录约 5.6 万词条,到第 7 版已扩充到 6.9 万;ONNX 词典恰好相反,起点极简、扩张迅猛——2017 年 Opset 1 只有十几个算子,到 2024 年 Opset 21 已经超过了 170 个词条。每一次再版背后,都是深度学习社区把新算法“辞条化”的过程:卷积、循环、注意力、量化……一个接一个变成标准词条。
先看版次的推进节奏:ONNX 本身的发布号与 Opset 近似同步,从 1.0 到 1.20,opset 跟着小版本基本 +1。这种“一年出几版”的速度,让词典始终跟得上算法潮流,但也意味着用户必须频繁面对新义项、新词条、甚至删词。
把二十几次再版分成三段来看,脉络更清晰。早期(1~6)奠定基础:Conv、Pool、MatMul、LSTM 等 CNN/RNN 主力词条定型。中期(7~13)是“大修订时代”:控制流算子(Loop/Scan)、序列算子、动态 shape 支持陆续加入,opset 13 更是把 Squeeze、ReduceSum 等一大批算子的 axes 从属性挪到输入,并正式移除 Upsample。近期(14~21)进入“多模态与 LLM 时代”:GridSample(16)、LayerNormalization(17)、Gelu(20)相继入典,词典从“视觉词典”变成“通用大模型词典”。
读者与作者都得跟上版次,但不必每版都追。真正的“词典大修订”往往集中在几个关键节点——第 9 版、第 13 版、第 20 版——读懂这些节点,就能以不变应万变。
- opset(版次号)算子词典的版次编号,数字越大词条越多、语义越新。
- ONNX 次版本号(发布号)发行版
1.x中的x,代表项目本身的版本节奏。 - +1(近似同步)历史上多数版本满足
opset = x + 1,如 ONNX 1.12 对应 opset 13;个别版本有微调。
算子新增与语义调整:新词入库、旧词改义
新词是怎么来的?一个算法先在 PyTorch 里被反复验证,社区觉得“这个操作值得标准化”,编委会就为它立新词条。Einsum 在 opset 12 入库、GridSample 在 16 入库、LayerNormalization 在 17 入库、Gelu 在 20 入库——每一次入库,都意味着某个原本要“拆成多个旧算子的组合”的操作有了正式名字,模型文件更短、引擎实现更高效。
比新增更危险的是“旧词改义”。以 Squeeze(压缩张量中长度为 1 的维度)为例:第 11 版词典里,要压缩哪些轴是作为属性(attribute)写死在图里的;到了第 13 版,把它改成了输入张量——这意味着轴向可以动态计算,模型更灵活,但旧写法在新义项下直接失效。同样是第 13 版,ReduceSum、ReduceMin、ReduceMax 也经历了同样的“属性变输入”迁移。
还有一类是“看似没动、实际改了义项”的词条。Resize 的坐标变换模式(coordinate_transformation_mode)在 opset 11 和 13 两次调整,用同样参数在不同版次下输出可能差一个像素甚至完全错误;Pad 在 11 版把 pads 从属性改成输入、13 版又调整了 pads 的顺序和常量值语义。这些都是部署工程师踩过的“同字不同义”的著名大坑。
升级模型的过程,本质上是“学习新义项”的过程。最怕的就是嘴上说升到了 13,心里还默写着 11 版的旧义项——这正是许多“升级后数值漂移”事故的根源。
- Squeeze(词条名)负责压缩张量形状、去掉长度为 1 的维度的算子。
- v11 旧写法(旧义项)axes 作为 attribute 固定在图中,导出后无法动态改变。
- v13 新写法(新义项)axes 变成第二输入张量,推理时可以动态计算。
- 箭头(迁移方向)导出方与引擎需要同步切换到新写法,旧写法在 v13 下不再被认可。
弃用算子处理:生僻字与退场词条
词典处理生僻字向来有一套规矩:先在附录里标注“建议不再使用”,保留几个版次供人过渡,最后才正式删除。ONNX 对算子的弃用(deprecated)也是同样节奏——官方会在 schema 里打上弃用标记,通常保留数个版本再移除,给足迁移时间。
回顾这份“退场词条表”,最著名的四个案例:Upsample 在 opset 10 被标记弃用、推荐改用 Resize,等到 opset 13 正式移除;Scatter 在 11 版被拆分成 ScatterElements(按索引替换)与 ScatterND(多维索引散布)两个更明确的词条;DynamicSlice 在 10 版并入功能更强的 Slice,后者支持负步长与多轴;BatchMatMul 在 9 版并入支持广播的 MatMul。它们的共同规律是:旧名字表达力不足或语义含糊,被更清晰的新词条取代。
当你的模型里出现这些退场词条时,有三条处理路线:第一,找到替代算子,重写对应子图;第二,如果目标引擎不认替代算子,就用等价子图展开——比如 Gelu 可以展开成 0.5 * X * (1 + erf(X / sqrt(2))) 的旧算子组合;第三,实在没有官方替代时,走自定义 domain + 自定义算子插件。绝大多数情况下,第一条路就够用。
处理弃用算子的心态,应当像查字典看到“巳废除”的标注一样:不慌、不无视,按附录指引找到替代词条,再通读一遍上下文确认新用法没有改变原意。
- Upsample(旧词条)只做近邻或线性缩放的老算子,表达力有限。
- Resize(新词条)支持 roi、坐标变换模式、多种插值方式,是 Upsample 的功能超集。
- 箭头与时间线(迁移路径)opset 10 标记弃用,opset 13 正式移除,中间留出迁移窗口。
- mode(词义扩展)新词条还引入
coordinate_transformation_mode参数,控制像素坐标的换算规则。
模型导出设置:写信时选哪个版次
写信之前,聪明的写信人会先问一句:收信人懂第几版词典?ONNX 导出就是“写信动作”,主流框架都给了选版参数:PyTorch 用 torch.onnx.export(..., opset_version=13),TensorFlow 系用 tf2onnx.convert(..., --opset 13),Paddle2ONNX 也有同样的入参。而且框架默认值一直在水涨船高——PyTorch 从早期默认 9、11,到 1.11 之后默认 13,再到后续版本逐步抬高。
选版原则可以浓缩成一个取最小值公式:可用版本 = min(转换工具支持的最高版本,目标引擎支持的最高版本)。实际操作中,先去查目标推理引擎的支持表,选“引擎稳定支持的最高版本”,而不是无脑追新。只有当模型确实需要某个新算子(比如 Gelu)时,才有必要为它把版本往上顶。
选错版本的代价是实打实的。版本选太高,老引擎在文件头检查时直接拒收,报“Opset version too new“;版本选太低,新算子没有对应词条可写,导出工具要么报错,要么悄悄把算子展开成一串等价旧算子,模型体积膨胀、性能还可能下降。所以”写信人”必须同时了解自己的字(算子)、对方读得懂的版次(引擎支持范围),才能落笔。
版本是写信人与收信人之间的契约。选定一个双方都能接受的版次,这封信才算真正“寄得出去”。
- export(写信动作)把训练框架的模型翻译成 ONNX 格式的导出函数。
- model / inputs(信的内容)待导出的模型与一组示例输入,用于追踪张量形状。
- path(收信地址)输出
.onnx文件的保存路径。 - opset_version=v(选版)决定转换器按第几版词典生成算子,并写入文件头的版次声明。
升级迁移方法:把旧信改写成新写法
手里有一批按旧版词典写的信,现在收信人换了、要求新版——怎么办?ONNX 提供了一位“自动翻译助手”:onnx.version_converter.convert_version(model, target),它能把算子从旧版语义批量重写成目标版本。但这位翻译并不是万能的:遇到自定义 domain、语义模糊或跨度过大的算子,它会直接罢工报错。
所以业内更推荐“两条腿走路”。第一条腿:只要还能拿到原始训练代码,就直接升级导出脚本、重新导出——这样生成的模型语义最新、最干净,不是翻译而是“重写”;第二条腿:只有 .onnx 文件时,才用官方 converter 做迁移,而且建议小版本逐步走,比如 11→12→13,而不是 9 一步跳到 20。跨度过大会让多个语义变化叠加在一起,出了问题很难定位是哪一个词条翻译错了。
升级的完整流程是一套“校对工序”:备份原始文件 → 扫描弃用算子并替换 → 用 converter 逐版升级 → 用 checker 做语法检查 → 新旧引擎各跑一遍、对比输出数值 → 目标引擎上做回归测试。这其中的每一次校对,都是为了回答同一个问题:新写法是否真的和旧写法表达了同样的意思。
把旧信改写成新写法,本质上就是“自动翻译 + 逐字校对”。翻译交给工具,校对必须交给流程——数值对比与回归测试,就是校对员手里的红笔。
- OK(model→v')(升级判决)整个模型能否安全升级到目标版次的最终判断。
- 合取符号 ∧(逐节点检查)所有节点必须全部满足条件,任一失败则整体失败。
- supported(n, v')(词条存在性)目标版词典中是否收录了该算子。
- sem 等价(义项一致性)验证新旧语义是否一致,不一致时需人工改写子图。
向后兼容策略:读者如何读懂跨版次信件
收信人(推理引擎)有一套自己的阅读规则:只要声明的版次不超过自己能懂的版次上限,这封信通常就能读下来。换句话说,一本支持到第 14 版的读者,可以顺畅阅读第 1~14 版任何一封信——这就是“旧模型在新引擎上运行”的向前兼容。反过来,如果一封信声明按第 16 版书写,读者只学到第 13 版,那就只能逐字遇到“查无此词”,报错拒读。
引擎读取模型时有一个明确的检查环节:解析 opset_import,与自身支持范围比对。版本号过高直接拒绝加载;版本号在范围内、但某个算子的内核实现缺失时,报"Unsupported operator";更隐蔽的是,即使算子名相同,不同引擎的内核实现也可能有细微行为差异——同是 Resize,ONNX Runtime 与 TensorRT 的坐标处理未必完全一致。这些都需要用真实推理来验证,而不是只看版本号。
面对范围之外的新词,读者也有一套“近义替代”的阅读技巧:把新算子展开成语义等价的旧算子组合。最经典的例子就是 Gelu——如果引擎不认识第 20 版才入库的 Gelu,就用 0.5 * X * (1 + erf(X / sqrt(2))) 这个由 MatMul、Add、Erf 等旧算子拼成的子图来等效替代。这就是“算子降级映射”。
新引擎能读旧信,旧引擎读不了新信——理解这条单向性,部署时就不会在高版本模型遇上老引擎时手足无措:要么升级引擎,要么展开子图。
- Engine(读者)ONNX Runtime、TensorRT 等推理后端,内部维护多套算子实现。
- graph(信纸正文)被解释执行的计算图,包含全部节点与张量流转关系。
- opset_v(版次键)引擎按哪个版次词典查找算子的语义实现。
- v ≤ v_engine(阅读范围)信件版次不高于读者上限时才保证可读,否则需要降级映射。
实践建议与工具:编辑部的质量守则
一本好的词典背后一定有严格的编委会守则。放到 Opset 实践中,版本选择要回答三个问题:目标引擎最高支持到哪个版次?模型里到底需要哪些新算子?升级带来的语义变化团队敢不敢买单?生产环境的答案通常偏向保守——锁定一个版本,把升级当作独立项目来立项,而不是随手改个数字就上线。
迁移检查清单是部署工程师的“校对表”,至少包含八项:opset 声明核对、算子覆盖扫描、弃用算子替换确认、IR 版本一致性、动态维度与边界测试、数值输出相对误差对比、目标引擎实跑、回归基准集结果比对。每一项都是可执行、可留痕的动作,而不是口头承诺。
工具链方面,官方与社区已经备齐了整套“编辑工具”:onnx 库提供 checker 与 version_converter,负责语法校验与版本翻译;ONNX Runtime 负责真实推理与精度对比;Netron 负责把计算图可视化,方便人眼审查算子细节;onnxsim 负责常量折叠与图精简;Polygraphy 等工具进一步做逐层精度对比。把它们串进 CI,每次模型变更都自动跑一遍“编辑部质检”。
编委会守则的最终目的,是让每一封“信”在出版前都经过同样的质检流水线。流程跑得越规范,线上翻车的概率就越低——这就是工程素养。
- checker(model)(语法校对)用官方检查器验证模型结构合法、字段完整。
- 相对误差(原意比对)对比升级前后推理输出,用 L2 范数归一化度量偏差。
- epsilon(容错阈值)允许的误差上限,一般取 1e-3 量级,过严会误伤浮点差异,过松会放过语义错误。
AI 应用场景
场景一:模型上线前的 Opset 体检
阶段:模型部署与兼容性检查
解决的问题:传统做法是模型推到推理引擎上报错才回头排查,浪费迭代时间。用 Opset 知识做前置体检,可以在导出阶段就拦下“版本超界”“弃用算子”“语义差异”三类风险。
具体案例:某团队导出 YOLOv5 到 TensorRT,第一次部署报 "Unsupported operator: Resize with coordinate_transformation_mode"。排查后发现是导出 opset 太高、TRT 的解析器不认新语义。改用较低但引擎稳定支持的 opset 重新导出后,问题消失。
场景二:旧模型资产的批量升级迁移
阶段:模型资产管理、跨版本迁移
解决的问题:公司沉淀了数百个 opset 9 的旧模型,新集群统一部署新版 ONNX Runtime。虽然新引擎能读旧信,但为利用量化算子与新优化,需要批量升级到 opset 13,并保证每个模型输出不漂移。
具体案例:平台团队写了一条自动化流水线:加载模型 → 扫描算子清单 → 用 version_converter 逐版升级 → checker 校验 → 在 CPU 与 GPU 上各跑一遍参考输出 → 对比相对误差并生成报告。200 个模型全部在一周内完成迁移,误差异检率为零。
场景三:LLM 新算子在旧引擎上的落地
阶段:推理优化、算子适配
解决的问题:新模型用了 opset 20 才入库的 Gelu,但线上 TensorRT 版本较老、不支持该算子。直接升级整个推理引擎成本太高,需要用“等价子图降级”的思路快速落地。
具体案例:优化工程师把 Gelu(x) = 0.5 * x * (1 + erf(x / sqrt(2))) 展开成 MatMul、Add、Erf、Mul 等旧算子组合,导出为低 opset 模型,再配合自定义插件完成推理。上线后吞吐只下降 3%,却省去了整轮引擎升级的排期。
Python 演示
import onnxfrom onnx import TensorProto, checker, helper, version_converter TARGET_OPSET = 13 # 构造一个最小模型: Relu 与 Sigmoid 相加# 相当于用第 11 版词典写了一封短信X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [1, 4])Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [1, 4])relu = helper.make_node("Relu", ["X"], ["r"])sigmoid = helper.make_node("Sigmoid", ["X"], ["s"])add = helper.make_node("Add", ["r", "s"], ["Y"])graph = helper.make_graph([relu, sigmoid, add], "mini_model", [X], [Y]) model = helper.make_model(graph)model.ir_version = 5model.opset_import[0].version = 11checker.check_model(model)print("升级前 opset:", model.opset_import[0].version) # 1) 扫描模型用到的算子清单used_ops = sorted({node.op_type for node in model.graph.node})print("模型用到的算子:", used_ops) # 2) 对照已知弃用表, 提前发现风险RENAMING = { "DynamicSlice": "Slice", "BatchMatMul": "MatMul", "Scatter": "ScatterElements", "Upsample": "Resize",}for op in used_ops: if op in RENAMING: print("注意:", op, "已弃用, 建议替换为", RENAMING[op]) # 3) 用官方转换器升级到目标 opsetconverted = version_converter.convert_version(model, TARGET_OPSET)converted.ir_version = 7checker.check_model(converted)print("升级后 opset:", converted.opset_import[0].version) # 4) 输出节点清单, 供数值对比前的语义核对for node in converted.graph.node: print("节点:", node.op_type)
model.opset_import[0].version = 11手动把版次声明写成第 11 版,模拟一封“旧信”。checker.check_model(model)官方语法校对器,模型结构不合法会直接抛异常。used_ops = sorted({node.op_type for node in model.graph.node})用集合推导式扫描图里所有算子名,去重后排序。version_converter.convert_version(model, TARGET_OPSET)调用官方翻译助手,把算子批量重写成目标版语义。converted.ir_version = 7升级后把消息结构版本同步到与 opset 13 匹配的档位。
常见误区
误区导出 ONNX 时直接选最新版 opset,觉得版本新就代表模型先进、引擎支持肯定也跟得上。
正解Opset 是写信人与收信人之间的契约,不是性能指标。版本越高,对读者的要求也越高——老引擎会直接拒收。正确做法是查目标引擎的支持表,选“引擎稳定支持的最高版本”,只有在需要新算子时才往上顶。就像给小学生写信,用最高级词典反而让对方查不到词。
为何常见:直觉上“新版本”总是和“更好的兼容性”挂钩,忽略了兼容性取决于最薄弱的收信人环节。
误区把 `ir_version` 和 `opset_import` 混为一谈,以为改大 ir_version 就等于升级了算子语义。
正解ir_version 是信封的印刷版式,管 protobuf 怎么解析;opset_import 是词典版次,管算子语义怎么解释。两者独立存在、各司其职。升级算子语义必须改 opset,改 ir_version 只是换了信封样式,信纸上的字义一个都没变。
为何常见:模型文件头同时出现两个版本号,小白很容易把它们当成同一个概念的两处写法。
误区把模型从 opset 11 升到 13 后直接上线,认为版本号变了但算子行为一定保持不变。
正解同一算子在不同版次下可能是“同字不同义”。Squeeze 的 axes 从属性变输入,Resize 的坐标模式调整,Pad 的 pads 顺序变化——这些都会影响实际输出。升级后必须做数值对比与回归测试,绝不能把升级当成改数字游戏。
为何常见:“版本升级=向后兼容”的惯性思维,忽略了 ONNX 明确允许的破坏性语义修订。
误区只要模型声明的 opset 不超过引擎支持上限,就认为一定能顺利推理。
正解版本号只是入场券,真正的门槛是具体算子的内核实现。自定义 domain 算子、引擎尚未实现的算子、以及同名算子的数值细节差异,都可能导致运行失败或结果漂移。版本号检查是必要条件,不是充分条件——就像读者认识 13 版词典,但不代表认识书里每一个生僻字。
为何常见:把抽象的版本范围当成了具体的算子覆盖保证,忽略了引擎实现滞后于规范声明的事实。
误区扫了一遍主图节点,没看到 Upsample、Scatter,就断言模型不涉及任何弃用算子。
正解弃用算子可能藏在子图、嵌套的 If/Loop 分支、以及框架自动生成的辅助节点里。更隐蔽的是,有些算子虽未标记弃用、语义却在某个版本默默变化(如 Resize)。正确姿势是用 Schema 历史做全图扫描,结合数值对比确认,而不是肉眼扫主图。
为何常见:人为只检查“看得到的地方”,而 ONNX 图结构天然支持嵌套子图,视觉排查容易漏检。
面试题
Q1. ONNX 模型里“按第几版算子字典书写”的声明写在哪个字段?
- A.opset_import 字段← 正确答案
- B.ir_version 字段
- C.node.domain 字段
- D.graph.name 字段
答案:A。opset_import 是版次声明,记录 domain 与 version,告诉引擎按第几版词典解读算子。ir_version 只管消息结构,node.domain 标注算子所属域,graph.name 是图的名字,都与版次无关。
Q2. PyTorch 导出 ONNX 时,选择算子字典版本使用哪个参数?
- A.opset_version=13← 正确答案
- B.ir_version=13
- C.onnx_version=13
- D.model_version=13
答案:A。torch.onnx.export 通过 opset_version 参数指定导出版次,转换器会据此生成对应语义的算子。ir_version 是结构版本不是算子语义,onnx_version 是库版本,model_version 是业务版本,都不对。
Q3. Squeeze、Unsqueeze 等算子的 axes 从属性改为输入,发生在哪个版本?
- A.opset 9 版本
- B.opset 11 版本
- C.opset 13 版本← 正确答案
- D.opset 16 版本
答案:C。opset 13 是一次“大修订”,把 Squeeze、Unsqueeze、ReduceSum、ReduceMin、ReduceMax 等一批算子的 axes 从 attribute 迁移到 input,支持动态 shape。opset 9、11 分别是量化算子与序列算子登场,opset 16 是 GridSample 等视觉算子入库。
Q4. 下列“弃用→替代”配对,哪些是正确的?
- A.Upsample 用 Resize 替代← 正确答案
- B.Scatter 用 ScatterElements 替代← 正确答案
- C.DynamicSlice 用 Slice 替代← 正确答案
- D.BatchMatMul 用 Relu 替代
答案:ABC。Upsample 在 opset 10 弃用、13 移除,由功能超集 Resize 接管;Scatter 在 11 被拆分为 ScatterElements 与 ScatterND;DynamicSlice 在 10 并入支持负步长的 Slice;BatchMatMul 在 9 并入 MatMul。Relu 是激活函数,与矩阵乘法毫无关系。
Q5. 一台推理引擎最高支持 opset 13,导出模型时应优先选择哪个版本?
- A.尽量选择最新版 21
- B.选择引擎支持的 13← 正确答案
- C.选择更低版本 9
- D.任选版本均可
答案:B。写信要选收信人能读懂的版次。引擎支持上限是 13,选 21 会被拒绝;选 9 虽然能读但可能缺少新算子支持。选“引擎稳定支持的最高版本 13”既能覆盖最多算子,又不超出读者范围。
Q6. Upsample 算子在哪个版本被正式移除?
- A.opset 10 正式移除
- B.opset 11 正式移除
- C.opset 13 正式移除← 正确答案
- D.opset 9 正式移除
答案:C。Upsample 在 opset 10 被标记弃用并推荐改用 Resize,到 opset 13 正式移除。这就是“先标注生僻字、再删除词条”的标准节奏,中间留出的迁移窗口正是给存量模型改造用的。
Q7. 把旧模型从 opset 11 升级到 13,推荐做法包含哪些?
- A.先升级再对输出做数值对比← 正确答案
- B.扫描并替换弃用算子← 正确答案
- C.跳过验证直接替换版本号
- D.用官方转换器逐步升级← 正确答案
答案:ABD。正确的迁移流水线是:先扫描弃用算子并替换,再用 version_converter 逐步升级,最后用 checker 与数值对比验证语义一致。跳过验证直接改版本号是灾难性做法,因为 11 到 13 之间发生了大量属性变输入与语义调整。
Q8. 某算子在更高版本中行为发生变化,这种变更通常被称为?
- A.breaking change 破坏性变更← 正确答案
- B.shape inference 形状推断
- C.constant folding 常量折叠
- D.weight pruning 权重剪枝
答案:A。当同一算子在新版词典中义项改变、旧写法失效时,就是破坏性变更(breaking change)。Shape inference 是形状推断、constant folding 是常量折叠、weight pruning 是网络剪枝,都是无关的优化技术名词。
Q9. 跨 Opset 迁移的检查清单中,必须包含哪些验证动作?
- A.数值输出相对误差比对← 正确答案
- B.算子支持性扫描← 正确答案
- C.动态维度与边界测试← 正确答案
- D.忽略自定义域算子
答案:ABC。迁移的核心是“新语义与旧语义等价”。数值比对验证结果一致,算子扫描检查词条覆盖,动态维度测试覆盖形状变化场景。自定义域算子恰恰最容易被官方转换器忽略,是排查重点,绝不能不查。
Q10. 官方提供的 Opset 版本转换工具位于哪个模块?
- A.onnx.version_converter← 正确答案
- B.onnxruntime.apply_model
- C.tf2onnx.merge_duplicates
- D.polygraphy.runner_util
答案:A。onnx.version_converter.convert_version(model, target) 是官方版本翻译助手,负责把算子从旧版语义重写成目标版本。其余选项分别是运行时优化、图去重和精度分析工具,不负责版本迁移。