跳到正文

目录

Quantization 量化技术完全指南:从原理到 LLM 实战

目录

一、先看一组数字

Qwen-3-Coder-Next 是一个 800 亿参数的模型:

  • 体积:159.4GB
  • 需要至少 159GB 内存才能运行
  • 它还不算「大型」模型——前沿模型参数常超 1 万亿,需要 2TB+ 内存

换个角度看,我们可以让 LLM 体积缩小 4 倍、速度提升 2 倍,精度只损失 5-10%,足以让很能打的模型在普通笔记本上跑起来。

这组数字背后就是 Quantization(量化)。


二、为什么大模型这么大

2.1 参数(Parameters)就是模型本身

把 LLM 加载进内存,它基本上就是一堆参数(也叫权重)。

从一个最简单的例子看起:

输入(input) = 2.0
    ↓
[参数(parameter) = 0.5]
    ↓
输出(output) = 1.0

这是现代 AI 的基本构建块:输入一个值,乘以参数,得到输出。

2.2 层(Layers)让模型变大

但 LLM 比这复杂得多,它有「层」结构:

输入层 ──┬──→ [层1: 节点A = 2.0×0.5 = 1.0] ──┬──→ [层2: 节点C = ...]
        └──→ [层1: 节点B = 2.0×0.5 = 1.0] ──┘

每两个节点之间都有一个参数。当 2 个连接汇聚到同一个节点时,值会相加。

2.3 现代 LLM 的规模

现代 LLM 的规模跨度很大:

规模维度典型数值
输入/输出节点数十万个
隐藏层层数数十层
每层节点数数千个
每层连接数数百万到数千万
总参数量数十亿到万亿

这就是 LLM 需要巨大内存和计算资源的原因。


三、计算机如何表示数字

3.1 位(Bits)与整数

计算机用 1 和 0(bits)来表示一切。

一个 unsigned int8(无符号 8 位整数)可以表示 0-255 的值:

位位置:   [128] [64] [32] [16] [8] [4] [2] [1]
二进制:     0    1    0    0   0   0   0   0
值: 64

每个位代表 2 的幂次方,加起来就是最终值。

3.2 整数是「离散」的

整数是离散的——1 和 3 之间只有 2,没有别的东西。计算机表示离散值毫无问题。

3.3 浮点数是「连续」的噩梦

但小数呢?1 和 3 之间有无穷多个值。

计算机无法表示无穷,只能做个妥协:

承诺在一定范围内保证精度,超出范围的就是「尽力而为」。

3.4 32 位浮点数(float32)的结构

┌─────────────────────────────────────────────────────────┐
│  1 位 sign(符号)  │  8 位 exponent(指数)  │  23 位 significand(尾数)  │
└─────────────────────────────────────────────────────────┘
组成部分位数作用
Sign(符号)10=正数,1=负数
Exponent(指数)8表示范围:±3.40×10³⁸
Significand(尾数)23表示精度:7 位有效数字

关键特性:

  • float32 可以表示的范围是 ±3.40×10³⁸
  • 但精度只有 7 位有效数字
  • 值不是均匀分布的——越接近 0,精度越高

3.5 精度分布图

     -3.4×10³⁸                              +3.4×10³⁸
          │───────────────────────────────────────────│
          
          │    负数区域        │    0     │    正数区域      │
          │                  │          │                 │
          │ 稀疏 ←────────────┼─────────→ 密集           │
                              ↑
                          0 附近精度最高

关键在于,float32 能表示的数值大多集中在 0 附近。

3.6 更小的浮点格式对比

格式符号位指数位尾数位范围精度内存占用
float321823±3.4×10³⁸7位32 bits (4 bytes)
float161510±655043位16 bits (2 bytes)
bfloat16187±3.4×10³⁸约 2 位十进制有效数字16 bits (2 bytes)
float8143自定义~1位8 bits (1 byte)
float4 (E2M1)121±60.5位4 bits (0.5 bytes)

注:float4 没有统一标准,常见格式有 E2M1(2 位指数+1 位尾数,范围约 ±6)和 E3M0(3 位指数+0 位尾数,范围更大但精度更低)。下文以 E2M1 为例。

3.7 bfloat16 的设计思路

Google Brain 创造了 bfloat16:

  • 8 个指数位 = 和 float32 一样的宽范围
  • 7 个尾数位 = 约 2 位十进制有效数字

这样设计的原因是:很多 LLM 场景不需要 float32 那样的高精度,但需要足够大的数值范围来避免溢出。

bfloat16 有接近 float32 的范围,却只占一半内存——对 LLM 来说是实惠的 tradeoff。

3.8 模型参数的实际分布

作者下载了 6 个主流开源模型并分析参数分布:

几乎所有参数都落在 -0.1 到 0.1 之间。

