news 2026/9/18 10:49:25

YOLOv8s INT8量化部署掉点24个点?从校准集到混合精度的完整排查实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv8s INT8量化部署掉点24个点?从校准集到混合精度的完整排查实战

1. 现象收敛:从 mAP 掉 24 个点开始

1.1 部署环境和复现条件

前几天接了一个部署任务,目标平台是地平线 J6m,模型是 YOLOv8s,任务要求挺明确:用工具链把训练好的权重量化到 INT8 后跑在 BPU 上,验证集 mAP 不能比 FP32 掉超过 3 个点。听起来是个常规 PTQ 项目,但第一版量化模型跑出来直接把我干沉默了——FP32 在 GPU 上评估是 0.458 mAP@0.5:0.95,上板 INT8 只有 0.218,等于掉了 24 个点。这不是精度小波动,这是模型基本废了。

先说下复现环境。模型是 COCO 预训练后拿业务数据微调过的 YOLOv8s,输入分辨率 640x640,导出 ONNX 时把训练时的 decode 头拆开保留原始输出,NMS 放在后处理做。部署工具链是地平线 OpenExplorer 这套,流程大致是 PyTorch 权重导出 ONNX,再用 hb_mapper 做浮点模型检查、PTQ 量化、编译成可以在 J6m 上跑的二进制。第一版我用的是工具里默认的校准配置:随机从训练集抽了 200 张图,batch 大小 4,其他参数基本没动。

复现之后就发现,这个精度下降不是均匀掉点。FP32 模型在同样的场景里,小目标、遮挡目标、晚上逆光目标都能框出来,只是置信度参差不齐;INT8 模型则是对低对比度目标直接漏检,行人在十米开外几乎全丢,车尾灯晚上也丢。更典型的是同一个视频序列里,FP32 框的位置稳定,INT8 的框会跳,同一辆车相邻两帧的置信度能从 0.6 跌倒 0.2。这种表现基本可以判断:量化误差影响到了某些对数值分布特别敏感的层,而且不是单纯的“整体变模糊”。

1.2 先复现,不要急着调参

遇到精度崩掉,我的一贯原则是先复现完整链路,再把变量一个个固定住。这里有个特别容易犯的错:看到 INT8 掉点就直接去调模型结构,比如换 Neck、把检测头改成解耦更彻底的结构,或者干脆去搞 QAT。这些方向不是没用,但第一步应该是把问题拆开:

  • FP32 ONNX 在 PC 仿真/板端浮点跑,mAP 是多少?
  • INT8 量化仿真(不上板)mAP 是多少?
  • INT8 编译上板后 mAP 又是多少?

我当时实际的链路结果大概是这样:

阶段mAP@0.5:0.95mAP@0.5直观表现
PyTorch FP32 验证0.4580.687正常
ONNX FP32 仿真0.4510.681基本正常
ONNX INT8 量化仿真0.2810.512小目标漏检严重
J6m 上板 INT80.2180.441漏检更明显,置信度抖动

这个表格说明两件事:第一,ONNX 导出和浮点仿真这一步几乎没有损失,问题不在这里;第二,INT8 量化仿真的精度已经掉了,说明误差是在量化环节引入的,不是板端编译“跑坏了”。板端比仿真再低 6 个点,那是后处理或者输入预处理在板端实现不一致导致的,这个后面说。

2. ONNX 导出和预处理不一致,是第一层陷阱

2.1 把 FP32 跑成不过板前参考线

很多人会跳过浮点模型在部署链路上跑通这一步,直接做 INT8。我这次一开始也差点这么干,后来复盘发现,FP32 环节的价值不是帮你“保证精度”,而是帮你划一条参考线。

FP32 在 PC 仿真阶段跑出 0.451,说明 ONNX 本身结构完整、算子支持、权重没有损坏。如果这一步就掉点,问题集中在导出方式:比如 training 和 export 模式没有切换正确,损失了 batchnorm 折叠后的行为;或者导出的 output 顺序、shape 和部署代码里的解析逻辑对不上。

