自动融合精度测试报告

关联文档:自动融合精度一致性说明

本报告为主文档四项核心论点提供实测数据支撑。测试结果回流主文档 §三与 §6.1。


一、概述

本报告拆分为 8 个独立任务(含一项共享基础设施),便于并行推进。每个任务自包含目的、配置、步骤、预期结果与产出物,可独立分配给不同负责人。

二、任务分工表

编号 测试名称 对应论点 负责人 工作量 优先级 前置依赖 状态
S 精度测试工具包(bit-diff / ULP / fp64 reference) 共享基础设施 TODO 1d P0(阻塞项) 待开始
A 自动融合自身的二进制确定性 主文档 §3.3 TODO 0.5d P0 待开始
C1 Tensor × Scalar 指令选择差异 主文档 §1.2 (2) TODO 0.5d P0 待开始
C2 Reduction 累加顺序差异 主文档 §1.2 (3) TODO 0.5d P0 切块控制接口 待开始
C3 rsqrt 算法迭代差异 主文档 §1.2 (4) TODO 0.5d P0 迭代次数控制接口 待开始
D 纯搬运类算子一致性 主文档 §二 TODO 0.5d P1 S 完成 待开始
B Eager vs AutoFuse 累积误差 主文档 §3.2 TODO 2–3d P1 S 完成 待开始
E 端到端训练指标对齐 主文档 §3.2 现实投射 TODO 5–7d P2 S 完成、NPU 资源 待开始

三、并行推进建议

  • Day 0 单独启动:S(基础设施,1 天,阻塞其他依赖它的任务)。
  • Day 0 起并行启动:A、C1、C2、C3 均不依赖 S,可与 S 同步开始。
  • Day 1 起并行启动:B、D、E 在 S 完成后切入。
  • 最小人力配置:2 人 ≈ 1 周;建议 4–5 人 ≈ 3–4 天。
  • 最短路径:P0 全部完成后,主文档已可补充关键数据。P1/P2 结果用于强化 §6.1 与端到端说服力。

四、任务 S — 精度测试工具包

目的

为 A/B/D/E 提供共享的数值比较工具,避免重复造轮子。工具包本身不产出论据,但是其它任务的前置依赖。

交付清单

  1. bit_equal(a, b) -> bool:逐元素 bit-wise 比较,返回是否完全一致。
  2. ulp_diff(a, b) -> ndarray:逐元素 ULP 差。
  3. error_stats(a, ref) -> dict:统计 max / mean / P50 / P99 的绝对误差、相对误差、ULP 误差。
  4. fp64_reference(graph, inputs) -> tensor:把子图用 numpy / torch CPU fp64 重算,作为真值基准。
  5. 打包形式:内部 pip 包 + pytest fixture 入口。

步骤

  1. 拉群对齐工具包接口与仓库位置。
  2. 先出 bit_equalulp_diff,满足 A/D 立即可用;其他功能两天内补齐。
  3. 单测覆盖:fp16 / bf16 / fp32 三档;正常值、非规范数、inf / nan 全覆盖。
  4. 发布到内部 pypi 或以源码形式集成。

产出物

工具包仓库链接、接口文档、单测报告。


五、任务 A — 自动融合自身的二进制确定性

目的

验证主文档 §3.3:编译期输入与运行期输入不变时,多次运行输出二进制一致,且编译产物本身 hash 稳定。

测试配置

  • 子图:matmul → add → layernorm → gelu(Transformer FFN 前半段)。
  • 输入 shape:[8, 2048, 4096](fp16)。
  • 硬件:单卡,固定型号。

步骤

  1. 构建子图,开启自动融合,采集融合 kernel 的二进制 hash(记为 H_k0)。
  2. 用固定种子生成一组输入 tensor,保存到磁盘。
  3. 同一进程内连续执行 1000 次,保存每次输出的 MD5。
  4. 清空 JIT 缓存,冷启动新进程,重新编译,再执行 1000 次。
  5. 汇总:
    • 第一轮 1000 次输出 MD5 集合大小应为 1。
    • 第二轮 kernel hash 应等于 H_k0,输出 MD5 应与第一轮相等。

