03-error-model/README.md3150 字节
# 示例 03:误差预算、参照选择与累加器精度
## 目的
把第 12 章的容差模型(`u` 与 `u/sqrt(K)`)推广到 fp8 / fp4 / int8 / int4,
并用三组实验回答正文第 5 节的问题:
* **实验 A**:同一份量化输入,分别以「原始值」和「量化后的值」为参照,误差差多少?
用来量化「参照选错」的后果。
* **实验 B**:K 从 64 到 4096,`u/sqrt(K)` 这个缩放律对每种格式成立到什么程度?
* **实验 C**:输入完全一样,只换累加器(fp16 顺序累加 / fp32 / 分段后 fp32 汇总)。
## 运行前提
Python 3.9+ 与 numpy。**不需要 GPU / NPU**。
## 运行命令
```bash
python3 error_model.py
python3 error_model.py --trials 400 --lengths 64,256,1024,4096
```
## 期望输出
作者在写作机上的实际输出(2026-10-01,Python 3.11.7 + numpy 1.24.1,默认参数):
```text
== 等价 u:每个格式「单元素相对误差」的上界 ==
格式 布局 等价 u 说明
fp16 1/5/10 4.883e-04 2^-11,浮点
bf16 1/8/7 3.906e-03 2^-8,浮点
e4m3 1/4/3 6.250e-02 2^-4,浮点码本
e2m1 1/2/1 2.500e-01 2^-2,浮点码本
int8 定点 8 位 3.937e-03 1/(2*127),均匀量化
int4 定点 4 位 7.143e-02 1/(2*7),均匀量化
== 实验 A:K=64,两种参照下的相对误差 ==
格式 中位数(对原始值) 中位数(对量化值) 最大(对量化值) 预算 u/sqrt(K) 比值
fp16 3.250e-05 5.935e-09 2.703e-08 6.104e-05 0.53
bf16 2.517e-04 1.620e-09 2.697e-08 4.883e-04 0.52
e4m3 3.687e-03 0.000e+00 0.000e+00 7.812e-03 0.47
e2m1 8.050e-02 0.000e+00 0.000e+00 3.125e-02 2.58
int8 7.250e-04 7.978e-09 4.845e-08 4.921e-04 1.47
int4 1.113e-02 7.297e-09 6.920e-08 8.929e-03 1.25
== 实验 B:误差随 K 的缩放(u/sqrt(K) 是否成立) ==
格式 K=64 K=256 K=1024 K=4096
fp16 0.53 0.54 0.58 0.48
bf16 0.52 0.41 0.41 0.47
e4m3 0.47 0.45 0.47 0.44
int8 1.47 1.27 1.32 1.30
== 实验 C:累加器精度(K=4096,20 次试验,与 fp64 比较) ==
方案 相对误差中位数 相对误差最大值
fp16 输入 + fp16 顺序累加 1.238e-04 5.695e-04
fp16 输入 + fp32 累加 1.243e-09 3.431e-09
fp16 输入 + 32 段 fp16 部分和 + fp32 汇总 2.063e-05 7.009e-05
```
## 三个判据
**(一)参照选错会差 4 个数量级以上。** fp16 是 `3.3e-5` 对 `5.9e-9`;e4m3 与 e2m1
的「对量化值」误差直接是 0——4 位尾数的乘积只有 8 位有效位,fp32 能精确表示,
连累加都不丢位。把「量化前的 fp32 结果」当参照,正常的量化误差会被判成逻辑错误。
**(二)`u/sqrt(K)` 对浮点是量级模型,对 fp4 失效。** fp16 / bf16 / e4m3 的
「实测 / 预算」稳定在 0.44–0.58(与第 12 章的 0.5 一致);int8 是 1.27–1.47
(均匀量化比浮点更接近最坏情形);e2m1 却是 2.58——4 位浮点的相对间距随指数变化
太剧烈,小数值的相对误差远超 `u`。**用 fp4 时不要套用 fp16 的容差公式,
要在真实数据分布上实测。**
**(三)累加器是独立的一档误差。** 同一份 fp16 输入,`fp16 顺序累加` 的中位误差是
`1.24e-4`,换成 fp32 累加变成 `1.24e-9`;而「32 段 fp16 部分和 + fp32 汇总」落在
中间(`2.06e-5`)。这就是 split-K 能保住精度的原因:每段的舍入误差只与该段长度有关,
跨段用 fp32 连接不会再放大。
## 注意事项
* 实验 B 的「预算」用的是 `u`,没有考虑数据分布与相关性;它只是量级判断。
* 实验 C 的 fp16 顺序累加用 `np.cumsum(..., dtype=np.float16)` 模拟:每一步都在
fp16 里舍入。它没有模拟硬件的 fused multiply-add(乘积本身不额外舍入),
所以真实 kernel 的累加误差可能比这里**小**一点。
* 真实 kernel 还有 epilogue 的 cast、分块累加的顺序差异,这些都不在本模型里。
## 作者实际做到的验证级别
**CPU 逻辑验证**:在 macOS (Apple Silicon)、Python 3.11.7 + numpy 1.24.1 上实际运行过,
退出码 0。未在任何加速器上运行过任何 kernel。