> 转载请注明出处：小锋学长生活大爆炸[xfxuezhagn.cn]
> 如果本文帮助到了你，欢迎[点赞、收藏、关注]哦~

> 本内容由ChatGPT-5.6 整理而成。

---

## 目录

* 为什么需要量化
* 量化的数学本质
* 常见数值格式
* 整数量化原理
* PTQ 与 QAT
* 浮点量化原理
* FP8 格式族
* FP8 缩放策略
* FP8 混合精度训练
* FP8 与 INT8 如何选择
* 工程实践中的关键指标
* FP8 量化伪代码
* Transformer Engine 示例
* 常见误区
* 总结
* 参考资料

***



随着神经网络参数规模、上下文长度和训练数据量持续增长，模型对**计算吞吐**、**显存容量**和**内存带宽**的需求迅速上升。**量化**通过减少数值表示所需的**比特数**，使模型能够使用更紧凑的数据格式执行计算，是当前训练与推理加速的核心技术之一。

## 为什么需要量化
假设一个模型包含 $N$ 个参数，每个参数使用 $b$ 位保存，则仅参数本身需要的存储空间为 $M=\frac{Nb}{8}$。对于十亿参数规模的模型，令$N=10^9$，则不同格式对应的参数存储量约为：

| 数据格式      | 每个参数位宽 | 十亿参数存储量 |
| --------- | ----- | ------ |
| FP32      | 32 bit |    4 GB |
| FP16/BF16 | 16 bit |    2 GB |
| FP8/INT8  |  8 bit |    1 GB |
| INT4      |  4 bit |  0.5 GB |


![](https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/07/23/1784823571610-4e764ad7-0538-4b76-9a37-d4135ba785e2.png)

训练阶段还需要保存：参数梯度、优化器状态、中间激活、注意力矩阵、通信缓冲区、临时工作空间。因此，降低数据位宽会同时影响多个系统瓶颈：
1. 减少模型存储空间。
2. 减少显存占用。
3. 降低显存和内存带宽压力。
4. 降低分布式训练中的通信数据量。
5. 提高低精度矩阵乘法单元的吞吐。
6. 降低数据移动和计算带来的能耗。

但量化并不是简单地把 `float32` 转换为 `int8` 或 `float8`。低精度格式只能表示**有限数量的离散值**，因此量化本质上是有损映射：

$$
x\in\mathbb{R}
\longrightarrow
q\in\mathcal{Q},
$$

其中， $\mathcal{Q}$ 是目标格式能够表示的有限数值集合。

模型量化是否成功，取决于以下几个方面：
* 目标格式的**动态范围**是否覆盖原始张量。
* 可表示值的**密度**是否足够。
* **缩放因子**是否与数据分布匹配。
* **量化误差**是否会被后续算子放大。
* **硬件**是否真正支持该低精度格式。

## 量化的数学本质
### 编码、解码与伪量化

一个完整的量化过程通常包含编码和解码：

$$
x
\xrightarrow{\operatorname{Quantize}}
q
\xrightarrow{\operatorname{Dequantize}}
\hat{x}.
$$

其中，$x$ 是原始高精度数值，$q$ 是低精度编码，$\hat{x}$ 是解码后的近似值，$e=x-\hat{x}$ 是量化误差。

在量化算法研究和量化感知训练中，经常使用**伪量化**：

$$
\hat{x}
=
\operatorname{Dequantize}
\left(
\operatorname{Quantize}(x)
\right).
$$

伪量化后的张量通常**仍然**以 FP32、FP16 或 BF16 存储和计算，但其数值被限制在**目标量化格式的可表示集合**中。伪量化只能**模拟精度损失**，并不会自动获得实际加速。只有**硬件和计算内核**真正使用 INT8、FP8 等低精度指令时，量化才会带来吞吐和带宽收益。


![](https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/07/23/1784823791881-5f091a23-6fd1-465b-9610-9f2a8aa0629a.png)


### 量化误差

量化后的近似值可以写成：

$$
\hat{x}=x+\varepsilon_q,
$$

其中 $\varepsilon_q$ 表示量化噪声。

对于步长为 $\Delta$ 的**理想均匀量化器**，如果假设误差在 $\left[-\frac{\Delta}{2},\frac{\Delta}{2}\right]$ 内均匀分布，并近似独立于输入，则有

$$
\mathbb{E}[\varepsilon_q]=0,
\qquad
\operatorname{Var}(\varepsilon_q)
=
\frac{\Delta^2}{12}.
$$

该结论说明，**均匀量化中的量化步长越大，误差方差通常越大**。但神经网络中的真实量化误差往往**不满足**独立均匀噪声假设。常见原因包括：

- 激活和权重分布并不均匀。
- Transformer 激活中可能存在显著离群值。
- 不同通道的动态范围差异很大。
- 截断会产生非零均值误差。
- 误差会经过矩阵乘法、归一化和非线性函数传播。


### 舍入误差与饱和误差

量化误差主要包括两部分。

- **舍入误差**：目标格式能够覆盖输入范围，但**输入值位于两个可表示值之间**，需要舍入到其中一个值。

- **饱和误差**：输入**超过**目标格式的表示范围，只能被限制到最大或最小可表示值：$\operatorname{clip}(x,x_{\min},x_{\max})$。

扩大表示范围可以减少饱和误差，但会让可表示值分布得更稀疏。缩小表示范围可以提高主体数据的表示精度，却会截断更多离群值。因此，量化范围选择通常可以表示为**优化问题**：

$$
\min_{\alpha}
\mathbb{E}
\left[
\left(
x-Q(x;\alpha)
\right)^2
\right],
$$

其中 $\alpha$ 表示**量化阈值**或**缩放参数**。

