LLM Systems 19: LLM Quantization: Basic methods

0. Recap

之前课程主线:

  • LLM 简介
  • CUDA 编程基础
  • 深度学习框架(自动求导)
  • Transformer/Pre-trained LLM
  • LLM 前后两个辅助组件:Tokenizer 与 Decoding
  • Transformer 加速:Kernel Fusion & Mix-precision & Memory Management Optimization
  • 支线(跳过):Guest Lecture:Google TPU/JAX 生态介绍
  • 分布式训练技术:
    • 基本概念与集合通信原语
    • DDP
    • TP
    • PP
    • MoE:限制 FLOPs 前提下提升模型参数效率 & EP 专家并行
    • ZeRO:内存效率优化

接下来的 3 个 lecture(即:lecture 19 & 20 & 21)介绍模型量化与高效微调技术,即:通过将高精度数值转换为低比特格式,缩小模型体积并加速计算过程;使用低秩矩阵分解等技术,进行 LLM 参数高效的微调。

1. Low precision numbers in computer

1.1 Background

问题:LLM 训练推理成本高昂

  • Llama-70B
    • 39.3M H100-80GB GPU hours to train
    • requires 140GB GPU memory for inference
  • DeepSeek V3 (671GB)
    • 2.8M H800 GPU hours to train
    • requires > 400GB GPU memory for inference

Solution:模型量化

  • 量化:使用低精度存储参数和层计算结果
    • 优点:
      • 减少内存占用 -> 更大 batch size
      • 加速计算,每个时钟周期更多运算
    • 缺点:
      • 可能降低精度

1.2 Precision Formats

Precision Formats


  • 浮点数表示:符号位 + 指数位 + 尾数位,表示数字计算方式: \(\pm 1.\text{mantissa} \times 2^{a - 2^{\text{exponent}-1} - 1}\)
    • FP32(32位单精度浮点数):
      • 位元分配:1 位符号位 + 8 位指数位 + 23 位尾数位。
      • 动态范围:\(-3.4 \times 10^{38} \sim 3.4 \times 10^{38}\)。
      • 精度:约为 \(7 \times 10^{-46}\),提供最高的数值精度但占用最大的内存与算力。
    • FP16(16位半精度浮点数):
      • 位元分配:1 位符号位 + 5 位指数位 + 10 位尾数位。
      • 动态范围:\(-8.59 \times 10^{9} \sim 8.59 \times 10^{9}\)。
      • 精度:约为 \(2.98 \times 10^{-8}\);相比 FP32 节省一半内存,但由于指数位较少,在深层网络训练中容易发生数值上溢或下溢。
    • BF16(16位 Brain Floating Point):
      • 位元分配:1 位符号位 + 8 位指数位(与 FP32 相同)+ 7 位尾数位。
      • 动态范围:与 FP32 完全相同(\(-3.4 \times 10^{38} \sim 3.4 \times 10^{38}\))。
      • 精度:约为 \(3.05 \times 10^{-5}\)。
      • 关键结论:BF16 相比精度更注重动态范围(BF16 favors Dynamic Range than Precision)。这使得它能有效避免梯度溢出,成为大语言模型(LLM)训练与推理的标配格式。
  • INT8(8位定点整数):
    • 数值范围:\(-128 \sim 127\)。
    • 精度:整数精度。在量化过程中,算法将高精度的浮点数(如 FP32/FP16)压缩映射到 INT8 的离散整数区间内,以实现存储与加速的收益。

半精度浮点数计算速度优势


  • GPU 内部提供了 HFMA2(Half-precision Fused Multiply-Add for 2 elements) 指令,即半精度融合乘加指令。
  • 加速效果:能够在单个时钟周期(one cycle)内同时并行计算 2 个元素的乘加操作,从而带来 2 倍的计算加速(2x speedup)。

CUDA APIs for Half Precision

  • __hadd2(const __half2 a, const __half2 b): 在“舍入到最近偶数(round-to-nearest-even)”模式下,执行 __half2(包含 2 个半精度浮点数的向量)的向量加法。
  • __hfma2(const __half2 a, const __half2 b, const __half2 c): 在“舍入到最近偶数”模式下,执行 __half2 的向量融合乘加运算。

