ARTICLE DETAIL

资讯详情

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

深度学习模型量化实战:从INT8矩阵乘到LLM量化的完整指南

深度学习模型量化实战:从INT8矩阵乘到LLM量化的完整指南 1. 为什么模型一上线就“变慢”从量化要解决的问题说起做过模型部署的人大概都有过这种体验训练时 loss 曲线漂亮得不行一推到线上推理延迟直接翻倍显存占用高得离谱原本实验室里跑得飞快的模型到了真实业务场景里就成了“电老虎”。我最早接触量化就是因为一个视觉检测模型在服务器上单张推理要 80ms业务方要求压到 30ms 以内硬件又不能换。当时第一反应是换更小的模型但精度掉得厉害后来才把目光转向了量化这条路。量化这件事说白了就是用更低的数值精度来表示原本高精度的权重和激活值。深度学习模型默认用 FP3232 位浮点数存储和计算一个参数占 4 个字节。而 INT8 只用 1 个字节理论上模型体积能压到原来的四分之一内存带宽需求也降到四分之一矩阵乘这类计算密集型操作的吞吐还能借助整数运算单元进一步提升。这不是玄学是实打实的数值表示和硬件特性带来的收益。但量化不是免费的午餐。把 FP32 压到 INT8本质上是把一个连续的、范围很大的数值映射到一个只有 256 个离散值的整数区间里必然带来精度损失。关键在于损失多少、损失在哪里、能不能补回来。这就是量化这门手艺的核心。围绕这个核心衍生出了一整套技术INT8 矩阵乘怎么算、校准Calibration怎么做、QAT量化感知训练怎么训、LLM 这种超大模型又该怎么量化。这篇就按我实际踩过的坑把这几个环节掰开揉碎讲清楚。适合读这篇的人做过模型训练、想把模型推到线上但被性能卡住的工程师正在评估量化方案、纠结 PTQ 还是 QAT 的算法同学以及想搞明白 LLM 量化到底靠不靠谱的从业者。不需要你精通数值分析但最好对矩阵乘、反向传播这些基础有概念。2. 量化到底在做什么数值映射的底层逻辑2.1 FP32、FP16、INT8 的区别与算力需求先把几个精度格式摆清楚这是理解量化的前提。FP32 是单精度浮点1 位符号、8 位指数、23 位尾数能表示的动态范围极大从 1e-38 到 1e38 都能覆盖代价是 4 字节。FP16 是半精度1 位符号、5 位指数、10 位尾数2 字节动态范围小很多但精度对多数推理任务够用。INT8 是 8 位整数只有 256 个取值1 字节没有指数位表示范围完全靠外部定义的 scale 和 zero_point 来决定。算力需求这块不同硬件的差异很大。以常见的 GPU 为例FP32 的算力通常是最低的FP16 借助 Tensor Core 能到 FP32 的几倍甚至十几倍INT8 的整数运算吞吐往往又是 FP16 的两倍左右。这不是绝对的取决于具体架构但趋势是精度越低、吞吐越高、能耗越低。CPU 上更明显很多现代 CPU 都有专门的 INT8 指令集比如 VNNI跑量化模型比跑 FP32 快得多。注意低精度不等于一定快。如果硬件没有对应的整数运算单元或者算子没有针对 INT8 优化量化后反而可能因为额外的反量化开销变慢。选方案前一定要确认目标硬件的支持情况。2.2 对称量化与非对称量化怎么选量化的核心公式其实很简单。把一个浮点值 x 映射到整数 q对称量化q round(x / scale)反量化 x q * scale。这里 scale 是一个正数通常取 max(|x|) / 127映射区间是 [-127, 127]零点固定在 0。非对称量化q round(x / scale) zero_point反量化 x (q - zero_point) * scale。这里 scale (max - min) / 255zero_point 用来把浮点的 0 精确映射到某个整数上。什么时候用对称、什么时候用非对称我的经验是权重通常用对称量化因为权重分布一般以 0 为中心对称量化实现简单、计算快。激活值尤其是 ReLU 之后的分布往往全为正或者偏移很大这时候非对称量化能更充分地利用 256 个整数格子精度更好。比如 ReLU 输出全是非负数用对称量化的话负半轴那 128 个格子就浪费了。2.3 逐张量、逐通道、逐组粒度决定精度上限量化粒度是另一个关键维度。逐张量per-tensor是整个张量共用一个 scale最省事但精度最差。逐通道per-channel是每个输出通道一个 scale常见于卷积和全连接层的权重量化精度提升明显代价是存储和计算稍微复杂一点。逐组per-group是把通道再分组每组一个 scale在 LLM 量化里特别常见因为 LLM 的权重分布在不同通道间差异极大逐通道有时候都不够。我实测下来权重量化从 per-tensor 换成 per-channel很多模型的精度能回升一大截尤其是那些通道间数值范围差异大的网络。激活值因为要动态计算通常还是 per-tensor因为逐通道的激活量化会引入额外的运行时开销。3. INT8 矩阵乘量化模型跑得快的真正原因3.1 整数矩阵乘的基本流程量化模型推理时最核心的算子就是 INT8 矩阵乘。假设有两个量化后的矩阵 A 和 B它们的量化参数分别是 (scale_a, zp_a) 和 (scale_b, zp_b)那么A_real (A_int - zp_a) * scale_a B_real (B_int - zp_b) * scale_b C_real A_real B_real scale_a * scale_b * (A_int - zp_a) (B_int - zp_b)展开后整数部分 (A_int - zp_a) (B_int - zp_b) 可以用整数乘加指令高效计算最后再乘上 scale_a * scale_b 得到浮点结果。这就是 INT8 矩阵乘能加速的本质把昂贵的浮点乘加换成整数乘加只在最后做一次缩放。如果是对称量化且 zero_point 为 0公式更简单C_int A_int B_intC_real C_int * scale_a * scale_b。这也是为什么对称量化在性能上更受青睐。3.2 累加器为什么必须是 INT32这里有个容易被忽略的细节INT8 乘 INT8 的结果是 INT16但矩阵乘要累加很多项如果累加器也用 INT8 或者 INT16很快就会溢出。所以实际实现里累加器通常是 INT32。以常见的卷积为例一个输出点可能要累加几百上千次INT8 的最大值是 127两个 127 相乘是 16129累加 1000 次就是 1600 万已经超过 INT16 的范围32767但远在 INT32约 21 亿之内。所以 INT32 累加器是标配。实操心得如果你自己写量化 kernel累加器类型一定要用 INT32别为了省寄存器用 INT16溢出导致的精度崩溃非常隐蔽往往表现为某些输出莫名其妙变成极值。3.3 反量化时机与算子融合反量化dequantize放在哪里直接影响性能。最朴素的做法是每个算子算完就反量化回 FP32下一个算子再量化回去这样来回转换开销很大。更好的做法是算子融合把量化、矩阵乘、反量化、激活函数打包成一个 fused kernel中间结果一直保持整数只在必要的时候转换。TensorRT、ONNX Runtime 这些推理引擎都做了大量这类融合。我见过一个案例同样的 INT8 模型没做融合时推理速度和 FP32 差不多做了 ConvBNReLU 的量化融合后速度直接提升 2.5 倍。所以量化不是把权重转成 INT8 就完事了算子融合才是性能提升的大头。4. 校准PTQ 精度的命门4.1 校准在做什么PTQPost-Training Quantization训练后量化不需要重新训练只需要一小批校准数据跑一遍模型统计各层激活值的分布据此确定 scale 和 zero_point。校准做得好不好直接决定 PTQ 模型能不能用。校准的核心问题是怎么从激活值的分布里选出一个合适的截断范围。因为激活值可能有长尾极端大值会把 scale 撑得很大导致大部分正常值被压缩到很少的几个整数格子里精度就崩了。所以校准算法本质上是在“覆盖范围”和“分辨率”之间做权衡。4.2 几种主流校准算法对比校准方法核心思路优点缺点适用场景Min-Max取激活值的最大最小值实现简单无偏对离群值极敏感分布均匀、无长尾百分位Percentile取 99.9% 分位数截断抗离群值分位数需调有少量离群值KL 散度最小化量化前后分布差异精度好理论扎实计算慢对精度要求高MSE最小化量化误差平方和平衡性好需搜索通用直方图统计分布后搜索最优截断稳定依赖 bin 数大多数场景我自己的习惯是先用 KL 散度或者 MSE 跑一版看精度如果掉得厉害再换百分位试试。Min-Max 基本只在激活分布非常干净的时候用实际网络里很少见。4.3 校准数据的准备与数量选择校准数据不需要标签但必须和真实推理数据的分布一致。我见过有人拿训练集的前 100 张图做校准结果线上精度崩了因为训练集和线上数据的分布差异很大。校准数据最好从真实业务数据里采样覆盖各种场景。数量上一般 100 到 500 个 batch 就够了再多收益递减。但要注意 batch 的多样性宁可 200 个多样本不要 1000 个相似样本。另外校准数据要预处理成和推理时完全一致的格式包括归一化参数这点经常被忽略。注意校准阶段如果用了错误的预处理比如归一化均值方差和推理时不一致scale 会整体偏移精度损失可能比不校准还严重。5. QAT把量化误差训回去5.1 QAT 的基本原理QATQuantization-Aware Training是在训练过程中模拟量化误差让模型学会适应低精度表示。核心是在前向传播里插入伪量化节点fake quantization把权重和激活值量化再反量化模拟量化带来的误差但反向传播时用直通估计器STE把梯度直接传过去。STE 的逻辑是量化函数本身几乎处处导数为 0没法直接反向传播所以干脆假设量化操作的梯度是 1让梯度原样穿过。这个近似虽然粗糙但实际效果出奇地好。5.2 伪量化节点的插入位置伪量化节点插在哪里很讲究。权重一般在卷积或全连接之前插入激活值在激活函数之后插入。但有些位置插入反而有害比如残差连接的加法之前因为两个分支的量化误差会叠加。BN 层通常会被折叠进卷积所以 QAT 前一般先做 BN folding。我的经验是先用 PTQ 跑一版看哪些层精度损失大再针对性地在这些层插入伪量化节点做 QAT。全模型 QAT 虽然简单但训练成本高而且有些层本来量化就没问题没必要一起训。5.3 QAT 训练的几个关键参数QAT 的学习率要比正常训练小一到两个数量级因为模型已经收敛只需要微调。通常用正常训练学习率的 1/100 到 1/10。训练轮数也不用多几个 epoch 往往就够。还有一个技巧是 warm-up刚开始训练时先不启用量化让模型适应一下过几百步再打开伪量化。这样能避免训练初期量化误差太大导致梯度爆炸。另外QAT 结束后要冻结量化参数scale 和 zero_point推理时直接用这些参数不要再动态更新。6. LLM 量化大模型时代的特殊挑战6.1 LLM 量化的难点在哪LLM 量化和传统 CNN 量化差别很大。第一个难点是激活值里的离群值outlier。LLM 的激活值在某些通道上会出现极端大的值比其他通道大几十倍甚至上百倍。如果按 per-tensor 量化这些离群值会把 scale 撑爆其他通道全被压成 0。第二个难点是 LLM 参数量巨大QAT 成本高得离谱基本只能走 PTQ 路线。第三个难点是 LLM 对精度敏感尤其是生成任务一点点误差会在自回归过程中累积放大。6.2 权重量化GPTQ、AWQ、GGUF权重量化是 LLM 量化里最成熟的方向。GPTQ 用二阶信息Hessian来指导权重的逐列量化把量化误差补偿到后续列上效果很好。AWQ 则观察到激活值里少数重要通道对精度影响大于是对这些通道的权重保留更高精度。GGUF 是一套格式和工具链支持多种量化级别Q4_K_M、Q5_K_M 等在本地部署场景很流行。我实测下来7B 级别的模型用 GPTQ 4bit 量化困惑度perplexity相比 FP16 通常只涨 0.1 到 0.3基本可用。但再往下压到 3bit精度就开始明显掉了。6.3 激活值量化与 KV Cache 量化激活值量化比权重量化难得多主要就是离群值问题。SmoothQuant 的思路是把激活值的量化难度“转移”一部分到权重上通过一个数学等价的变换让激活值分布更平滑。具体做法是对每个通道除以一个平滑因子 s同时权重乘以 s保持数学等价但激活值的动态范围被压缩了。KV Cache 量化是另一个热点。LLM 推理时 KV Cache 占用大量显存尤其是长上下文场景。把 KV Cache 量化到 INT8显存能省一半对长文本推理帮助很大。但 KV Cache 的量化对精度影响比权重量化敏感需要仔细校准。6.4 LLM 量化的实操建议如果你要量化一个 LLM我的建议是先确定目标硬件和推理框架不同框架支持的量化格式不一样。然后从 4bit 权重量化开始跑一版看困惑度和实际生成质量。如果精度不够试试 AWQ 或者提高量化位数。激活值量化先别急着上除非显存实在不够。KV Cache 量化在长上下文场景值得做但要做好精度回归测试。实操心得LLM 量化后一定要做端到端的生成质量评估不能只看困惑度。困惑度涨 0.2 可能生成质量还行也可能在某些任务上直接崩必须用真实 prompt 测。7. 常见问题与排查技巧实录7.1 量化后精度暴跌怎么排查精度暴跌是最常见的问题。排查顺序我一般是这样先看校准数据分布是否和真实数据一致这是最容易出问题的地方。然后看是不是某些层的 scale 异常大或者异常小用工具把每层的量化参数打出来。接着检查是否有算子没被量化导致量化图和浮点图混跑。最后看是不是离群值导致的试试换校准算法或者对激活值做截断。7.2 量化后速度没提升甚至变慢速度没提升的原因通常有几个硬件不支持 INT8 加速算子没做融合或者反量化开销太大。先确认目标硬件有没有 INT8 指令集再看推理引擎有没有做算子融合。如果模型里有大量小算子融合效果差量化收益就有限。另外batch size 太小的时候量化带来的吞吐优势体现不出来因为瓶颈在启动开销。7.3 常见问题速查表问题现象可能原因排查方法解决思路精度暴跌校准数据分布不符对比校准与真实数据统计重新采样校准数据精度暴跌离群值撑大 scale打印各层 scale换 KL/MSE 校准或截断精度暴跌某些层未量化检查量化图补全量化配置速度无提升硬件不支持 INT8查硬件指令集换硬件或改方案速度无提升算子未融合看推理引擎日志启用融合或换引擎速度变慢反量化开销大profile 各算子耗时优化融合策略输出异常值累加器溢出检查累加器类型改用 INT32QAT 不收敛学习率太大看 loss 曲线降低学习率QAT 不收敛伪量化插入位置不当逐层排查调整插入位置7.4 几个容易踩的坑第一个坑是 BN folding 没做。QAT 之前一定要把 BN 折叠进卷积否则量化参数会算错。第二个坑是校准数据的 batch size 和推理时不一致导致统计分布有偏差。第三个坑是量化后忘了更新推理配置还在用 FP32 的输入输出格式。第四个坑是 LLM 量化后没测长文本短 prompt 正常长 prompt 直接崩。8. 量化方案选型的实战决策8.1 PTQ 还是 QAT怎么选PTQ 快、成本低适合精度要求不极端、时间紧的场景。QAT 精度好但需要训练资源和时间。我的决策逻辑是先跑 PTQ如果精度达标就用 PTQ如果差一点试试更好的校准算法还不行再上 QAT。对于 LLM基本只能 PTQ因为 QAT 成本太高。8.2 量化位数的选择INT8 是通用性最好的选择硬件支持广精度损失可控。INT4 在 LLM 场景很流行但传统 CNN 用 INT4 风险较大。混合精度部分层 INT8、部分层 INT4是折中方案但实现复杂。我的建议是CNN 优先 INT8LLM 可以试 INT4但一定要做充分的精度评估。8.3 工具链的选择PyTorch 有原生的量化 APIeager mode 和 FX graph modeONNX Runtime 支持 PTQ 和 QAT 的量化模型TensorRT 对 NVIDIA 硬件优化最好LLM 场景有 GPTQ、AWQ、llama.cpp 这些专门工具。选工具链要看目标部署环境别在训练框架里量化完发现推理引擎不支持。9. 我个人的一些经验体会量化这件事理论看着简单实操全是细节。我最大的体会是不要指望一套参数打天下每个模型、每个硬件、每个业务场景都需要单独调。校准数据的质量比校准算法更重要很多人精度上不去问题出在数据而不是算法。另外量化不是孤立的优化手段它要和算子融合、图优化、内存布局优化一起用才能发挥最大价值。单独做量化收益可能只有预期的一半。最后量化后的模型一定要做完整的回归测试包括精度、速度、内存、边界情况别只看一个指标就上线。还有一个小心得做量化的时候把每一层的量化参数和量化前后的误差都记录下来形成一个量化报告。这样出问题的时候能快速定位也方便对比不同方案。这个习惯帮我省了很多排查时间。
返回列表