ONNX 图里三个容易被工具忽略的细节
这周我修了 ONNX 工具链里的三个 bug:两个在 ONNX Runtime 的图优化器里,一个在我自己写的导出代码里。它们都没有让程序崩溃,只是让模型跑得比应有的慢一点,或者让测试只在 CI 上失败。三个问题都来自 ONNX 格式里一些很小、写图优化或导出器时容易忽略的细节。
1. 空名字的可选输入,不等于省略的输入
ONNX 里省略一个可选输入有两种写法:直接不写在输入列表末尾,Conv(x, w);或者写成空名字,Conv(x, w, "")。两种都表示"没有 bias",也都是合法的。
ONNX Runtime 的 Conv+BatchNormalization、Conv+Add 和 Conv+Mul 融合用 InputDefs().size() == 3 判断有没有 bias。写成 "" 时 Conv 有三个输入,于是走了"有 bias"的分支;接着检查 bias 是不是常量,空输入当然不是,融合就在所有优化级别下都被跳过了(microsoft/onnxruntime#34139)。只把它当成"没有 bias"还不够:没有 bias 的分支用的辅助函数只能往输入列表末尾追加新 bias,而第 3 个位置已经被 "" 占了。
修复(microsoft/onnxruntime#34141)同时检查 InputDefs()[2]->Exists(),并改用一个能填充空位置、也能处理省略情况的辅助函数来设置新的 bias。
写图优化的规则:可选输入只有在位置存在并且名字非空时才算存在,只数输入个数是不够的。
2. 带切片的 Shape 只需要选中的维度已知
从 opset 15 开始,Shape 支持 start 和 end,比如 Shape(x, start=1) 返回除第一维以外的所有维度。导出的模型里经常这样用,通常是为了让符号化的 batch 维不进入 Reshape。
ONNX Runtime 的常量折叠先收集所有维度的值,遇到第一个符号维度就放弃,然后才应用切片。对于 x: [N, 3],Shape(x, start=1, end=2) 永远是 [3],却因为 N 没有被折叠(microsoft/onnxruntime#34140)。修复(microsoft/onnxruntime#34142)先按秩对 start/end 做截断,再只要求选中的维度已知:
输入 [N, 3, 4, M],start, end | 修复前 | 修复后 |
|---|---|---|
| 1, 3 | 不折叠 | 折叠为 [3, 4] |
| -3, -1 | 不折叠 | 折叠为 [3, 4] |
0, 2(包含 N) | 不折叠 | 不折叠 |
写图优化的规则:先应用算子自己的属性,再判断它的输入"是不是不够静态"。
3. onnx 1.21 之前不接受用 Constant(value_ints) 作为 Pad 的 pads
这个是我自己的 bug。我给 onnxscript 写了一个 adaptive_avg_pool 的 torchlib 实现(microsoft/onnxscript#3201),里面有一个 Pad,它的 pads 来自 op.Constant(value_ints=[...])。本地测试全部通过,CI 上几乎所有任务都失败了:
[ShapeInferenceError] 'pads' input must be a 1D (shape: [2 * num_axes]) tensor of type int64
CI 固定使用 onnx==1.18,而我本地是 onnx 1.23。用一个只包含 Pad 的最小模型就能看出区别,pads 是同样的六个 int64 值,只是构造方式不同:
| onnx | Constant(value_ints=...) | Constant(value=tensor) |
|---|---|---|
| 1.18、1.19、1.20 | 形状推断报错 | 正常 |
| 1.21、1.23 | 正常 | 正常 |
把 pads 写成张量在所有版本上都能工作。更一般的教训是:用 CI 固定的依赖版本跑测试,而不只是用本地环境里的版本。我现在保留了一个和 onnxscript CI 版本一致的虚拟环境,推送之前先在里面跑一遍。
一个共同点
这三个 bug 都藏在一个"默认情况能正常工作"的背后:大多数模型直接省略不用的 bias,而不是写 "";大多数 Shape 节点取完整的形状;大多数开发者装的是较新的 onnx。只构造常见写法的测试都能通过。对这三个修复来说,真正有用的测试是专门构造那种少见但合法的写法,再和常见写法对比。
编写修复、构造复现和撰写本文时,我使用了 Claude(Anthropic)辅助。所有结果都经过我本人审阅,测试也由我亲自运行。
包启明,现居新西兰奥克兰的 AI 研究者,研究方向为大模型推理与模型部署。博士毕业于奥克兰大学 Strong AI Lab,参与 PyTorch ONNX 导出器、ONNX Runtime、ONNX Runtime GenAI 和 Microsoft PyRIT 等开源项目的贡献。