YOLOv8s 的结构里有很多 SiLU 激活函数,以及 C2f、SPPF 这种多次 concat 的结构。导出 ONNX 时如果打开了某些优化开关,比如把 SiLU 替换成近似形式,或者把某些常量折叠掉,虽然模型能跑,但数值会有微小变化。这个变化在 FP32 下通常可接受,但会成为 INT8 量化的额外误差来源。所以我在做量化前,会固定使用统一的 ONNX 导出参数,并且导出的 ONNX 先在 torchscript / onnxruntime 上跑一遍随机张量,对比 pytorch 输出,最大误差控制在 1e-4 量级。

2.2 输入归一化方式的核对

预处理不一致是我这次排查过程中第一个确认的坑。

YOLOv8s 在 Ultralytics 训练框架里的标准预处理是:把图像 resize 到 640x640 并做 letterbox 填充,然后除以 255 做归一化,最后转成 NCHW 的 float 张量。但在部署时,地平线工具链一般不会让你在 BPU 上跑原始除以 255 的浮点操作,因为 BPU 是定点计算,输入图像通常是 uint8 格式,需要一个“量化到 int8 输入层”的过程。

这里容易错的是:模型实际学到的输入分布是什么?如果训练时用的是“除以 255 再减 mean 再除 std”,那么部署配置里的归一化参数必须和训练一致;如果训练时只是简单除以 255,部署配置里就要用 data_scale 的方式做定点缩放,而不是套用一套 ImageNet 的 mean/std 上去。

我第一版部署配置里写的是norm_type: data_scalescale_value: 0.00392156862745098,这本身没错。但我后来发现板端的前处理代码里,图像读出来后先做了 BGR 到 RGB 转换,然后再 letterbox。而训练时 Ultralytics 默认是 BGR,模型实际上吃的是原始 BGR 顺序。就这么一个通道顺序问题,导致所有图像的 R 通道和 B 通道内容互换,模型虽然没崩,但 mAP 直接掉了 5 个点。查这类问题最快的方式不是盯 mAP,而是跑一张固定测试图,把 FP32 仿真的网络中间层输出和板端输出做逐层对比。通道顺序错了,往往第一个卷积层的输出分布就能看出来。

2.3 踩过的一个预处理坑

再补一个很隐蔽的坑:letterbox 的填充值。YOLO 系列训练时通常用灰色 114 做填充,这个 114 是在归一化之后除以 255 得到的约 0.447。如果部署代码里 letterbox 填充的是 0,那么图像边缘大量填充区域和训练分布不一致,对带 padding 的小目标影响特别明显。这个坑我在别的项目里踩过,当时一批边界位置的检测框置信度整体偏低,后来发现就是 fill value 的问题。

所以预处理这块,我后来养成了固定习惯:读图、resize、letterbox、颜色通道、归一化,全部收敛到一个函数,并且用一个真实样例图把训练预处理和部署预处理后的输入张量打印出来逐元素对比。这一步排干净了,再谈量化精度才有意义。

3. 校准集选不好,后面全白搭:重新做 PTQ 校准的实操

3.1 校准集的分布偏差

排完预处理问题,INT8 量化仿真精度从 0.218 回到了 0.281 左右,但这还是不合格。下一个鬼问题出在 PTQ 的校准集上。

地平线工具链的 PTQ 校准原理,可以简化理解为:喂入一批代表性图片,统计每一层激活值的 min/max 或者分布直方图,然后按某种优化策略找到每个 tensor 的 scale。校准集的作用是“让量化器知道真实数据长什么样”。如果校准集本身不具代表性,量化 scale 就会偏向训练集某个局部特征,验证集上自然一塌糊涂。

我第一版用的是训练集里随机抽的 200 张图,而且是从服务器日志里直接抽帧,没做去重。这些图大部分是白天干线场景,夜间和雨天占比极低,小目标密集的城区路口也不多。结果就是:模型白天场景精度还能看,一到夜间和雨天直接崩,行人和两轮车漏检严重。