预期结果

  • 运行输出 hash:全部一致。
  • 编译产物 hash:冷启动后不变。
  • 若出现不一致:定位来源(随机 dropout、非确定性 reduce、NUMA 飘移等),记录并反馈框架团队。

产出物

表格:运行次数 / 唯一 hash 数 / 最大 ULP 差,期望 1000 / 1 / 0


六、任务 C1 — Tensor × Scalar 指令选择差异

目的

验证主文档 §1.2 (2):同一数学语义走不同硬件指令,会产生末位差异。

测试配置

  • 输入 A:fp16 tensor shape [1024, 1024],固定种子生成 U(-1, 1)
  • 标量:0.7
  • 路径 A:走标量乘指令(muls 或等效 Python 写法 x * 0.7,让框架自动降为 muls)。
  • 路径 B:先将标量 broadcast 为 [1024, 1024] 的 tensor,再走向量乘指令(x * scalar_tensor.expand_as(x))。

步骤

  1. 编写两个最小复现脚本。
  2. 用 profiler / op trace 验证两条路径实际生成的指令不同。
  3. 两条路径同一输入各执行 1 次,保存输出。
  4. 用工具 S 统计两者 ULP 差分布。
  5. 汇报:ULP = 0 / ULP = 1 / ULP ≥ 2 各占比。

预期结果

两条路径输出完全一致,大多数元素 ULP ≤ 1,极少量更高。数据本身就是"指令选择引入末位差异"的证据。

可行性风险

框架可能自动选择指令,需要 profiler 确认两条路径实际生成了不同指令;否则测试意义丧失。若无法控制,改为"观察一条路径的指令变化"并如实披露。

产出物

脚本 + profiler 截图 + ULP 差直方图。


七、任务 C2 — Reduction 累加顺序差异

目的

验证主文档 §1.2 (3):浮点加法不满足结合律,切块大小不同会引入末位差异。

测试配置

  • 输入:fp16 tensor shape [1, 16384],固定种子。
  • 操作:sum(dim=-1)
  • 路径 A:切块 512。
  • 路径 B:切块 1024。
  • 路径 C:切块 2048。
  • 参考:fp64 CPU 精算。

步骤

  1. 通过框架的切块配置(编译 flag 或 kernel 级 hint)生成三个切块版本。
  2. 每个版本用同一输入跑 1000 次,校验自身确定性(工具 S 的 bit_equal)。
  3. 三版本之间两两比较 ULP 差。
  4. 每个版本与 fp64 参考比较,看哪个更接近。

预期结果

  • 每个版本自身 1000 次完全一致(自身确定性)。
  • 不同切块版本之间存在 ULP ≥ 1 的差。
  • 三者都落在 fp64 基准的随机误差范围内。

可行性风险

框架不对外暴露切块控制时,需要框架团队配合出一个调试 flag。无此接口时该测试需搁置,或改为"观测不同 shape 下框架自选切块策略与输出的关系"。

产出物

三版输出的 ULP 差矩阵、与 fp64 的对比表。


八、任务 C3 — rsqrt 算法迭代差异

目的

验证主文档 §1.2 (4):迭代逼近类算子的实现差异直接反映为末位精度差。

测试配置

  • 输入:fp16 tensor shape [1024, 1024],值域 U(0.01, 100),固定种子。
  • 操作:rsqrt
  • 路径 A:框架默认实现。
  • 路径 B:同一框架下调整迭代次数(例如 2 轮 vs 3 轮牛顿迭代)。
  • 参考:fp64 精算。

步骤

  1. 联系框架团队获取迭代次数配置接口(环境变量或编译开关)。
  2. 两路径同输入各跑 1 次,保存输出。
  3. 用工具 S 统计 A / B 两者与 fp64 参考的 ULP 差分布。
  4. 若顺畅,可再补一个例子:div(x, y),机理相同。

预期结果

  • A 与 B 输出不完全一致。
  • 迭代次数更多的路径 ULP 差更小。

可行性风险

与 C2 相同:若无法控制迭代次数,改为"框架实现 A vs 同语义的 numpy fp32 实现"对比,说服力略降但仍能展示算法层差异。

产出物

两路径 ULP 差直方图 + 迭代次数与末位差的关系表。


