granite-4.0-h-small-w8a8-llmcompressor量化配方解码:为什么MoE Router必须豁免INT8量化?

原创2026-10-21 23:59:33577 阅读

granite-4.0-h-small-w8a8-llmcompressor量化配方解码:为什么MoE Router必须豁免INT8量化?

granite-4.0-h-small-w8a8-llmcompressor 是 AMD 用 LLM Compressor 对 Granite 4.0 Hybrid Small 进行 W8A8 INT8 量化的模型,体积从 60.0 GiB 压缩到 30.4 GiB。它的量化配方只有 8 行,而其中最值得推敲的一笔,是把 MoE Router 写进了豁免名单——这个决定背后的原因,正是理解 INT8 量化边界的关键。

30 秒了解这个量化模型 📦

项目 内容
架构 GraniteMoeHybridForCausalLM(Mamba + MoE 混合)
基座模型 granite-4.0-h-small(BF16,约 60.0 GiB)
量化方案 W8A8:权重 INT8 per-channel + 激活 INT8 per-token 动态量化
量化算法 RTN(Round-to-Nearest),免校准数据
压缩后体积 30.4 GiB(约 49% 缩减)
推理环境 AMD EPYC CPU + ZenDNN + vLLM

这个基座模型是典型的"混合架构":40 层中只有 4 层是完整注意力层,其余 36 层是 Mamba 线性注意力层;每层都挂载一个 72 个专家的 MoE 模块,每个 token 只路由到 top-10 个专家参与计算(见 config.json 中的 num_local_experts: 72 与 num_experts_per_tok: 10)。

W8A8 量化配方:完整配置只有 8 行 🔍

打开仓库里的 recipe.yaml,全部内容如下:

default_stage:
  default_modifiers:
    QuantizationModifier:
      targets: [Linear]
      ignore: [lm_head, 're:.*block_sparse_moe.router']
      scheme: W8A8
      bypass_divisibility_checks: false
      requires_calibration_data: false

信息密度极高:

  • targets: [Linear] —— 默认"全部线性层都量化",这是最激进的覆盖范围;
  • scheme: W8A8 —— 权重 8-bit、激活 8-bit;权重用 per-channel 静态量化,激活用 per-token 动态量化(对应 config.json 中 quantization_config 的 strategy: channel / dynamic: true);
  • requires_calibration_data: false —— RTN 是数据无关(data-free)算法,不需要校准集,量化过程一步完成;
  • ignore 只有两条:lm_head(业界惯例)和正则 re:.*block_sparse_moe.router(40 层所有 MoE 路由器)。

值得注意的是 config.json:LLM Compressor 把这两条规则自动展开成了 82 条精确的 ignore 记录——40 个 block_sparse_moe.router、40 个 mamba.norm,加上 lm_head。

一个可以验证的细节:model.safetensors.index.json 中一共存在 8808 个 weight_scale 张量,恰好等于 40 层 × 72 专家 × 3 个投影(8640)+ 40 个共享 MLP(80)+ 72 个 Mamba 投影(72)+ 16 个注意力投影(16)。而每一个 router.layer.weight 都没有对应的 scale 张量——它以 BF16 原样存储。豁免不是口号,权重文件里可以直接看到。

为什么 MoE Router 必须豁免 INT8 量化? 🧭

Router 不是"普通的小网络",而是总开关

MoE Router 的职责是在每个 token 到达时,对 72 个专家打出一组 logits,再按 top-k 规则选出 10 个专家去参与计算。它输出的不是数值近似值,而是一份"执行名单"。

INT8 量化的本质是有损舍入:每个权重都被取整到 8-bit 网格上。对大矩阵(比如专家里的 gate_proj、down_proj),单点误差会被后续成千上万次乘累加稀释,输出质量几乎不受影响——这就是为什么配方敢于把 72 个专家全部量化,拿到接近 50% 的体积收益。

但 Router 不同,README 对它的解释一针见血(README.md):

the router is a tiny Linear whose logits decide expert assignment, where an 8-bit rounding error can flip the top-k selection and change which experts run.

关键风险在于排名翻转:logits 是用于比较排序的,一旦某个专家的分数被舍入误差推高或压低一点点,top-k 的边界就可能换位——于是同一个输入,量化前后跑的是两组不同的专家。这不再是"输出精度略降",而是推理路径发生了结构性变化,质量损失完全不可控。

豁免的代价几乎为零

从收益角度看,量化 Router 省不了什么:它只是一层很小的线性投影(4096 → 72),参数量相对 72 个专家的三大投影矩阵可以忽略。"省几 MB、冒整条路由链路的风险",这笔账明显不划算。

顺带看完整张 BF16 名单

除 Router 外,保持 BF16 的还有(见 README.md):

  • mamba.norm 及 Mamba 状态空间内部件(conv1d、A_log、D、dt_bias)——它们不是 Linear 层,不在 targets 覆盖范围内,且状态空间参数对扰动敏感;
  • lm_head / embed_tokens / 各层 LayerNorm——输出打分与归一化模块,量化收益小、风险高。

也就是说,配方遵循的原则非常清晰:能量化的"大头"全部量化以拿满压缩率,影响"决策"和"归一化"的小模块一律豁免。

99.65% 的得分恢复:豁免换来了什么 ✅

官方用 lm-evaluation-harness 对比了 BF16 基线与 W8A8 量化版(README.md):

基准 BF16 基线 W8A8(本模型) 恢复率
GSM8K(5-shot) 0.8643 0.8613 99.65%

用不到 0.4 个百分点的数学推理得分,换来了体积近乎减半(60.0 GiB → 30.4 GiB)以及在 AMD EPYC CPU 上跑 INT8 推理的能力——这正是 Router 豁免策略的价值兑现。

三步跑起来:量化模型使用指南 🚀

第 1 步:锁定版本。该模型与版本强绑定,README.md 要求:

torch==2.13.0.0
zentorch==2.13.0.0
vllm==0.29.0
llmcompressor==0.13.0

第 2 步:设置 OpenMP 预加载。启动前用 LD_PRELOAD 指向 libomp.so 或 libiomp5.so,可获得最佳 CPU 性能(README.md)。

第 3 步:用 vLLM 加载(README.md):

from vllm import LLM, SamplingParams

model = LLM(
    model="amd/granite-4.0-h-small-w8a8-llmcompressor",
    dtype="bfloat16",
    trust_remote_code=True,
)
outputs = model.generate(["Hello, how are you?"],
                         SamplingParams(temperature=0.7, max_tokens=256))
print(outputs[0].outputs[0].text)

注意两点限制:模型仅面向 AMD EPYC CPU 推理优化,不适用于 GPU;Mamba 状态空间内部件仍是 BF16,INT8 加速主要落在投影层、专家和注意力上。

关键结论 📌

  • 配方极简:recipe.yaml 仅 8 行,靠"全量化 + 2 条 ignore"拿到 49% 压缩率;
  • MoE Router 豁免 INT8 的根本原因:它的 logits 决定 top-k 专家选择,8-bit 舍入误差可能翻转排名,直接改变哪些专家被执行,这种结构性错误无法被后续层"洗掉";
  • Router 参数量极小,量化收益趋近于零,豁免几乎无成本;
  • 效果验证:GSM8K 得分恢复率 99.65%,体积减半,CPU 推理可用。

这套"激进量化 + 精准豁免"的取舍思路,对任何 MoE 模型的 INT8 量化都是值得借鉴的范式。

登录后查看全文
granite-4.0-h-small-w8a8-llmcompressor