这类问题在日志上很有特点——它不是所有类别均匀掉点,而是某几个特定类别掉得特别狠。我统计了各类别 AP 变化,发现 bus 掉了将近 30 个点,motorbike 掉了 22 个点,而 daytime 的大目标是正常下降。这基本就是校准集类别不均衡的直接反应。

3.2 重新设计校准集

第二次我老老实实重新采集了校准集。规则很简单,但也花了不少功夫:

  • 总数控制在 800 张,而不是越多越好。
  • 按场景比例分配:白天 50%、夜间/弱光 25%、雨天/逆光 15%、隧道/地下车库 10%。
  • 每个场景内按目标尺度分层,保证大目标、中目标、小目标都有覆盖。
  • 做了简单去重,避免同一辆车连续帧重复出现,导致校准集倾向性太大。
  • 特意加入业务 road 上容易漏的 hard case,比如被遮挡的行人、远处的小车、密集两轮车。

这里要说一个反直觉点:校准图片并不是越多越好。校准集数量过大,量化器反而会过度拟合到“所有样本的均值分布”,忽略了极端值;数量过少,又覆盖不了动态范围。我们实际测试下来,在这个业务场景下 800 张是一个比较稳的点,再扣到 300 张会掉 2 到 3 个点,加到 1500 张基本没有额外收益,反而编译时间和校准时间长了不少。

校准集换完之后,INT8 量化仿真精度从 0.281 升到了 0.341,mAP@0.5 从 0.512 升到了 0.578。有提升,但离目标还差很远。而且这时候掉点模式变了:不再集中在特定类别,而是整体均匀下降。这说明校准集问题基本解决,剩下的问题出在模型结构对量化的敏感度上。

3.3 校准参数和量化感知节点的配合

在校准集之外,我还踩过一个参数配置的坑。工具链在量化时会有一些“量化感知”相关配置项,比如哪些算子需要特殊处理、哪些层需要强制为某种量化精度、是否允许对某些激活层做 extra clip。刚开始我把这些配置全部保持默认,后来发现有些结构的 pooling 层或者 concat 节点在默认配置下会用比较保守的量化方式,误差不大但拖累整体精度。

不同版本工具链的字段不一样,我建议关注两个事情:第一,校准过程中的 batch size 不要设得过大,我这边设为 4 比较稳,太大反而会导致统计直方图不够平滑;第二,如果配置文件里有“允许对激活层做截断”的选项,可以先打开试一下,因为 YOLOv8s 很多激活值存在明显的长尾分布,直接按 max 定 scale 会让绝大多数有效数值都挤在很小的量化桶里,做一点截断反而能提升精度。

校准完成后,可以用工具链生成每层校准的量化误差报告,别只看最终 mAP。有时候整体 mAP 涨了,但某个关键中间层的 cosine similarity 反而更差,这种就要警惕过拟合校准集,需要在下一轮校准里调整图片。

4. 用逐层敏感度定位“头重脚轻”的 DFL 回归分支

4.1 为什么 YOLOv8s 的检测头对 INT8 这么敏感

校准集救回来一部分精度后,剩下的问题基本锁定在模型本身。YOLOv8s 是一个 anchor-free 检测器,检测头分成分类分支和回归分支。回归分支里有一个叫 DFL(Distribution Focal Loss)的结构,它不是直接回归框的宽高,而是对一组预设的离散 bin 做 softmax 加权求和,最后得到坐标偏移。

这里的问题在于:DFL 分支里的输出特征,数值分布非常“尖”。很多 bin 的权重集中在某一个值附近,softmax 之后概率分布有一个明确的峰值。这种分布对量化误差特别敏感——如果量化后的 scale 稍微大了一点,或者某个关键 bin 的概率被舍入到相邻 bin,最终解码出来的框坐标就会偏移好几个像素。在 640x640 输入下,几个像素的偏移对远处小目标来说就是 IoU 从 0.7 跌到 0.3,mAP 自然崩。

