通用量化模型

对于任意张量 (X),量化可以抽象为:

\[ X \approx \hat{X} = s_X \cdot Q_X \]

其中:

核心计算目标

GEMM 的通用形式

\[ Y = XW \]

量化后:

\[ X \approx s_X Q_X,\quad W \approx s_W Q_W \]

代入:

\[ Y \approx (Q_X Q_W) \cdot (s_X s_W) \]

在低精度空间完成大部分计算,同时最小化恢复到高精度的代价

也就是:

\[ \text{目标:} \quad XW \approx (Q_X \cdot Q_W) \cdot (s_X \cdot s_W) \]

理解视角

把 GEMM 拆成两部分:

低精度核心计算

\[ Z = Q_X Q_W \]

特点:

高精度缩放恢复

\[ Y = Z \cdot (s_X s_W) \]

特点:

进一步抽象

我们可以写成一个统一计算框架:

\[ Y = f(Q_X, Q_W, s_X, s_W) \]

其中:

\[ f = \text{LowPrecisionMatmul}(Q_X, Q_W) \cdot g(s_X, s_W) \]

优化目标

大模型推理中,真正优化的是:

计算效率

最大化:

\[ \text{Throughput}(Q_X Q_W) \]

也就是:


精度误差最小化

最小化:

\[ |XW - (Q_X Q_W) s_X s_W| \]

这决定:

scale 的计算/存储成本

你那句其实点到了关键:

需要额外乘两个 scale

工程上目标是:

👉 让 scale 成本“消失”

方法:


其实可以把整个量化推理写成:

\[ \boxed{ \min_{Q_X, Q_W, s_X, s_W} |XW - s_X s_W (Q_X Q_W)| } \]

约束:

量化粒度

{{< svg “/images/quantization_granularity_v2.svg” >}}

粒度从粗到细:

粒度 含义 scale 数量
Per-tensor 整个张量共享一个 scale 1
Per-channel / Per-token 权重按输出通道、激活按 token 各一个 scale N 或 M
Per-group 输入通道每 group_size 个共享一个 scale N × (K/group_size)
Per-block 二维分块,如 128×128 的 tile 一个 scale(DeepSeek FP8 用的就是这种) 按 block 数

Per-group 内还可以细分: - 顺序分组:通道 [0..127] 一组、[128..255] 一组(常见,Kimi K2.5 就是这种) - act_order 分组:先按激活幅度排序再分组(GPTQ desc_act=True)

量化对象

量化对象有两个:weight(权重)和 activation(激活)

默会前提:现代大模型量化,默认原始精度 ≈ BF16

如果 W4,就是代表 weight 用 4bit,4 < 16,就是用了量化 如果 A8,就是代表 activation 用 8bit,8 < 16,用了量化

这是一个重要维度,命名惯例如 WxAy 表示权重 x-bit、激活 y-bit:

数据类型

两大阵营:

整数

浮点

关键区别:整数量化是均匀映射(量化值等间距),浮点量化是非均匀映射(小值处密、大值处疏),更符合权重的实际分布。

先说明几个单词:

Sign:符号位 Exponent(E):指数位 Mantissa(M):小数位

浮点格式 符号位 指数位 小数位 量化 group size(典型值)
bfloat16 1 8 7 通常不分组或按层量化
fp16 1 5 10 通常不分组或按层量化
fp8(e4m3) 1 4 3 128(常见于权重分组)
fp8(e5m2) 1 5 2 128(常见于权重分组)
fp8(e8m0) 1 8 0 128(blackwell 默认使用)
fp4(e2m1) 1 2 1 32 或 64(常用于权重)
mxfp8 1 4 3 128(类似 fp8(e4m3))
mxfp4 1 2 1 32 或 64(类似 fp4(e2m1))

mxfp8 和 标准fp8 量化方案的区别

量化算法

上面说的是”格式”,还有一个维度是”怎么选 scale”:

Example

量化方案 = 粒度 × 对象 × 数据类型 × 算法

Kimi K2.5 的位置:
  粒度:   per-group (group_size = 32)
  对象:   weight-only (W4A16)
  类型:   INT4 对称 (uint4b8, 无 qzeros)

DeepSeek V3/R1 的位置:
  粒度:   Per-block 128×128
  对象:   weight+activation (W8A8)
  类型:   FP8 (e4m3)

uint4b8 就是 [0, 15] - 8,范围是和 int4 一样的,那为什么要用 uint4

区别纯粹是存储编码层面的:unsigned 4-bit 在 bit packing 到 int32 时不需要处理符号位扩展,实现上更简单。GPU kernel 解包时直接 (v >> shift) & 0xF 就行,不用额外做符号位处理。