ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kimi 2.7 MoE 从 4bit 存储到 INT8 拆解:W4A8 量化推理实战

Kimi 2.7 MoE 从 4bit 存储到 INT8 拆解:W4A8 量化推理实战 1. 为什么 W4A8 是当下大模型推理的甜点区第一次看到“Kimi 2.7 Moe 从 4bit 存储到 INT8 拆解”这个标题很多人会以为只是把权重压到 4bit 就完事了。实际做过 MoE 推理优化的人都知道真正难的不是“存得下”而是“算得快、算得准、显存还省”。W4A8 这个组合——权重 4bit 存储、激活 INT8 计算——恰好卡在了一个非常微妙的平衡点上显存占用接近纯 4bit 方案而计算吞吐又比 W4A16 高出一大截。先把概念理清楚。W4A8 里的 W 指 Weight权重A 指 Activation激活。W4 表示权重以 4bit 精度存储A8 表示激活值在计算时量化到 INT8。注意这里有个关键区别权重是“存储精度”激活是“计算精度”。权重 4bit 存下来之后在 kernel 内部会被反量化到更高精度参与计算而激活走 INT8 是为了吃到整数运算单元的吞吐红利。为什么不是 W4A4因为激活对精度太敏感了。激活值在 MoE 里经过 router 的 softmax、专家层的 GeLU/SiLU 之后动态范围变化剧烈4bit 激活会直接把模型质量打崩。为什么不是 W8A8因为权重 8bit 存储省不下多少显存MoE 模型动辄几百 GB 的权重8bit 和 16bit 的差距在部署成本上是数量级的。所以 W4A8 是工程上反复权衡后的结果。这里要区分几个容易混淆的精度概念。FP32 是 32 位浮点1 位符号 8 位指数 23 位尾数动态范围极大但算力需求最高。FP16 是 16 位浮点1510动态范围够用但尾数精度有限。BF16 是 187指数位和 FP32 一样动态范围大但尾数精度低适合训练。INT8 是 8 位整数没有指数位靠 scale 和 zero_point 把浮点映射到整数区间算力需求最低、吞吐最高。FP8 则是 143 或 152介于 FP16 和 INT8 之间近两年在训练侧很火但推理侧生态还不如 INT8 成熟。提示W4A8 不是“权重和激活都压到 4bit/8bit 就完事”权重的 4bit 是存储格式计算时通常要反量化激活的 INT8 是计算格式需要 per-token 或 per-channel 的动态量化。两者是两套独立的量化逻辑不能混为一谈。MoE 模型为什么特别适合 W4A8因为 MoE 的参数量大但激活参数量小。以 Kimi 2.7 Moe 这类架构为例总参数量可能几百 B但每个 token 只激活其中一小部分专家。这意味着权重的存储开销是主要矛盾而激活的计算量相对可控。W4 把权重存储压到极致A8 保证激活计算不拖后腿这个组合对 MoE 来说几乎是量身定做的。2. 4bit 权重存储的三种主流格式与选择逻辑权重压到 4bit不是简单地把 FP16 截断。4bit 能表示的只有 16 个离散值怎么把这 16 个值映射到原始权重分布上直接决定了量化后的模型质量。目前工程上主流有三种格式GPTQ 风格的 group-wise 对称量化、AWQ 风格的激活感知量化、以及 NF4/FP4 这类非线性格式。2.1 GPTQ 的 group-wise 量化到底在做什么GPTQ 的核心思路是逐层做最小二乘误差补偿。它把权重矩阵按列分组每组比如 128 个元素共享一个 scale 和 zero_point然后把这组权重映射到 4bit 整数区间。反量化时用w (q - zero_point) * scale还原。group size 越小量化误差越小但 scale 的存储开销越大。128 是常见的折中64 更精细但元数据翻倍。这里有个实操细节GPTQ 量化时用的是校准集calibration set的激活统计而不是原始权重分布。因为权重的量化误差最终要通过激活体现出来所以校准集的质量直接影响量化效果。我一般会用 128 到 512 条覆盖多领域的样本做校准太少会导致某些专家层的量化误差被低估。2.2 AWQ 为什么在 MoE 上更稳AWQ 的出发点和 GPTQ 不同。它观察到权重里有一部分“重要通道”对激活影响特别大如果这些通道被量化误差污染模型质量会明显下降。所以 AWQ 会先识别这些重要通道然后在量化时对它们做保护——要么保留更高精度要么在 scale 上做补偿。MoE 模型里这个特性尤其明显。不同专家的权重分布差异很大有些专家处理的是高频 token激活值大有些专家处理长尾 token激活值小。如果统一用一套量化参数长尾专家的质量会先崩。AWQ 的 per-channel 保护机制在 MoE 上通常比 GPTQ 更稳代价是量化过程更慢、实现更复杂。2.3 NF4 和 FP4 的适用边界NF4Normal Float 4是 QLoRA 里提出的格式假设权重近似正态分布把 4bit 的 16 个值按正态分布的分位数放置。FP4 则是标准的 4 位浮点1 位符号 2 位指数 1 位尾数动态范围比 INT4 大但精度低。这两种格式在 MoE 推理里用得相对少因为它们的反量化 kernel 优化不如 INT4 成熟。但在显存极度受限、且能接受一定质量损失的场景下NF4 是个可选项。我的经验是如果目标是生产环境的高吞吐推理优先选 INT4 group-wise如果是实验性质、追求极致显存压缩可以试 NF4。格式映射方式优点缺点适用场景INT4 group-wise线性对称/非对称kernel 成熟、吞吐高需要 scale 元数据生产推理首选NF4正态分位数低比特下质量好kernel 优化少实验、显存极限FP4浮点映射动态范围大精度低、生态弱研究性质AWQ INT4激活感知保护MoE 上质量稳量化慢、实现复杂质量敏感场景注意4bit 存储的权重在加载后通常要先反量化成 FP16 或 BF16 才能参与矩阵乘。如果 kernel 支持“权重 4bit 直接参与 INT8 计算”的融合反量化那才是真正的 W4A8 加速。否则反量化本身的开销会吃掉一部分收益。3. 激活 INT8 量化的动态范围陷阱权重 4bit 是静态的量化一次就固定了。激活 INT8 是动态的每个 token、每个 batch 的激活分布都不一样。这是 W4A8 里最容易翻车的地方。3.1 per-tensor、per-token、per-channel 怎么选激活量化的粒度直接决定精度和开销。per-tensor 是整个张量共享一个 scale开销最小但精度最差遇到激活值分布不均时会大量截断。per-token 是每个 token 一个 scale精度好很多开销也可接受是目前 LLM 推理的主流选择。per-channel 是每个通道一个 scale精度最好但开销大通常只在权重侧用。在 MoE 里激活量化还要考虑专家路由的影响。同一个 token 被路由到不同专家后激活分布可能完全不同。如果所有专家共享一套激活量化参数某些专家的激活会被过度压缩。更稳的做法是每个专家维护独立的激活 scale但这会带来额外的元数据管理和 kernel 复杂度。3.2 校准集与动态量化的取舍激活量化有两种模式静态量化和动态量化。静态量化用校准集预先统计激活范围推理时直接用固定 scale动态量化在推理时实时统计当前 batch 的激活范围再算 scale。静态量化快但校准集覆盖不到的长尾输入会掉点。动态量化准但每次都要做 reduce 操作有额外开销。我的经验是prefill 阶段用动态量化因为序列长、激活分布变化大decode 阶段用静态量化因为单 token、分布稳定。很多推理框架已经支持这种混合模式。3.3 激活异常值的处理LLM 的激活里存在少量“异常值”outlier数值可能是正常值的几十倍。这些异常值如果直接参与 INT8 量化会把 scale 拉得很大导致正常值全部被压到很小的整数区间精度损失严重。常见的处理方式有三种一是用 per-token 量化把异常值隔离在单个 token 内二是对异常值通道做特殊处理比如保留 FP16三是用 SmoothQuant 这类方法把激活的难度迁移一部分到权重上。在 W4A8 场景下因为权重已经是 4bit 了迁移空间有限所以更依赖 per-token 量化和异常值通道保护。# 激活 per-token INT8 量化的简化逻辑 def quantize_activation_int8(x, eps1e-8): # x: [batch, seq, hidden] # 每个 token 计算自己的 scale abs_max x.abs().amax(dim-1, keepdimTrue) # [batch, seq, 1] scale abs_max / 127.0 eps q torch.round(x / scale).clamp(-128, 127).to(torch.int8) return q, scale def dequantize_activation_int8(q, scale): return q.to(torch.float16) * scale提示激活量化里的 zero_point 在对称量化下是 0非对称量化下需要额外计算。LLM 激活通常近似对称分布所以对称量化够用还能省掉 zero_point 的存储和计算。4. MoE 专家层的 W4A8 拆解实操MoE 的推理路径和 dense 模型不同token 先过 router 算出门控权重再被分发到 top-k 个专家专家各自计算后再加权合并。W4A8 在每个环节的落地方式都不一样。4.1 Router 和 Gate 的精度策略Router 的输出是门控权重决定 token 去哪些专家。这部分计算量小但对精度敏感——如果 router 量化误差大token 可能被路由到错误的专家后续再准也没用。所以 router 和 gate 通常保留 FP16 或 BF16不参与 W4A8 量化。我见过有人为了省显存把 router 也量化了结果 top-k 选择频繁抖动模型输出质量断崖式下跌。这个坑没必要踩router 的参数量在 MoE 里占比极小保留高精度的收益远大于那点显存。4.2 专家权重的 4bit 分组量化每个专家的权重矩阵独立做 4bit group-wise 量化。这里的关键是 group size 和 scale 精度的选择。group size 128 是常见起点scale 用 FP16 存储。如果显存实在紧张group size 可以放到 256但质量会掉一点。专家权重的量化顺序也有讲究。建议先量化所有专家的权重再统一做校准和误差评估。因为不同专家的权重分布差异大逐个量化容易在后期发现前面专家的参数需要调整返工成本高。4.3 专家激活的 INT8 量化与合并专家计算完成后输出需要加权合并。合并前的激活是 INT8合并时通常要反量化回 FP16 再乘门控权重。如果门控权重也是 FP16那合并就是 FP16 运算。这里有个优化点如果 top-k 的门控权重能提前量化到 INT8合并可以在整数域完成但精度损失需要评估。实际部署里我倾向于合并阶段用 FP16因为合并后的结果要进入下一层精度损失会累积。省这一步的计算不值得。4.4 一个完整的 W4A8 推理流程把上面的环节串起来一个 token 在 MoE 层的 W4A8 推理流程大致是输入激活FP16进入 routerrouter 用 FP16 计算门控权重和 top-k 索引。激活做 per-token INT8 量化得到 INT8 激活和 scale。根据 top-k 索引把 INT8 激活分发到对应专家。专家权重以 4bit 存储kernel 内部反量化到 INT8 或 FP16与 INT8 激活做矩阵乘。专家输出反量化回 FP16乘以门控权重累加合并。合并结果进入下一层。这个流程里第 4 步是性能关键。如果 kernel 能把“4bit 权重反量化 INT8 矩阵乘”融合在一起吞吐会非常可观。如果反量化和矩阵乘是分开的中间还要写回显存收益就打折了。5. HIP 后端上的 kernel 适配与性能验证标题里提到了 HIP说明这套 W4A8 方案是在 AMD GPU 生态上落地的。HIP 的编程模型和 CUDA 类似但指令集、warp 调度、内存层次都有差异直接照搬 CUDA kernel 往往跑不出预期性能。5.1 HIP 上 INT8 矩阵乘的指令选择AMD GPU 的 INT8 矩阵乘通常走v_dot系列指令或 MFMAMatrix Fused Multiply-Add指令。不同架构支持的指令不同比如 CDNA 架构的 MFMA 对 INT8 有专门优化。写 kernel 前要先确认目标架构支持哪些指令再决定数据布局。数据布局上INT8 矩阵乘通常要求权重按特定 tile 格式排列才能让 MFMA 指令高效读取。4bit 权重的反量化也要配合这个布局否则反量化后的数据还要重排开销很大。5.2 4bit 权重反量化的内存访问优化4bit 权重在显存里是紧凑存储的两个 4bit 值打包在一个 byte 里。反量化时要先解包再乘 scale。这个过程的瓶颈往往不在计算而在内存访问——如果解包后的数据要写回显存再读带宽就浪费了。优化思路是让反量化和矩阵乘在同一个 kernel 里完成反量化结果直接进寄存器或 LDSLocal Data Share不落显存。HIP 里可以用__shared__或 LDS 来缓存反量化后的权重 tile减少全局内存访问。5.3 实测性能对比与瓶颈定位验证 W4A8 效果时我一般会对比三组数据FP16 基线、W8A8、W4A8。关注两个指标吞吐tokens/s和显存占用。理想情况下W4A8 的吞吐应该比 W8A8 高 20% 到 40%显存占用比 W8A8 低 40% 左右。如果吞吐没达到预期先查是不是反量化开销太大。用 profiler 看 kernel 时间分布如果反量化 kernel 占比超过 15%说明融合没做好。如果矩阵乘 kernel 本身慢查指令选择和数据布局。如果显存占用没降下来查 scale 元数据是不是存太多了——group size 太小会导致 scale 数量爆炸。配置权重显存激活显存吞吐相对 FP16质量损失FP16100%100%1.0x基线W8A850%50%1.3-1.6x很小W4A825%50%1.5-2.0x可控W4A425%25%1.8-2.2x明显注意上表的吞吐是理想值实际取决于 kernel 实现和硬件。如果反量化没融合W4A8 的吞吐可能还不如 W8A8因为反量化本身有开销。6. 量化质量评估怎么判断 W4A8 有没有把模型搞坏量化做完不是终点质量评估才是。W4A8 的质量损失主要来自两处权重 4bit 的量化误差和激活 INT8 的量化误差。两者叠加可能在某些任务上放大。6.1 困惑度之外还要看什么困惑度PPL是最常用的量化评估指标但它对长尾任务不敏感。我一般会加三组测试一是标准 PPL看整体质量二是下游任务准确率比如问答、摘要、代码生成三是长文本一致性看模型在长上下文里会不会“跑偏”。MoE 模型还要特别关注专家利用率。如果量化后某些专家的激活值被过度压缩router 可能会减少对这些专家的路由导致专家利用率下降。这不会直接体现在 PPL 上但会影响模型的表达能力。6.2 分层敏感度分析不是所有层对量化的敏感度都一样。通常 embedding 层、router、最后的输出层对精度最敏感中间专家层的容忍度较高。做分层敏感度分析的方法是逐层替换成量化版本看 PPL 变化。变化大的层保留高精度变化小的层放心量化。这个分析在 MoE 上尤其值得做因为专家层数量多如果能把大部分专家层量化、少数敏感层保留整体收益很可观。6.3 量化误差的累积与补偿W4A8 的误差会在层间累积。如果每层都有微小误差几十层下来可能就明显了。补偿的思路有两种一是量化感知训练QAT在训练时模拟量化误差让模型学会适应二是训练后补偿PTQ用校准数据做局部微调。QAT 效果最好但成本高需要重新训练。PTQ 成本低但补偿能力有限。实际项目里如果 W4A8 的 PPL 损失在 2% 以内通常直接上 PTQ如果超过 5%就要考虑 QAT 或者调整量化配置了。7. 踩过的坑与实战经验说几个我在 W4A8 落地过程中真实踩过的坑都是文档里不会写的。第一个坑是 group size 设太小。一开始为了追求质量把 group size 设成 32结果 scale 元数据占了大量显存整体显存占用比预期高了 30%。后来改成 128质量只掉了不到 0.5%显存省了一大截。group size 不是越小越好要算总账。第二个坑是激活量化用了 per-tensor。在 prefill 阶段不同位置的激活分布差异极大per-tensor 直接把长序列后半段的激活压没了。改成 per-token 后长文本质量立刻恢复。这个坑在短序列测试时发现不了一定要用长文本验证。第三个坑是 HIP kernel 的数据布局没对齐。4bit 权重解包后的 tile 布局和 MFMA 指令要求的布局不一致导致每次矩阵乘前都要重排性能比预期低了 40%。后来重写了反量化 kernel让解包直接输出目标布局性能才上来。第四个坑是忽略了 router 的精度。有次为了省显存把 router 也量化了结果 top-k 选择在相邻 token 间频繁跳变生成文本的连贯性明显下降。router 那点参数量不值得省保留 FP16 是最稳的。提示W4A8 的调优是个系统工程不要指望一次配置就到位。建议先跑通 FP16 基线再逐步加量化每加一步都做质量评估这样才能定位问题出在哪个环节。8. 从 W4A8 继续往下走的方向W4A8 不是终点。往更激进的量化走有 W4A4、W3A8 这些方向但质量风险更大。往更稳的方向走有 W4A16、W8A8质量好但收益小。W4A8 目前是收益和风险的平衡点。如果要在 W4A8 基础上继续优化我建议从三个方向入手一是 kernel 融合把反量化、矩阵乘、激活量化尽量融到一个 kernel 里减少显存往返二是专家粒度的量化策略不同专家用不同的 group size 和量化格式把敏感专家保护起来三是动态精度切换根据输入难度动态决定用 W4A8 还是 W8A8简单输入走激进量化难输入走保守量化。MoE 架构本身还在演进Kimi 2.7 这类模型的专家路由策略、专家数量、激活比例都在变。W4A8 的具体配置也要跟着调没有一劳永逸的参数。我的习惯是每换一个模型先做一轮敏感度分析再定量化方案这样最稳。
返回列表