☰
端侧小模型+合成数据:多模态工业质检落地实践
2026/10/28 19:54:46 网站建设 项目流程

上周跟一个做产线视觉的朋友吃饭,他问了个很具体的问题:手里那台工控机只有8G内存、没有独立显卡,能不能把原本跑在服务器上的那套质检模型搬下来。这个问题听着像是硬件选型,实际上把最近大半年的三条技术线全串起来了——端侧小模型负责"塞得进去",合成数据负责"训得起来",多模态质检负责"用得下去"。这三件事单独看都有成熟的讨论,凑在一起才是真正的工程难题,因为任何一环掉链子,整条链路都跑不通。

这篇是我这段时间的落地观察记录,偏向实操视角,不讲论文里的漂亮指标,只讲把东西搬到现场时会遇到什么。适合正在做边缘部署、工业视觉、数据标注成本控制的同学看,也适合刚接触多模态、想知道真实项目长什么样的朋友。文中的数字都来自小规模实验和现场测算,不同硬件、不同数据分布下会有出入,但推算方法和踩坑点是可以直接搬走的。

1. 这三个词为什么总是在同一张桌子上出现

1.1 端侧算力的天花板决定了模型选型的第一步

很多人做端侧部署的第一反应是"把大模型裁小",这个思路方向对,但顺序错了。正确的顺序是先算清楚设备能给多少资源,再倒推能上多大的模型,而不是先选一个模型再想办法压缩。我见过太多项目在服务器上把7B模型调得很漂亮,搬到现场发现内存直接爆掉,回头再改架构,前后返工两个月。

算力的三个硬约束是内存、算力峰值和功耗。内存最容易被低估,因为大家习惯只看模型权重大小。实际运行时的内存峰值等于权重、KV Cache、中间激活值、框架自身开销四者之和,而权重往往只占一半。算力峰值决定了首字延迟和吞吐,功耗决定了设备要不要加风扇、能不能塞进密闭机柜。工业现场还有个隐性约束:散热。一台无风扇工控机在35度的车间里连续跑8小时,芯片降频是常态,实验室里测出来的延迟数据到现场要打七折。

我一般的做法是先画一张资源预算表,把内存、算力、功耗、散热四项列出来,每项留30%的余量,剩下的才是模型能用的空间。这个余量不是保守,是给现场环境波动留的缓冲。车间温度、粉尘、电压波动都会影响实际表现,实验室里跑得稳不代表现场跑得稳。

1.2 合成数据解决的是标注成本,不是数据本身

合成数据这两年热度很高,但很多团队对它的期待是错的。它真正解决的问题是"稀有样本拿不到"和"标注人力不够",而不是"数据不够多"。这两者差别巨大:前者是供给侧的结构性缺口,后者只是量的堆叠,而量的堆叠靠采集就能解决,没必要上合成。

质检场景特别典型。一条产线跑一个月,正常品可能几十万张,真正的缺陷品可能只有几十张,某些缺陷类型甚至一年才出现几次。这种情况下你没法靠采集解决,因为缺陷本身就不产生。合成数据在这里的价值就体现出来了:用生成模型或者图形渲染,把缺陷的形态、位置、光照条件参数化,批量造出可控的样本。

但这里有个陷阱我必须强调:合成数据的分布和真实数据不一致时,模型学到的可能是生成器的伪影,而不是缺陷本身的特征。我见过一个项目,合成数据造得非常逼真,人眼几乎分辨不出,但模型上线后在真实缺陷上召回率只有六成。后来排查发现,生成器在缺陷边缘总会留下一圈轻微的高频纹理,模型把这圈纹理当成了判断依据,真实图像里没有这圈纹理,模型就懵了。这个坑后面第6章会详细讲排查过程。

1.3 多模态质检是前两者最现实的交汇点

为什么说质检是多模态最现实的落地场景?因为它的需求天然就是多模态的:图像给出缺陷的位置和形态,文本给出缺陷的类型、等级和处理建议,两者缺一不可。传统的纯视觉分类只能告诉你"这是缺陷",但产线需要的是"这是划伤、二级、建议返修",这个信息层次靠单一模态表达不出来。

