
先说明可信度: 第一部分 Mac(Apple 芯片)是我亲手实践的,数据都是在 M3 Max 上实测的,脚本和日志都留着,可以复现。第二部分 NVIDIA 显卡我没有实践过(手头没有 NVIDIA 显卡),内容是 2026 年 10 月初联网查询并通读官方文档整理的,命令和代码都没有运行过,也没有速度和精度数据,请当作操作指引。「较新的方法」一节同样来自官方说明,效果数字是官方自己的说法,我没有复现。
摘要
大模型量化就是把 16 位权重压到 4 位,换更小的体积和更快的解码。这篇文章分 Mac 和 NVIDIA 两种硬件讲怎么做:Mac 上我用 RTN、GPTQ、AWQ 把 Qwen2.5-1.5B-Instruct 量化成 INT4,体积从 3087MB 降到 869 到 1143MB,解码速度从 82 提到 205 到 213 token/s,困惑度上升 0.7 到 2 左右;NVIDIA 上按显卡架构选格式(INT4、FP8、NVFP4),用 llm-compressor、GPTQModel、NVIDIA Model Optimizer 量化,再交给 vLLM 部署。
背景与问题
1.5B 模型的 bf16 权重就要 3GB,7B 要 14GB,70B 要 140GB。显存和带宽是部署的硬门槛,量化让同一块卡装得下更大的模型,每个 token 读的数据也更少。
网上的量化文章大多只讲原理,或贴别人的数字,还默认你有 NVIDIA 显卡。真正动手会遇到的问题:
- RTN、GPTQ、AWQ 到底差多少,值不值得多花时间
- 校准数据怎么选,踩坑怎么办
- 只有 Mac 能不能做真实的量化,速度是不是真的变快
- 手里是 NVIDIA 显卡,该选什么格式、什么工具、怎么部署
- 新出的方法(FP8、NVFP4、蒸馏式量化等)该不该用
核心思路与优势
格式和算法
格式 决定权重最后存成什么精度,算法决定怎么压进去而少损失精度。
- INT4 权重量化(W4A16):权重 4 位整数,激活保持 16 位,按组(如 128 个一组)存缩放系数,Mac 和 NVIDIA 都能用
- FP8(W8A8):权重和激活都用 8 位浮点,较新的 NVIDIA 显卡才有硬件加速
- FP4 微缩放(NVFP4、MXFP4):4 位浮点,按小块共享缩放系数,可做到权重和激活都 4 位(W4A4),Blackwell 显卡有原生 FP4 计算
三个经典算法:
- RTN(四舍五入):把每组权重线性映射到 16 个整数档位,不用数据,几秒完成,最粗糙
- GPTQ:逐层用校准数据估计二阶信息(Hessian),量化一个权重后把误差补偿到还没量化的权重上,用计算换精度
- AWQ:论文观察到约 1% 的权重通道特别重要,重要与否看对应激活的大小。它不做误差补偿,而是按激活幅度搜索逐通道缩放系数来保护这些通道,再统一量化
较新的方法(截至 2026 年 10 月初)
- AutoRound(llm-compressor 支持):引入三个可训练参数,逐解码层用块输出的重建误差优化取整方式和裁剪范围,目标是低比特下保住精度
- 变换类方法(llm-compressor):量化前做旋转类变换(如 Hadamard),压低异常值的影响,官方示例支持 SpinQuant/QuaRot 和 QuIP 两种风格
- MSE、iMatrix 观察器(llm-compressor):MSE 用网格搜索找误差最小的缩放系数,iMatrix 再按各输入通道的激活重要性加权。官方 README 称它们在 NVFP4 上平均优于 GPTQ,依据是官方内部的困惑度基准
- GPTQ 的 Triton 内核(llm-compressor):README 称 0.13.0 之后 GPTQ 加入了 Triton 量化内核,比之前快约 15 倍。我翻了 0.14.0 的源码,它只在 CUDA 或 Intel XPU 设备上启用,和我在 Mac 上纯 CPU 跑一小时没有关系
- 量化感知蒸馏 QAD、局部 Hessian 缩放(NVIDIA Model Optimizer):针对激进的 NVFP4,用蒸馏补回精度;官方 9 月 16 日的教程称 Qwen3.6-35B-A3B 做 NVFP4 W4A4 加 QAD 后,在 vLLM 上吞吐最高为 bf16 的 1.30 倍,检查点小 3.1 倍
- mlx-lm 的 DWQ 和动态量化 (Mac):
dwq拿原模型当老师,蒸馏改进已量化的学生模型;dynamic_quant先估计各层敏感度,再按目标平均位数让敏感层用高位宽、其余用低位宽 - GPTQModel:除 GPTQ、AWQ,README 的支持列表里还有 ParoQuant、EXL3、GPTAQ 等
面向人群
- 想把模型压小、跑快的工程师
- 只有 Mac,想真实做一次量化的人
- 需要在 RTN、GPTQ、AWQ 之间做选择,想看对比数据的人
- 有 NVIDIA 显卡(T4、RTX 30/40 到 A100、H100、Blackwell),想知道选什么格式和工具、怎么用 vLLM 部署的人
实践步骤
第一部分:Mac(Apple 芯片)
实验环境
- 机器:Apple M3 Max,36GB 统一内存
- 模型:Qwen2.5-1.5B-Instruct,bf16 权重 3087MB
- 工具:llm-compressor 0.14.0 和 mlx-lm 0.32.0(Apple 芯片原生)
- 评测:WikiText-2 测试集,按 1024 个 token 切成 292 个不重叠窗口算困惑度,越低越好
在国内建议先用魔搭社区下载模型,找不到再走 Hugging Face,这个模型在两边名字一致:
bash
modelscope download --model Qwen/Qwen2.5-1.5B-Instruct --local_dir ./Qwen2.5-1.5B-Instruct
我这次是直接走 Hugging Face 下的,通过代理下这 3GB 花了十几分钟。
路线一:llm-compressor
一个脚本跑三种方法,区别只在 recipe:
python
import torch
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier, QuantizationModifier
# 让 llm-compressor 看不到 Apple GPU,校准全程在 CPU 上跑
torch.accelerator.is_available = lambda: False
torch.mps.is_available = lambda: False
torch.backends.mps.is_available = lambda: False
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
# RTN 换成:QuantizationModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(
model="Qwen/Qwen2.5-1.5B-Instruct",
dataset="wikitext",
dataset_config_name="wikitext-2-raw-v1",
splits={"calibration": "train"},
recipe=recipe,
max_seq_length=512,
num_calibration_samples=128,
concatenate_data=True,
shuffle_calibration_samples=True,
output_dir="out/qwen15-gptq-w4a16",
)
W4A16 是权重 4 位整数、激活 16 位,128 个一组、对称量化,lm_head 不量化。
踩的坑 :没加 concatenate_data=True 时,GPTQ 在第一层就报 Expected tensor for argument #1 'indices' to have one of the following scalar types: Long, Int。我先怀疑是 MPS 后端的问题,切到 CPU 后还是同一个错,只是类型名变成了 FloatTensor。看源码后更可能的原因是:WikiText 有大量空行,库不会过滤空样本,空字符串分词得到空列表,torch.tensor([[]]) 默认是浮点型,于是 input_ids 变成了浮点。开启 concatenate_data=True 后文本先拼接再切块,问题消失。
| 方法 | 困惑度 | 权重体积 | 量化耗时 |
|---|---|---|---|
| bf16 原模型 | 10.805 | 3087MB | - |
| RTN | 12.771 | 1143MB | 几秒 |
| GPTQ | 11.539 | 1143MB | 约 59 分钟(纯 CPU) |
GPTQ 把困惑度上升从 RTN 的 1.97(+18%)压到 0.73(+6.8%),消掉了 RTN 约 63% 的精度损失。这一组校准用 WikiText 训练集、评测用 WikiText 测试集,同一领域,对 GPTQ 有利。评测时 INT4 权重被 transformers 解压回 bf16 再算,所以这张表只说明精度,不说明速度。
体积没降到理论上的四分之一,是因为词嵌入层没量化(输入输出共用一张表,lm_head 又被排除),它单独占约 467MB。
llm-compressor 的 AWQ 我没跑完:它对每层 4 组平滑映射各做 20 个点的网格搜索,CPU 上每个点约 22 秒,28 层估算超过 10 小时,中止了。下面 AWQ 的数字只来自 mlx-lm。
路线二:mlx-lm
mlx-lm 自带三种量化命令,Apple 芯片上直接能跑,不依赖 CUDA:
bash
M=Qwen/Qwen2.5-1.5B-Instruct
# RTN
python -m mlx_lm convert --hf-path $M --mlx-path mlx/rtn4 -q --q-bits 4 --q-group-size 64
# AWQ
python -m mlx_lm awq --model $M --mlx-path mlx/awq4 --bits 4 --group-size 64
# GPTQ
python -m mlx_lm gptq --model $M --mlx-path mlx/gptq4 --bits 4 --group-size 64
分组 64,同一套评测窗口:
| 方法 | 困惑度 | 权重体积 | 量化耗时 |
|---|---|---|---|
| bf16 原模型 | 10.804 | 3087MB | - |
| RTN | 12.404 | 869MB | 7 秒 |
| AWQ | 12.157 | 883MB | 约 14 分钟 |
| GPTQ | 12.816 | 927MB | 约 10 分钟 |
这一组和上一组结论相反:GPTQ(12.816)反而比 RTN(12.404)差,AWQ(12.157)最好。 两组不能直接比,原因有:
- mlx-lm 的 GPTQ 和 AWQ 默认校准文本不是 WikiText,而是 llama.cpp 社区常用的通用文本
- 格式不同:llm-compressor 是对称、分组 128,mlx-lm 是带零点的仿射量化、分组 64;mlx-lm 的 GPTQ 还把没被处理的层(含词嵌入)回退成 6 位,所以体积更大,AWQ 的词嵌入默认 4 位、分组 32
- 模型只有 1.5B,量化噪声本来就明显
所以我只能说:在这个模型上,换了校准数据和实现,GPTQ 与 RTN 的胜负翻转了,两组同时变了好几个因素,没法把原因单独归到校准数据上。"GPTQ 一定比 RTN 好"这个说法并不严谨。
速度
mlx_lm benchmark,输入 512 个 token,生成 256 个,3 次取平均:
| 模型 | 预填充 token/s | 解码 token/s | 峰值内存 |
|---|---|---|---|
| bf16 | 2450 | 82.1 | 3.46GB |
| RTN 4 位 | 2524 | 213.2 | 1.62GB |
| AWQ 4 位 | 2472 | 209.5 | 1.62GB |
| GPTQ 4 位 | 2478 | 205.4 | 1.67GB |
解码快约 2.5 倍,峰值内存降约 53%,三种方法速度差在 4% 以内(GPTQ 稍慢,对应它回退成 6 位的层更大)。预填充几乎没变:预填充受算力限制,解码受带宽限制,权重变小只帮得上后者。
同一个提示词"用三句话解释什么是模型量化,并说明 INT4 为什么能省显存",贪心解码,bf16 和 AWQ 4 位的回答都通顺没有胡话,只有一个样本,只能说明没有明显崩坏。
第二部分:NVIDIA 显卡
按显卡架构选格式
| 显卡架构 | 典型型号 | 推荐格式 | 说明 |
|---|---|---|---|
| Ampere | A100、RTX 30 系 | INT4(W4A16,GPTQ 或 AWQ) | vLLM 用 Marlin 内核加速,省显存、降解码延迟 |
| Ada、Hopper | RTX 40 系、L40S、H100 | FP8(W8A8),或 INT4 | FP8 需要算力 8.9 以上,免校准数据 |
| Blackwell | B200 等 | NVFP4 | 原生 FP4 计算,需要算力 10.0 |
官方文档里的几个细节:
- INT4 的最低要求,vLLM 的 INT4 文档写算力大于 8.0(Ampere 起),llm-compressor 的方案选择文档写 W4A16 从 Turing(7.5)起可用。两处不一致,保险起见按 Ampere 起步,T4 这类 Turing 显卡先小规模验证
- 算力低于 8.9 的显卡可以加载 FP8 模型,但只按 W8A16 权重量化运行,用 FP8 Marlin 内核
- NVFP4 在没有原生 FP4 内核的显卡上,vLLM 会退回 W4A16 路径并打印警告,吞吐可能不及预期
工具现状
别再装 AutoAWQ 和 AutoGPTQ:两个仓库都已归档,AutoAWQ 的 README 写明正式弃用,替代品是 llm-compressor;AutoGPTQ 的 README 建议改用 GPTQModel。仍在维护的主要有三套:
- llm-compressor:vLLM 项目的官方量化库,GPTQ、AWQ、SmoothQuant、FP8、NVFP4 都有,产物是 compressed-tensors 格式
- GPTQModel:GPTQ、AWQ、FP8 等多种格式,覆盖 NVIDIA CUDA、AMD ROCm、Intel、CPU,也能在 Apple 芯片上用
- NVIDIA Model Optimizer:主打 FP8、INT4 AWQ、NVFP4,检查点可给 TensorRT-LLM、vLLM、SGLang 用
方案一:llm-compressor
Mac 那份脚本去掉强制 CPU 的三行就能搬过来,校准会自动用 GPU。官方示例对校准数据的建议:从 512 条样本、2048 序列长度起步,精度掉得多再加样本,并使用模型训练时的对话模板。
python
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from llmcompressor import oneshot
from llmcompressor.modifiers.gptq import GPTQModifier
MODEL_ID = "./Qwen2.5-1.5B-Instruct" # 换成你自己的模型
NUM_SAMPLES, MAX_LEN = 512, 2048
model = AutoModelForCausalLM.from_pretrained(MODEL_ID)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
# 校准数据:用对话模板格式化,再分词(模板已带 bos,所以 add_special_tokens=False)
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split=f"train_sft[:{NUM_SAMPLES}]").shuffle(seed=42)
ds = ds.map(lambda ex: {"text": tokenizer.apply_chat_template(ex["messages"], tokenize=False)})
ds = ds.map(
lambda s: tokenizer(s["text"], padding=False, max_length=MAX_LEN,
truncation=True, add_special_tokens=False),
remove_columns=ds.column_names,
)
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(model=model, dataset=ds, recipe=recipe,
max_seq_length=MAX_LEN, num_calibration_samples=NUM_SAMPLES)
SAVE_DIR = "Qwen2.5-1.5B-Instruct-W4A16-G128"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
换成 AWQ,官方配方是两个修饰器串起来:AWQModifier 先算缩放,QuantizationModifier 再做量化,官方示例用带零点的 W4A16_ASYM:
python
from llmcompressor.modifiers.quantization import QuantizationModifier
from llmcompressor.modifiers.transform.awq import AWQModifier
recipe = [
AWQModifier(),
QuantizationModifier(ignore=["lm_head"], scheme="W4A16_ASYM", targets=["Linear"]),
]
注意导入路径:我查了 0.14.0,AWQModifier 已搬到 llmcompressor.modifiers.transform.awq,旧路径 llmcompressor.modifiers.awq 只是会报弃用警告的兼容层,沿用的是把量化参数直接传给 AWQModifier 的旧写法,两种写法不要混用。
量化出来的目录可以直接交给 vLLM:
bash
vllm serve ./Qwen2.5-1.5B-Instruct-W4A16-G128
python
from vllm import LLM
llm = LLM("./Qwen2.5-1.5B-Instruct-W4A16-G128")
vLLM 官方文档提醒,vLLM 和 llm-compressor 最好装在两个独立的虚拟环境里,依赖可能冲突。
方案二:GPTQModel
接口是"加载、量化、保存"三步,适合只想要 GPTQ 或 AWQ 产物的人:
bash
pip install -v gptqmodel
python
from datasets import load_dataset
from gptqmodel import GPTQConfig, GPTQModel
model_id = "./Qwen2.5-1.5B-Instruct"
quant_path = "Qwen2.5-1.5B-Instruct-gptqmodel-4bit"
calibration_dataset = load_dataset(
"allenai/c4",
data_files="en/c4-train.00001-of-01024.json.gz",
split="train",
).select(range(1024))["text"]
quant_config = GPTQConfig(bits=4, group_size=128)
model = GPTQModel.load(model_id, quant_config)
# 按显存大小调大 batch_size 可以加快量化
model.quantize(calibration_dataset, batch_size=1)
model.save(quant_path)
README 的示例用英文 C4 数据,模型主要处理中文的话,校准数据最好换成中文语料。vLLM 文档里同一个示例用的类名是 QuantizeConfig,README 里是 GPTQConfig,前者仍是通用入口,后者是 GPTQ 专用配置类,以你装的版本为准。GPTQModel 的 GPTQ 检查点,vLLM 可用 Marlin 或 Machete 内核加载,适用 Ampere(A100 起)和 Hopper(H100 起)。
方案三:FP8
RTX 40 系、L40S、H100 上,FP8 往往比 INT4 更省心:权重逐通道 FP8,激活推理时按 token 动态量化,权重量化阶段不需要校准数据,最简单的 RTN 就能保住精度:
python
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier
# model、tokenizer 的加载方式同上,不需要准备校准数据
recipe = QuantizationModifier(targets="Linear", scheme="FP8_DYNAMIC", ignore=["lm_head"])
oneshot(model=model, recipe=recipe)
model.save_pretrained("Qwen2.5-1.5B-Instruct-FP8-Dynamic")
tokenizer.save_pretrained("Qwen2.5-1.5B-Instruct-FP8-Dynamic")
FP8 权重体积约为 bf16 的一半(vLLM 文档说显存需求降低 2 倍),INT4 约四分之一。vLLM 文档称 FP8 对精度影响很小,具体差多少要在自己的模型上测。
方案四:NVIDIA Model Optimizer
Blackwell 显卡或部署目标是 TensorRT-LLM 时,官方推荐用它:
bash
pip install -U "nvidia-modelopt[hf]"
"量化,再导出"两步,校准数据官方说一般 128 到 512 条:
python
import torch
import modelopt.torch.quantization as mtq
from modelopt.torch.export import export_hf_checkpoint
def forward_loop(model):
for batch in calib_set: # 你准备好的校准数据加载器
model(batch)
# 就地替换成量化模块;NVFP4 换成 FP8 或 INT4 AWQ 对应的配置即可
model = mtq.quantize(model, mtq.NVFP4_DEFAULT_CFG, forward_loop)
with torch.inference_mode():
export_hf_checkpoint(model, export_dir)
也可以用官方现成脚本,--quant 可选 fp8、int4_awq、nvfp4 等,--tp 指定张量并行数;README 建议在 TensorRT-LLM 的 Docker 镜像里跑:
bash
scripts/huggingface_example.sh --model $HF_PATH --quant nvfp4 --tp 1
docker pull nvcr.io/nvidia/tensorrt-llm/release:1.2.0
导出的检查点可部署到 TensorRT-LLM、vLLM、SGLang。vLLM 通过 hf_quant_config.json 识别 ModelOpt 产物,官方示例加载时还加了 --quantization modelopt。README 说明 NVFP4 推理需要 Blackwell 显卡和 TensorRT-LLM 1.2 或更新版本;用 vLLM 部署时,非 Blackwell 显卡会退回 W4A16,能跑但没有 FP4 计算的速度。想要更高的 NVFP4 精度,README 建议改用只量化 MLP 层、保留注意力层的 mtq.NVFP4_MLP_ONLY_CFG。
零校准试水:bitsandbytes
不想准备校准数据、也不在乎吞吐的话,transformers 自带的 bitsandbytes 在加载时现场量化,最快。新版也支持 save_pretrained 存下 4 位模型,只是这种格式为省显存设计,不是为高吞吐部署优化的:
python
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained("./Qwen2.5-1.5B-Instruct", quantization_config=bnb, device_map="auto")
量化完怎么验证
llm-compressor 官方示例用 lm_eval 加 vLLM 在 GSM8K 上做精度回归。README 提醒量化模型对 bos 开头符号敏感,而 lm_eval 默认不加 bos,所以要写 add_bos_token=true,否则分数可能偏低。这条来自 Llama 3 的示例,Qwen2.5 的分词器没有定义 bos,对它基本不起作用,写上无妨:
bash
lm_eval --model vllm \
--model_args pretrained="./Qwen2.5-1.5B-Instruct-W4A16-G128",add_bos_token=true \
--tasks gsm8k --num_fewshot 5 --limit 250 --batch_size 'auto'
别只看这一个任务,拿自己业务的评测集对比量化前后,再决定能不能上线。
我的看法
Mac 实测得出的:
- 先用 RTN 当基线。 几秒就好,这个模型上困惑度涨幅 15% 到 18%,能接受就不必多花时间。
- GPTQ 和 AWQ 值得跑,但校准数据要接近真实任务。 一份无关的校准集可能让 GPTQ 的优势消失。
- 别只看困惑度,也别指望速度差异来自方法。 困惑度只能排除明显坏掉的量化;速度收益主要来自权重变小,三种方法差在 4% 以内,选方法看精度和量化成本。
- 量化成本差异巨大。 CPU 上 GPTQ 约一小时、AWQ 估算超过 10 小时,mlx-lm 用 Apple GPU 只要 10 到 14 分钟。只有 Mac 的话优先用 mlx-lm 自带命令。
NVIDIA 上的建议(基于官方资料):
- 先选格式,再选工具。 Ampere 起用 INT4,Ada 和 Hopper 优先试 FP8,Blackwell 看 NVFP4;工具用 llm-compressor、GPTQModel 或 Model Optimizer。
- 较新的方法值得关注,但别盲信官方数字。 那些数字来自论文、官方内部基准或针对某个模型的教程,换成你的模型和任务要自己验证。
结论只来自一个 1.5B 模型、一个评测集、一台机器,不要外推成方法的通用排名,也不要直接套到 70B 这类大模型上,那里的量化噪声通常更小,方法之间的差距也可能不同。