首页
/ vLLM 在线量化(Online Quantization)实战指南:加载期量化 Linear 与 MoE 权重

vLLM 在线量化(Online Quantization)实战指南:加载期量化 Linear 与 MoE 权重

2026-09-07 09:17:38作者:宣利权Counsellor

导读

vLLM 的在线量化(Online Quantization)允许直接加载一个 BF16/FP16 的原始模型,并在模型加载阶段将其 Linear 与 MoE(专家混合)层权重转换为 FP8 / MXFP4 等低精度格式,全程无需预先量化的 checkpoint,也无需校准数据集。整个过程分为两段:权重在加载时完成一次性转换,激活值则在每次前向传播时动态计算缩放因子。本文基于 在线量化官方文档 与 vLLM 仓库源码,完整讲解其支持的量化方案、quantization_config 高级配置语法、Dense 与 MoE 分层量化、逐层排除与逐层覆盖等能力,并深入到 vllm/config/quantization.pyvllm/model_executor/layers/quantization/online 的实现细节,让读者既能直接上手,也能理解背后的解析与派发机制。

在线量化是什么

传统量化流程通常是离线的:先用校准数据(calibration data)统计数值范围,产出预量化 checkpoint,再在推理时加载。在线量化把"量化"这一动作搬进了模型加载流程:

  • 权重:BF16/FP16 权重在加载时被转换为更低精度(例如 FP8 的 e4m3),缩放因子基于权重自身的最大值一次性计算;
  • 激活:不依赖校准统计,每个 forward pass 都依据实际输入张量的数值范围动态求取缩放因子。

该设计有两类典型收益:一是省去了 pre-quantized checkpoint 与校准管线;二是能够对已经发布但只有原始权重、且当前没有对应量化权重发布的模型立刻做低精度推理。

从源码看,在线量化是一整套独立的量化方法族。模型级入口是 OnlineQuantizationConfig,位于 vllm/model_executor/layers/quantization/online/base.py,其 get_name() 返回 "online",并且 get_config_filenames() 返回空列表——这正说明它不是从 checkpoint 的量化配置文件中恢复出来的,而是完全由运行期传入的 quantizationquantization_config 参数驱动。在线线性方法基类 OnlineLinearBase 还把权重放到 meta device 上延迟物化(uses_meta_device: bool = True,见 fp8.py),并在权重物化过程中完成在线量化,这也是"不需要预量化 checkpoint"能够成立的关键工程实现。

Quick Start:两种入口立即上手

在线量化最简单的用法是给 quantization 参数传入一个方案名(shorthand)

Python API

from vllm import LLM

# Per-tensor FP8 量化(每个权重张量一个缩放因子)
llm = LLM("meta-llama/Llama-3.1-8B", quantization="fp8_per_tensor")

# Per-block FP8 量化(权重按 128x128 block 缩放,激活按 1x128 block 缩放)
llm = LLM("meta-llama/Llama-3.1-8B", quantization="fp8_per_block")

# 权重与激活均为 MXFP8
llm = LLM("meta-llama/Llama-3.1-8B", quantization="mxfp8")

# MXFP4 权重;激活是否量化取决于所选 linear_backend
llm = LLM("meta-llama/Llama-3.1-8B", quantization="mxfp4")

# MXFP4 仅作用于 MoE 层的权重与激活(Dense 层保持原 dtype)
llm = LLM(
    "Qwen/Qwen3.5-35B-A3B",
    quantization="mxfp4",
    quantization_config={"linear": {"activation": None, "weight": None}}
)

CLI

vllm serve meta-llama/Llama-3.1-8B --quantization fp8_per_tensor
vllm serve meta-llama/Llama-3.1-8B --quantization fp8_per_block
vllm serve meta-llama/Llama-3.1-8B --quantization mxfp8
vllm serve meta-llama/Llama-3.1-8B --quantization mxfp4

vllm serve Qwen/Qwen3.5-35B-A3B --quantization mxfp4 \
    --quantization-config '{"linear":{"activation":null,"weight":null}}'

最后一个例子展示了一个重要细节:对于 35B-A3B 这类 Dense+MoE 混合架构,mxfp4 shorthand 默认同时作用于 Linear 与 MoE;如果只想量化 MoE 专家(通常是推理与显存热点),就把 linear 的 weight/activation 都显式置为 null

支持的量化方案总览

