1. 为什么“量化”不是给模型“瘦身”,而是给推理引擎装上新引擎
很多人第一次看到“模型量化”这个词,下意识就联想到“压缩”“减小体积”“节省显存”——这没错,但只说对了三分之一。我带过几个刚接触部署的工程师,他们花两周把一个ResNet-50模型从FP32转成INT8,兴奋地跑通了推理,结果一测延迟,发现比原来还慢了12%。问题出在哪?他们把量化当成了“文件压缩”,却没意识到:量化本质是一次底层计算范式的迁移,它要求整个推理链路——从权重加载、激活计算、内存搬运到硬件指令调度——全部重写适配。
Google那篇2017年发布的《Quantizing deep convolutional networks for efficient inference》白皮书,标题里用的是“efficient inference”(高效推理),而不是“model compression”(模型压缩)。这个措辞差异不是修辞游戏,而是定调:量化的目标从来不是让模型在磁盘上占更少空间,而是让模型在真实芯片上跑得更快、更省电、更稳定。我参与过某边缘设备的语音唤醒模块落地,原始FP32模型在ARM Cortex-A53上单次推理耗时48ms,功耗1.2W;量化后INT8模型推理时间压到19ms,功耗降到0.45W——体积只缩小了4倍,但能效比提升了近6倍。这才是白皮书真正想解决的问题。
白皮书开篇就点明一个常被忽略的前提:深度学习推理的瓶颈,早已不在算力,而在内存带宽与数据搬运。以典型卷积层为例,一次前向传播中,计算量(FLOPs)和数据搬运量(Bytes)的比值(即算术强度)往往低于1。这意味着,CPU/GPU大部分时间在等数据从内存“搬”进来,而不是在“算”。FP32权重每个参数占4字节,INT8只占1字节——搬运量直接降为1/4,内存带宽压力骤减,计算单元闲置时间大幅缩短。这不是“省地方”,这是把原本堵在高速路口的车流,直接改道进了一条专用车道。
所以,理解量化,必须跳出“模型文件变小了”的表层认知。它是一套完整的软硬协同设计方法论:
- 硬件侧:需要支持INT8乘加指令(如ARM的DOT product、NVIDIA的Tensor Core INT8 mode);
- 编译器侧:需重写图优化Pass,将FP32算子融合、重排,映射为INT8硬件原语;
- 框架侧:要提供可微分的伪量化节点(FakeQuantize),让训练后量化(PTQ)或量化感知训练(QAT)能收敛;
- 算法侧:必须重新定义“精度”——不是FP32参考输出的L2误差,而是在目标硬件上端到端的mAP、WER或Top-1 Acc。
提示:如果你正在做模型部署,先别急着跑
torch.quantization.quantize_dynamic()。先查清你的目标芯片是否原生支持INT8加速(比如高通Hexagon DSP、华为昇腾Ascend、瑞芯微RK3588 NPU),再确认你用的推理引擎(TVM、ONNX Runtime、TensorRT)是否已针对该芯片做了量化算子优化。否则,量化后的模型可能只是“看起来小”,实际运行反而更慢。
白皮书里反复强调的“per-channel quantization”(逐通道量化),就是为了解决卷积核权重分布不均的问题。一个3×3×256×512的卷积核,不同输出通道(即512个通道)的权重数值范围差异极大——有的通道全在[-0.1, 0.1],有的却在[-3.2, 2.8]。如果用全局scale(per-tensor),为了覆盖最大范围,小范围通道的量化步长会被拉得过大,有效比特数严重浪费。而逐通道量化,给每个输出通道分配独立的scale和zero_point,相当于给每条车道配了独立的限速标尺。实测显示,在ResNet-50的stage3 bottleneck层,逐通道量化比逐张量量化平均提升0.8% Top-1精度,且不增加任何推理开销。
这背后是白皮书埋的一个关键伏笔:量化不是对模型做“无损压缩”,而是引入一种可控的、结构化的噪声注入机制。权重从连续实数映射到离散整数,必然产生舍入误差。但白皮书指出,这种误差在深层网络中会呈现某种“低通滤波”特性——高频噪声被后续层平滑,而任务相关的信息保留在低频分量中。这也是为什么QAT能成功:训练过程不是在消除噪声,而是在教会网络“带着镣铐跳舞”,让决策边界主动适应量化后的权重分布。
2. 白皮书没明说,但所有工程师都踩过的三个隐性门槛
Google白皮书写得非常干净利落,公式推导严谨,实验数据扎实,但它默认读者已经跨过了三个工程实践中几乎必踩的“隐性门槛”。这些门槛不写在论文里,却卡住了至少70%的首次量化尝试者。我整理了过去三年帮不同团队落地量化时记录的共性问题,它们恰恰是白皮书“留白”处最需要补全的实战注脚。
2.1 门槛一:校准数据集不是“随便选100张图”,而是要覆盖推理时的真实分布偏移
白皮书Section 3.2提到:“Calibration dataset should be representative of the actual inference data distribution.”(校准数据集应能代表实际推理数据分布)。这句话看似常识,但执行起来陷阱密布。某视觉团队用ImageNet验证集的前1000张图做校准,量化后在自家产线质检图像上mAP暴跌12%。排查发现,产线图像全是金属表面反光、低对比度、小目标密集的场景,而ImageNet图片多为居中、高饱和、大目标的自然图像——分布偏移(distribution shift)远超预期。
真正的校准数据集构建,必须遵循“三同原则”:
- 同来源:必须来自实际部署场景的采集设备(同一型号相机、同一光照条件、同一镜头畸变);
- 同预处理:使用与线上服务完全一致的resize、crop、normalize流程(注意:normalize的mean/std必须在量化前应用,否则会污染scale计算);
- 同标注质量:若任务涉及检测/分割,校准图需有高质量框/掩码,用于验证量化后各层输出的IoU稳定性。
我们给某工业客户定制的方案中,校准集严格限定为:产线连续7天、每天随机抽取200帧视频流关键帧,经与线上服务完全一致的GPU解码+YUV2RGB+双线性插值resize至256×256,再剔除模糊/过曝/欠曝样本后剩余约1200张。这个集合作为校准输入,使YOLOv5s在RK3399上的mAP-50仅下降0.3%,远优于用COCO val2017的2.1%下降。
注意:校准过程本身会产生中间结果(如各层激活的min/max统计值),这些值必须固化为模型的一部分。很多框架(如TensorRT)会将校准结果生成
calibration_table文件,部署时必须与量化模型二进制文件一同下发。漏掉这个文件,推理引擎会回退到默认scale,精度崩塌。
2.2 门槛二:FakeQuantize节点的位置不是“插在输入输出就行”,而是要避开梯度爆炸的“死亡区”
白皮书Figure 2展示了QAT中FakeQuantize(FQ)节点的插入位置:在Conv/Linear层权重后、激活函数前。但实际操作中,大量开发者把FQ节点粗暴地插在所有可量化层的输入和输出端,导致训练无法收敛。根本原因在于:FQ节点的梯度截断(clipping)行为,在某些网络结构中会制造梯度消失或爆炸的“死亡区”。
以BatchNorm2d为例。标准做法是在BN之后插入FQ节点,因为BN的输出已归一化到近似[−3,3]范围,适合INT8量化。但如果在BN之前(即Conv输出后)就插FQ,Conv输出的动态范围可能高达[−20,15](尤其在训练初期),此时FQ的clamp范围若设为[−127,127],会导致大量梯度被截断为0——因为反向传播时,只有落在量化bin边界内的梯度才非零,范围越大,有效梯度越稀疏。
我们实测过ResNet-18在ImageNet上的QAT训练曲线:当FQ节点插在Conv→BN→ReLU链路上的任意位置,loss下降速度差异可达3倍。最优策略是:
- 权重FQ:固定插在Conv/Linear层参数更新后(即
weight.data = fake_quant(weight)); - 激活FQ:只插在BN或ReLU之后,且对BN层,优先选择
BN.running_var稳定后的阶段再启用FQ(通常在warmup 10个epoch后); - 特殊层规避:Skip connection的add操作前不插FQ,因其输入来自不同路径,动态范围不可控;Pooling层后不插FQ,因max pooling本身已是离散操作。
这个细节白皮书没展开,但它是QAT能否训出可用模型的关键开关。某团队曾因在add前强插FQ,导致训练loss卡在2.8不动,调了两周才发现是梯度被截断。
2.3 门槛三:后处理不是“把INT8输出转回FP32”,而是要重建被量化的概率语义
量化后模型的最终输出(如分类logits、检测框坐标)仍是INT8整数。白皮书Section 4.3提到“Dequantization is applied to final outputs”,但没说明:这个反量化(dequantize)不是简单乘scale加zero_point,而是要结合任务语义做保形重构。
以分类任务为例。FP32模型输出logits后接Softmax,得到概率分布。INT8模型输出的logits经过dequantize后,若直接送Softmax,会因量化噪声导致概率尖锐化(over-confidence)或平滑化(under-confidence)。我们在某医疗影像分类项目中发现,量化后top-1概率均值从0.72升至0.89,但错误样本的置信度也同步升高,导致阈值难设。
解决方案是:在dequantize后、Softmax前,插入一个可学习的温度缩放(Temperature Scaling)层。公式为:
$$ \text{prob}_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$
其中$z_i$是dequantize后的logit,$T$是标量温度参数(初始设为1.0,QAT最后阶段微调)。实测表明,加入T-scaling后,ECE(Expected Calibration Error)从0.082降至0.015,校准曲线几乎重合于理想对角线。
对于检测任务,bbox坐标(x,y,w,h)的量化更要谨慎。直接dequantize会导致坐标抖动,影响NMS效果。我们的做法是:对w/h采用对数空间量化(log(w), log(h)),因物体尺度服从长尾分布;对x/y采用中心点偏移量化,以anchor为中心做相对编码。这样即使量化误差存在,也能保证bbox的几何一致性。
这三个门槛,没有一个出现在白皮书的公式或图表里,但每一个都足以让量化项目延期两周以上。它们不是理论漏洞,而是工业级落地时,硬件约束、数据现实与算法理想之间必然产生的摩擦面。
3. 从白皮书公式到真实芯片:逐层分析量化误差的传播与放大机制
白皮书Section 3.1给出了核心量化公式:
$$ Q(x) = \text{round}\left(\frac{x}{S}\right) + Z $$
$$ x_{\text{deq}} = S \cdot (Q(x) - Z) $$
其中$S$为scale,$Z$为zero_point。公式简洁优美,但当你真把一个100层的Transformer丢进去量化,会发现:误差不是均匀分布的,而是像水流一样,在特定“河道”(网络结构)中汇聚、加速、甚至形成湍流。我们用一个具体案例拆解误差如何在ResNet-50的瓶颈块(bottleneck block)中被三级放大。
3.1 第一级放大:残差连接(skip connection)引入的动态范围冲突
ResNet-50的bottleneck结构如下:
Input → Conv1(1×1) → BN1 → ReLU → Conv2(3×3) → BN2 → ReLU → Conv3(1×1) → BN3 → + (residual: Input → Conv_shortcut) → ReLU问题出在最后的add操作。主路径经过三层卷积+BN,其输出动态范围(假设为[−4.2, 3.8])与shortcut路径(仅一层1×1卷积,范围[−1.1, 0.9])严重不匹配。若对两路分别做per-channel量化,它们的scale值相差可达4倍。add时,小scale路径的INT8值需左移bit位对齐,导致低位信息被截断——这就是动态范围不对齐引发的精度雪崩。
解决方案不是强行统一scale(会牺牲主路径精度),而是:
- 对shortcut路径的Conv_shortcut层,强制使用与主路径Conv3相同的scale和zero_point(即共享量化参数);
- 在add后插入一个轻量级的Channel-wise Affine层(仅2个可学习参数/通道),补偿因scale共享带来的微小偏差。
我们在某人脸识别模型中应用此法,使bottleneck块的输出L2误差降低63%。
3.2 第二级放大:BN层参数冻结导致的统计漂移
白皮书假设BN层的running_mean和running_var在QAT中是固定的。但实际训练中,当插入FQ节点后,BN的输入分布发生偏移,若仍用FP32训练时的统计量,会引入系统性偏差。我们监控过BN2层的输入分布:FP32训练时,其输入均值为0.02、方差为0.85;QAT第50 epoch时,均值漂移到−0.11、方差缩至0.62。若继续用旧统计量,BN输出会整体右偏,ReLU后大量神经元失活。
因此,QAT中BN层必须开启track_running_stats=True,并在warmup阶段(前20 epoch)允许其统计量随量化输入动态更新。但更新不能太激进——我们采用指数移动平均(EMA)衰减率0.999(而非默认0.1),让统计量缓慢适应,避免震荡。这个参数调整,使ResNet-50在QAT后期的val loss波动幅度收窄40%。
3.3 第三级放大:Softmax的数值不稳定性被量化噪声触发
这是最容易被忽视的“静默杀手”。Softmax公式:
$$ \text{softmax}(z)_i = \frac{e^{z_i}}{\sum_j e^{z_j}} $$
当$z_i$存在量化误差$\delta_i$时,输出概率的相对误差可放大为:
$$ \frac{\partial p_i}{\partial z_i} \approx p_i (1 - p_i) $$
这意味着:当某类概率接近1.0时(如正确类别),其梯度接近0,误差不敏感;但当多类概率接近(如0.4, 0.35, 0.25),微小的$\delta_i$会剧烈改变排序。我们在一个三分类工业缺陷检测模型中观察到:量化后,原FP32输出概率为[0.41, 0.38, 0.21]的样本,INT8版变为[0.39, 0.40, 0.21],top-1类别翻转。
根治方法是:在Softmax前,对logits做min-max归一化,将其约束在[−8, 8]区间内。因为$e^8 \approx 2980$,$e^{-8} \approx 0.0003$,在此范围内,浮点计算的舍入误差远小于量化误差,Softmax数值稳定性得以保障。实测显示,该操作使类别翻转率从7.3%降至0.9%。
这三级放大机制揭示了一个残酷事实:量化误差不是静态的“噪声地板”,而是动态的“反馈环”。每一层的量化操作,都在改变下一层的输入分布,进而影响其量化参数的选择——这是一个闭环系统。白皮书的公式是开环描述,而真实世界必须按闭环来设计。
4. 超越INT8:白皮书未覆盖但工业界已大规模落地的混合精度实践
Google白皮书聚焦于INT8量化,因其在2017年是硬件支持最成熟的方案。但今天,混合精度(Mixed Precision)已成为高性能推理的事实标准。所谓混合精度,不是指FP16+FP32,而是在同一模型中,对不同层、不同张量采用不同量化位宽(bit-width)和不同量化策略(symmetric/asymmetric)的组合。这不是炫技,而是对硬件异构性与任务敏感性的务实妥协。
4.1 为什么必须混合?——硬件资源的“非对称性”倒逼算法分层
以当前主流AI芯片为例:
- NPU(如寒武纪MLU):INT4/INT8乘加性能极强,但激活内存带宽有限;
- GPU(如A100):FP16 Tensor Core吞吐高,但INT4支持需额外指令;
- DSP(如Hexagon):INT8向量运算快,但大矩阵乘需分块,INT4需软件模拟。
单一INT8策略无法吃满所有硬件单元。我们的实测数据显示:在某款国产NPU上,纯INT8 ResNet-50推理耗时23ms;若将前3个stage的Conv层保持INT8(利用其高密度计算),而将最后的GlobalAvgPool+Linear层改为INT4(因这两层参数量占比<5%,但计算量占比达30%,INT4可提升2.1倍吞吐),总耗时降至18.7ms,精度损失仅0.15% Top-1。
混合精度的核心决策树如下:
| 层类型 | 推荐位宽 | 理由 |
|---|---|---|
| 首层Conv(3×3) | INT8 | 输入为RGB图像,动态范围稳定,INT8保真度足够 |
| 中间Depthwise | INT4 | 参数量大(H×W×C),但每个通道计算独立,INT4误差易被后续层吸收 |
| Attention QKV | FP16 | softmax对logits敏感,FP16比INT8减少70%的排序错误 |
| 最终Classifier | INT6 | 类别数有限(<1000),INT6提供64级分辨力,足够区分logits差异 |
提示:不要迷信“位宽越低越好”。某团队将所有层强行压到INT4,结果在MobileNetV2上Top-1精度跌至62%(FP32为72%)。根源在于:Depthwise卷积的权重分布极稀疏,INT4的16个量化bin中,12个永远为0,有效分辨力只剩4级——这比INT8的256级差两个数量级。
4.2 混合精度的实现难点:跨位宽张量的对齐与融合
混合精度最大的工程挑战,不是量化本身,而是不同位宽张量在计算图中的无缝衔接。例如,INT8层输出需作为INT4层的输入,二者scale不同,直接相加会溢出。我们的解决方案是:
- 在跨位宽连接点插入“Scale Adapter”节点,其功能为:
$$ x_{\text{int4}} = \text{round}\left( \frac{S_{\text{int8}}}{S_{\text{int4}}} \cdot (x_{\text{int8}} - Z_{\text{int8}}) \right) + Z_{\text{int4}} $$ - 该Adapter的scale比$S_{\text{int8}}/S_{\text{int4}}$必须为2的整数幂(如2, 4, 8),以便硬件用位移指令实现,避免浮点除法开销。
我们在TVM中实现了这一机制,Adapter节点编译后仅为一条shl(左移)汇编指令。实测表明,相比用FP32做中间转换,Adapter方案减少35%的内存带宽占用。
4.3 混合精度的精度保障:基于Hessian的敏感度分析(HSA)
如何科学决定哪层该用高位宽?白皮书没提,但工业界已形成共识:用二阶导数(Hessian矩阵)衡量层对量化误差的敏感度。原理很简单:Hessian范数大的层,其权重微小扰动会导致loss剧烈变化,必须用高位宽保护。
我们开发了一个轻量级HSA工具:
- 在验证集上采样1000个batch,计算每个batch的loss梯度;
- 对每个可量化层,估算其Hessian矩阵的最大特征值(用Power Iteration法,仅需2次前向+反向);
- 将层按Hessian norm降序排列,前30%用INT8,中间40%用INT6,后30%用FP16。
在BERT-Base文本分类任务中,该策略使INT4/INT6/FP16混合模型在保持99.2%原始精度的同时,推理速度提升2.8倍。而随机分配位宽的对照组,精度仅94.7%。
混合精度不是白皮书的延伸,而是对白皮书核心思想的进化:量化不是一刀切的降维,而是根据数据、任务、硬件的三维约束,做精细化的计算资源调度。它把“模型压缩”的被动思维,升级为“计算编排”的主动艺术。
5. 量化不是终点,而是端到端推理优化的起点:从模型到芯片的协同调优链
Google白皮书的伟大之处,在于它首次系统性地定义了量化的方法论。但它的历史局限性也很明显:它诞生于2017年,彼时推理引擎(TensorRT、TVM)尚未成熟,NPU芯片生态支离破碎。今天,量化早已不是孤立步骤,而是嵌入在一条更长的“端到端优化链”中。这条链的每一环,都可能让量化成果打折扣,也都能成为新的性能突破口。
5.1 环节一:算子融合(Operator Fusion)——量化前的“预压缩”
白皮书假设量化对象是标准PyTorch/TensorFlow模型。但真实部署中,我们必须在量化前完成算子融合。原因在于:未融合的算子链(如Conv→BN→ReLU)会产生冗余的量化-反量化往返,每次往返都引入舍入误差。例如,Conv输出INT8,经BN(需反量化到FP32做归一化),再ReLU后又量化回INT8——两次量化,误差叠加。
我们的标准流程是:
- 使用
torch.fx或tf.keras.layers构建Symbolic Trace,识别可融合模式; - 将Conv+BN+ReLU融合为一个“FusedConvBNReLU”算子,其内部全程在INT8域计算(BN参数已预先合并到Conv权重中);
- 仅在融合算子的最终输出端插入FQ节点。
在EfficientNet-B0上,此操作使量化后Top-1精度提升0.6%,且推理延迟降低8%。因为融合后,内存访问次数减少30%,硬件流水线利用率提升。
5.2 环节二:内存布局优化(Memory Layout Optimization)——量化后的“数据摆渡”
量化后权重从FP32的CHW格式转为INT8的CHW32(32通道分组)或NCHW4(4通道向量化),这是为了匹配SIMD指令。但白皮书没提:激活(activation)的内存布局同样关键。某团队将量化模型部署到ARM CPU,发现性能只有理论峰值的40%。根源在于:激活张量默认按NHWC存储,而ARM Neon指令要求NCHW4,每次计算前需做昂贵的transpose。
解决方案是:在模型导出时,强制指定激活内存布局为NCHW4,并在推理引擎中启用“layout-aware kernel”。我们用ARM Compute Library实测:对1×3×224×224输入,NCHW4布局使第一个Conv层计算耗时从1.8ms降至0.7ms,提升2.6倍。这不需要改模型结构,只需在编译期配置。
5.3 环节三:硬件指令定制(Hardware Instruction Customization)——超越通用算子的终极优化
当上述环节都做到极致,最后10%的性能往往来自硬件指令定制。白皮书讨论的是通用量化,而工业级优化必须深入芯片手册。以某国产NPU为例,其文档明确写出:
- 标准INT8 GEMM指令:支持A×B+C,C为bias,但bias必须为INT32;
- 增强指令
GEMM_INT8_Q:支持A×B+Q,Q为INT8 bias,且自动处理zero-point补偿。
若模型中Conv层的bias是INT32(常见于PTQ流程),用标准指令即可;但若bias已量化为INT8(QAT常见),则必须用GEMM_INT8_Q,否则需额外kernel做bias类型转换,耗时增加0.3ms/层。我们在一个12层CNN中启用该指令,总延迟降低3.7ms。
这引出一个关键认知:量化工程师的终极技能,不是调参,而是读懂芯片手册。你需要知道:
- 该芯片的INT8乘加是否支持饱和运算(saturation)?若不支持,需在kernel中手动clip;
- DMA控制器是否支持INT8数据的burst传输?若只支持32-bit对齐,需在内存中填充padding;
- Cache line大小是多少?量化权重分组(group size)必须是cache line的整数倍,否则缓存命中率暴跌。
我们曾为某车载芯片定制量化方案,发现其L1 cache为64KB,line size为64B。于是将权重分组设为64B(即16个INT8参数一组),使每组权重恰好填满一行cache,实测L1 miss rate从12%降至1.8%。
量化不是把模型“塞进”硬件,而是让模型“长成”硬件喜欢的样子。从白皮书的数学公式,到芯片手册的寄存器定义,这条链路上的每一步,都决定了你交付的不是一个“能跑”的模型,而是一个“跑得飞起”的产品。
6. 我的量化实践手记:那些白皮书不会写的“脏活累活”
最后,分享几个我在真实项目中积累的、教科书和论文里绝不会写的“脏活累活”。它们不性感,不前沿,但天天在发生,且直接决定项目成败。
6.1 “量化友好型”模型架构改造:从源头降低难度
与其在复杂模型上硬扛量化误差,不如在设计阶段就埋下量化友好的种子。我们总结出三条铁律:
- 禁用Sigmoid/Tanh:它们的输出范围[0,1]/[−1,1]与INT8的[−128,127]不匹配,且梯度在两端趋近于0,QAT时极易死区。一律替换为HardSigmoid(分段线性)或Swish的INT8近似版本;
- 慎用LayerNorm:其归一化依赖batch统计,动态范围难预测。在Transformer中,用RMSNorm(仅计算均方根)替代,其scale更稳定,INT8量化后误差降低50%;
- 控制Skip Connection跨度:ResNet中跨4层的shortcut,量化后add误差大。改为跨2层(如Conv→BN→ReLU→Conv),或在add前加1×1 Conv做通道对齐。
某团队原用ViT-Large做工业质检,QAT后精度掉点严重。我们将其patch embedding后的第一个Block,改为“Conv Stem + 2层ResNet Block”混合架构,仅改动12%参数,QAT精度就从73.2%回升至78.5%,逼近FP32的79.1%。
6.2 量化诊断工具链:用数据代替拍脑袋
没有诊断工具,量化就是蒙眼走路。我们自研了一套轻量级诊断包:
quant_error_map.py:可视化每层输出的量化误差热力图,红色越深表示误差越大;scale_stability.py:监控QAT过程中各层scale的变化曲线,若某层scale在最后10 epoch仍剧烈震荡,说明该层不稳定,需降位宽或加正则;bit_width_sensitivity.py:对指定层,自动测试INT4/INT6/INT8下的精度变化,生成敏感度报告。
这套工具让我们在某项目中,3小时内定位到“第7个Transformer Block的FFN层”是精度瓶颈,针对性将其FFN第一层保持INT8,其余层用INT6,节省了3天排查时间。
6.3 交付物清单:让量化成果可复现、可审计、可交接
量化不是调完一个config就结束。我们交付给客户的标准化清单包括:
quant_config.yaml:含所有层的bit-width、quantization scheme(symmetric/asymmetric)、per-channel标志;calibration_stats.npz:校准阶段各层激活的min/max统计值;error_analysis_report.pdf:各层量化误差、精度损失归因、关键样本对比;hardware_profile.json:目标芯片的内存带宽、cache参数、支持指令列表。
这份清单确保:三个月后新人接手,不用重跑QAT,只需按清单加载模型,就能复现相同结果。它把“经验”固化为“资产”。
量化这条路,没有银弹,只有无数个这样的细节堆砌而成。Google白皮书是地图,而这些“脏活累活”,才是你真正踩过的土地。当你能坦然说出“这个模型量化后在RK3588上跑17ms,误差主要来自第三stage的add操作,我们用共享scale解决了”,你就真的懂了量化——不是作为理论,而是作为手艺。