☰
嵌入式平台实例分割Int8量化实战:SparseInst踩坑与混合精度优化
2026/10/24 1:12:08 网站建设 项目流程

前一阵子在帮某项目收尾,目标是在一台算力很紧的嵌入式GPU平台上跑实时实例分割。卡子的算力本来就不宽裕,还要同时扛视频流解码、前处理、跟踪和业务逻辑,留给模型的空间极小。一开始跑经典的两阶段检测加分割方案,实测不到3FPS,换成了以轻量著称的SparseInst之后,FP16精度勉强能到12FPS,但离25FPS的目标还差一半多。算力不够只能从精度换——我的第一反应就是做Int8量化。

这听起来很常规,但真正动手之后才发现,实例分割的Int8量化和普通检测分类完全不是一回事。检测任务的输出是框和类别,几个数值的精度稍微掉一点问题不大;实例分割要在像素级别给出掩膜,量化误差一旦落到特征图上,会直接表现为掩膜断裂、小目标消失、边缘锯齿,而且很多问题在整体指标上看不出来。这篇内容就是整个过程中的选型思路、量化准备、踩坑定位和最终落地记录,适合同样被性能卡住、正准备对分割模型下手做量化的朋友。

1. 为什么选SparseInst:不是因为它名字带“快”,而是结构确实适合量化

1.1 传统实例分割的速度瓶颈

先说说为什么项目里会换成SparseInst。经典两阶段方案的问题不在精度,而在速度结构:它多一个区域提议的流程,每个候选框都要单独做一次分割分支,这个分支即便很小,目标一多整体计算量就会线性涨上去。更麻烦的是后处理NMS很重,在CPU侧处理耗时有时比GPU推理还高,这对嵌入式平台很不友好。

还有一类单阶段方案,直接输出掩膜系数,后处理相对简单,但它的问题在于对每个像素位置都要生成一个巨大的掩膜特征,通道数通常在几百以上。特征金字塔每层都有这个开销,模型参数没见多少,但激活值占用和计算量非常可观,量化时也容易在密集特征上积累误差。我一开始其实试过这条路,延迟还是压不下来。

1.2 SparseInst的“稀疏”到底拆掉了什么

后面看到SparseInst,它在结构上把上述两个问题都绕开了。核心思路是引入了稀疏实例查询,也就是一组可学习的向量,每个查询向量代表一个实例。输入图像经过主干和编码器得到特征后,这些实例查询直接去解码出两样东西:一个是实例类别的分类结果,另一个是掩膜系数。最终掩膜由这些系数和基础掩膜特征做线性组合得出。

这个设计拆掉了逐区域的分割分支,也没有逐像素密集分类那一套,实例间通过查询向量天然隔离,不需要NMS。实测对数量很多的目标场景也能保持稳定,在算力受限平台上尤其划算。FP16能跑到12FPS的那一版就是这么来的。

1.3 一个容易被忽视的隐藏优势:量化误差更可控

真正做量化之后,我反而觉得SparseInst最值得选的原因是它的计算模式。实例查询解码出来的是系数,不是直接在像素网格上做密集预测,计算主体集中在卷积和矩阵运算上,结构工整,没有那种形状敏感的动态分支,量化的算子覆盖率和计算密度都很高。相比之下,有些结构里有大量自定义算子的模型,导出到通用推理框架就已经很痛苦,更不用说量化了。

打个比方,密集分割模型像在一张全幅画卷上层层覆盖墨水,量化误差均匀分布还会叠加,最后画面发灰;SparseInst像用少量关键笔触去勾勒线条,只要线条数值准,整体画面就不会崩。当然,“关键笔触”的数值敏感度更高,这也是后面量化时最主要的问题来源。把这条记住,下面的排查思路就顺了。

2. 上手量化前,先把这三个坑填平

2.1 预处理和后处理一定留在网络外部

量化之前,先保证导出到通用格式的模型是干净的计算图。常见的原罪是训练代码里的归一化、缩放、甚至后处理的掩膜放大都被当成节点塞进了模型。

这不是洁癖问题。量化校准时,框架会用一群有代表性的输入去统计激活值分布,如果模型内部还带着归一化运算,它会改变输入分布到激活值分布的映射关系,标定的Scale可能整体偏移。而掩膜后处理如果留在图里,往往引入一些不支持量化的算子,框架只能回退到高精度,速度和预期完全对不上。

我的做法是:预处理只保留基础的尺寸调整和通道重排,归一化数值在外层算好再送进网络;掩膜恢复到原图尺寸、阈值过滤这些操作全部写在宿主程序里,让网络只负责输出系数。这样第一版的TensorRT优化图才足够干净。