这意味着:

  1. LLM 参数正好落在 float 能最精确表示的范围内
  2. 但仍存在少量**异常值(Outliers)**超出这个范围

四、朴素量化的困境

4.1 什么是量化

量化就是:把大范围的值压缩到小范围。

最简单的方式是 Round-to-Nearest(最近舍入):

原始值: 1.23
  ↓ 四舍五入
量化值: 1

4.2 直接量化会崩

如果直接把 float16 → float8 → float4:

格式结果
float8基本可用
float4崩溃!输出始终为 0

为什么会崩:

float4 (E2M1) 可表示值: ±{0, 0.5, 1, 1.5, 2, 3, 4, 6}
参数范围:                -0.89 到 +0.16

问题:
1. float4 要预留给 Infinity 和 NaN
2. 可表示的值非常稀疏(只有十几个离散点)
3. 参数集中在 -0.89 到 +0.16,而 float4 的大部分表示空间给了更大的值
4. 大量表示空间被浪费!

参数被量化后,很多值变成 0,于是输入无论如何变化,输出始终是 0。


五、对称量化(Symmetric Quantization)

5.1 基本思路:缩放(Scaling)

量化不是简单舍入,而是按比例缩放数据到更紧凑的范围:

原始范围 [-14, +14]     →    量化范围 [-7, +7]
    -14 ──────────────────→ -7
     0 ───────────────────→  0  (0 保持在中心)
    +8 ───────────────────→ +4

5.2 如何找缩放因子

function quantize({ values, bits }) {
    // 1. 找到最大绝对值
    const vmax = Math.max(...values.map(Math.abs));  // e.g., 0.89
    
    // 2. 计算量化后的最大值(4-bit: 7, 8-bit: 127)
    const qmax = 2 ** (bits - 1) - 1;  // 7
    
    // 3. 计算缩放因子
    const scale = vmax / qmax;  // 0.89 / 7 = 0.127
    
    // 4. 量化:除以 scale,然后四舍五入
    return {
        values: values.map(v => Math.round(v / scale)),
        scale
    };
}

function dequantize({ values, scale }) {
    // 反量化:乘以 scale
    return values.map(v => v * scale);
}

5.3 实际例子

参数:[-0.89, 0.16, 0.08, -0.13, 0.16, -0.54]

vmax = 0.89
qmax = 7
scale = 0.127

// 量化结果:
[-7, 1, 1, -1, 1, -4]

5.4 误差分析

原始值反量化值误差误差率
-0.89-0.8900.0%
0.160.127-0.033-20.6%
0.080.127+0.047+58.9%
-0.13-0.127+0.003-2.2%
0.160.127-0.033-20.6%
-0.54-0.509+0.031-5.8%

平均误差约 18%,但体积缩小了 4 倍(16-bit → 4-bit)。

5.5 为什么对称量化更常用

  1. 0 保持在中心——维持正负对称性,这对神经网络很重要
  2. 自动适应数据分布——不浪费表示空间
  3. 实现简单——只需一个 scale 参数

六、非对称量化(Asymmetric Quantization)

6.1 对称量化的局限

如果数据分布不以 0 为中心,对称量化会浪费一半的表示范围。

6.2 非对称量化公式

function quantizeAsymmetric({ values, bits }) {
    const vmin = Math.min(...values);
    const vmax = Math.max(...values);
    const qmax = 2 ** (bits - 1) - 1;  // 7 for 4-bit
    const qmin = -(2 ** (bits - 1));   // -8 for 4-bit
    
    // 缩放因子
    const scale = (vmax - vmin) / (qmax - qmin);
    
    // 零点偏移(关键区别!)
    const zeroPoint = qmin - Math.round(vmin / scale);
    
    return {
        values: values.map(v => Math.round(v / scale + zeroPoint)),
        scale,
        zeroPoint
    };
}

function dequantizeAsymmetric({ values, scale, zeroPoint }) {
    return values.map(v => (v - zeroPoint) * scale);
}

6.3 对称 vs 非对称对比

特性对称量化非对称量化
0 的位置始终在中心可能偏移
存储参数只需 scale需 scale + zeroPoint
实现复杂度简单稍复杂
适用场景参数分布均匀参数分布偏斜

七、异常值(Outliers)问题

7.1 异常值的存在

大部分参数集中在 -0.1 到 0.1,但存在少量异常值超出这个范围。

7.2 异常值的影响

异常值会带来两个后果:

  1. scale 变大——为了容纳异常值
  2. 正常值的精度损失增加——因为 scale 变大了

7.3 原文的主解法:分块量化

实践中不会把整个模型一次性量化,而是把参数拆成很多小块,每块单独计算量化参数。常见做法是 Block-wise Quantization(分块量化),通常每 32-256 个参数一组:

# 每个 block 单独计算 scale / zero_point
for block in blocks(weight, size=128):
    vmin = block.min()
    vmax = block.max()
    scale = (vmax - vmin) / 15
    zero_point = -8 - round(vmin / scale)
    quantized_block = round(block / scale + zero_point)

效果是:

  • 少量异常值只污染当前 block,不会拖累整个模型
  • 每个 block 的取值范围更窄,scale 更小,量化误差通常更低

7.4 进一步的工程优化:逐通道量化

分块之外,工程上还常见 Per-Channel Quantization(逐通道量化):

  • 每个通道单独计算 scale
  • 更适合处理不同通道分布差异明显的权重
  • 这属于超出原文范围的扩展知识

八、衡量量化质量

从本章开始,原文改用 Qwen3.5-9B 做实验模型(不同于开篇的 80B Qwen-3-Coder-Next),这个规模更适合在本地复现和对比量化效果。

8.1 困惑度(Perplexity)

这是衡量语言模型质量的主要指标:

Perplexity = 2^(-1/N × Σ log₂ P(wi))
  • 越低越好——表示模型预测越准确
  • 量化后 Perplexity 略有上升是正常的

原文给出的一组实测值:

格式Perplexity
bfloat168.186
8-bit symmetric8.193(+0.1%)
4-bit asymmetric8.563(+4.6%)
4-bit symmetric8.710(+6.4%)
2-bit asymmetric66.1(+707.5%)

结论:8-bit 几乎无损,4-bit 有退化但仍可用,2-bit 在该模型上已接近崩溃。

8.2 KL 散度(KL Divergence)

Perplexity 只看「正确 token 的概率」,看不到整个概率分布如何变化。

KL 散度衡量的是:量化后的输出分布,与原始模型输出分布有多少差别。

  • 越低越好
  • 0 表示两个分布完全重合
  • 适合比较「同一个模型的不同量化版本」

原文的实测结果:

格式Mean KL divergence
8-bit symmetric0.0008
4-bit asymmetric0.0593
4-bit symmetric0.0675
2-bit asymmetric2.1447

8.3 基准测试(Benchmarking)

统计指标之外,更直接的方式是跑任务基准。原文用 GPQA Diamond 做示例:

格式正确率错误率无回答
bfloat1666.7%33.3%0%
8-bit symmetric73.2%26.8%0%
4-bit asymmetric62.6%36.4%1%
4-bit symmetric66.2%29.3%4.5%
2-bit asymmetric1%2%97%

8-bit symmetric(73.2%)反超 bfloat16(66.7%),并不代表量化「提升了模型能力」——更可能是 GPQA Diamond 样本量较小、结果受随机性影响,属于正常波动。

这提醒我们:别把某个百分比精度损失当成普适规律,不同模型、任务和量化实现,表现差异很大。

8.4 精度对照表

量化格式常见现象体积缩小建议理解方式
FP16 / BF16基本作为精度基线2x(相对 FP32)适合做参考版本
INT8往往接近无损4x(相对 FP32)很多场景的稳妥选择
INT4可能有可感知退化,但通常仍可用8x(相对 FP32)适合内存受限部署
INT2很容易质量崩溃16x(相对 FP32)必须按具体模型实测

九、实战工具

9.1 llama.cpp

纯 C/C++ 实现,支持 CPU 和 GPU:

# 安装
brew install llama.cpp

# 下载并启动 BF16 模型
llama-server -hf unsloth/Qwen3.5-9B-GGUF:BF16 --port 8000

# 量化模型
cd ~/Library/Caches/llama.cpp
llama-quantize unsloth_Qwen3.5-9B-GGUF_Qwen3.5-9B-BF16.gguf \
               unsloth_Qwen3.5-9B-GGUF_Qwen3.5-9B-Q8_0.gguf \
               Q8_0

# 运行量化模型
llama-cli -m unsloth_Qwen3.5-9B-GGUF_Qwen3.5-9B-Q8_0.gguf

原文实测用的格式:Q8_0(8-bit symmetric)、Q4_1(4-bit asymmetric)、Q4_0(4-bit symmetric)、Q2_K(2-bit asymmetric 风格)。

9.2 GPTQ

常见的后训练量化方法,通常需要校准数据:

pip install auto-gptq
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

quant_config = BaseQuantizeConfig(bits=4, group_size=128)
model = AutoGPTQForCausalLM.from_pretrained("your-model", quantize_config=quant_config)

9.3 AWQ(Activation-Aware Quantization)

考虑激活分布的 4-bit 量化:

pip install autoawq
from transformers import AutoTokenizer
from awq import AutoAWQForCausalLM