方案 权重配方 激活配方 备注
fp8_per_tensor fp8_e4m3 数据,fp32 每张量缩放 fp8_e4m3 数据,fp32 每张量缩放 部分 GPU(Ada、Hopper)上线性层激活改用 per-token 缩放以提升性能
fp8_per_block fp8_e4m3 数据,fp32 按 128x128 block 缩放 fp8_e4m3 数据,fp32 按 1x128 block 缩放
mxfp8 fp8_e4m3 数据,e8m0 按 1x32 block 缩放 fp8_e4m3 数据,e8m0 按 1x32 block 缩放 w8a8 形态需要 SM 100+(Blackwell 或更新);其余 GPU 走 w8a16 回退
mxfp4 fp4_e2m1 数据,e8m0 按 1x32 block 缩放(遵循 OCP Microscaling 格式规范) Linear:部分后端为 fp4_e2m1 + e8m0 按 1x32 block 缩放,其余为 BF16;MoE:fp4_e2m1 + e8m0 按 1x32 block 缩放 Linear 的 MXFP4 后端按平台自动选择,并不强制激活 dtype,部分后端使用 BF16 激活;可通过 --linear-backend 固定,如 --linear-backend flashinfer

需要说明两点:

  1. mxfp8 中两种格式皆为 OCP MX(Microscaling)格式。在仓库实现里,mxfp8 对应动态量化键 kMxfp8Dynamicquant_utils.py),实现在 online/mxfp8.py;MXFP8 线性方法支持动态缩放激活、逐 block e8m0 缩放权重。
  2. mxfp4 方案对应静态权重键 kMxfp4Staticquant_utils.py),实现位于 online/mxfp4.py。MXFP4 是 4 bit 格式,权重以 FP4(e2m1)存储并配上块共享的 8 bit e8m0 缩放指数,相比纯 FP8 更进一步压缩权重体积。

除上述 shorthand 外,--quantization 还接受 fp8_per_channelint8_per_channel_weight_onlynvfp4_per_token 以及字面量 online。见 vllm/config/quantization.pyONLINE_QUANT_SHORTHAND_NAMES 的定义。

quantization_config:细粒度高级配置

要获得精确控制,应使用 quantization_config 字典。

Schema 结构

quantization_config:
  linear:
    weight: <name>      # 取值见 QUANT_KEY_NAMES(vllm/config/quantization.py)
    activation: <name>
  moe:
    weight: <name>
    activation: <name>
  ignore: [<layer-name-or-regex-or-fnmatch-pattern>, ...]

该结构的 Python 端定义是 QuantizationConfigArgsvllm/config/quantization.py),其中:

  • linear:应用到所有 LinearBase 层(Dense GEMM);
  • moe:应用到 RoutedExperts(MoE 专家层,对应 FusedMoEFactory 体系);
  • ignore:跳过量化的层名单;
  • targets:按层精细指定不同方案(见下文)。

linearmoe 的每个字段都接受一个 QuantSpecvllm/config/quantization.py),即 {weight, activation} 的完整字典,其中字段值既可以是对应权重方案的 QuantKey 名称,也允许直接置为 None(表示该侧不量化、回退到方法类自身默认值)。

权重方案名表(QUANT_KEY_NAMES)

文档中 weight/activation 可填的名字由 vllm/config/quantization.pyQUANT_KEY_NAMES 定义:

名称 含义
fp8_per_tensor_static FP8 e4m3,fp32 每张量静态缩放
fp8_per_tensor_dynamic FP8 e4m3,fp32 每张量动态缩放
fp8_per_token FP8 e4m3,fp32 每 token 动态缩放
fp8_per_channel_static FP8 e4m3,fp32 每输出通道静态缩放
fp8_per_block_static FP8 e4m3,fp32 按 128x128 block 静态缩放
fp8_per_block_dynamic FP8 e4m3,fp32 按 block 动态缩放
mxfp8 e8m0 缩放,MXFP8 动态
mxfp4 e8m0 缩放,MXFP4 动态
int8_per_channel_static INT8 每通道静态缩放

传入非法名称时,解析器会抛出错误并列出合法候选项(_coerce_quant_keyvllm/config/quantization.py)。

字符串简写解析规则

linearmoe 除了接受完整的 {weight, activation} 字典,也接受裸字符串。裸字符串的解析分两步(见 _coerce_specvllm/config/quantization.py):

  1. 先与 --quantization 的 shorthand 对照——若命中(如 "fp8_per_block"),取出该 shorthand 在当前层种类(linear 或 moe)槽位上的默认配置;
  2. 若未命中 shorthand,再按 QUANT_KEY_NAMES 解析为权重方案名(如 "fp8_per_block_static")。

未显式设置的字段回退规则:优先取 --quantization shorthand 的默认值;对本身已量化的 checkpoint,则回退到 checkpoint 声明的配置。

