Skip to content

模型量化技术

模型量化是一种通过降低模型参数数值精度(例如从 FP16 降到 INT8 甚至 INT4)来压缩模型体积、加速推理的技术,是大语言模型在消费级硬件上高效部署的关键手段。

可以把量化想象成”把高清照片压缩成 JPEG”。原始照片的每个像素可能用 24 位色彩表示,信息极其丰富但体积庞大;JPEG 通过丢弃人眼难以察觉的细节,把文件压缩到十分之一甚至更小,而看起来几乎没有差别。量化做的是类似的事:神经网络里的权重本来用 16 位甚至 32 位浮点数存储,但相邻权重之间的微小差异对最终输出影响极小。量化把这些浮点数”四舍五入”到更少的几个离散档位上(比如 256 档对应 INT8,16 档对应 INT4),从而同时减少存储和计算开销。关键在于:只要选对档位(让重要的数值落得准),模型能力几乎不下降。

另一个有用的类比是”用直尺量身高”。如果直尺的最小刻度是 1 毫米(高精度浮点),你能量出 175.3 毫米;但如果只需要知道这个人有没有到 1.7 米,一把只有厘米刻度的尺子(低精度整数)就完全够用了。量化本质上就是在问:“我们真的需要那么多小数位吗?”

量化的核心是把一段连续的浮点数区间映射到有限个整数档位上。给定一组数值的范围 min 和 max,目标位宽为 bit(例如 8 或 4),档位总数为 2bit−12^{\text{bit}} - 1(例如 8 位有 256 档)。

量化过程(从浮点到整数):

scale=max⁡−min⁡2bit−1q=round(x/scale)+zero_point\text{scale} = \frac{\max - \min}{2^{\text{bit}} - 1} \quad q = \text{round}(x / \text{scale}) + \text{zero\_point}

反量化过程(用整数恢复出近似的浮点数):

x≈scale×(q−zero_point)x \approx \text{scale} \times (q - \text{zero\_point})

其中 scale(缩放因子)决定了每一档代表多大的浮点跨度,zero_point(零点)用来让浮点的零值精确对应到某个整数,避免零点附近的误差累积。

让我们用一个具体例子来走一遍。假设有一组 4 个权重值 [-1.2, 0.0, 0.5, 2.0],要把它们量化到 INT4(4 位整数,范围 0~15,共 16 档):

第 1 步:确定范围
min = -1.2, max = 2.0
第 2 步:计算 scale 和 zero_point
scale = (2.0 - (-1.2)) / (16 - 1) = 3.2 / 15 ≈ 0.2133
zero_point = round(-min / scale) = round(1.2 / 0.2133) ≈ round(5.625) = 6
第 3 步:逐个量化
q(-1.2) = round(-1.2 / 0.2133) + 6 = round(-5.625) + 6 = -6 + 6 = 0
q(0.0) = round(0.0 / 0.2133) + 6 = round(0) + 6 = 0 + 6 = 6
q(0.5) = round(0.5 / 0.2133) + 6 = round(2.344) + 6 = 2 + 6 = 8
q(2.0) = round(2.0 / 0.2133) + 6 = round(9.375) + 6 = 9 + 6 = 15
第 4 步:反量化验证
x̂(0) = 0.2133 × (0 - 6) = -1.28 (原始 -1.2, 误差 0.08)
x̂(6) = 0.2133 × (6 - 6) = 0.0 (原始 0.0, 误差 0)
x̂(8) = 0.2133 × (8 - 6) = 0.427 (原始 0.5, 误差 0.073)
x̂(15) = 0.2133 × (15 - 6) = 1.92 (原始 2.0, 误差 0.08)

可以看到,4 位量化后每个值的误差都在 0.08 左右——精度确实下降了,但大体保留了原始数值的相对大小关系。这就是量化的本质:用可控的误差换取巨大的存储和计算节省。

量化质量通常用均方误差(MSE,Mean Squared Error,即误差平方的平均值)来度量:

MSE=1n∑(xi−x^i)2\text{MSE} = \frac{1}{n} \sum (x_i - \hat{x}_i)^2

其中 xᵢ 是原始浮点值,x̂ᵢ 是反量化后的近似值,n 是元素总数。上面的例子中:

MSE=(0.082+02+0.0732+0.082)/4≈0.00487\text{MSE} = (0.08^2 + 0^2 + 0.073^2 + 0.08^2) / 4 \approx 0.00487