另外,YOLOv8s 的检测头最终输出层之前通常有几个 3x3 卷积,这些卷积的输入特征图来自不同尺度的 Neck 融合,数值范围比 backbone 中间层大得多。如果这些层被一刀切地用 INT8 量化,误差会在最后的输出层被放大。

我之前掉点 24 个点的第一版,很可能就是这部分在作祟。当时只知道模型掉精度,没有工具去定位。后来养成了习惯:每次做完校准,先把量化后每一层相对 FP32 的 cosine similarity 拉出来看一遍,而不是只看最终指标。

4.2 逐层 cosine similarity 排序

地平线工具链支持做逐层精度分析,类似量化敏感度分析。原理不复杂:把 FP32 模型和 INT8 模型的中间激活值逐一拉出来,计算每个节点输出的 cosine similarity。越接近 1,说明量化对这个节点的影响越小;如果某个节点跌到 0.99 以下,那基本就是重点怀疑对象。

我这边跑完一轮分析后,结果非常有指向性:low level 和 backbone 大部分节点 cosine similarity 都在 0.999 以上,属于正常量化损失;但检测头后段的若干卷积节点,尤其是和 DFL 分支直接相关的那几个节点,sim 值跌到了 0.97 左右。最离谱的是靠近输出的某个 concat 节点,fp32 和 int8 的激活分布直接对不上。看到这个现象,基本可以确定“头重脚轻”——问题集中在 head 而不是 backbone。

这时候我建议的做法是:不要急着把所有 head 层都设置成 float,而是先把最敏感的节点挑出来。我当时把 cosine similarity 排序,取了最后 8 个敏感节点,逐一查看它们所在的子图,发现其中 5 个都在回归分支,另外 3 个在检测头的第一个 3x3 卷积附近。这说明这个模型的主干和 Neck 已经被量化得足够好,唯独检测头的数值分布不适合 INT8。

4.3 混合精度方案:把这几个层 keep_float

定位到节点之后,解决方案就清晰了:对这些敏感节点做 mixed precision,也就是让它们在编译时保持 FP32 计算,模型里其他层仍然走 INT8。

我当时的做法是在量化配置里增加混合精度节点列表,大致是这种形式:

quantization: mixed_quant: float_nodes: - "/model.22/reg/.../DFL/Conv" - "/model.22/reg/.../Conv"

具体字段名不同版本有差异,以你自己下载的工具链 release note 为准。但思路是一样的:把那些对量化特别敏感的层挑出来,让它们在 BPU 上用浮点方式跑,或者落到 CPU 上跑。

这里有个取舍点:keep_float 的节点越多,精度越接近 FP32,但代价是计算图切分次数变多,可能增加内存搬运甚至整体耗时。所以我通常不会一次性把所有 head 层全加上,而是先加最敏感的几个,跑一遍精度和耗时,再看没有没提升,逐轮迭代。

我这轮把 5 个敏感回归分支节点设为 float 后,量化仿真精度从 0.341 升到了 0.412,mAP@0.5 从 0.578 升到了 0.648。接着我又试着把检测头第一个 3x3 卷积也加进去,精度只涨了 0.4 个点,但耗时增加明显,最后果断撤回。

4.4 尝试 int16 激活的取舍

除了 keep_float,我也顺手试了工具链里的 int16 激活选项。J6m 这类 BPU 通常支持多种量化精度混合,int16 的精度比 int8 高,能处理动态范围更大的激活值,但代价是计算量和内存带宽开销会涨。

实测下来,在 DFL 分支附近用 int16 激活,量化仿真精度大概提到了 0.39 左右,比纯 INT8 好,但和直接 keep_float 的 0.41 比还差一点,而且编译时间明显变长。我当时的判断是:这个模型的耗时预算不宽裕,与其用 int16 覆盖一大片区域,不如精准地把几个最敏感的节点用 float 处理。所以最后方案选择了 float 节点 + INT8 主干混合精度,而不是大范围 int16。

如果你手上的部署平台对耗时比较敏感,且 keep_float 的数量太多,int16 是一个折中方案。但一定记住,别指望它帮你救回所有掉点,int16 适合“数值范围大但分布还算连续”的层,不适合 DFL 这种“分布极尖”的层。

