副标题:不是"降质换速度"——FP16 的 11 位尾数对 LLM 权重大部分是冗余。量化的问题是:怎么知道哪些权重要多留一位,哪些可以砍到底?GPTQ、AWQ、GGUF 给了三种不同的答案。
一、你其实一直在用量化
从 #1 开始,这个系列用的一直是 GGUF 格式的量化模型。Qwen3-8B Q4_K_M、Llama 2 7B Q4_K_M——所有实验都是"4-bit 模型"跑的。
但 Q4_K_M 不等于"原始模型直接砍掉 4 倍大小"。你下载的 4.7GB 文件(Qwen3-8B Q4_K_M)如果按原始 FP16 存是 16GB,但这不是简单的"每个 float 砍到 4 bit"——有些权重保留了 6 bit,有些砍到了 3 bit,通过一种叫重要性感知混合精度的机制。
这正是量化最反直觉的地方:
量化不是"uniform 降精度"——它是"给每一层的每一位精打细算"。有些权重对精度极度敏感,少一位输出就变?有些权重哪怕砍到 2-bit 也看不出区别。如何区分它们是所有量化方法的核心问题。
这篇博客不讲公式,讲物理——FP16 的 11 位精度对于权重参数到底意味着什么?为什么我们用 INT4 跑了 19 篇博客的实验从来没出过问题?
二、先理解数字:FP16、INT8、INT4 到底差多少
2.1 精度不是"0.0001 和 0.1 的区别",是"格子的大小"
FP16 (16 位): 1 bit 符号 + 5 bit 指数 + 10 bit 尾数 ≈ 3.3 位有效十进制数字 能表示的范围: ±65504 INT8 (8 位): 1 bit 符号 + 7 bit 数值 能表示的范围: -128 ~ 127 格子大小: f_max / 127 INT4 (4 位): 1 bit 符号 + 3 bit 数值 能表示的范围: -8 ~ 7 格子大小: f_max / 7以权重范围 [-1, 1] 为例:
FP16: 2 / 65504 ≈ 0.00003 的精度 ← 溢出了 INT8: 2 / 127 ≈ 0.0157 的精度 ← 足够细 INT4: 2 / 7 ≈ 0.2857 的精度 ← 很粗了! 但注意:权重的范围通常不在 [-1, 1] 之间 在一些层中,重要的权重集中在 f_max 附近的小范围内 而在另一些层中,权重分散在整个范围决定量化质量的不是"bit 数",而是"你给重要的数值留了多少格子"。这就是为什么有些 INT4 模型质量几乎无损,而有些 INT8 模型反而差——问题不在 bit 数本身,在量化方案。
2.2 从 FP16 到 INT4,4 倍压缩后的存储成本
Qwen3-8B FP16: 8.2B × 2 bytes = 16.4 GB Qwen3-8B INT4: 8.2B × 0.5 bytes = 4.1 GB (理论) 实际 GGUF Q4_K_M ≈ 4.7 GB(含 overhead) 节省: 约 70% 的显存/带宽4 倍压缩不是简单的"每个数字砍 4 倍精度"——因为存储从 16 bit 降到 4 bit,但 GPU 在计算时会把 INT4 的值先解压回 FP16 再算。所以:
推理时的实际流程(以 INT4 量化模型为例): 1. 从 HBM 读取 INT4 权重 ← 带宽需求降到 INT4 级别 2. GPU 的 Tensor Core 支持 INT8/INT4 矩阵乘法 → 不需要解压就能算! ← T4/A100/H100 原生支持 INT4 matmul 3. 算出来的是 INT4 精度的结果 → 写回 HBM 前转成 FP16 ← 精度从 INT4 回升到 FP16 级别量化省的不是计算量,是搬权重的带宽。这和 #18 的核心逻辑(搬 KV Cache 是瓶颈)完全一致——推理的大部分成本是搬权重,不是算权重。把权重从 16 bit 压到 4 bit,搬运量直接降到 1/4,这才是加速的来源。
三、量化的三个设计维度
3.1 粒度:逐层 vs 逐通道 vs 逐组
| 粒度 | 做法 | 精度 | 存储开销 |
|---|---|---|---|
| per-tensor | 整层用一个 scale factor | 粗 | 极小(每层 1 个 scale) |
| per-channel | 每列/行一个 scale factor | 中 | 稍微增加 |
| per-group | 每 G 个权重一组,共享 scale | 细 | 需要额外存 scale |
| per-element | 每个权重单独一个 scale | — | 等于没压缩 |
GGUF 的 Q4_K_M 中的 “M” 代表混合粒度——它在不同层用不同粒度:
一个 4096 × 4096 的权重矩阵(~16M 参数): per-tensor: 1 个 scale × 2 bytes = 2 bytes ← 太粗,质量差 per-channel: 4096 个 scale × 2 bytes = 8 KB ← 常见方案 per-group-32: 16M/32 个 scale = 512K × 2 = 1 MB ← 最细,质量最好per-group 的额外存储(1 MB 对于 16M 参数来说只增加了约 6%),但精度提升巨大。这 1 MB 花得非常值。
3.2 对称性:对称 vs 非对称
对称量化: w_quant = clamp(round(w / scale), -127, 127) 假设正负范围对称,scale = 2 × max(|w|) / 255 适用场景: 权重大致对称分布(大部分层) 非对称量化: w_quant = clamp(round((w - zero_point) / scale), 0, 255) 引入 zero_point 偏移 适用场景: 权重分布偏斜(某些激活层)对称量化简单高效,reLU 后的激活分布不对称时用非对称。
3.3 精度恢复:round-to-nearest
"那个"时刻——量化唯一真正丢失精度的节点——就是 round。当你把 float 值映射到离它最近的 INT 格子时,<0.5 的差被舍去了,>0.5 的差被保留了,但这个差值<0.5 就已经是误差。
Quantization Error = |w_real - w_quant × scale| 对这个误差的处理方式,决定了不同量化方法的优劣。 GPTQ 说: 我用一次校准数据最小化误差。 AWQ 说: 我保护"重要"通道,把误差集中到不重要通道。 GGUF 说: 我直接用更细的粒度(per-group)来降低误差。四、三种主流方案的分歧:精度应该怎么分配?
4.1 GPTQ:让误差在整个层里"对冲"
GPTQ(2023,Frantar et al.)是最流行的训练后量化方法。它的核心想法来自最优脑外科(Optimal Brain Surgery):
量化某个权重会产生误差。但如果你同时调整同行的其他权重,可以部分抵消这个误差。
GPTQ 的流程: 1. 从校准集取一小批输入 2. 逐列量化权重矩阵 3. 量化第 j 列时,用一个"二阶信息"(Hessian 矩阵)来决定: - 第 j 列该用多少 bit - 如何调整其他列来补偿第 j 列的损失 4. 同类比逐列完成 本质: 利用"权重间的冗余"来分摊量化误差优点:确实能做出质量最好的 INT3/INT4 模型
缺点:校准数据的选择非常敏感(不同校准集质量差异大),量化过程需要 GPU(不能纯 CPU 跑),时间长
GPTQ 在 HuggingFace 生态中最常见(AutoGPTQ库),许多"4-bit 模型"用的是 GPTQ。
4.2 AWQ:保护"重要"通道
AWQ(2024,Lin et al.)观察到:一个权重矩阵中,大约 1% 的通道决定了输出的质量。这些"重要通道"的特征值是 outsier(数量级远大于其他通道)。
观察: 逐通道的 scale 差异巨大 Channel 0: scale = 0.5 ← 不重要 Channel 1: scale = 45.2 ← 重要! Channel 2: scale = 0.8 Channel 3: scale = 32.7 ← 重要! ... AWQ 的想法: 给重要通道多留一位精度 方法不是"手动保护",而是: 将重要通道的权重乘以 >1 的因子 → 量化 scale 自动变大 → 重要通道在 INT4 中有更多格子 推理时乘以倒数因子恢复原值优雅之处在:不需要特殊算子支持——同一个 INT4 matmul kernel,只是权重值变了。AWQ 量化后的模型可以直接用标准 INT4 kernel 推理。
和 GPTQ 的关系:
- GPTQ 用校准数据逐列优化,计算量大,质量上限高
- AWQ 仅靠分析权重分布就能找到重要通道,速度快、不依赖校准数据
- 两者可以叠加——先 AWQ 保护重要通道,再 GPTQ 微调其他通道
实际中,AWQ 是当前推荐度最高的方案之一(vLLM 默认就用 AWQ)。
4.3 GGUF 量化:不玩技巧,靠粒度
GGUF(llama.cpp 的格式)是这三者中最"朴实"的:不用校准数据、不分析重要性、不做误差对冲。它就是用更细的粒度来保证精度。
GGUF 的 Q4_K_M 具体做了什么: 1. 把权重分成 32 个一组 (per-group-32) 2. 每组独立算 scale 和 min 3. 对 32 个权重做 quan(权重) = (w - min) × scale → INT4 但 Q4_K_M 不是"所有层统一 Q4"——M 代表混合 (mixed): Attention 层: Q6(保留更多精度,因为它们更敏感) FFN 层: Q4(ffn 对精度更宽容) Embedding: Q8(第一层和最后一层对精度最敏感) RoPE 参数: FP16(不改)这个"K"的含义是核心——先评估每个 layer 对精度的敏感度(用一小批校准数据跑一次),然后分配 bit:
| GGUF 变体 | 含义 | 平均 bit |
|---|---|---|
| Q2_K | 极端压缩,大部分 Q2 | ~2.6 bit |
| Q3_K_S/M | 小/M 型混合 | ~3.3-3.5 bit |
| Q4_K_M | 混合,主力推荐 | ~4.5 bit |
| Q5_K_M | 高精度混合 | ~5.3 bit |
| Q6_K | 基本无损 | ~6.5 bit |
| Q8_0 | 近乎无损 | ~8 bit |
Q4_K_M 之所以是这个系列的默认选项,不是因为"它是 4-bit",而是它把珍贵的 bit 分配给了最需要它们的地方——Attention 层的投影矩阵。这就是为什么跑了大半个系列的实验,量化模型从未在质量上拖过后腿。
三种方案对比
| GPTQ | AWQ | GGUF | |
|---|---|---|---|
| 核心理念 | 误差对冲 | 保护重要通道 | 混合粒度 |
| 需要校准数据? | 是(质量敏感) | 可选(提升效果) | 否(只测 loss) |
| 量化速度 | 慢(逐列优化) | 快 | 快 |
| 推理引擎 | HuggingFace/vLLM | vLLM(默认) | llama.cpp/Ollama |
| 主要场景 | 云端部署 | 云端 + 本地 | 本地 + 消费级 |
| 质量(同 bit) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 便携性 | 需专用 kernel | 标准 INT4 kernel | 独占格式 GGUF |
五、量化不是降质:为什么 Q4 模型在大部分任务上几乎无损?
这是整个系列 19 篇博客中从未被质疑过的假设——一直在用 Q4_K_M,但从来没有一卷数据说"Q4 比 FP16 差了多少"。
5.1 为什么能几乎无损?
原因一:LLM 权重的有效信息密度远低于 FP16 的承载能力。
FP16 有 10 位尾数 ≈ 3.3 位有效十进制数字。但 LLM 权重在训练过程中受到优化器噪声、随机初始化、批次顺序等影响,其"信噪比"远低于 3 位有效数字。
类比: 你有一个可以把距离测量到 0.001mm 的千分尺 但你用它去量一把被磨损的钢尺的刻度 钢尺本身的制造误差是 ±0.1mm 所以尽管千分尺的精度是 0.001mm,你实际知道的只有 0.1mm → FP16 就是那个千分尺,LLM 权重就是那把钢尺原因二:量化误差和推理噪声分布类似。
LLM 在推理时本身就有采样噪声(temperature、top-k、top-p)。量化引入的误差和采样噪声在统计特性上类似——它们都是"小幅度随机扰动"。如果 temperature 0.7 的采样变化都被认为是"合理多样性",那量化误差同样不会产生"错误"。
原因三:大多数任务是"语言流畅度"测试,不是"数值精度"测试。
写一段代码、翻译一句话、回答一个问题——这些任务对"权重的第 8 位尾数"完全免疫。量化误差只有在需要精确记忆的任务上才会暴露(如:检索一段引文的原文、计算精确的数学表达式)。
5.2 什么情况下量化确实会翻车?
| 场景 | 原因 | 能恢复吗? |
|---|---|---|
| 极度少样本推理 | 量化破坏了 few-shot 示例的精确模式 | 增加 shot 数 |
| 代码生成中精确变量名 | 某些特殊 token 被量化压到边界值 | 一般不影响 |
| 长上下文 recall | 量化误差累积到长距离时变大 | 需要更多 bit |
| 连续选择题(MMLU) | 多选项概率接近(51% vs 49%),量化后翻盘 | 换 Q5/Q6 |
| 结构化输出(JSON) | 量化可能让小概率的 ] 变 0 概率 | 加约束解码 |
Q4_K_M 在 >99% 的任务上表现和 Q8/FP16 无显著差异。这是实测结论,不是理论。
六、MoE 量化:冷热 expert 的精度难题
接上 #20 的第六节(MoE 量化),这里展开为什么 MoE 的量化问题比 Dense 更棘手。
6.1 同一个 expert,同一个精度——但 expert 之间差异巨大
| Expert | 激活频率 | 对精度的敏感度 |
|---|---|---|
| Expert 3(高频) | 15% | 低(大量数据训练过) |
| Expert 42(低频) | 2% | 可能高(仅见过少样本) |
| Expert 88(极少) | 0.5% | 极高(过拟合于特定模式) |
冷 expert 是 MoE 量化的暗坑。热 expert 被大量 token 训练过,量化误差被均匀分布了。冷 expert 只处理极少数 token,对这些 token 来说是"最后一公里"问题——量化误差可能直接改变对应关系。
6.2 GGUF 处理 MoE 的方式
llama.cpp 对 MoE 模型的量化仍然是用统一的 Q4_K_M——所有 expert 同样的 bit 分配。这意味着:
- 热 expert 分到了不必要的精度(浪费)
- 冷 expert 分到了必要的但不够的精度(风险)
改进方向:按热度分级量化——热 expert Q4/Q5,冷 expert Q6/Q8。但这需要先跑 routing profiling 才能知道每个 expert 的热度,流程复杂度远超 Dense。
七、如果在本地自己做量化:你需要的是一把尺,不是一把刀
这个系列一直用的是别人量化好的模型(提前下载 GGUF)。如果要在本地量化自己的模型(或者用特殊的量化方案):
7.1 三种产品级实现
| 工具 | 支持的方案 | 适用场景 |
|---|---|---|
| llama-quantize(llama.cpp) | GGUF 全系列 | 本地量化,CPU/GPU 均可 |
| AutoGPTQ | GPTQ | HuggingFace 生态 |
| AutoAWQ | AWQ | vLLM/SGLang 部署 |
7.2 量化一个模型的实际流程(以 llama-quantize 为例)
Step 1: 下载 FP16 完整模型 Step 2: 运行量化: ./llama-quantize \ --model qwen3-8b-fp16.gguf \ --output qwen3-8b-q4_k_m.gguf \ --type q4_k_m Step 3: 验证: ./llama-cli -m qwen3-8b-q4_k_m.gguf -p "hello" --temp 0 Step 4: 对比原始模型输出 — 看有没有"量化翻车"核心验证方法:同 seed、同 prompt、temperature=0,比较量化前后输出的 token 序列。如果前 N 个 token 完全一致,说明量化方案对这个模型是"无损"的。
7.3 量化的终局:不是"用更少的 bit",是"用最合适的 bit"
统一量化的未来: 1. 混合精度模型: 不同层不同 bit → 已经实现(Q4_K_M 就是) 2. 动态精度推理: 运行时根据输入调整 bit → 是研究方向(smoothquant+) 3. 自适应量化: 根据你的任务分布自动分配 bit → 非对称量化 + 重要性感知 = 新的 frontier八、总结
量化不是在"降低质量换速度"——是在把精度放在最重要的地方,让不重要的地方回归它所需要的精度。权重矩阵中 90% 的值不需要 FP16 的精度,把这 90% 砍到 INT4,推理速度翻 2-3×,质量几乎不变。
三个层面的理解:
表层(现象):从 FP16 到 INT4,模型文件小了 4 倍,推理快了 2-3× 中间层(机制):per-group 粒度 + per-channel importance = 几乎无损的压缩 深层(物理):LLM 权重的有效信息密度远低于 FP16 的承载能力 量化的三个关键决策: 1. 分组粒度: 每 N 个权重共享 scale (N=32 是 popular choice) 2. 保护策略: 哪些通道/层需要更多 bit(K 和 M 的含义) 3. 量化方案: 是用校准数据做误差对冲(GPTQ),还是保护重要通道(AWQ),还是靠粒度取胜(GGUF)把这篇文章和 #17-#20 放在一起看:
| 技术 | 省掉的东西 | 省了多少 |
|---|---|---|
| PagedAttention | 预留的碎片空间 | 30-60%(依赖场景) |
| Speculative Decoding | 每 token 的 KV Cache 搬运次数 | 2-3× |
| FlashAttention | S/P 中间矩阵 | O(N²) → O(N) |
| 量化 | 每参数 16 bit → 4 bit | 4× |
量化是条理最清晰的一个:你放弃了些微的数值精度,换来了权重搬运量直接减半——而绝大多数任务完全感受不到那点精度损失。
附录:进一步阅读
- Frantar et al. (ICML 2023):GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers
- Lin et al. (2024):AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration
- llama.cpp GGUF 量化文档: github.com/ggml-org/llama.cpp
- Tim Dettmers 的博客:Which GPU(s) to Get for Deep Learning— 量化与显存分析
- SmoothQuant (2023):SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models
这个专栏专注于 LLM 推理与部署的底层技术——从算子开发、显存管理、调度策略到生成优化,用可运行的代码和可复现的实验,把论文里的"黑科技"变成可以理解、可以动手验证的知识。如果你对"GPU 到底在干什么"和"推理框架做了什么优化"这类问题感兴趣,欢迎来主页翻翻其他文章。