同时质检对延迟极度敏感。产线节拍可能是每件产品2秒,检测必须在1.5秒内出结果,否则就得停线等待。这个延迟要求直接把云端方案排除了——网络往返加排队,很难稳定压在几百毫秒内。所以质检天然适合端侧部署,而端侧部署又天然要求小模型,小模型又天然需要合成数据补足训练样本。这条逻辑链是自洽的,也是为什么这三个词总在一起出现。

2. 端侧小模型的参数账:从1.5B到7B的实际取舍

2.1 内存占用到底怎么算

先给一张我常用的估算表,按FP16、INT8、INT4三档量化,列出纯权重占用。这张表是讨论的起点,但不是终点。

模型规模FP16权重INT8权重INT4权重2K上下文KV Cache(FP16)建议最小内存
1.5B约3.0GB约1.5GB约0.8GB约0.2GB4GB
3B约6.0GB约3.0GB约1.5GB约0.4GB6GB
7B约14.0GB约7.0GB约3.5GB约1.0GB12GB

KV Cache的算法是:2(键和值)× 层数 × 隐藏维度 × 序列长度 × 每元素字节数。以7B模型32层、隐藏维度4096、序列长度2048、FP16存储为例,2×32×4096×2048×2字节,约等于1.07GB。这个数字很多人会忽略,但它是随序列长度线性增长的,如果你的场景需要处理长文本描述,这块会迅速膨胀。把KV Cache也量化到INT8,能省一半,代价是长上下文下的精度会有轻微损失。

除了权重和KV Cache,还要加上中间激活值和框架开销。激活值在batch为1、序列2048的情况下,峰值通常在几百MB量级;框架自身的运行时开销(内存池、算子缓冲区、模型加载时的临时对象)也要预留500MB到1GB。所以7B模型INT4量化后,理论权重3.5GB,加上这些杂项,实际峰值会到5.5GB左右,8GB设备跑起来非常紧张,一旦系统里还有别的进程,就容易触发内存交换,延迟直接从百毫秒级跳到秒级。

如果场景是多模态,还得额外算上视觉编码器的开销。一个参数量三亿左右的视觉塔,FP16下权重约600MB,加上图像预处理和特征缓存,整体再加1GB是保守估计。这就是为什么很多多模态方案在端侧跑不动——不是语言模型太大,是视觉部分吃掉了预算。

2.2 量化位数背后真实的精度代价

量化不是免费的。从FP16到INT8,大多数任务上的指标下降在1个点以内,基本可以接受;从INT8到INT4,就要看任务类型了。分类、检索这类任务对量化相对鲁棒,生成式任务和细粒度判别任务就敏感得多。

我做过一组对比,在同一个缺陷分类任务上,用同一批测试集评估不同量化档位的表现:

量化方案分类准确率稀有类召回单帧延迟内存峰值
FP16基线94.2%81.5%380ms6.8GB
INT8对称量化93.8%80.9%210ms3.9GB
INT4分组量化(group=128)92.1%76.3%130ms2.4GB
INT4分组量化(group=64)93.0%79.1%145ms2.6GB

注意稀有类召回这一列,它比总体准确率更能反映问题。INT4在总体准确率上只掉了2个点,但稀有类召回掉了5个多点。这在质检场景里是灾难性的,因为稀有类恰恰是你最想检出的那类缺陷。原因不难理解:量化误差对高频特征更敏感,而稀有类的判别依据往往就是一些细微的纹理差异,这些差异在4bit的表示精度下被抹平了。

分组量化能缓解这个问题,group size从128降到64,精度明显回升,代价是内存和延迟都增加了一点。我的经验是,如果任务的判别依据是细粒度纹理,group size不要大于64;如果是粗粒度分类,128足够。另外校准集的选取极其关键,必须用真实场景采的图,不能用公开数据集凑数,这一点后面第6章有血泪案例。

还有一个容易被忽略的点:量化后的模型不能只看平均值,要看最差类别。我习惯把每个类别的指标单独拉出来看,如果某个类别的指标掉得特别厉害,说明这个类别的特征对量化最敏感,要么给它单独保留高精度分支,要么在训练时对这个类别做量化感知训练。

2.3 推理框架选型时真正该看的三个指标

框架选型网上有很多对比,但多数在比吞吐和算子数量,这些对端侧项目不是最重要的。我实际选型时只看三件事:内存峰值是否可控、是否支持动态输入尺寸、异常恢复能力如何。