model = AutoAWQForCausalLM.from_pretrained("your-model")
tokenizer = AutoTokenizer.from_pretrained("your-model")
quant_config = {"zero_point": True, "q_group_size": 128}
model.quantize(tokenizer, quant_config=quant_config)

9.4 bitsandbytes

Hugging Face 集成:

pip install bitsandbytes
from transformers import AutoModelForCausalLM, BitsAndBytesConfig

quant_config = BitsAndBytesConfig(load_in_8bit=True)

model = AutoModelForCausalLM.from_pretrained(
    "your-model",
    quantization_config=quant_config,
)

9.5 工具对比

工具量化格式精度速度易用性
llama.cppQ4/Q5/Q8高快⭐⭐⭐
GPTQINT4很高中⭐⭐⭐⭐
AWQINT4很高快⭐⭐⭐⭐
bitsandbytesINT8/INT4高快⭐⭐⭐⭐⭐

十、关键判断与路线选择

10.1 量化是有损压缩

没有免费午餐——量化必然损失精度。目标是把精度损失控制在可接受范围。

10.2 对称 vs 非对称

场景推荐方法
参数分布以 0 为中心对称量化
参数分布偏斜非对称量化

10.3 异常值是主要瓶颈

异常值会拉大 scale、压低正常值的精度。原文的主解法是分块量化,让异常值只影响局部 block;逐通道量化是进一步的常见工程优化。

10.4 4-bit 是实用下限

  • INT4(4-bit):在很多模型上是实用的压缩点,但精度损失要看模型、任务和实现
  • INT2(2-bit):在原文测试的 Qwen3.5 9B 配置里已基本不可用
  • NF4(4-bit 归一化):能更好保留精度,是新兴格式

10.5 PTQ vs QAT:两种量化路线

本文所有方法都属于 PTQ(Post-Training Quantization,训练后量化)——拿到已训练好的模型后直接量化。另一条路线是 QAT(Quantization-Aware Training,量化感知训练):

维度PTQ(训练后量化)QAT(量化感知训练)
时机训练完成后,直接对权重量化训练过程中模拟量化误差
成本低——无需重新训练,分钟级高——需要训练数据和 GPU 算力
精度8-bit 通常无损;4-bit 有退化同等位宽下精度显著优于 PTQ
适用场景快速部署、大多数 8-bit 场景追求极致精度的低位宽(4-bit/2-bit)
代表工具GPTQ, AWQ, llama.cppQLoRA, LLM-QAT

一句话:PTQ 是「拿来就量化」,QAT 是「边训练边适应量化」。对大多数开发者,PTQ 已经够用;当 PTQ 在低位宽下精度不达标时,QAT 是进阶选择。

10.6 模型在进步,量化也要演进

随着模型能力提升,量化策略也在演化:

  • 更智能的异常值处理
  • 量化感知训练(QAT)逐渐普及
  • 更高效的稀疏量化

十一、总结与展望

量化公式

# 对称量化
scale = max(|weight|) / (2^(bits-1) - 1)
quantized = round(weight / scale)
dequantized = quantized * scale

# 非对称量化
qmax = 2^(bits-1) - 1
qmin = -(2^(bits-1))
scale = (max(weight) - min(weight)) / (qmax - qmin)
zero_point = qmin - round(min(weight) / scale)
quantized = round(weight / scale + zero_point)
dequantized = (quantized - zero_point) * scale

关键数据

指标数值
Qwen-3-Coder-Next80B 参数,159GB
量化可达到体积缩小 4-8x
精度损失8-bit 通常很小,4-bit 常见为轻到中度退化
速度提升往往会更快,但提升幅度依硬件和实现而变

实用建议

  1. 内存受限? 优先试 INT4,但别只看权重体积;实际运行还要考虑 KV cache、buffer 和框架开销
  2. 精度优先? 用 INT8 或 FP16
  3. CPU 部署? 用 llama.cpp,尤其适合本地快速验证
  4. GPU 部署? 用 GPTQ/AWQ,GPU 加速

硬件适配参考

不同硬件对量化格式的支持差异很大,选择时要匹配:

硬件推荐格式说明
NVIDIA GPU(Ampere+)INT8/INT4Tensor Core 原生支持,吞吐最高
Apple Silicon(M1-M4)Q4_K_M / Q8_0llama.cpp Metal 后端适配良好
x86 CPUINT8 为主AVX-512/VNNI 指令集加速;INT4 支持较新
NPU/VPU各家不同需查硬件 SDK 支持的量化格式

未来趋势

  • 更小的模型、更高的精度:量化技术进步会带来更好的 tradeoff
  • 硬件支持:新一代 NPU/VPU 内置量化支持
  • 自动化量化:AutoQ 等工具自动寻找最优量化策略

十二、参考资源


量化让超大模型能在普通硬件上运行。理解它,你就能自己动手部署大模型。

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。