
onnx.checker.check_model 报错先别动模型。TaoToken https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 上先建一把 Key把 Codex 的 Base URL 填成 https://taotoken.net/api然后在本地把 check 跑一遍把完整报错、模型里的 opset 版本、graph 的节点清单一起贴过去让它逐条帮你对照着看。ONNX 是跨框架的模型中间表达框架而 checker 只校验 ModelProto 这份序列化描述自不自洽算子在当前 opset 版本里能不能找到、图的边有没有断、value_info 里的形状写全了没有。它不判断模型精度也不替你跑推理所以报错里的每一句话都精确落在描述层的某个字段上而不是模型效果好不好。很多人看到CheckerError的第一反应是回训练脚本重导一遍这动作本身没错但顺序错了。校验失败往往只有一两个字段的问题先看清楚它骂的是哪一层再决定是重导、改图还是补形状能省掉大量来回折腾的时间。1. CheckerError 出现时先分清它到底在骂哪一层ONNX 的模型文件本质是一个 protobuf 序列化出来的ModelProto。你在 Python 里onnx.load()拿到的就是这个结构体checker 做的事情就是从头到尾走一遍这个结构体逐字段比对规范。它不会去读权重里的数值也不会去推断你的模型能不能训出好结果所以报错信息里的关键词非常值得逐字看——opset、graph、value_info、SSA这几个词直接指向不同的修改动作。1.1 把完整堆栈留下来最后一行信息量最小checker.check_model()抛异常时终端最下面那行通常只有一句onnx.onnx_cpp2py_export.checker.ValidationError: ...真正有用的内容在冒号后面那一段。养成习惯把从Traceback到异常信息末尾整段复制下来同时记录三样东西——你装的onnx版本、模型里的ir_version、以及opset_import列表。这三样合起来才能判断是导出端的锅还是加载端的锅。如果模型是别人给你的你连导出脚本都看不到那就更要把结构信息打出来。下面这段代码不涉及任何推理只是把描述层的关键字段读出来本地跑一遍就能拿到排查所需的基础事实import onnx from onnx import checker model onnx.load(model.onnx) print(ir_version:, model.ir_version) print(producer:, model.producer_name, model.producer_version) for op in model.opset_import: print(opset domain:, op.domain or ai.onnx, version:, op.version) print(node count:, len(model.graph.node)) print(input count:, len(model.graph.input)) print(output count:, len(model.graph.output)) print(initializer count:, len(model.graph.initializer)) print(value_info count:, len(model.graph.value_info)) try: checker.check_model(model) print(check passed) except checker.ValidationError as e: print(check failed:) print(str(e))opset_import那条循环尤其重要domain为空字符串时代表的是默认域ai.onnx。很多人打印出来看到一行空白就以为是坏了其实那是正常写法。1.2 三类高频报错对号入座校验失败的文案看起来五花八门归一下类其实集中在三个方向。下表是排障时最省事的对照表拿到报错先在左边找关键词再去右边对应动作报错片段指向的层优先排查的动作No Op registered for Xxx with domain_version of N算子集版本检查导出时的opset_version或改用低版本算子实现Graph must be in single static assignment (SSA) form命名空间检查是否有节点输出名重复Nodes in a graph must be topologically sorted图结构检查节点顺序输入是否引用了尚未产生的输出names must be valid C identifiers命名规则检查名字里是否带.、/、:之类的字符Field shape of type is required but missing形状信息跑一遍shape_inference.infer_shapesUnrecognized attribute: xxx for operator Yyy算子属性该属性在新版被改名或移除需要降 opset把这六行存下来下次报错直接对号入座比漫无目的地搜关键词快得多。注意一点报错里出现的是第一个被发现的错误修完之后很可能还会冒出第二个所以排查是循环的不是一次性的。2. opset、graph、value_info 这三块决定了 check 的通过率回到 ONNX 规范本身。一个模型必须先声明它依赖哪些算子集再给出计算图最后才是图里各个值的类型和形状。checker 就是按这个顺序逐层往下验证的所以我们的排查顺序也应该顺着来跳步只会来回打转。2.1 opset_import 是你手里的算子池子opset_import定义了「这个模型可以从哪些域里取算子每个域的版本号是多少」。所有模型都会隐式导入默认的 ONNX 算子集你可以显式写出来也可以靠默认值。关键点在于某个属性或某个算子在哪个版本被引入、在哪个版本被废弃是有明确时间线的。举个常见的例子Pad算子在较早的 opset 里用pads、value作为属性传参到较新的版本里改成了通过输入张量传入。如果你把一个用新写法描述的节点放到一个声明为旧版本的模型里checker 立刻就会报属性不认识。这不是模型坏了是描述和声明不匹配。查看当前模型声明的版本用前面那段代码就够了。判断某个算子在你的目标版本里存不存在可以在模型广场拿到可用的模型 ID 之后让 Codex 帮你比对两段导出脚本的差异——它擅长做这种字段级的对照但结论仍然要你自己在本地跑 check 验证。2.2 graph 的四份清单input、output、initializer、nodegraph是真正装东西的地方。它由元数据字段、模型参数列表和计算节点列表组成落到 Python 结构上就是graph.input、graph.output、graph.initializer、graph.node这几份清单。计算图被组织成一个拓扑排序的节点列表这意味着节点按顺序排好后任何一个节点的输入必须在它之前已经被某个节点的输出、或者图的输入、或者初始化器提供。图的边就是靠「后续节点的输入名等于前面某个节点的输出名」建立起来的。名字对不上边就断了边断了拓扑排序的校验就会失败。初始化器initializer是那些在推理时不需要外部喂数据的常量张量也就是权重和偏置。它们和graph.input里的名字不能冲突这一点在手工拼图时特别容易踩到。2.3 value_info 空了不一定错缺 shape 才报错graph.value_info存的是中间张量的类型和形状信息。它和graph.input、graph.output一样用的是ValueInfoProto结构。这里有个非常容易误解的地方value_info为空是完全合法的ONNX 并不要求你把每一个中间张量的形状都写出来。真正会报错的是「声明了value_info但里面的type字段缺了shape」。这种情况通常出现在你手写模型、或者某个转换工具只填了名字没填形状的时候。如果你从 PyTorch 导出后直接 check 通过、但拿去做图优化时报形状相关的错那多数是优化 pass 需要value_info而原始模型里没有补一遍 shape inference 就好。3. 用 Codex 对照报错之前先把 TaoToken 通道配好排查过程中最费时间的不是改代码而是在一堆报错文案里找出「这句话到底对应规范里的哪个字段」。这类字段级对照交给 Codex 很合适但它需要一个稳定可用的模型入口。官方通道在频繁试错时容易碰到额度边界这里改成走统一 API 通道工具配置一次后面排查全程不用再管。3.1 在 TaoToken 控制台建 Key、在模型广场挑模型 ID先打开 TaoToken 完成注册并创建 API KeyKey 在控制台的 API Keys 页面生成生成后复制出来下面配置里统一用占位符YOUR_API_KEY代替别把真实 Key 写进任何要提交到仓库的文件。模型 ID 不要凭记忆写。不同通道对模型 ID 的命名习惯不一样写错一个字符就是 404。正确做法是打开 模型广场 看当时列表里实际可用的 ID复制哪个用哪个。同理套餐和额度这类信息也以页面当时显示为准不要参考任何二手截图。3.2 ~/.codex/config.toml 里的 base_url 不要带 /v1Codex 的配置走的是~/.codex/config.toml不是环境变量那一套别把别的工具的变量名套过来。下面这份是完整可用的写法model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat两点必须确认base_url填https://taotoken.net/api末尾不要加/v1加了这个后缀路径就对不上env_key写的是环境变量的名字不是 Key 本身所以还要在 shell 里导出一次export TAOTOKEN_API_KEYYOUR_API_KEY想让它每次开终端都生效就把这一行追加到~/.zshrc或~/.bashrc。model字段填你在模型广场复制的 IDmodel_provider要和下面的[model_providers.taotoken]段名保持一致写错了 Codex 会直接忽略这段配置继续走默认通道。不同版本的 Codex 对字段命名可能略有出入改完先启动一次看它有没有报解析错误。3.3 提问模板把报错、opset、节点清单一次性喂进去配置通了不代表问得好。ONNX 排障最忌讳只丢一句「check 不通过怎么办」那样拿回来的一定是泛泛而谈。有效的问题应该包含四块内容完整报错文本、ir_version和opset_import列表、出问题节点前后的几行节点定义、以及你的导出代码或转换流程。可以照着这个结构组织本地跑 onnx.checker.check_model 失败报错原文如下 粘贴完整 ValidationError 文本 模型信息 ir_version ... opset_import [domainai.onnx, version...] 相关节点定义 粘贴 model.graph.node 里报错涉及的节点 导出方式PyTorch torch.onnx.exportopset_version... 请帮我逐项对照报错指向的是 opset 版本、图结构还是 value_info 每一项给出在本地如何验证的最小代码片段。最后一句是关键——要求它给出在本地验证的片段而不是替你把模型改好。ONNX 文件只有你手上有Codex 看不到你的磁盘任何「我帮你重新导出一次」的说法都不成立。它的价值是告诉你该查哪个字段执行动作永远在你自己这边。4. 按报错逐项改从 opset 版本到 shape inference 的动手顺序有了对照结论接下来是真正动手。顺序建议从版本类问题开始因为版本不对的话后面结构类的校验根本走不到改了半天也看不到新报错。4.1 版本类报错先查导出端的 opset_version如果报错是No Op registered for Xxx先确认两件事模型声明的 opset 版本是多少你调用的这个算子在那个版本里存不存在。导出端最直接的调整就是指定版本号import torch torch.onnx.export( model, dummy_input, model.onnx, opset_version13, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, )opset_version不要随手写成很激进的数字如果你的下游推理引擎只支持到某个版本那导出的模型再新也没意义。反过来如果你拿到的模型声明的版本很新而本地装的 onnx 版本比较旧checker 会因为不认识新算子而报错——这种情况升级 onnx 包就行不用动模型。4.2 结构类报错SSA、拓扑排序、命名规则Graph must be in single static assignment (SSA) form的意思是一个名字在同一个命名空间里只能被赋值一次。节点、输入、输出、初始化器和属性各自有不同的命名空间但节点输出这一类的名字全局唯一。手工用helper.make_node拼图时很容易复制粘贴导致输出名重复。用几行代码就能自查import onnx model onnx.load(model.onnx) seen {} for idx, node in enumerate(model.graph.node): for out in node.output: if out in seen: print(重名:, out, 出现在节点, seen[out], 和, idx) seen[out] idx拓扑排序的问题则通常是「某个节点的输入名在它之前既不是 graph input也不是 initializer 的输出也不来自前序节点」。把节点按顺序遍历一遍维护一个可用名字集合遇到输入不在集合里的就打印出来一眼就能定位。命名规则方面ONNX 要求名字遵守 C 标识符语法。带点号、斜杠、冒号的名字在某些框架内部看着正常序列化进图里就会报错。导出后统一做一次名字清洗是值得的。4.3 形状类报错infer_shapes 之后再 full_checkField shape of type is required but missing这类报错处理起来最省事因为它有标准解法——跑一遍形状推断import onnx from onnx import shape_inference model onnx.load(model.onnx) inferred shape_inference.infer_shapes(model) print(推断后 value_info 数量:, len(inferred.graph.value_info)) onnx.checker.check_model(inferred, full_checkTrue) onnx.save(inferred, model_inferred.onnx)full_checkTrue会额外做更严格的形状一致性校验排查阶段建议打开。跑完之后value_info里会多出大量中间张量的形状信息很多原本模棱两可的报错会直接消失。注意推断后的模型要另存一份别直接覆盖原始文件方便对比。4.4 自定义域domain 没注册谁也救不了如果opset_import里出现了非ai.onnx的域比如某些厂商自定义的域而本地环境没有注册对应的算子定义checker 一定会拦下来。这种情况不是配置问题也不是 Key 问题是算子定义本身缺失。你要么拿到算子定义所在的包要么让导出端改用标准算子重新表达这段逻辑。任何声称跳过 checker 校验的做法都不解决问题只会把错误推到推理阶段那时候报错更难查。5. 改完再 check 一遍确认结论再收工改完不要只跑一次就完事。ONNX 的校验是逐项报错的第一个错修好后第二个才浮出来。建议把「打印结构信息 → check → 打印新报错」做成一个固定循环每轮只改一处这样一旦某次改错了你能立刻知道是哪一步引入的。5.1 一段可以反复跑的本地排查脚本把前面几段拼起来就是一份可以重复使用的自查脚本。它不修改模型只读不写安全得很import onnx from onnx import checker, shape_inference MODEL_PATH model.onnx model onnx.load(MODEL_PATH) print(ir_version , model.ir_version) for op in model.opset_import: print(opset, op.domain or ai.onnx, op.version) print(nodes , len(model.graph.node)) print(value_info , len(model.graph.value_info)) try: checker.check_model(model) print([原始模型] check passed) except checker.ValidationError as e: print([原始模型] check failed:, e) inferred shape_inference.infer_shapes(model) print(inferred value_info , len(inferred.graph.value_info)) try: checker.check_model(inferred, full_checkTrue) print([推断后模型] check passed) except checker.ValidationError as e: print([推断后模型] check failed:, e)两侧结果对比一下很多时候就能分清问题是模型本身描述有问题还是单纯缺形状信息。分清了再去对话里问提问范围会小很多。需要再强调一遍上面这些 check、推断、导出全部由你在本地执行。Codex 能帮你对照报错文案、解释某个属性在哪个 opset 版本被废弃、甚至帮你写清洗节点名字的脚本但它不会连上你的机器去替你跑onnx.checker。把本地跑出来的结果贴回对话这条链路才是成立的。5.2 回控制台看这次调用有没有记上账排查一轮下来对话可能来回几十次这时候值得回控制台确认一下调用记录对不对得上。先去 TaoToken 模型对话 用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错如果这类排障是长期工作可以顺手看看 Coding Plan 的套餐是否够用Key 本身则在 控制台 API Keys 管理用完觉得不放心就直接吊销重建换完记得同步更新本地环境变量。最后留一句经验ONNX 的校验报错极少是「模型坏了」绝大多数是描述和声明对不上。把opset_import、graph 拓扑、value_info这三块按顺序过一遍比反复重导模型有效得多。