内存峰值可控指的是框架有没有提供内存池配置,能不能限制最大占用。有些框架默认会预分配一大块内存,在小内存设备上直接OOM,即使实际用不到那么多。支持动态输入尺寸是因为质检图像的尺寸经常随产品型号变化,如果框架只支持固定shape,每换一个型号就要重新编译一次模型,运维成本极高。异常恢复能力指的是推理过程中出现异常输入(比如空图、超尺寸图、损坏文件)时,框架是抛异常还是直接崩溃。产线环境里什么奇怪输入都可能有,一个没处理好就可能导致整个服务挂掉。

我一般会在正式选型前做一个最小验证:拿一台目标配置的设备,跑一个持续24小时的稳定性测试,中间夹杂10%的异常输入。这个测试能暴露的问题比任何benchmark都有价值。曾经有个方案在跑分上领先,但连续跑6小时后内存缓慢增长,最后发现是某个算子的输出缓存没释放,这种问题只看单次推理延迟是发现不了的。

3. 合成数据的完整链路:生成只是第一步

3.1 从"能造出来"到"能用"之间隔着什么

生成合成数据现在门槛很低,一个开源生成模型加几十行脚本就能批量出图。但造出来的图能不能用,是另一回事。中间隔着的是分布对齐这个环节,而这一步没有任何自动化工具能完全替代人工判断。

分布对齐要检查的维度包括:颜色分布、纹理频谱、缺陷形态的物理合理性、背景复杂度。颜色和纹理可以用统计方法比对,比如计算真实集和合成集在特征空间的分布距离;缺陷形态的物理合理性必须靠人看,因为生成模型不懂物理——它可能画出一个违反材料力学规律的裂纹走向,或者一个不可能出现在那个位置的划痕。背景复杂度更微妙,合成数据的背景往往过于干净,模型在干净背景上学到的特征,到杂乱的真实背景里就失效了。

我通常的做法是做一个三档抽检:先随机抽100张合成图自己过一遍,筛掉明显不合理的;再用一个预训练的特征提取器算真实集和合成集的特征分布距离,距离过大就说明分布偏了;最后拿少量真实数据训练一个小分类器,用它给合成图打分,分数异常的挑出来复查。这套流程跑下来,能过滤掉七八成不可用的合成数据。

3.2 质量过滤的三道筛子

第一道是规则筛,纯工程手段,成本最低。检查图像尺寸是否合规、是否有损坏、是否有大面积纯色区域、清晰度是否达标。这一步能干掉大部分低级错误,比如生成过程中断产生的半张图、尺寸不对的图、纯噪声图。规则筛的阈值要根据实际数据定,不要照抄,比如清晰度阈值得用真实数据的分布来标定,取真实数据清晰度的5%分位数作为下限比较合理。

第二道是模型筛,用一个已经在真实数据上训练过的模型给合成数据打分。这里有个细节:不要用最终要训练的那个模型来筛,会造成信息泄漏,让模型对自己的偏好过度自信。用一个结构不同、训练数据不同的小模型做筛选更可靠。打分之后按置信度分层,高置信度的直接用,中等置信度的进人工复查池,低置信度的直接丢弃。

第三道是人工抽检,比例控制在5%到10%。人工抽检的重点不是逐张判断对错,而是发现系统性偏差。比如你抽了50张,发现其中8张的缺陷位置都集中在图像右下角,这就是系统性偏差,说明生成时的位置参数分布有问题,要回头改生成策略,而不是逐张删掉这8张。

下面是我实际用的一个过滤脚本骨架,逻辑很直白:

import numpy as np from PIL import Image def rule_filter(path, min_side=512, min_sharpness=80.0): try: img = Image.open(path).convert("RGB") except Exception: return False, "decode_fail" w, h = img.size if min(w, h) < min_side: return False, "too_small" gray = np.asarray(img.convert("L"), dtype=np.float32) # 用拉普拉斯响应的方差近似清晰度 lap = np.abs(np.diff(gray, axis=0)[:, :-1] + np.diff(gray, axis=1)[:-1, :]) if lap.var() < min_sharpness: return False, "blurry" # 大面积纯色检测 if gray.std() < 5.0: return False, "flat" return True, "ok" def score_filter(path, scorer, low=0.3, high=0.85): ok, reason = rule_filter(path) if not ok: return "reject", reason s = scorer(path) # 返回0~1的置信度 if s >= high: return "accept", s if s >= low: return "review", s return "reject", s