1.3 Direct Quantization Approach

  • 使用低精度
    • 将参数从 fp32 转换为 int8 或者 int4 格式
    • 在低精度下执行所有运行
  • 问题:
    • 精度损失(Loss of Precision):引发量化噪声(Quantization Noise)的逐层累积。
    • 数值范围错配(Range Mismatch):极端数值被强制截断(Clipping),导致关键信息丢失。
    • 量化误差(Quantization Error):由于浮点数取整舍入(Rounding Errors)带来的数学偏差。
  • 量化方法:
    • Absmax quant
    • Zero-point quant


  • 下面的 Absmax & Zero-point 两个简单量化方法,本质是都是在做一个一次函数映射,所谓的 scale 相当于直线斜率,所谓的零点是直线不过零点时的 \(y=kx + b\) 中的 \(b\)。
  • 标量量化本质上就是做一次函数映射 \(y = kx + b\):把连续的浮点区间先按斜率 \(k\) 缩放、再按截距 \(b\) 平移,整体‘套’进 INT8 的整数网格里;Scale 就是网格步长(\(\frac{1}{k}\)),Zero-point 就是把截距 \(b\) 锁死成整数后的原点投影。

Absmax 量化

Absmax 是最基础的对称量化方法,它根据数据矩阵中最大的绝对值进行等比例缩放,将数值对称线性映射到 \([-127, 127]\) (注意:这里取最大值是 127,而不是 int8 的表示范围 \([-128, 127]\) ,这与下面的 scale 计算方式取 \(\max |X|\) 有关,需要取整数取值最大值 127 作为 scale 计算参数)的 INT8 范围。

  • 数学公式:

    • 量化(Quantization): 其中 \(\frac{127}{\max |X|}\) 为 scale。

    \[ X_{\text{quant}} = \text{round}\left( \frac{127}{\max |X|} \cdot X \right) \]

    • 反量化(Dequantization):

    \[ X_{\text{dequant}} = \frac{\max |X|}{127} \cdot X_{\text{quant}} \]

  • PyTorch 代码实现:

1
2
3
4
5
6
7
8
def absmax_quantize(X):
# 1. 计算缩放因子 scale
scale = 127 / torch.max(torch.abs(X))
# 2. 量化并取整
X_quant = (scale * X).round()
# 3. 反量化恢复浮点数
X_dequant = X_quant / scale
return X_quant.to(torch.int8), X_dequant

Zero-point 量化

当输入数据的分布非对称(例如激活值经过 ReLU/GeLU 后全为正数,或分布极度偏向某一侧)时,Absmax 会浪费表示空间。Zero-point 量化通过引入零点偏移量(Zero-point),将数据区间 \([\min(X), \max(X)]\) 完整映射到 INT8 的 \([-128, 127]\) 全范围(区间长度为 255)。

  • 数学公式:

    • 缩放因子(Scale):

    \[ \text{scale} = \frac{255}{\max(X) - \min(X)} \]

    • 零点偏移量(Zero-point):

    \[ \text{zeropoint} = -\text{round}(\text{scale} \cdot \min(X)) - 128 \]

    这一步本质在将区间的左端点 \(\text{scale} \cdot x_{\min}\) 平移到 int8 表示范围的最左侧,即:\(\text{scale} \cdot x_{\min} + \text{zeropoint} = -128 \implies \mathbf{\text{zeropoint} = -\text{round}(\text{scale} \cdot x_{\min}) - 128}\) ,变换后进一步得到零点表示(Zero-point 就是真实的浮点数 \(0.0\) 在量化后的整数空间中所对应的那个整数值)。

    • 量化(Quantization):

    \[ X_{\text{quant}} = \text{round}(\text{scale} \cdot X + \text{zeropoint}) \] 注:实际代码中会使用 clip 将数值限制在 [-128, 127] 范围内)

    • 反量化(Dequantization):

    \[ X_{\text{dequant}} = \frac{X_{\text{quant}} - \text{zeropoint}}{\text{scale}} \]

  • PyTorch 代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
