开源大模型量化后精度下降,先别急着换量化方法。多数情况要先把两类原因分开:一类是量化本身带来的权重误差,一类是评测口径、提示模板、解码参数或推理后端不一致造成的假掉点。建议先花半天时间把评测口径对齐并分桶看结果,再决定是调量化参数、换量化方案,还是回退到更高位宽。
量化掉点多数能收敛到可接受范围,但前提是先分清是量化误差还是评测口径不一致:同一套提示模板、同一批样本、同一组解码参数重跑,再按任务类型分桶找塌陷位置。若差距集中在数值、代码或长上下文,优先提高位宽、让 lm_head 与 embedding 保持高精度,并留一份非量化权重做回退。
先确认当前场景和目标
“精度下降”要有可比基线才算成立。先把下面这些信息列出来,缺一项就先补齐,不要凭感觉调参:
- 任务类型:生成式(摘要、改写、多轮问答)通常对量化更宽容;结构化抽取、分类打分、代码、数学、多语言长尾更敏感。这两类要用不同的容忍标准。
- 基线数据:量化前用的是什么模型、什么评测集、多少条样本、指标怎么算。没有基线就没法判断掉多少。
- 量化方式:GPTQ、AWQ、GGUF 的 K-quant、bitsandbytes 的 NF4/INT8,位宽、group size、是否只量化权重、是否量化 KV cache。这些参数不同,掉点形态完全不同。
- 推理环境:推理后端、显卡型号、计算 dtype(fp16 / bf16)、是否开 flash attention、batch 与 padding 策略。
- 解码参数:temperature、top_p、max_new_tokens、是否贪心解码。基线用采样、量化后用贪心,结果不可比。
判断条件可以简化成一句话:如果量化前后的调用参数或提示模板不一致,先修口径;如果口径完全一致、差距仍然集中在某几类样本,才按量化问题处理。

按这个顺序处理
从成本最低、最可能排除误判的动作开始,逐级往下走,每级都保留改动记录。
- 统一评测口径:同一份 chat template、同一 tokenizer、同一 dtype、贪心解码(
do_sample=False)跑一遍。很多“掉点”到这一步就消失了。 - 分桶定位塌陷样本:把基线和量化版的输出按样本 id 对齐,按任务类型分桶,先看结构化信号(空输出、截断、重复、JSON 是否可解析),再看语义质量。别只看总平均分。
- 调量化粒度:先尝试让
embed_tokens、lm_head保持高精度,再调 group size;仍不够就提高位宽,或换另一种量化方法重新离线量化。 - 检查 tokenizer 与特殊 token:BOS/EOS、pad token、chat template 里的角色标记是否与基线一致;低比特模型对特殊 token 的处理异常会表现为“答非所问”或提前截断。
- 单独排查 KV cache 与长上下文:KV cache 量化、长上下文截断通常是独立开关,先关掉再做对比,能快速判断是不是它引起的。
- 兜底路由:对精度敏感的任务保留非量化模型或更高位宽实例,把请求按任务类型路由过去。
一个通用的对比脚本骨架(模型路径、提示词、样本集替换成自己的):

# 只用于口径对齐,替换模型路径后跑两遍:基线一次、量化版一次
from transformers import AutoModelForCausalLM, AutoTokenizer
def run(model_path, prompts, max_new_tokens=256):
tok = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", torch_dtype="bfloat16")
outs = []
for p in prompts:
msgs = [{"role": "user", "content": p}]
ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt").to(model.device)
gen = model.generate(ids, max_new_tokens=max_new_tokens, do_sample=False)
outs.append(tok.decode(gen[0][ids.shape[-1]:], skip_special_tokens=True))
return outs
量化加载配置的示意(字段名以所用加载库为准,模块名需先打印模型结构核对):
# 示例:权重 4bit 加载,跳过高精度敏感层
quant_config = {
"load_in_4bit": True,
"quant_type": "nf4",
"compute_dtype": "bfloat16",
"double_quant": True,
"skip_modules": ["lm_head", "embed_tokens"], # 名称按模型实现确认
}
结构化信号的检查可以先写成规则,避免全靠人眼看:

# 逐样本对照,先看能被程序判定的异常
for case in dataset:
out = quant_out[case.id]
flags = {
"empty": len(out.strip()) == 0,
"json_bad": case.expect_json and not is_json(out),
"repeat": has_repeat(out, n=3),
"short": len(out) < case.min_len,
}
record(case.id, case.bucket, flags)
改完后怎么验证
验证要同时看指标和形态,只看一个数字容易被误导。
- 重跑同一评测集:样本、提示模板、解码参数全部与基线一致,输出整体指标和分桶指标两张表,对比差距是否收敛到可接受范围。
- 看结构指标:空输出率、截断率、重复率、JSON 可解析率、超长回答比例。这些指标在量化后往往比语义分先恶化。
- 并排抽检:从每个桶里各抽若干条,把基线输出和量化版输出并排放,人工判断是“语气变了”还是“事实错了”。语气差异可以接受,事实错误必须处理。
- 稳定性检查:贪心解码下同一 prompt 多次调用结果应基本一致;如果反复发散,先怀疑后端或 batch padding 问题。
- 长尾复测:数字计算、代码、多语言、超长输入单独跑一组,这些是量化掉点的常见位置。
预期信号:分桶差距回到与任务容忍度匹配的水平,结构指标不差于基线,抽检中没有系统性的事实错误或格式崩坏。达不到就退回上一步,不要在同一组参数上反复微调。
容易忽略的边界
- 量化格式与后端强耦合:GPTQ、AWQ、GGUF 各自依赖对应的推理内核,换推理框架或内核实现可能重新掉点,需要结合环境确认,不能默认可迁移。
- 硬件与 dtype 不可比:不同显卡支持的算子和 dtype 不同,fp16 与 bf16 的溢出行为也不一样,跨机器对比前要先统一。
- 低比特不等于更快:实际吞吐受显存带宽、批大小、反量化开销影响,别把“量化后更省显存”直接当成性能结论,要按真实并发去测。
- 不适用场景:打分排序、数值计算、强约束的合规审核这类任务,对权重误差没有多少容忍空间,小修小补通常不够,应直接回退到更高位宽或非量化模型。
- 回退建议:始终保留一份非量化权重或高位宽实例,作为线上兜底和 A/B 对照;量化版本改动写入评测脚本的版本记录,否则下次无法复现。