CLI 等价写法:JSON 或点号路径

命令行既支持整段 JSON,也支持点号路径(dotted keys):

vllm serve <model> --quantization-config '{"moe":{"activation":"mxfp8"}}'
vllm serve <model> --quantization-config.moe.activation mxfp8

两种写法都会被 Pydantic 解析进同一个 QuantizationConfigArgs,再经 resolve_quantization_config 合并到 --quantization 的 base 配置上——凡是 quantization_config 显式设置的字段都优先于 shorthand 的默认值(vllm/config/quantization.py)。

--quantization 的完整 shorthand 清单

vllm/config/quantization.py_ONLINE_SHORTHANDS 给出了所有可直接用于 --quantization 的方案及其解糖后的实际配置:

shorthand 解糖语义
fp8_per_tensor linear.weight = kFp8StaticTensorSym,moe.weight = kFp8StaticTensorSym
fp8_per_block linear.weight = kFp8Static128BlockSym,moe.weight = kFp8Static128BlockSym
fp8_per_channel 每输出通道权重缩放 + 动态 per-token 激活;形状与 llmcompressor 的 FP8_DYNAMIC recipe 一致
mxfp8 linear.weight = kMxfp8Dynamic,moe.weight = kMxfp8Dynamic
mxfp4 linear.weight = kMxfp4Static,moe.weight = kMxfp4Static
int8_per_channel_weight_only 仅 MoE 用 INT8 weight-only;Linear 不量化(无 linear 槽)
nvfp4_per_token MoE 使用 NVFP4 + 每 token 动态激活缩放(仅 Blackwell + FlashInfer TRTLLM 后端);Linear 不量化

注意 mxfp4mxfp8 同时是常见的 checkpoint 量化方法名。_DEFERRED_ONLINE_SHORTHANDSvllm/config/quantization.py)说明:当检测到 checkpoint 自带量化元数据时,这两个名字会被"延迟",优先复用 checkpoint 的量化方式;只有 checkpoint 没有量化元数据时才解析为在线配置。

在已量化 checkpoint 上覆盖激活格式

对 checkpoint 本身已量化的模型,quantization_config 允许你独立于固化在权重里的格式,挑选激活的量化格式。可选覆盖集合因 checkpoint 而异。目前该能力已接通 MXFP4 MoE checkpoint(如 gpt-oss 系列),可为原生的 FP4 权重选择 FP8 激活:

vllm serve openai/gpt-oss-20b --quantization-config.moe.activation mxfp8

需要与特定 kernel 家族组合时,还可以配合 --moe-backend 固定 MoE 的 kernel 实现。

需要留意实现边界:在 base.py_get_method_cls 中,各在线方法类内部自行决定激活格式,目前并非每个方法类都接通了显式的 activation 覆盖——对尚未支持显式覆盖的类传入非空 activation 会直接报错。因此 activation 覆盖应当严格按"checkpoint 特定、方法类已支持"的前提使用。

部分量化 checkpoint:对未量化层补充在线量化

在线量化还可以叠加在"只量化了一部分层"的已有 checkpoint 上,与其原始 quant_method(如 modeloptcompressed-tensorsquark 等)无关。其组合原则是:

  • checkpoint 的 quant_method 继续负责它已量化的那些层;
  • 原 checkpoint 中保持未量化的层,则采用本次请求的在线量化方式。

举例来说,下面的命令给一个 Quark 格式、仅量化了 MoE 专家的 checkpoint 的 Dense Linear 层补上 MXFP8 在线量化:

vllm serve amd/Qwen3.5-35B-A3B-MXFP4 \
  --quantization-config.linear mxfp8

其组合逻辑在 resolve_quantization_config 中:当 --quantization 不是在线 shorthand(即它是 checkpoint 方法名)但给出了 quantization_config 时,vLLM 会把 base quant_method 的加载与在线层的补充量化衔接起来。

一个容易踩的坑:quantization_config.ignore仅在线量化生效的排除列表。原 quant_method 的忽略逻辑只依赖它自己的 ignore 实现与 config.json 里声明的忽略层,二者互不干扰。

Dense 与 MoE 分层使用不同方案

通过 linearmoe 两个字段,可以让 Dense Linear 层与 MoE 专家层使用完全不同的量化方案。每一侧都可给出完整 spec dict,或裸字符串(在线 shorthand 名,如 "fp8_per_block",或权重格式名,如 "fp8_per_block_static");未设置的字段回退到 shorthand 默认值。

from vllm import LLM

