亲手量化大模型,Mac实测和NVIDIA指南

先说明可信度: 第一部分 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 显卡。真正动手会遇到的问题:

  1. RTN、GPTQ、AWQ 到底差多少,值不值得多花时间
  2. 校准数据怎么选,踩坑怎么办
  3. 只有 Mac 能不能做真实的量化,速度是不是真的变快
  4. 手里是 NVIDIA 显卡,该选什么格式、什么工具、怎么部署
  5. 新出的方法(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 实测得出的:

  1. 先用 RTN 当基线。 几秒就好,这个模型上困惑度涨幅 15% 到 18%,能接受就不必多花时间。
  2. GPTQ 和 AWQ 值得跑,但校准数据要接近真实任务。 一份无关的校准集可能让 GPTQ 的优势消失。
  3. 别只看困惑度,也别指望速度差异来自方法。 困惑度只能排除明显坏掉的量化;速度收益主要来自权重变小,三种方法差在 4% 以内,选方法看精度和量化成本。
  4. 量化成本差异巨大。 CPU 上 GPTQ 约一小时、AWQ 估算超过 10 小时,mlx-lm 用 Apple GPU 只要 10 到 14 分钟。只有 Mac 的话优先用 mlx-lm 自带命令。

NVIDIA 上的建议(基于官方资料):

  1. 先选格式,再选工具。 Ampere 起用 INT4,Ada 和 Hopper 优先试 FP8,Blackwell 看 NVFP4;工具用 llm-compressor、GPTQModel 或 Model Optimizer。
  2. 较新的方法值得关注,但别盲信官方数字。 那些数字来自论文、官方内部基准或针对某个模型的教程,换成你的模型和任务要自己验证。

结论只来自一个 1.5B 模型、一个评测集、一台机器,不要外推成方法的通用排名,也不要直接套到 70B 这类大模型上,那里的量化噪声通常更小,方法之间的差距也可能不同。

相关推荐
成旭先生1 小时前
AI 文本审核 API:一段中文文本判风险等级、命中标签与处置建议
java·前端·人工智能·api接口·内容风控·文本审核·ugc审核
草上飞95271 小时前
把前沿能力装进便宜产物,本身是多数模型还不会的能力
人工智能·深度学习·llm
林澈在路上1 小时前
2026生成后可发行的AI音乐工具怎么选
人工智能·版权·ai音乐·音乐发行·melo音乐
LoneEon1 小时前
CentOS7 部署 Nacos3.x 集群实战:从注册中心到 AI 管理中心
linux·人工智能·nacos
跟我学机器学习2 小时前
Qwen3 Reranking原理与使用:重排模型在 RAG 中的作用
人工智能·深度学习·transformer
袁俪2 小时前
数字孪生驱动大模型
人工智能
Bazingga2 小时前
从0到1搭一个Agent:Spring AI显式ReAct循环完整实战
后端
江润舟2 小时前
万物|炼器:从零手搓工业级旋转目标检测网络·卷4 —— 模块化重构(一)
人工智能·深度学习
LEE2 小时前
前端转型全栈 05:SQL 与迁移,AI 写的 SQL 怎么安全上线
前端·后端·ai编程