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

AI瘦身失败事件

一次送入 32 行却被拒绝 — 救活死掉的批次轴

在 TT Lab 中继续学习

目标

用相同的权重,生成批次轴为符号的 /root/onnxq-shape/dyn.onnx 和固定为 1 的 /root/onnxq-shape/fixed.onnx,并编写读取轴并真正放入数值的工具 /root/onnxq-shape/axes.py。最后找出常量形状被写死、批次轴已经失效的模型,把它修复过来。

为什么重要

ONNX 中的轴,要么固定为整数,要么以符号名保持开放,要么干脆为空。三者含义完全不同,但粗略看文件时分辨不出来。想增大批次来测吞吐量的实验在第一步就被挡住,原因多半在这里。 同样重要的是,拒绝发生在放入数值时,而不是打开会话时。若因为会话打开了就断定不是模型的问题,就会跑去挖别的地方。 如果同一个符号名用在两个输入上,那就不是“两者都是动态的”,而是“两者的行数必须相等”这一约束。违反时会被拒绝,但运行时不会点出符号名,而是报出优化之后的节点名称。原因与现象之间隔着一层。 最安静的事故是 Reshape 中写死了常量形状。输入以符号保持开放,但从那个位置起批次轴变成了整数。没有错误也没有警告,形状推断原样相信该常量,给出前后一致的答案。 评分器不会相信你写下的文字。它会在临时目录里摆出自己亲手搭建的文件,真正运行你的工具,把结果与评分器自己读取并运行同一文件得到的答案进行核对。轴名称、形状和写死的行数每次运行都不同。

步骤

  1. 创建并运行 /root/onnxq-shape/build_shapes.py,生成 /root/onnxq-shape/dyn.onnx 和 /root/onnxq-shape/fixed.onnx。
  2. 在 /root/onnxq-shape/axes.py 中实现 dims,读出输入输出的轴是整数还是符号。
  3. 加入 feed,按给定的行数生成数值并真正放入,记录结果或拒绝。
  4. 加入 infer,区分 onnx.shape_inference 填上的与没能填上的。
  5. 加入 symbols,汇总同一个符号名用在哪些位置。
  6. 加入 scan,找出常量形状被写死的 Reshape。
  7. 加入 repair,把写死的形状中的批次位置改回去,并给输出轴重新加上符号名。
  8. 生成 /root/onnxq-shape/shape_report.json 和 /root/onnxq-shape/shape_report.md 作为报告。

参考

做出只有轴声明不同的两个版本

创建并运行 /root/onnxq-shape/build_shapes.py,生成 /root/onnxq-shape/dyn.onnx 和 /root/onnxq-shape/fixed.onnx。两个文件的权重完全相同,只有第 0 轴的声明不同。

在 make_tensor_value_info 的形状列表中,第 0 位放入字符串就是符号,放入整数就固定为该值。权重只创建一次,把同一个对象放入两个计算图——值不同的话,后面就无法比较这两个模型。

读出轴是开放的还是固定的

在 /root/onnxq-shape/axes.py 中实现 dims <모델>(占位符为模型文件),读出运行时输入和输出的轴。整数就记为整数,符号就记为字符串,没有就记为 null。

一个轴要么有 dim_param(符号)或 dim_value(整数)中的一个,要么两者都没有。请把这三种情况区分开来记录。另外,由 initializer 填充的名称不是运行时输入,要从 inputs 中去掉。

真正放入数值试试

加入 feed <모델> <행수...>(占位符依次为模型文件与各输入的行数),为每个运行时输入按该行数生成数值并放入,输出 {"status", "error_type", "message", "shapes"}。即使被拒绝,退出码也是 0。

后面的轴原样使用声明的整数,只把第 0 轴换成给定的行数。拒绝不是失败而是答案——捕获异常,记下类名和第一行,以后那段文案会告诉你在哪个轴期望什么。把打开会话的步骤和放入数值的步骤分开包裹。二者在不同的地方失败。

推断填上的与没能填上的

加入 infer <모델>(占位符为模型文件),区分 onnx.shape_inference 填上的中间张量和没能填上的名称。响应为 {"value_info", "unknown"}。

infer_shapes 会返回新的 ModelProto,填上的结果放在 graph.value_info 中。unknown 是由节点产生、但不是计算图输出的名称中没有被填上的那些。在完全没有中间张量的计算图里,value_info 会以空的状态返回——那不是失败,而是没有东西可填。

相同的名称就是相同的值

加入 symbols <모델>(占位符为模型文件),为每个符号名汇总它出现的位置,写成 "텐서이름:축번호"(韩文占位符,依次意为张量名称与轴编号)。然后向使用同一符号的两个输入放入不同的行数,用 feed 确认被拒绝。

如果两个输入的第 0 轴写着相同的名称,就意味着运行时要求两个行数必须相同。违反时会被拒绝,但运行时不会点出符号名,而是报出优化之后的节点名称。所以预先建好这张表,才能解读那个错误。

找出写死常量形状的 Reshape

加入 scan <모델>(占位符为模型文件),找出目标形状是 initializer 且其第 0 位为正数的 Reshape。响应为 {"frozen_reshape", "shapes"}。

第 0 位为 -1 时由其余轴计算,所以轴是活的。0 表示原样使用输入的那个轴,所以同样是活的。只能挑出正数。如果目标形状不是 initializer 而是其他节点的输出,那就是运行时才确定的,不在这项检查的对象之内。

让已死的批次轴复活

加入 repair <모델> <출력>(占位符依次为模型文件与输出文件),把被写死的常量形状的第 0 位改为 -1,把计算图输出的第 0 轴重新声明为第一个运行时输入的符号名,并保存为新文件。

想就地修改 initializer,可以新建张量并用 CopyFrom 覆盖。输出轴要清除 dim_value,并把名称放入 dim_param。不要动权重和节点——修复后的文件在原来能放入的行数上必须给出相同的值,才能说修复了。

把两个版本并排摆放后做报告

在 /root/onnxq-shape/shape_report.json 中写入 dyn_input、fixed_input 和 trials,并把 /root/onnxq-shape/shape_report.md 写成 ## 어떤 축이 열려 있나、## 굳은 축에 무엇을 넣었나、## 추론이 못 채운 곳、## 보내는 쪽에 요청할 것 四节(四个标题为韩文,依次意为“哪些轴是开放的”“往固定轴里放了什么”“推断没能填上的地方”“要向发送方提出的请求”)。

trials 是记录对哪个模型放入多少行、得到什么结果。评分器会亲自放入同样的组合来核对,所以要实际测量后再写。报告正文中要写出批次轴的符号名,接收方才能用这个名称来交流。