📦 模型量化与端侧部署
为什么 FP32 模型必须"瘦身"才能上机器人 —— 量化四本账 · scale/zero_point 数学 · PTQ/QAT 两条流水线 · 2:4 稀疏化 · 推理引擎图优化 · PyTorch 量化代码实战 · 量化对 NPU RTL 的反向要求
INT8 量化
PTQ / QAT
2:4 稀疏化
推理引擎
端侧部署
软硬结合
🎯 本页学习目标
1. 能算清量化的"四本账":INT8 相对 FP32 在存储、带宽、功耗、算力面积上各省多少
2. 能写出对称/非对称量化的 scale 与 zero_point 公式,并解释 per-channel 为什么救精度
3. 能画 PTQ(校准)与 QAT(伪量化 + STE)两条流水线,并说出各自的适用场景
4. 能解释推理引擎的算子融合、tensor arena 内存规划,以及 2:4 结构化稀疏的跳零加速原理
5. 能跑通 PyTorch 动态量化 + FX 静态 PTQ + ONNX 导出的完整流水线,并设计 LeNet 量化前后精度对比实验
建议用时: 约 50 分钟(本页是「第二阶段 · AI 通识课」的核心页,直接为阶段三 NPU RTL 设计提供算法侧输入)
1 为什么要量化:算力、带宽、功耗、存储四本账
1.1 为什么要量化:把"四本账"算清楚
是什么: 量化(Quantization)就是把神经网络里的 FP32 浮点权重和激活,近似映射成低位宽的整数(INT8 甚至 INT4)来存储和计算。为什么: 因为在机器人端侧,你被四本账同时卡死 —— 存储装不装得下、带宽喂不喂得动、电池供不供得起、算力转不转得快。只要把下面这本账算清楚,你就明白为什么业界几乎所有 NPU 都把 INT8 当作"第一公民"。
账本 FP32 基线 量化到 INT8 后 为什么省
① 存储 ResNet-50 约 98 MB 约 25 MB(4× 缩减 ) 每个权重 4 字节 → 1 字节,模型文件/片上 SRAM 占用同步缩小
② 带宽 读一个权重 32 bit 读一个权重 8 bit(4× 缩减 ) NPU 是"访存受限"的:MAC 阵列算得快不快,取决于权重/激活喂得进喂不进
③ 功耗 从 DRAM 读 32 bit 数据 同样数据量能耗约 1/4 访存能耗远大于计算能耗(每搬 1 bit 过 DRAM 总线的能耗是乘加的几十上百倍),数据变窄 → 搬运能耗直接按比例降
④ 算力/面积 FP32 MAC:对阶、规格化等浮点逻辑,面积大、主频难提 INT8 MAC:同样面积可塞下数倍 的乘加单元 8×8 乘法器面积约为 32×32 浮点路径的零头;整数加法不需要对阶,流水线短、功耗低。所以同一颗 NPU,INT8 TOPS 往往是 FP16 的 2 倍、FP32 的 4~8 倍
量化四本账(以 INT8 = 1× 为基准的相对开销)
FP32
FP16
INT8
98MB
49MB
25MB
① 存储占用
ResNet-50 权重大小(近似)
4×
2×
1×
② 带宽需求
搬运同样多权重占总线宽度
4×
2×
1×
③ 推理能耗(访存主导)
第④本账「算力面积」同理:
INT8 TOPS ≈ FP32 的 4~8×
记忆锚点:位宽减半 → 存储/带宽/能耗约减半;FP32 一步到 INT8 → 三本账同时乘 1/4。
图 1:量化四本账——FP32/FP16/INT8 的存储、带宽与能耗对比
机器人端侧的例子: 一台人形机器人的头部 SoC(如瑞芯微 RK3588 的 6 TOPS INT8 NPU,或 Jetson Orin Nano),要同时跑双目深度、人体检测、VSLAM、语音 VAD,还可能要给 VLA 小模型留算力。若全部用 FP32:一个检测模型就占上百 MB 内存、NPU 的浮点吞吐只有 INT8 的零头,30 fps 的感知闭环根本转不起来;量化到 INT8 后模型缩小 4×、NPU 跑满 6 TOPS、发热也压得住 —— 这就是"量化是部署的第一课"的原因。
端侧平台(量级参考,官方标称) INT8 算力 对量化部署的含义
瑞芯微 RK3588 NPU 约 6 TOPS(INT8) 国产 SoC 常见配置:量化到 INT8 才"够得着"实时,FP32 只剩零头可用
NVIDIA Jetson Orin Nano 约 40 TOPS(INT8) 机器人头部/小整机常用;TensorRT 全整型链路成熟,INT8 是默认交付形态
NVIDIA Jetson AGX Orin 约 275 TOPS(INT8) 整机大脑级平台:留得住 VLA/多路感知,但功耗预算同样逼你上 INT8
一个粗算的"感知预算"例子:三路模型(检测 1.5 TOPS + 深度 1.2 TOPS + VSLAM 1.0 TOPS)按 30 fps 连续跑,平均需求约 (1.5+1.2+1.0) TOPS;在 6 TOPS 的 NPU 上,若全用 FP32(算力按 INT8 的 1/4 计,有效约 1.5 TOPS),连一路都跑不满 —— 全 INT8 量化则是用掉 62% 算力,还给语音和 VLA 留了余量。学会算这本账,你在选型会上就有话语权。
🎤 面试怎么问: "INT8 相对 FP32 能带来多少收益?收益是从哪来的?" —— 标准答法:存储与带宽 4×、访存能耗约 1/4、NPU 的 INT8 算力面积效率数倍于浮点;并补一句"NPU 常见访存受限,所以带宽那一本账往往最关键"。
⚠️ 易错点: ①量化 ≠ 线性提速 4×:实际加速比受访存模式、算子是否被引擎支持、是否回退 CPU 执行影响,常见实际只有 2~3×;②"省的是带宽不只是存储" —— 很多初学者只记得模型变小,忽略了 NPU 上真正的瓶颈经常是数据搬运。
2 数值表示与量化数学
2.1 数值表示基础:FP32 / FP16 / BF16 / INT8 到底差在哪
是什么: 浮点数用「符号 + 指数 + 尾数」三段表示一个实数:FP32 是 1 位符号 + 8 位指数 + 23 位尾数,解码为 (-1)S × 2E-127 × (1 + M/223 ) ;定点整数(INT8/INT16)则只有一段裸整数,靠一个外挂的 scale 系数还原量纲。为什么 FP32 对硬件不友好: 浮点加法必须先「对阶」—— 比较两个数的指数、把小指数的尾数右移对齐,再相加、再规格化;这条路径让浮点 MAC 的面积、功耗、流水线深度都远大于纯整数 MAC。这就是为什么 NPU 的 MAC 阵列几乎都做成 INT8/INT16 整数阵列(衔接 08-07 脉动阵列 )。
格式 位宽(符/指/尾) 动态范围 有效精度 典型用途
FP32 1 + 8 + 23 约 ±3.4×1038 约 7 位十进制 训练金标准、对拍基准;端侧几乎不用
FP16 1 + 5 + 10 约 ±65504 约 3.3 位十进制 GPU 混合精度训练、部分 NPU 推理
BF16 1 + 8 + 7 与 FP32 相同 (指数同为 8 位)约 2~3 位十进制 大模型训练/推理:范围大不易溢出,精度靠累积
INT8 纯整数 8 bit + scale 256 级台阶 相对量程 ≈ 1/256 端侧部署主力,NPU 第一公民
INT16 纯整数 16 bit + scale 65536 级台阶 相对量程 ≈ 1/65536 MCU 上精度敏感层(CMSIS-NN 支持 int16)、音频/信号类模型
记忆锚点:指数位决定动态范围,尾数位决定分辨率 。BF16 把尾数砍到 7 位却保留 8 位指数,就是"牺牲分辨率保范围不溢出"的典型取舍;INT8 更激进 —— 直接把范围交给人工选定的量化区间 [rmin , rmax ],256 级台阶全花在分辨率上。
🎤 面试怎么问: "BF16 和 FP16 都是 16 位,有什么区别?为什么大模型训练偏好 BF16?" —— 答:BF16 指数 8 位与 FP32 对齐,动态范围相同、不易上下溢,只是尾数精度低;FP16 范围小,训练中容易溢出需要 loss scaling。
⚠️ 易错点: 不要把"定点数"和"浮点数"混为一谈 —— 定点 Qm.n 格式(如 Q1.7)的小数点是约定死的,靠软件记住 scale;TFLite 的 INT8 模型里每个张量都带着自己的 scale/zero_point 元数据,推理时硬件只做纯整数运算。
2.2 量化数学:scale、zero_point 与反量化
是什么: 量化就是把实数区间 [rmin , rmax ] 线性映射到整数区间,映射关系由两个参数唯一确定:scale(步长 s) 决定一个整数台阶代表多大实数,zero_point(零点 z) 记录实数 0 落在哪个整数上(保证 0 被精确表示,否则卷积的 padding 全部产生误差)。
s = \frac{r_{max} - r_{min}}{2^b - 1}, \qquad z = \mathrm{round}\!\left(-\frac{r_{min}}{s}\right)
q = \mathrm{clip}\!\left(\mathrm{round}\!\left(\frac{r}{s}\right) + z,\; 0,\; 2^b - 1\right) \qquad \text{(非对称量化,UINT8: b=8)}
\hat{r} = s \cdot (q - z) \qquad \text{(反量化:推理输出、精度对拍时用)}
对称量化是非对称的特例:令 rmin = −rmax ,则 z = 0,公式简化为 q = clip(round(r/s), −127, 127) ,其中 s = max|W| / 127。权重常用对称(有正负、分布近零对称),激活常用非对称(如 ReLU 后全为非负,zero_point=0 的对称量化会浪费一半编码空间)。
FP32 实数轴(连续,例:[-1.0, 2.0])
r_min = -1.0
r = 0.83
r_max = 2.0
UINT8 整数轴(仅 256 级台阶)
0
z = 85 (zero_point)
128
q = 155
255
r_min ↔ 0
q = clip(round(r/s) + z)
r_max ↔ 255
s=(2.0−(−1.0))/255≈0.0118 z=round(1.0/0.0118)≈85 反量化: r̂=s·(q−z)=0.0118×70≈0.83
图 2:仿射量化映射示意——浮点区间 [r_min, r_max] 线性映射到 UINT8 整数轴
🎤 面试怎么问: "写一下非对称量化的公式,zero_point 为什么必须存在?" —— 答:q = clip(round(r/s)+z, 0, 255),z 让实数 0 精确落在某个整数上;没有 z,ReLU 的 0 与卷积 padding 会被量化成 ±s/2 的误差并逐层放大。
⚠️ 易错点: ①round 是四舍五入不是向下取整;②clip 范围别忘了 +z 之后做 —— 先 clip 再加 z 是经典 bug;③对称量化 INT8 取 ±127 而不用 −128,是为了让量化器对称、硬件省一个特殊 case(TFLite 全整型方案同样如此)。
2.3 量化粒度:per-tensor vs per-channel
是什么: 一组 (s, z) 作用在多大范围上,就是量化粒度。per-tensor :整个张量共用一组参数,硬件最友好;per-channel :卷积权重的每个输出通道各有一组参数(激活通常仍用 per-tensor,因为推理时激活的统计范围随输入变化,逐通道 scale 会拖垮硬件调度)。
对比项 per-tensor(逐张量) per-channel(逐通道)
参数量 1 组 (s, z) 输出通道数 Cout 组 (s, z)
硬件实现 一次乘法即可完成 requantize,最简单 累加后按通道取不同 multiplier,需要小查表逻辑
精度 各通道动态范围差异大时明显掉点 量化掉点的第一救命稻草 ,MobileNet 等深度可分离卷积模型几乎必开
典型场景 激活张量 卷积/全连接权重
为什么 per-channel 救精度: 不同输出通道的权重动态范围可能相差一个数量级(有的通道权重普遍偏大,有的接近 0)。per-tensor 用"最大通道的最大值"定 scale,等于让小范围通道只能用 256 级台阶里很窄的一段,分辨率被白白浪费;per-channel 让每个通道都吃满 ±127 的整数刻度,舍入误差整体缩小。这就是 08-07 里说的"硬件要让算法有得选"—— 好的量化方案必须能映射成可实现的 RTL。
🎤 面试怎么问: "per-channel 为什么好?为什么激活一般不用 per-channel?" —— 见上文;加分项:提到 per-channel 权重 + per-tensor 激活是 TFLite/PyTorch 默认组合,硬件侧对应"每个输出通道一个 INT32 multiplier + shift"。
⚠️ 易错点: per-channel 的通道轴是输出通道 (权重的 shape [Cout , Cin , k, k] 的第 0 维),别按输入通道切;另外逐通道量化会让"算 scale"从一次统计变成 Cout 次统计,校准代码要按通道分组。
3 PTQ 与 QAT:两条量化路线
3.1 PTQ 训练后量化:不训练,靠校准数据算 scale
是什么: PTQ(Post-Training Quantization)在模型训练完成之后 做量化:拿几百张有代表性的数据"喂"进模型跑前向(不训练),统计每层激活的数值分布,据此算出 scale/zero_point,然后一次性把权重和激活转成整数。为什么常用: 零训练成本、不需要原始训练代码和数据集,几十分钟就能出结果,是端侧部署的默认第一步。
路线 A · PTQ 训练后量化(不训练,几十分钟)
训练好的 FP32 模型 已收敛权重
喂校准数据 几百张 只前向不训练
统计激活分布 min-max / KL 熵 / 百分位
算 scale/zp 每层每组参数 权重+激活
INT8 模型 直接导出 验证精度
路线 B · QAT 量化感知训练(要微调,精度更高)
FP32 模型 + 训练集 可微调
插入伪量化节点 量化→反量化 前向走阶梯
正常训练 前向+反向 STE 直通梯度
微调恢复精度 小学习率 几个 epoch
convert 转真 INT8 导出
两条线终点相同:INT8 模型 → 交给推理引擎。策略:先跑 PTQ,精度达标就收工;掉点 >1% 再上 QAT。
经验:小模型/低比特(INT4)/Transformer 注意力层 QAT 几乎必需;CNN 分类/检测用 PTQ+per-channel 常只掉 0.5% 内。
图 3:PTQ 与 QAT 两条量化流水线对比——校准统计 vs 训练中模拟量化
PTQ 的灵魂在校准方法 —— 怎么从激活的统计分布里选出量化区间 [rmin , rmax ]。三种主流方法对比:
校准方法 原理 优点 缺点 典型使用
Min-Max 取校准集上每层激活的最小/最大值作为量化区间 最简单、可复现、不留截断 对异常值敏感:一个 outlier 把 scale 撑大,正常值的分辨率暴跌 PyTorch/TFLite 默认;分布规整的模型首选
熵校准(KL) 枚举候选截断阈值 t:把 [rmin , t] 量化再反量化得到近似分布 Q,与原分布 P 算 KL 散度,取 KL(P‖Q) 最小的 t 关注整体分布形状,天然抗异常值 逐候选阈值算直方图,校准慢;实现复杂 TensorRT 早期经典方案(INT8 白皮书思路的工程化)
百分位截断 取激活分布的 99.9% / 99.99% 分位数当 rmax ,尾部极端值直接丢掉 一次统计、实现简单,抗 outlier 效果好 分位阈值要调;被截掉的值产生削峰误差 激活有稀疏大值的检测/注意力模型常用
🎤 面试怎么问: "PTQ 的完整流程是什么?三种校准方法怎么选?" —— 按上图答流程;选择策略:先 min-max,掉点超标换 KL 或百分位,配合 per-channel;并强调"校准数据必须覆盖实机真实分布"。
⚠️ 易错点: ①校准数据量不是越多越好,几百张有代表性的即可,关键在分布覆盖 ;②KL 校准选的是"量化后信息损失最小"的截断点,不是"最大值"——把熵校准理解成找 min-max 就错了。
3.2 QAT 量化感知训练:伪量化节点与直通估计器 STE
是什么: QAT(Quantization-Aware Training)把量化"搬进"训练过程:在网络的每个权重/激活后面插入伪量化节点 (fake-quantize),前向传播时先把数值量化成 INT8 再反量化回浮点 —— 网络每一步都在"亲身体验"量化后的台阶值;为什么精度更高: 训练时损失函数已经包含量化噪声,梯度下降会自动把权重调整到"量化后依然正确"的位置,相当于网络对量化误差产生了免疫力。奠基性工作是 Google 的 INT8 白皮书(arXiv:1712.05877),TFLite 全整型方案即源于此。
\text{伪量化(前向):}\quad \hat{r} = s \cdot \left[\mathrm{clip}\!\left(\mathrm{round}\!\left(\tfrac{r}{s}\right) + z,\; q_{min},\; q_{max}\right) - z\right] \qquad \text{STE(反向):}\quad \frac{\partial \hat{r}}{\partial r} \approx 1
STE 为什么必须存在: round 是阶梯函数,梯度几乎处处为 0 —— 反向传播一遇到 round 梯度就"断流"。直通估计器(Steal-Through Estimator)粗暴地把 round 的梯度当成 1,让上层梯度"原样穿过"伪量化节点,训练得以继续。数学上不严格,但工程上极其有效,是 QAT 的标准做法。
怎么做(量化 + 微调流程): ① 加载训练好的 FP32 模型;② prepare:插入观测器(统计 min/max)与伪量化节点;③ 用训练集(或其子集)以小学习率 (如原学习率的 1/100)微调几个 epoch,BN 统计照常更新;④ convert:把观测器统计转成最终 scale,伪量化替换为真量化算子,导出 INT8 模型。
🎤 面试怎么问: "QAT 和 PTQ 怎么选?STE 是什么、为什么需要它?" —— 答:PTQ 优先、掉点超标再上 QAT;round 不可导,STE 把梯度当 1 直传;补一句"QAT 微调是恢复精度,不是重新训练,所以只需几个 epoch"。
⚠️ 易错点: ①QAT 微调用大学习率会把已收敛权重打飞,精度反而更差;②QAT 前先跑一次 PTQ 对比基线 ,否则你不知道 QAT 到底赚了多少;③不是 epoch 越多越好 —— 过度微调会让权重过拟合到训练集的量化噪声上,泛化变差。
3.3 量化误差从哪来:舍入、截断与"敏感层"
是什么: 量化误差主要有两笔:舍入误差 —— round 把实数吸附到最近的台阶,误差均匀分布在 ±s/2 内,温和且各层互相抵消;截断(削峰)误差 —— 超出校准区间的异常值被 clip 到边界,单个值的误差可达数个 s,是掉点的主要元凶。误差还会逐层累积放大 :上一层的输出误差成为下一层的输入扰动。
首层最敏感: 输入图像的动态范围随光照、传感器噪声剧烈变化,首层分布不稳定,scale 估不准会直接污染整张特征图;
末层最敏感: 分类 logits 之间往往只差零点几,量化误差足以翻转 argmax,表现为"精度凭空掉几个点";
中间层相对扛造: 特征冗余大,小扰动影响有限。
怎么做(救精度清单): ① 权重 per-channel 量化(第一救命稻草);② 首层、末层保持 FP16/INT16 高精度(混合精度量化);③ 换校准方法(min-max → 百分位/KL);④ 补充校准数据覆盖实机真实分布;⑤ 用逐层余弦相似度/SQNR 定位误差最大的层,精准 QAT 只救那一两层。
🎤 面试怎么问: "量化为什么掉精度?哪些层最敏感、为什么?" —— 按舍入/截断/累积三层作答,再用首层(分布不稳)末层(logit 差距小)举例,最后给救精度清单,基本就是满分答案。
⚠️ 易错点: ①掉点就无脑全模型 QAT —— 先做逐层误差分析,90% 的情况是一两个敏感层在作怪;②用 ImageNet 归一化均值做校准、却在实机摄像头(不同白平衡/分辨率)上部署,分布错配是端侧项目最常见的翻车点;③偏置(bias)不量化 —— 标准做法是 INT32 存储、在 requantize 阶段累加,把 bias 也压到 INT8 属于自找掉点。
4 稀疏化与推理引擎
4.1 稀疏化性能优化:剪枝、2:4 结构化稀疏与硬件跳零
是什么: 稀疏化(Sparsity)把权重矩阵中"不重要"的连接置零 —— 剪枝 依据通常是权重大小(magnitude):数值接近 0 的权重对输出贡献小,砍掉后微调几步即可恢复精度。它与量化正交:量化压"每个数的位宽",稀疏化压"有多少个数",两者收益可以叠加。
对比项 非结构化剪枝 结构化剪枝 2:4 半结构化稀疏
剪法 单个权重任意置零,稀疏度可达 90%+ 整通道/整滤波器剪除,模型直接变小一号 每连续 4 个权重至少 2 个为 0 ,固定 50% 稀疏度
硬件加速 难:稀疏位置不规则,索引开销吃掉收益,通用 CPU/GPU 基本白剪 好:剪完是稠密小模型,任何硬件直接受益 NVIDIA Ampere 起 Sparse Tensor Core 原生支持 ,数学吞吐 2×
压缩存储 最好(值 + 索引稀疏格式) 与剪枝比例成正比 50% + 每值 2 bit 元数据
典型用途 模型压缩存储、学术研究 端侧模型瘦身 GPU/TensorRT 推理加速(NVIDIA 三步法:稠密训练 → 按 magnitude 剪到 2:4 → 微调恢复)
硬件跳零加速原理: 权重按"非零值 + 2 bit 位置索引"压缩存放,MAC 阵列读数时遇到 0 直接跳过乘法 ——乘法器不翻转,省动态功耗;对 2:4 这种规则模式,硬件还能成对复用加法树,等效数学吞吐翻倍。NVIDIA 官方数据:A100 的 Sparse Tensor Core 下 FP16 达 624 TOPS(稠密 312)、INT8 达 1248 TOPS(稠密 624);TensorRT 8.0 实测稀疏 ResNeXt-101 推理性能提升约 20%、小批量场景性能/功耗比提升约 36%,且精度与稠密基线持平。
# 2:4 稀疏微观示例:每 4 个连续权重为一组,每组保留绝对值最大的 2 个
dense = [ 0.42, -0.03, 0.51, 0.11 ] # 剪枝前的稠密权重组
sparse = [ 0.42, 0.00, 0.51, 0.00 ] # 剪掉 -0.03 和 0.11(幅度最小)
indices= [ 00, --, 10, -- ] # 每个非零值附带 2 bit 位置索引
# 硬件视角:Y = 0.42*x0 + 0.51*x2,两次乘加完成"四次乘加"的等效工作量 → 2× 吞吐
# 注意:剪完必须微调,让剩余 2 个权重"接管"被剪权重原本承担的输出分量
剪枝-量化组合流水线: 顺序必须是先剪后量 —— 剪枝会改变权重分布,先量化再剪会让 scale 全部作废。标准流水线:① 稠密训练 → ② 按 2:4 / 通道粒度剪枝 → ③ 微调恢复精度 → ④ INT8 量化(PTQ 校准或 QAT 微调)→ ⑤ 导出部署。收益叠加:2:4 让权重数减半,INT8 让每权重位宽减为 1/4,合计权重量约 1/8。
🎤 面试怎么问: "非结构化剪枝压缩率那么高,为什么端侧很少用?" —— 答:稀疏索引解码开销与不规则访存让通用硬件无法加速,收益只在存储端;要"算得快"得用结构化或 2:4 这类硬件友好的规则稀疏。
⚠️ 易错点: ①剪枝后必须微调 再评估,剪完直接测精度掉成渣是正常现象不是方法错误;②2:4 稀疏只在支持 Sparse Tensor Core 的硬件(Ampere 及以后 + TensorRT/PyTorch 对应路径)上有加速,部署到 NPU/CPU 前先确认硬件支持;③量化与剪枝同时一步做,几乎必掉点 —— 分步走、每步给恢复期。
4.2 推理引擎原理:计算图、图优化与内存规划
是什么: 推理引擎(Inference Engine)是把训练框架导出的模型,变成目标芯片上高效可执行程序的那套运行时。它拿到的是一个计算图 (算子为节点、张量为边),核心工作有三件:图优化 、内存规划 、算子调度 。
算子融合(最经典的图优化): conv + BN + ReLU 三个算子合成一个。BN 在推理时是固定的逐通道仿射变换,可直接折叠进 conv 权重:W' = W·γ/√(σ²+ε),b' = (b−μ)·γ/√(σ²+ε)+β。融合后 BN 和 ReLU 不再产生中间张量 —— 省的不只是计算,更是两次"中间结果写回内存再读出"的访存,这在 NPU 上往往比乘法本身还贵。
其他图优化: 常量折叠(编译期算完固定表达式)、算子替换(把目标硬件不擅长的算子换成等效组合)、布局变换(NCHW ↔ NHWC,选硬件最爱的排布)。
内存规划(tensor arena): 端侧没有给你随意 malloc 的资本。TFLite Micro 的做法是:编译期分析所有张量的生命周期,把整个网络需要的内存一次性规划成一块静态内存池 (tensor arena),生命周期不重叠的张量复用同一块 buffer(乒乓复用),既零碎片化又可预知内存峰值 —— 这也是 MCU 部署"arena 大小要反复试"的由来。
算子调度: 按拓扑序执行,配合分块(tiling)让数据在片上 SRAM 里停留更久;NPU 上则由指令序列驱动 MAC 阵列(衔接 08-07 的脉动阵列数据流)。
与编译器的关系(一句话定位): TVM 是"把计算图编译成目标硬件高效代码"的深度学习编译器 (图优化 + 自动算子代码生成);MLIR 是"提供多层级中间表示"的编译器基础设施 ,让各家芯片厂在自己的 IR 层上接入 —— 前者是产品,后者是造产品的框架。
① 模型格式层
ONNX(.onnx,通用中间格式) · TFLite(.tflite,flatbuffer+量化元数据) · NCNN(.param 结构 + .bin 权重)
② 图优化 / 编译层(引擎的大脑)
算子融合 conv+BN+ReLU · 常量折叠 · 布局变换 · 内存规划(tensor arena 静态内存池) · 量化信息落地
一句话定位:TVM = 深度学习编译器(图优化+代码生成);MLIR = 多层级 IR 的编译器基础设施
③ 算子库层(手工优化的 kernel)
ncnn / MNN / XNNPACK(移动 CPU) · CMSIS-NN(Cortex-M) · cuDNN/TensorRT(GPU) · NPU 专用算子库
④ 硬件层
CPU(NEON/SVE 向量) · GPU · DSP · NPU(INT8 MAC 阵列,见 08-07 脉动阵列)
一次推理 = ①的模型被 ② 重排融合后,按拓扑序逐算子调用 ③ 的优化 kernel,最终落在 ④ 上执行。
图 4:端侧推理引擎四层栈——模型格式/图优化编译/算子库/运行时
🎤 面试怎么问: "推理引擎为什么能加速?conv 后面的 BN 为什么可以直接融掉?" —— 答:推理时 BN 是固定仿射变换可折叠进权重,融合消灭中间张量访存;再列常量折叠/布局变换/内存规划,体现"引擎赚的主要是访存和调度,不是发明新数学"。
⚠️ 易错点: ①图优化发生在离线转换阶段 ,不要和运行时动态批处理混为一谈;②融合的前提是引擎支持对应融合模式 —— 你在 PyTorch 里手写的花式结构,导出后可能根本融不起来,写模型时就要照顾部署;③tensor arena 是静态单块内存, arena 给小了直接分配失败,给大了浪费珍贵 RAM,必须实测校准。
5 端侧部署实战与代码
5.1 端侧部署实战要点:格式、算子支持度、arena 与时延
是什么: 从"量化好的模型"到"机器人上跑起来的推理",中间隔着模型格式、转换工具、运行时内存和实测时延四道坎。怎么做: 按下面的流水线走,每一步都有明确的检查物。
1 训练 FP32 基线 固化精度/大小/时延三项基线指标,量化后一切对比以它为准
2 量化(PTQ/QAT) 先 PTQ 后 QAT;逐层对拍余弦相似度,锁住掉点层
3 导出 + 检查图 导出 ONNX(opset 13+),用 Netron 打开逐算子检查
4 转换引擎格式 onnx2ncnn / MNN 转换 / tflite 转换;核对算子支持度表
5 板端集成 静态内存 arena、输入预处理对齐训练、初始化引擎
6 实测与上线 板端时延(预热+取均值)、内存峰值、精度回归三表齐全
格式 文件构成 特点与适用
ONNX 单文件 .onnx(protocol buffer) 框架无关的通用中间格式,生态最广;转换链路的"枢纽站":PyTorch → ONNX → 各引擎私有格式
TFLite / TFLite Micro 单文件 .tflite(flatbuffer,内嵌 scale/zero_point 量化元数据) 全整型 INT8 方案成熟;Micro 版面向 MCU,配 tensor arena 静态内存,是 Cortex-M 上的事实标准
NCNN 两个文件:.param(网络结构)+ .bin(权重) 腾讯开源,手机端老牌强者;纯 C++ 零依赖、ARM NEON/汇编手工优化,INT8 与 FP16 都支持;PyTorch 走 pnnx/ONNX 转入
MNN 转换后的 .mnn 模型 阿里开源,淘宝/天猫等 30+ App 生产验证;CPU/GPU/NPU 多后端,MNN-LLM 可端侧跑大模型
算子支持度检查: 模型 95% 的算子引擎都支持,剩下 5% 不支持的部分会静默回退到 CPU —— 精度没坏但时延爆炸。转换后务必用引擎日志/Netron 确认"每个算子落在哪个设备上"。
内存峰值与 arena: MCU 上 arena 大小靠实测:先给大跑通,二分缩小到刚好不失败;SoC 上关注峰值内存 (权重 + 最大激活 + 引擎开销),别只看模型文件大小。
Cortex-M 与 CMSIS-NN: Arm 官方的 int8/int16 优化 kernel 库,自动调用 M4 的 DSP SIMD 指令与 M55/M85 的 Helium(MVE),与 TFLite Micro 量化规格位精确对齐 —— MCU 部署的标准答案是"TFLite Micro 框架 + CMSIS-NN kernel"。
时延测量: 板端计时必须预热若干帧再取多次平均 ,并区分预处理/推理/后处理三段;对比基线时固定 CPU 频率、关掉省电模式,否则数据飘得没意义。
🎤 面试怎么问: "一个 PyTorch 模型要部署到机器人 SoC,说出完整链路和每步的风险点。" —— 按上面六步流水线作答,风险点:算子回退 CPU、arena 估错、预处理与训练不一致、时延测量不预热。
⚠️ 易错点: ①Netron 里图"能打开"≠引擎"能执行";②训练用 NCHW、NPU 爱 NHWC,布局转换忘了做会慢一截;③量化模型上线前必须做端到端精度回归 (实机数据集),离线对拍通过不代表真机不掉点。
部署翻车案例速查(现象 → 根因 → 修复)
现象 最可能根因 修复动作
量化后精度暴跌(掉 5% 以上) 激活有异常值,min-max 校准被撑爆 / 敏感层没保护 换百分位或 KL 校准;权重 per-channel;首末层 FP16;逐层对拍定位
板端时延是预期 3~5 倍 部分算子不被 NPU 支持,回退 CPU 执行 看引擎日志逐算子核对设备;替换/改写不支持算子
tflite-micro 报 arena 不足 tensor arena 估算过小或模型含 int16 大张量 内存报告定位峰值层;降输入分辨率;二分调 arena
离线对拍全过,真机精度却掉 端侧预处理(归一化/色域/插值)与训练不一致 逐算子比对预处理输出;统一到训练侧的数值管线
推理结果偶发"花屏/全黑" 累加器溢出回绕,或输入溢出量化区间被饱和到边界 检查硬件饱和逻辑;检查输入 scale 是否按实机数据范围定标
同模型不同次测量时延波动大 CPU 降频/热节流,测量前未预热 锁频、预热 N 帧再取均值;记录板温
导出 ONNX 后精度与 PyTorch 不一致 动态算子(如 view/reshape)trace 错误,或 opset 过低 提高 opset 版本;用 torch.onnx.export 前做数值对拍
QAT 微调精度反而低于 PTQ 学习率过大打飞权重,或未从 PTQ 好的参数出发 学习率降到原来的 1/100 量级;QAT 初始化用 PTQ 结果
INT8 模型跑出的却是 FP32 速度 引擎没识别量化参数,静默回退浮点执行 检查 Q/DQ 节点或量化参数表格式与引擎约定一致;看日志确认 kernel 类型
多线程/多帧并发时结果错乱 引擎上下文/arena 被多路推理共享冲突 每路独立 arena 与 context,或加锁串行化推理入口
📋 上线前五分钟检查清单: ① 量化模型与 FP32 基线在实机数据集 上的精度回归报告;② 引擎日志确认所有算子落在目标设备(无 CPU 回退);③ 板端时延 = 预热后 100+ 帧均值,含 P95;④ 峰值内存(权重 + arena + 引擎)实测值;⑤ 输入预处理与训练侧逐算子一致(归一化/色域/插值)。五项齐全才允许合入发布分支。
5.2 实战:PyTorch 量化流水线代码 + LeNet 精度对比实验
动态量化 最简单:权重提前转 INT8,激活在推理时动态量化,只适合 Linear/LSTM 类算子。FX/静态 PTQ 是端侧标准做法:整图捕捉 → 插观测器 → 校准 → 转换。下面是一套可直接跑的完整流水线(MNIST LeNet 为例):
import torch, torch.nn as nn
from torch.ao.quantization import quantize_dynamic, get_default_qconfig
# ---------- 1) 动态量化:3 行搞定,只量 nn.Linear / LSTM 等算子 ----------
model_fp32 = my_lenet.eval() # 训练好的 FP32 模型
model_dyn = quantize_dynamic(
model_fp32.cpu(), {nn.Linear}, dtype=torch.qint8)
# ---------- 2) FX 静态 PTQ:校准数据 → 统计分布 → 算 scale ----------
from torch.ao.quantization import quantize_fx, fuse_fx
example_input = torch.randn(1, 1, 28, 28) # 校准样本,与训练同分布
qconfig_dict = {"": get_default_qconfig("qnnpack")} # Arm 端用 qnnpack,x86 用 x86
model_fused = fuse_fx(model_fp32.cpu().eval(), example_input) # conv+BN+ReLU 先融合
model_pre = quantize_fx.prepare_fx(model_fused, qconfig_dict)
with torch.inference_mode(): # 校准:只前向,统计 min/max
for _ in range(100):
model_pre(torch.randn(1, 1, 28, 28))
model_int8 = quantize_fx.convert_fx(model_pre) # 伪量化 → 真 INT8 模型
# ---------- 3) 导出 ONNX,交给 NCNN / MNN / TensorRT ----------
torch.onnx.export(
model_int8, example_input, "lenet_int8.onnx",
opset_version=13, input_names=["input"], output_names=["logits"])
# 之后:onnx2ncnn / pnnx 转 NCNN;或 MNNConvert 转 .mnn 上板
LeNet INT8 量化前后对比实验设计 (建议当成你的第一次量化实验,所有指标自己测、自己填表):
步骤 操作 记录指标 通过标准(参考)
① 基线 训练 LeNet 至收敛,测 FP32 精度、模型大小、PC 端时延 Top-1 ≈ 99.2%,约 1.7 MB 作为三列基线
② 动态量化 quantize_dynamic 只量 Linear 精度/大小/时延 精度几乎无损(−0.1% 内)
③ 静态 PTQ FX prepare → 100 张校准 → convert,全模型 INT8 精度/大小/时延;逐层余弦相似度 精度掉点 ≤ 0.5%,模型 ≈ 0.45 MB(4×)
④ 逐层对拍 FP32 与 INT8 同输入跑前向,比对每层输出 余弦相似度 / SQNR 各层相似度 ≥ 0.999,末层重点盯
⑤ 上板 导出 ONNX → NCNN(MNN/tflite-micro)→ 板端跑测速 板端时延、内存峰值 相对 FP32 提速 2× 以上(实测为准)
⑥ (选做)QAT 若 PTQ 掉点超标:prepare_qat → 小学习率微调 2~3 epoch → convert 与 PTQ 对比 精度恢复到 −0.2% 内
# 实验主循环:三个模型同一套测试集,输出对比表
import os
def evaluate(model, loader):
model.eval(); correct = total = 0
with torch.inference_mode():
for x, y in loader:
correct += (model(x).argmax(1) == y).sum().item()
total += y.size(0)
return correct / total
def size_mb(model):
torch.save(model.state_dict(), "_tmp.p")
s = os.path.getsize("_tmp.p") / 1e6; os.remove("_tmp.p"); return s
for name, m in [("FP32", model_fp32), ("Dynamic", model_dyn),
("PTQ-INT8", model_int8)]:
print(f"{name:10s} acc={evaluate(m, test_loader):.4f} "
f"size={size_mb(m):.3f}MB") # 期待:精度微降,大小降到约 1/4
🎤 面试怎么问: "说说 PyTorch 里 PTQ 的五步流程" —— fuse(可选)→ prepare_fx(插观测器/伪量化)→ 喂校准数据前向 → convert_fx(统计值落地为 scale)→ export;再加一句"qnnpack/x86 后端要与目标硬件一致,否则数值和性能都对不上"。
⚠️ 易错点: ①量化前必须 model.eval() 且模型在 CPU(BNB 后端限制),带 BN 的模型先 fuse 才能贴近真实部署;②校准的 torch.randn 要换成真实测试图像(同归一化),随机噪声校准出的 scale 是错的;③导出 INT8 ONNX 后,接收方引擎的量化算子格式(Q/DQ 节点 vs 量化参数表)要对上,否则会静默按 FP32 跑。
✅ 通关建议: 本页的产出物就是那张你自己填出来的"LeNet 量化对比表"(精度/大小/时延 × FP32/动态/静态 INT8 三列)。表填不出来,说明流水线没跑通;表填出来了,你就具备了 NPU 团队实习生级别的量化实操能力。下一步把测试集换成自己摄像头拍的图,体验"分布漂移导致掉点"的真实感。
5.3 NPU 工程师视角:量化如何反向塑造 RTL 设计
是什么: 量化不只是算法侧的事 —— 你选定的量化方案,直接决定 NPU 数据通路的每一根线。为什么: 算法侧的"精度承诺"要靠硬件侧的"位宽预算"兑现,两端必须共同设计(衔接 08-07 脉动阵列与本板块后续 MAC 阵列 RTL 页 08-11)。
INT8 MAC: 乘法器面积随位宽近平方增长,8×8 无符号乘法阵列比 16×16 小约 3/4 —— 这就是 NPU 宣称"INT8 TOPS = 4×FP16"的物理来源;MAC 阵列输入是否支持 int8 有符号、是否复用同一阵列跑 INT4/INT16,是微架构第一个要拍板的问题。
累加位宽: INT8×INT8 乘积是 16 bit,连续累加 K 项还需加 ⌈log2 K⌉ 位:3×3 卷积 K=9 时约 16+4=20 bit,加上激活通道维度冗余,INT32 累加器 是行业标准答案(TFLite 全整型规范即按 INT32 累加设计)。
饱和处理: 累加器溢出时必须饱和(saturate)而非回绕(wrap) ,否则一个大数值变成负数,输出直接花屏;最终 requantize 用"定点乘数 + 移位"完成 scale 换算:qout = saturate_int8((acc × M) >> shift) ,per-channel 量化意味着每个输出通道一组 (M, shift) 寄存器 。
对验证的联动要求: RTL 验证要用量化对拍脚本(同输入比对 NPU 输出与 Python 位精确模型),位精确(bit-exact)是 NPU 验证的金标准。
ACC_{bits} = 8 + 8 + \lceil \log_2 K \rceil \;\le\; 32 \qquad q_{out} = \mathrm{saturate}_{int8}\!\left(\dfrac{acc \times M + 2^{shift-1}}{2^{shift}}\right)
🎤 面试高频双连(算法 × 硬件各答一半): "量化为什么掉精度?" —— 表示精度从连续降到 256 级台阶(舍入),异常值撑大 scale(截断),误差逐层累积(首层末层敏感);"per-channel 为什么好?" —— 各通道独立定标吃满整数刻度,硬件代价只是每通道一组 multiplier/shift 寄存器,收益/代价比极高。能从算法和 RTL 两侧同时作答的候选人,在 NPU 团队面试里非常稀缺。
⚠️ 易错点: ①别把累加器做成 INT16 "省面积" —— 20 bit 的真实需求摆在那里,溢出在验证后期才暴露是灾难;②per-channel 的 (M, shift) 切换时机在输出通道粒度 ,流水线里要用旁路寄存器预取下一组参数,否则每通道都要插入气泡;③硬件饱和逻辑要在中间累加阶段就位,不是只在最终输出端做一次。
6 高频疑问 FAQ 与术语速查
本节收录量化/部署学习中最常被问到的"边缘问题",以及大模型时代的新概念与本页 CNN 量化的关系,建议通读一遍再进入自测。
量化参数(scale/zero_point)最终存在哪里?硬件怎么拿到?
存在模型文件里:ONNX 里是 Q/DQ 节点携带的量化参数,TFLite flatbuffer 里是每个张量的 quantization 元数据,NCNN/MNN 转换后写进各自的权重文件;推理引擎加载时读出并交给运行时,NPU 上则由编译器把 per-channel 的 (multiplier, shift) 编译进指令流或常量表。所以"量化方案"从来不是只属于算法或只属于硬件 —— 它是双方共同遵守的数据契约。
为什么不能直接在 INT8 上训练(原生低比特训练)?
整数没有连续梯度、round 不可导,反向传播在数学上无法直接进行;现有方案要么像 QAT 一样"浮点训练 + 伪量化模拟",要么用随机舍入、低精度梯度累加等技巧逼近(如 8-bit 训练、二值网络等研究方向)。端侧工程界的共识:训练归浮点世界,部署归整数世界,QAT 是两者之间的桥 —— 这也是为什么量化参数必须作为"契约"从训练侧一路传递到硬件侧。
INT4、FP8 又是什么?和 INT8 什么关系?
INT4 把整数刻度压到 16 级,模型再小一半以上,常见做法是"权重 INT4 + 激活保持 FP16"(权重-only 量化),靠 per-channel / 分组(group-wise)量化兜住精度;FP8 (E4M3/E5M2 两种格式)是新一代 GPU 的 8 位浮点 类型 —— 位宽与 INT8 相同但保留指数位,动态范围更大,主要服务大模型训练/推理。一句话:INT8 是 NPU 端 CNN 部署主力,INT4/FP8 是大模型时代向更低位宽的自然延伸。
大模型量化常听到的 GPTQ / AWQ / llama.cpp,和本页讲的 PTQ/QAT 是什么关系?
它们都属于训练后量化(PTQ)大家族 ,只是"怎么选量化参数"更聪明:GPTQ 用二阶信息逐层最小化量化误差;AWQ 依据激活统计保护少数"重要权重通道";llama.cpp 的 GGUF 用混合位宽(不同层不同 bit)。与本页 CNN 全整型方案的最大差异:LLM 量化通常只量权重、激活保持高精度 ,因为 Transformer 激活分布有大量离群值,全整型极难。原理根基( scale、round、clip )完全一致。
量化后的模型还能继续训练吗?
真整型模型不能训练 :整数没有梯度,round 不可导。QAT 的本质是在浮点副本 上插入伪量化节点"模拟"量化效果做训练,训练完成后 convert 成真整型模型用于部署。记住分工:训练在浮点世界,推理在整数世界,QAT 是两个世界之间的"预演"。
为什么激活不做 per-channel 量化?
权重的取值范围是静态 的(部署前就定死),per-channel 只是给每个输出通道存一组常量 (multiplier, shift),寄存器堆就能放下;激活的范围随输入变化 ,逐通道 scale 意味着推理时逐通道在线统计与存储,硬件代价和调度复杂度剧增。所以业界默认:权重 per-channel + 激活 per-tensor。
校准数据可以用随机噪声凑数吗?为什么强调"与实机同分布"?
不能。校准的全部产出就是每层激活的 min/max(或分布直方图),scale 由分布决定 —— 拿噪声校准等于给一个不存在的输入分布定标,实机数据一进来大量值被 clip,精度崩掉。正确做法:从实机传感器 采集几百张覆盖典型工况(不同光照/姿态/场景)的数据做校准。
tflite-micro 的 tensor arena 一直分配失败,怎么排查?
三步:① 打开框架的内存报告(记录每算子 arena 占用),看峰值出现在哪一层;② 常规手段:输入分辨率降一档、深度可分离卷积替换标准卷积、确认全模型 INT8(而不是 int16);③ arena 大小二分逼近:先给大跑通,再逐步缩小到刚好通过。注意 arena 除模型激活外还要留算子临时缓冲,别按"激活总量"贴地估算。
量化、剪枝、蒸馏怎么组合成完整压缩流水线?
典型顺序:蒸馏(大模型教小模型)→ 剪枝(2:4 或通道级)→ 微调恢复 → 量化(PTQ/QAT)→ 导出部署 。原则:越靠近训练的变换先做(蒸馏/剪枝都在训练期),量化放最后收尾;每两步之间留出恢复期,每步都和 FP32 基线对拍。切忌三件套一步到位同时做。
PTQ Dynamic 和 PTQ Static 有什么区别?(面试常问)
PTQ Dynamic(动态量化) 只提前量化权重,激活在推理时按每个 tensor 的实际范围在线算 scale —— 零校准、精度好,但"在线算 scale"本身有开销,且多数 NPU 不支持动态激活量化,主要用在 CPU 上加速 Linear/LSTM。PTQ Static(静态量化) 权重和激活都提前定死 scale(靠校准数据),是 NPU/端侧部署的标准形态 —— 硬件全程纯整数运算。记忆:动态省校准、静态上板子。
什么是混合精度量化?什么时候用它?
混合精度量化 = 不同层用不同位宽:对误差敏感的少数层(首层、末层、深度可分离卷积通道数少的层)保持 FP16/INT16,其余层 INT8,在精度与收益间逐层做权衡。典型工作流:先全模型 INT8 → 逐层对拍找"最伤精度的层" → 只把这几层升位宽 → 复测。它也是排查掉点的诊断工具:如果"只保护首末层"就能恢复大部分精度,说明误差确实集中在两端。
机器人端侧推理为什么一般 batch=1?这和量化部署有什么关系?
机器人感知是流式任务:相机一帧来一帧,推理必须跟着帧率走,攒不满 batch;而且 batch 越大时延越高,控制闭环等不起。这决定了端侧优化方向与服务器不同:不追求吞吐,追求单帧时延与确定性 —— 所以 kernel 要针对 batch=1 手工优化(如 ncnn 的做法),量化收益也要按"单帧省多少毫秒"来评估,而不是服务器那边"每秒多处理多少张图"。
术语速查表(随用随查)
术语 一句话解释
scale / zero_point 量化的两个参数:s 是一个整数台阶代表的实数步长,z 记录实数 0 落在哪个整数上
对称 / 非对称量化 对称:z=0、区间取 ±max(权重常用);非对称:z≠0、区间任取(激活常用)
per-tensor / per-channel 一组 (s,z) 作用于整个张量 / 每个输出通道各一组(s,z)
PTQ Post-Training Quantization,训练后量化:喂校准数据统计分布,不训练
校准(Calibration) PTQ 中用代表性数据统计每层激活的 min/max 或分布,以确定 scale
KL / 熵校准 枚举截断点选量化前后分布 KL 散度最小者,TensorRT 经典方法
QAT Quantization-Aware Training,训练中插入伪量化节点让网络适应量化噪声
伪量化(fake quant) 前向时"量化→反量化"模拟整数台阶,权重仍存 FP32
STE 直通估计器:round 梯度近似为 1,让反向传播穿过伪量化节点
2:4 稀疏 每 4 个连续权重至少 2 个为 0,Ampere Sparse Tensor Core 原生 2× 加速
算子融合 conv+BN+ReLU 等合成一个算子,BN 折叠进权重,消灭中间张量访存
tensor arena TFLM 的静态内存池:编译期规划所有张量、生命周期复用,零 malloc
requantize INT32 累加结果回到 INT8 的过程:乘定点 multiplier、移位、饱和
对拍(bit-exact / 余弦相似度) 量化模型与 FP32 模型同输入比对输出,验证数值正确性的手段
opset / Q-DQ 节点 ONNX 算子集版本;QuantizeLinear/DequantizeLinear 节点是 ONNX 里表达量化的标准方式
混合精度量化 敏感层 FP16/INT16、其余层 INT8,逐层权衡精度与收益
ARM NEON / Helium(MVE) Arm 的 SIMD 向量扩展:NEON 服务 Cortex-A,MVE 服务 Cortex-M55/M85,是移动端/MCU 算子库提速的底层依赖
权重-only 量化 只量化权重、激活保持高精度的方案(INT4 权重 + FP16 激活),大模型量化主流形态
动态量化(PTQ-Dynamic) 只量权重、激活推理时在线算 scale:零校准,适合 CPU 加速 Linear/LSTM,NPU 基本不支持
静态量化(PTQ-Static) 权重与激活的 scale 都靠校准提前定死:NPU/端侧部署标准形态,硬件全程纯整数
7 精选资源与延伸阅读
以下 12 条资源均已于 2026-09 逐一核实可访问(论文/官方文档/官方仓库/官方博客/可播放视频),按"理论 → 工具 → 中文社区"排序,⭐ 为强烈推荐。
📄
⭐ Google INT8 白皮书:Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference
量化领域奠基论文(Jacob et al., 2017):提出全整型 INT8 量化方案与量化感知训练(QAT),TFLite 的 scale/zero_point 设计即源于此。本页 2.2/3.2 节的理论源头。
arXiv 论文 ⭐ 必读 进阶
📘
⭐ PyTorch 官方量化文档(torch.ao.quantization)
动态量化 / FX 静态 PTQ / QAT 三条路径的官方权威文档(新版本正向 torchao/PT2E 迁移)。本页 5.2 节代码的直接依据,动手前先通读一遍。
官方文档 ⭐ 配套代码 中级
📄
⭐ NVIDIA 官方博客:Accelerating Inference with Sparsity Using the NVIDIA Ampere and TensorRT
2:4 结构化稀疏的官方定义与实测:A100 Sparse Tensor Core 理论吞吐 2×(INT8 1248 TOPS),TensorRT 实测 ResNeXt-101 性能 +20%、性能/功耗比 +36% 且精度无损。本页 4.1 节数据出处。
NVIDIA 官方 ⭐ 2:4 权威 进阶
🔧
⭐ Tencent ncnn:手机端极致优化推理框架(23k+ Star)
纯 C++ 零第三方依赖,ARM NEON 手工优化,支持 INT8 量化与 FP16,走 pnnx/ONNX 从 PyTorch 转入;QQ/微信等腾讯产品生产验证。端侧 CPU 部署首选之一(.param + .bin 双文件格式)。
GitHub 开源 ⭐ 上手部署 中级
🔧
Alibaba MNN:轻量级推理引擎(淘宝/天猫等 30+ App 验证)
CPU/GPU(OpenCL/Vulkan/Metal)/NPU 多后端,支持训练与 MNN-LLM 端侧大模型;Apache 2.0 协议,转换器支持 ONNX/TensorFlow/Caffe/TorchScript 输入。
GitHub 开源 中级
🔧
TensorFlow Lite Micro(TFLM):MCU 级推理框架
TFLite 的微控制器移植版,静态内存 tensor arena、零动态分配,支持 Cortex-M/RISC-V/Hexagon/Xtensa;全整型 INT8 量化规格与 CMSIS-NN 位精确对齐。Cortex-M 部署标准答案。
官方开源 中级
🔧
Arm CMSIS-NN:Cortex-M 神经网络优化内核库
int8/int16 高效 kernel,自动利用 M4 的 DSP SIMD 与 M55/M85 的 Helium(MVE)指令,目标"最大化性能、最小化内存";与 TFLM 量化规范位精确一致,配合使用。
Arm 官方 中级
📘
ONNX 官网:开放神经网络交换格式
框架无关的标准算子集 + 文件格式,是"训练框架 → 端侧引擎"转换链路的枢纽;部署前了解 opset 版本与 Q/DQ 量化算子约定必备。
官方文档 入门
🔧
AMD/Xilinx Vitis AI:FPGA/自适应平台 AI 推理开发栈
含 DPU IP、模型 zoo、量化器与编译器,完整走通"量化 → 编译 → DPU 部署";想理解"NPU 工具链长什么样"最好的开源参照(与 08-11 节 RTL 视角互补)。
GitHub 开源 进阶
📄
PyTorch 官方博客:Accelerating Neural Network Training with Semi-Structured (2:4) Sparsity
PyTorch 原生 2:4 稀疏支持(to_sparse_semi_structured)官方教程:剪枝到 2:4 掩码、转稀疏格式、加速推理的完整代码路径,配合本页 4.1 节食用。
官方博客 进阶
📄
⭐ 知乎·老潘:老潘的 AI 部署以及工业落地学习之路
高赞部署工程师经验帖:PTQ/QAT 对比、TensorRT INT8 实战、onnx2trt/torch2trt 转换链路与量化工具链选型;知乎若访问受限可读
腾讯云开发者社区转载版 。
知乎专栏 ⭐ 中文实战 中级
🎬
⭐ B站·ZOMI酱:训练后量化 PTQ 深度解读!与量化部署核心原理【推理引擎】
模型压缩系列第 4 集(14 分钟):PTQ Dynamic/Static 两条路线、校准原理与量化部署核心逻辑,动画讲解直观,适合配合本页 3.1 节食用;同系列还有剪枝/蒸馏/量化训练各集。
B站视频 ⭐ 入门视频 入门
8 本节自测
1. 模型从 FP32 量化到 INT8 后,关于"四本账"的收益,下列说法正确的是?
A. 只有模型文件变小,推理速度基本不变
B. 存储、带宽各省约 4×,但计算能耗不变
C. 存储约 4× 缩减、带宽约 4× 缩减、访存能耗约降至 1/4,且 NPU 的 INT8 算力面积效率远高于 FP32
D. 四本账收益都来自"乘法变快",与数据搬运无关
💡 每权重 4 字节→1 字节,存储/带宽同步约 4×;访存能耗随数据宽度近似线性下降,所以搬运能耗约 1/4;此外 INT8 乘加逻辑远小于浮点路径,同面积可放数倍 MAC。注意 A 错在忽略了带宽与算力收益,D 错在 NPU 常是访存受限,带宽那本账往往最关键。
2. 非对称量化 q = clip(round(r/s) + z, 0, 255) 中,zero_point z 的作用是?
A. 放大量化后数值的范围,提升动态范围
B. 记录实数 0 落在哪个整数刻度上,保证 0 能被精确表示(padding、ReLU 输出不受量化偏移影响)
C. 充当激活函数的偏置,替代 bias 参与卷积
D. 用于加密模型权重,防止模型被复制
💡 z = round(−r_min/s),它把实数域的 0 映射到整数域的某个刻度,使 0 被无损表示;若 0 映射有误差,卷积 padding 和 ReLU 的 0 都会被量化成 ±s/2 的偏移并逐层放大。z 与"扩范围""替代 bias"无关。
3. 某模型 per-tensor 权重量化后精度明显下降,改用 per-channel 权重量化后恢复,原因最可能是?
A. 各输出通道的权重动态范围差异大,per-tensor 用最大通道定 scale 使小范围通道的有效分辨率被浪费
B. per-channel 能减少模型的参数量,模型变小所以精度变高
C. per-channel 把激活也逐通道量化了,表示更精细
D. per-channel 增大了累加器位宽,减少了舍入误差
💡 通道间权重范围可能差一个数量级,per-tensor 按最大通道定标会让小范围通道只占 256 级刻度中很窄一段,舍入误差占比升高;per-channel 让每个通道独立吃满 ±127。注意:通常只有权重做 per-channel,激活仍用 per-tensor(C 错),累加器位宽不变(D 错)。
4. 关于 PTQ 的校准方法,下列说法错误的是?
A. Min-Max 校准取激活的最小/最大值定量化区间,简单但对异常值敏感
B. 熵(KL)校准枚举候选截断点,选量化前后分布 KL 散度最小的那个
C. 百分位校准取 99.9% 分位数当上限,截掉极端尾部以对抗 outlier
D. 熵校准的本质是找到激活的最大值作为截断点,与 Min-Max 结果相同
💡 KL/熵校准的目标是"量化后信息损失最小":它会在多个候选截断点里选一个往往小于最大值的点,主动牺牲极端尾部换取主体分布的分辨率 —— 正是它抗异常值的原因,结果通常不等于 Min-Max,D 错误。A/B/C 均为正确描述。
5. QAT 中必须使用直通估计器(STE),是因为?
A. STE 可以把 INT8 数值直接转换为 FP16,加速训练
B. STE 用于增大学习率,让微调收敛更快
C. round 是阶梯函数、梯度几乎处处为 0,STE 把该处梯度近似为 1,让反向传播的梯度"直通"伪量化节点
D. STE 是一种正则化手段,防止过拟合
💡 伪量化前向是 量化→反量化(含 round),round 的导数几乎处处为 0,梯度链在节点处断裂;STE 在反向时把 ∂r̂/∂r 近似为 1,梯度原样回传,训练才能进行。它与学习率、正则化、数值格式转换无关。
6. 关于 2:4 结构化稀疏,下列说法正确的是?
A. 指每 4 个权重中最多保留 2 个非零,稀疏位置任意,通用 CPU 都能获得 2× 加速
B. 每连续 4 个权重至少 2 个为 0,NVIDIA Ampere 起 Sparse Tensor Core 硬件原生支持,数学吞吐约 2×,剪枝后需微调恢复精度
C. 2:4 稀疏是纯软件压缩技术,只能减小模型文件,不能加速计算
D. 2:4 剪枝后无需微调,精度天然无损
💡 2:4 = 每组 4 个连续值至少 2 个为 0(50% 稀疏度),Ampere Sparse Tensor Core 硬件跳零、成对计算,INT8 吞吐达稠密 2 倍(A100 上 1248 TOPS);NVIDIA 三步法要求剪枝后微调恢复精度(D 错),且加速依赖专用硬件支持(A/C 错)。
7. 端侧部署时,模型在引擎上"能跑通但时延比预期慢好几倍",最常见的原因是?
A. 部分算子不被引擎/NPU 支持,静默回退到 CPU 执行
B. 模型量化后 scale 太大,计算量反而增加
C. ONNX 文件太大导致下载慢
D. 推理引擎的版本号太旧,日志打不出来
💡 转换后必须核对算子支持度表/引擎日志,确认每个算子落在哪个设备上:不支持的自定义或冷门算子会静默回退 CPU,精度不坏但时延爆炸。这是部署实战(5.1 节)的第一大坑,配合 Netron 查图 + 引擎 verbose 日志定位。
📌 本节小结: ①量化是端侧部署第一课:INT8 相对 FP32 存储约 4× 缩减、带宽约 4×、访存能耗约 1/4,且 NPU 的 INT8 算力面积效率数倍于浮点;②量化数学只靠两个参数 —— scale 定步长、zero_point 定零点,权重常用对称 + per-channel,激活常用非对称 + per-tensor;③两条量化路线:PTQ(校准数据统计分布,min-max/KL/百分位三法)先跑,QAT(伪量化 + STE 微调)救场;④误差来自舍入、截断与逐层累积,首层末层最敏感,per-channel 与混合精度是第一救命稻草;⑤稀疏化与量化正交:2:4 结构化稀疏有 Ampere Sparse Tensor Core 硬件跳零 2× 加速,先剪后量收益叠加;⑥推理引擎靠图优化(算子融合省访存)、tensor arena(静态内存规划)与算子调度赚钱;⑦量化反向塑造 NPU RTL:INT8 MAC、INT32 累加器 + ⌈log2 K⌉、饱和与 per-channel multiplier/shift,算法与硬件必须共同设计。
🤔 思考题:
1. 你的机器人头部 SoC 的 NPU 是 6 TOPS INT8:如果模型坚持用 FP32,等效算力大约还剩多少?这对你同时跑"检测 + VSLAM + 语音"三路感知意味着什么?
2. 一个检测模型 PTQ 后 mAP 从 0.512 掉到 0.465:请给出你的排查顺序(逐层对拍?校准数据分布?首末层混合精度?per-channel?),并说明每一步在验证什么假设。
3. 如果让你为 08-11 节的 NPU MAC 阵列写 RTL:量化方案选 per-channel INT8 后,你的数据通路需要额外增加哪些部件?位宽账怎么算?
4. 2:4 剪枝 + INT8 量化的模型,权重存储理论上压缩到 FP32 基线的几分之一?为什么实际文件往往达不到这个数(提示:想想哪些部分没法稀疏化、索引元数据占多少)?
9 参考来源
以下链接均于 2026-09 逐一核实可访问。理论以 Google INT8 白皮书与 PyTorch 官方文档为纲,工具以官方仓库为准,中文社区选高赞实战文/视频。