4 bit 量化之后,一个权重参数只能在 0~15 之间取值,也就是只有 16 个“格子”。第一次看到这个数字的人,几乎都会问同一个问题:16 个格子,凭什么能装下一个动辄几百亿参数的大模型?这个问题并不外行,它刚好切中了低比特量化的核心矛盾:量化损失的本质,就藏在这 16 个格子怎么分配、怎么使用里。
更准确地说,4 bit 量化不是把 16 个格子平均撒在整个权重范围上,而是让一小段权重共享一把“局部尺子”,让格子的位置跟着数据分布移动。GPTQ 和 AWQ 是两种最有代表性的移动尺子、分配量化误差的技术路线。读懂它们,你就能理解为什么 4 bit 大模型能在精度几乎不明显损失的情况下跑起来,也能避免在实际部署时选错方案、调错参数。
AI Infra 对量化的关注,也不只是因为“位宽数字听起来更小”。大模型推理的瓶颈往往是显存带宽:同样一个 7B 模型,FP16 权重大约占 14GB,4 bit 后降到 3.5GB 左右,从显存搬运权重的带宽成本直接下降约 4 倍。所以真实的问题不是“16 个格子还能用吗”,而是“16 个格子怎么用才不伤模型”。
这篇文章分层拆开这 16 个格子:先看量化误差从哪来,再分别讲 GPTQ 和 AWQ 的核心机制,最后落到部署时需要关注的参数、验证方法和常见排查路径。如果你正在做推理优化、边缘部署,或者准备给团队调研低比特量化方案,这篇文章可以帮你少走一些弯路。
1. 为什么“16个格子”不够,但实际又能用
1.1 量化到底在压缩什么
量化是把连续浮点权重映射到一组离散整数的过程。以 4 bit 为例,量化后的每个权重只能取 0 到 15 中的一个整数代码。反量化时,再用一个统一的比例因子(scale)和零点(zero_point)把它还原成浮点值,得到一个近似原权重的新权重。
可以用一个很小的公式来描述:
q = round((x - zero_point) / scale) x_hat = q * scale + zero_point这里的 scale 决定两个整数之间对应的浮点间隔。4 bit 只有 16 个整数代码,在相同的动态范围下,间隔往往比 8 bit 更大。比如原始权重分布在 -2.0 到 2.0 之间,均匀 4 bit 量化的步长大约是 0.25,很多小权重会被吸到同一个整数上,于是每个权重都产生一定误差。
问题就来了:模型里动辄上百亿个权重,每个权重都有误差,这些误差难道不会累积成灾难吗?
关键在于,4 bit 量化并不是对每个权重独立使用一把全局尺子。它通常把一个矩阵切成若干分组,每组共享自己的 scale 和 zero_point。这样一来,16 个整数代码在每个分组里对应完全不同的浮点数值。组 A 的代码 5 可以是 0.13,组 B 的代码 5 可以是 -0.87。
所以准确的说法是:每个权重在编码时只能用 0~15 这 16 个编号,但编号还原出来的浮点值并不是全模型统一的一一对应关系。一个模型里有成千上万套 16 个格子,每套格子只服务一小段权重。
1.2 权重分布决定了误差的性质
大模型的权重分布并不是均匀的。大多数权重集中在零附近,绝对值很小,只有少数权重的绝对值比较大,类似一种中间高、两边低的钟形分布。如果只用一套全局尺度,就会出现两个极端:
- 为了覆盖最右侧的大权重,scale 会被拉大,导致零附近的小权重被“颗粒化”,大量细节直接丢失;
- 如果为了照顾小权重把 scale 调小,大权重又会超出量化范围,必须截断,产生巨大误差。
分组量化解决的就是这个问题。每一组内部的权重分布相对集中,scale 可以随着该组动态范围去调整。组内变化越小,16 个格子的“布局”就越贴近这组权重的真实分布,量化误差越小。
这也是为什么 group_size 是低比特量化最重要的参数之一。它在本质上决定了“局部尺子”的分辨率。
1.3 误差会被模型结构消化掉一部分
还有一个容易被忽视的事实:单个权重的微小误差,并不会直接决定模型输出。矩阵乘法是大量乘加操作的累加,误差有正有负,在求和过程中会部分抵消。残差连接、注意力加权、多层非线性变换,也会对噪声做一定程度的“平滑”。
量化误差更像给模型加了一层噪声,而不是把模型的一部分直接删除。只要这个噪声不是系统性地偏向某一类输出,模型通常能保持主体能力。GPTQ 和 AWQ 要解决的问题,就是尽量减少这种噪声中“尤其危险”的部分,并避免误差逐层累积。
2. 从朴素量化到分组量化:16个格子是如何“移动”的
2.1 三种尺度:per-tensor / per-channel / per-group
量化尺度可以作用于不同粒度的数据:
| 粒度 | 共享尺度的范围 | 优点 | 代价 |
|---|---|---|---|
| per-tensor | 整个张量共享一套 | 实现最简单 | 动态范围大,误差最大 |
| per-channel | 每个输出/输入通道各一套 | 能适配通道差异 | 元数据稍多 |
| per-group | 每个固定大小的权重块一套 | 精度高,广泛用于 4bit | 元数据比 per-channel 更多 |
4 bit 量化场景里,per-tensor 往往不够用,per-channel 在很多大模型里也还有压力,所以主流方案会进一步使用 per-group,常见 group_size 为 128 或 64。
2.2 group_size 改变的是什么
当 group_size=128 时,每 128 个权重共享一组 scale 和 zero_point,也就是共享一套 16 个格子。这 128 个权重可以来自同一输出通道的相邻输入特征,也可以是一个矩阵中的连续块,具体切法取决于框架实现。
group_size 越小,意味着有更多的“局部尺子”,16 个格子的覆盖范围越精准,量化误差越小。但代价也很直接:需要存储的 scale 和 zero_point 数量更多,显存开销和反量化计算量会上升。实际部署中,128 是一个比较常见的平衡点;64 会更稳,但收益不一定足够明显。
2.3 对称与非对称量化
对称量化通常把整数范围中心设在 0,不需要 zero_point;非对称量化则增加了一个零点,能让 16 个格子更好地覆盖偏斜的分布。
权重分布通常比较对称,许多量化方案会首选对称量化实现更简洁;但激活值分布常常偏斜,所以一些量化配置会在激活侧或特定层使用非对称量化。对于 4 bit 权重量化,非对称配合 group_size 能进一步提升精度,代价是反量化时多一个参数参与计算。
这里的关键结论是:16 个格子本身不是限制,限制在于每个分组里格子的分布是否与权重分布匹配。分组、缩放、零点,都是为了让格子的“位置”和“间距”尽量贴合局部数据。
3. GPTQ:用误差传播补偿来“挽救”量化
3.1 核心理念:最小化层输出的变化,而不是单个权重误差
GPTQ 的全称是 Generative Pre-trained Transformer Quantization,但它的核心思想并不是某种大模型专属公式,而是把量化看成一个优化问题。
普通量化只关注“把 W 变成 \hat{W},让 ||W - \hat{W}|| 尽量小”。GPTQ 则认为,真正重要是“用 \hat{W} 替换 W 之后,这一层的输出变化尽量小”。也就是说,目标不是让每个权重都接近原值,而是让整个层的输出接近原输出。
用数学语言表达,GPTQ 对每一层解决类似这样一个问题:给定校准数据 X,希望找到量化后的权重矩阵 \hat{W},最小化 ||WX - \hat{W}X||。
因为是线性层的输出,误差经过 X 加权后,不同位置的权重对输出的影响并不相同。这种思路让 GPTQ 可以在量化一个权重的同时,通过修改同一层其他未量化权重,去抵消前面已经产生的误差。
3.2 算法流程:逐层重建、二阶信息、权重更新
GPTQ 的实际实现是逐层、逐块处理的。它并不直接对所有权重同时做全局搜索,那在百亿参数模型上不现实。它的流程可以概括为几个关键步骤:
- 用一小段校准文本(比如几百条 C4 语料)跑一遍模型,记录每一层输入,作为该层的校准数据 X;
- 对当前层,用 X 构造近似二阶信息矩阵,用来估计“如果这个权重被量化,下一个权重应该往哪个方向调整”;
- 按一定顺序逐列/逐块量化权重,每完成一个块,就根据量化误差更新后续的未量化权重;
- 所有层都重建完之后,保存量化后的权重,以及每一层的 scale、zero_point 等参数。
这里不需要人工设计如何调整,半步是核心的数学优化。实际工程中会用到 Cholesky 分解、块坐标下降、阻尼项这些工具,目的都是让更新过程在可接受的时间内完成。
用伪代码看大致是这样:
对每一层: H = 2 * (X * X^T) + 阻尼项 # 近似二阶信息 for 每个需要量化的列块: 对块内每个权重做量化 计算量化误差 利用 H 的逆更新剩余权重,抵消误差GPTQ 最大的特点是“不需要知道哪些权重更重要”,而是通过误差传播自动调整。它把量化误差重新分配到了那些对层输出影响更小的位置上。
3.3 GPTQ 的优势与潜在问题
GPTQ 在 4 bit 量化上效果通常很好,这也是它成为社区早期最常见方案之一的原因。它适合一次性离线量化大模型,量化过程可以跑在 CPU/GPU 上,不一定需要目标推理硬件。
但它也有明显的工程限制:
- 量化过程耗时较长,尤其是大模型逐层重建时需要大量矩阵运算;
- 对校准数据有一定敏感性,校准集如果和实际任务分布偏差很大,精度会打折扣;
- 二阶信息矩阵在超大模型上显存消耗不小;
- 权重会被“更新”成一组新的值,不再是原始预训练权重,这会让一些依赖权重分布的调试和分析变得复杂。
所以 GPTQ 更适合“花时间换取精度”的离线量化场景。只要校准数据选得稳,量化结果通常非常接近 FP16。
4. AWQ:用激活值决定哪些权重更重要
4.1 权重本身没有绝对的重要性,重要性要看激活值
AWQ 全称 Activation-aware Weight Quantization,名字已经把核心写清楚了:权重的重要性不由权重本身决定,而由它对应的激活值决定。
同样是两个绝对值相同的权重,如果一个位于激活值很大的通道上,它对输出贡献就更大;如果一个位于激活值接近 0 的通道上,即使量化误差再大,对最终输出也可能无关紧要。
AWQ 的思路非常直观:先跑一小段校准数据,观察每一层的激活分布,找出哪些输入通道上的激活值普遍较大。这些通道对应的权重,就是需要“重点保护”的对象。
这件事在传统剪枝里叫“重要性分析”,在量化里同样有价值。GPTQ 是通过后验误差调整来保护整体输出,AWQ 则是先验地找到重要位置,在量化前就把保护措施做好。
4.2 AWQ 的三步:统计激活、缩放权重、再平衡
AWQ 的做法不是直接改权重数值,而是对重要通道的权重乘一个缩放系数 s。
如果某一个通道的权重普遍很小,而激活值普遍很大,这个通道的权重一旦量化,相对误差会被放大。给这些权重统一乘上一个大于 1 的系数,可以让它们落在量化格子中占据更多档位,从而降低量化相对误差。
但是权重变大了,输出也会变大,所以需要在激活侧也做一次反向缩放,把放大抵消掉。最终的效果是:
- 权重侧:重要通道的权重被放大,量化误差相对变小;
- 激活侧:重要通道的激活被缩小,保证矩阵乘法的数学结果不变;
- 部署时:反向缩放可以融合到相邻的 LayerNorm、线性层或反量化参数中,不增加推理计算量。
换句话说,AWQ 并不是把权重重写成一个“更聪明”的新矩阵,而是给重要通道换了一把更合适的尺子。这把尺子在量化后又被悄悄融进网络的其他部分,所以用户感知不到额外开销。
4.3 为什么 AWQ 能比 GPTQ 更轻量
GPTQ 需要反复更新权重,依赖二阶信息矩阵,量化过程比较重。AWQ 主要靠激活统计和缩放搜索,不需要对权重做在线重建,量化速度快很多。
当然,AWQ 也离不开校准数据。没有校准数据,模型就无法知道哪些通道重要。它的优点是把大部分额外计算放到预分析阶段,量化过程本身更简洁。
AWQ 在 4 bit 和更低位宽场景下表现稳定,在多模态模型、视觉语言模型和边缘部署上尤其受关注。原因是这类模型里不同模态、不同层的激活分布差异很大,先看激活值再决定保护策略,往往比统一做后验误差补偿更直接。
4.4 AWQ 的局限性
AWQ 不是万能的。如果校准数据无法代表真实推理时的输入分布,统计出来的“重要通道”可能并不准确。另外,缩放系数s的选取方式会影响最终效果,不同实现里的搜索策略也不完全一样。
对比来看,GPTQ 更像“事后补救”,AWQ 更像“提前布局”。两者目标一致,但路径不同,实际效果与模型结构、校准数据和量化配置都有关系。
5. GPTQ vs AWQ:核心对比与选型
| 对比维度 | GPTQ | AWQ |
|---|---|---|
| 核心思路 | 量化后用误差补偿更新剩余权重 | 量化前用激活值保护重要通道 |
| 是否修改权重数值 | 会,权重被重建 | 基本不重建,主要做缩放 |
| 是否依赖校准数据 | 依赖 | 依赖 |
| 是否需要二阶信息 | 需要,计算较重 | 不需要,主要统计激活分布 |
| 量化过程速度 | 较慢,尤其大模型 | 较快 |
| 典型工具链 | AutoGPTQ、ExLlama、transformers、LMDeploy 等 | AutoAWQ、vLLM、MLC-LLM、transformers 等 |
| 常见位宽配置 | 4bit,group_size 128 | 4bit,也常用于更低 bit 和多模态 |
| 特色优势 | 逐层输出误差控制能力强 | 轻量、快速、对激活分布敏感场景友好 |
选型时最直接的判断是:如果你要一次性离线量化一个比较大的模型,对时间不敏感,想要一个成熟稳定的方案,GPTQ 依然是可靠选项。如果你在反复试验不同量化位宽,或者模型是多模态、任务输入分布变化大,又或者在边缘设备上做部署,AWQ 的轻量特性会更有优势。
不过没有必要把两者看成互相替代的关系。同一个模型用不同方案量化后,评价指标可能非常接近;真正拉开差距的,往往不是方案本身,而是校准数据质量和参数配置。
6. 16个格子在真实工程中的配置
6.1 常见量化参数解释
一个 4 bit 量化配置通常包含下面这些字段。先看一份典型 JSON:
{ "bits": 4, "group_size": 128, "desc_act": false, "sym": false, "damp_percent": 0.01, "calibration_dataset": "c4" }字段含义:
- bits:量化位宽,4 表示用 0~15 共 16 个格子;
- group_size:每组权重数量,128 是最常用配置之一;
- desc_act:是否按激活值顺序处理权重列。开启后精度可能更好,但计算量增加,部分框架下速度更慢;
- sym:是否对称量化。false 表示使用非对称量化,保留 zero_point;
- damp_percent:为二阶信息矩阵加的阻尼比例,主要用于数值稳定性;
- calibration_dataset:校准数据集名称,GPTQ 系列里常见的是 C4。
这些参数看起来简单,但对最终模型质量影响很大。尤其是 group_size,从 128 改成 64,显存和计算代价会变高,但精度可能并没有明显提升;从 128 改成 256,显存省了,但量化误差会变大,必须通过评测确认是否可接受。
6.2 使用 AutoGPTQ 量化模型
下面是一个用 AutoGPTQ 做 4 bit 量化的通用示例。不同版本 API 可能会有少量差异,但整体流程一致。
from transformers import AutoTokenizer from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig model_path = "meta-llama/Llama-2-7b-hf" quant_path = "./llama2-7b-gptq-4bit" quantize_config = BaseQuantizeConfig( bits=4, group_size=128, desc_act=False, damp_percent=0.01, ) tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=True) model = AutoGPTQForCausalLM.from_pretrained( model_path, quantize_config=quantize_config, ) examples = [ tokenizer("Attention is all you need.", return_tensors="pt"), tokenizer("The quick brown fox jumps over the lazy dog.", return_tensors="pt"), ] model.quantize(examples) model.save_quantized(quant_path)这段代码的作用是:加载原始模型,声明 4 bit、group_size=128 的量化配置,用少量校准文本触发逐层重建,最后把量化模型保存到本地目录。实际使用时校准文本应该覆盖任务相关的风格和领域,数量也往往需要几百条,而不只是两句。
6.3 使用 AutoAWQ 量化模型
AutoAWQ 的量化流程更短,核心是告诉它位宽、分组大小和是否使用 zero_point:
from awq import AutoAWQForCausalLM from transformers import AutoTokenizer model_path = "meta-llama/Llama-2-7b-hf" quant_path = "./llama2-7b-awq-4bit" quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM", } tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=True) model = AutoAWQForCausalLM.from_pretrained(model_path) model.quantize(tokenizer, quant_config=quant_config) model.save_quantized(quant_path)与 GPTQ 相比,AWQ 量化过程通常更快,因为不需要逐层做高代价的权重更新。它主要依赖校准文本的激活统计来决定缩放策略。
6.4 加载量化模型进行推理
量化完成后,生产环境通常不会再用量化脚本加载模型,而是用推理框架直接读量化权重。以 transformers 为例,AutoGPTQ 生成的模型可以直接加载:
from transformers import AutoTokenizer, AutoModelForCausalLM quant_path = "./llama2-7b-gptq-4bit" tokenizer = AutoTokenizer.from_pretrained(quant_path) model = AutoModelForCausalLM.from_pretrained( quant_path, device_map="auto", torch_dtype="auto", ) inputs = tokenizer("4bit量化为什么能保持效果?", return_tensors="pt") outputs = model.generate(**inputs, max_new_tokens=64) print(tokenizer.decode(outputs[0], skip_special_tokens=True))如果使用 vLLM、LMDeploy 等服务框架,加载 AWQ/GPTQ 模型的命令通常会显式声明量化方式,例如:
vllm serve ./llama2-7b-awq-4bit/ \ --quantization awq \ --max-model-len 4096到这里,16 个格子已经从“理论问题”变成了“工程参数”。你不再需要关心格子到底长什么样,只需要关心 group_size、sym、desc_act 这些参数是否匹配,以及模型在目标任务上的表现是否达标。
7. 量化模型部署后的验证与常见问题
7.1 效果验证的两条路径
量化之后不能只看显存下降,就认为万事大吉。建议至少跑两条验证线:
第一条是损失/困惑度验证。用固定评测集中计算模型在量化前后的困惑度或验证集 loss,对比 FP16 baseline。正常的 4 bit 量化,困惑度增加通常很轻微;如果你的量化模型 loss 明显变差甚至出现乱码,大概率是校准集不合适、位宽/分组配置不合理,或者加载参数和量化参数不一致。
第二条是任务指标验证。常见做法是跑一组与业务强相关的 few-shot 评测,比如代码生成、数学推理、指令跟随。任务指标比单一 loss 更能反映实际体验。
7.2 常见问题排查表
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 加载模型失败或报权重大小不匹配 | 量化库版本与生成模型版本不一致 | 查看错误堆栈,检查模型目录里的量化配置 | 统一量化库版本,重新导出模型 |
| 生成结果乱码、胡言乱语 | 加载时的 dtype/设备配置与量化配置不匹配 | 检查加载参数里的 torch_dtype、device_map | 改为与导出时一致,或使用官方推理脚本 |
| 量化后指标下降明显 | 校准集太小或与业务分布偏差大 | 扩大校准集,加入领域语料后重新量化 | 重新量化,尝试 group_size=64 |
| 掉点集中在长尾内容 | group_size 过大或 desc_act 开启状态不合适 | 对比不同 group_size 的指标差距 | 优先把 group_size 调整为 128 或 64 |
| 推理速度反而变慢 | 量化后权重被反量化回 FP16,或算子未走优化路径 | 检查是否调用优化 kernel,看 profiling | 使用 vLLM、LMDeploy 等优化好量化算子的框架 |
| 显存下降但吞吐没提升 | 模型未真正使用整数/反量化后的低带宽读取 | 用 profiler 观察权重搬运量 | 确认推理框架支持 quantized GEMM |
| 多模态模型量化后能力失衡 | 不同模态对量化敏感度不同 | 分别评测视觉/文本子任务 | 考虑混合精度,对敏感模块保留更高位宽 |
排查时最重要的一件事是先保留 FP16 baseline。没有 baseline,你无法判断量化后的差距到底是方案问题,还是本身评测噪声就很大。
8. 什么时候16个格子真不够用
低比特量化不是免费的午餐。以下情况里,16 个格子可能真的不够用:
第一,模型本身很小。参数量越少,单个权重对能力的贡献越大,量化误差缺乏统计稀释空间。一个 1B 模型做 4 bit 量化的风险,通常高于 70B 模型。
第二,任务对数值精度高度敏感。比如某些数学推理、时间序列预测、科学计算场景,可能对绝对值精度要求更高,量化误差会被放大。
第三,校准数据无法覆盖实际分布。无论 GPTQ 还是 AWQ,都依赖校准数据。如果校准数据来自英文通用语料,部署时却大量处理代码、中文、方言或多语种,重要权重可能出现误判。
第四,想继续压到 2 bit 以下。4 bit 的 16 个格子已经是比较紧张的边界,再往下走,每个权重只有 4 个格子或 2 个格子,就必须配合稀疏化、混合精度、量化感知训练等手段,单纯靠校准很难稳住。
未来低比特量化的方向,其实已经不是“16 个格子怎么用”,而是如何让不同部分使用不同位宽、不同表示方式,甚至跳过某些参数。1.58 bit 这样的极端位宽也被反复讨论,背后思路和 AWQ 一脉相承:把有限表达能力集中到真正重要的部分上。
9. 最后一个建议
如果你正准备在项目里引入 4 bit 量化,我的建议是不要一上来就迷信某一个框架或某一种配置。先拿一个和业务最接近的小模型,跑通 FP16 baseline、4bit GPTQ、4bit AWQ 三组实验,记录困惑度和任务指标,再看显存与吞吐收益是否值得。绝大多数踩坑,根源都是校准数据不匹配、group_size 设置不谨慎和加载参数不一致,而不是“16 个格子”本身。
把位数从 16 bit 降到 4 bit,本质上不是把模型变笨,而是把误差重新分配到不影响输出的地方。GPTQ 用后验补偿做到了这一点,AWQ 用激活感知做到了这一点。理解了这个前提,再去看各种量化工具的文档,很多参数你一眼就能判断该不该调。