九、任务 D — 纯搬运类算子一致性

目的

验证主文档 §二:不做数学运算的搬运类算子,自动融合与 Eager 可以二进制一致。

测试配置

  • 算子清单:transpose / reshape / slice / concat / split / broadcast
  • 组合子图(需要触发融合):
    • transpose → concat
    • slice → broadcast → concat
    • split → transpose → concat
  • 输入:fp16 / bf16 / fp32 各一套,shape 覆盖典型业务值。

步骤

  1. 对每个子图分别跑 Eager 与 AutoFuse。
  2. 用工具 S 的 bit_equal 校验。
  3. 枚举 dtype × shape 组合,填充勾选表。
  4. 出现反例时(例如 broadcast 伴随隐式类型提升),记录并说明原因。

预期结果

所有纯搬运子图 bit-wise 一致;带隐式计算的 broadcast 可能有差异,单独标注。

产出物

勾选表 + 反例清单。


十、任务 B — Eager vs AutoFuse 累积误差对比

目的

支撑主文档 §3.2 最核心的定量结论:自动融合累积误差 ≤ Eager 模式。

测试配置

  • 算子链(覆盖不同计算模式):
    1. 纯 elementwise:mul → add → gelu → mul
    2. 含 reduction:layernormsoftmax
    3. 含 matmul:matmul → add → layernorm(FFN 前半段)
    4. 可选:attentionqk → softmax → pv 片段
  • 输入:每条链 1000 组随机输入,固定种子池。
  • 三路执行:
    • fp64 CPU 参考(真值)
    • fp16 Eager(逐算子执行,边界落盘)
    • fp16 AutoFuse

步骤

  1. 用工具 S 的 fp64_reference 生成真值。
  2. 分别跑 Eager 与 AutoFuse,收集输出。
  3. 对两路径各计算最大绝对误差、相对误差、ULP 误差的 P50 / P99 / Max。
  4. 画 1000 组误差的直方图与 CDF。
  5. 逐算子链对比:AutoFuse 的 P99 / Max 是否 ≤ Eager 的 P99 / Max。
  6. 出现反例时:
    • 定位具体算子与输入分布。
    • 结果回流主文档 §6.1,作为"需要按算子类型关闭融合"的实证依据。

预期结果

  • 绝大多数算子链 AutoFuse 的 P99 / Max 误差 ≤ Eager。
  • 个别反例需单独落到主文档 §6.1。

产出物

  • 每条算子链一张误差分布对比图 + 一行汇总表。
  • 反例清单(直接交给 §6.1 作者)。

十一、任务 E — 端到端训练指标对齐

目的

支撑主文档 §六开头"关注数值正确性可直接启用"的建议,用下游模型实验回应训练稳定性担忧。

测试配置

模型候选(按成本升序):

  1. GPT-2 small(124M)—— 最小成本,1k step 约数小时。
  2. LLaMA-7B 的 LoRA 微调 —— 中等成本。
  3. 小规模 pretrain(如 300M 模型 5k step)—— 最有说服力但最贵。

起步建议:先跑候选 1,结论明确可跳过后续。每个模型固定:种子、数据集、batch、lr schedule、optimizer。

步骤

  1. 搭建可复现训练脚本(Eager 与 AutoFuse 两个开关)。
  2. 同一脚本两套开关各跑一次,采样间隔 50 step。
  3. 对比指标:
    • loss 曲线(train / eval)。
    • grad norm。
    • 若为 LM:PPL。
  4. 两条曲线应在正常随机波动内重合。如显著偏离:
    • 记录偏离点、偏离量。
    • 回流主文档 §6.1。

预期结果

候选 1 跑完即能得到初步结论。两条曲线应基本重合。

可行性风险

需要 NPU / GPU 资源和数据集配合。若资源紧张,至少先做候选 1。

产出物

loss / eval 指标对比曲线图 + 最终指标差异表。


十二、完成后的回流动作

测试执行完成后:

  1. 将 A / B 两张关键结论表以链接形式反向嵌入主文档 §三。
  2. 若 B / E 出现反例,驱动主文档 §6.1(@wangxiaotian)落地具体开关列表。
  3. 工具 S 作为后续精度回归基准设施长期保留。