MSE 越小说明量化质量越高。GPTQ、AWQ 等高级算法的目标,本质上就是在给定位宽下最小化这个误差(或其对模型输出的影响)。

对称量化假设数值围绕零点对称分布(例如 -3 到 3),zero_point 固定为 0,公式简化为:

scale=max⁡(∣x∣)2(bit−1)−1q=round(x/scale)\text{scale} = \frac{\max(|x|)}{2^{(\text{bit}-1)} - 1} \quad q = \text{round}(x / \text{scale})

它计算高效、硬件友好,常用于权重——因为权重通常是近似零均值的高斯分布(即大部分值聚集在零附近,呈钟形曲线)。

非对称量化允许 min 和 max 不对称(例如 0.2 到 2.5,常见于激活值——尤其是经过 ReLU 激活函数后全部变成非负数),需要计算一个 zero_point 偏移。它的精度更高但计算略复杂,因为反量化时要做一次额外的减法。

为什么 ReLU 后激活值全为非负? ReLU(Rectified Linear Unit,修正线性单元)的定义是 f(x)=max⁡(0,x)f(x) = \max(0, x),即把所有负数截断为 0。这导致 ReLU 之后的激活值永远 ≥0\geq 0,分布明显不对称,所以激活值量化通常用非对称方案。

当一组权重内部数值范围差异很大时(有些通道的值在 0.01 量级,另一些在 100 量级),用单一 scale 量化会导致小值通道精度极差。解决方案是分组量化(Group-wise Quantization):把权重分成若干小组,每组单独计算 scale。

  • 逐张量量化(per-tensor):整个矩阵共用一个 scale。最简单,但精度最差。
  • 逐通道量化(per-channel):每个输出通道一个 scale。精度好,但需要存储多个 scale。
  • 分组量化(per-group):折中方案,每 N 个连续权重共用一个 scale(常见 N=32、64、128)。
import numpy as np
# 演示分组量化:每 4 个权重一组,各自独立量化
weights = np.array([0.01, 0.02, 0.01, 0.03, 50.0, 60.0, 55.0, 58.0])
group_size = 4
for start in range(0, len(weights), group_size):
group = weights[start:start + group_size]
g_min, g_max = group.min(), group.max()
scale = (g_max - g_min) / 15 # INT4: 16 档
q = np.round((group - g_min) / scale).astype(np.int8)
deq = g_min + q * scale
print(f"组 {start//group_size}: 原始 {group} → 量化 {q} → 反量化 {np.round(deq, 3)}")

输出将显示两组各自的量化精度都很好——如果用单一 scale,第一组(0.01 量级)几乎全部量化为 0,信息全丢。

PTQ(Post-Training Quantization,训练后量化)是最简单的路径:模型训练完成后,用一小批校准数据(calibration data,即用于统计激活值分布的样本,通常几百条)统计每层激活值的范围,直接把权重和激活量化到低精度。优点是无需重新训练、几行代码即可完成;缺点是低位宽(如 INT4)下精度损失明显,因为模型从未”见过”量化带来的误差。

PTQ 的工作流程可以概括为:

  1. 加载预训练模型(FP16 或 FP32)。
  2. 准备校准数据集(100~1000 条与推理分布相近的样本)。
  3. 前向传播收集统计量:记录每层激活值的 min/max(或更精细的分布直方图)。
  4. 计算量化参数:为每层的权重和激活分别确定 scale 和 zero_point。
  5. 导出量化模型:权重转为低精度整数,推理时用整数运算。

QAT(Quantization-Aware Training,量化感知训练)在训练或微调阶段就插入”伪量化”操作:前向传播时模拟量化与反量化的舍入误差,反向传播时用直通估计器(Straight-Through Estimator,即绕过不可导的 round() 函数、把梯度直接传过去的近似方法)把梯度传回去。这样模型能学会在量化约束下尽量保持精度,INT8 下几乎无损,INT4 下也比 PTQ 好得多。代价是需要训练数据和算力,流程更重。

为什么需要直通估计器? round() 函数是阶梯形的,几乎处处导数为 0,这意味着梯度无法通过它回传。STE 的做法很简单:反向传播时假装 round() 不存在,直接把下游梯度原封不动地传给上游。这在数学上不严谨,但在实践中效果出奇地好。

