资讯动态

MQBench实战:量化感知训练QAT从原理到部署全流程

发布时间:2026/10/18 17:40:52 来源:尧图企业网站定制
量化这个事圈子里聊得不少但很多同学对 QAT量化感知训练始终停留在“听说过、没跑通”的状态。要么是在 PTQ 上被精度回落搞得焦头烂额要么是翻了几天文档也不知道该把量化节点插在哪儿。我最早接触 QAT 是在一个端侧视觉模型的落地项目上当时手上的模型浮点精度表现不错一旦切到 int8 推理就掉了不少点最后迫不得已才认真研究量化感知训练。折腾几轮之后发现QAT 本身并不玄乎难的是整个链路里杂碎细节太多而像 MQBench 这样的统一评估框架恰好能把“量化方案设计”和“后端部署验证”这两件事拧在一起省掉大量重复造轮子的时间。这篇文章就围绕 MQBench 的 QAT 实践展开重点讲清楚三件事量化感知训练到底改了什么MQBench 是怎么把量化建模和后端解耦的以及我实际跑通一套 QAT 实验时踩过哪些坑、调过哪些参数。不管是刚入门量化的新人还是已经做过 PTQ 想再往前一步的工程师顺着这篇文章的操作路径走一遍应该都能对 QAT 建立起一套完整可复用的认知。1. 为什么需要认真看待 QAT 量化而不是只靠推理时补救1.1 PTQ 的精度损失到底出在哪里后训练量化也就是 PTQ通常是很多人接触量化的第一站。做法很简单加载一个训练好的浮点模型拿一批样本跑一遍前向统计每一层激活值的分布然后据此算出 scale 和 zero point把权重和激活从 float32 映射到 int8之后在推理引擎里用整数算子来完成计算。听起来很顺畅但真实落地时往往不是这么回事。浮点模型训练的时候每一层的权重和激活分布都是在连续空间中演化出来的很多层对数值扰动非常敏感。一旦用统计出来的量化参数做截断和round相当于给每个张量加了一点点噪声这些噪声在浅层可能无关痛痒但经过多层累积之后在像目标检测的边框回归分支或者分割模型的边界细节上就会被放大得很明显。PTQ 常见的补救手段是调校准集、调校准算法、做逐通道量化折腾一圈有时能把精度拉回来一些但某些结构复杂或者分布特别不均匀的模型PTQ 的精度就是死活补不回来。1.2 QAT 的本质是让模型“学会”容忍量化噪声QAT 的思路和 PTQ 完全不同。PTQ 是把训练好的模型拿过来硬生生做一次“截肢手术”而 QAT 是在模型还活着的时候就给它的关键部位装上传感器让它一点点适应低精度表示。具体来说就是在训练或者微调过程中把浮点模型里的权重和激活计算替换成带伪量化节点的版本前向推理时模拟量化误差反向传播时又按浮点梯度去更新参数。这样模型在整个优化过程中就知道自己的数值会在低精度下被“打折”于是会主动调整权重分布让自己在 int8 表示下也能保持较好的精度。我用一句话概括两者差异PTQ 是让模型忍耐损失QAT 是让模型学会回避损失。如果你手里模型的结构对量化特别不友好或者目标硬件平台对精度要求很高PTQ 压不住的情况下QAT 几乎是唯一还能保住模型的出路。1.3 MQBench 在量化实践中扮演什么角色光有 QAT 的概念还不够落地的时候马上会撞上两个问题一是量化方案太多不知道该选哪种二是即使选好了方案不同硬件后端的支持能力也不一样容易在评估时得出互相矛盾的结论。MQBench 这类框架要解决的就是这两件事。它把量化策略、校准策略、伪量化节点实现都做成了可插拔的模块同时在后端抽象上做了统一封装让同一套量化配置可以很自然地切换目标设备进行验证。在我自己的实践中MQBench 最大的价值不是某个单独算法的精度有多高而是它把“量化建模”和“后端评测”分开了。以前我想对比对称量化和非对称量化在某个设备上的表现得分别调两套接口、写两套评测逻辑现在在 MQBench 里改配置就行。这让做量化实验的人能把主要精力放在方案分析和调参上而不是反复写胶水代码。2. 动手之前先把量化方案这几个问题想清楚2.1 伪量化节点是怎么插入到训练图里的真实 QAT 里你并不会直接丢掉浮点权重而是通过伪量化节点模拟“量化再反量化”的过程。伪量化节点在前向计算时做两件事先把浮点数值映射到有限的整数范围比如 int8 就是从 -128 到 127然后再把整数映射回浮点尺度。整个过程可以写成# 伪量化的核心逻辑简化示例 def fake_quantize(x, scale, zero_point, qmin, qmax): x x / scale zero_point x torch.clamp(torch.round(x), qmin, qmax) x (x - zero_point) * scale return x注意前向推理真的是在模拟误差但反向传播时round 这个操作几乎是不可导的。常规做法是使用 STE也就是直通估计梯度不通过 round 层反向传播而是直接把上游传来的梯度当作这个节点的梯度继续往下传。这样权重更新就能顺利进行同时模型又能感知到量化边界带来的截断效应。理解这一点非常关键因为很多调参问题都出在 STE 的梯度近似和量化边界之间的平衡上。2.2 对称量化、非对称量化以及逐张量与逐通道的区别选择量化方案不是越复杂越好而是要匹配硬件和模型的实际分布。对称量化不需要 zero point实现简单很多硬件的整数乘加单元能因此省掉一部分计算。如果权重分布比较接近以零为中心的对称形状用对称量化基本没有损失。但激活值往往不是对称的特别是经过 ReLU 之后全为正数这时候非对称量化能更精细地利用整数表示范围。逐张量和逐通道又有讲究。权重一般更适合做逐通道量化因为不同输出通道的权重数值范围可能差很多如果强制用一个全局 scale数值范围较小的通道会被浪费大量表示精度。激活值因为要在推理时动态计算统计信息逐层量化是主流逐通道激活量化对硬件要求更高不是所有后端都支持。MQBench 让我觉得方便的地方就在这里这些组合都可以通过配置项自由切换不用为每一种排列单独写一套训练循环。2.3 量化范围统计的更新节奏直接影响收敛质量伪量化节点里还有一个看不见但很重要的参数scale 和 zero point 怎么更新。在 QAT 训练中这些统计值可以随着每个 batch 动态计算也可以用移动平均的方式持续更新还可以在训练若干轮后重新用校准集刷新一次。我自己的经验是训练刚开始时模型还在剧烈变化如果每个 batch 都让量化范围跟着乱跳梯度信号会很不稳定。比较稳妥的做法是训练初期固定量化范围或者让 observer 的更新率很慢等模型的权重分布大致稳定后再放开更新。这个细节很多教程不会写但影响非常大我在 4 位量化实验里曾经因为统计更新太激进Loss 反复震荡调低更新率之后才开始正常收敛。3. 用 MQBench 跑通一次 QAT 实验的完整过程3.1 环境准备与依赖安装MQBench 本身是面向研究和工程验证的量化框架依赖主流的深度学习训练库。我在本地环境里用 Python 3.8训练部分基于 PyTorch另外还需要安装 mqbench 本体以及用于评测的推理后端工具。这里需要提醒一点因为训练框架和推理后端的版本迭代都比较快建议在开始前先确认版本兼容性最好用虚拟环境隔离免得后面被依赖冲突卡住。按照仓库的说明通常是先下载源码再执行 pip 安装git clone https://github.com/mqbench/mqbench.git cd mqbench pip install -e .装好之后可以快速验证一下导入是否正常把基础依赖跑通再开始后续操作。3.2 准备模型与数据集这次实验我用的是一个图像分类模拟项目模型结构选择了在移动端比较常见的轻量级网络。为什么不用超大模型因为 QAT 的验证需要反复迭代调参模型太大训练一轮就要半天根本没法快速试错。先用一个小模型把整个链路跑通确认方案有效后再迁移到大模型上会更高效。数据集选择了常用的公开小猫小狗数据集类别数量不多但数据量足够让量化微调看到明显收益。注意这里我说的“量化微调”是从一个已经训练好的浮点模型开始而不是从头开始训练。从头训练 QAT 也不是不行只是耗时更长、超参更难控制工程上通常先拿到一个精度不错的浮点模型再进入量化感知微调。3.3 配置 MQBench 的量化方案MQBench 的配置逻辑是通过一个字典或者配置文件来描述的。以我当时的使用经验为例核心要配置的字段包括量化位宽、量化策略、量化种类是权重还是激活、是否开启逐通道量化、是否使用混合精度等。一个参考配置如下qat_config { quant_dict: { weight: { bit: 8, symmetry: True, per_channel: True, }, activation: { bit: 8, symmetry: False, per_channel: False, } }, observer: minmax, backend: generic, }为什么权重用对称逐通道、激活用非对称逐层这不是随手写的。权重通常以零为中心分布对称可以少算一个 zero point而激活经过激活函数后普遍偏正非对称能多利用一点表示范围。这套组合在大多数通用后端上都是精度和效率之间比较平衡的默认值。3.4 把浮点模型转换成带伪量化节点的 QAT 模型配置好方案之后最关键的一步就是把浮点模型转换成为可训练的量化感知模型。在 MQBench 里这个过程通常是调用 prepare_qat 接口完成的它会遍历模型结构把卷积、全连接、归一化层等关键算子替换为支持伪量化节点的新版本。下面是我的参考代码from mqbench.prepare import prepare_qat qat_model prepare_qat( float_model, qat_config, backendgeneric )转换完成后模型结构里会多出很多 QuantWrapper 或者说 Observer 节点。训练之前最好先打印一遍模型结构确认需要量化的算子都被正确替换了。我遇到过某些自定义算子不被框架识别的情况此时需要额外注册或者手动替换这个后面再展开。3.5 训练循环与学习率调整策略QAT 模型的训练循环看起来和普通训练差不多但有几个明显的差异。首先学习率不能沿用浮点模型从头训练时的大学习率一般要缩小一个数量级甚至更多因为是微调而不是重新训练。其次损失函数通常保持原任务的损失但也可以加上一些针对量化层权重的约束。第三训练轮数不需要太长我通常控制在浮点模型训练轮数的 10% 到 20% 之间重点观察验证集精度能否快速回到浮点基线附近。一个我习惯用的训练骨架大致是这样的optimizer torch.optim.SGD(qat_model.parameters(), lr0.0001, momentum0.9) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) for epoch in range(epochs): for images, labels in train_loader: optimizer.zero_grad() outputs qat_model(images) loss criterion(outputs, labels) loss.backward() optimizer.step()这里有一个容易踩的坑不要直接照搬浮点模型的优化器配置。QAT 的梯度因为经过 STE 的截断本身就比正常浮点训练更“粗糙”如果再加上过大的学习率和冲量很容易让权重分布跑偏。我的建议是先用 1e-4 左右的学习率跑 2 到 3 个 epoch观察 loss 变化如果 loss 不降或者下降极慢再适当调大而不要一上来就大学习率猛冲。3.6 导出量化模型并在目标后端上完成精度验证QAT 训练完之后还需要把训练态的模型导出成真正的低精度推理模型。MQBench 提供了 convert 之类的转换接口会把伪量化节点里的 scale 和 zero point 固定下来并且完成一些算子融合操作把 BatchNorm 融合到卷积里。这一步做完之后模型就不再依赖训练框架的运行机制了可以直接交给后端的量化推理引擎去跑。随后要做的就是用验证集在目标后端上重新做一次精度评估。这一步非常关键因为训练时的 QAT 模拟和真实后端算子实现并不是完全一致的比如某些后端的量化方式带自己特定的对齐规则或者某些算子对 scale 的归一化方式有细微差别。如果只停留在框架自带的模拟精度上很容易被表面上的“精度恢复”骗过去。我最后落地时还会做一步在真实开发板上用实际推理时间来衡量性能收益。有些方案看起来精度不错但某些后端的算子效率特别差实际加速比并不理想。性能评估要结合目标平台的算子支持和内存带宽条件不能只看理论计算量。4. 量化训练中的“手感”与参数调优记录4.1 第一次跑 QAT 我的 Loss 曲线为什么是歪的第一次认真跑 QAT 的时候我发现 loss 曲线非常奇怪前几个 batch 下降得很快但中途会突然跳高一下然后又缓慢下降。后来定位到原因是量化范围 observer 在训练初期更新太频繁权重还在剧烈更新scale 也跟着每个 batch 跳梯度就变得飘忽不定。解决方式是把 observer 的更新频率降低或者让量化范围在头几个 epoch 固定不变。具体实现上是在配置里关闭 observer 的动态更新等模型分布稳定后再打开。经过这个调整之后loss 曲线变得平滑很多验证集精度也随之回升。这个经验在后来的多个模型上都得到了验证如果你在 QAT 时遇到 loss 锯齿状抖动优先检查量化范围统计的更新节奏。4.2 学习率到底用多少合适很多同学会问 QAT 微调用什么学习率比较好。我的经验是看你的浮点模型是怎么训练出来的。如果浮点模型已经收敛得不错QAT 更像是在原最优解附近做“小范围平移”学习率太大容易把模型推离原来的优势区域学习率太小呢量化误差的适应过程又太慢。我一般从浮点模型最终学习率的十分之一开始试然后根据前几个 epoch 的 loss 走向决定微调方向。在 MQBench 的默认配置下我试过固定学习率、余弦退火、以及阶段性下降三种策略。固定学习率最省事适合快速验证余弦退火在训练轮数较多的长任务里表现更稳阶段下降手调痕迹重但能精确控制。实际项目里我会先用固定小学习率跑通正确性确认精度达标后再去优化 schedule这样能减少调试变量。4.3 要不要从头训练还是先浮点后 QAT有一段时间我喜欢把 QAT 和训练混在一起直接从头训一个量化感知模型觉得这样会让模型更适应量化。但实际效果并不总是更好。从头训练 QAT 意味着伪量化节点从初始化开始就参与梯度计算模型的收敛空间会更复杂再加上 STE 的梯度噪声超参选择难度明显上升。如果想追求稳定的精度我建议还是走“浮点预训练 QAT 微调”这条主流路线。先得到一个好的浮点模型再用 QAT 微调让它适应量化这个流程最可控。MQBench 的示例代码也大多按这个路径组织毕竟工程化项目里浮点预训练模型往往早就有了QAT 只是最后的适配环节。5. 容易被忽略的坑与排查速查表5.1 量化范围统计抖动导致精度不稳定这个问题在 2.3 和 4.1 里都提到过这里再深挖一层。训练过程中如果量化范围统计策略是逐 batch 计算的话遇到数据分布波动比较大的批次scale 会产生明显跳变。跳变之后同一层权重在不同 batch 里对应着不同的映射方式相当于训练样本在系统性地换标尺模型很难学得稳。排查时可以先把这个开关关掉手动固定一个统计范围确认 loss 是否变稳。如果是再改成 EMA 滑动平均的方式让范围平滑变化。在 MQBench 的 observer 配置里EMA momentum 默认值如果偏高也建议适当调低比如取 0.9 而不是 0.99这样既能跟踪分布变化又不容易被异常 batch 带偏。5.2 BatchNorm 融合带来的精度落差QAT 训练完成后从训练态模型切到推理态模型经常发现推理精度比训练时模拟的精度低不少一个常见原因就是 BatchNorm 融合策略不对。量化感知训练时BatchNorm 通常是保留在浮点域里的推理部署时会把 BatchNorm 参数折叠进卷积权重里。这个折叠过程会改变原始权重分布如果统计折叠之后的分布再设置量化参数和训练时统计的分布可能不一致。所以标准做法是在训练完成后先做 BatchNorm 折叠融合再基于融合后的模型重新校准一次量化参数然后再导出推理模型。MQBench 的转换流程通常已经把折叠包括进去了但不同版本行为可能不同最好在导出后打印模型的量化参数验证一下 scale 是否针对折叠后的权重重新计算过。5.3 自定义算子没有进入量化列表实际项目里很少全是标准卷积、全连接这些常规算子像一些检测头里的特殊计算、注意力模块里的矩阵乘加经常会以自定义算子的形式出现。MQBench 在自动替换时不一定能覆盖到这些算子如果没有显式注册它们就一直留在浮点精度里。这样的话模型加载和训练都没问题但导出低级推理模型时会发现要么不支持该算子要么该算子仍然是浮点导致整个推理链路被拖慢。排查办法很简单打印替换后的模型结构逐一检查关键算子是否被量化包装托管。没有覆盖的自定义算子要根据后端支持情况决定是改写成支持的形式还是单独做量化适配。这一步不提前处理全部训练做完才发现返工成本很高。5.4 常见问题速查表问题现象可能原因快速排查与处理Loss 锯齿状抖动量化范围统计更新太快关闭 observer 动态更新改为 EMA 或固定范围初始化验证精度始终低于浮点基线学习率太大或训练轮数不足降低学习率至浮点模型十分之一增加微调轮数训练态精度正常推理态精度下降BatchNorm 折叠后未重新校准折叠融合后重新统计激活量化范围自定义算子仍是浮点算子未注册到量化替换表检查模型结构注册算子或改写为可量化形式内存占用比浮点训练明显增大伪量化节点过多或 batch 过大精简监视节点按比例降低 batch sizeQAT 不收敛loss 不下降初始化量化范围异常打印 scale 初值确认从浮点校准起步6. 再往深走一步如何做混合精度与敏感层分析6.1 先找到量化敏感层再谈混合精度做完一整轮 QAT 实验后如果精度还是差一点比如分类任务差 0.5 个点或者检测任务 mAP 差不到 1 个点这时候直接全部切成 4 位通常风险很大。更稳的做法是找到模型里对量化最敏感的那些层针对性地保持更高位宽其余层位宽压到更低。这个思路在业界习惯叫混合精度量化。找敏感层可以先做敏感性分析把所有层都量化到目标位宽然后逐层把某一层恢复回浮点精度跑一遍验证集看精度回升多少。回升幅度大的层就是对量化最敏感的层。这个过程比较耗时但可以借助 MQBench 里已经搭好的实验框架来批量化跑只需要循环遍历不同层配置就行。6.2 混合精度配置如何落到 MQBench 里MQBench 的配置是支持逐层指定位宽或者量化策略的。我在实验里最常用的做法是先按位宽把所有层分成两组例如敏感层保持 8 位其余压到 4 位然后在配置里统一覆盖这些层的参数。这里我要提醒一点不要为了好看强行混合精度一定要先确认目标硬件后端是否支持逐层不同位宽。很多端侧推理引擎出厂时就固定好整模型只支持 8 位或者支持有限的混合位宽组合不支持的话你再怎么配置也落不了地。所以做混合精度实验之前最好先把目标平台的算子能力列表拿来看一眼避免后期推倒重来。6.3 蒸馏辅助与正则化暴躁调试经验在 QAT 的过程中有时候还可以引入知识蒸馏来进一步稳定收敛。做法是让带伪量化节点的学生模型去逼近原始浮点教师模型的输出这样量化误差对梯度的影响会变得更平滑。我在某些检测模型上试过确实能让量化微调后的精度再提升一点但代价是训练复杂度增加一倍不止。如果项目时间紧张建议先不碰蒸馏把 QAT 基本功跑好再说。QAT 过程中的数据增强也要克制。有时为了缓解过拟合加了很重的随机裁剪和色彩抖动反而让量化范围统计变得更加飘忽。QAT 微调时我习惯使用比较轻的增强主要目的是模拟部署时的真实输入分布而不是追求泛化收益。最后说点我在实际操作中的感受量化感知训练并不是一个“跑完就结束”的步骤它更像是一次对模型、数据和部署后端关系的重新审视。每一次精度回升的背后都是对某些原理的更深理解。做 QAT 这一路我最大的心得就是不要迷信默认配置也不要跳过中间验证步骤——反复检查模型结构、量化参数和后端支持列表才是真正能让你从“能用”走到“好用”的关键。希望这份基于 MQBench 的 QAT 实践笔记能帮你少走一段弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