def zeropoint_quantize(X):
# 1. 计算数据极差
x_range = torch.max(X) - torch.min(X)
x_range = 1 if x_range == 0 else x_range
# 2. 计算 scale 与 zeropoint
scale = 255 / x_range
zeropoint = (-scale * torch.min(X) - 128).round()
# 3. 量化、截断并转为 int8
X_quant = torch.clip((X * scale + zeropoint).round(), -128, 127)
# 4. 反量化
X_dequant = (X_quant - zeropoint) / scale
return X_quant.to(torch.int8), X_dequant

Reference:

2. Basic Quantization Methods

量化方法可以分为训练中和训练后两类:

  • Quantization-Aware Training (QAT) incorporates the weight conversion process during the pre-training or fine-tuning stage, resulting in enhanced model performance. However, QAT is computationally expensive and demands representative training data.
  • Post-Training Quantization (PTQ) is a straightforward technique where the weights of an already trained model are converted to lower precision without necessitating any retraining. Although easy to implement, PTQ is associated with potential performance degradation.
    • preserve accuracy(hard to scale to large parameters (billions))
      • AdaQuant (Hubara et al., 2021)
      • BRECQ (Li et al., 2021)
      • OBQ (Frantar et al., 2022)
    • scale to large parameters(accuracy loss when lower-bit precision (ex. 3, 4 bits per parameter))
      • ZeroQuant (Yao et al., 2022)
      • LLM.int8() (Dettmers et al., 2022)

Basic Approach: Layer-Wise Quantization

  • 直接对参数量化容易引发误差累积,因此基础逐层量化提出:在逐层进行线性投影时,寻找一个量化后的权重矩阵 \(\hat{W}\),使其输出尽可能贴近原始全精度矩阵 \(W\) 的输出。
  • 转化为优化问题:最小化输出误差

\[ \arg\min_{\hat{W}} \| W X - \hat{W} X \|_2^2 \]

  • 其中:
    • \(W\):原始全精度的线性投影权重矩阵(例如 Transformer 中的 FFN 和 Self-Attention)。
    • \(\hat{W}\):所要求解的目标量化权重矩阵。
    • \(X\):当前层的输入激活值。
  • 问题:仍然会导致量化误差在逐层叠加中不断累积(accumulation of quantization error)。

2.1 AdaQuant

  • 上述朴素 Layer-Wise Quantization 缺点:没有考虑输入激活值 \(X\) 在实际推理中也会被量化的情况,并且缺少优化的自由度,因而误差仍会在深层网络中不断累积。
  • AdaQuant 优化目标:

\[ (\hat{\Delta w}, \hat{\Delta x}, \hat{V}) = \arg\min_{(\Delta w, \Delta x, V)} \left\| W X - Q_{\Delta w}(W + V) \cdot Q_{\Delta x}(X) \right\|_2^2 \]

其中:

  • \(W X\)(基准 Target):
    • 原始全精度(FP32/FP16)线性层的真实输出结果。\(W\) 为全精度权重,\(X\) 为传入该层的全精度激活值。
  • \(V\)(连续修正矩阵 / Continuous Offset Matrix):
    • AdaQuant 的核心创新。因为量化函数 \(Q(\cdot)\) 是不可导的,直接对取整后的权重求导无法优化。
    • AdaQuant 在量化前给原权重叠加了一个连续可微的微调矩阵 \(V\)(即计算 \(W + V\))。在小批量校准数据上,通过梯度下降来微调 \(V\),使量化后的权重能够拟合出更好的局部极小值。
  • \(\Delta w\) 与 \(\Delta x\)(量化步长 / Step Sizes):
    • 分别代表权重和激活值的量化步长(Scale)。它们决定了连续浮点数被划分到哪个离散的整数网格中。
    • 在 AdaQuant 中,这两个步长不仅可以手动设定,还可以作为可学习参数通过梯度下降进行优化。
  • \(Q_{\Delta w}(\cdot)\) 与 \(Q_{\Delta x}(\cdot)\)(量化函数):
    • 分别对“修正后的权重 \(W+V\)”和“输入激活值 \(X\)”执行带步长的量化与反量化操作。
  • \(\|\cdot\|_2^2\)(L2 范数 / 均方误差):
    • 衡量“全精度输出 \(W X\)”与“量化后输出 \(Q_{\Delta w}(W + V) \cdot Q_{\Delta x}(X)\)”之间的均方误差。
  • 量化函数如何进行反向传播:
    • 使用:直通估计器(Straight-Through Estimator, STE)
    • \(V\) 求导:
      • 前向传播(Forward):严格执行真实的量化取整逻辑 \(Q_{\Delta w}(W+V)\),得到离散的量化值。
      • 反向传播(Backward):在计算导数时,人为将取整函数的导数近似替代为 (即恒等映射): \(\frac{\partial Q(y)}{\partial y} \approx 1\)
      • 作用:梯度不会因为取整函数而中断,损失函数 \(\mathcal{L} = \| W X - Q_{\Delta w}(W + V) \cdot Q_{\Delta x}(X) \|_2^2\) 对量化输出的梯度,能够无损地穿透 \(Q(\cdot)\) 直接传递给 \(V\)。
    • \(\Delta w\) 与 \(\Delta x\) 求导:

