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

AI瘦身失败事件

数清你真正拿到的 — 执行提供程序与误差累积

在 TT Lab 中继续学习

目标

编写工具 eprun.py,测出请求的执行提供程序和会话实际拿到的提供程序,数出计算图会按所支持的算子列表被切成几段,并把量化误差在每个层边界如何增长做成表。

为什么重要

执行提供程序是用来请求的,而不是有保障的。在这个环境里亲自测一下,就会发现即使把不存在的提供程序放进列表,也不会抛异常,而是直接落回 CPU 运行。所以“正在用加速器”的错觉,会在日志里不留任何痕迹地持续很久。确认的办法只有一个——去问会话。 提供程序不是承担整个模型,而是只拿走每个节点中自己能承担的部分。所以如果支持列表在计算图的顺序上被截断,模型就会被切成多段,每段之间都要传递值。片段切分方式不同,计算顺序就不同,同一个模型放入同样的输入,不同的机器也可能得出不同的数。 对误差也需要同样的态度。量化误差在每一层都会新产生,前一层的误差会进入下一层。不过它不是每一层均匀增长的,所以不要用说法带过,要在每个层边界测量并做成表,才能看出哪一层有问题。 这个 Pod 里只有两个提供程序(AzureExecutionProvider、CPUExecutionProvider)。不把不存在的加速器当作存在,只处理在这里能观察到的东西。评分器每次都会用不同的模型、随机种子和支持列表,真正运行你的工具,并把同样的计算重做一遍来核对。

步骤

  1. 创建并运行 /root/ep/gen_ep.py,生成 /root/ep/fp32.onnx 和 /root/ep/int8.onnx。层数在五层以上。
  2. 在 /root/ep/eprun.py 中实现 providers,并把各个请求列表的结果写入 /root/ep/providers.json。
  3. 加入 partition,数出在给定支持的算子列表时,计算图会被切成几段。
  4. 加入 expose,生成把层边界张量取成计算图输出的版本,并保存 /root/ep/fp32_exposed.onnx 和 /root/ep/int8_exposed.onnx。
  5. 加入 layers,给出每个层边界的最大绝对误差、相对误差和最小余弦。
  6. 加入 growth,给出逐层叠加时误差增长的比值,并把你这个模型的结果写入 /root/ep/growth.json。
  7. 加入 settings,给出同一个模型、同一个输入只改变设置运行的结果,并写入 /root/ep/settings.json。
  8. 把 /root/ep/ep_report.md 写成四节。

参考

做出叠了多层的模型及其整数版本

创建并运行 /root/ep/gen_ep.py,生成 /root/ep/fp32.onnx 和 /root/ep/int8.onnx。线性算子必须有五个以上。

如果只有两层或三层,就看不出误差增长的样子。请叠五层以上。静态量化需要 CalibrationDataReader,校准数据要从与测试输入相同的分布中抽取,但不必是同一批样本。

把请求的和拿到的并排摆放

在 /root/ep/eprun.py 中实现 providers <model> <목록> [...](占位符依次为模型文件与 EP 列表),并把只写了存在的提供程序的请求,和混入了不存在的提供程序的请求的结果,写入 /root/ep/providers.json。

放入不存在的提供程序,似乎会抛异常,但在这个环境里并不会。亲自放进去试试,测一测会发生什么。session.get_providers() 是答案,请求列表不是答案。也许有的版本会抛异常,所以把异常名称也记下来。另外,回退提示会输出到标准输出,要防止它混进答案——contextlib.redirect_stdout 很好用。

数出计算图会被切成几段

加入 partition <model> <지원연산자목록>(占位符依次为模型文件与所支持的算子列表),模拟如果有一个只支持这些算子的提供程序,计算图会被怎样切开。同时输出片段数和传递次数。

这不是模仿实际的分配,而是数出支持列表在计算图的顺序上被截断的位置。持有者相同且连续的区间就是一段。如果假定整数版本只支持 MatMul,由于 Q/DQ 的存在,片段会增加多少,一眼就能看到。

把层边界张量取出来

加入 expose <model> <out.onnx>,保存把层边界张量追加为计算图输出的版本,并生成 /root/ep/fp32_exposed.onnx 和 /root/ep/int8_exposed.onnx。

在整数版本里,QuantizeLinear 的输出是 int8。如果写成要把它以 float 取出,会话就打不开。线性算子接收输入的那一侧张量,在两个计算图里都是 float,所以是安全的。把保存的版本实际打开并运行一下,再往下走。

在每个层边界测误差

加入 layers <fp32> <int8> <seed>,把用随机种子生成的同一个输入放进两个版本,输出每个层边界的最大绝对误差、大小、相对误差和最小余弦。

只看绝对误差,靠后的层总是显得更差。因为值本身变大了。所以要同时给出除以该层张量最大绝对值得到的相对误差。两个计算图的边界名称不同,请按位置顺序配对。

把增长的比值做成表

加入 growth <fp32> <int8> <seed>,输出各层的相对误差及其比值、是否单调、整体增长倍数、以及跳得最高的层,并把你这个模型的结果写入 /root/ep/growth.json。

测一下就会发现,也会出现比值小于 1 的区间。因为激活函数会截掉一部分,每一层的缩放因子也是重新确定的。不单调本身就是结果,请原样写下。前一层的相对误差为 0 时无法相除,记为 null。

只改变设置,用同一个输入再运行

加入 settings <model> <seed>,把同一个模型、同一个输入只改变优化级别和线程数来运行,输出与基准的差异。把你这个模型的结果写入 /root/ep/settings.json。

在这个 Pod 里,四种情况给出的值都相同。请原样写下这个结果——不要编造说应该不同。重要的不是它们相同这个事实,而是具备了测量相同还是不同的流程。换一台机器,可能会得出不同的答案。

写下什么只在这台机器上成立

把 /root/ep/ep_report.md 写成 ## 무엇을 요청했고 무엇을 쥐었나、## 그래프가 쪼개지는 자리、## 층을 거듭하며 자란 오차、## 이 기계에서만 참인 것 四节(四个标题为韩文,依次意为“请求了什么,拿到了什么”“计算图被切开的位置”“逐层叠加后增长的误差”“只在这台机器上成立的事”)。可用的提供程序个数和整体误差增长倍数,要以数字写进去。

最后一节是这份报告的关键。请写下在这里测得的值里,哪些与这个 Pod 的提供程序配置和这些设置绑定在一起。也要写下没有测时间这件事。