# Linear: per-block FP8;MoE: per-tensor FP8(从 shorthand 继承)
llm = LLM(
    "ibm-granite/granite-3.0-1b-a400m-base",
    quantization="fp8_per_tensor",
    quantization_config={
        "linear": "fp8_per_block",
    },
)

或者反过来:

from vllm import LLM

# Linear: per-tensor FP8(继承);MoE: per-block FP8
llm = LLM(
    "ibm-granite/granite-3.0-1b-a400m-base",
    quantization="fp8_per_tensor",
    quantization_config={
        "moe": "fp8_per_block",
    },
)

从源码看,linear/moe 之所以能够各自独立生效,是因为在线量化在运行时按层派发:resolve_quant_method_clsbase.py)根据层实例类型决定走哪张派发表与哪个 spec——LinearBase 层取 args.linear + _ONLINE_LINEAR_METHODSRoutedExperts 层取 args.moe + _ONLINE_MOE_METHODS。两张派发表定义在 base.py:Linear 侧支持 FP8 per-tensor / per-block / per-channel、MXFP8、MXFP4;MoE 侧额外支持 INT8 与 NVFP4。

用 ignore 排除不想量化的层

ignore 参数用来跳过特定层,接受三类匹配形式:

  • 精确层名:如 "model.layers.1.self_attn.o_proj"
  • 正则表达式:以 re: 为前缀;
  • fnmatch 模式:使用 Python fnmatch.fnmatch 的语义(*?[seq] 等)。
from vllm import LLM

llm = LLM(
    "ibm-granite/granite-3.0-1b-a400m-base",
    quantization="fp8_per_tensor",
    quantization_config={
        "ignore": [
            # 精确层名
            "model.layers.1.self_attn.o_proj",
            # 正则:跳过所有 QKV 投影
            "re:.*[qkv]_proj",
            # fnmatch:跳过所有 MoE 专家
            "*mlp.experts*",
        ],
    },
)

注意 fused 层(如 qkv_proj 融合了 q_proj/k_proj/v_proj)的特殊性:匹配模式既可以直接命中融合后的层名,也可以命中其全部未融合分片名。实现上,忽略判断经由 should_ignore_layer,并传入 packed_modules_mapping(融合映射)与 use_fnmatch=True,见 base.py

targets:逐层精细量化方案

如果不想让同一方案作用于 linear/moe 两大类里的所有层,而是要给具体不同的层分配不同方案,就使用 targets 参数。其 key 支持精确层名、re: 正则、fnmatch 模式;value 是方案 shorthand 名(fp8_per_tensorfp8_per_blockfp8_per_channelmxfp8int8_per_channel_weight_onlynvfp4_per_token)。

from vllm import LLM

llm = LLM(
    "Qwen/Qwen3.5-35B-A3B",
    quantization="online",
    quantization_config={
        "targets": {
            # 精确层名
            "model.layers.0.self_attn.o_proj": "fp8_per_tensor",
            # 正则:量化所有 QKV 投影
            r"re:.*self_attn\.qkv_proj.*": "mxfp8",
            # fnmatch:量化所有 MoE 专家
            "*mlp.experts*": "mxfp4",
        },
    },
)

或者使用 CLI:

vllm serve Qwen/Qwen3.5-35B-A3B \
  --quantization online \
  --quantization-config '{"targets":{"model.layers.0.self_attn.o_proj":"fp8_per_tensor","re:.*self_attn\\.qkv_proj.*":"mxfp8","*mlp.experts*":"mxfp4"}}'

使用 targets 时,--quantization 通常取字面量 "online",因为具体方案已由 targets 逐层指定。

targets 的约束与校验规则

在线量化的 targets 在解析期会做严格校验,这些约束在源码中均有对应实现(_validate_targets_validate_targets_exclusivityvllm/config/quantization.py):

  • targetslinear/moe 互斥:只能设置其一。同时给出会在配置解析阶段直接抛出 ValueError
  • 未命中任何 pattern 的层保持不变:维持 checkpoint 原始 dtype,不做在线量化。
  • 同一层不能同时命中 targetsignore:会抛出错误(base.py)。
  • 同一层不能命中多个 targets pattern:命中多个同样报错(base.py)。
  • fused 层的分片约束:融合层(如 qkv_proj)的每个分片都必须命中相同方案,不同分片若各自命中了不同 target 会报错——这保证了 fused 权重不会被切分成互相矛盾的量化格式(base.py)。
  • fnmatch 风格仅在线量化支持:Quark 或 compressed-tensors 的配置不会应用 fnmatch pattern。
  • targets 的值必须是合法 shorthand 名:否则在 _validate_targets 中即报错并给出可用清单;re: 前缀的 pattern 还会先做一次正则编译验证。