假设一个标准的量化函数为(其中 \(\Delta\) 为 scale): \[ Q_\Delta(z) = \Delta \cdot \text{round}\left(\frac{z}{\Delta}\right) \]

为了书写简便,我们令 \(y = \frac{z}{\Delta}\),取整后的离散整数为 \(\hat{z} = \text{round}(y)\)。那么量化输出就是:

\[ Q_\Delta(z) = \Delta \cdot \hat{z} \]

现在我们对步长 \(\Delta\) 求偏导(运用乘积法则与链式法则):

\[ \frac{\partial Q_\Delta(z)}{\partial \Delta} = \frac{\partial (\Delta \cdot \hat{z})}{\partial \Delta} = \hat{z} + \Delta \cdot \frac{\partial \hat{z}}{\partial \Delta} \]

接下来关键的一步,利用直通估计器(STE),将取整函数的导数近似为 1(即 \(\frac{\partial \text{round}(y)}{\partial y} \approx 1\)):

\[ \frac{\partial \hat{z}}{\partial \Delta} = \frac{\partial \text{round}(y)}{\partial y} \cdot \frac{\partial y}{\partial \Delta} \approx 1 \cdot \left( -\frac{z}{\Delta^2} \right) = -\frac{z}{\Delta^2} \]

将结果代回最上面的求导公式中,得到:

\[ \frac{\partial Q_\Delta(z)}{\partial \Delta} \approx \hat{z} + \Delta \cdot \left( -\frac{z}{\Delta^2} \right) = \hat{z} - \frac{z}{\Delta} = \text{round}\left(\frac{z}{\Delta}\right) - \frac{z}{\Delta} \]

最后的求导结果:\(\text{round}\left(\frac{z}{\Delta}\right) - \frac{z}{\Delta}\) 正好就是舍入误差(Quantization / Rounding Error):

  • 内部未截断区域(Rounding Area):梯度的方向和大小完全由当前的“舍入偏差”决定。梯度下降会自动调整 \(\Delta\) 的大小,使得网格间距尽可能去匹配数据分布,从而降低平均舍入误差。
  • 边界截断区域(Clipping Area):如果数值 \(z\) 太大超出了 INT8 范围(即触发了 clip 截断),比如被截断为最大值 \(q_{max}\),此时 \(Q_\Delta(z) = \Delta \cdot q_{max}\)。
    • 对 \(\Delta\) 求导直接结果就是 \(q_{max}\)
    • 物理含义:如果有太多的数值被截断丢失了信息,梯度会产生一个强有力的推力将 \(\Delta\) 放大,从而拓宽动态范围,减少截断损失。

2.2 ZeroQuant

  • 逐层知识蒸馏(Layer-by-layer knowledge distillation)
    • 原始高精度模型作为 Teacher
    • 量化模型作为 Student
  • 优化目标:

\[ \mathcal{L}_{LKD, k} = \left\| L_k \cdot L_{k-1} \cdot \dots \cdot L_1 (X) - \tilde{L}_k \cdot \tilde{L}_{k-1} \cdot \dots \cdot L_1 (X) \right\|_2^2 \]