这套脚本不复杂,关键是阈值要用真实数据标定,而且在项目推进过程中要定期重新标定,因为随着合成策略调整,数据的分布会变。

3.3 真实与合成的配比实验记录

配比没有通用答案,必须做实验。我在一个缺陷分类任务上跑过一组对比,固定总训练样本数为8000,调整真实与合成的比例,结果如下:

真实:合成验证集准确率稀有类召回测试集准确率测试集稀有类召回
1:0(全真实)91.5%62.3%89.8%58.1%
1:194.2%78.6%92.1%73.4%
1:295.1%82.4%92.8%75.9%
1:395.8%85.1%91.9%71.2%
1:596.3%87.2%90.4%66.8%

这张表最值得注意的是验证集和测试集的背离。随着合成比例提高,验证集指标一路涨,但测试集在1:2之后开始掉。原因是验证集里也混了合成数据,模型在合成数据上过拟合的表现,在验证集上体现不出来,只有纯真实的测试集才能暴露问题。

所以有一条我反复强调的纪律:验证集和测试集必须全部是真实数据,一张合成数据都不能混进去。混进去的后果是你会得到一个虚高的指标,然后在上线时被打脸。1:2这个比例在这个任务上是甜点,但换个任务就未必,缺陷形态越简单,合成数据能提供的边际价值越小;缺陷越稀有、越难采集,合成的价值越大。

4. 多模态质检落地时那些纸面上看不到的问题

4.1 缺陷样本的极端不平衡怎么处理

真实的质检数据集长这样:正常品占比99%以上,剩下的不到1%里,还分成十几个缺陷类型,某些类型只有个位数样本。这种分布下,任何标准的训练流程都会退化成一个"全判正常"的模型,因为这样损失最小。

处理手段有几层。最直接的是采样策略,对稀有类做过采样,但简单复制样本会导致过拟合,我一般配合强增强一起用,几何变换、颜色抖动、局部遮挡轮着上。第二层是损失函数,用带类别权重的交叉熵,权重取类别频率的倒数再开方,开方是为了避免权重过激导致训练不稳定。第三层是合成数据补充,这一层效果最明显,但前提是前面第3章说的过滤流程做到位。

还有一个常被忽略的点:评估指标的选择。准确率在这种分布下毫无意义,我一般看每类的召回率和误检率,再算一个加权指标,权重按业务上每类缺陷的严重程度来定。严重缺陷漏检的代价远高于轻微缺陷误检,这个不对称必须在指标里体现出来,否则模型会朝着"少误检"的方向优化,把严重缺陷也一起放过。

4.2 光照、工位、相机型号带来的域偏移

实验室里训练的模型,搬到车间准确率掉十几个点是常态。原因基本集中在三处:光照条件、相机参数、工位差异。

光照是最麻烦的,因为车间的光照随时间变化——白天有自然光从窗户进来,晚上只有顶灯,阴天和晴天的色温也不一样。应对办法是训练时做充分的光照增强,同时在部署端做白平衡校正。白平衡这一步很多人省掉了,觉得模型能自己学,实际上固定一个白平衡基准能大幅降低分布漂移,成本几乎为零。

相机参数包括焦距、光圈、曝光时间、色彩配置。同型号不同批次的相机,出厂色彩配置都可能有细微差异。我的做法是给每台相机做一次标定,用标准色卡拍一张,把色彩偏差校正到统一基准,把这个校正参数固化到预处理流程里。

工位差异指的是同一台设备在不同产线的安装位置不同,导致拍摄角度、距离、背景都变了。这个问题没法靠算法完全解决,只能靠数据覆盖——每个工位都要采一批数据进训练集,并且在部署时给每个工位单独配置阈值。如果工位数量多到采不过来,那就得考虑用合成数据做视角变换,把已有工位的数据扩展到新视角,这是合成数据在域偏移问题上比较实用的一个用法。

4.3 误检率的业务容忍度与阈值设定