![](https://fastly.jsdelivr.net/gh/bucketio/img9@main/2026/07/23/1784823896275-f2e327c6-6ec8-4868-a256-2b43a53cc240.png)


## 常见低精度数值格式

量化是将高精度数值映射到一个更有限的可表示集合，而这个集合既可以由整数格式定义，也可以由低精度浮点格式定义。深度学习中常见的数值格式包括：

- FP32，即 IEEE 754 binary32
- BF16
- FP16，即 IEEE 754 binary16
- FP8 E4M3
- FP8 E5M2
- INT8、INT4 等整数格式
- MXFP8、MXFP6、MXFP4 等微缩放格式


### 浮点格式的位宽分配

浮点格式通常由**符号位**、**指数位**和**尾数位**组成：

$$
\underbrace{S}_{\text{符号位}}
\quad
\underbrace{E}_{\text{指数位}}
\quad
\underbrace{M}_{\text{尾数位}}.
$$

不同格式的区别，本质上是有限的总位宽如何在指数和尾数之间分配。

| 格式 | 总位宽 | 符号位 | 指数位 | 尾数位 | 主要特征 |
|---|---:|---:|---:|---:|---|
| FP32 | 32 | 1 | 8 | 23 | 精度和动态范围都较高 |
| BF16 | 16 | 1 | 8 | 7 | 接近 FP32 的动态范围，精度较低 |
| FP16 | 16 | 1 | 5 | 10 | 精度高于 BF16，动态范围较小 |
| E4M3 | 8 | 1 | 4 | 3 | FP8 中精度较高 |
| E5M2 | 8 | 1 | 5 | 2 | FP8 中动态范围较大 |
| E8M0 | 8 | 0 | 8 | 0 | 通常作为块级共享缩放因子 |

**指数位主要决定动态范围，尾数位主要决定数值精度**。因此，在总位宽固定时，指数位和尾数位之间存在直接权衡：

$$
\text{指数位增加}
\Rightarrow
\text{动态范围扩大，但尾数精度下降},
$$

$$
\text{尾数位增加}
\Rightarrow
\text{表示精度提高，但动态范围缩小}.
$$


![](https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/07/23/1784824097810-1405f4a4-ba49-487a-91ff-b7c01135f2ec.png)


### 整数格式

整数格式没有指数位和尾数位，其编码在整数域中**均匀分布**。例如 INT8 可以表示：

$$
-128,-127,\dots,126,127.
$$

为了表示任意实数范围，需要使用**缩放因子**和**零点**：

$$
\hat{x}=s(q-z).
$$

因此，整数格式本身提供**均匀整数网格**，而缩放因子负责把这个网格映射到目标实数范围。

### 低精度浮点格式

FP16、BF16 和 FP8 保留了浮点指数结构，因此其可表示值在实数轴上不是均匀分布的。绝对值较小时，可表示值较密；绝对值较大时，可表示值间隔增大。这使低精度浮点格式更适合表示跨越多个数量级的数据。

### 微缩放格式

微缩放格式将一组低精度元素与一个共享缩放因子组合起来。其一般形式为：

$$
x_i\approx s_{\mathrm{block}}q_i.
$$

其中，一个数据块内的多个低精度元素 $q_i$ 共享同一个缩放因子 $s_{\mathrm{block}}$。例如 MXFP8 通常以 32 个连续元素为一个块，并为每个块保存一个 E8M0 缩放因子。块内元素可以使用 E4M3 等 FP8 格式。

需要注意，E8M0 通常不是用来独立表示普通张量元素，而是用来编码共享缩放因子。由于它没有符号位和尾数位，其表示值主要是 2 的幂。


## 整数量化原理


![](https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/07/23/1784825047353-3b847ccc-5cd0-4b8e-9247-0e34cb5ea721.png)


### 仿射量化 (Affine Quantization)

最常见的**整数仿射量化**定义为：

$$
q
=
\operatorname{clip}
\left(
\operatorname{round}\left(\frac{x}{s}\right)+z,
q_{\min},
q_{\max}
\right),
$$

反量化为：

$$
\hat{x}=s(q-z).
$$

其中，$s$ 是缩放因子 (scale)，决定**相邻整数编码之间对应的实数间隔**。$z$ 是零点 (zero-point)，也可以说是**实数零对应的整数位置 (不一定就是零)**，用于保证**实数零能够被精确编码**。$q_{\min}$ 和 $q_{\max}$ 是整数编码范围。不同量化类型具有不同范围：

| 量化类型 | $q_{min}$​ | $q_{max}$​ |
| --- | --- | --- |
| UINT8 | 0   | 255 |
| INT8 | -128 | 127 |
| 对称 INT8 常用范围 | -127 | 127 |
| UINT4 | 0   | 15  |
| INT4 | -8 | 7   |

这里还需要注意存在两种不同的**坐标范围**：
- **实数范围**: $[x_{min},x_{max}]$
- **整数编码范围**: $[q_{min},q_{max}]$

> **分四步理解上述量化公式：**
> 假设 $s = 0.1， z = -64， q_{max} = 127， q_{min} = -128$，待量化的实数 $x = 0.73$。
> ![](https://fastly.jsdelivr.net/gh/bucketio/img1@main/2026/07/23/1784812661543-06906dfb-9f1a-4e43-98d0-920d3842a100.png)

> **理解缩放因子 $s$ ：**
> 假设两个相邻整数编码分别是 $q$ 和 $q+1$，它们表示的实数为: $\hat{x}_q=s(q-z)$ 和 $\hat{x}_{q+1}=s(q+1-z)$，则两者之差为：$\hat{x}_{q+1}-\hat{x}_q = s$。所以 $s$ 的含义非常明确：INT8 编码每增加 1，对应的实数增加 $s$。也因此，$s$ 越小，量化网格越密，精度越高，但能够覆盖的实数范围越小。
> ![](https://fastly.jsdelivr.net/gh/bucketio/img6@main/2026/07/23/1784824507022-29b8af96-ee3f-4e03-a01b-4901c25f20e7.png)

> 如果已知 $x$ 和 $q$ 的取值范围，则仿射量化的缩放因子与零点通常计算为：
> - $s$ 的计算方式：$s = \frac{x_{\max}-x_{\min}} {q_{\max}-q_{\min}}$
> - $z​$ 的计算方式：​$z = clip(round(q_{min}​−\frac{x_{min}}{s}​​),q_{min}​,q_{max}​)$
> 




### 对称量化

对称量化通常令 $z=0$。如果浮点张量使用对称范围 $[-\alpha,\alpha]$，则缩放因子可以取 $s=\frac{\alpha}{q_{\max}}$。量化公式简化为：

$$
q
=
\operatorname{clip}
\left(
\operatorname{round}\left(\frac{x}{s}\right),
-q_{\max},
q_{\max}
\right).
$$

对称量化的优点是：

- 实现简单。
- 零点补偿开销较少。
- 适合以零为中心的权重分布。
- 便于矩阵乘法内核优化。

缺点是**当数据分布明显不对称时，部分编码空间可能被浪费**。

### 非对称量化

当原始数据范围为 $[x_{\min},x_{\max}]$，缩放因子可以取 $s=\frac{x_{\max}-x_{\min}}{q_{\max}-q_{\min}}$，零点可以取 $z=\operatorname{round}\left(q_{\min}-\frac{x_{\min}}{s}\right)$。

非对称量化更适合**单侧或存在明显偏移的激活分布**，例如 ReLU 后的**非负激活**。但非零零点会使整数矩阵乘法产生额外修正项。设

$$
X\approx s_x(X_q-z_x),
\qquad
W\approx s_w(W_q-z_w),
$$

则

$$
XW
\approx
s_xs_w
(X_q-z_x)(W_q-z_w),
$$

展开后会出现与**行和**、**列和**及**零点乘积**相关的补偿项。


![](https://fastly.jsdelivr.net/gh/bucketio/img5@main/2026/07/23/1784824311842-1fc71074-1af7-479b-bfc1-5660c2c2053f.png)


### 量化粒度

**缩放因子的作用范围**称为量化粒度。粒度级别对模型压缩、推理速度和潜在准确度损失之间的权衡有显著影响。

| 粒度 | 缩放因子作用范围 | 特点 |
|---|---|---|
| Per-tensor | 整个张量 | 开销最低，容易受离群值影响 |
| Per-channel | 每个通道 | 权重量化中常用，精度较高 |
| Per-group | 每组若干通道 | 常用于 INT4 权重量化 |
| Per-token | 每个 Token | 适合动态变化的激活分布 |
| Per-block | 每个固定大小的数据块 | 精度和硬件效率之间折中 |

粒度越细，各局部区域越容易充分利用量化范围，但代价也会增加：
- 需要保存更多缩放因子。
- 需要更复杂的数据布局。
- 可能增加访存和反缩放开销。
- 硬件需要支持相应的缩放粒度。

![](https://fastly.jsdelivr.net/gh/bucketio/img15@main/2026/07/23/1784814846251-52155cc2-819c-4cac-99f6-5f41714d0a9b.png)

量化 (quantization)粒度的选择取决于几个因素：

- **目标精度** 对于侵略性较低的量化（例如 INT8），逐通道通常能提供良好的平衡。对于极低精度（INT4、INT3），逐组量化通常是保持可接受准确度所必需的。
- **张量类型**：权重 (weight)通常受益于更细的粒度（逐通道或逐组），因为它们的分布在通道之间甚至通道内部都可能明显不同。激活有时是逐张量量化的，尽管动态量化（逐令牌，通常是该令牌内的逐张量）也很常见。
- **硬件/软件支持**：目标推理 (inference)引擎或硬件可能对特定粒度有优化例程。
- **额外开销容忍度**：更细的粒度会增加比例因子和零点所需的存储空间，并可能在推理设置或反量化步骤中增加计算开销。
- **准确度要求**：最重要的因素。如果像逐张量这样更简单的方法导致模型在下游任务上的性能下降到不可接受的程度，那么更细的粒度就变得有必要。

实践中，逐通道量化是权重的常见默认选择，而当需要降低精度或逐通道量化不足时，则采用逐组量化。逐张量最简单，但通常保留用于激活或对准确度影响最小的情况。理解这些选择能帮助您在对大语言模型 (LLM)进行量化时做出明智的决定。




## PTQ 与 QAT

### 训练后量化 (PTQ)

Post-Training Quantization，简称 PTQ，在模型**训练完成后执行量化**。典型流程包括：

1. 选择校准数据。
2. 收集各层权重和激活分布。
3. 估计缩放因子和截断范围。
4. 执行量化。
5. 评估任务精度。
6. 对敏感层保留更高精度。

![https://apxml.com/zh/courses/practical-llm-quantization](https://fastly.jsdelivr.net/gh/bucketio/img8@main/2026/07/23/1784815187247-4c06b3a3-7b80-4d68-9daf-88145301bc16.png)


常见校准方法包括：

- 最大绝对值校准
- 百分位截断
- 均方误差最小化
- KL 散度或分布匹配
- 分通道或分组校准

PTQ 不需要重新训练，部署成本较低。8 位量化通常相对容易，但随着位宽降低，量化误差会明显增大。

### 量化感知训练 (QAT)

Quantization-Aware Training，简称 QAT，在训练期间插入伪量化节点：

$$
\hat{x}=Q_{\mathrm{fake}}(x).
$$

由于 `round` 函数几乎**处处不可导**，反向传播通常使用**直通估计器**：

$$
\frac{\partial \hat{x}}{\partial x}
\approx
\begin{cases}
1, & x\text{ 位于量化范围内},\\
0, & x\text{ 位于截断范围外}.
\end{cases}
$$

QAT 使模型参数能够主动适应量化网格，通常比 PTQ 更适合：

- INT4 或更低位宽量化。
- 激活值量化。
- 数值敏感模型。
- 量化误差较大的网络结构。

其代价是需要训练数据、额外训练资源和更复杂的训练流程。

### 大模型中的离群值问题

Transformer 激活中经常存在少量**幅值很大**的通道。如果使用单一的 per-tensor 缩放因子：

$$
s
=
\frac{\max_i |x_i|}
{q_{\max}},
$$

![](https://fastly.jsdelivr.net/gh/bucketio/img8@main/2026/07/23/1784824660292-bc504a76-8201-4b15-9e85-65a102584d4d.png)


少量离群值会决定整个张量的量化范围，使绝大多数普通值只能使用少量有效编码。这说明量化难度不只取决于张量整体方差，还取决于：

- 通道之间动态范围是否均衡。
- Token 之间幅值是否均衡。
- 离群值是否集中在固定通道。
- 缩放粒度是否足够细。

![](https://fastly.jsdelivr.net/gh/bucketio/img19@main/2026/07/23/1784824799336-994a64a5-567a-4567-8cc8-ffb8bc5f3f0e.png)


SmoothQuant 等方法通过数学等价变换，把激活中的量化困难迁移到更容易进行 per-channel 量化的权重中，从而改善大模型的训练后 W8A8 量化。



## 浮点(FP)量化与整数(INT)量化的区别

INT8 使用线性均匀网格。对于固定缩放因子，相邻可表示值之间的**实数间隔**近似恒定：

$$
\Delta_{\mathrm{INT8}}=s.
$$

FP8 使用**指数**和**尾数**组成非均匀网格。数值越大，相邻可表示值之间的**绝对间隔**越大，但在同一个 binade 内，相对间隔大致稳定。

因此：

- INT8 提供近似恒定的绝对精度。
- FP8 提供近似恒定的相对精度。
- INT8 高度依赖缩放因子的选择。
- FP8 自带指数，对跨数量级分布更自然。
- FP8 仍然需要缩放，只是通常比纯整数格式更容易覆盖动态范围。

从数学角度看，FP8 同样属于量化，因为它把高精度浮点数映射到只有 256 种比特模式的有限编码集合。

## 浮点数的编码结构

一个二进制浮点数通常由三部分组成：

$$
\underbrace{S}_{\text{符号位}}
\quad
\underbrace{E}_{\text{指数位}}
\quad
\underbrace{M}_{\text{尾数位}}.
$$

对于有限规格化数，且相应位模式未被保留为 Inf 或 NaN，其值为

$$
x
=
(-1)^S
\times
2^{E-\mathrm{bias}}
\times
\left(
1+\frac{M}{2^m}
\right),
\qquad
S\in\{0,1\}
$$

其中，$E$ 是指数编码，$\mathrm{bias}$ 是指数偏置，$M$ 是尾数字段对应的整数，$m$ 是尾数位数，规格化数具有隐含前导 $1$。

> **规格化数**是浮点数中采用隐含前导 1表示的普通非零数。即对于 $1+\frac{M}{2^m}$​，这里最前面的 **1** 并**没有真实存储在比特中**，而是默认存在，因此称为**隐含前导 1**。
> **为什么可以隐含这个 1 ？** 二进制科学计数法中的非零数，可以调整指数，使有效数字写成 **$1.xxx_2​×2^e$**。例如：$6.5_{10}=110.1_2$，改写成二进制科学计数法是 $6.5_{10} = 110.1_2 = 1.101_2\times2^2.$。规格化之后，小数点前永远是 **1**。既然这个 1 总是存在，就没有必要再占用一个比特存储，只需要保存小数点后的部分 **101**。因此，即使 E4M3 只有 3 位尾数，规格化数实际上具有 **1+3=4** 位有效二进制精度。


对于非规格化数，指数编码为零，不再使用隐含前导 $1$：

$$
x
=
(-1)^S
\times
2^{1-\mathrm{bias}}
\times
\frac{M}{2^m}.
$$

非规格化数用于**填补零与最小规格化数之间的空隙**，使下溢过程更加平滑 (渐进下溢)。

> **非规格化数**也称为次正规数或 subnormal number，用来表示非常接近零的数。当指数编码为全零时，不再使用隐含前导 1，此时有效数字是 **$0.xxx_2​$**。这样可以表示比最小规格化数还小的数。如果没有非规格化数，那么最小正规格化数以下的所有数都会直接变成零。

**规格化数与非规格化数的区别**

| 特性  | 规格化数 | 非规格化数 |
| --- | --- | --- |
| 指数编码 | 通常非零 | 全零  |
| 有效数字形式 | $1.xxx_2$ | $0.xxx_2$ |
| 隐含前导 1 | 有   | 无   |
| 相对精度 | 较稳定 | 越靠近零越差 |
| 主要作用 | 表示普通数值 | 表示接近零的小数 |
| 数值范围 | 从最小规格化数开始 | 位于零和最小规格化数之间 |

> **一句话理解**：规格化数采用 $1.xxx\times2^e$ 的标准科学计数法表示，前导 1 被省略存储，从而**多获得一位有效精度**；非规格化数采用 $0.xxx\times2^e$，牺牲部分精度，用来**表示更接近零的数**。

![](https://fastly.jsdelivr.net/gh/bucketio/img3@main/2026/07/23/1784824942414-4d33618f-75c8-4338-9d6c-a7049a0d531d.png)


## FP8 格式族

深度学习中最常见的两种 FP8 格式是：

- E4M3
- E5M2

名称中的 `E` 表示指数位数(Exponent)，`M` 表示尾数位数(Mantissa)。

| 格式 | 符号位 | 指数位 | 尾数位 | 指数偏置 |
|---|---:|---:|---:|---:|
| E4M3 | 1 | 4 | 3 | 7 |
| E5M2 | 1 | 5 | 2 | 15 |

二者都只有 8 位，但设计目标不同。

- E4M3 用一个指数位换取一个尾数位，因此精度较高，范围较小。
- E5M2 用一个尾数位换取一个指数位，因此精度较低，范围较大。

典型的混合格式训练策略是：

$$
\text{权重与前向激活}
\rightarrow
\mathrm{E4M3},
$$

$$
\text{反向梯度}
\rightarrow
\mathrm{E5M2}.
$$

### E4M3 解析

E4M3 的 8 位结构为 `S EEEE MMM`，也可以写为

$$
[
\boxed{S}
\boxed{E_3E_2E_1E_0}
\boxed{M_2M_1M_0}
].
$$

它包含：

* 1 位符号位
* 4 位指数
* 3 位尾数
* 指数偏置 7


对于普通规格化数：

$$
x
=
(-1)^S
2^{E-7}
\left(
1+\frac{M}{8}
\right).
$$

#### E4M3 的精度

在区间 $[2^e,2^{e+1})$ 内，E4M3 相邻规格化数的间隔为 $\operatorname{ULP}_{\mathrm{E4M3}}=2^{e-3}$。例如在区间 $[1,2)$ 中，$e=0$，相邻数值间隔为 $2^{-3}=0.125$。因此该区间中的可表示值为：

$$1.000, 1.125, 1.250, 1.375, 1.500, 1.625, 1.750, 1.875.$$

使用就近舍入时，典型的单位舍入误差约为：

$$
u_{\mathrm{E4M3}}
=
2^{-(3+1)}
=
6.25\%.
$$

该结果描述的是规格化区间内的典型相对误差上界，在非规格化数、溢出区域和 binade 边界附近需要单独分析。

#### E4M3 的表示范围

E4M3 的关键数值为：

$$
x_{\max}=448,
$$

$$
x_{\min,\mathrm{normal}}
=
2^{-6}
=
0.015625,
$$

$$
x_{\min,\mathrm{subnormal}}
=
2^{-9}
=
0.001953125.
$$

E4M3 的一个重要特点是，**不支持正负无穷大**。当指数位为 `1111` 时，并没有像常规 IEEE 754 格式一样把整个指数区域全部用于无穷大和 NaN。最大有限值编码为 `S.1111.110`，其数值为：

$$
\left(
1+\frac{6}{8}
\right)
2^{15-7}
=
1.75\times 2^8
=
448.
$$

高端的部分可表示值为：

$$
256,\;
288,\;
320,\;
352,\;
384,\;
416,\;
448.
$$

可以看到，E4M3 在大数区域的绝对间隔已经很大，这正是浮点数“**相对精度近似稳定、绝对精度随指数增长**”的体现。

#### E4M3 的适用场景

E4M3 通常适合表示：

- 模型权重
- 前向激活
- 对表示精度较敏感的矩阵乘法输入
- 经过细粒度缩放后动态范围受控的梯度

权重和前向激活通常需要区分**大量幅值相近的数值**。相比 E5M2，E4M3 多一个尾数位，因此舍入误差更小。

### E5M2 深度解析

#### 位布局

E5M2 的结构为 `S EEEEE MM`，也可以写为：

$$
\boxed{S}
\boxed{E_4E_3E_2E_1E_0}
\boxed{M_1M_0}.
$$

它包含：

- 1 位符号位
- 5 位指数
- 2 位尾数
- 指数偏置 15

对于规格化数：

$$
x
=
(-1)^S
2^{E-15}
\left(
1+\frac{M}{4}
\right).
$$

---

#### E5M2 的精度

在区间 $[2^e,2^{e+1})$ 内，相邻规格化数间隔为 $\operatorname{ULP}_{\mathrm{E5M2}}=2^{e-2}$。

在 $[1,2)$ 中，可表示值只有：

$$
1.00,\;
1.25,\;
1.50,\;
1.75.
$$

其典型单位舍入误差约为：

$$
u_{\mathrm{E5M2}}
=
2^{-(2+1)}
=
12.5\%.
$$

因此，E5M2 的尾数精度比 E4M3 低一档。

#### E5M2 的表示范围

E5M2 的关键数值为：

$$
x_{\max}=57344,
$$

$$
x_{\min,\mathrm{normal}}
=
2^{-14},
$$

$$
x_{\min,\mathrm{subnormal}}
=
2^{-16}.
$$

最大有限值为：

$$
\left(
1+\frac{3}{4}
\right)
2^{30-15}
=
1.75\times2^{15}
=
57344.
$$

当指数位为 `11111` 时：

- 尾数为 `00` 表示正负无穷大。
- 尾数为 `01`、`10` 或 `11` 表示 NaN。

因此，E5M2 的特殊值编码更接近 IEEE 754 的常规设计。

#### E5M2 为什么常用于梯度

梯度在不同层、不同训练阶段和不同样本之间可能跨越多个数量级。相比权重和前向激活，梯度通常更需要**动态范围**。如果梯度的绝对值范围是 $10^{-6}\sim10^3$，则其动态范围跨越约 9 个十进制数量级。E4M3 即使配合缩放，也可能更容易在同一个张量中发生下溢或饱和。E5M2 通过增加指数位，显著扩展了可表示范围。

典型 FP8 混合训练因此采用：

$$
\text{Forward}
\rightarrow
\mathrm{E4M3},
$$

$$
\text{Backward}
\rightarrow
\mathrm{E5M2}.
$$

这并不意味着梯度对精度不敏感，而是说明在很多网络中，梯度首先面临的是动态范围问题。

### E4M3 与 E5M2 对比

| 特性 | E4M3 | E5M2 |
|---|:---|:---|
| 指数位 | 4 | 5 |
| 尾数位 | 3 | 2 |
| 指数偏置 | 7 | 15 |
| 最大有限值 | 448 | 57,344 |
| 最小规格化数 | $2^{-6}$ | $2^{-14}$ |
| 最小非规格化数 | $2^{-9}$ | $2^{-16}$ |
| 无穷大 | 不支持 | 支持 |
| 典型单位舍入误差 | 约 6.25% | 约 12.5% |
| 主要优势 | 精度较高 | 动态范围较大 |
| 常见用途 | 权重、激活 | 梯度 |

可以概括为：
- $\mathrm{E4M3}=\text{较精细的刻度}+\text{较短的量程}$
- $\mathrm{E5M2}=\text{较粗的刻度}+\text{较长的量程}$

## FP8 为什么仍然需要缩放

FP8 自带指数，并不意味着它可以不使用缩放因子。假设某个激活张量主要分布在 $[-0.02,0.02]$，如果直接转换为 E4M3，大量值会接近最小规格化数区域，甚至进入非规格化区间，能够使用的有效编码较少。如果先把张量放大 1000 倍 $x_s=1000x$，原始区间会映射到 $[-20,20]$，从而更充分地利用 E4M3 的可表示数值。

一种常见的 FP8 量化表达为：

$$
a
=
\frac{F_{\max}}
{\operatorname{amax}(x)},
\qquad
q
=
\operatorname{cast}_{\mathrm{FP8}}(ax),
\qquad
\hat{x}
=
\frac{q}{a}.
$$

其中，$F_{\max}$ 是目标 FP8 格式的最大有限值，$\operatorname{amax}(x)=\max_i|x_i|$，$a$ 是编码阶段的放大因子。也可以定义反缩放因子 $s=\frac{1}{a}$，并写成 $x\approx sq$。

缩放因子的作用不是增加 FP8 的编码数量，而是**把张量的重要数值区域移动到 FP8 的高利用率区间**。

## FP8 缩放策略

### Current scaling

Current scaling 根据当前张量的最大绝对值计算缩放因子：

$$
a_t
=
\frac{F_{\max}}
{\operatorname{amax}(x_t)}.
$$

其优点是**缩放因子与当前数据分布高度匹配**。缺点是**通常需要额外的数据遍历**：

1. 首先读取张量并计算 `amax`。
2. 再次读取张量，执行缩放和 FP8 转换。

对于带宽受限的量化算子，这个额外遍历可能显著降低收益。


### Delayed scaling

Delayed scaling 使用之前若干次迭代的最大值历史估计当前缩放因子：

$$
a_t
=
f
\left(
\operatorname{amax}_{t-1},
\operatorname{amax}_{t-2},
\dots,
\operatorname{amax}_{t-k}
\right).
$$

例如可以使用历史最大值：

$$
a_t
=
\frac{F_{\max}}
{\max_{j=1,\dots,k}
\operatorname{amax}_{t-j}}.
$$

这样可以在算子生成输出时直接完成 FP8 转换，避免为了当前 `amax` 再扫描一次输出。代价是**缩放因子具有滞后性**。当张量分布突然变化时，可能出现：

- 缩放过大，导致饱和。
- 缩放过小，导致大量数值下溢或集中在零附近。
- 历史离群值长期占据动态范围。
- 不同训练阶段使用的缩放因子不再适配当前分布。

### Per-tensor scaling

传统 FP8 通常对一个张量使用一个 FP32 缩放因子：

$$
q=Q_{\mathrm{FP8}}(ax).
$$

该方式元数据开销较低，也便于融合到矩阵乘法中，但**整个张量必须共享同一动态范围**。当不同通道之间尺度差异较大时，单一缩放因子可能导致：

- 小通道下溢。
- 大通道决定整体范围。
- 大量尾数编码没有得到充分利用。


### Block scaling

Block scaling 将张量划分为多个块：

$$
x=
\left[
x^{(1)},x^{(2)},\dots,x^{(K)}
\right],
$$

每个块使用独立缩放因子：

$$
q^{(k)}
=
Q_{\mathrm{FP8}}
\left(
a_kx^{(k)}
\right).
$$

细粒度缩放会降低每个块内部需要覆盖的动态范围，从而减少饱和和下溢。代价包括：

- 缩放因子元数据增多。
- 数据布局和转置更加复杂。
- 硬件需要支持块级缩放。
- 不同矩阵乘法方向可能需要不同的块划分。

### MXFP8

MXFP8 是微缩放 FP8 方案。其核心思想是**为每 32 个连续元素设置一个共享缩放因子**，并使用 `E8M0` 类型表示该缩放因子。E8M0 没有符号位和尾数位。除 NaN 编码外，其有限编码全部表示 2 的整数次幂，并且没有零的编码。相较于传统 per-tensor FP8：

- 缩放粒度更细。
- 局部动态范围要求更低。
- 更多张量一般可以统一使用 E4M3。
- 缩放元数据和布局处理更复杂。

需要注意，FP8 block scaling 和 MXFP8 并不是完全等价的概念。它们都使用细粒度缩放，但块大小、缩放格式和硬件语义可能不同。

## FP8 矩阵乘法的数值流程

对于矩阵乘法 $Y=XW$，假设激活和权重分别使用缩放因子 $s_x$ 和 $s_w$：

$$
X\approx s_xX_q,
$$

$$
W\approx s_wW_q.
$$

则

$$
Y
\approx
s_xs_w
\left(
X_qW_q
\right).
$$

硬件可以使用 FP8 执行乘法输入，但点积累加通常采用更高精度：

$$
A_{ij}
=
\sum_k
X_{q,ik}W_{q,kj}.
$$

最后再执行反缩放：

$$
Y\approx s_xs_wA.
$$

因此，实际 FP8 GEMM 通常包含：

1. 高精度输入缩放。
2. FP8 转换。
3. FP8 乘法。
4. FP16、BF16 或 FP32 累加。
5. 缩放因子恢复。
6. 输出到较高精度格式。

这也是理解 FP8 的关键：

> FP8 通常用于矩阵乘法输入，而不是要求整个算子链和累加器都使用 8 位浮点数。


![](https://fastly.jsdelivr.net/gh/bucketio/img7@main/2026/07/23/1784826216891-038b90e2-5509-48e3-ae92-9aa565ac0b9c.png)


## FP8 混合精度训练

![](https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/07/23/1784826620966-12c00c57-195c-45af-9ddd-110ff6eb6ff4.png)


一个典型的 FP8 训练步骤可以表示如下。

### 前向传播

权重和激活先进行缩放：

$$
W_q=Q_{\mathrm{E4M3}}(a_wW),
$$

$$
X_q=Q_{\mathrm{E4M3}}(a_xX).
$$

矩阵乘法使用 FP8 输入：

$$
Y_{\mathrm{high}}
=
\frac{1}{a_xa_w}
\operatorname{GEMM}(X_q,W_q).
$$

输出通常保存为 BF16、FP16 或 FP32。

---

### 反向传播

激活梯度使用 E5M2：

$$
G_q
=
Q_{\mathrm{E5M2}}(a_gG).
$$

权重梯度可以近似写成

$$
\nabla W
\approx
\frac{1}{a_xa_g}
X_q^\mathsf{T}G_q.
$$

输入梯度可以近似写成

$$
\nabla X
\approx
\frac{1}{a_wa_g}
G_qW_q^\mathsf{T}.
$$

---

### 参数更新

优化器状态和主权重通常保留更高精度。假设参数更新量为 $\Delta W$，如果主权重直接使用低精度保存，可能出现：

$$
Q(W+\Delta W)=Q(W),
$$

即更新量小于当前量化间隔，被完全舍入掉。因此，FP8 训练通常采用：

$$
\boxed{
\text{FP8 GEMM 输入}
+
\text{高精度累加}
+
\text{高精度敏感算子}
+
\text{高精度优化器状态}
+
\text{高精度主权重}
}
$$

而不是让整个训练过程全部使用 FP8。

## 哪些算子不适合直接使用 FP8

并不是所有算子都能从 FP8 中获得收益。**矩阵乘法**和**卷积**具有较高计算强度，低精度吞吐提升容易覆盖缩放和转换成本。以下算子通常更敏感：

- Softmax
- 指数和对数
- LayerNorm
- RMSNorm
- 大规模归约
- 方差计算
- 残差累加
- 损失函数
- 优化器更新

例如 Softmax 为 $p_i=\frac{e^{x_i}}{\sum_j e^{x_j}}$，指数函数会放大输入扰动。若输入误差为 $\delta$，则 $e^{x+\delta}=e^xe^\delta$。即使 $\delta$ 很小，也会以乘性形式影响输出。

归一化算子中还存在**均值**、**平方和**、**方差**和**倒数平方根**等操作，这些操作对累加精度和极小数处理都比较敏感。

## FP8 与 INT8 如何选择

FP8 并不必然优于 INT8，二者适合不同场景。

### INT8 更有优势的情况

- 模型主要用于推理。
- 校准数据具有代表性。
- 权重和激活分布容易控制。
- 硬件具有成熟的 INT8 内核。
- 极致能效比格式通用性更重要。
- 可以使用 per-channel 或 per-token 缩放。
- 零点和缩放补偿开销可以被融合。

### FP8 更有优势的情况

- 需要训练和推理统一使用低精度浮点格式。
- 张量跨越多个数量级。
- 激活或梯度动态范围变化明显。
- 不希望进行复杂的整数零点设计。
- 硬件原生支持 FP8 矩阵乘法。
- 需要降低大模型训练中的显存和通信开销。

### 数值特性的本质差异

INT8 的优势来自**均匀密集的整数网格**：$\hat{x}=s(q-z)$。

FP8 的优势来自**指数编码**：$x=(-1)^S2^{E-\mathrm{bias}}\left(1+\frac{M}{2^m}\right)$。

因此：

- INT8 更适合动态范围受控、校准充分的张量。
- FP8 更适合跨数量级、训练期间分布持续变化的张量。
- 低位整数通常可以提供更高的局部绝对精度。
- FP8 通常具有更强的动态范围容忍能力。

## 工程实践中的关键指标

仅比较最终任务精度通常无法定位量化问题。建议同时监控以下统计量。

### 饱和率

$$
r_{\mathrm{sat}}
=
\frac{
\#\{|ax_i| > F_{\max}\}
}{N}.
$$

饱和率过高通常说明：

- 缩放因子过大。
- 张量中存在严重离群值。
- 缩放粒度过粗。
- 历史缩放因子已不适配当前分布。

---

### 零值率

$$
r_0
=
\frac{
\#\{Q(ax_i)=0\}
}{N}.
$$

零值率异常升高可能说明：

- 缩放因子过小。
- 大量数值进入下溢区域。
- 张量本身具有高稀疏性。
- E4M3 的范围不足以同时覆盖大小数值。

---

### 非规格化数比例

$$
r_{\mathrm{sub}}
=
\frac{
\#\{Q(ax_i)\text{ 为非规格化数}\}
}{N}.
$$

非规格化数比例过高意味着大量有效值位于格式的低精度边缘。

---

### 均方误差

$$
\operatorname{MSE}
=
\frac{1}{N}
\sum_i
(x_i-\hat{x}_i)^2.
$$

MSE 适合衡量绝对误差，但会对大幅值元素更加敏感。

---

### 相对误差

$$
r_i
=
\frac{|x_i-\hat{x}_i|}
{|x_i|+\epsilon}.
$$

相对误差更适合分析浮点量化，但在接近零的位置容易不稳定，因此需要加入 $\epsilon$。

---

### 信号量化噪声比

$$
\operatorname{SQNR}
=
10\log_{10}
\frac{\|x\|_2^2}
{\|x-\hat{x}\|_2^2}.
$$

SQNR 可以用于逐层比较量化噪声。较低的 SQNR 不一定直接导致任务精度下降，但通常是需要重点检查的信号。

---

### 缩放因子的时间稳定性

对于 delayed scaling，可以观察 $\frac{a_t}{a_{t-1}}$ 或 $\operatorname{Var}\left(\log_2 a_t\right)$。如果缩放因子频繁剧烈变化，说明张量分布不稳定，可能需要：

- 更短的历史窗口。
- 更鲁棒的 `amax` 聚合方法。
- 更细粒度的块级缩放。
- 对部分层保留 BF16 或 FP16。
- 对离群值进行单独处理。

## FP8 量化伪代码

下面的代码展示 current scaling 的基本逻辑。它只描述算法，不代表真实硬件级 FP8 加速实现。

```python
def quantize_fp8(x, fp8_max, cast_fp8, eps=1e-12):
    """
    x:
        高精度输入张量
    fp8_max:
        E4M3 为 448
        E5M2 为 57344
    cast_fp8:
        执行舍入、饱和和目标 FP8 编码的函数
    """
    amax = x.abs().max()

    # 编码阶段的放大因子
    encode_scale = fp8_max / amax.clamp_min(eps)

    # 映射到 FP8 的有效表示范围
    scaled = x * encode_scale
    q = cast_fp8(scaled)

    # 解码阶段恢复原始数量级
    x_hat = q / encode_scale

    return q, x_hat, encode_scale
```

矩阵乘法需要分别保存两个输入张量的缩放因子：

```python
x_q, _, scale_x = quantize_fp8(
    x,
    fp8_max=448,
    cast_fp8=cast_e4m3,
)

w_q, _, scale_w = quantize_fp8(
    weight,
    fp8_max=448,
    cast_fp8=cast_e4m3,
)

acc = fp8_gemm_with_high_precision_accumulation(
    x_q,
    w_q,
)

output = acc / (scale_x * scale_w)
```

真实硬件实现通常会融合：

- 缩放
- FP8 转换
- 数据布局转换
- GEMM
- 反缩放
- Bias
- 激活函数

以减少中间张量写回显存的开销。

## Transformer Engine 示例

下面展示 Transformer Engine 中典型的 HYBRID delayed scaling 配置：

```python
import torch
import transformer_engine.pytorch as te

from transformer_engine.common.recipe import (
    DelayedScaling,
    Format,
)

recipe = DelayedScaling(
    fp8_format=Format.HYBRID,
    amax_history_len=16,
    amax_compute_algo="max",
)

layer = te.Linear(
    1024,
    1024,
    params_dtype=torch.bfloat16,
).cuda()

x = torch.randn(
    32,
    128,
    1024,
    dtype=torch.bfloat16,
    device="cuda",
)

with te.autocast(enabled=True, recipe=recipe):
    y = layer(x)

loss = y.float().square().mean()
loss.backward()
```

其中：

- `Format.HYBRID` 通常表示前向使用 E4M3，反向使用 E5M2。
- `amax_history_len` 控制最大绝对值历史长度。
- `amax_compute_algo="max"` 表示使用历史最大值计算缩放因子。
- 参数本身仍然可以使用 BF16 保存。
- `autocast` 负责维护 FP8 转换、`amax` 历史和缩放因子。

## 常见误区

### 误区一：FP8 有指数，所以不需要缩放

指数只能解决单个格式内部的相对动态范围问题，无法保证同一个固定格式同时适配所有层、所有张量和所有训练阶段。FP8 训练通常仍然需要 per-tensor、per-block 或其他细粒度缩放。

### 误区二：把张量保存为 8 位就一定会加速

性能收益需要：

- 硬件原生支持
- 低精度 GEMM 内核
- 合理的数据布局
- 算子融合
- 足够大的矩阵规模
- 较低的格式转换开销

使用 FP32 模拟 FP8 只能评估精度，通常不会获得速度收益。


### 误区三：整个网络都应该使用 FP8

FP8 主要用于计算密集型算子的输入。归一化、指数、损失计算、累加和参数更新通常需要更高精度。


### 误区四：E5M2 一定比 E4M3 更稳定

E5M2 的动态范围更大，但尾数精度更低。对于权重和激活，舍入误差可能比溢出风险更严重。格式选择需要根据张量的动态范围和误差敏感性共同决定。


![](https://fastly.jsdelivr.net/gh/bucketio/img18@main/2026/07/23/1784826112662-cd245c59-fdd5-4c86-9f41-c0e9074d98b6.png)


### 误区五：最大值校准总是最安全

最大值校准不会截断有限输入，却可能让一个离群值占据绝大多数量化范围。对于长尾分布，适度截断可能降低总体误差。


### 误区六：FP8 与 INT8 都是 8 位，所以精度相同

两者的 256 种编码分布完全不同：

- INT8 的编码在线性数轴上均匀分布。
- FP8 的编码按指数分段分布。
- INT8 的绝对步长固定。
- FP8 的绝对步长随数值幅度增长。
- INT8 通常需要显式缩放因子。
- FP8 使用浮点符号、指数和尾数结构。


## 总结

量化的本质不是简单降低数据类型，而是**在有限数值集合、模型误差和硬件效率之间进行联合设计**。

整数量化使用线性量化网格，需要显式设计缩放因子和零点。浮点量化通过指数提供非均匀网格，在较大数值处使用较粗间隔，在较小数值处使用较细间隔，因此更适合跨数量级的数据分布。

FP8 的两种核心格式解决了不同问题：

- $\mathrm{E4M3}\rightarrow\text{较高表示精度，适合权重和激活}$
- $\mathrm{E5M2}\rightarrow\text{较大动态范围，适合梯度}$

真正可用的 FP8 系统还需要配合：

- 合理的张量级或块级缩放。
- RTNE 或随机舍入。
- 高精度累加器。
- 高精度归一化和非线性算子。
- 高精度主权重和优化器状态。
- 对饱和率、下溢率和量化噪声的持续监控。
- 原生支持低精度计算的硬件和内核。

FP8 的价值并不仅在于“把 16 位变成 8 位”，而在于它**提供了一种适合深度学习数值分布的低精度浮点体系，使训练、推理、存储、通信和硬件计算能够围绕低位宽表示进行协同优化**。

---

## 参考资料

1. Open Compute Project, [OCP 8-bit Floating Point Specification (OFP8), Revision 1.0](https://www.opencompute.org/documents/ocp-8-bit-floating-point-specification-ofp8-revision-1-0-2023-12-01-pdf-1)
2. Micikevicius et al., [FP8 Formats for Deep Learning](https://arxiv.org/abs/2209.05433)
3. NVIDIA, [Using FP8 and FP4 with Transformer Engine](https://docs.nvidia.com/deeplearning/transformer-engine/user-guide/examples/fp8_primer.html)
4. Nagel et al., [A White Paper on Neural Network Quantization](https://arxiv.org/abs/2106.08295)
5. Jacob et al., [Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference](https://arxiv.org/abs/1712.05877)
6. Xiao et al., [SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models](https://arxiv.org/abs/2211.10438)
7. Open Compute Project, [OCP Microscaling Formats (MX) Specification](https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf)


