vLLM INT4 W4A16 量化实战:llm-compressor 校准压缩到 vLLM 推理评估全流程
vLLM 支持将模型权重压缩为 INT4 位宽(W4A16:4-bit 权重 + 16-bit 激活),以显著降低显存占用与模型体积,尤其适合低 QPS 但对延迟敏感的服务场景。本文完整覆盖从环境准备、校准数据构造、llm-compressor 一键量化,到 vLLM 加载验证与 lm_eval 精度评估的全流程,并结合 vLLM 源码深入解析 W4A16 量化模型在 vLLM 运行时中是如何被识别、反量化并经 Marlin 内核加速的,帮助你不仅"会量化",还能理解其底层机制与调优抓手。
1. INT4 W4A16 量化概述
W4A16 属于 weight-only 量化:权重以 4-bit 整数存储(相比 FP16 约节省 4 倍权重显存),前向计算时权重先反量化到 16-bit 再参与矩阵乘。这类格式在低 QPS 工作负载中收益明显——推理瓶颈往往在权重搬运而非算力,减小权重体积即可降低带宽压力并维持低延迟。
硬件前提(来自官方文档的明确约束):
- INT4 计算支持 NVIDIA GPU 计算能力 > 8.0 的架构,即 Ampere(SM 8.0/8.6)、Ada Lovelace(SM 8.9)、Hopper(SM 9.0)、Blackwell;
- 在 vLLM 整体量化支持矩阵中(见 量化总览),Marlin 系列内核(覆盖 GPTQ/AWQ 等 INT4 权重路径)自 Turing 起可用,但本文档针对 llm-compressor 产出的 W4A16 checkpoint,以"计算能力 > 8.0"为准。
vLLM 侧消费的是 llm-compressor(基于 compressed-tensors 规范)保存的量化 checkpoint,config.json 中的量化元数据会被自动识别,无需在 LLM(...) 中额外指定 quantization 参数。更多压缩格式可参考 llm-compressor 概览(其支持 FP8、INT8、INT4 等多种方案)以及同目录的 FP8 W8A8 指南。
2. 环境准备
量化与服务部署应使用相互独立的虚拟环境,因为 llm-compressor 与 vLLM 的依赖组合不一定兼容:
# 量化环境
(venv-llm-compressor) pip install llmcompressor
# vLLM 推理 + 评估环境
(venv-vllm) pip install vllm "lm-eval[api]>=0.4.12"
要点:
llmcompressor:负责模型加载、校准前向与 GPTQ 量化改写,产出自包含的压缩 checkpoint;vllm:加载并推理量化模型;lm-evaluation-harness >= 0.4.12:用于量化前后精度回归验证,--model vllm后端会调用 vLLM 引擎批量打分。
3. 量化流程四步走
整体流程分为四步:加载模型、准备校准数据、应用量化、在 vLLM 中评估精度。
3.1 第一步:加载模型
使用标准 transformers 自动类加载权重与分词器:
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
device_map="auto",
dtype="auto",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
device_map="auto"让 accelerate 自动切分大模型到多卡;dtype="auto"尊重 checkpoint 的torch_dtype声明,避免精度意外降级。
3.2 第二步:准备校准数据
W4A16 需要样本数据来估计权重更新与校准 scale(GPTQ 通过观测激活的统计信息决定逐通道量化误差的补偿量)。经验法则:校准数据越贴近部署时的真实流量,量化后精度损失越小。对通用指令微调模型,官方示例采用 ultrachat 数据:
from datasets import load_dataset
NUM_CALIBRATION_SAMPLES = 512
MAX_SEQUENCE_LENGTH = 2048
# Load and preprocess the dataset
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft")
ds = ds.shuffle(seed=42).select(range(NUM_CALIBRATION_SAMPLES))
def preprocess(example):
return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False)}
ds = ds.map(preprocess)
def tokenize(sample):
return tokenizer(sample["text"], padding=False, max_length=MAX_SEQUENCE_LENGTH, truncation=True, add_special_tokens=False)
ds = ds.map(tokenize, remove_columns=ds.column_names)
关键细节:
- 先
shuffle(seed=42)再select,保证校准样本在数据集内均匀分布,避免头部样本偏置; - 用
apply_chat_template把多轮messages渲染成模型训练时见过的完整对话文本,而不是裸拼接; truncation=True防止超长样本污染校准批次,add_special_tokens=False避免重复插入模板自带的特殊 token。
3.3 第三步:应用量化并保存
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor.modifiers.smoothquant import SmoothQuantModifier
# Configure the quantization algorithms
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
# Apply quantization
oneshot(
model=model,
dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# Save the compressed model: Meta-Llama-3-8B-Instruct-W4A16-G128
SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A16-G128"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
参数解读:
| 参数 | 含义 |
|---|---|
targets="Linear" |
只量化 nn.Linear 类型的层(注意 MoE 专家权重也是 Linear 子类,默认会一并压缩) |
scheme="W4A16" |
4-bit 对称整数权重 + 16-bit 激活的快捷写法,等价于 GROUP 策略、group_size=128 的完整配置 |
ignore=["lm_head"] |
输出投影层不量化,避免词表映射处误差被放大 |
max_seq_length / num_calibration_samples |
校准前向时的截断长度与样本数,须与数据预处理阶段一致 |
save_compressed=True |
保存时把 4-bit 打包权重(packed weights)连同 scale 等元数据一起写入 checkpoint,vLLM 直接按压缩格式加载,无需二次转换 |
保存目录名中的 G128 表示 group size 128:每 128 个权重共享一组 scale,是精度与压缩率之间的常用折中。
3.4 第四步:在 vLLM 中加载并评估精度
量化完成后,用 vLLM 直接加载本地压缩 checkpoint(量化配置会从 config.json 自动识别):
from vllm import LLM
llm = LLM("./Meta-Llama-3-8B-Instruct-W4A16-G128")
再用 lm_eval 做精度回归。以 GSM8K 为例:
lm_eval --model vllm \
--model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A16-G128",add_bos_token=true \
--tasks gsm8k \
--num_fewshot 5 \
--limit 250 \
--batch_size 'auto'
--batch_size 'auto'让 harness 自适应选择吞吐最优的批大小;--limit 250便于快速抽查,正式验收时去掉该参数跑全量;- 务必携带
add_bos_token=true:量化模型对bostoken 的有无往往敏感(校准数据是否含特殊 token 会影响 scale 估计),评估时与训练/校准侧的分词约定保持一致,否则会出现难以归因的精度偏差。
建议将基座模型的原始精度分数一并跑出,量化后做逐任务对比,确认损失在业务可接受范围内再上线。
4. 最佳实践与超参调优
官方文档给出的经验清单:
- 校准数据从 512 条起步,精度不达标时逐步增加;
- 校准集要有足够多样性,避免过拟合到某一类用法;
- 序列长度从 2048 起步;
- 使用模型训练时的 chat / instruction 模板渲染校准文本;
- 若模型经过微调,优先取训练数据的子集做校准;
- 调优量化算法的核心超参:
dampening_frac:控制 GPTQ 正则化的强度。取值更低通常精度更好,但过小可能引入数值不稳定导致算法发散失败;actorder:决定权重通道被量化的顺序。按激活重要性排序(如actorder="weight")可以在不增加运行时延迟的前提下改善精度。
一个可按需扩展的完整 recipe 示例(显式声明 GROUP 策略与分组量化细节):
from compressed_tensors.quantization import (
QuantizationArgs,
QuantizationScheme,
QuantizationStrategy,
QuantizationType,
)
recipe = GPTQModifier(
targets="Linear",
config_groups={
"config_group": QuantizationScheme(
targets=["Linear"],
weights=QuantizationArgs(
num_bits=4,
type=QuantizationType.INT,
strategy=QuantizationStrategy.GROUP,
group_size=128,
symmetric=True,
dynamic=False,
actorder="weight",
),
),
},
ignore=["lm_head"],
update_size=NUM_CALIBRATION_SAMPLES,
dampening_frac=0.01,
)
对照快捷写法可以读出 scheme="W4A16" 的默认展开:num_bits=4、type=INT、strategy=GROUP、group_size=128、symmetric=True、dynamic=False。当你需要非对称量化、更大 group size 或调整 GPTQ 行为时,切换到这种 config_groups 显式写法即可。
5. 源码透视:vLLM 如何执行 W4A16 推理
上面产出的是 compressed-tensors 规范 checkpoint。vLLM 侧的加载与执行链路如下,均可在仓库中直接查证。
5.1 配置识别与方案分发
入口在 compressed_tensors.py:CompressedTensorsConfig 解析 config.json 中的 quantization_config,逐组判断权重/激活的位宽与策略后分发到具体 scheme。对标准 INT4 权重-only 配置(非 MXFP4/NVFP4 等特殊路径),最终走 CompressedTensorsWNA16 分支(该文件约第 801 行的分发点)——源码注释也明确说明"标准 4/8-bit 权重-only(无输出激活 scale)仍落入 WNA16"。
5.2 CompressedTensorsWNA16 方案实现
实现位于 compressed_tensors_wNa16.py,几个值得注意的实现细节:
- 位宽打包:
pack_factor = Fraction(32, num_bits),4-bit 时一个 32-bit 整数打包 8 个权重,对应 vLLM 的PackedvLLMParameter,保证按32/4的粒度做张量并行切分; - 支持的位宽:从源码结构看,
WNA16_SUPPORTED_TYPES_MAP覆盖 2~8 bit 的多个非标准位宽(uint2b2、uint3b4、uint4b8、uint5b16、uint6b32、uint7b64、uint8b128),W4A16 对应scalar_types.uint4b8;非对称零点是 4/8 bit 支持的额外能力; - actorder 落地:
self.has_g_idx = actorder == ActivationOrdering.GROUP,即第 4 节 recipe 里的actorder="weight"在运行时体现为额外的通道排序索引g_idx,由 Marlin 内核按序反量化; - 约束校验:pack 量化必须使用 group 或 channel 策略(
group_size == -1且非 channel 会直接报错),这与 llm-compressor 侧strategy=GROUP的配置形成呼应。
5.3 Marlin 内核执行
该方案通过 kernels/linear 中的 choose_mp_linear_kernel 选择混精度线性内核,W4A16 的典型执行路径是 MarlinLinearKernel:激活保持 16-bit,4-bit 打包权重 + group scale 直接在 Marlin GEMM 内核内解包参与运算。这也解释了文档中"INT4 计算要求计算能力 > 8.0"的来源——Marlin 依赖 Ampere 及以上架构的指令特性。
5.4 MoE 模型
如果量化对象是 MoE 架构,压缩后的专家权重由 compressed_tensors_moe 下的方案处理(按激活位宽分发到 WNA16/FP8 等内核,如 compressed_tensors_moe_wna16_rdna3.py、compressed_tensors_moe_w4a16_flydsl.py 等平台实现)。llm-compressor 的 targets="Linear" 同样会覆盖专家投影,评估 MoE 模型时同样建议对比量化前后基准分数。
6. 故障排查
- 精度异常偏低:优先检查
add_bos_token是否与校准/训练侧一致、lm_head是否已忽略、校准数据是否与目标负载分布相符;其次考虑增大NUM_CALIBRATION_SAMPLES或降低dampening_frac; - 量化过程数值不稳定/报错:调大
dampening_frac; - vLLM 加载失败:确认
save_compressed=True保存、checkpoint 的config.json中quantization_config完整、GPU 计算能力 > 8.0; - 遇到问题或提交功能需求,可到 llm-compressor 项目的 issue 区反馈;完整的 W4A16 示例脚本也在 llm-compressor 仓库的 examples 目录(
quantization_w4a16/llama3_example.py)中可查阅。
小结
| 环节 | 工具/组件 | 仓库内可查证位置 |
|---|---|---|
| 量化产出 | llm-compressor oneshot + GPTQModifier(W4A16) |
本文第 3.3 节完整 recipe |
| 格式规范 | compressed-tensors(quantization_config) |
compressed_tensors.py |
| 运行时方案 | CompressedTensorsWNA16(2~8 bit,GROUP/CHANNEL,actorder) |
compressed_tensors_wNa16.py |
| 加速内核 | Marlin(MarlinLinearKernel) |
kernels/linear |
| 精度验收 | lm_eval --model vllm |
本文第 3.4 节命令 |
掌握本文流程后,你可以针对任意指令微调 LLM 完成一条可复现的"压缩 → 服务 → 验证"链路:用贴近业务的校准数据跑 llm-compressor,产出 W4A16-G128 checkpoint 后直接交给 vLLM 加载,并用 lm-eval 量化前后对比把关精度。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00