ONNX 节点测试模式(Node Test Pattern)完整指南:为算子编写 backend test 用例

ONNX 节点测试模式(Node Test Pattern)完整指南:为算子编写 backend test 用例 人工智能深度学习机器学习【免费下载链接】onnxOpen standard for machine learning interoperability项目地址https://gitcode.com/gh_mirrors/onn/onnx点击查看免费下载本文是 ONNX 开源仓库中 .agents/skills/add-op/references/node-test-pattern.md 的深度展开版面向需要在 ONNX 仓库中新增算子、或为既有算子补充测试覆盖的开发者。文章以该文档给出的「节点测试模式」为核心骨架结合仓库中onnx/backend/test/case/目录的真实源码实现完整讲解测试文件的组织位置、export*静态方法与expect助手的运行原理、测试用例的注册与收集机制、参考实现的配合方式以及测试数据的生成与执行方法。读完后你将能按照 ONNX 官方约定为自己的算子编写一份可被 backend 测试基础设施自动发现、校验并执行的节点测试文件。一、节点测试在 ONNX 仓库中的定位ONNX 仓库的算子测试分为多个层次节点测试node test负责验证「单个算子节点」的语义是算子正确性的第一道防线与之配套的还有参考实现reference implementation、形状推断测试、版本转换测试等。在新增算子add-op的整体流程中节点测试是明确的一环。根据 .agents/skills/add-op/SKILL.md 中的文件清单新增一个算子通常需要触及组件文件位置Schema 定义onnx/defs/domain/defs.cc算子集注册onnx/defs/operator_sets.h类型/形状推断Schema 内联的.TypeAndShapeInferenceFunction(...)参考实现onnx/reference/ops/op_lowercase_name.py节点测试onnx/backend/test/case/node/lowercase_name.py形状推断测试tests/python/shape_inference_test.py版本转换适配器onnx/version_converter/adapters/name_from_to.h完整的算子新增流程详见 docs/AddNewOp.md。本篇文章聚焦于上表中的「节点测试」一栏即onnx/backend/test/case/node/name.py文件的标准写法。二、节点测试模式一个文件对应一个算子原文档给出的模式非常简洁每个算子的测试文件位于onnx/backend/test/case/node/name.py文件名使用算子名的全小写形式文件内定义一个继承Base的类类名与算子名一致PascalCase类中的每个export*静态方法都会自动成为一个独立的测试用例。标准骨架如下保留原文档完整代码from __future__ import annotations import numpy as np import onnx from onnx.backend.test.case.base import Base from onnx.backend.test.case.node import expect class OpName(Base): staticmethod def export() - None: node onnx.helper.make_node( OpName, inputs[x], outputs[y], ) x np.random.randn(3, 4, 5).astype(np.float32) y np.some_operation(x) expect(node, inputs[x], outputs[y], nametest_opname) staticmethod def export_with_broadcasting() - None: node onnx.helper.make_node( OpName, inputs[x, y], outputs[z], ) x np.random.randn(3, 4, 5).astype(np.float32) y np.random.randn(5).astype(np.float32) expect(node, inputs[x, y], outputs[x y], nametest_opname_bcast)要点拆解make_node只声明图结构不负责计算onnx.helper.make_node(OpName, inputs[x], outputs[y])仅生成一个NodeProto其中的inputs/outputs是符号名称字符串不是真实数据。真实数据由后面的 numpy 数组x、y提供expect会负责把符号名与数据一一对应起来。预期输出由 numpy 直接计算y np.some_operation(x)是在 Python 侧用 NumPy 实现的「参考结果」它不依赖 ONNX 自身的执行引擎从而避免「用被测实现验证被测实现」的循环依赖。expect是注册入口expect(node, inputs[x], outputs[y], nametest_opname)将节点、输入数据、期望输出与用例名打包成一个TestCase。从源码结构看export*方法名还承担了「用例名后缀」的职责在 onnx/backend/test/case/base.py 的process_snippet中name[len(export_) :]会去掉export_前缀剩余的字符串若为空则退化为算子名的小写形式将作为测试代码片段的命名依据。三、export*方法如何变成测试用例Base的元类机制原文档指出「Eachexport*static method becomes a separate test case」其底层实现是Base类使用的元类_Exporter见 onnx/backend/test/case/base.pyclass _Exporter(type): exports: ClassVar[dict[str, list[tuple[str, str]]]] defaultdict(list) def __init__( cls, name: str, bases: tuple[type[Any], ...], dct: dict[str, Any] ) - None: for k, v in dct.items(): if k.startswith(export): if not isinstance(v, staticmethod): raise ValueError(Only staticmethods could be named as export.*) export getattr(cls, k) Snippets[name].append(process_snippet(name, k, export)) # export functions should call expect and so populate # TestCases np.random.seed(seed0) export() super().__init__(name, bases, dct) class Base(metaclass_Exporter): pass该机制揭示了几条重要约定类被定义时即注册无需显式调用只要测试模块被 import元类就会在类定义阶段遍历类字典dct中所有以export开头的成员因此每个export*方法在「import 阶段」就被执行并注册测试用例而不是等到 pytest 收集时才运行。方法必须是staticmethod如果不是静态方法元类会直接抛出ValueError(Only staticmethods could be named as export.*)这正是原文档代码中每个方法都标注staticmethod的原因。随机数种子被固定为 0每次执行export()前都会调用np.random.seed(seed0)保证np.random.randn(...)生成的输入数据可复现。这是 ONNX 测试基础设施刻意为之——同一用例在任何机器、任何时间生成的模型与数据完全一致便于对比与回归。每个方法独立成用例多个export_xxx方法 多个测试用例它们共享同一个类但互不影响。四、expect助手从「节点 数据」到TestCaseexpect是节点测试模式的核心助手其实现位于 onnx/backend/test/case/node/init.py。它的工作流可以概括为五步第一步校验与查重。若全局设置了_TargetOpType按算子过滤收集时使用且当前节点类型不匹配则直接跳过若name已存在则抛出ValueError防止用例名冲突见_existing_names字典。第二步构造图并推断 ValueInfo。通过_extract_value_info见 base.py根据 numpy 数组的dtype与shape自动生成ValueInfoProtonp.float32映射为TensorProto.FLOATshape直接取自数组。因此测试作者不需要手动写make_tensor_value_info——只需提供数据即可。第三步确定 opset 版本。默认情况下未传入opset_importsexpect会查询onnx.defs.get_schema(node.op_type).since_version以该算子的「引入版本」作为模型 opset 版本见 node/init.py。这意味着ops 每次升级版本号后测试模型会自动跟随since_version使用最新 opset避免 opset 变更导致模型版本过时。第四步生成模型。调用_make_test_model_gen_version构造ModelProto并将producer_name设为backend-test。第五步注册TestCase。生成的TestCase包含以下字段见 onnx/backend/test/case/test_case.py字段值说明name传入的name用例名如test_absmodel生成的ModelProto单节点图模型data_sets[(inputs, outputs)]一组输入/期望输出对kindnode用例类型rtol1e-3相对误差容忍度atol1e-7绝对误差容忍度注意rtol1e-3, atol1e-7是节点测试的默认数值比较精度backends 在比对输出时会使用这一标准。4.1 可选输入/输出的处理expect对 ONNX 的可选optional输入/输出做了专门处理节点声明中空字符串表示「该位置省略」。例如一个有三个输入、第二个可选的算子node.input会形如[Param1, , Param3]而inputs参数只包含实际存在的两个值。expect内部通过present_inputs [x for x in node.input if x ! ]过滤后再与inputs一一对应生成 ValueInfo注释见 node/init.py。4.2 函数型算子的自动展开测试对于以 Function函数体定义的算子expect还会自动生成额外的「展开后」测试function_testcase_helper会取出 schema 中各 opset 版本的FunctionProto含 context-dependent function通过function_expand_helper把函数体展开为原始节点序列生成名为test_xxx_expanded必要时附加_ver版本号后缀的附加TestCase见 node/init.py 与 L346-L402。也就是说为一个函数型算子写一份节点测试expect会自动验证其展开后的等价图。五、仓库中的真实示例5.1 最简示例Abs仓库中最简洁的真实用例是 onnx/backend/test/case/node/abs.py与原文档的骨架模式完全一致import numpy as np import onnx from onnx.backend.test.case.base import Base from onnx.backend.test.case.node import expect class Abs(Base): staticmethod def export() - None: node onnx.helper.make_node( Abs, inputs[x], outputs[y], ) x np.random.randn(3, 4, 5).astype(np.float32) y np.abs(x) expect(node, inputs[x], outputs[y], nametest_abs)对应的参考实现位于 onnx/reference/ops/op_abs.pyimport numpy as np from onnx.reference.ops._op import OpRunUnaryNum class Abs(OpRunUnaryNum): def _run(self, x): return (np.absolute(x),)测试中的y np.abs(x)与参考实现中的np.absolute(x)语义一致——节点测试的期望值正是「参考实现应当产生的结果」两者共同锁定算子语义。5.2 属性参数与多用例示例Gemm当算子带有属性attributes时通常的做法是为每个关键属性组合各写一个export*方法。以 onnx/backend/test/case/node/gemm.py 为例它在模块顶部定义了一个独立的 numpy 参考函数然后为每种场景编写用例def gemm_reference_implementation( A: np.ndarray, B: np.ndarray, C: np.ndarray | None None, alpha: float 1.0, beta: float 1.0, transA: int 0, transB: int 0, ) - np.ndarray: A A if transA 0 else A.T B B if transB 0 else B.T C C if C is not None else np.array(0) Y alpha * np.dot(A, B) beta * C return Y.astype(A.dtype) class Gemm(Base): staticmethod def export_default_zero_bias() - None: node onnx.helper.make_node(Gemm, inputs[a, b, c], outputs[y]) a np.random.ranf([3, 5]).astype(np.float32) b np.random.ranf([5, 4]).astype(np.float32) c np.zeros([1, 4]).astype(np.float32) y gemm_reference_implementation(a, b, c) expect(node, inputs[a, b, c], outputs[y], nametest_gemm_default_zero_bias) staticmethod def export_all_attributes() - None: node onnx.helper.make_node( Gemm, inputs[a, b, c], outputs[y], alpha0.25, beta0.35, transA1, transB1, ) a np.random.ranf([4, 3]).astype(np.float32) b np.random.ranf([5, 4]).astype(np.float32) c np.random.ranf([1, 5]).astype(np.float32) y gemm_reference_implementation( a, b, c, transA1, transB1, alpha0.25, beta0.35 ) expect(node, inputs[a, b, c], outputs[y], nametest_gemm_all_attributes)该文件共包含 10 个export*方法default_zero_bias、default_no_bias、default_scalar_bias、default_single_elem_vector_bias、default_vector_bias、default_matrix_bias、transposeA、transposeB、alpha、beta、all_attributes完整覆盖了Gemm的transA、transB、alpha、beta属性组合与各种 bias 形态。这种「一个方法覆盖一个属性/场景」的组织方式值得借鉴属性通过make_node的关键字参数传入如transA1、alpha0.5参考函数同步接收这些属性参数保证期望输出与节点声明一致用例名如test_gemm_transposeA清晰描述所测场景便于定位失败用例。六、测试用例的收集机制测试文件写好后如何被测试框架发现入口是 onnx/backend/test/case/node/init.py 中的collect_testcasesdef collect_testcases(op_type: str | None None) - list[TestCase]: Collect node test cases, optionally filtered to a single op_type. global _TargetOpType _TargetOpType op_type import_recursive(sys.modules[__name__]) return _NodeTestCases其工作方式递归 import 全部节点测试模块import_recursive见 onnx/backend/test/case/utils.py利用pkgutil.iter_modules遍历onnx.backend.test.case.node包下的所有子模块并逐一 import。由于上一节所述的元类机制import 动作本身就是注册动作——所有export*方法在 import 过程中已把TestCase追加进_NodeTestCases列表。可选按算子过滤传入op_type时expect会跳过所有op_type不匹配的节点见_TargetOpType判断从而支持只针对单个算子生成测试。导出为独立数据onnx/backend/test/cmd_tools.py的generate_data子命令会把收集到的TestCase落盘为标准的 backend 测试数据目录输出结构为node/case_name/model.onnx与node/case_name/test_data_set_i/input_*.pb、output_*.pb见 onnx/backend/test/cmd_tools.py。仓库中onnx/backend/test/data/下的.onnx、.pb文件即由此机制生成。七、如何运行与验证节点测试7.1 运行参考实现测试节点测试的期望输出由参考实现保证最直接的验证方式是运行 reference evaluator 测试。仓库的 tests/python/backend_reference_test.py 会加载各节点测试用例用onnx.reference.ReferenceEvaluator执行模型并与期望输出比较使用TestCase中记录的rtol/atol。新增算子后运行pytest tests/python/backend_reference_test.py7.2 通过 DummyBackend 做结构校验tests/python/backend_test.py 中的DummyBackend提供了一种不依赖参考实现的快速校验它的prepare会执行onnx.checker.check_model(model)与严格模式形状推断infer_shapes(model, check_typeTrue, strict_modeTrue)从而验证「测试用例生成的模型本身是合法、形状信息完整的 ONNX 模型」。由于没有真实计算这类测试最终会以「跳过」收尾但结构校验部分已经执行。7.3 生成测试数据需要把测试用例导出为标准测试数据目录时python onnx/backend/test/cmd_tools.py generate-data -o /path/to/output该命令会为每个TestCase生成model.onnx与test_data_set_N/数据目录供外部 backends如 ONNX Runtime按 ONNX 标准格式消费。7.4 添加算子后的完整检查清单根据 .agents/skills/add-op/SKILL.md 的「After Making Changes」一节写完节点测试后还应执行python onnx/defs/gen_doc.py python onnx/backend/test/stat_coverage.py python onnx/gen_proto.py # 仅当 proto 变更时 lintrunner -a --output oneline其中stat_coverage.py用于统计算子测试覆盖率确保新算子已被测试覆盖。八、与参考实现模式的配合节点测试与参考实现reference implementation是新增算子时的「双保险」节点测试本文主题给出「期望输入→期望输出」的数据对是声明式的验收标准参考实现位于onnx/reference/ops/op_name.py给出可执行的算子语义是 onnx.reference 参考求值器ReferenceEvaluator实际运行的代码。两者的命名与类名约定一致如op_abs.py中的Abs且参考实现的可选基类决定了其接口形态单输入单输出数值算子继承OpRunUnaryNum二元算子继承OpRunBinaryNumpy通用算子继承OpRun详见 .agents/skills/add-op/references/reference-impl-pattern.md。编写节点测试时期望输出应始终与参考实现的语义保持一致。九、编写节点测试的实践要点总结结合原文档与仓库实现归纳以下要点文件位置与命名onnx/backend/test/case/node/lowercase_name.py类名用 PascalCase 且与算子名一致。一个方法一个场景每个export*静态方法对应一个独立测试用例属性组合、广播、边界形状等场景应拆分为多个方法并给方法名与用例名起有语义的名字如export_with_broadcasting→test_opname_bcast。数据必须可复现随机数据使用np.random.randn/np.random.ranf因为元类会固定种子为 0也可使用确定性数据如np.zeros、np.arange。dtype 明确指定np.float32是节点测试的主流精度务必通过.astype(np.float32)显式转换避免平台默认浮点精度不一致。期望输出自算用 numpy 独立计算期望输出不要调用 ONNX 求值器生成期望值。用例名全局唯一expect会对重复name抛出ValueError。利用自动展开函数型算子无需手写展开图expect会自动生成_expanded测试。运行验证新增文件后运行pytest tests/python/backend_reference_test.py与tests/python/backend_test.py并用stat_coverage.py确认覆盖率。按照上述模式任何新增算子都能获得一份与仓库既有测试风格一致、可被 ONNX backend 测试基础设施自动发现与校验的节点测试文件。赞分享人工智能深度学习机器学习【免费下载链接】onnxOpen standard for machine learning interoperability项目地址https://gitcode.com/gh_mirrors/onn/onnx点击查看免费下载相关推荐用 react-aria/test-utils 编写 React Aria 组件测试ARIA Pattern Tester 完整指南用 react aria/test utils 编写 React Aria 组件测试ARIA Pattern Tester 完整指南 react aria前端UI组件设计系统国际化状态管理Sway 单元测试完全指南用 forc test 为智能合约编写与运行 [test]Sway 单元测试完全指南用 forc test 为智能合约编写与运行 test 本篇技术指南围绕 Sway 语言官方文档的 Unit Testing 章节展编程语言编译器区块链Unity Test测试用例设计模式可复用测试代码编写方法Unity Test测试用例设计模式可复用测试代码编写方法 Unity Test作为C语言单元测试的轻量级框架提供了多种测试用例设计模式帮助开发者编写可复测试嵌入式上一篇【免费下载】 Fate-Grand-Automata 使用与安装教程下一篇【亲测免费】 RTSPtoWeb 项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考