模型量化:精度、体积与速度
量化用更少的比特近似模型中的数值。它最直接的收益是减少存储和传输的数据量;是否更快、损失多少任务质量,还取决于模型、量化方法、算子和硬件。先有显存预算,再讨论把哪部分压小,比只看文件名里的“4-bit”更有用。
讨论压缩方案前,可先用浮点数与稳定计算区分表示精度、数值范围与运算误差。
从一个数到一组低比特整数
以对称均匀量化为例,选择缩放系数 s,把权重 w 转成有限范围的整数 q = round(w / s),解码时近似为 s × q。超出整数范围的值会被截断。非对称量化还会引入零点,使整数区间对应偏离零中心的实数区间。
下面用四个虚构权重演示对称 4-bit 的一个简化约定:使用 -7 到 7,保留一个编码不用。真实格式可以使用不同布局。
weights = [-1.0, -0.2, 0.3, 1.0]
scale = max(abs(w) for w in weights) / 7
quantized = [max(-7, min(7, round(w / scale))) for w in weights]
restored = [round(q * scale, 3) for q in quantized]
print(quantized) # [-7, -1, 2, 7]
print(restored) # [-1.0, -0.143, 0.286, 1.0]
这次近似改变了中间两个权重:-0.2 变成 -0.143,误差 0.057;0.3 变成 0.286,误差 0.014。
如果一整层共用缩放系数,一个很大的离群值会让普通小权重量得很粗。还用这组数,只把 1.0 换成 3.5:缩放系数从 1 ÷ 7 ≈ 0.143 变成 3.5 ÷ 7 = 0.5,-0.2 除以 0.5 得 -0.4,取整成 0;0.3 除以 0.5 得 0.6,取整成 1,还原成 0.5。两个普通权重的误差都变成了 0.2,分别是原来的三倍多和十四倍。所以要按通道或按较小分组设置缩放系数,可以贴近各组数值范围,却需要额外元数据,也会改变内核实现成本。
拖动某个权重,或选中后按 ↑ ↓ 调整(← → 切换到相邻权重)。每组共用一个缩放系数,组内每个权重都吸附到本组网格上最近的一级。
权重 7,第 2 组q = round(w / s) = round(0.08 / 0.101) = 1 → ŵ = s × q = 0.101 (误差 +0.021)
| 组 | 最小 … 最大 | 缩放系数 s |
|---|---|---|
| 1 | −1.00 … 1.00 | max|w| / 7 = 1.00 / 7 = 0.143 |
| 2 | −0.55 … 0.71 | max|w| / 7 = 0.71 / 7 = 0.101 |
| 3 | −0.86 … 0.50 | max|w| / 7 = 0.86 / 7 = 0.123 |
| 4 | −0.47 … 0.64 | max|w| / 7 = 0.64 / 7 = 0.0914 |
- 均方根误差
- 0.028
- 最大 0.057
- 每个权重的位数
- 8
- 4 + 16 / 4 · 每组 128 个时:4.125
- 80 亿个权重
- 8 GB
- 16 位时 16 GB · 50%
权重是虚构的。每个权重的位数按每组一个 16 位缩放系数计算;非对称时另加一个与编码同位数的零点。真实格式存放元数据的方式各不相同。
“4-bit 模型”没有说完哪些数被压缩
Transformers 的量化概览列出多种方法及后端支持。比较时至少分清以下对象:
权重是 4-bit 时,激活和缓存仍可能是 16-bit;计算也可能先局部反量化再使用浮点算子。W4A16 表示权重 4-bit、激活 16-bit,不表示整个进程每个数都是 4-bit。文件容器、量化算法和执行内核同样是不同层:能下载一个格式,不代表所选运行时和设备能高效执行它。
容量算例要把边界写清
假设一个虚构稠密模型恰有 80 亿个权重。只算裸权重,16-bit 为 8 × 10^9 × 2 = 16 GB,4-bit 为 8 × 10^9 × 0.5 = 4 GB。这里用十进制 GB;换成 GiB,分别约是 14.9 GiB 和 3.7 GiB。
实际文件还包含缩放系数、零点、未量化张量与元数据。运行内存另外需要 KV 缓存、临时缓冲、运行时和并发请求空间。因此“4 GB 权重”不能推出“4 GB 显存足够跑任意上下文”。对于混合专家模型,也要区分每 token 激活多少参数与常驻或调入多少权重;少量激活参数不会自动减少全部模型的存储。
下载页上的后缀写着格式和比特数
在本地下载一个模型,常会看到同一个模型排着一列文件,名字后缀就是量化方式。llama.cpp 的 quantize 文档给出了 Llama 3.1 8B 按各档量化后的实际大小:
后缀里的数字是主要比特数,实际平均值总要高一点,因为缩放系数也占地方。按 Hugging Face 的 GGUF 说明,K 指 k-quant:权重分成 256 个一组的超块,块里再分小块,各存一个缩放系数。所以 Q4_K 实际约 4.5 bit。末尾的 S、M、L 是混合方案的档位,部分张量留在更高精度,所以 Q4_K_M 比 Q4_K_S 多出约 0.2 bit。IQ 开头的 i-quant 借助重要性矩阵(imatrix)决定怎样近似,llama.cpp 文档说合适的 imatrix 能减小困惑度和 KL 散度上的损失。拿上一节的算法对照:80 亿权重按 4 bit 算是 4 GB,Q4_K_M 的文件却是 4.58 GiB,约 4.9 GB,多出来的就是这些元数据和高精度张量。
GGUF 之外还有几种常见格式:
- MLX:Apple Silicon 上 MLX 用的格式。mlx-lm 用
mlx_lm.convert -q把 Hugging Face 权重转成量化版本,文档里的例子生成的是 4-bit 模型。MLX 文件和 GGUF 不能互换。 - AWQ、GPTQ:safetensors 里的 GPU 量化权重,主要给 vLLM、Transformers 这类服务端用。vLLM 的量化文档列出了各自的硬件支持。vLLM 0.30.0 起忽略 GPTQ 激活重排用的
g_idx,依赖它的旧文件要先确认。 - MXFP4:4-bit 的微缩放块浮点,GGUF 也收了这个类型。gpt-oss-20b 在后训练时就把 MoE 权重量化成 MXFP4,模型卡说它能在 16 GB 内存里运行。
- 厂商发布的 QAT 文件:Google 为 Gemma 4 31B 发布了官方的
qat-q4_0GGUF,文件 17.7 GB。模型卡说靠量化感知训练保持了接近 bfloat16 的质量。这是下一节 QAT 的现成例子。
KV 缓存的精度不写在文件名里,要在运行时设置。Ollama 用 OLLAMA_KV_CACHE_TYPE,llama.cpp 用 -ctk 和 -ctv,vLLM 用 kv_cache_dtype。各档省多少、代价是什么,见 KV 缓存笔记。
PTQ、QAT 与校准数据
训练后量化(PTQ)在已有权重上做近似;量化感知训练(QAT)让训练过程考虑量化影响。简单四舍五入可以作为理解起点,但成熟方法会利用权重和激活分布减少关键误差。GPTQ使用近似二阶信息进行训练后权重量化;AWQ利用激活统计识别并保护重要权重通道。
另一条路是 SmoothQuant:把激活里的离群值挪一部分到权重里,让激活和权重都好量化。它服务的是权重和激活一起量化的服务端加速器,与只量化权重的 AWQ 不同。
这里的校准数据用于观察量化范围或近似误差,与概率校准中拟合输出概率的含义不同。代码、长上下文、多语任务的激活分布可能不同。使用不具代表性的校准文本,会使一个总体基准看似良好的格式在目标任务上退步。
LoRA 是参数高效适配,蒸馏是让学生学习教师行为,都不等同于数值量化;可以组合使用,具体训练关系见迁移学习、LoRA 与蒸馏。
为何更小不一定更快
一个人逐字生成时,每出一个 token 都要把全部权重从显存读一遍,而读的时间远比算的时间长,推理性能笔记用数字讲了这一步。所以上面那个 80 亿权重的模型,4-bit 的 4 GB 比 16-bit 的 16 GB 每个 token 少读四分之三,decode 就快。可是如果低比特内核要先把权重解压回 16-bit 再算、不适配硬件,或者算子走了慢速回退路径,省下的时间又会被吃掉。
长提示词的 prefill 一次算很多 token,瓶颈在算力,少读权重帮得不多;逐 token 的 decode 和不同批量大小又各有瓶颈,所以不能用一个速度数字概括一种量化格式。
比较两个量化版本时,固定原始模型、运行时、硬件、提示词与输出预算,记录峰值内存、首字延迟、生成速度,以及目标任务的正确率和失败样例。尤其检查长文本、稀有术语、结构化输出和工具参数,不要只比较困惑度或几句聊天。若较低精度导致频繁重试,单次推理节省可能被端到端成本抵消。测量口径见推理性能。