5. 修改后的精度验证与上板实测

5.1 验证集和场景集分开测

混合精度改完后,我先跑了一遍完整的评估流程。这里我强调“完整”,是因为只跑 mAP 一个指标很容易骗人。我把评估拆成两块:

第一块是和训练对齐的标准验证集,用来和 PyTorch FP32 做横向对比,这是项目验收的硬指标。第二块是单独挑出来的场景集,专门覆盖夜间、雨天、小目标密集、遮挡这些业务难点,这块不参与验收,但能反映真实路况下的可用性。

混合精度方案在标准验证集上最终达到了 0.438 mAP@0.5:0.95,和 FP32 的 0.458 只差 2 个点,mAP@0.5 从 0.687 降到 0.665,勉强在 3 个点的预算内。场景集上的表现也好很多,夜间漏检率明显下降,小目标的框虽然置信度仍然偏低,但至少不再大面积丢失。

这里多提一句:别只看 mAP 平均数,要按置信度阈值画 PR 曲线。有时候 mAP 差不多,但 PR 曲线的形状差很多,说明模型的输出概率校准出了问题。上板模型如果在后处理里用固定置信度阈值,比如 0.4,那即使 mAP 达标,也可能因为置信度普遍偏低而表现为“漏检严重”。我后来在后处理里把置信度阈值从 0.4 降到 0.3,召回率涨了 6 个点,误报只多了 1 个点,这个修正比任何模型改动都短平快。

5.2 编译配置和板端耗时的观察

量化仿真通过后,我把模型编译到 J6m 上跑。上板后的 mAP 比量化仿真又低了大概 3 个点,这属于正常现象,因为 PC 仿真是比较理想的计算环境,板端会有内存布局、算子切分、并行调度等因素影响。但如果上板比仿真低超过 5 个点,就要查以下三个方向:

第一,模型编译时有没有做层级优化或者算子替换。有时候工具链的优化器会把某些小算子合并,如果合并后改变了数值计算的顺序,INT8 下可能引入额外误差。

第二,后处理实现是否和 PC 评估时一致。我这次上板掉点里有一半就出在这里。PC 评估时用的 NMS 实现是 Ultralytics 默认的,而板端后处理是同事另外写的 C++ 代码,里面对每个类别的 confidence threshold、IOU threshold、最大检测数量都重新调过参数。查了半天,最后发现是板端代码把 score 乘了一个 0.95 的缩放因子,导致低置信度目标直接被阈值过滤掉。

第三,输出张量的解析顺序。YOLOv8s 的输出通常是一个大的二维数组,每列代表一个 anchor/refine point,顺序如果搞错,模型精度再高也没用。

5.3 上板后还是掉点?从后处理里找问题

我见过很多项目,模型改了半天没效果,最后发现是板端代码里用了一个有 bug 的 NMS 实现。比如只按类别循环 NMS,没有跨类别抑制,或者对同一类的框没有按 score 排序,导致输出框奇奇怪怪。

我的建议是,在上板前做一个“后处理对拍”:把同一组输入图像同时喂给 PC 端模型和板端模型,导出两边的 raw output,再用同一套后处理代码分别解析,对比最终框的坐标和置信度。如果 raw output 一致但最终框不一致,问题肯定在后处理。如果 raw output 本身不一致,再去查模型编译和输入预处理。

我们这次把后处理修好之后,上板 mAP 从 0.218 恢复到 0.426,量化仿真 0.438 和板端 0.426 之间的差距只有 1.2 个点,基本符合预期。

6. 复盘:J6m 上部署 YOLOv8s 的 INT8 检查清单

6.1 排查链路