阈值设定不是技术问题,是业务问题。同一套模型,阈值调高一点,漏检少误检多;调低一点,误检少漏检多。选哪个取决于业务能承受哪种错误。

我一般的做法是拉一张阈值扫描表,把不同阈值下的漏检率和误检率都列出来,让业务方来选。表格形式大致如下:

阈值漏检率误检率每小时人工复核量业务反馈
0.9012.4%1.2%约15件漏检太多,不可接受
0.806.8%2.9%约35件可接受上限
0.703.1%5.6%约68件复核压力偏大
0.601.9%9.8%约120件复核扛不住

这张表的价值在于把技术指标翻译成了业务语言。"每小时人工复核量"这个列是最有说服力的,因为复核人力是实打实的成本。选0.80这一档,意味着每小时35件需要人工确认,一个人能扛得住;选0.70就是68件,可能就得配两个人。

还有个技巧:分级阈值。不同缺陷类型用不同阈值,严重缺陷阈值调低保证不漏,轻微缺陷阈值调高减少误报。这个做法在实际项目里效果很好,因为它精准匹配了业务的不对称性。实施成本也不高,就是在推理后处理阶段维护一张阈值映射表。

5. 把三件事串起来:一个小型质检原型的搭建过程

5.1 硬件与软件栈的确定过程

回到开头朋友那个问题,8G内存无独显的工控机能不能跑。我给的建议是先别急着定硬件,把需求拆开:要检测几种缺陷、图像分辨率多大、节拍要求多少、能不能接受外接一个边缘计算盒子。

如果必须用现有工控机,那模型规模基本锁死在1.5B到3B这个区间,且必须INT4量化,视觉部分要用轻量级的编码器。如果允许加一个边缘盒子,选择空间就大很多,可以上到7B INT4,视觉部分也能用标准规模的编码器。

软件栈我一般这样配:推理引擎选支持动态shape和内存池配置的,服务层用一个轻量的异步框架,中间加一层消息队列做削峰。消息队列这层很多人省掉,但产线场景下突发流量是常态(比如换班时集中检测),没有缓冲层很容易丢帧。存储层用本地环形缓冲区,保留最近若干帧的原图和推理结果,方便出问题时回溯,这个成本很低但排查问题时价值巨大。

部署形态上,我倾向于把预处理、推理、后处理拆成三个独立进程,通过本地队列通信。好处是任何一环卡住不会拖垮整体,坏处是增加了一点通信开销。在延迟预算充足的情况下,这个拆分带来的稳定性提升远大于那点开销。

5.2 数据闭环的最小可行设计

数据闭环听起来很重,其实最小可行版本只需要三个环节:现场采样、人工复核、回流训练。

现场采样就是在推理服务里加一个逻辑,按一定比例(比如1%)保存原图,同时保存推理结果和置信度。采样要分层,高置信度的正常品少采,低置信度的、被判为缺陷的多采,这样回流的数据才有信息量。

人工复核是给采样出来的图打标,重点是纠正模型的错误判断。这一步的人力成本要提前算清楚,按每小时能复核多少张来估算需要的工时。如果复核量太大,就降低采样比例,或者只复核低置信度区间。

回流训练是把复核后的数据加入训练集,重新训练或微调。频率不用太高,两周一次足够,太频繁反而不好管理版本。每次回流训练都要记录清楚加了哪些数据、指标变化如何,形成可追溯的记录。

这套闭环跑起来之后,你会发现合成数据的需求会逐渐下降——因为真实数据的覆盖度在提升。这是个好现象,说明系统在自我完善。合成数据应该定位成冷启动阶段的助推器,而不是长期依赖。

5.3 上线前必须过一遍的评估清单

我自己维护一份清单,每次上线前逐项打勾:

  • 延迟:P50和P99都要测,P99不能超过节拍预算的70%
  • 内存:连续运行24小时的峰值内存,不能有缓慢增长趋势
  • 准确率:每个类别单独看,不能只看总体
  • 误检率:按业务容忍度核对,超标的类别单独处理
  • 长稳:连续72小时无崩溃、无内存泄漏、无结果漂移
  • 异常输入:空图、超尺寸、损坏文件、纯色图,逐个测一遍
  • 降级方案:推理服务挂掉时,产线能否降级到人工全检,切换耗时多少
  • 日志与回溯:任意一帧的结果能否在5分钟内定位到原图和中间结果

