跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 27 章 低精度、量化与数值正确性

/code/27-low-precision-quantization/

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。

← 浏览本章全部代码