首页
/ vLLM INT4 W4A16 量化实战:llm-compressor 校准压缩到 vLLM 推理评估全流程

vLLM INT4 W4A16 量化实战:llm-compressor 校准压缩到 vLLM 推理评估全流程

2026-09-04 22:19:50作者:明树来

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:量化模型对 bos token 的有无往往敏感(校准数据是否含特殊 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=4type=INTstrategy=GROUPgroup_size=128symmetric=Truedynamic=False。当你需要非对称量化、更大 group size 或调整 GPTQ 行为时,切换到这种 config_groups 显式写法即可。

5. 源码透视:vLLM 如何执行 W4A16 推理

上面产出的是 compressed-tensors 规范 checkpoint。vLLM 侧的加载与执行链路如下,均可在仓库中直接查证。

5.1 配置识别与方案分发

入口在 compressed_tensors.pyCompressedTensorsConfig 解析 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 的多个非标准位宽(uint2b2uint3b4uint4b8uint5b16uint6b32uint7b64uint8b128),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.pycompressed_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.jsonquantization_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 量化前后对比把关精度。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384