2.2 固定输入尺寸和批量大小,不要用动态形状

SparseInst推理时有一个参数是“最多支持多少个实例查询”,这相当于是固定的计算上限,模型主干本身也接受固定分辨率输入。很多人为了灵活,导出时把高度宽度维度甚至批量维度设成动态的。这个在FP16下没问题,但做Int8校准的时候,动态形状意味着某些卷积层的维度Shape范围是浮动的,校准统计在某些shape上不适用,框架会多走几次重校准逻辑,容易埋下隐患。

我在目标平台上直接固定成640x640单batch输入。单batch本来是这个嵌入式场景的实际使用方式,固定之后计算图稳定,动态分配内存的开销也省了。如果实际判断是多路视频流并行,可以固定一个小批量而非动态batch,量化效果会比动态形状可靠得多。

2.3 校准集的质量,直接决定掩膜会不会断

这是量化前最容易偷懒也最容易埋雷的一步。校准集不是随便从验证集里抽几百张图就完事了。我建议至少要覆盖五类场景:目标密集、目标稀疏、大目标、小目标、目标边缘和背景颜色接近的图像。均衡比例大概按照线上真实数据的分布来定,宁可多放难样本,也不要只挑“好看的”容易图。

校准样本数量上也别迷信越大越好。我用过500张和2000张对比,发现对稳定分布特征的效果相同,反而每多一批校准数据,校准时间线性地涨。关键是样本多样性要够,再配合框架默认的熵校准就能得到不错的初始结果。此时最好把校准集的图保存一份,后面排查精度问题时还能回溯。

3. 第一批Int8数据:AP指标没崩,可视化却先崩了

3.1 量化配置和第一次跑通

导入模型后,我用某主流推理优化框架的Python接口开启Int8模式,核心配置基本是:

# 思路说明,具体工具接口各有差异 calibrator = Calibrator( algorithm="entropy", # 默认熵最小化校准 calibration_batches=500, # 与校准集图片数匹配 weights_per_channel=True, # 卷积权重用per-channel activations_per_tensor=True # 激活值用per-tensor )

per-channel的权重量化能保留每个卷积核的数值范围,减少权重误差;激活输出通常还是per-tensor,因为很多算子在硬件上只支持张量级Scale。校准跑完后,对比同一批测试数据的指标:

版本AP50AP75掩膜IoU延迟显存占用
FP3284.1%65.2%0.82122.3ms780MB
FP1683.8%64.7%0.81312.5ms400MB
INT882.9%61.3%0.7526.8ms220MB

AP50只掉了一个多点,速度翻倍,显存减半,初次看到这个数据我是满意的,以为收工了。但这只是把“框级别”的正确率衡量了,实例分割的下游场景看的是掩膜质量,不是框子准不准,刚想庆祝就被可视化打脸了。

3.2 为什么不能只看AP:掩膜断洞、细长目标消失

把量化后的结果图片放到工位场景里看,问题立刻暴露。细长的工具类目标掩膜断成了好几段,中间出现明显的空洞;靠墙的几件小目标直接漏检;目标的锯齿边缘明显变粗,像是从矢量图退化成了低分辨率底图。这些现象在量化前从未出现过。

如果只看AP曲线,大概能骗过验收,因为AP指标受大目标区域主导,掩膜边缘差几个像素贡献微不足道。但对定位、计数、测量这类下游应用来说,掩膜质量就是产品验收的核心指标。量化效果的审查必须包含可视化抽样,甚至要专门挑难样本看。

3.3 掩膜系数和基础掩膜的点积,对量化误差天然放大

为什么掩膜输出头比其他头更敏感?根源就在SparseInst的掩膜生成方式:最终掩膜是掩膜系数与基础掩膜特征的加权线性组合。这个操作数学上很简单,但有一个坏处——系数和特征都是浮点数,当两者被量化成整数后,原始范围被切分为有限个离散档位。

举个直观例子:某些细长目标对应的掩膜系数只有0.01到0.05的范围,量化步长如果接近0.01,系数只剩几个有效档位。原本连续变化的掩膜边界变成阶梯状跳变,视觉上就是断裂和空洞。分类分支反而没事,因为类别输出的激活值范围宽、量程阶梯划分对它来说足够精细。

这个认知对我的后续排查起了决定性作用:问题不在校准数据,而在于掩膜生成链路对量化误差的放大机制。这也解释了为什么单纯更换校准集只能改善局部。

4. 掩膜断洞的完整排查链路:从校准集到逐层敏感度扫描

4.1 第一步:换校准集和校准算法,发现只治标

