首页
/ vLLM INT8 W8A8 量化实战:用 llm-compressor 制作 8 位权重与 8 位激活的推理模型

vLLM INT8 W8A8 量化实战:用 llm-compressor 制作 8 位权重与 8 位激活的推理模型

2026-09-06 15:19:09作者:劳婵绚Shirley

本文以 vLLM 官方量化文档(int8_w8a8.md)为主线,完整讲解如何用 llm-compressor 将 LLM 量化为 INT8 W8A8(8 位权重 + 8 位激活)checkpoint 并在 vLLM 中加载评估,同时结合 vLLM 源码(compressed_tensors_w8a8_int8.py)解析 vLLM 如何识别、加载并执行这类 checkpoint。读完后,你将能够独立完成"量化制作—vLLM 加载—lm-eval 精度验证"的全流程,并理解 vLLM 内部为 W8A8 模型选择的 kernel 与参数布局。

一、INT8 W8A8 是什么,支持哪些硬件

vLLM 支持将权重和激活同时量化到 INT8,用于降低显存占用并加速推理。W8A8 与"仅量化权重"(W8A16)的关键区别在于:激活值也在 8 位整数域内完成矩阵乘法(INT8 GEMM),因此在内存带宽受限的负载下往往能获得更高的吞吐,代价是需要校准数据来估计激活的量化范围。

文档明确了硬件适用前提,部署前必须确认:

  • 支持:计算能力 > 7.5 的 NVIDIA GPU,即 Turing、Ampere、Ada Lovelace、Hopper 架构;
  • 不支持(Blackwell 限制):计算能力 >= 10.0 的 GPU(如 RTX 6000 Blackwell)不支持 INT8 量化,应改用 FP8 量化,或在 Hopper/Ada/Ampere 架构上运行。

这一点在 vLLM 源码中可以直接印证。W8A8 INT8 的量化方案类 CompressedTensorsW8A8Int8 通过 get_min_capability() 声明了最低硬件要求:

@classmethod
def get_min_capability(cls) -> int:
    # turing and up
    return 75

返回的 75 即 Compute Capability 7.5(Turing),与文档中"compute capability > 7.5"的支持说明一致。在加载 checkpoint 时,vLLM 会在 compressed_tensors.pyget_scheme 末尾调用 _check_scheme_supported(scheme.get_min_capability()),若当前设备不满足方案要求会直接报错——这就是在 Blackwell 上加载 W8A8 模型会失败/回退的机制来源之一(Blackwell 的 CC 为 10.x,INT8 方案不被选择,故文档建议直接使用 FP8)。

二、环境准备:vLLM 与 llm-compressor 分环境部署

量化制作依赖 llm-compressor 库,该库提供 GPTQ、AWQ、AutoRound 等量化算法以及 SmoothQuant 之类的迁移变换,输出 compressed-tensors 格式供 vLLM 直接加载。文档建议安装两个互相独立的环境:

# 量化制作环境
(venv-llm-compressor) pip install llmcompressor

# vLLM 推理与评估环境
(venv-vllm) pip install vllm "lm-eval[api]>=0.4.12"

文档特别强调:请为 vLLM 和 llm-compressor 使用独立的 Python 环境,两者直接混装在同一个环境中可能无法协同工作(依赖版本冲突)。这个建议在实操中非常关键:量化是一次性离线流程,推理是线上常驻流程,两者解耦后各自升级互不影响。

三、量化流程四步走

文档将量化流程拆分为四个主要步骤:加载模型、准备校准数据、应用量化算法、在 vLLM 中评估精度。下面按顺序完整展开(以 Llama-3-8B-Instruct 为例,代码与文档一致,可直接复制运行)。

3.1 第一步:加载模型

使用标准的 transformers AutoModel 类加载模型与分词器:

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" 会把大模型自动切分到多卡;dtype="auto" 让 transformers 从模型配置中读取原始精度(通常为 bfloat16),保证量化前的参考状态与发布模型一致。

3.2 第二步:准备校准数据

激活量化到 INT8 时,必须用样本数据估计激活的量化尺度(scale)。文档的准则是:校准数据应尽可能贴近你的实际部署数据。对于通用指令微调模型,可以使用 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)

