TT Lab
开始
学习 学习路径 课程

AI瘦身失败事件

一个轴被固化,整个批次就过不去

在 TT Lab 中继续学习

一句话总结

ONNX 中的轴只有三种情形:固定为整数、以符号名保持开放、或者根本没有。无法增大批次的事故,绝大多数出在第一种而不是第三种——而且出在不报错就放行的地方。

为什么需要它

要测量量化后的模型快了多少,就得把批次调大。想一次放入 32 个样本,运行时却拒绝了。明明听说模型是按“动态批次”导出的,会话也成功打开了,可一放入数值就卡住。

这时常见的做法是去翻运行时选项。答案其实在文件里。会话能打开,只说明计算图成立,并不说明它会接受我们想放进去的形状。

轴有三种状态

ONNX Concepts 所说的张量形状,每个轴都是三种之一。

第二种里的“相同的名称表示相同的值”常被忽略。如果两个输入的第 0 轴都写着 batch,这就不只是“两个都是动态的”,而是 两者的行数必须相等 这一约束。一边放 5 行、另一边放 3 行,就会被拒绝。

但这种拒绝并不友好。运行时不会告诉你“符号 batch 分裂成了 5 和 3”。它报出的是计算图优化之后的节点名称,说该节点的输入形状不匹配。原因与现象之间隔了一层优化,初次见到时看不出原因。

선언            x [batch, 4]   bias [batch, 3]
넣은 것          x 5행          bias 3행
런타임의 말      (융합된 노드 이름) 의 입력 모양이 맞지 않는다
실제 원인        같은 심볼에 다른 값을 넣었다

推断能填上的与填不上的

onnx.shape_inference 会遍历计算图,把中间张量的形状填入 value_info。输入为 [batch, 4]、权重为 4 x 6 时,中间张量会被填成 [batch, 6]。符号会以符号的形式传播下去。

有两种情况填不上。一种是 输入的形状为空 时。从未知出发,就会一直未知到底。另一种是 根本没有中间张量 时。如果只有一个节点,它的输出就是计算图的输出,那就没有可填的对象,value_info 会以空的状态返回。此时不能解读成“推断失败了”——只是没有东西可填。

还有更危险的情形:推断自信满满地填入了错误的值。如果 Reshape 的目标形状被写成了常量,推断就会原样相信这个常量。即使把输入开放成 [batch, 4],只要 Reshape 指向 [3, 6],其后所有张量都会固定为 3。不会报错。批次轴只在声明中留着,实际上已经死了。

在现场相遇的样子

第一,按批次 1 导出的模型。给导出工具一个示例输入,它的形状就会原样固化下来。如果不指定动态轴,[1, ...] 就会被写死,这个文件永远只能一次处理一条。测量吞吐量的实验就整个失去了意义。

第二,拒绝发生在放入数值时,而不是打开会话时。会话能顺利打开。于是得出“模型加载成功了,所以不是模型的问题”的结论,跑去挖别的地方。固定轴的报错信息会准确写明哪个输入的第几个轴期望什么、收到了什么,只读那一行就够了。

第三,写死了常量形状的 Reshape。这是最安静的事故。打开模型看,输入好端端地开放成 [batch, 4],推断结果也前后一致。只是批次轴从某个位置起已经变成了整数。于是“明明按动态轴导出了,为什么不行”这句话会不断冒出来。解决办法很简单——把批次位置设为 -1,它就会由其余轴计算出来,轴就复活了。

第四,量化虽然不会固定轴,却会掩盖原因。量化后的计算图节点名称变了,Q/DQ 也插了进来,变得难以阅读。所以 轴的问题在量化之前确认 要便宜得多。

实际工作中真正重要的事

下一项实验要做什么

用相同的权重,做出批次轴为符号的版本和固定为 1 的版本并排摆放,再一步步扩展工具 axes.py。用同样的代码向两个文件放入多种行数,记录在哪里分道扬镳;区分推断填上的与没能填上的;向使用同一符号的两个输入放入不同的行数试试。最后找出写死了常量形状的 Reshape,把批次位置改回去,让轴复活。评分器每次都会用不同的轴名称、形状和写死的行数构建自己的文件,真正运行你的工具并核对答案。