GPTQ:基于 Hessian 的逐层权重量化

Section titled “GPTQ:基于 Hessian 的逐层权重量化”

GPTQ 是一种高效的训练后权重量化方法,能把大模型压到 INT4 甚至 INT3 而精度损失很小。核心思想是逐层处理:把该层的权重视为矩阵 WW,该层输入为 XX,目标是找到量化后的 W^\hat{W},使得 WX≈W^XWX \approx \hat{W}X,即层输出差异最小。

GPTQ 的数学推导基于一个关键观察。量化误差对输出的影响可以用 Hessian 矩阵(即二阶导数矩阵,衡量”改变一个权重对输出的二阶影响”)来加权:

误差∝∑i(Wi−W^i)2⋅Hii−1\text{误差} \propto \sum_i (W_i - \hat{W}_i)^2 \cdot H_{ii}^{-1}

其中 H=XXTH = X X^T 是该层输入的 Hessian 矩阵(注意:这里是对权重而言的经验 Hessian)。Hii−1H_{ii}^{-1} 越大表示第 ii 个权重越”不重要”(改变它对输出影响小),因此可以优先量化那些不重要的权重。

GPTQ 的具体做法是按列依次量化并补偿:

  1. 对权重矩阵 WW 按列逐个处理。
  2. 量化第 ii 列后,量化误差为 Δi=Wi−W^i\Delta_i = W_i - \hat{W}_i。
  3. 用尚未量化的列去补偿这个误差:更新 WW 的剩余列,使得它们的输出能”抵消”掉 Δi\Delta_i 的影响。
  4. 补偿公式利用 Hessian 逆矩阵来决定每列贡献多少补偿。

由于只需一次前向传播收集 Hessian 信息,量化速度很快(一个 1750 亿参数的模型可在几小时内量化完)。

AWQ(Activation-aware Weight Quantization,激活感知权重量化)的关键洞察是:并非所有权重都同等重要,权重的重要性取决于它对应的激活值大小。如果某个通道的激活值经常很大,那么这个通道的权重即使量化误差很小,也会被放大传播到输出。

用数学语言说:对于线性层 y=Wxy = Wx,如果第 jj 个通道的激活 xjx_j 很大,那么权重 WijW_{ij} 的量化误差 ϵij\epsilon_{ij} 会被放大为 ∣ϵij∣⋅∣xj∣|\epsilon_{ij}| \cdot |x_j|。因此我们要保护那些”乘以大激活值”的权重。

AWQ 的解决方案非常优雅。给每个通道乘上一个缩放系数 sjs_j(sj>1s_j > 1 表示该通道”重要”):

量化前:Wij′=Wij⋅sj,xj′=xj/sj\text{量化前}:W'_{ij} = W_{ij} \cdot s_j, \quad x'_j = x_j / s_j 线性层:y=W′x′=(W⋅diag(s))(x/s)=Wx(数学上等价)\text{线性层}:y = W'x' = (W \cdot \text{diag}(s))(x / s) = Wx \quad \text{(数学上等价)}