参数要点:

  • NUM_CALIBRATION_SAMPLES = 512:文档最佳实践建议的起步校准样本数,若精度下降可继续增加;
  • MAX_SEQUENCE_LENGTH = 2048:起步序列长度,同样可按场景调整;
  • apply_chat_template:使用模型训练所用的 chat template 组装文本,避免校准分布偏离真实推理输入(文档最佳实践之一);
  • truncation=True, add_special_tokens=False:截断超长样本且不重复添加特殊 token。

如果模型经过领域微调,文档建议改用一份训练数据的采样作为校准集,让激活统计更贴合目标分布。

3.3 第三步:应用量化算法(SmoothQuant + GPTQ)

llm-compressor 通过 oneshot 一次性执行 recipe 中的多个 Modifier。W8A8 的推荐组合是:

from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor.modifiers.smoothquant import SmoothQuantModifier

# Configure the quantization algorithms
recipe = [
    SmoothQuantModifier(smoothing_strength=0.8),
    GPTQModifier(targets="Linear", scheme="W8A8", 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-W8A8-Dynamic-Per-Token
SAVE_DIR = MODEL_ID.split("/")[1] + "-W8A8-Dynamic-Per-Token"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)

各配置项的含义:

  • SmoothQuantModifier(smoothing_strength=0.8):SmoothQuant 迁移变换,把激活中难以量化的"离群通道"难度部分转移给权重,使两者都更容易用 8 位表示。smoothing_strength 控制迁移强度;
  • GPTQModifier(targets="Linear", scheme="W8A8", ignore=["lm_head"]):以 GPTQ 算法对全部 Linear 层做 W8A8 量化;ignore=["lm_head"] 保持词表投影层不量化(输出层对生成质量敏感,通常保留高精度);
  • scheme="W8A8" 产出的激活量化方式是动态逐 token(dynamic per-token),因此文档将保存目录命名为 ...-W8A8-Dynamic-Per-Token
  • save_compressed=True:以 compressed-tensors 格式序列化,这是 vLLM 能够自动识别并选择 W8A8 方案的 checkpoint 格式。

量化完成后,得到权重与激活均为 8 位整数的 W8A8 模型,体积约为 BF16 原始模型的一半。

3.4 第四步:在 vLLM 中加载并评估精度

量化后的模型可以直接用 LLM 类加载,无需指定量化参数——vLLM 会从 checkpoint 的量化配置中自动推导方案:

from vllm import LLM

llm = LLM("./Meta-Llama-3-8B-Instruct-W8A8-Dynamic-Per-Token")

随后用 lm_eval 做精度评估(以 GSM8K 五样例为文档示例):

lm_eval --model vllm \
  --model_args pretrained="./Meta-Llama-3-8B-Instruct-W8A8-Dynamic-Per-Token",add_bos_token=true \
  --tasks gsm8k \
  --num_fewshot 5 \
  --limit 250 \
  --batch_size 'auto'

!!! 注意:add_bos_token 参数 !!!

文档特别提醒:量化模型对 bos token 的有无可能敏感,运行评估时务必带上 add_bos_token=true(即 add_bos_token=True),否则分数可能异常波动。这是 W8A8 评估中一个容易被忽略的坑。

四、源码级解析:vLLM 如何识别和执行 W8A8 checkpoint

vLLM 通过 compressed-tensors 量化框架处理 llm-compressor 的产物。理解加载链路有助于排查"为什么我的模型走了 W8A8 方案"或"为什么加载失败"。

4.1 方案判定:静态 per-tensor 还是动态 per-token

checkpoint 的 quantization_config 中记录了权重与激活各自的位宽、策略(tensor/channel/token)、动态性等信息。vLLM 在 compressed_tensors.py 中按两类条件分派到 W8A8 INT8 方案:

if self._is_static_tensor_w8a8(weight_quant, input_quant):
    return CompressedTensorsW8A8Int8(
        strategy=weight_quant.strategy,
        is_static_input_scheme=True,
        input_symmetric=input_quant.symmetric,
    )

if self._is_dynamic_token_w8a8(weight_quant, input_quant):
    return CompressedTensorsW8A8Int8(
        strategy=weight_quant.strategy,
        is_static_input_scheme=False,
        input_symmetric=input_quant.symmetric,
    )

两个判定函数给出了精确的方案边界(见 _is_static_tensor_w8a8 / _is_dynamic_token_w8a8):

判定条件 静态 per-tensor 变体 动态 per-token 变体
位宽 权重 8 位、激活 8 位 权重 8 位、激活 8 位
权重策略 tensor 或 channel tensor 或 channel
激活策略 tensor(静态全局 scale) token(逐 token 动态)
动态性 权重与激活均静态 权重静态、激活动态
对称性 权重必须对称;激活对称/非对称均可 权重必须对称;激活对称/非对称均可

本文 3.3 节由 GPTQModifier(scheme="W8A8") 产出的 W8A8-Dynamic-Per-Token 模型,走的正是第二行"动态 per-token"分支,且权重策略通常为 channel。

4.2 权重布局与 kernel 选择

CompressedTensorsW8A8Int8.create_weights 为每个被量化的线性层注册三类参数,这也是 compressed-tensors checkpoint 中张量组织的对应关系:

  • weighttorch.int8 张量,形状 [out_features, in_features],按 TP 分片维度为 output 维;
  • weight_scale:float32。channel 策略下形状为 [out_features, 1](每行权重一个尺度,用 ChannelQuantScaleParameter 承载);tensor 策略下是逐层单值(PerTensorScaleParameter);
  • input_scale / input_zero_point:仅在静态输入方案(is_static_input_scheme=True)时注册。若激活量化非对称,zero point 以 int8 存储(加载后按 int32 使用,即 azp)。

kernel 的选择在同一步完成:

self.kernel = init_int8_linear_kernel(
    is_channelwise=(self.strategy == QuantizationStrategy.CHANNEL),
    is_static_input_scheme=self.is_static_input_scheme,
    input_symmetric=self.input_symmetric,
    module_name=self.__class__.__name__,
)

从源码结构看,vLLM 依据"channel 还是 tensor 权重尺度 × 静态还是动态激活 × 激活是否对称"三个二元组合,在加载完成后由 self.kernel.process_weights_after_loading(layer) 把 checkpoint 的序列化形态重排(repack)成 kernel 期望的格式,前向时统一走 self.kernel.apply_weights(layer, x, bias) 完成 INT8 反量化矩阵乘。这意味着量化侧(llm-compressor 产物)与推理侧(kernel)之间由 compressed-tensors 配置契约解耦:只要配置落在上述判定边界内,vLLM 即可自动选路。

4.3 未匹配时的行为

若某层的量化参数组合落不到任何受支持的方案上,get_scheme_from_parts 会抛出 NotImplementedError,并打印该层的 layer_name、权重/激活/输出量化参数与 format,方便定位是哪一层、哪种配置不被支持;若 checkpoint 某层没有任何权重量化配置,则回退到未量化的线性方法(UnquantizedLinearMethod),与 ignore=["lm_head"] 的行为一致。

五、最佳实践

文档给出的最佳实践汇总如下,建议在制作自己的 W8A8 checkpoint 时逐条对照:

  • 校准数据从 512 条样本起步,若精度下降则增加样本量;
  • 校准序列长度从 2048 起步,按目标上下文长度调整;
  • 使用模型训练时所采用的 chat/instruction template 构造校准文本;
  • 若模型经过微调,考虑取训练数据的一个采样作为校准集,使激活统计与部署分布对齐。

此外,结合本文源码部分可以补充两条工程实践:量化时固定忽略 lm_head 等对精度敏感的输出层;评估时始终显式指定 add_bos_token=true 以消除 bos 敏感性带来的分数偏差。

六、故障排查

遇到问题时可按以下路径排查:

  1. Blackwell 上无法使用 INT8:CC >= 10.0 的 GPU 不支持该方案,改用 FP8 量化(见 fp8.md)或换用 Hopper/Ada/Ampere;
  2. 加载时报 "No compressed-tensors compatible scheme":用报错中打印的 weight_quant/input_quant 字段对照 4.1 节的判定边界,检查位宽、策略与动态性是否落在受支持组合内;
  3. 量化环境与推理环境冲突:回到 3.1 节前提,确认 llm-compressor 与 vLLM 分环境部署;
  4. 更复杂的量化问题:文档指引到 vllm-project 的 llm-compressor 仓库 issue 区提交问题或功能请求。

参考文件

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