出差前收拾行李箱:羽绒服蓬松地摊开,几乎占掉半个箱子;可一旦装进真空压缩袋,立刻变成薄薄一块。衣服还是那件衣服,只是把“空气”(多余的精度)挤掉了,穿起来多了几条褶子而已。手机就是一只迷你登机箱,动辄几百 MB 的大模型是那件羽绒服——不压缩,根本放不进去。 “量化”做的正是这件事:把模型里用 32 位浮点数(FP32)书写的参数和中间结果,改写成 8 位甚至 4 位整数(INT8/INT4)。数值还是那些数值的“近似”,但存储体积、内存带宽、计算能耗统统缩小数倍。于是语音唤醒、人像分割、端侧大模型这些 AI 功能,才能挤进手机、耳机、摄像头这些“小箱子”里。这篇文章,我们跟着一位“出行收纳师”的视角,从压缩袋上的刻度讲到机场安检,把移动端量化的原理、算法和工程坑位一次讲透。
量化是端侧部署的第一张“登机牌”。掌握它,你就能把云端的庞然大物塞进用户口袋,这是 AI 从“能演示”到“能商用”的分水岭。
可视化演示
展示FP32到INT8量化映射原理、PTQ与QAT流程对比及量化后推理加速效果,步进切换查看移动端量化核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
量化映射:FP32到INT8线性变换 scale和zero_point将浮点范围映射到0-255 反量化时q乘scale加zero_point恢复近似值 q = round(x/scale) + zp 对称量化zp=0非对称量化含零点偏移
量化基本概念
收拾行李的第一步,是认清每件物品的“原包装”。模型里的每个参数,默认都穿着 32 位浮点数这件“厚重外套”——它能表达从 1.4e-45 到 3.4e38 的几乎所有数字,细致到小数点后七位,代价是每个数占 4 字节。手机这只小箱子,可没那么多地方。
所谓量化,就是把这些连续的浮点数,映射到一排“预制格子”里。比如 INT8 只有 256 个格子(0~255),INT4 只有 16 个。每个浮点数按“取最近原则”塞进某一格,格的编号就是机器里真正存的数。于是有了两个关键概念:数值精度(FP32/FP16/INT8/INT4 的位宽,决定格子总数)和量化类型(按对象分:只压权重叫权重量化,连中间计算结果一起压叫激活量化;按时机分:训练后量化与量化感知训练,后面细讲)。模型体积与位宽成正比:
把位宽从 32 降到 8,箱子立刻缩到四分之一——羽绒服一压,登机箱就能合上了。当然,衣服版型会变“粗”一点,这就是精度换体积的代价。
- 参数量模型里待存储的数值个数,相当于行李的总“件数”,包括权重和偏置。
- 位宽每个数值用多少比特来表示,相当于每件行李的包装层数。
- 除以 8把比特换算成字节,因为 1 字节 = 8 比特。
硬件算力基础
机场规定登机箱限 20 寸、限重 7 公斤,这就是硬规则。手机的“行李规则”由芯片定:手机里不只有 CPU 一个“搬运工”,GPU 擅长并行图形运算,NPU(神经网络处理单元)则给低比特整数乘法开了“绿色通道”——一次能吞吐海量 INT8 运算,同样的电路跑 FP32 往往只有几分之一。所以在 NPU 上,INT8 不只是省内存,更是“踩中了硬件最擅长的步点”。
但算得快不快,还取决于另一个瓶颈——内存带宽。模型每算一个数,都要先从内存“搬”到计算单元;手机的内存带宽通常只有几十 GB/s。如果计算速度快于搬运速度,整体就卡在“等数据”上,这叫访存受限。此时压缩体积(量化)的收益最大。算法工程师常用“计算访存比”来判断瓶颈在哪:
手机的算力单位常用 TOPS(每秒万亿次运算),能效比用 TOPS/W 表示。INT8 把访存量降为四分之一,对访存受限的层几乎“搬多快就快多快”。
- 运算量(分子)完成一次推理需要的浮点运算次数,比如卷积中乘加次数之和。
- 访存量(分母)推理过程中从内存读取和写回的数据字节数。
- 比值含义若它小于硬件理论计算吞吐与带宽之比,说明算力在等数据,压缩体积收益最大。
核心量化算法
压缩袋必须印刻度尺,否则看不出“一格”代表多厚。量化也有两个关键参数:缩放因子 s(每格代表多少数值)和零点 z(刻度起点对齐在哪里),它们合起来定义了“一格有多大、从哪开始数”。
对称量化假定数据以 0 为中心对称分布,把 [-rmax, rmax] 映射到 [-127, 127],只需要一个 s;非对称量化允许分布整体偏移,把 [rmin, rmax] 映射到 [0, 255],除了 s 还要一个 z 来对齐最小值。像压缩袋:对称像是“居中折叠”,非对称则是“顺着物品形状错位折叠”,更适合 ReLU 之后全为正数的激活值。完整的非对称量化公式:
拿到编号后,再用 $x \approx s \cdot (x_q - z)$ 还原,就能“穿回”那件压缩过的羽绒服——版型略松紧,但保暖依旧。
- 缩放因子 s一格刻度代表多少数值,s 越小量化越精细。
- 除法 x/s把浮点数换算成“它占几格”。
- 取整 round压入最近一格,这一步产生取整误差。
- 平移 +z把格数换算成 0~255 的编号,z 就是刻度的起点偏移。
- 截断 clip超出 [0, 2^b-1] 的值一律砍到边界,产生截断误差。
校准与误差
压缩袋买多大,得先看一眼要装什么。量化前同样要先“清点行李”:跑一批有代表性的真实输入,统计每层激活的数值分布,这叫校准(Calibration),那批输入叫校准数据集。
最朴素的是 MinMax 法:直接取最小值、最大值当边界。但真实分布常有“长尾”——个别异常值会把范围撑得很大,格子却浪费在无人区。于是有了百分位法和基于 KL 散度的熵校准:宁可牺牲最边缘的几个点,也要保证中间主体部分分得足够细。KL 散度衡量两个分布之间的“信息差距”:
清点得准,压缩袋的刻度才不会浪费:既不会因尺子太大丢了细节,也不会因尺子太小把东西硬塞爆。
- P(i)原始 FP32 分布的直方图概率,即校准集清点出的“原行李清单”。
- Q(i)量化后重建分布的直方图概率,即“压缩后的小行李箱清单”。
- ln(P/Q)两个清单在每一格的差距,差距越大信息丢失越多。
- 求和把所有格的差距按 P 加权相加,得到总失真;逐候选阈值试算,取 KL 最小的那个。
量化范式对比
打包有两种时机。一种是把行李全装好,到机场再临时塞压缩袋——省事,但容易出褶子;另一种是收拾时就想着“待会儿要压缩”,一边叠一边压——麻烦一点,但衣服板正。
前者对应训练后量化(PTQ):模型已经训练完,直接转换。它又分两种:动态量化(推理时临时统计激活范围,无需数据)和静态量化(用校准集预先定好每层的 s/z,推理零开销,最常用)。后者对应量化感知训练(QAT):在训练图里插入“伪量化节点”,让模型在训练阶段就习惯“被压扁”的感觉。
伪量化节点前向把 x 压成格子再还原(模拟误差),反向则用直通估计(STE)把阶梯函数的梯度“假装成”一条直线传回去,让权重朝着“压扁后依然好用”的方向更新:
QAT 是“带着压缩袋意识叠衣服”,精度要求高的任务里它往往比 PTQ 更稳,代价是训练成本更高。再往上还有混合精度:敏感层保留 FP16/FP32,皮实的层用 INT8/INT4——贵重的西装单独挂,普通 T 恤随便压。
- 前向伪量化x 先压成格子编号,再乘 s 还原成浮点,模拟真机推理时的形状。
- clip 截断防止中间值超出量化范围,超出部分按边界处理。
- STE 近似梯度round 不可导,直接把上游梯度当作 x 的梯度传回——把楼梯“看成”斜坡。
性能优化策略
行李整理不只是“压”,还有“合并”与“叠放”。把洗发水和沐浴露倒进同一个分装瓶,叫算子融合;把袜子卷起来塞进鞋里,叫内存布局优化。
以最经典的 Conv+BN+ReLU 融合为例:三个算子原本各自读写一遍中间张量(多次内存往返),融合成一个算子后,卷积算完立刻过 BN 和 ReLU,中间数据只留在寄存器里,既省访存又省内核启动。收益可用加速比衡量:
内存布局上,NHWC 与 NCHW 的选择、按 16 字节对齐,都影响缓存命中率;SIMD 宽度决定了 INT8 一次能并行处理 16 个值,把通道数补到 16 的倍数能避免“空跑”。端侧调优还有绑核、线程数、动态降频策略等——每一条都像行李的叠放顺序:不改变物品数量,却决定能不能塞进去。
- 分子n 个分离算子各自耗时之和,每个算子的耗时 = 计算时间 + 访存时间。
- 分母融合后单个算子的总耗时,访存次数从 n 次压成 1 次。
- 比值大于 1 说明融合有效,值越大收益越明显。
工程实战部署
行李打包完,还要过安检、上飞机,落地后在传送带上取回。部署就是把量化模型送上真机的全过程。
工具链上,TFLite、ONNX Runtime、MNN、NCNN、CoreML 都支持一键 PTQ 或导入 QAT 模型,选型要看目标芯片和算子覆盖度。上线前必须做质量评估:精度变化、首帧/平均延迟、内存峰值、功耗和模型体积,其中精度是首要验收线:
排查套路也很固定:先看校准集分布是否贴近真实;再逐层对比中间输出,定位“矫情”层单独回退 FP16;最后检查是否踩中编译器“不支持算子”导致意外回退。只有多机型真机实测、用真实输入分布验过延迟和内存,这箱“量化行李”才算真正交到旅客手上。
- FP32 基准原模型在验证集上的准确率。
- INT8 结果量化后模型在同样数据上的准确率。
- 差值通常希望 ≤ 1%~2%;若骤降,说明校准或某些层有问题。
AI 应用场景
手机语音助手的端侧唤醒
阶段:模型压缩与低功耗推理
解决的问题:唤醒词识别需要 7×24 小时常驻运行,FP32 模型功耗高、占用内存大,无法做到“随叫随到”。量化到 INT8 后,模型体积缩小 4 倍,放到低功耗 DSP/NPU 上,待机功耗降到原来的几十分之一,用户喊“你好”的瞬间就能响应。
具体案例:某手机语音助手把 12MB 的唤醒词模型量化至 INT8(3MB),常驻内存减半,唤醒延迟从 250ms 降到 90ms,且断网可用。
实时人像分割与背景虚化
阶段:推理性能优化
解决的问题:拍照预览要求每帧延迟低于 50ms,FP32 模型在手机 NPU 上访存压力大、帧率抖动明显。通过 INT8 静态量化配合 Conv+BN+ReLU 算子融合,延迟从 120ms 降到 30ms,发热也明显缓解。
具体案例:某手机相机 App 的“人像模式”实时分割人像,量化后内存峰值从 800MB 降到 220MB,中低端机型也能流畅预览虚化效果。
端侧大语言模型的本地推理
阶段:模型压缩与内存映射优化
解决的问题:7B 参数的 LLM 用 FP16 存储约需 14GB,手机根本装不下。用 4-bit/8-bit 权重量化把体积压到 4GB 以内,配合内存分页和 KV 缓存量化,用户才能在飞行模式下离线对话。
具体案例:手机厂商在旗舰芯片上本地跑起 7B 级对话模型,首 token 延迟控制在秒级,聊天记录不出设备,隐私性大幅提升。
Python 演示
# -*- coding: utf-8 -*-"""移动端量化核心算法演示:对称/非对称量化与误差分析"""import numpy as np def symmetric_quantize(x, bits=8): qmax = 2 ** (bits - 1) - 1 scale = np.max(np.abs(x)) / qmax xq = np.clip(np.round(x / scale), -qmax, qmax).astype(np.int8) return xq, scale def asymmetric_quantize(x, bits=8): qmin, qmax = 0, 2 ** bits - 1 scale = (np.max(x) - np.min(x)) / qmax zero_point = qmin - round(np.min(x) / scale) xq = np.clip(np.round(x / scale) + zero_point, qmin, qmax).astype(np.uint8) return xq, scale, zero_point def per_channel_quantize(x, bits=8): qmax = 2 ** (bits - 1) - 1 flat = x.reshape(x.shape[0], -1) scales = np.max(np.abs(flat), axis=1) / qmax qflat = np.clip(np.round(flat / scales[:, None]), -qmax, qmax) return qflat.reshape(x.shape).astype(np.int8), scales def dequantize(xq, scale, zero_point=0): return scale * (xq.astype(np.float32) - zero_point) def per_channel_dequant(xq, scales): return xq.astype(np.float32) * scales.reshape(-1, 1, 1, 1) def mse(a, b): return float(np.mean((a - b) ** 2)) np.random.seed(42)weight = np.random.randn(64, 3, 3, 3) * 0.3 # 模拟 64 个输出通道的卷积权重act = np.abs(np.random.randn(5000)) * 2.0 # 模拟 ReLU 后全为正的激活 wq, ws = symmetric_quantize(weight)wp, wscales = per_channel_quantize(weight)print("per-tensor MSE: %.6f" % mse(weight, dequantize(wq, ws)))print("per-channel MSE: %.6f" % mse(weight, per_channel_dequant(wp, wscales))) aq, ascale, azero = asymmetric_quantize(act)print("asym activ MSE: %.6f" % mse(act, dequantize(aq, ascale, azero))) print("FP32 体积: %d B -> INT8 体积: %d B, 压缩 %.2fx" % (weight.nbytes, wq.nbytes, weight.nbytes / wq.nbytes))
scale = np.max(np.abs(x)) / qmax求出压缩袋的“刻度”:把最大绝对值铺满量化范围。np.clip(np.round(x / scale), -qmax, qmax)取整压格 + 截断裁边,模拟真机上的 INT8 行为。zero_point = qmin - round(np.min(x) / scale)计算零点 z,让最小值恰好映射到 0。return scale * (xq.astype(np.float32) - zero_point)反量化:读回刻度,还原近似浮点值。flat = x.reshape(x.shape[0], -1)按输出通道展平,为每个通道单独打包。per_channel_dequant(wp, wscales)每通道刻度分别还原,通常比 per-tensor 误差更小。
常见误区
误区我试过把模型转成 INT8,准确率掉了 5%,说明量化必然损伤精度,生产环境根本不敢用。
正解掉 5% 通常不是“量化”本身的错,而是“没校准就硬压”。只要用贴近真实场景的校准集先统计分布,必要时换 QAT 或混合精度,INT8 的精度损失往往能控制在 1% 以内——就像压缩袋用好了,衣服只多几条褶子,不影响穿。
为何常见:很多人第一次是“零校准一键转换”失败的,于是把个例当成了普遍规律。
误区既然量化能省空间,那就越低越好,直接上 4 位甚至 2 位,反正目标就是更小更快。
正解位宽降到 4 位以下,量化级数只有 16 个,权重和激活的动态范围很难表达,精度往往断崖式下跌。业界做法是混合精度:先逐层测敏感度,“娇气”的层留 FP16,“皮实”的层才压到 INT8/INT4。
为何常见:看到厂商宣传“4-bit 大模型”就觉得越低越好,忽略了背后逐层敏感度分析和混合精度的复杂工程。
误区剪枝、蒸馏、量化都是压缩模型,原理差不多,会一个就会另外两个。
正解三者手术方式完全不同:剪枝删连接(改结构)、蒸馏换小模型(重新学习)、量化只改数值刻度(结构不变)。目标都是“让行李更小”,但一个是扔东西,一个是换小包,一个是真空压缩。
为何常见:模型压缩的几个概念常被放在一起讲,初学者容易混为一谈。
误区在服务器上用相同算子库测了延迟,达到 30ms,直接上架肯定没问题。
正解服务器和手机是两个世界:CPU 降频、NPU 驱动版本、内存带宽、缓存大小都不同,量化算子在不同设备上可能走不同实现。必须多机型真机验收,用真实输入分布测 P99 延迟和内存峰值——行李在家量完,还要到机场实测。
为何常见:桌面端工具链太成熟,让人误以为移动端也“应有尽有”。
误区校准数据越多越准,我从训练集抽一万张喂进去准没错。
正解校准的关键是分布贴近真实场景,而不是数量多。如果只喂白天的照片,模型上线遇到夜景就会把范围估歪。几百张覆盖各场景的代表样本,往往远胜几万张单一分布的偏科样本。
为何常见:“数据越多越好”的直觉太强,忽略了校准统计的是范围与分布形状,而不是在学知识。
面试题
Q1. 移动端部署中,最常见的权重量化目标精度是哪种?
- A.FP16 半精度浮点
- B.INT8 八位整数← 正确答案
- C.INT64 大整数类型
- D.FP64 双精度浮点
答案:B。INT8 用 256 个格子表达数值,体积是 FP32 的四分之一,并且在手机 NPU 上有原生高效算子,是端侧量化的“默认档位”。FP16 只省一半且硬件支持参差;INT64/FP64 反而更大。
Q2. 量化公式 xq=round(x/s)+z 中,z 的作用是什么?
- A.在取整前先做缩放
- B.对齐刻度的零点← 正确答案
- C.拉大量化的步长
- D.消除数值的溢出
答案:B。s 是缩放因子,决定一格多大;round 负责取整;z(零点)把格数平移对齐到 0~255 的编号。好比压缩袋刻度的起点:没有 z,偏置分布的数据就读错数。
Q3. 对称量化与非对称量化最本质的区别是什么?
- A.是否引入零点偏移← 正确答案
- B.是否扩大表示范围
- C.是否需要校准数据
- D.是否改变网络结构
答案:A。对称量化假设数据关于 0 对称分布,只用缩放因子 s;非对称量化多一个零点 z 来适配整体偏移的数据,如 ReLU 后的正激活。B/C/D 都不是两者的本质差异。
Q4. 训练后量化与量化感知训练最主要的区别是什么?
- A.是否在训练中模拟量化误差← 正确答案
- B.是否使用更大的训练数据集
- C.是否改变模型的激活函数
- D.是否增加模型的参数量
答案:A。PTQ 在训练完成后直接转换,快但误差不可控;QAT 在训练时就插入伪量化节点,让模型适应“被压扁”后的表达。核心区别在“是否在训练中模拟误差”,与数据集、激活函数、参数量无关。
Q5. 静态量化前的校准阶段,通常需要做哪些事情?
- A.统计激活值的动态范围← 正确答案
- B.统计权重的数值分布← 正确答案
- C.更换全新的训练数据集
- D.确定缩放因子和零点← 正确答案
答案:ABD。校准要跑一遍真实输入,统计各层激活范围与权重分布,最终确定每层的 s 与 z。更换训练集与校准无关,校准关心的是“分布形状”,不是“学更多知识”。
Q6. KL 散度校准方法最常用于解决什么问题?
- A.为分布选择最优截断阈值← 正确答案
- B.为网络寻找最佳学习率
- C.为数据集生成更多样本
- D.为损失函数调整权重
答案:A。KL 散度衡量原分布与量化后分布的信息差距,逐个候选阈值试算、取 KL 最小的那个,从而解决“异常值撑爆范围、格子浪费在长尾”的问题。
Q7. 为什么 INT8 量化能让许多端侧模型明显提速?
- A.访存量降低为原来的四分之一← 正确答案
- B.模型参数数量直接减少一半
- C.网络结构变得更浅更简单
- D.模型完全不再需要进行推理
答案:A。INT8 比 FP32 小四倍,权重和中间张量的访存都降到四分之一;对访存受限的算子,耗时几乎同步缩四倍,还能踩中 NPU 的低比特计算通道。参数数量、结构、推理本身都不变。
Q8. 量化感知训练中,伪量化节点的作用包括哪些?
- A.前向模拟量化引入的误差← 正确答案
- B.反向用直通估计传递梯度← 正确答案
- C.直接把所有权重变成布尔值
- D.更新统计缩放因子和零点← 正确答案
答案:ABD。伪量化前向把 x 压成格子再还原,制造与真机一致的误差;反向用 STE 把阶梯函数“看成”斜坡传梯度;同时 s 与 z 也随训练更新。它不做二值化,也没有消除计算开销。
Q9. 量化后模型在真机上精度骤降,最优先排查什么?
- A.校准数据是否贴近真实分布← 正确答案
- B.是否更换最新的旗舰手机
- C.是否增大推理批处理大小
- D.是否重新安装手机操作系统
答案:A。精度骤降的头号嫌疑是校准集不贴近真实分布,导致每层的量化范围估歪、截断过多。旗舰机、batch 大小、系统重装与量化误差没有直接关系。
Q10. Conv+BN+ReLU 融合成一个算子的好处包括哪些?
- A.减少中间张量在内存中的读写← 正确答案
- B.减少内核启动和同步的开销← 正确答案
- C.让模型体积自动地缩小一半
- D.避免所有数值溢出和舍入误差
答案:AB。融合把多次内存往返合并成一次,减少中间张量访存和内核启动/同步开销,是性能优化而非压缩手段。模型体积缩小来自量化本身;数值误差也无法靠融合消除。