新闻详情

新闻详情

首页 / 资讯中心 / 详情

INT8量化实战:从矩阵乘到LLM部署的精度与性能优化

发布时间:2026/9/29 7:42:35来源:尧图网络
INT8量化实战:从矩阵乘到LLM部署的精度与性能优化
继续写部署优化系列这期是量化。项目标题里挂的是“模型部署与推理优化02量化原理与实践INT8 矩阵乘、校准、QAT 与 LLM 量化”我尽量不写成教科书。前阵子有人问我“INT8 是不是把每个权重除以 255 再存一下”我听完就知道他没真正把模型跑在硬件上。量化这层窗户纸捅破之后其实就三件事怎么把浮点值装进整数盒子、怎么保证装了之后损失可控、以及怎么让整套机制在真实推理链路里跑得稳。这篇文章我会按 INT8 矩阵乘、校准、QAT、LLM 量化的顺序讲中间穿插我实际踩坑的案例争取每个点都能直接拿到自己的部署项目里用。1. 先从直觉理解量化浮点尾巴怎么被砍掉1.1 一个例子看懂 FP16 到 INT8先别管复杂的数学量化本质上就是“用有限个整数格子去近似原来的浮点数”。FP16 有 5 位指数、10 位尾数表达范围很大INT8 只有 256 个格子。把一个大范围的东西塞进小盒子就必须先定一个缩放比例。假设某个权重值r 0.123我们选择缩放因子scale 1/127 ≈ 0.007874。公式是q round(r / scale)于是q round(0.123 * 127) round(15.621) 16反量化回来是16 * (1/127) ≈ 0.12598。相对误差约 2.4%看起来不大但注意这是单个值。模型里几十亿参数误差会以矩阵乘的形式累积所以后面要引入校准和 QAT 这类手段来“控制误差的分布”而不是幻想每个值都精确。我见过很多刚入门的人把量化理解为“直接除以 255”这会把正负号、分布范围、对称性全搞乱。INT8 有符号的合法范围是[-128, 127]你最多只能把max(abs(tensor))映射到127不是 255。255是uint8的概念用在神经网络权重上会浪费符号信息。1.2 为什么要做量化显存、带宽、能耗量化不是闲着没事找精度损失。做个最简单的盘点一个 70 亿参数的模型FP16 权重占 14GBINT8 占 7GB如果上 INT4 那就是 3.5GB。显存减半意味着同样的显卡可以塞下更大的 batch、更长的上下文或者直接把原来放不下的模型放进去。更关键的是带宽。Transformer 解码阶段是典型的 memory-bound每生成一个 token 都要把全部或大部分权重搬一遍。H100 的显存带宽差不多 3.35TB/s读 7GB 和读 14GB 的时间差是肉眼可见的。算力再高数据搬不过来核心只能空转。我在跑长上下文场景时把 KV cache 也量化到 INT8 之后并发数直接翻了一倍多这就是带宽收益最直观的体现。低比特还会带来能耗下降。移动端、边缘盒子上的 NPU 对 INT8 支持普遍很成熟FP16 反而跑不顺。说句不好听的量化是让模型“下放”到真实设备的重要手段纯算力堆上去不是产品方案。2. INT8 矩阵乘的数学底子缩放、零点和溢出2.1 对称量化与非对称量化的取舍映射方式主要有两种。如果数据分布在 0 两侧比较均匀比如权重常采用对称量化r s * q其中s max(abs(r)) / 127零点固定为 0。好处是整数乘法不用额外处理零点项累加代码更干净。如果数据分布明显偏向某一侧比如 ReLU 后的激活都是非负数用对称量化就浪费了一半格子。此时用非对称量化r s * (q - z)z是零点含义是浮点 0 对应哪个整数。量化时q clamp(round(r / s) z, -128, 127)。激活值通常用非对称权重通常用对称这是绝大多数推理引擎的默认做法。零点听起来简单实际解码时特别容易出问题。很多自写的量化 Kernel 要么把零点当成可选项跳掉要么用舍入后的z做整数运算时产生偏差。我最开始手写矩阵乘时零点处理错一位整个误差就变成“系统性偏移”跑 NLP 任务时文本都开始胡言乱语调试了好久才发现z的符号写反了。2.2 矩阵乘怎么用整数指令算浮点结果假设计算Y A · BA 和 B 都已有量化参数。量化后A ≈ s_a * (Q_a - z_a)B ≈ s_b * (Q_b - z_b)。代入矩阵乘展开Y ≈ s_a * s_b * (Q_a · Q_b) 修正项修正项来自零点实际工程里通常会通过“吸收到偏置”来消化比如卷积里可以把零点项并入biasGEMM 里也可以先算Q_a · Q_b再单独扣除z_b * ΣQ_a和z_a * ΣQ_b。核心计算变成 INT8 的乘加。每个 INT8 乘法产生一个 INT16 精度的临时结果但累加时必须用 INT32 防止溢出。举个例子K 维度是 256两个 INT8 相乘的最大绝对值是 127*12716129256 个累加后理论最大超过 400 万INT16 肯定爆必须由 INT32 累加器承接。最终结果再乘上s_a * s_b恢复浮点这一步叫反量化。现代 CPU 上有 VNNI 指令比如vpdpbusd一条指令同时做多个 INT8 乘加并累加到 INT32GPU 上对应 Tensor Core 的 M 维计算结构。这也是为什么 INT8 部署不是简单用float循环而是要尽量用指令集否则收益会被解释成本吃光。2.3 离线缩放因子为什么要预先算好如果推理时每一层都要重新统计max(abs(x))那成本就失控了。所以传统 INT8 部署会用校准集预先算出每层激活的scale和zero_point权重更是在离线阶段就固定好了。推理阶段只需要查表或用常数寄存器取参数所有整数运算走固定流程。这也意味着校准质量直接决定运行期精度。你校准阶段算出来的scale只是对真实分布的估计如果估计偏大量化步长就偏大小数值直接被压成 0如果估计偏小大数值被截断离群点干脆飞出允许范围。别小看这一步后面第 3 节我会展开讲。3. 校准从数据里摸出数值分布3.1 几种常见校准方法怎么选量化参数不是拍脑袋定的。常见校准方法有四种各有适用场景。MinMax最简单取张量统计的最小值和最大值按线性映射到[0, 255]。问题是它只看两个极端点一个离群噪声就能把整个量化范围撑大其余值全挤在一起精度崩掉。Percentile缓解这个问题统计分布后取99.99%分位作为最大值相当于把极端值裁掉。虽然极端值被截断会引入误差但通常这些点数量极少整体损失反而更小。KL 散度是 TensorRT 的老牌方法把激活分布做成直方图然后枚举候选阈值看用该阈值量化后的分布和原始分布的 KL 散度哪个最小。它追求的不仅是数值接近更是信息量损失最小适合分布长尾明显的模型。MSE 方法则直接最小化量化前后张量的均方误差通常配合搜索策略使用。实际选型建议是CNN 分类任务用 Percentile 或 KL 都行Transformer 类模型如果做 PTQ我会先用 MinMax 快速出一版基线再用 KL 或 MSE 对比而不是盲目迷信某个方法。校准方法对照如下表方法鲁棒性计算成本适用场景MinMax差极低分布干净、无离群值Percentile中低有少量离群噪声KL 散度高中TensorRT/通用部署MSE高中高对数值误差敏感的回归类任务3.2 校准集构造与批次敏感问题校准集不是训练集。它的作用是“让模型吐出一系列激活值”从而估算分布。校准集必须贴近真实部署输入但绝不能拿测试集做校准否则精度指标会虚高到自己都不敢信。样本量上我一般从验证集里抽 300 到 1000 条覆盖不同表现形式的输入。太少了分布估计不准太多了校准时间翻倍收益却逐渐消失。跑一次校准不难难的是怎么保证覆盖率如果真实环境有长短不一的文本、不同温度的采样参数校准集也要混合进去。只用一批固定长度的输入去校准上线后 batch 一变激活分布整体漂移量化参数还是老一套必然掉点。批次层面还有个隐藏问题batch1时激活统计相对剧烈batch32时均值被拉平。如果你用batch1的样本做校准上线却是batch32分布自然不匹配。所以校准的 batch size 最好和实际部署接近或者至少在多个 batch size 下都采一些样再做统计融合。3.3 校准后的“可复现”检查校准完成千万别直接打包上线。我会先留一组固定的 golden 样本分别跑 FP16 模型和 INT8 模型比较输出 logits 的余弦相似度、KL 散度以及最终任务指标。通常余弦相似度在 0.99 以上是安全的如果掉到 0.95 以下说明某个量化参数有问题。还可以检查量化后的数值分布利用率如果量化出来的整数只集中在[-2, 2]这么窄的区域说明 scale 太大、有效精度严重浪费。这种情况要么换校准方法要么改用更小粒度的量化per-channel 或 per-group。你甚至可以拿直方图打印出来看一眼比看指标更直观。提示校准集、验证集、测试集必须严格分开。我见过一个团队用验证集做校准又把验证集当测试集汇报上线后效果比预期差一大截最后发现是“校准过拟合”了。这个坑很常见别踩。4. QAT把量化误差当训练信号4.1 伪量化节点和直通估计器STEPTQ 再怎么校准误差是事后补救的。QAT量化感知训练的思路更狠把“量化-反量化”这个过程直接塞进前向让训练过程见到模拟误差从而调整权重去适应量化噪声。前项里的核心是伪量化节点。它对输入做round和clamp然后反量化回浮点本身不改变数值精度只是在训练阶段“预演”一遍部署时的量化误差。问题在于round的导数几乎处处为零梯度没法往回传。于是大家用直通估计器STE前向老老实实量化反向直接把梯度原样传回去或者只在量化范围内传梯度。一段简化的 PyTorch 伪代码是这样的class FakeQuantize(torch.autograd.Function): staticmethod def forward(ctx, x, scale, qmin, qmax): ctx.save_for_backward(x, scale) x_q torch.clamp(torch.round(x / scale), qmin, qmax) return x_q * scale staticmethod def backward(ctx, grad_output): x, scale ctx.saved_tensors qmin -128 qmax 127 mask (x qmin * scale) (x qmax * scale) grad_input grad_output * mask return grad_input, None, None, None反向时如果x超出截断范围梯度归零相当于不更新这部分权重在范围内则把梯度透传。这就是 STE 基本形态。4.2 QAT 在什么情况下值得做QAT 不是免费的。训练资源、数据标注、调参时间都要成本。我的经验是能 PTQ 就 PTQPTQ 确实救不了再上 QAT。哪些场景值得 QAT首先是低比特场景比如 INT4、3-bit 权重分布被压得极紧PTQ 很难校准到可用精度其次是紧凑型小模型本身容量小几个百分点的误差就会被放大最后是部署目标对某个指标极其敏感比如自动驾驶里的检测 IoU差 0.5 就不能上线。针对大模型很多团队会用“量化感知微调”只调很小一部分参数量也能明显缓解低比特带来的损失。QAT 还需要配合蒸馏。如果没有真实标签可以用原始 FP16 模型在某批数据上的输出作为软标签让量化模型去逼近效果通常比硬标签更好。这一步不是必须但我在视觉模型上试过用 logits KD 可以让 QAT 后的下游任务指标再回升 1 到 2 个点。4.3 一个可操作的 QAT 微调流程如果决定做 QAT我的推荐流程大致如下。先用 PTQ 跑一版确定精度差距有多大并保留校准出来的scale和zero_point作为初始参数。然后把网络中的Conv2d或Linear替换成带伪量化节点的组合注意bias通常不量化保持 FP32。训练策略要保守学习率降到正常训练的十分之一甚至百分之一如果网络里还有 BatchNorm先冻结 BN 的统计量再用小学习率微调。QAT 一般不需要跑满整个训练流程一两个 epoch 就能看到效果跑多了反而可能过拟合校准分布。验证阶段很关键导出模型时要把伪量化节点转成真正 INT8 算子同时把scale以常量形式烘焙进计算图。我在导出时吃过一次亏伪量化节点还在图上导致推理引擎以为要动态算缩放结果速度和精度都不对。导出后一定要手工检查一遍算子类型确认没有残留的 float 量化节点。5. LLM 量化难点不是精度是分布5.1 LLM 激活值离群点为什么会打破 INT8进入 LLM 量化第一个拦路虎是激活的离群维度。Transformer 结构里某些维度在特定 layer 上会出现特别大的绝对值可能比典型值大一个数量级。这些离群点虽然占比不到 0.1%却会把 per-tensor 的scale撑得很大。后果是量化步长变大绝大多数正常激活值只能落在很小的整数范围比如[-2, 2]或[-1, 1]有效精度几乎被抹平。这就是为什么直接把 CNN 的 INT8 方案套到 LLM 上会崩。LLM.int8() 这篇工作很典型它发现约 99.9% 的激活可以用 INT8但 0.1% 的离群维度需要保留高精度。于是把矩阵乘拆成两个分支一个 INT8 矩阵乘处理大部分值一个 FP16 矩阵乘处理离群列最后把两者结果加回去。这种做法效果不错但要算两套乘效率提升会打折。实际部署时“尽量拆开/单独处理敏感维度”的思路保留了下来后面很多量化方法都在解决这个问题。5.2 Group-wise 量化与 GPTQ/AWQ 的基本思路Per-tensor 解决不了离群点那把尺度粒度放细就行。Group-wise 量化是对通道分组比如每 128 个通道共享一组scale和zero_point。这样离群维度只影响它所在的小组不会污染整个张量。INT8 时代每通道量化已经够用到了 INT4 必须用 group-wise否则精度掉得没法看。GPTQ 走的是另一条路它基于二阶信息做逐层重建。量化某一层权重时它不是孤立地看单个权重而是用 Hessian 矩阵衡量“改动这个权重对层输出的影响”然后一边量化一边更新剩下的权重让输出尽量不变。这种误差补偿机制让 GPTQ 在 4-bit 权重下也能保持不错的效果缺点是校准阶段需要跑一轮反向传播计算量偏大。AWQ 的思路更轻巧它发现不是所有权重都“平等”那些对应激活值大的通道更重要。于是先按激活绝对值统计通道重要性再给重要通道乘一个小缩放因子把它们保护起来。AWQ 的核心是人不在循环里不需要反向传播校准成本低且能配合硬件高效实现。我实际用下来的感受如果只是快速落地先试 AWQ如果追求极限压缩且你有预算跑一晚上校准GPTQ 更稳。两者的精度往往差距不大但 AWQ 的可控性更好。5.3 KV cache 量化和部署时值得注意的点别只盯着权重。自回归推理里 KV cache 会随着序列长度线性增长长上下文模型跑起来后KV cache 显存很容易超过权重本身。把 KV cache 压成 INT8 或更低位能直接提升并发度和上下文长度上限。但 KV cache 量化有它自己的坑K 和 V 的数值分布不一样通常 K 的方差大V 更平缓不能用同一套缩放参数。最合理的做法是 K 按 channel 或 head 维度计算 scaleV 按 token 维度或 channel 维度处理这和权重量化天然不同。另外缓存的值会被反复读取反量化必须每步都做如果 scale 没融合进 attention 算子Kernel 会频繁做浮点转换性能可能不升反降。部署端还有一个常见的妥协方案权重 INT4、KV cache INT8激活保持 FP16。这条组合在长上下文场景下性价比最高。我实测过 7B 模型同样单卡把 KV cache 从 FP16 改成 INT8最大可处理长度提升了接近一倍生成速度还有微小幅度的上涨因为访存压力小了。6. 实操避坑与排查我在部署时踩过的量化坑6.1 敏感层与混合精度选择不是所有层都适合量化。做了这么多次量化我从来不做“一键全转”。上线前会跑层敏感度分析逐层把该层权重量化成 INT8其余层保持高精度然后看输出指标或 logits 余弦相似度掉多少。以常见 Causal LM 为例第一层、最后一层 Embedding、LM Head 通常最敏感LayerNorm 和残差分支的数值尺度也不稳定量化的收益很小风险很大。我通常会把这几处保留 FP16其他层再上量化。注意这是经验规律不同模型可能不同最好在自己模型上实测。我遇到过一个开源模型量化后损失最大的是某一个o_proj层并不是首尾层最后还是靠逐层分析才定位。混合精度方案可以在校准后自动决定也可以手工指定。落地时你要看清推理引擎是否支持单层覆盖配置否则模型文件整个都按统一格式硬编码你只能含泪全量 INT8。6.2 精度回退与“推理时动态 Scale”陷阱量化模型部署到不同硬件最容易出问题的不是算法而是各家的算子实现差异。比如某些 NPU 支持动态量化也就是根据每个 batch 的激活统计当前 scale但这会导致同一个模型的输出在不同 batch size 下不一致给排查带来麻烦。我遇到的另一个坑是zero_point的精度被压缩成int8导致零点本身就有误差。对于小 scale 的参数一个 gate 的零点误差可能让输出偏一个量级。正确的做法是zero_point 存储用int16或float至少在校准和导出时保持一致别在转换链路上损失。精度回退机制也要提前设计。如果量化后效果确实不行且 QAT 又来不及至少要有一个“某敏感层自动切回 FP16”的后备方案。这个比硬调参更实用。6.3 算子对齐测试我怎么确认量化结果不是靠运气交付前我要做三类验证功能正确性、性能指标、精度指标。算子对齐测试是最基础的构造固定随机张量给定相同的量化参数让 FP16 实现和 INT8 实现分别算同一个 GEMM比较最大绝对误差和相对误差。误差阈值可以在1e-3到1e-2量级超过就要查实现。这一步能挡住大部分“核函数写错”的问题。端到端指标验证也很重要。生成类任务我会看困惑度和几个业务指标分类任务看 top-1/ top-5。这里有个经验只测一个指标往往会漏问题。比如对话模型困惑度恢复得很好但某些特定句式下开始重复因为量化误差改变了 logits 尾部的相对排序。所以最好再对比一下 top-20 token 的分布重合度。最后把容易混淆的问题整理成一张速查表现象可能原因处理建议所有任务指标大幅下降scale 或 zero_point 转换错误核对导出链路检查量化参数短文本正常长文本崩KV cache 量化边界处理不一致检查 K/V 的 scale 维度设计batch 变化后表现不稳定校准集覆盖不足或动态 scale 不一致重新校准固定静态 scale单层误差突出该层是敏感层对敏感层回退 FP16推理速度没有提升反量化操作没有融合进算子查看 Kernel 调度做算子融合我个人实际操作的体会是量化项目最花时间的从来不是“写量化代码”而是“验证量化后的行为是否符合预期”。每次上线前我都会保留一份固定输入、固定种子、固定 batch 的 golden 输出后面所有改动都要过这一关。只要这关稳定后面出问题就能快速缩小范围要么是输入分布变了要么是算子实现变了不会在量化算法本身上反复打转。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Token 烧钱?OpenClaw 这几个配置让我省了一半开销:TaoToken 统一 Key 接入与 config.toml 骨架实测 2026/9/29 8:37:32