但量化后的误差不再等价:Wij⋅sjW_{ij} \cdot s_j 被放大了,量化时占据更多整数档位,相对误差变小;而 xj/sjx_j / s_j 变小了,乘以同样有误差的 W′^\hat{W'} 后绝对误差也变小了。AWQ 通过搜索最佳的 ss 向量来最小化量化误差,整个过程不需要反向传播,也不需要重建训练数据,量化速度快、效果好,是目前 INT4 部署的主流选择之一。

SmoothQuant 解决的是离群值通道(outlier channels,即激活值中极少数但极大的通道)的问题。当个别通道的激活值比其他通道大 100 倍时,对称量化必须让 scale 适应这些极端值,导致其余 99% 的通道精度极差。

SmoothQuant 的思路是”劫富济贫”——把激活值中的极端大值缩小,同时把对应的权重放大,保持乘积不变:

迁移:x^j=xj/sj,W^ij=Wij⋅sj(sj=max⁡(∣xj∣)/max⁡(∣Wj∣))\text{迁移}:\hat{x}_j = x_j / s_j, \quad \hat{W}_{ij} = W_{ij} \cdot s_j \quad (s_j = \sqrt{\max(|x_j|) / \max(|W_j|)})

迁移后,激活值的分布变得平滑(不再有极端离群值),容易量化;权重的分布略有拉伸,但权重是静态的、离群值少的,量化起来也还好。SmoothQuant 让 INT8 全量化(权重 + 激活)成为可能,是 NVIDIA TensorRT-LLM 中的核心量化流程。

旋转量化:QuaRot 与 SpinQuant(2024-2025)

Section titled “旋转量化:QuaRot 与 SpinQuant(2024-2025)”

2024 年以来,一类基于随机旋转(Random Rotation)的量化方法引起了广泛关注,代表方法包括 QuaRot 和 SpinQuant。

核心思想很巧妙:如果权重矩阵 WW 的某些通道有极端值,我们可以在量化前对 WW 乘一个正交矩阵 QQ(旋转操作),得到 WQWQ。数学上:

Y=WX=(WQ)(QTX)Y = WX = (WQ)(Q^T X)

因为 QQT=IQQ^T = I(正交矩阵的性质),旋转不改变输出 YY。但旋转后的 WQWQ 数值分布更加均匀(极端值被”摊平”),量化误差大幅降低。这就像把一张偏暗的照片做直方图均衡化——内容不变,但数值分布更友好。

QuaRot 结合旋转技术和 GPTQ 式的残差补偿,首次实现了 INT4 权重 + INT4 激活的 W4A4 全量化,在 LLaMA 系列上几乎无损。SpinQuant(Meta,2024)进一步优化了旋转矩阵的学习方法,成为 2025 年低比特量化的重要方向。

BitNet 与 1.58-bit 量化:原生低精度训练(2024-2025)

Section titled “BitNet 与 1.58-bit 量化:原生低精度训练(2024-2025)”

传统的量化流程是”先训练高精度模型,再量化降精度”。微软研究院的 BitNet 系列提出了一个颠覆性思路:直接在低精度下从零训练模型。

BitNet b1.58(2024 年)将每个权重量化为三值 {−1,0,+1}\{-1, 0, +1\}——即每个权重只有 3 种状态,理论上只需 log⁡2(3)≈1.58\log_2(3) \approx 1.58 个比特。这被称为 1.58-bit 量化。权重矩阵乘法退化为加减法(乘以 +1 不变,乘以 -1 取反,乘以 0 为零),完全不需要浮点乘法器。

2025 年,BitNet 生态进一步发展:

  • BitNet b1.58-2B4T:微软发布了 20 亿参数的实用化 BitNet 模型,在同等规模下性能可比肩 FP16 模型,推理速度和内存效率大幅领先。
  • 原生 1.58-bit 训练框架开源,社区开始探索在 1.58-bit 权重基础上的 MoE(Mixture of Experts,混合专家模型)架构。
  • 这类模型被称为 “原生量化”(Native Quantization) 模型——它们不是高精度模型量化而来,而是天生就活在低精度空间里。

BitNet 的意义在于:它证明了如果从一开始就用低精度训练,模型能学会在极低精度下工作,而不需要事后的量化补偿。这为未来”天生小而快”的模型架构开辟了新路径。

FP8 量化:GPU 原生浮点低精度(2024-2025)

Section titled “FP8 量化:GPU 原生浮点低精度(2024-2025)”

与整数量化(INT8/INT4)不同,FP8(8-bit Floating Point)保留了浮点数的指数-尾数结构,只是在更少的比特中表示。NVIDIA H100 / H200 / Blackwell GPU 原生支持 FP8 运算,使其成为 2024-2025 年推理加速的新标准。

FP8 有两种格式:

  • E4M3(4 位指数 + 3 位尾数):动态范围大,适合前向传播。
  • E5M2(5 位指数 + 2 位尾数):动态范围更大但精度低,适合反向传播的梯度。

FP8 相比 INT8 的优势在于:浮点数的指数结构天然适配不均匀分布的数据(大部分值小、少部分值大),不需要复杂的校准和缩放就能获得好精度。2025 年,NVIDIA Blackwell B200 GPU 进一步支持了 FP4(4 位浮点) 运算,为下一代超低精度推理铺平了道路。TensorRT-LLM、vLLM 等推理引擎均已支持 FP8 推理。

GGUF(GPT-Generated Unified Format,GPT 生成的统一格式)是 llama.cpp 生态使用的模型文件格式,把权重、词表、配置统一打包到一个文件,便于跨平台加载。它内置的 k-quants 方法是一种分块混合位宽方案:把权重分成若干小组(常见的是每 32 或 64 个权重一组),每组单独计算 scale;同时允许不同张量使用不同位宽(重要的用 5 位或 6 位,次要的用 3 位或 4 位)。

llama.cpp 常见的量化等级(Q 后缀数字代表每权重平均比特数):

量化等级每权重比特数说明
Q8_08.5 bit接近无损,体积约为 FP16 的一半
Q5_K_M~5.5 bit性价比高,精度损失极小
Q4_K_M~4.8 bit最流行的选择,体积小、精度好
Q3_K_M~3.9 bit极限压缩,精度有一定下降
Q2_K~2.6 bit超低位宽,适合极小显存设备

这种”按重要性分配比特”的策略在极低位宽下表现稳健,适合在 CPU 或低显存 GPU 上运行。

经验上,INT8 量化通常带来约 2 倍的推理加速、模型体积减半,且精度几乎无损;INT4 量化带来约 4 倍加速、体积压缩到四分之一,但需要配合 GPTQ/AWQ 等技术才能保持可接受精度。需要注意的是,“速度倍数”高度依赖硬件和推理框架——专用整数矩阵乘法单元(如部分 GPU 的 INT8 Tensor Core)才能充分兑现加速,否则瓶颈会转移到内存带宽上,加速比更多体现在”能塞进显存”而非”算得更快”。

一个关于大模型推理的重要事实:大模型推理是内存带宽瓶颈(memory-bound)而非计算瓶颈。生成每个 token 时只需要做一次矩阵-向量乘法,计算量很小,但需要从显存读取全部权重。量化降低了权重体积,等价于”每秒能读取更多份权重”,因此即使在缺乏专用整数算力单元的设备上,仅权重量化(weight-only quantization)也能通过减少内存带宽压力来显著加速推理。

关于混合精度训练与数值格式的更多背景,可参考 混合精度训练。

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
# For a 7B model: model size = params * bytes_per_param
model_params = 7e9
formats = [
("FP32", 4, 0.0, "#4CAF50", "o", 200),
("FP16/BF16", 2, 0.1, "#2196F3", "o", 200),
("INT8 (PTQ)", 1, 0.5, "#FF9800", "s", 160),
("INT8 (QAT)", 1, 0.2, "#E91E63", "s", 160),
("FP8 (E4M3)", 1, 0.3, "#9C27B0", "D", 160),
("INT4 (GPTQ)", 0.5, 2.0, "#F44336", "^", 140),
("INT4 (AWQ)", 0.5, 1.5, "#FF5722", "^", 140),
("1.58-bit (BitNet)", 0.2, 3.0, "#607D8B", "P", 140),
]
fig, ax = plt.subplots(figsize=(10, 6))
fig.patch.set_facecolor("white")
for label, bps, acc_loss, color, marker, size in formats:
sz = model_params * bps / 1e9
ax.scatter(sz, acc_loss, c=color, marker=marker, s=size, zorder=5, edgecolors="white", linewidths=0.8)
ax.annotate(label, (sz, acc_loss), xytext=(5, 5), textcoords="offset points",
fontsize=8.5, fontweight="bold", color="#333")
ax.axhspan(0, 0.5, alpha=0.04, color="green")
ax.axhspan(0.5, 2.0, alpha=0.04, color="orange")
ax.axhspan(2.0, 4.0, alpha=0.04, color="red")
ax.set_xlabel("Model Size (GB, 7B params)", fontsize=12, fontweight="bold")
ax.set_ylabel("Accuracy Drop (% vs FP32)", fontsize=12, fontweight="bold")
ax.set_title("Quantization Trade-off: Model Size vs Accuracy (7B Model)", fontsize=13, fontweight="bold")
ax.grid(True, alpha=0.2, linestyle="--"); ax.set_xlim(-1, 32); ax.set_ylim(-0.5, 4)
plt.tight_layout()
plt.savefig("/mnt/kvm_ata-Netac_SSD_480GB_AA000000000000000904-part1/proj/docs/img/generated/quantization-size-accuracy.png",
dpi=180, bbox_inches="tight", facecolor="white")

量化精度与模型大小权衡:不同精度格式的散点对比

下面用纯 NumPy 实现对称量化与非对称量化,帮助理解底层原理:

import numpy as np
def asymmetric_quantize(x: np.ndarray, n_bits: int = 8) -> tuple[np.ndarray, float, int]:
"""
非对称量化:将浮点数组 x 映射到 n_bits 位整数。
返回 (量化后的整数数组, scale, zero_point)。
"""
q_min, q_max = 0, 2 ** n_bits - 1 # 例如 INT8: 0 ~ 255
x_min, x_max = x.min(), x.max()
scale = (x_max - x_min) / (q_max - q_min) # 每一档代表的浮点跨度
zero_point = round(q_min - x_min / scale) # 让浮点零值对齐到某个整数
# 量化:浮点 → 整数
q = np.clip(np.round(x / scale + zero_point), q_min, q_max).astype(np.int32)
return q, scale, zero_point
def symmetric_quantize(x: np.ndarray, n_bits: int = 8) -> tuple[np.ndarray, float]:
"""
对称量化:zero_point = 0,数值围绕零点对称。
返回 (量化后的整数数组, scale)。
"""
q_max = 2 ** (n_bits - 1) - 1 # 例如 INT8: -128 ~ 127
abs_max = np.abs(x).max()
scale = abs_max / q_max
q = np.clip(np.round(x / scale), -q_max - 1, q_max).astype(np.int32)
return q, scale
def dequantize(q: np.ndarray, scale: float, zero_point: int = 0) -> np.ndarray:
"""反量化:整数 → 近似浮点数"""
return scale * (q.astype(np.float32) - zero_point)
# ---- 测试 ----
np.random.seed(42)
weights = np.random.randn(1000).astype(np.float32) * 0.1 # 模拟一层权重
# 非对称 INT8 量化
q_asym, scale_a, zp_a = asymmetric_quantize(weights, n_bits=8)
deq_a = dequantize(q_asym, scale_a, zp_a)
mse_a = np.mean((weights - deq_a) ** 2)
# 对称 INT8 量化
q_sym, scale_s = symmetric_quantize(weights, n_bits=8)
deq_s = dequantize(q_sym, scale_s, zero_point=0)
mse_s = np.mean((weights - deq_s) ** 2)
print(f"非对称 INT8 MSE: {mse_a:.2e}")
print(f"对称 INT8 MSE: {mse_s:.2e}")
print(f"原始存储: {weights.nbytes} 字节")
print(f"量化存储: {q_asym.nbytes} 字节 (压缩 {weights.nbytes / q_asym.nbytes:.1f}x)")

运行后你会看到 MSE 在 1e-7 量级(极小),而存储压缩了 4 倍(float32 → int8)。尝试把 n_bits 改为 4,观察误差如何增大。

示例 2:用 bitsandbytes 加载 INT4 大模型

Section titled “示例 2:用 bitsandbytes 加载 INT4 大模型”

下面用 transformers 与 bitsandbytes 加载一个 INT4 量化的 LLM,对比直接加载与量化加载的显存占用:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
model_id = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id)
# 方式一:直接加载 FP16 基线模型
base = AutoModelForCausalLM.from_pretrained(
model_id, torch_dtype=torch.float16, device_map="auto"
)
print("FP16 显存(GB):", round(base.get_memory_footprint() / 1024**3, 2))
del base # 释放显存
torch.cuda.empty_cache()
# 方式二:用 bitsandbytes 加载 INT4 量化模型(NF4 精度)
# NF4 = Normal Float 4-bit,专为正态分布权重设计的 4 位数据类型
quant_cfg = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # 使用 NF4 量化类型
bnb_4bit_compute_dtype=torch.float16, # 计算时反量化为 FP16
bnb_4bit_use_double_quant=True, # 双重量化:连 scale 本身也量化
)
q_model = AutoModelForCausalLM.from_pretrained(
model_id, quantization_config=quant_cfg, device_map="auto"
)
print("INT4 显存(GB):", round(q_model.get_memory_footprint() / 1024**3, 2))
# 快速生成测试,验证模型可用
inputs = tokenizer("量化让大模型跑在笔记本上", return_tensors="pt").to(q_model.device)
out = q_model.generate(**inputs, max_new_tokens=20)
print(tokenizer.decode(out[0], skip_special_tokens=True))

