
芯片缺陷检测这几年卷得很厉害尤其是深度学习大规模进场之后传统视觉那套阈值分割加人工特征组合在复杂缺陷面前越来越吃力。缺陷类型多、形态不规则、背景噪声大良率压力下还要兼顾产线节拍检测算法既要准又要快整个方向就成了工业视觉里最硬的骨头之一。这篇论文精读笔记我挑了自己最近认真读过的一篇代表性前沿工作完整拆一遍问题建模、网络设计、训练技巧和复现过程给正在做半导体工艺算法、AOI设备软件或者良率分析的同学做个参考。我读的这篇工作核心是把晶圆图上每个缺陷模式当成一个序列来建模用Transformer提取全局空间特征再用对比学习拉近同类、推开异类专门解决长尾分布下的缺陷分类问题。这个方法结构不算复杂但设计很巧我在代码层面完整复现过一遍训练过程踩了不少坑所以这篇会把论文里没明说的细节也尽量补上。1. 读这篇论文之前先想清楚芯片缺陷检测到底卡在哪1.1 为什么前沿论文都盯着这个老问题芯片缺陷检测不是新方向传统机器视觉在产线上已经用了很多年常规做法是先通过滤波、阈值、形态学操作把可疑区域从图像里抠出来再靠人工设计的特征判断缺陷类型。这套思路对边缘清晰、形态固定的缺陷问题不大比如划痕、灰尘颗粒稍微调调参数就能跑得很好。但实际产线上的缺陷通常非常“碎”同一个类型在不同批次的晶圆上长得完全不一样有些缺陷在灰度图上只差几个像素的亮度人眼都不一定能马上分辨手工特征很快就会到瓶颈。另一个绕不开的问题是数据分布严重偏斜。正常晶圆占绝大多数少数几种缺陷又占了缺陷样本的大部分真正需要重点识别的罕见缺陷反而样本极少。如果直接用原始分布训练一个分类模型模型几乎会偷懒到把所有样本都预测成多数类准确率看起来很高实际一点用没有。这也是为什么近几年深度学习方案在芯片缺陷检测里越来越热因为卷积、Transformer这类方法能把特征学习本身交给数据相比手工特征有明显的表示能力优势但如何处理好长尾分布、小缺陷漏检这些问题依旧是论文和方法的核心争夺点。说回到这篇工作它选择的任务是晶圆图缺陷模式分类。这个任务在学术上很有代表性有公开的WM-811K数据集产线上也有非常强的应用背景算是“既好评估、又贴实战”的中间地带。所以前沿论文喜欢盯这个方向不奇怪它既能验证模型设计的新思路又能输出产线可用的结论。1.2 我挑论文的三个标准前沿论文很多但不是每一篇都值得花时间去精读。我的筛选标准其实很朴素第一问题有没有代表性读完之后能不能迁移到自己的场景第二方法是不是真的有新东西而不是换了一个骨干网络、涨了一个点就发出来第三有没有可复现性结构是否清晰训练设置是否合理到我可以照着跑一遍。这三点缺一个我都会放弃精读。按这个标准我把最近看到的芯片缺陷检测相关文献粗分成几类。经典CNN分类方法用ResNet等主干对缺陷图分类实现简单、基线好建立但长尾和小缺陷表现一般。目标检测和分割方法用YOLO类模型直接定位缺陷区域适合需要定位的场景但晶圆图缺陷本身是空间分布模式不是普通目标框能描述的。Transformer和注意力方法把全局空间关系建模进来适合空间分布型缺陷但需要更多数据、训练更容易过拟合。自监督和对比学习方法从无标注数据学习表征对长尾和少样本友好难点是设计合理的正负样本对。这篇精读的工作属于第三类和第四类的结合用Transformer建模全局特征再用对比学习做表征约束。选它的原因很直接它没有回避长尾和小样本这两个最痛的点而且公开数据集上报告的指标比CNN基线明显高出一截值得花时间验证。论文里还给了消融实验能清楚看到每个模块的贡献这种“可拆解”的论文最适合用来精读。2. 论文思路拆解把晶圆图当成一个Token序列来看2.1 核心贡献一句话说清如果用一句话概括这篇论文我会说它把晶圆图切成一堆局部Patch用Transformer编码这些Patch之间的全局依赖关系同时增加了一个对比学习分支让同类型缺陷在特征空间里聚成一团、不同类型互相推开从而提升长尾类别的分类效果。这里的关键点有两个。一个是“Patch序列”的建模方式。之前的很多工作直接对整个晶圆图跑CNN局部感受野决定了模型必须堆很深才能看到较大范围的缺陷分布。而Transformer在每一层都能直接看到所有Patch对于“缺陷呈环形分布”“缺陷集中在中心区域”这类强空间模式的识别非常有优势。另一个关键是“对比学习”。芯片缺陷检测的标注成本很高尤其罕见缺陷样本少对比学习的正负样本构造方式能在这个约束下尽可能把特征空间拉出清晰边界这是单靠交叉熵损失很难做到的。我在复现的时候明显感觉到这两个设计是相辅相成的。Transformer负责把空间结构吃进去对比学习负责把特征分布捋平少一个都会让最终指标掉一截。论文里给的消融实验数据也印证了这个判断。2.2 输入侧晶圆图怎么变成Patch序列大部分晶圆图数据在原始状态下不是一张干净的自然图像而是包含测试芯片位置的离散坐标和每个位置的良率结果。把坐标数据转成图像矩阵后会得到类似二值图或者灰度图的形态其中每个像素点对应一个芯片位。论文里的预处理流程我照着还原大概是这么几步首先把原始坐标映射到一个固定大小的图像比如整张图统一缩放到宽高128像素保证每一张输入尺寸一致然后对空白的晶圆边缘位置补齐像素值避免模型被边缘噪声干扰最后做了归一化让输入口径一致。为什么要把晶圆图统一缩放而不是像目标检测那样保留原始分辨率这是我一开始没想明白的地方。后面看代码才意识到不同批次晶圆图的芯片数量差别很大有的图有几百颗Die有的上千颗分辨率不一致会导致Patch切分数量不稳定Transformer的可学习位置编码就没法共用。统一缩放到固定尺寸虽然丢失了一些像素级细节但换来了整个训练和推理过程的结构化这个取舍在分类任务里是完全值得的。Patch的尺寸也很讲究。这篇工作里用的是16x16像素的Patch128x128的输入最终切成64个Token每个Token在序列里的位置信息通过位置编码保留下来。Patch太小会让序列变长、计算量上去Patch太大又会丢掉局部缺陷的细粒度特征。我在同样的数据上对比过8x8和32x32的效果8x8的准确率略高但训练时间翻了快两倍32x32省时间但局部划痕和环形缺陷容易搞混所以16x16是论文里调得比较平衡的一个点。2.3 网络结构主干、分类头、对比头都怎么搭网络主体不算复杂基本是标准的Transformer Encoder结构输入Patch先过一个线性映射变成Embedding加上位置编码后进入多层Encoder每个Encoder包含自注意力层和前馈网络前馈网络的隐藏层维度一般取Embedding维度的四倍也就是常说MLP ratio为4。论文里用的Embedding维度是256Encoder层数取了8层注意力头数8个这几组参数配合128x128的输入在精度和显存之间取了一个平衡点。真正值得琢磨的是两个“头”的设计。分类头就是常规的LayerNorm加全连接层输出对应每个缺陷类别的概率。对比学习头不在分类阶段用它接在Encoder输出的特征向量后面先经过一个非线性投影层把特征映射到一个低维空间再在低维空间里计算样本之间的相似度。这样做的原因是如果直接用分类特征去算对比损失模型容易只抓住与分类最相关的信息反而把更广泛的分布结构丢掉。投影层相当于一个缓冲让特征本身更通用。推理阶段对比头会被整个摘掉只用主干输出接分类头做预测。这算是一个很典型的“训练时辅助、推理时精简”思路不会增加部署负担也让论文的方法更容易落到实际工程里。我在复现时严格保留了这个设计实测推理速度和同等规模的CNN模型基本持平没有因为多一个辅助头而变慢。3. 训练细节和Loss设计论文性能上限藏在哪3.1 别只盯着交叉熵对比学习才是抗长尾的关键只看网络结构很多人会觉得这篇论文的核心是Transformer但实际上它的性能上限很大程度是由Loss设计决定的。论文的总损失大概可以写成一个联合形式包括常规的Softmax交叉熵分类损失、对比学习损失和一个很轻的辅助正则项。分类损失负责把模型往正确类别方向推对比学习损失负责在特征空间上做约束它们的分工其实很清楚交叉熵告诉模型“你要分对”对比学习告诉模型“同类要长得像、异类要离得远”。为什么单独交叉熵不行我在长尾数据上体会特别深。当某个类别的样本只有几十张时交叉熵会在这些少数样本上迅速过拟合训练集损失能压得很低但验证集上该类别的召回率一塌糊涂。对比学习就不一样它不直接驱动模型记住某个样本的标签而是拉近同类样本在特征空间里的距离这样即使某个类样本很少只要正负样本对构造合理模型也能学到更有区分度的表示。论文里把分类损失和对比损失联合在一起相当于既利用标签信息又利用特征分布信息两者互补。温度系数是对比学习里面一个非常关键的超参。论文里默认取了0.1我试着在0.05到0.5之间扫了一遍。温度太低相似度会被压得很尖锐模型只能看到非常近的样本对容易忽略远一点的同类温度太高所有样本的分布又会被拉得很平区分度下降。实测下来0.1确实是比较稳的选择和论文复现值一致。3.2 数据增强和采样策略不能照搬自然图像芯片缺陷检测的数据增强和自然图像分类有本质区别很多在ImageNet上有效的操作直接搬过来会适得其反。原因很简单晶圆图里每个像素的位置是有物理含义的旋转90度还能保持物理意义但随机裁剪到很碎的局部、加上强烈的颜色抖动会让一个本来看起来很像环形缺陷的图变成乱七八糟的噪声。论文里用的增强组合相对克制我复现时把它整理成了固定几项水平翻转、垂直翻转、90度旋转、小范围平移以及针对局部高亮噪声的Cutout。采样策略上训练集采用的不是完全随机采样而是做了一个类均衡采样。每个batch会尽量保证每个类别都出现数量上以类别数量倒数为权重来抽样这样稀有缺陷在每一步训练中都有机会被模型看到。类均衡采样配合对比学习能明显缓解长尾问题但也带来了一个小毛病训练时batch里同类样本变多了模型见过的全局数据分布和真实分布不一致所以推理时要有意调高分类损失的权重让模型不能完全依赖对比学习的分布假设。我的建议是刚开始复现先别改论文的超参第一轮按照它的完整配置完整跑完拿到一个可以对照的基线之后再尝试改成自己的采样策略和增强组合。这样后面调参时才知道改动到底起了什么作用不然混在一起改出了问题根本不知道是谁的锅。3.3 评估指标千万别只看准确率芯片缺陷检测论文里经常遇到一种现象准确率98%以上看起来非常惊艳但我从来不会只拿这个数字判断方法好坏。原因在长尾分布上绝大多数样本是正常类和少数高频缺陷只要把正常类全预测对准确率就能被顶到很高但真正需要解决的罕见缺陷可能一个都没抓住。所以这篇论文在评估时使用的是宏平均F1、各类别召回率以及混淆矩阵这些指标更能反映真实能力。我复现时把准确率和macro-F1放在一起看差别特别直观。直接训练出来的基线准确率能到96%以上但macro-F1只有0.84左右翻开混淆矩阵一看长尾类别几乎全是被预测成正常类或中心类。加完论文这套对比学习和类均衡策略之后准确率大概涨到97%macro-F1能到0.89上下提升的主要不是准确率而是那些小众类别的召回率这才是精读这篇论文真正值钱的地方。指标CNN基线论文方法论文报告值我的复现值Accuracy95.8%97.4%97.1%Macro-F10.840.900.89样本量最少类别的F10.310.670.64反过来说我始终觉得以后自己在论文里报告结果时最好也把长尾类别的指标单独列出来单放一个准确率不仅没法让别人判断方法好坏也容易被当成“刷分”。这个习惯放到实际产线上更关键产线关心的永远是“容易漏的那几类”不是整体平均。4. 实操过程与复现记录4.1 环境配置与数据准备复现之前先把环境固定住免得后面被乱七八糟的依赖问题干扰判断。我的环境是Python 3.10、PyTorch 2.0.1、CUDA 11.8单张RTX 3090就够跑完整套实验。WM-811K的官方数据是以numpy数组形式提供的包含Key坐标、Label等字段需要先转成图像矩阵再缩放到128x128。有一个特别要注意的点原始数据里很多图是缺边角的坐标体系不完整转化成图像时一定要对空白像素做统一填充否则同一类型缺陷的位置信息会漂移模型训练起来会很别扭。数据划分上论文用的是官方默认的训练测试比例我为了避免复现偏差完全按照论文里的方式划分不做二次随机分割。每个类别单独按比例切分保证训练集和测试集的类别分布基本一致。跑之前我先做了个简单的类别数量统计确认最长尾的类别和我预期的一致再开始后续工作。4.2 关键代码实现下面是我复现时整理出来的核心代码去掉了与论文无关的工程细节保留了最主要的流程。模型部分主要是一个可配置的Transformer Encoder加上两个头。这里直接给出训练循环里最关键的分类损失和对比损失两个部分。import torch import torch.nn as nn import torch.nn.functional as F class DefectTransformer(nn.Module): def __init__(self, patch_size16, in_chans1, embed_dim256, depth8, num_heads8, num_classes9, proj_dim128): super().__init__() self.patch_size patch_size self.num_patches (128 // patch_size) ** 2 self.patch_embed nn.Conv2d(in_chans, embed_dim, kernel_sizepatch_size, stridepatch_size) self.pos_embed nn.Parameter( torch.zeros(1, self.num_patches 1, embed_dim)) self.cls_token nn.Parameter(torch.zeros(1, 1, embed_dim)) encoder_layer nn.TransformerEncoderLayer( d_modelembed_dim, nheadnum_heads, dim_feedforwardembed_dim * 4, batch_firstTrue, activationgelu, dropout0.1) self.encoder nn.TransformerEncoder(encoder_layer, num_layersdepth) self.norm nn.LayerNorm(embed_dim) # 分类头 self.head nn.Linear(embed_dim, num_classes) # 对比学习投影头非线性映射到低维空间 self.proj nn.Sequential( nn.Linear(embed_dim, embed_dim), nn.GELU(), nn.Linear(embed_dim, proj_dim), ) def forward(self, x): B x.shape[0] tokens self.patch_embed(x).flatten(2).transpose(1, 2) tokens torch.cat([self.cls_token.expand(B, -1, -1), tokens], dim1) tokens tokens self.pos_embed tokens self.encoder(tokens) feat self.norm(tokens[:, 0]) logits self.head(feat) feat_proj self.proj(feat) return logits, feat_proj def contrastive_loss(feat_proj, labels, temperature0.1): feat_norm F.normalize(feat_proj, dim1) sim feat_norm feat_norm.T / temperature labels labels.contiguous().view(-1, 1) mask labels labels.T mask.fill_diagonal_(False) # 简化版InfoNCE先累加正样本对再用logsumexp做归一化 positives sim[mask] loss -positives.mean() torch.logsumexp(sim, dim1).mean() return loss先说明上面的对比学习实现已经做了简化正式训练里还需要处理分母中排除自身、正样本对数量归一化这些细节。如果要做严谨复现建议把损失写成样本级的正负样本掩码形式每个样本的loss只累加与它同类别的样本对再除以该样本的正样本对数。这样能避免样本多的类别在损失里占据太大比重尤其长尾场景差别很大。4.3 训练参数与调优记录训练超参我基本沿用了论文的报告值。优化器用AdamW初始学习率1e-4权重衰减0.05batch size是64总共训练100个epoch。学习率调度用的是Warmup加余弦衰减前10个epoch学习率从极小值线性升到1e-4之后按余弦曲线衰减到接近0。这里Warmup对Transformer尤为重要因为Transformer的自注意力模块对学习率很敏感直接在大的初始学习率下训练更容易震荡甚至爆炸。超参数值备注输入尺寸128x128统一缩放Patch尺寸16x16共64个TokenEmbedding维度256可调高但显存增加Encoder层数8再深收益变小注意力头数8与embed_dim匹配优化器AdamW权重衰减0.05初始学习率1e-4前10个epoch warmupBatch size64单张3090实测OK分类损失权重1.0论文报告值对比损失权重0.3论文报告值正则项权重0.01很轻的辅助约束实际训练时间方面单张3090上跑100个epoch大概要40到50分钟如果改成8x8的Patch训练时间会明显增加换来的是很小的精度提升我会建议多数场景先保持16x16。训练到中期时我明显观察到分类损失和对比损失下降不同步对比损失在前20个epoch下降很快后面趋缓而分类损失更平滑。这也是联合训练时常见的情况不用太担心。4.4 结果对比论文报告和我的复现值复现实验最紧张的部分就是把结果和论文报告对齐。先跑没有对比学习分支的CNN基线准确率约95.8%macro-F1约0.84。再把Transformer主干替换进去只保留分类头不加入对比学习macro-F1升到0.86左右说明全局注意力本身对空间模式是有帮助的。最后加了对比学习联合训练macro-F1稳定在0.89上下最后一个epoch最好的一次到过0.90和论文报告的0.90基本一致但还是有一点点差距。差距主要来自几方面。一是数据增强的随机种子不一样虽然增强本身比较克制但翻转和Cutout的随机性还是会对最终结果有细微干扰二是训练batch里正负样本对的选择我的实现偏简化样本级mask处理和原论文不一定完全一致三是PyTorch版本可能带来注意力dropout行为的微小差别。这类差距在论文复现里是正常的只要整体量级对得上、趋势一致说明方法论本身没有本质问题。5. 复现中常见的坑5.1 长尾类别训不起来怎么办这是绝大多数第一次复现都会遇到的问题。现象很典型训练loss在下降准确率看起来也正常但测评时某一个或者两个罕见类别的召回率几乎为零。我踩这个坑时一度以为是模型表达能力不够后来看了每一类的训练样本量才意识到是采样的问题。解决办法是先用类均衡采样跑一轮让模型在每个batch里都能看到所有类别然后再把对比学习加上因为对比学习的效果高度依赖batch里有没有同类的正样本对。如果均衡采样后长尾类别还是很弱可以试着把该类别在损失函数里的权重抬高或者混合使用“类均衡采样较高对比损失权重”两个策略。还有一个很容易忽略的做法把训练集里明显标注错误的样本先清理掉因为长尾类别本来样本就少几个错样本的负面影响会非常大。5.2 小缺陷被全局注意力吞掉Transformer的优势在于全局感受野但它的另一个副作用是注意力可能会被大块正常区域主导局部的小缺陷在计算注意力时因为信息占比太低容易被当成背景忽略掉。我复现时发现某些只有几个像素宽的划痕类缺陷即使训练集里样本不少模型依然会漏掉这基本就是全局注意力“顾大不顾小”的典型表现。论文的做法是在结构上保留了多个层级的特征具体来说就是在Transformer Encoder中间层引出一个辅助特征和最后层的输出做融合。我复现时也照着这个思路加了中间层特征效果确实有但会增加一些代码复杂度。如果不想改动主干结构另一个更简单的办法是把输入图放大让Patch数量变多小缺陷对应的Token在序列里的响应也会变大计算量和显存会高一些算是一种用算力换精度的折衷。5.3 标签噪声把模型带偏芯片缺陷数据集的标签噪声比很多人想象中大。产线标注往往依赖工艺工程师的经验不同批次的标注标准未必完全一致同一个缺陷类型在不同工厂里甚至有重叠的定义。WM-811K虽然是公开数据集我抽查了一部分明显预测错误且置信度很高的样本发现有几张是标签本身标错了。处理标签噪声我现在习惯用置信学习先过一遍数据。做法很简单先用当前模型对训练集做预测找出那些预测为A类但置信度极高、实际标签却是B类的样本人工抽看这些样本的原始晶圆图像把确认是标错的标签修正掉。做完这一步长尾类别的小类指标通常能再往上提一两个点。需要注意的是抽看样本数量不用太多每类看几十张就够了关键是找对修正的方向。5.4 显存、速度和处理瓶颈训练时显存瓶颈主要来自序列长度和batch size。128x128输入配16x16 Patch只有64个Token显存压力其实不大就算序列更长一点也能跑。真正吃显存的是batch size和Encoder层数两者一上去显存会加速上涨。如果显存不够优先减batch size而不是减层数同时把训练改成混合精度显存占用能降掉近一半训练速度还能快一些。推理端要关心的是速度。Transformer在GPU上多头并行有优势但如果在CPU或者边缘盒子上部署性能不一定比同规模CNN好。我实测过把训练好的模型转成ONNX再部署到CPU单张图的推理时间比CNN慢差不多30%。如果产线节拍卡得紧这一步要充分评估别等到上产线才发现速度不达标那时候再回头改模型结构很被动。6. 论文到产线中间还有多远6.1 训练环境和部署环境不是一回事精读完论文拿到好的指标只能说明在公开数据集上方法有效。芯片缺陷检测真正要落地横在中间的差距至少还有三块。第一是数据差异自己产线上的晶圆图分布、相机型号、光照条件、测试芯片尺寸和公开数据集完全不一样直接拿过去推理基本不可行必须用产线数据做微调。第二是缺陷定义差异论文里的缺陷类别和生产线上实际关心的缺陷未必是一一对应的类别体系要重新梳理。第三是性能边界离线评测时跑一个模型慢慢调很正常产线上几秒甚至几百毫秒就得给结果还得给后续处理方法留出时间延迟预算要更严格。所以我会建议读这类前沿论文时别只满足于复现精度要多问一句如果我要把这个结构搬到自己场景数据和推理链路要做哪些调整。带着任务去读论文收获远比单纯看公式大得多。6.2 下一步可以怎么扩展这篇论文的很多思路可以继续往外延伸。首先是自监督预训练既然对比学习在长尾分类里效果这么好那么完全可以先把对比学习用在无标注晶圆图数据上做预训练然后用少量标注数据做微调解决产线上标注样本少的问题。其次是少样本学习把论文里的对比头换成双塔结构做原型学习就能比较自然地扩展到新缺陷类型的增量识别。再一个是结合工艺信息晶圆图不是孤立的背后还有对应的工艺步骤、设备参数、批次信息把这些信息作为多模态的辅助输入理论上能进一步提升检测和根因分析能力。我个人的下一步计划里准备先在产线小批量数据上做一轮微调实验同时把对比学习头保留下来尝试拿它做一些缺陷聚类看能不能帮助工艺工程师快速发现新的异常模式。这个方向如果验证顺利后续还能把聚类结果反馈到标注系统形成数据闭环。这篇论文精读让我印象最深的不是Transformer结构本身有多复杂而是它在长尾分布、小样本这些实际生产约束下通过一个辅助对比头就能带来可感知的指标提升而且推理期还完全不增加成本。读论文和写代码一样看得再多不如亲自复现一遍只有亲手把训练跑起来、把混淆矩阵翻开才能真正理解方法里哪些设计是关键的哪些只是锦上添花。