Token 烧钱?OpenClaw 这几个配置让我省了一半开销:TaoToken 统一 Key 接入与 config.toml 骨架实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Avalonia图表开发实战:用LiveCharts2搞定四类核心图表 2026/9/29 8:37:31

Avalonia图表开发实战:用LiveCharts2搞定四类核心图表

跨平台桌面端做数据可视化,这几年我踩的坑比写的代码都多。尤其是从 WPF 迁移到 Avalonia 之后,第一个头疼的问题就是图表:以前在 WPF 里用 WinForms 的 Chart 控件、或者老牌库,放到 Avalonia 里要么直接崩,要么渲染出…

阅读更多 →
vLLM 与 SGLang 架构决战:双引擎调度器与执行模型的底层解剖 2026/9/29 8:37:06

vLLM 与 SGLang 架构决战:双引擎调度器与执行模型的底层解剖

vLLM 与 SGLang 架构决战:双引擎调度器与执行模型的底层解剖在大语言模型(LLM)推理服务进入工业化成熟期的今天,vLLM 与 SGLang 已然成为高性能开源推理运行时(Inference Runtime)的绝代双骄。两者的核心目…

阅读更多 →
Agent之Tool–手写cursor 最小版本:用 TaoToken 统一 Key 跑通 Cline 配置骨架 2026/9/29 8:36:59

Agent之Tool–手写cursor 最小版本:用 TaoToken 统一 Key 跑通 Cline 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenClaw 接入微信的保姆级教程:TaoToken 统一 Key 配置与 webhook 验证 2026/9/29 8:36:59

OpenClaw 接入微信的保姆级教程:TaoToken 统一 Key 配置与 webhook 验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
GitHub 46k+ Star 的 OpenMontage:Windows 上 AI 视频制作环境配置与验证 2026/9/29 8:36:59

GitHub 46k+ Star 的 OpenMontage:Windows 上 AI 视频制作环境配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