排查肯定从最常规的怀疑对象开始,也就是校准环节。我把校准集从300张扩到500张,并加入大量细长目标和低对比样本;校准算法也从默认熵校准换成了最小化均方误差的校准策略。量化后再测试,掩膜IoU从0.752涨到0.771,确实有改善,但细长目标的断洞还在,小目标漏检率也没有本质变化。

这说明问题不在“代表性样本不够”,而在模型内部某些结构对量化误差的容忍度太低。继续换样本就是在绕弯子,接下来必须逐层定位敏感层。

4.2 第二步:逐层量化误差扫描,定位误差爆发点

我用了逐层对比的方法:把每个卷积层分别应用量化和不量化的Full FP16组合,其他层保持Int8,逐一跑同一批验证图片,记录每层单独量化时掩膜IoU相对全部FP16的损失。最终扫出误差损失排名前五的层,结果很有意思,前几层里没有一条属于分类路径,全是掩膜分支相关的层,尤其是把基础掩膜特征和掩膜系数做点积前的最后几个卷积层。

对这些层的激活值分布做统计后发现,它们的输出分布接近均匀分布,没有明显的单峰集中于某个区间。校准算法采集直方图时,对这种分布拟合得不算差,但Per-tensor的Scale为了覆盖边界值,拉大了阶梯步长,中间区域的信息量化损失就大。

4.3 第三步:定位到双路径结构的分布差异

到这里我才彻底想明白是SparseInst的双路径结构惹的“分配不公”。分类分支的激活值集中在几个响应区间,量化误差小;掩膜分支的激活值均匀铺开,同一套per-tensor校准点很难兼顾两者。可以在校准阶段为掩膜分支单独收集激活分布,但更直接的办法是混合精度。

采用保留部分敏感层为FP16、其余层保持Int8的策略。具体来说就是:掩膜特征解码的最后两级卷积、掩膜系数预测层、以及点积前的汇聚层继续用FP16,其他大量卷积全部走Int8。实际统计下来只有约7%的层留在FP16,代价可忽略。

4.4 第四步:混合精度落地后的实际指标

混合精度调整之后的实测数据:

版本AP50AP75掩膜IoU延迟保留FP16层占比
全量INT882.9%61.3%0.7526.8ms0%
混合精度INT883.9%64.6%0.8087.1ms约7%

延迟只增加了不到0.4毫秒,掩膜IoU基本回到FP16水平,断洞现象在可视化里明显消失。这一步之后的模型才真正有资格进入部署阶段。这个过程给我最大的教训就是:实例分割的量化排错不能停留在校准集,逐层敏感度扫描一定要做,尤其是结构里有两条并行输出路径的模型,分布差异本身就是量化误差放大的源头。

5. 换到嵌入式目标平台后,最后几个部署细节

5.1 小心布局切换和CPU算子回退的隐形开销

模型层面的量化完成之后,性能并不一定自动达标。在实际平台上部署时发现,某些算子在后端实现里没有高效的Int8内核,会回退到更慢的路径。这从模型延迟数据看不出来,但从检查过程的明细日志能发现,个别层的耗时比预期高了几十倍。

常见原因是通道数和步长不满足最新向量指令的约束,一个简单办法是回退到4通道对齐,或者主动把这类层也留在FP16,实测延迟反而下降。另外,引擎在切换不同尺寸输入时有时候会带来布局转换,尽量避免运行时动态尺寸修改,提升稳定性。

5.2 显存和吞吐的细节优化

嵌入式平台上显存往往比算力更紧张。量化后模型权重和中间激活都大幅瘦身,内存池还是建议手工固定复用,避免频繁建张量、释放张量的碎片化。多路视频如果共用一块推理引擎实例,注意批次提交的节奏,把多张小图拼成一个大张量一次推理,吞吐提升比单路提高线程数明显得多。

5.3 换到其他推理后端时需要重新验证

最后提醒一句:从主流的量化推理优化框架跨到别的开源部署库时,不能认为“导出同样的模型与权重、带上同样的量化Scale”就万事大吉。不同后端的算子实现、布局处理、校准细节都不完全一致,必须把可复现的校准日志和逐层敏感度测试脚本一起迁移,并且在目标硬件上重新跑一遍可视化检查。特别是掩膜分支,一旦换后端,之前踩过的那种断洞问题很可能换个面貌又出现。

在我个人的实操习惯里,做完一次量化会固定整份“校准图像+逐层敏感度报告+部署后端验证记录”当作该模型的量化档案。之后每次修改训练数据、调整网络结构或升级部署框架,都要对着这份档案重新检查一遍。管线跑起来不难,但真正让量化从“能跑”变成“跑得好”的,正是这些细节上的较真。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询