聊端侧AI绕不开一个词模型量化。这一篇是端侧AI入门笔记系列的第四篇就从浮点格式和INT8的差别讲起。如果你做过端侧AI硬件部署一定遇到过类似画面PC上跑得好好的FP32模型挪到手机、开发板、智能摄像头这类小设备上要么内存不够直接加载崩溃要么推理慢到没法用可换成INT8量化版本之后模型体积缩到四分之一延迟和功耗也都跟着降。模型量化就是在这样的背景下成了端侧部署最关键的临门一脚。这篇文章我打算把浮点格式本身的位布局、INT8量化的映射逻辑、实操工具链以及精度补救手段一起讲透适合刚接触端侧AI、准备把手头模型部署到小设备上的朋友也是我自己这段踩坑经历的一份整理。1. 先看账本INT8换来的空间与速度到底有多可观1.1 一个7B模型四档精度四档体积先说最直观的账。一个模型占多大内存基本等于参数量乘以每个参数的字节数。按7B参数规模的大语言模型来算FP32是4字节FP16/BF16是2字节INT8是1字节INT4只需要0.5字节于是最终体积是这样的精度单参数字节数7B模型近似占用FP32428 GBFP16/BF16214 GBINT817 GBINT40.53.5 GB28GB什么概念现在新一代旗舰手机内存普遍是12GB到16GB绝大多数开发板更是只有4GB到8GBFP32模型连装都装不下更别提运行。而INT8版本直接把峰值内存需求压到7GB虽然单看还是很大但对7B这个级别的模型来说至少让在有限内存设备上跑大模型从不可能变成了可尝试。我在实际部署时发现很多端侧场景真正卡住的不是FLOPS不够而是内存根本不够分INT8这一步换来的空间收益是最直接、最确定的。1.2 端侧算力卡在哪内存带宽比FLOPS更致命很多人一开始只关注算力觉得设备每秒多少TOPS够跑模型了但真正部署过就知道端侧推理的瓶颈往往是内存带宽。原因很简单计算单元要不停地从内存里把权重和中间激活值搬进寄存器或Cache搬运的速度跟不上算力再高也在空转。举一个更具体的例子。假设同一份权重数据FP32需要搬4个字节INT8只需要搬1个字节带宽占用直接降为四分之一。在内存带宽有限的端侧芯片上这个差距会非常明显地反映在推理延迟上尤其像大模型的decode阶段每个token都需要从头读取一遍全部权重这阶段基本就是跑在内存带宽上。实测下来权重量化到INT8之后decode的吞吐往往能提升两倍甚至更多靠的不是算力变强了而是每次要搬的数据变少了。1.3 为什么很多自带NPU只认整型还有一个更现实的原因大量端侧芯片自带的NPU或DSP原生计算单元只支持整型运算对浮点支持要么需要额外硬件单元要么效率极低。你用FP32模型跑这种NPU要么直接不被支持要么被软件层转换成模拟浮点速度惨不忍睹。我自己用过几款常见的端侧推理芯片它们的SDK里都明确要求模型必须量化成INT8才能走硬件加速路径。也就是说在某些平台上量化不是可选项而是能不能用上NPU的门票。这也是为什么每轮端侧模型评测里INT8的模型的部署帧率总是比FP32高出一大截——因为它们的FP32根本没走加速单元或者走了也打不到理想频率。2. 浮点格式的底牌FP64、FP32、FP16和BF16各站各的位2.1 IEEE 754里的三分天下要理解量化到底改变了什么先得把浮点格式的家底翻出来。现代计算机里的浮点数基本都遵循IEEE 754标准一个浮点数由三部分组成符号位、指数位、尾数位。符号位决定正负指数位决定数值范围尾数位决定小数精度。举个例子FP32是1位符号、8位指数、23位尾数总共32位。FP64则是1位符号、11位指数、52位尾数总共64位。FP16是1位符号、5位指数、10位尾数。后面还有个BF161位符号、8位指数、7位尾数。这几者的区别不仅仅是占用字节数更关键的是它们能表示的数值范围和精度完全不同格式总位数符号位指数位尾数位最大有限值最小正规数机器精度FP646411152约1.80e308约2.23e-308约2.22e-16FP32321823约3.40e38约1.18e-38约1.19e-7FP1616151065504约6.10e-5约9.77e-4BF1616187约3.40e38约1.18e-38约7.81e-3注意看FP16它的最大有限值只有65504最小正规数约6.10e-5。一旦数值超过65504就会变成无穷大一旦小于约6.10e-5并且不是次正规数就会被刷新成0。这就是为什么FP16在训练中经常出现loss直接跑飞的问题——梯度或激活值很容易就超出这个范围。BF16的情况则刚好相反它保留了FP32的8位指数所以动态范围和FP32一致不会动不动溢出但它的尾数只有7位精度更低。简单来说FP16牺牲范围换精度BF16牺牲精度保范围。2.2 FP16的近0灾难和BF16的取舍举个实操中会遇到的例子。如果你用FP16去存一个神经网络的权重比如某个矩阵元素真实值是0.1FP16能表示的最接近0.1的数约是0.0999756误差已经到万分之一数量级。对于大多数推理场景这个误差没问题但如果你在做梯度累积或者某些对极小数值敏感的计算FP16就会吃大亏。BF16更夸张它的机器精度约是2^-7也就是0.0078125。用BF16存0.1实际可能存成0.1015625或0.09765625。这个误差肉眼可见但如果只是用来做训练前向或梯度回传的粗略表示配合FP32的Master Weight效果也可以接受。问题在于很多人把FP16和BF16混为一谈以为都是2字节省钱结果在端侧部署时发现数值范围对不上模型输出异常。实际上选择哪种格式取决于你的模型数值是容易溢出还是需要精度。激活函数、softmax前面、注意力分数这类容易产生大数值的地方优先考虑BF16或者干脆保留FP32对精度要求高又不是极端范围的部分FP16更合适。2.3 训练与推理对精度诉求不一样为什么训练时候用的是FP32、FP16混合精度到了推理阶段反而敢用INT8核心原因是训练和推理面对的任务性质完全不同。训练要反向传播梯度在每一层之间反复传递任何一个精度损失都可能被后续计算放大而且还要跨很多step累积更新所以对数值精度极其敏感。推理则只是做一次前向计算每一层的输入和输出都局限在有限范围内。我们不需要精确还原每一个原始浮点值只需要让最终输出保持可用精度。用生活类比来说训练像是在完整抄写一本书每一个字都不能歪推理像是做摘要只要核心意思在个别错字不影响阅读。这个认知差异是理解量化的关键也是为什么INT8在训练上基本行不通但推理却能用得很好的根本原因。3. 从浮点到INT8映射、刻度、零点一个都不能少3.1 量化的本质不是取整而是定标很多人潜意识里觉得INT8量化就是把浮点小数四舍五入成整数把小数点砍掉。这样理解会错过最重要的逻辑。真正量化做的是把一个浮点区间映射到有限的整数值集合上同时记录一个合适的刻度尺scale让整数能够尽量准确地被翻译回浮点数。公式很简单量化q round(r / scale) zero_point反量化r (q - zero_point) * scale这里的scale本质上就是一个INT8刻度代表多少浮点数zero_point则是浮点0对应的整数偏移。只要这两个值定得准量化看起来像丢弃精度实际上是给数据换了一套更稀疏的编码方式。拿一组浮点数据举例[0.2, -0.5, 1.3, 4.0]。如果采用对称量化scale按最大绝对值4.0来定即scale 4.0 / 127 ≈ 0.031496。量化后得到[6, -16, 41, 127]反量化后是[0.189, -0.504, 1.291, 4.0]。可以看到4.0这种极值被完美还原但0.2这种小值却丢了约5%的精度。原因很简单整个可表示区间都让给了一个大值小值只分配到很少的几个整数刻点。3.2 对称与非对称差一个零点上面说的是对称量化也就是zero_point固定为0整数范围取[-128, 127]。这种方案简单、部署效率高因为不需要额外处理零点偏移。但它的代价是如果数据分布正负不对称比如全是正数或者负值很少就会浪费大量整数编码空间。非对称量化则把整数范围用到[0, 255]允许zero_point取非零值。同样拿[0.2, -0.5, 1.3, 4.0]来算rmin-0.5rmax4.0scale (4.0 - (-0.5)) / 255 ≈ 0.017647zero_point round(-(-0.5) / 0.017647) ≈ 28。量化后结果接近[39, 0, 102, 255]反量化后误差都在0.006以内比对称量化均匀得多。所以在实际部署时激活值往往是非对称分布的比如ReLU之后的特征图全是非负值这时候用非对称量化可以极大提高编码效率。权重则因为正负都有且相对对称用对称量化通常就够了。这也是为什么很多推理引擎默认权重走对称、激活走非对称的原因。3.3 校准数据集怎么选scale才算得准上面这些scale和zero_point不是凭空算的而是要拿一小批真实数据跑到模型里统计每一层的激活值范围这个步骤叫校准Calibration。很多人校准时偷懒随便拿几十张图就过了结果部署后精度崩得厉害。关键在于校准数据要和真实部署场景的数据分布保持一致。比如你的模型是做人脸检测的校准数据就不能全用风景图做语音唤醒的校准数据里的音频长度、噪声环境也要贴近实际。我自己习惯选100到500个样本来源尽可能覆盖各类典型输入而不是只选最好识别的那一批。样本太少统计出的min/max不稳定样本太杂且和场景无关scale照样会偏。另外校准方法也有讲究。常见的MinMax直接取数据里的最小最大值简单但容易被离群值带偏Entropy会选择一个让信息损失最小的截断位置对视觉模型通常更稳Percentile则会人为掐掉最极端的几个百分点。我一般先试Percentile 99.9如果精度不够再切Entropy很少一上来就用MinMax。3.4 per-tensor与per-channel权重值得更精细的刻度再往深一层scale可以作用在一个完整的张量上per-tensor也可以作用在每个输出通道上per-channel。per-tensor实现简单计算量小但遇到不同通道数值范围差异很大的权重时表现很差。比如某个卷积核的一个通道权重最大到0.8另一个通道最大只有0.01共用同一个scale会导致第二个通道大量有效值被压缩进极少整数格点里。per-channel则给每个输出通道单独算scale更精细权重量化误差通常能降低一个数量级。我第一次把模型从per-tensor切到per-channel后同一个INT8模型的精度直接涨了1到2个百分点代价只是稍多的存储开销和略微复杂的部署逻辑。对这个收益来说很值。不过要注意有些老旧的推理引擎或底层算子实现不一定支持per-channel量化动手前先查一下SDK文档别等部署阶段才发现平台不支持。4. 实操把模型压成INT8并跑上端侧4.1 工具链怎么选ONNX Runtime、TFLite、OpenVINO先说结论工具链的选择取决于你的模型最终要部署到什么平台。如果目标是Android或者嵌入式Linux上的通用端侧硬件ONNX Runtime QDQ格式是个不错的起点兼容性好而且能够充分发挥NPU加速。如果目标是移动端App并且用的是TensorFlow生态那TFLite的INT8量化会更顺滑转换工具链成熟还有现成的delegate可以接NPU。如果模型最终要在Intel平台或者部分边缘盒子上跑OpenVINO自带量化工具和它的推理运行时绑定很深。TensorRT则适合NVIDIA平台对INT8/TensorCore优化得最极致。我自己最常用的路线是PyTorch训练 - 导出ONNX - ONNX Runtime静态量化 - QDQ INT8模型 - 端侧推理引擎转换。原因有两个第一PyTorch和ONNX之间转换成熟中途可以快速检查精度第二ONNX模型靠近一个统一中间表示后面再接TFLite或其他平台的转换器都比较方便。4.2 ONNX Runtime静态量化完整流程这一步是重头戏。静态量化static quantization需要先跑一遍校准数据收集激活值范围然后把权重和激活都量化成INT8适合追求极致提速的场景。动态量化dynamic quantization只量化权重激活在运行时动态算scale省事但提速有限更适合处理NLP这类激活分布不稳定的模型。下面是一个可以直接跑的ONNX Runtime静态量化示例import numpy as np from onnxruntime.quantization import ( quantize_static, QuantType, QuantFormat, CalibrationMethod, ) from onnxruntime.quantization.shape_inference import quant_pre_process # 第1步导出并优化模型。这一步会补全ONNX节点的shape信息避免后续量化的坑。 quant_pre_process(model_fp32.onnx, model_opt.onnx) # 第2步写一个校准数据读取器每次返回一个batch的输入key是模型输入名。 class CalibReader: def __init__(self, data_list, input_name): self.data_list data_list self.input_name input_name self.idx 0 def get_next(self): if self.idx len(self.data_list): x self.data_list[self.idx] self.idx 1 return {self.input_name: x} return None def rewind(self): self.idx 0 # 第3步执行静态量化。这里用了QDQ格式兼容性更好。 quantize_static( model_inputmodel_opt.onnx, model_outputmodel_int8.onnx, calibration_data_readerCalibReader(calib_data_100, input), quant_formatQuantFormat.QDQ, per_channelTrue, activation_typeQuantType.QInt8, weight_typeQuantType.QInt8, calibrate_methodCalibrationMethod.Percentile, )跑完之后用Netron或者简单打印模型节点看一眼确认里面已经出现了QuantizeLinear/DequantizeLinear节点说明量化算子已经插进去了。下一步就是加载量化的ONNX模型在真实设备上做推理与精度验证。如果你暂时不想收集校准数据也可以先用动态量化快速看效果from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_fp32.onnx, model_dyn_int8.onnx, weight_typeQuantType.QInt8, )动态量化不需要校准数据集但通常只对Transformer、LSTM这类权重占比高、激活偏动态的模型提升明显对CNN效果不如静态量化。4.3 量化前后实测对比实操之后一定别忘了做对比记录。我自己有一个固定模板每次量化完都会填一张表指标FP32原模型INT8量化后变化幅度模型文件大小128 MB32 MB下降75%峰值内存占用468 MB152 MB下降67%单帧推理耗时42 ms16 ms提速2.6倍mAP / Top-10.8120.796下降1.6%像这种情况精度只掉了不到两个点速度和内存却大幅改善端侧部署基本可以接受。如果精度掉得超过两到三个点就不建议直接上线了需要按照第5章的方法去排查和修复。对比时还有一点要注意别只看模型大小一定要跑真实设备上的端到端延迟。有时候INT8模型在PC上看着比FP32快不了多少但到了低带宽的端侧设备上速度差距会进一步拉大因为内存带宽瓶颈在低端设备上更明显。4.4 部署时容易踩的算子坑量化完之后模型可能在自己的开发机上跑得好好的布置到端侧就报错。常见的坑有这么几个模型里含有自定义算子或太新的算子比如某些注意力变体底层推理引擎不认QDQ模式。部分端侧NPU只支持非对称量化不支持对称量化或者反过来导致模型转换后精度归零。有的平台不支持per-channel量化转换工具会在日志里警告如果忽略了后续精度下降找不到原因。动态shape问题比如batch维度是某些NPU需要固定batch才能用INT8 kernel。遇到这种情况我的排查顺序是先打印量化后的模型结构找到报错节点再尝试把这些节点单独保持为FP16或FP32精度也就是混合精度法通常能绕开绝大多数兼容性问题。5. 精度去哪儿了以及怎么找回来5.1 离群值和激活抖动误差的两个主要来源很多人以为INT8精度下降是小数被四舍五入导致的但真正的原因往往更隐蔽。第一类元凶是权重或激活值里的离群值某个通道突然出现一个极大值比如激活值跑到30但剩下99%的值都在0到1之间。这时候按max30去定scale正常值的编码区间被压到不足十分之一等于大量精度被浪费在了一个异常值上。这种情况用Percentile校准或加一个clip操作把尾巴掐掉往往能立刻找回不少精度。第二类元凶是激活值分布在不同输入下波动很大。校准数据集只覆盖了一部分分布真实部署时遇到更极端输入激活会超出校准统计范围scale就失效了。所以前面才反复强调校准数据要贴近真实场景。另外如果模型有多个不同输入源比如多模态模型里的文本和图像要保证校准数据覆盖到所有输入源而不是只挑其中一个。5.2 先做层敏感性分析别盲目全量化不是所有层都适合量化成INT8这是很多入门同学最容易忽略的地方。有些层比如第一个卷积层、最后的分类层或者某些残差连接频繁合并的层对量化误差特别敏感。如果全模型一刀切量化哪怕整体精度只掉两个点也往往是把难点的精度损失隐藏在了大多数普通层里。我会在动手量化前先做一个简单的敏感性分析用一个脚本逐层替换成INT8或者逐层加入模拟量化误差观察最终精度变化。这个方法不需要完整跑整个量化流程但能帮我快速找出千万要保持浮点的层。实际经验是图像分类模型通常第一层和最后一层喜欢浮点检测模型里则经常是输出头的几个卷积不能动。把这些层标记为不可量化或者混合精度处理后整体精度损失往往能控制在1%以内。5.3 QAT是个保底手段但不是首选后训练量化PTQ不动模型权重只是把训练好的模型数值重新编码快而且方便大多数情况下够用。但如果PTQ试了几种校准方法精度还是掉太多就该考虑量化感知训练QAT了。QAT的核心思想是在训练过程中就模拟量化误差让模型的前向计算带上假量化的噪声从而让权重适应量化后的数值分布等训练完了再转INT8精度损失会小很多。代价也很明显需要训练数据和原始训练流程时间成本高调参经验要求也高。我之前在某个检测模型上PTQ掉了3.8个点怎么校准都拉不回来最后对最后三层做了QAT就把精度损失压到了1.1个点。但这个过程花了我差不多两周时间所以我的建议是先用PTQ并配合per-channel和混合精度实在不行再请QAT出山。5.4 混合精度和Clip策略我的个人实操顺序最后分享一个我在多个模型上都验证过的实操顺序你可以直接拿去试先用per-channel权重量化 非对称激活量化跑一遍PTQ看精度损失。精度损失在2%以内收工部署。损失超过2%换Percentile校准方法尝试不同截断比例99.9%、99.5%、99%。还没改善做层敏感性分析把最敏感的5%到10%的层保持FP16或FP32。还不行对特定敏感层做QAT注意训练时加量化模拟别等训练完再转。如果模型里有明显离群值考虑手动clip权重或激活范围再重复上述步骤。这套流程看起来很笨但胜在可复现每走一步都能看到数字变化不会凭感觉优化。尤其是第4步很多人跳过敏感性分析直接QAT结果时间花了效果却不理想。最后说一个我自己实际操作中的体会。量化的本质不是把模型里的浮点数变成整数那么简单而是把一个对端侧设备来说过于奢侈的连续表示换成一种更克制、更紧凑、也更贴近硬件底层的存储方式。它改变的是内存占用、推理速度、功耗表现以及在有限硬件上能不能跑起来的可能性。希望这篇笔记能帮你在INT8这条路上少踩几个坑也欢迎你在评论区聊聊自己遇到的量化问题我基本都是实战派大家交流一下真实数据比什么都强。
企业数字化 ERP 产品动态
相关推荐
VideoGen-Agent实战:强化学习驱动的视频生成智能体系统 1. 从“能生成”到“会生成”:VideoGen-Agent 到底在解决什么问题视频生成模型这两年进步飞快,输入一段文字就能吐出几秒钟的画面,这件事本身已经不算稀奇。但真正上手做过视频生成项目的人都知道,单次生成的质量和“完成一个视频… · 2026/9/26 13:29:53
批量发送短信接口集成方案:从队列调度到回执处理的完整实践 1. 为什么我要自己封装一套群发短信方案 那个下午我到现在还记得,系统刚上线,运营说要给全量注册用户发一条版本升级通知,当时代码里写着的是for循环逐条调用服务商的单发接口。程序跑了两个多小时,跑到三分之一被服务商限频&… · 2026/9/26 13:29:53
猫狗识别算法源码解析:从CNN训练到迁移学习实战指南 简介:这是一份面向计算机相关专业学生与机器学习初学者的猫狗识别算法项目源码,适合用来完成课程设计、期末大作业或毕业设计中的图像分类任务。项目基于机器学习方法实现猫狗二分类,代码经过调试可直接运行,读者可在现有基础上调… · 2026/9/26 13:29:53
FontForge 的起源与演进:从 PfaEdit 到开源字体编辑器的二十年技术编年史 桌面应用图形学 【免费下载链接】fontforge Free (libre) font editor for Windows, Mac OS X and GNULinux 项目地址: https://gitcode.com/gh_mirrors/fo/fontforge 点击查看 免费下载 导读
本文基于 FontForge 官方文档 ff-history.rst(作者 George… · 2026/9/26 14:33:53
嵌入式AI实战:Microduck-HD1910硬件调试与模型部署全流程解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:33:53
NHentai-android开源项目:原生Android漫画阅读器架构与性能优化实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:33:46
会议语音转写准确率真相:为什么98%不等于好用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 14:33:46
grid还是skeleton?srt-whiteboard-animation笔迹路径选择简单指南 grid还是skeleton?srt-whiteboard-animation笔迹路径选择简单指南 【免费下载链接】srt-whiteboard-animation 将 SRT 字幕做成暖米黄纸张底的流式笔迹白板手绘动画 skill:mask 分区遮罩编排 stream 连续笔迹(ink→color)。 项… · 2026/9/26 14:33:30
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46