平台与后端注意事项

XPU 上 Linear 的 W8A16/W8A8 选择

在 XPU 平台,非 block 的 FP8 scaled-mm Linear 层默认使用 W8A16;可通过后端开关改变:

  • --linear-backend xpu:强制 W8A8(使用 XPU 自定义 kernel);
  • --linear-backend xpu_woq:显式选择 weight-only 量化(W8A16);
  • --linear-backend torch:同样强制 W8A8,但 GEMM 通过 torch._scaled_mm 执行,而非自定义 XPU kernel。

Blackwell 世代与 MoE 后端的依赖

  • mxfp8 的 w8a8 形态要求 SM 100+(Blackwell 或更新);其他 GPU 自动回退到 w8a16 方案。
  • nvfp4_per_token 在线 MoE 量化要求 Blackwell GPU 与 FlashInfer 的 TRTLLM 后端配合。
  • MoE 场景可用 --moe-backend 固定特定的 kernel 家族,与 --quantization-config.moe.activation 覆盖搭配使用。

在线量化在源码里如何工作

逐层派发与摘要日志

加载模型时,vLLM 会遍历每个模块,用其完整前缀名(prefix,如 model.layers.0.self_attn.qkv_proj)调用 get_quant_method(layer, prefix)base.py):

  • 命中的层会被登记进 quantized_layers,并返回对应量化方法类实例(Linear 方法须继承 OnlineLinearBase,MoE 方法须继承 FusedMoEMethodBase);
  • 未命中的 LinearBase 回落到 UnquantizedLinearMethod,未命中的 RoutedExperts 回落到 UnquantizedFusedMoEMethod,即"保持原 dtype"。

启动日志会通过 quantized_layer_summariesbase.py)输出形如 self_attn.o_proj: 24 (from targets: re:.*self_attn\.o_proj, mxfp4) 的汇总,方便核对哪些层被量化成了什么格式。

权重量化与并行一致性

在线权重量化发生在加载完成后的处理阶段(对应 process_weights_after_loading 等钩子)。对 tensor parallel 与 expert parallel 场景,缩放因子必须在各 rank 之间保持一致。仓库为此提供了一组 amax 归约辅助函数(quant_utils.py):

  • amax_for_tp_weight_quant:当权重沿 amax 归约维被张量切分时,在 TP group 上做 MAX all-reduce;
  • amax_for_moe_weight_quant:在 EP 覆盖的 DP × PCP × TP 范围上归约(EP 下每个 rank 持有完整专家,无需归约);
  • amax_for_moe_activation_quant:把 per-expert 激活缩放归约为所有专家共享的单一值——在开启 EPLB 时,缩放会折入 per-expert 反量化 alpha,因此必须保证跨 rank 完全一致。

动态激活缩放与 kernel 路由

激活侧不保存静态缩放,而是在每次前向时依据输入算 amax 再推导 fp32 scale(_fp8_scale 等,见 online/fp8.py)。可执行 kernel 的挑选也被封装进统一的 kernel 初始化入口 init_fp8_linear_kernel,配合平台能力自动选择 CUTLASS / Marlin 等 scaled-mm kernel 实现。

常见误区与限制小结

  • 不是所有 activation 覆盖都能随意使用:激活覆盖目前只对已接通的 checkpoint/方法类生效(文档明确给出的是 MXFP4 MoE checkpoint 选择 FP8 激活的场景),对未支持的方法类显式传 activation 会报错。
  • ignore 不是"全局忽略":对 checkpoint 自带 quant_method 的场景,ignore 只影响在线叠加的部分。
  • 方案有硬件门槛:MXFP8 w8a8 需要 Blackwell+,NVFP4 per-token MoE 需要 Blackwell + FlashInfer TRTLLM,运行前应结合目标 GPU 能力选择,避免落到非预期的回退路径。
  • fused 层必须整体一致:被融合进同一个权重张量的多个投影必须使用相同的 target 方案,否则加载直接报错,这是设计上对正确性的强制约束。

在线量化把"权重一次性转换 + 激活动态缩放"这套能力完整地内置到了 vLLM 加载管线中,配合 quantization_config 的分层、逐层覆盖能力,可以在不重新发布 checkpoint 的前提下,灵活地对不同模型结构探索低精度推理的最佳配置。更多背景可回到 在线量化官方文档 及其所在目录 docs/features/quantization 查阅其他量化方法的对比。

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

项目优选

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