granite-4.0-h-small-w8a8-llmcompressor量化配方解码:为什么MoE Router必须豁免INT8量化?
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 量化都是值得借鉴的范式。