把这次整个排查过程压缩成一条链路,大概是这样:

  • 先确认 ONNX 导出没问题,FP32 在部署链路上不掉点。
  • 再确认输入预处理完全对齐,包括 resize、letterbox、通道顺序、填充值、归一化参数。
  • 接着检查校准集是否覆盖目标场景,是否类别均衡,是否做了去重。
  • 然后用逐层敏感度分析定位最敏感的量化节点,优先处理检测头和 DFL 分支。
  • 混合精度不要一次开太多,从最敏感节点开始逐轮迭代。
  • 上板后如果精度比量化仿真低超过 5 个点,优先怀疑编译优化和后处理,不要再去折腾模型结构。

这个顺序很重要。我见过同事头疼了一周,最后发现只是部署代码里把图像通道顺序搞反了;也见过有人花大量时间调模型结构,结果发现是 NMS 阈值设错了。先排除部署链路的问题,再谈模型本身,才是 INT8 部署的正确姿势。

6.2 一些心法

再分享几个比较虚但对工作很实用的心得。

第一,量化精度排查一定要每次只改一个变量。跑一次精度的成本不高,但如果同时换了校准集、混合精度、预处理三个东西,结果变好了你也不知道是哪个起了作用,结果变差了你更没法定位。我这次就是先锁死预处理,重做校准集,确认提升有限后才开始动混合精度,每一步都有日志和对应数值。

第二,养成保留量化报告的习惯。每轮校准完都会生成一些中间统计信息,别直接删除。有时候回退到某个历史版本时需要对比,这些报告是唯一线索。

第三,对 YOLOv8 这类检测模型,heads 的量化敏感度通常高于 backbone,这是一个通用规律,但不代表每次都必须把 head 设成 float。先跑敏感度分析,用数据说话,别凭感觉。

最后一点,也是我踩过多次之后才真正记住的:上板模型精度不达标时,先不要怀疑 BPU 算错了,绝大多数情况下是“你在 PC 上定义的模型”和“你在板端喂给模型的输入”根本就不是同一个东西。把两边的输入张量和原始输出拉出来对拍一次,几分钟能解决的问题,别拖成几个通宵。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:49:05

Python+OpenCV车牌识别实战:从图像预处理到模板匹配全流程拆解

把一张带车牌的图片扔进程序,几秒钟后返回一串字符——“京A12345”。听起来很酷对不对?我第一次跑通PythonOpenCV车牌自动识别的时候也兴奋了好一阵,但说实话,从能跑到稳定识别,中间踩的坑一点都不少。这个项目虽然是…

作者头像 李华
网站建设 2026/9/18 10:46:51

五个生活场景盘活闲置电视盒:家庭服务器从零搭到跑通

五个生活场景盘活闲置电视盒:家庭服务器从零搭到跑通 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk3588, r…

作者头像 李华
网站建设 2026/9/18 10:46:42

Redis常用命令精讲:Linux运维必会的核心操作与避坑指南

做运维和开发这些年,Redis 基本是绕不开的一个组件。缓存、session、分布式锁、排行榜、消息队列,样样都有它的影子。而要在 Linux 服务器上把它用得顺手,核心靠的就是那一批常用命令。这些命令看着简单,但真正到了生产环境&#…

作者头像 李华
网站建设 2026/9/18 10:44:49

BabelDOC:排版不乱的 PDF 翻译,从零到双语对照只需 5 分钟

BabelDOC:排版不乱的 PDF 翻译,从零到双语对照只需 5 分钟 【免费下载链接】BabelDOC Yet Another Document Translator 项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC 拿到一份英文学术 PDF,你多半不想要一堆翻译出来的…

作者头像 李华
网站建设 2026/9/18 10:44:26

Modbus协议在工控取证中的应用:报文分析、流量追踪与证据链重建

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

作者头像 李华
网站建设 2026/9/18 10:44:18

YOLOv5到v11工程范式迁移:2026目标检测选型决策指南

1. 这不是版本迭代,是目标检测工程范式的迁移YOLO 演进 v5→v11 与 2026 选型指南——这个标题里藏着一个被多数人忽略的事实:我们讨论的早已不是“哪个模型更准几个百分点”,而是整个目标检测落地链条的重构。从 YOLOv5 到 YOLOv11&#xff…

作者头像 李华