vLLM 中的 LLM Compressor:FP8/INT8/INT4 量化模型的生产部署实战指南
本文围绕 vLLM 官方量化文档入口 LLM Compressor 指南 展开,系统讲解如何用 llm-compressor 库将大模型压缩为 FP8、INT8、INT4、W4A8 等低精度格式,并无缝加载进 vLLM 部署。读完本文,你可以独立完成「加载模型 → 准备校准数据 → 一键量化(oneshot)→ vLLM 推理验证 → lm_eval 精度评估」的完整量化流水线,并理解 vLLM 源码中 compressed-tensors 格式的加载与调度机制。
什么是 LLM Compressor,为什么需要它
现代 LLM 通常包含数十亿乃至数百亿参数,且多以 16/32 位浮点存储,对 GPU 显存要求很高,直接限制了部署选项。量化(Quantization)通过把模型权重与激活的精度降到更小的数据类型,在几乎不损失推理质量的前提下降低显存占用。LLM Compressor 正是 vLLM 量化生态的「制作端」:它负责量化算法、校准数据与格式转换的全部复杂度,最终产出可直接被 vLLM 加载的模型(compressed-tensors 格式)。
按官方文档,使用 LLM Compressor 的三大收益是:
- 更小的内存占用:在更小的 GPU 上运行更大的模型;
- 更低的推理成本:单卡可服务更多并发用户,直接降低生产环境单次查询成本;
- 更快的推理速度:更小的数据类型意味着更低的内存带宽消耗,对内存带宽受限(memory-bound)的工作负载往往能转化为更高吞吐。
其关键特性包括:
- 多种量化算法:支持 AWQ、GPTQ、AutoRound、Round-to-Nearest(RTN),并支持 QuIP、SpinQuant 风格的变换(transform),以及 KV cache 与注意力量化;
- 多种量化方法:支持 FP8、INT8、INT4、NVFP4、MXFP4 以及混合精度量化;
- One-Shot 量化:只需极少的校准数据即可快速完成量化;
- vLLM 深度集成:量化产物使用 compressed-tensors 格式,vLLM 可直接加载部署;
- Hugging Face 兼容:与 Hugging Face Hub 上的模型无缝配合,量化后通过
save_pretrained即可保存为标准 HF 模型目录。
在 vLLM 的量化文档体系中,LLM Compressor 是推荐的量化入门路径:量化总览 在开头就提示读者从这里入手,并给出了四个细分格式的完整实操文档:
硬件支持矩阵:先确认你的平台能跑哪种方案
不同量化格式对硬件计算能力(compute capability)的要求不同,选型前必须先核对平台。量化总览 中的兼容矩阵中,与 LLM Compressor 产物直接相关的行如下:
| 实现 | Volta | Turing | Ampere | Ada | Hopper | AMD GPU | x86 CPU | Arm CPU |
|---|---|---|---|---|---|---|---|---|
| llm-compressor INT8 (W8A8) | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| llm-compressor INT8 (W4A8) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| llm-compressor FP8 (W8A8) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
(Volta 指 SM 7.0,Turing 指 SM 7.5,Ampere 指 SM 8.0/8.6,Ada 指 SM 8.9,Hopper 指 SM 9.0。)
结合各细分文档的说明,可以归纳出几条硬约束:
- FP8 W8A8:NVIDIA GPU 需要 compute capability >= 8.9(Ada/Hopper/Blackwell)才能做 FP8 计算;Turing/Ampere 上 FP8 模型会以权重-only 的 W8A16 模式(Marlin 内核)运行;AMD MI300x 也受支持。FP8 可将模型显存需求降低约 2 倍,吞吐提升最高约 1.6 倍且精度损失极小;
- INT8 W8A8:NVIDIA GPU 需要 compute capability > 7.5(Turing 起),Blackwell(SM >= 10.0)不支持 INT8,应改用 FP8;x86/Arm CPU 也受支持;
- INT4 W4A16:NVIDIA GPU 需要 compute capability > 8.0(Ampere 起),适合低 QPS、低延迟场景;
- INT8 W4A8:主要面向 Arm CPU(文档明确说明在 Arm 平台上通过 KleidiAI 加速),GPU 矩阵中无 ✅。
该矩阵会随 vLLM 演进而变化,最权威的实现清单位于 vllm/model_executor/layers/quantization 目录。
环境准备:双虚拟环境的安装方式
所有量化流程的准备工作一致:量化端和推理端要分开安装、使用不同虚拟环境(官方文档明确提示两者混装可能不兼容):
# 量化环境:安装 llm-compressor
(venv-llm-compressor) pip install llmcompressor
# 推理/评测环境:安装 vllm 与评估框架
(venv-vllm) pip install vllm "lm-eval[api]>=0.4.12"
量化流程总体分为三步或四步:加载模型 →(需要校准数据时)准备校准数据 → 应用量化 → 在 vLLM 中验证精度。下面按四种主流方案分别展开。
FP8 W8A8:无需校准数据的最快路径
FP8(8 位浮点)有两种硬件常见的表示形式,各自适用不同场景:
- E4M3:1 位符号 + 4 位指数 + 3 位尾数,可存储 +/-448 和
nan; - E5M2:1 位符号 + 5 位指数 + 2 位尾数,可存储 +/-57344、+/-
inf和nan,动态范围更大但精度更低。
FP8 的一个显著优点是:用简单的 RTN(Round-to-Nearest)量化就能保持精度,因此完全不需要校准数据。推荐方案是 FP8_DYNAMIC,覆盖所有 Linear 层,它采用:权重侧静态 per-channel 量化,激活侧动态 per-token 量化。
1. 加载模型
使用标准 transformers AutoModel 类加载模型与 tokenizer:
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)
2. 应用 FP8 量化
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier
# 配置简单的 PTQ 量化
recipe = QuantizationModifier(
targets="Linear", # 量化所有 Linear 层
scheme="FP8_DYNAMIC",
ignore=["lm_head"], # 输出头保持原精度
)
# 应用量化算法(无需 dataset 参数)
oneshot(model=model, recipe=recipe)
# 保存模型:Meta-Llama-3-8B-Instruct-FP8-Dynamic
SAVE_DIR = MODEL_ID.split("/")[1] + "-FP8-Dynamic"
model.save_pretrained(SAVE_DIR)
tokenizer.save_pretrained(SAVE_DIR)
3. 在 vLLM 中评估精度
from vllm import LLM
llm = LLM("./Meta-Llama-3-8B-Instruct-FP8-Dynamic")
result = llm.generate("Hello my name is")
print(result[0].outputs[0].text)
再用 lm_eval 做精度评估(例如 250 条 gsm8k 样本):
MODEL=$PWD/Meta-Llama-3-8B-Instruct-FP8-Dynamic
lm_eval \
--model vllm \
--model_args pretrained=$MODEL,add_bos_token=True \
--tasks gsm8k --num_fewshot 5 --batch_size auto --limit 250
官方示例的输出分数:
|Tasks|Version| Filter |n-shot| Metric | |Value| |Stderr|
| --- |------:| -------------- |-----:| --------- | - |----:| - |-----:|
|gsm8k| 3|flexible-extract| 5|exact_match|↑ |0.768|± |0.0268|
| | |strict-match | 5|exact_match|↑ |0.768|± |0.0268|
注意:量化模型对
bostoken 的有无可能很敏感,而lm_eval默认不添加bostoken,评估时务必带上add_bos_token=True。
FP8 的 GEMM 内核选择与排障
vLLM 加载 FP8 模型时会自动挑选 GEMM 内核,并在启动时打印一行 Selected <kernel> for <module> 日志。从文档描述看,块量化(block-quantized)检查点在 CUDA 上的尝试顺序为:FlashInfer/DeepGEMM 混合内核(仅 Hopper)→ DeepGEMM → CUTLASS → Marlin → Triton → Humming → PyTorch 兜底;没有原生 FP8 支持的 GPU(如 Turing/Ampere)会落到权重-only 的 W8A16 Marlin 内核。
排障要点(来自 FP8 指南):
- 如果推理挂起但无报错,可尝试
VLLM_USE_DEEP_GEMM=0或--linear-backend cutlass; --linear-backend只影响量化后的 Linear 层,MoE 专家走独立的--moe-backend;- 显式指定的后端若不被硬件支持会直接报错,而不会静默回退;
- 完整取值列表可在 CLI 参考的
KernelConfig章节查阅,或用vllm serve --help=KernelConfig查看。
此外,vLLM 还支持在线动态 FP8 量化作为替代路径:不产出量化检查点,直接在加载时把 BF16/FP16 模型量化为 FP8(见 在线量化文档):
from vllm import LLM
llm = LLM("facebook/opt-125m", quantization="fp8")
result = llm.generate("Hello, my name is")
该模式下所有 Linear 模块(除 lm_head)的权重量化为 FP8_E4M3(per-tensor scale),激活则每次前向实时统计 min/max 得到动态 per-tensor scale。由于每次前向都要统计激活范围,该模式的延迟改善有限——对性能敏感的场景,更推荐上面的离线 FP8_DYNAMIC 方案。
INT8 W8A8:SmoothQuant + GPTQ 的校准式量化
INT8 W8A8 需要校准数据来估计激活的量化 scale,因此流程多一步「准备校准数据」。
1. 加载模型
与 FP8 流程相同,使用 AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="auto", dtype="auto") 加载。
2. 准备校准数据
量化激活到 INT8 需要样本数据估计激活 scale。校准数据应尽量贴近真实部署数据;通用指令模型可用 ultrachat 这类数据集:
from datasets import load_dataset
NUM_CALIBRATION_SAMPLES = 512
MAX_SEQUENCE_LENGTH = 2048
# 加载并预处理数据集
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)
注意这里使用模型训练时的 chat template 渲染文本——这是官方最佳实践之一。
3. 应用量化(SmoothQuant + GPTQ)
W8A8 的典型 recipe 是先做 SmoothQuant 平滑迁移,再用 GPTQ 量化:
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor.modifiers.smoothquant import SmoothQuantModifier
# 配置量化算法
recipe = [
SmoothQuantModifier(smoothing_strength=0.8),
GPTQModifier(targets="Linear", scheme="W8A8", ignore=["lm_head"]),
]
# 应用量化
oneshot(
model=model,
dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# 保存压缩模型: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)
保存时带上 save_compressed=True,产物为权重/激活均为 8 位整数的 W8A8 模型。
4. 在 vLLM 中加载与评估
from vllm import LLM
llm = LLM("./Meta-Llama-3-8B-Instruct-W8A8-Dynamic-Per-Token")
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'
校准最佳实践
官方文档给出的 W8A8 最佳实践:
- 校准样本从 512 条起步,精度下降再增加;
- 序列长度以 2048 作为起点;
- 使用模型训练时的 chat/instruction template;
- 若模型经过微调,建议用训练数据的一部分做校准。
INT4 W4A16:GPTQ 权重-only 量化与超参数调优
INT4 将权重压到 4 位整数,显著减小模型体积,适合低 QPS、低延迟负载。流程与 W8A8 类似(加载 → 校准数据 → 量化 → 评估),核心区别在 recipe:
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(
model=model,
dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# 保存压缩模型: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)
产物为默认 group_size=128 分组的 W4A16 模型(G128),vLLM 端可通过 LLM("./Meta-Llama-3-8B-Instruct-W4A16-G128") 直接加载,并用与 W8A8 相同的 lm_eval 命令评估。
INT4 指南 还提供了两个关键 GPTQ 超参数的调优说明:
dampening_frac:控制 GPTQ 算法的强度。调低可能提升精度,但过低会引发数值不稳定导致算法失败;actorder:量化权重通道时的排序策略,设为actorder="weight"可在不增加延迟的情况下提升精度。
一个完全展开、可自定义的 recipe 示例(基于 compressed_tensors.quantization 的显式配置组):
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,
)
INT4 的校准最佳实践比 W8A8 多一条:确保校准数据样本多样性充足,防止向特定用例过拟合。
INT8 W4A8:面向 Arm CPU 的极致压缩
W4A8 把权重压到 INT4、激活保持 INT8,是模型体积与推理性能的折中方案,W4A8 指南 明确其目标平台是 Arm CPU(通过 KleidiAI 加速)。该格式提供两种权重粒度:
- Groupwise(分组量化):精度更好;
- Channelwise(按通道):推理性能更好。
Groupwise 版本的 recipe:
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
recipe = [
GPTQModifier(
targets="Linear",
scheme="W4A8",
ignore=["lm_head"],
dampening_frac=0.01,
),
]
oneshot(
model=model,
dataset=ds,
recipe=recipe,
max_seq_length=MAX_SEQUENCE_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)
# 保存压缩模型:Meta-Llama-3-8B-Instruct-W4A8-G128-Dynamic-Per-Token
SAVE_DIR = MODEL_ID.split("/")[1] + "-W4A8-G128-Dynamic-Per-Token"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
Channelwise 版本则通过显式 config_groups 指定权重 strategy=CHANNEL、group_size=None,激活 strategy=TOKEN、dynamic=True:
from llmcompressor.modifiers.quantization import GPTQModifier
from compressed_tensors.quantization import QuantizationStrategy, QuantizationType
scheme = {
"targets": ["Linear"],
"weights": {
"num_bits": 4,
"type": QuantizationType.INT,
"strategy": QuantizationStrategy.CHANNEL,
"symmetric": True,
"dynamic": False,
"group_size": None,
},
"input_activations": {
"num_bits": 8,
"type": QuantizationType.INT,
"strategy": QuantizationStrategy.TOKEN,
"dynamic": True,
"symmetric": False,
"observer": None,
},
"output_activations": None,
}
recipe = [
GPTQModifier(
targets="Linear",
config_groups={"group_0": scheme},
ignore=["lm_head"],
dampening_frac=0.01,
),
]
量化后的模型同样用 LLM("./Meta-Llama-3-8B-Instruct-W4A8-...") 加载并跑 lm_eval(记得 add_bos_token=true)。
vLLM 源码视角:compressed-tensors 模型是如何被加载的
上述所有流程的产物都是同一套东西:save_pretrained 写出的 HF 模型目录中带有 quantization config(compressed-tensors 格式)。vLLM 加载时按 quantization_config 分发到对应的 QuantizationConfig 实现。从源码结构看,LLM Compressor 产物的消费端集中在 vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors.py:
CompressedTensorsConfig继承自QuantizationConfig,get_name()返回"compressed-tensors",这正是 vLLM 识别 LLM Compressor 检查点的标志;- 构造函数接收
target_scheme_map(目标层 → 量化方案的映射)、ignore列表、quant_format、可选的kv_cache_scheme与transform_config(对应文档中提到的 QuIP/SpinQuant 风格变换); get_supported_act_dtypes()声明支持 float32/float16/bfloat16 激活,get_min_capability()返回 70(Volta 起可用,具体内核由各 scheme 进一步限制);- 同一模块导入了完整的 scheme 家族:
CompressedTensorsW8A8Fp8、CompressedTensorsW8A8Int8、CompressedTensorsW8A16Fp8(即低算力 GPU 上 FP8 模型的 weight-only 回退)、CompressedTensorsW4A8Int、CompressedTensorsWNA16、CompressedTensorsWNA4Int/CompressedTensorsWNA8Int(GPTQ/AWQ 风格的分组 INT 权重)以及CompressedTensorsW4A4Fp4/CompressedTensorsW4A4Mxfp4等 4 位浮点方案——与文档宣称的 FP8/INT8/INT4/NVFP4/MXFP4/混合精度能力一一对应; - Linear 层统一走
CompressedTensorsLinearMethod,MoE 模型由 compressed_tensors_moe 下的CompressedTensorsMoEMethod处理。
这套结构也解释了硬件矩阵为何能成立:vLLM 不需要用户指定后端,而是依据检查点中的量化 scheme 与 GPU 能力自动选择内核(如 FP8 的 DeepGEMM/CUTLASS/Marlin/Triton 链条、W8A8 的 int8 scaled_mm 路径等),这也是为什么「量化一次、多处部署」在 vLLM 生态里是可行的。更完整的量化方法注册机制(@register_quantization_config 自定义插件)可参考 量化总览 的 Out-of-Tree Quantization Plugins 一节。
常见问题与支持
- 量化产物对
bostoken 敏感:lm_eval评估务必加add_bos_token=True; - FP8 推理挂起:尝试
VLLM_USE_DEEP_GEMM=0或--linear-backend cutlass; - Blackwell GPU 不支持 INT8:改用 FP8 或换 Hopper/Ada/Ampere 平台;
- vLLM 与 llm-compressor 不要装在同一虚拟环境;
- 遇到其他问题或有功能需求,到 llm-compressor 上游仓库(vllm-project/llm-compressor)提交 issue;更丰富的量化示例(如完整的
quantization_w4a16/llama3_example.py)可在 llm-compressor 仓库的 examples 目录中查阅。
小结
LLM Compressor 在 vLLM 量化体系中扮演「模型压缩工厂」的角色:oneshot 一行调用即可把 HF 模型转为 compressed-tensors 格式的 FP8 W8A8(免校准)、INT8 W8A8(SmoothQuant+GPTQ)、INT4 W4A16(GPTQ 分组)或 INT8 W4A8(Arm CPU)检查点,随后由 vLLM 的 CompressedTensorsConfig 与 scheme 分发机制自动完成内核选择与部署。选型时的决策链是:平台算力(FP8 需 Ada/Hopper/AMD,W4A8 面向 Arm CPU,Blackwell 避开 INT8)→ 是否需要校准(FP8_DYNAMIC 免校准)→ 精度/吞吐权衡(W8A8 均衡、W4A16 省显存、W4A8 极致压缩)。掌握本文的完整流水线与源码映射关系后,你就可以把任意 HF 模型以正确的低精度格式稳定部署到 vLLM 上了。
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