运行后通常可以看到 FP16 占用约 13 GB 显存,而 INT4 仅占用约 4 到 5 GB,在 6 GB 显存的消费级显卡上即可流畅推理。更多部署优化技巧见 推理优化 与 LLM 推理。

示例 3:用 PyTorch 内置 API 做量化感知训练

Section titled “示例 3:用 PyTorch 内置 API 做量化感知训练”
import torch
import torch.nn as nn
# PyTorch 提供了量化感知训练(QAT)的 API
# 这里演示如何在简单全连接网络上做 QAT
class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
return self.fc2(self.relu(self.fc1(x)))
# ---- 步骤 1:准备 QAT 版本的模型 ----
# qconfig 指定了权重和激活的假量化方案(这里用对称 INT8)
model = SimpleNet()
model.qconfig = torch.ao.quantization.get_default_qat_qconfig('fbgemm')
# 在模型中插入假量化节点(FakeQuantize)
model_train = torch.ao.quantization.prepare_qat(model.train())
# ---- 步骤 2:正常训练(假量化会在前向传播时模拟量化误差)----
optimizer = torch.optim.Adam(model_train.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
for epoch in range(3): # 简单训练 3 个 epoch
for x, y in train_loader: # 假设已有 train_loader
out = model_train(x)
loss = criterion(out, y)
optimizer.zero_grad()
loss.backward() # STE 让梯度穿过 round() 函数
optimizer.step()
# ---- 步骤 3:转换为真正的 INT8 量化模型 ----
model_train.eval()
quantized_model = torch.ao.quantization.convert(model_train)
# 现在权重量已变为 INT8,推理时使用高效的整数运算
print(quantized_model)

这段代码展示了 QAT 的三个关键阶段:插入假量化 → 训练让模型适应量化误差 → 转换为真正的量化模型。注意 FakeQuantize 在前向传播时模拟量化-反量化的舍入操作,在反向传播时用 STE 传递梯度。

  • 优先权重量化,再考虑激活量化:权重是静态的,量化简单且无误差累积;激活值随输入变化,需要校准数据。仅量化权重(weight-only quantization)就能显著降低显存与内存带宽占用。
  • 校准数据要有代表性:PTQ 的精度高度依赖校准集。用与真实推理分布相近的数据(例如对话模型用真实问答样本),少量但覆盖面广即可。
  • INT8 选 PTQ,INT4 选 GPTQ/AWQ:INT8 下 PTQ 已足够好;降到 INT4 必须用带误差补偿的方法,否则精度断崖式下跌。
  • 关注离群值通道:少数极端大的激活通道会撑大 scale、压低整体精度。处理离群值(如 SmoothQuant 的激活迁移、QuaRot 的旋转预处理)是低位宽下保持精度的关键技巧。
  • 不要盲目追求低位宽:位宽越低,对硬件整数算力支持越敏感。若推理框架或硬件没有 INT4 矩阵乘内核,加速效果可能远不如预期,反而不如 INT8。
  • 保留少数敏感层为高精度:输出投影层、最终 logits 层通常对量化敏感,保持 FP16 而其余量化(混合精度),能以极小代价换取明显精度提升。
  • 善用旋转预处理:对于需要全量化(W8A8 或 W4A4)的场景,先做随机旋转(如 Hadamard 变换)再量化,已成为 2025 年的标准操作,能显著缓解离群值问题。
  • 测完真实任务再下结论:困惑度(perplexity,语言模型对文本的”惊讶程度”指标)下降不一定等于能力下降。务必在下游评测集上验证,参考 模型评估。
  • llama.cpp:开源项目,使用 GGUF 格式与 k-quants,让 LLaMA、Mistral 等模型在普通笔记本 CPU 上运行,是量化在端侧部署的标杆案例。详见 LLaMA 系列。
  • GPTQ-model / AutoGPTQ:为大量 Hugging Face 模型提供 INT4 GPTQ 量化权重,社区已发布数千个量化模型可直接下载使用。
  • vLLM:高性能推理引擎,支持 AWQ、GPTQ、FP8 等多种量化格式,通过 PagedAttention 与低精度权重结合,在单卡上服务高并发请求。2025 年 vLLM 进一步集成了 FP8 推理支持,在 NVIDIA H100 上实现了接近 2 倍于 FP16 的吞吐量。
  • TensorRT-LLM:NVIDIA 的推理加速库,内置 SmoothQuant 与 INT4/INT8/FP8 量化流程,针对自家 GPU 的 Tensor Core 做了深度优化。2025 年已支持 Blackwell GPU 的 FP4 精度。
  • Ollama:面向开发者的本地大模型运行工具,底层基于 llama.cpp 的 GGUF 量化方案,一条命令即可拉取并运行量化模型。
  • BitNet 推理框架:微软开源的 bitnet.cpp(原 llm.c 分支),专门针对 1.58-bit 模型优化,在 CPU 和 ARM 设备上实现了极高的能效比。
类库语言说明
bitsandbytesPython8 位与 4 位(NF4)量化,集成于 transformers,最易上手
AutoGPTQPythonGPTQ 量化训练框架,支持大规模 LLM 的 INT4 量化
AutoAWQPythonAWQ 量化实现,支持主流开源模型的一键量化
llama.cppC++GGUF 格式与 k-quants 方法,跨平台端侧推理
TensorRT-LLMPython/C++NVIDIA 官方推理库,支持 SmoothQuant、INT8/INT4/FP8/FP4
Quanto (HuggingFace Optimum)Pythontransformers 官方量化后端,支持多种量化算法
HQQ (Half-Quadratic Quantization)Python数据无关的快速量化方法,无需校准数据集
bitnet.cppC++/CUDA微软 BitNet 1.58-bit 模型专用推理框架
术语英文解释
量化Quantization把高精度浮点数映射到低精度整数的过程
训练后量化Post-Training Quantization (PTQ)训练完成后直接量化,无需重训
量化感知训练Quantization-Aware Training (QAT)训练时模拟量化误差以提升低位宽精度
对称量化Symmetric Quantization数值围绕零点对称分布,zero_point 为 0
非对称量化Asymmetric Quantization允许不对称范围,需计算 zero_point 偏移
缩放因子Scale每个整数档位对应的浮点跨度
零点Zero Point浮点零值对应的整数索引,用于精确表示零
校准Calibration用少量数据统计激活值范围的过程
仅权重量化Weight-Only Quantization只量化权重、激活保持高精度的折中方案
离群值通道Outlier Channel数值明显偏大的通道,会拖低整体量化精度
直通估计器Straight-Through Estimator (STE)让不可导的舍入操作能回传梯度的技巧
分组量化Group-wise Quantization把权重分成小组,每组独立计算 scale
全量化Weight-Activation Quantization (WA)权重和激活同时量化,如 W8A8、W4A4
旋转量化Rotation-based Quantization用正交矩阵旋转权重以平滑分布,再量化的方法
原生量化Native Quantization模型从零开始就在低精度下训练,而非事后量化
双重量化Double Quantization把量化产生的 scale 参数本身也量化,进一步压缩

经典论文与方法:

  • GPTQ 原始论文:Frantar et al., “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers” (2022)
  • AWQ 原始论文:Lin et al., “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration” (2023)
  • SmoothQuant:Xiao et al., “SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models” (2022)
  • LLM.int8():Dettmers et al., “LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale” (2022)
  • bitsandbytes NF4:Dettmers et al., “QLoRA: Efficient Finetuning of Quantized LLMs” (2023)

2024-2025 年前沿进展:

  • BitNet b1.58:Ma et al., “The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits” (2024) — 微软提出的原生三值量化训练
  • BitNet b1.58-2B4T:微软 2025 年发布的首个实用化 1.58-bit 模型(20 亿参数),开源权重与推理框架
  • QuaRot:Ashkboos et al., “QuaRot: Outlier-free 4Bit Inference in Rotated LLMs” (2024) — 基于旋转的 W4A4 全量化
  • SpinQuant:Liu et al., “SpinQuant: LLM Quantization with Learned Rotations” (Meta, 2024) — 学习最优旋转矩阵
  • FP8 精度与格式:NVIDIA, “FP8 Formats for Deep Learning” (2022),以及 H100/Blackwell 架构白皮书
  • QServe:Lin et al., “QServe: W4A8KV4 Quantization and System Co-design for Efficient LLM Serving” (2024) — 混合精度量化服务系统

工具与文档:

  • llama.cpp 与 GGUF 格式文档:github.com/ggerganov/llama.cpp
  • Hugging Face 量化指南:huggingface.co/docs/transformers/main/quantization/overview
  • NVIDIA TensorRT-LLM 量化教程:github.com/NVIDIA/TensorRT-LLM
  • bitnet.cpp(BitNet 推理框架):github.com/microsoft/BitNet