其中:

  • \(L_k\):代表教师模型第 \(k\) 层的输出。

  • \(\tilde{L}_k\):代表学生模型第 \(k\) 层的量化输出。

  • 核心逻辑:在校准第 \(k\) 层时,输入 \(X\) 会同时通过教师模型和学生模型的前 \(k-1\) 层累积计算;通过最小化第 \(k\) 层输出的 L2 范数距离(蒸馏损失 \(\mathcal{L}_{LKD, k}\)),来精确调整第 \(k\) 层的量化参数与步长,从而确保量化后的学生模型能最大程度复现教师模型的逐层表示。

  • 通过 kernel fuse 优化性能,示意图如下:


  • 原始计算形式:
    • Norm/Activation
    • 量化
    • GEMM
    • 反量化
  • fuse 后:
    • Norm/Activation + 量化
    • GEMM + 反量化
  • ZeroQuant 性能:
    • 成功在 20B 规模模型(GPT-NeoX-20B) 上验证了可扩展性。
    • 对于 1.3B 规模的模型,蒸馏校准计算时间约为 3 小时
      • 校准速度仍不及后续的 GPTQ(GPTQ 可以在约 4 小时内完成比其大 100 倍模型的量化)。

2.3 LLM.int8()

  • 问题与背景
    • 当 Transformer 模型的参数规模突破 6.7B 阈值时,模型的激活值中会突然涌现出极少数数值巨大(如绝对值 \(\ge 6.0\))的“离群特征”(Outlier Features)**。
    • 如果使用传统的 8-bit 均匀量化(如 Absmax),为了把这些极大的离群值囊括进来,缩放因子 Scale 会被拉大,导致占绝大多数(99.9% 以上)的常规数值被挤压映射到极少数 INT8 离散网格里,造成严重的信息丢失与精度崩塌(Accuracy Degradation)。
    • 如下图所示:


  • 橙线(8-bit baseline):在 6.7B 参数前表现正常,但在 6.7B 节点后由于离群特征的涌现,准确率发生“断崖式”下跌。
  • 绿线(16-bit baseline) 与 蓝线(LLM.int8()):LLM.int8() 的准确率曲线与全精度 FP16 曲线完全重合,成功将 INT8 量化扩展到了 175B 规模。

方法示意图如下:


  • 离群特征的定义:
    • 满足 数值极大(\(\ge 6.0\))、存在于超 25% 的层,且集中分布在约 6% 的特征序列维度(即呈现列状分布)。
  • 混合计算流程:普通值用 int8 计算,离群值用 fp16 计算,然后二者结果相加
    • 第一步:拆分(Decomposition):
      • 将输入激活矩阵 \(X\) 和权重矩阵 \(W\) 按照列/行进行拆分。
      • 常规数值(上图蓝块):占 99% 以上的普通特征,转入 INT8 路径。
      • 离群数值(上图黄块):提取出这 6% 包含离群值的特定列,保留 FP16 高精度。
    • **第二步:并行矩阵乘法:
      • 8-bit 向量级量化路径:对常规数值执行 INT8 矩阵乘法。这里采用了向量级缩放(Vector-wise Quantization)——对 \(X\) 按行计算缩放常数 \(C_x\),对 \(W\) 按列计算 \(C_w\),比整个矩阵共享单个 Scale 更精细。
      • 16-bit 高精度路径:对离群列直接执行常规的 FP16 矩阵乘法 \(X_{F16} W_{F16}\)。
    • 第三步:反量化与结果相加(Dequantize & Add):
      • 将 INT8 矩阵乘法的结果反量化还原为 FP16,与 FP16 离群路径的乘积直接相加(\(\text{Out}_{F16} = \text{Out}_{\text{INT8}} + \text{Out}_{\text{FP16}}\)),得到最终的输出矩阵。

3. Conclusion

  • low-bit number representation in computer
    • BF16: 16-bit half precision floating point numbers, better for ML
    • int8
  • Direct quantization
    • Absmax: linearly scale according to max abs value
    • Zero-point: finding zero-point and scale
  • Layer-wise quantization approaches
    • AdaQuant
    • KD: ZeroQuant
    • LLM.int8()

LLM Systems 19: LLM Quantization: Basic methods
https://arcsin2.cloud/posts/2026/11/2260193832/
作者
arcsin2
发布于
2026年11月14日
许可协议