最后两项经常被跳过,但它们是事故发生时唯一的救命稻草。降级方案没做好,服务一挂整条线就停;日志没做全,出了问题只能靠猜。

6. 这一周踩到的几个具体坑

6.1 量化之后某个类别集体失灵

这周最花时间的一个问题:INT4量化后的模型,在某个特定缺陷类别上准确率从88%掉到了31%,几乎等于随机猜。总体指标只掉了2个点,所以一开始没发现,是逐类看指标才揪出来的。

排查过程分三步。第一步确认是量化引入的,用同一批测试数据跑FP16版本,指标正常,问题复现。第二步定位是哪一层出的问题,逐层对比量化前后的输出差异,发现是某个注意力层的输出偏差特别大。第三步查这个层的输入分布,发现这个类别的图像在某个颜色通道上的数值范围特别窄,而量化校准集里几乎没覆盖这个范围,导致量化步长按全局分布定,把这个窄区间的信息全压没了。

修复方案是把校准集换成按类别分层的采样,保证每个类别都有足够样本,同时把这个类别的样本在合成数据生成时增加了权重。改完之后该类准确率回到85%,虽然比FP16的88%还差一点,但在可接受范围内。

这个坑的教训很明确:量化校准集的代表性比数量重要得多。用一万张正常品校准,不如用一千张按类别均衡采样的数据。

6.2 合成数据把模型带偏的隐蔽现象

另一个坑更隐蔽。加了合成数据训练之后,验证集指标涨得很好,但测试集纹丝不动,甚至略有下降。第一反应是过拟合,于是加了正则、减了合成比例,都没用。

后来做了个对比实验:把测试集按"缺陷边缘清晰度"分层,发现合成数据训练的模型在清晰度高的样本上表现好,在清晰度低的真实样本上表现差。再往下查,发现生成模型造出来的缺陷图,边缘总是偏锐利,即使刻意做了模糊处理,模糊的质感也和真实相机拍出来的不一样——真实图像的模糊是光学模糊加上传感器噪声,生成图像的模糊是算法平滑,两者在频域上的表现完全不同。

模型确实学到了东西,但它学到的是"锐利边缘等于缺陷"这个错误关联。修复办法是在合成数据的后处理里加入模拟真实成像链路的环节:加传感器噪声、做光学模糊、模拟压缩伪影。这套后处理比生成本身还花时间,但效果立竿见影,测试集稀有类召回从71%提到了79%。

这件事让我对合成数据的态度谨慎了很多。它是个好工具,但必须假定它自带偏差,然后用工程手段去消除,而不是假定它是对的。

6.3 多模态输入的对齐问题

最后一个坑来自多模态。图像和文本描述同时输入时,如果文本描述比较长,会出现特征对齐问题——图像的视觉token和文本的语义token在序列里的相对位置变化,导致模型对图像区域的关注发生偏移。

具体表现是:短描述("划伤,二级")时检测正常,长描述("表面有线性划伤,长度约3毫米,位于边缘区域,建议返修")时,模型开始漏检边缘区域的缺陷。排查发现是位置编码在长序列下的外推能力不足,视觉token的相对位置被稀释了。

临时方案是限制文本描述长度,把结构化字段拆出来单独处理;长期方案是改用支持更好位置外推的编码方式,并且在训练数据里刻意混入长短不一的描述,让模型适应。这个问题在多模态方案里很常见,尤其是在端侧用的小模型上,因为小模型的位置编码设计往往更保守。

我现在做多模态方案,会在一开始就测一遍长短描述的一致性——同一个样本,用短描述和长描述分别推理,结果差异超过5%就要警惕。这个测试五分钟就能做完,能提前发现大问题。

我个人在实际操作中的体会是,这条链路上没有哪个环节可以一次做对,都是在现场反复撞墙之后才收敛的。端侧的内存账要按峰值算而不是按权重算,合成数据的价值取决于过滤链路做得多细,多模态的坑大多藏在输入的对齐细节里。如果非要给一个起点建议:先把资源预算表和评估清单这两张纸做出来,再动手选模型,能省掉一大半返工。

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

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

立即咨询