[粉丝专享] 用Plotly做交互式良率图:从静态截图到钻取分析的完整落地
[粉丝专享] 用Plotly做交互式良率图:从静态截图到钻取分析的完整落地
【摘要】本文讲清如何用Plotly与Dash把良率分析从静态PNG升级为可钻取的交互看板。覆盖lot到die四层数据模型设计、Scattergl与Heatmap的渲染选型、回调风暴的治理、DuckDB预聚合与缓存策略,并给出一次边缘环状低良的实战定位过程与前后效率对比数据。
分类:数据工具 | 发布:2026-08-17 | 首发:半导体智能制造博客
一、背景故事:一次真实的产线事件
去年十二月的一次良率检讨会,我到现在还记得当时的尴尬。产品工程师把一张Wafer Map的PNG贴在投影上,边缘一圈明显偏低。厂长盯着看了几秒,问了一个很自然的问题:这一圈低良的片子,是不是集中在某几台机台上?会议室安静了大概十秒钟,然后PE说,这个图是昨天导出来的汇总,要按机台拆需要重新跑一遍SQL和脚本,大概要一两个小时。厂长说那就下次再讲,会议直接跳到下一个议题。
这样的场景在我们厂一个月要发生三四次。当时的分析链路是这样的:工程师用SQL从测试数据库导出CSV,拉进Excel做透视,再用matplotlib画Wafer Map和Bin Pareto,最后把PNG贴进PPT。整条链路跑一轮,顺利的话四个小时,中间任何一个维度想换一下——换个产品、换个时间段、换个分组方式——都要从头再来一遍。我统计过团队的工时台账,那个季度里,光是"重跑一遍图"这件事,六个人合计花掉了二百七十多个小时。
真正的问题不是画图慢,而是这种链路杀死了分析的连续性。良率分析本质上是一个不断提问、不断缩小范围的过程:先看整体趋势,发现某周异常,再看是哪个产品,再看是哪个Bin,再看Bin在Wafer上的空间分布,再看这些片子的加工履历有没有共同点。这个链条上任何一环需要等一小时,思路就断了。后来我们花了两周做了一套基于Plotly和Dash的交互看板,把这条链路压到分钟级。这篇文章把整个过程完整写下来,包括数据模型、渲染选型、性能优化和踩过的坑。
二、技术原理:把机制讲清楚
先说清楚Plotly的架构,这决定了后面所有设计。plotly.py在Python侧做的事情其实只有一件:把你的数据和样式配置序列化成一份JSON格式的figure规范,这份JSON包含data数组和layout对象。真正的渲染发生在浏览器端,由plotly.js接管,用SVG或者WebGL把图画出来。缩放、平移、hover提示、图例点选这些交互,全部在浏览器本地完成,不需要回到Python。这就是为什么单纯用plotly.express画的图,导出成HTML之后仍然可以缩放和hover。
但"钻取"是另一回事。点击一个Bin柱子,要求联动刷新Wafer Map,这需要把浏览器端的点击事件传回Python,重新计算数据再推回前端。这一步就是Dash的价值所在。Dash在Plotly之上提供了一套回调机制,把前端组件的属性(比如figure的clickData、selectedData、relayoutData)声明为Input,把需要更新的组件属性声明为Output,中间用Python函数连接。理解这一点很重要:凡是需要重新查数据的交互,都要走回调,都有网络往返和计算开销;凡是纯视觉的交互,尽量让plotly.js在前端消化掉。
第二个原理是层级数据模型。良率钻取本质上是一个过滤器状态机加多层视图。我们定义了四层:Lot层(批次级良率趋势)、Wafer层(单片良率与Wafer Map)、Die层(每颗die的Bin编号与坐标)、Bin层(失效模式分布)。每一层都是同一份底层数据在不同粒度上的投影,四层共享同一个过滤器状态:时间范围、产品型号、测试程序版本、机台清单、Bin清单。用户的每一次点击,实际上就是往这个状态里追加一个约束条件,然后所有视图按新状态重绘。
第三个必须提前想清楚的是数据量。一片八英寸晶圆按die size不同,通常有一千到五千颗die;一个批次二十五片;一个月一个产品可能有八十到一百五十个批次。算下来die级明细一个月轻松上千万行。这个量级用pandas全量读进内存不是不行,但每次回调都重新读一次就完全不可接受。所以数据层必须做分层预聚合,把Lot级和Wafer级的汇总提前算好,Die级明细只在用户真正钻到那一层时按最小范围查询。
三、现状分析:多数工厂现在是怎么做的
我走访过的几家中小Fab,良率可视化的成熟度大致分三档,各有各的问题。第一档是Excel加条件格式做Wafer Map,用单元格填充颜色模拟die阵列,再配上下拉框做筛选。这种做法的优点是零成本、人人会用,缺点是一旦超过十片晶圆就开始卡顿,而且die坐标一旦和实际排布对不上,很难发现。我见过一个厂用了三年的Excel模板,后来才发现Y轴方向是反的,也就是说他们看到的所有"底部缺陷"实际上都在顶部。
第二档是采购商用的良率管理系统。功能确实完整,从数据采集、Wafer Map、Overlay叠图、Commonality分析到统计报表一应俱全,大厂基本都在用。问题有三个:一是价格,一套下来加上年维护费不是小数目;二是定制困难,想加一个我们厂特有的分组维度,要提需求走原厂排期,周期常常按季度算;三是数据要先按它的模型导入,我们厂有些自定义的测试项和履历字段塞不进去,只能放弃。
第三档是Python静态出图,也就是我们改造前的状态。matplotlib的表达能力其实很强,Wafer Map、Pareto、箱线图、趋势图都能画得很专业,批量出图脚本一跑几百张。问题就是不能交互,看到一个可疑现象,只能记下来,回去改脚本参数重跑。这一档的另一个隐性成本是脚本的碎片化,每个人都有自己的一套脚本,参数写死在代码里,换个人根本跑不起来。
我们最后选的路线是自建轻量交互看板。定位很明确:不追求商用系统的全功能,只解决从趋势到根因这条最高频的分析路径;开发周期控制在两周以内;部署在内网一台普通虚机上;数据源直接对接现有的测试数据库和MES履历表,不做数据搬家。事后回看,这个定位是这个项目能成的关键,如果一开始就想做成"我们自己的YMS",多半会烂尾。
四、瓶颈问题:卡在哪里
第一个卡点是数据量导致的首屏加载慢。最初的版本很naive,应用启动时把最近三个月的die级数据全读进一个全局DataFrame,想着后面查询都走内存会很快。结果是应用启动要四分多钟,占用内存接近十一个G,一台八G内存的虚机根本跑不起来。更麻烦的是数据会更新,全局DataFrame变成了一份随时过期的快照,工程师看到的良率数字和数据库里对不上,信任度直接崩塌。
第二个卡点是回调延迟。改成按需查询之后内存问题解决了,但每次点击都要走一次数据库查询加计算,实测Wafer Map的刷新要三到八秒。这个数字听起来还行,实际用起来非常难受,因为钻取是一个连续动作,用户可能三十秒内点十次,累积等待就是一分钟以上。我们做过一次可用性观察,超过两秒的响应,工程师就会开始怀疑是不是自己点错了,然后再点一次,反而触发更多重复查询。
第三个卡点是回调风暴。我们有六个筛选控件加四张联动图表,最初的写法是每个图表分别声明六个Input。这带来两个后果:一是任何一个筛选变化会同时触发四个回调,四次重复的数据库查询;二是出现了链式触发,A图的点击更新了B图的figure,B图的figure变化又触发了C图的回调,一次点击最多引发过十一次回调执行。日志里满屏都是重复查询,服务器CPU直接打满。
第四个卡点很隐蔽,是坐标系不一致。我们的CP测试数据来自两台不同厂商的探针台,一台导出的die坐标原点在左上角、Y轴向下,另一台原点在中心、Y轴向上,而且notch方向的定义也不同。刚上线时,同一个产品在两台机台上测的Wafer Map看起来是镜像的,工程师以为是工艺问题,追了两天才发现是坐标转换的锅。这个坑我认为是所有做Wafer Map的人都会踩一次的,建议提前把坐标标准化做进数据接入层。
五、解决方案:可落地的完整做法

数据层的方案是三层物化加一层按需。我们用DuckDB作为分析引擎,原始测试数据按天和产品分区落成Parquet文件。在此之上物化两张汇总表:lot_summary存批次级的良率、片数、测试时间、主要Bin占比;wafer_summary存单片级的良率、Bin分布向量、加工履历关键字段(前道CMP机台、光刻机台、注入机台)。这两张表的数据量分别是万级和十万级,全量读进内存也就几十兆。Die级明细不做物化,用户钻到Wafer Map时才按lot_id和wafer_id去Parquet里查,单次查询命中的分区很小,DuckDB实测在两百毫秒以内返回。
缓存这一层用flask-caching配Redis。关键设计是缓存键的构造:把过滤器状态序列化成一个规范化的字典,排序后做MD5,作为缓存键。这样只要过滤条件相同,无论是哪个用户、从哪个入口点进来,都命中同一份缓存。缓存过期时间设为十五分钟,同时提供一个手动刷新按钮,因为测试数据是批次完成后一次性入库的,十五分钟的延迟工程师完全可以接受。上线后缓存命中率稳定在百分之七十以上。
回调架构的重构是收益最大的一步。核心做法是引入一个dcc.Store组件作为唯一的状态容器。所有筛选控件的Input只指向一个"状态更新回调",这个回调负责把用户操作合并进过滤器状态,写回Store;所有图表的回调只监听Store这一个Input。这样六控件四图表的组合,从二十四条依赖边简化成了六加四条,链式触发也彻底消失。同时用callback_context判断触发源,区分是筛选变化还是钻取点击,走不同的数据路径。
渲染选型上有几条经验值得记下来。Wafer Map不要用Scatter画每颗die,一片三千颗die画出来的SVG节点数就够浏览器卡半天,应该用go.Heatmap,把die阵列组织成二维数组,缺失位置填NaN,配一个离散colorscale映射Bin编号,实测渲染时间从一点八秒降到零点一二秒。如果确实需要点级别的hover信息,用go.Scattergl走WebGL渲染,五万点以内都很流畅。Bin Pareto用go.Bar,趋势图数据点超过一万也换Scattergl。
坐标标准化建了一张设备坐标映射表,字段包括探针台编号、原点位置、X轴方向、Y轴方向、notch定义、die pitch。数据接入时统一转换成内部标准坐标系:原点在wafer中心,X轴向右,Y轴向上,notch朝下。转换代码只有二十来行,但省掉的排查时间难以估量。我们还在Wafer Map上固定画一个notch标记,任何时候看图都能立刻确认方向对不对。
部署方案是gunicorn起四个worker,前面挂nginx做反向代理和静态资源缓存。这里有个细节:Dash默认在多worker下会有状态不一致问题,因为它把一部分状态放在服务端。解决办法是把所有状态都放进客户端的dcc.Store,服务端保持完全无状态,这样多worker横向扩展就没有障碍。
六、实战案例:一个完整的改造过程
今年三月,一个零点一一微米的成熟制程产品出现良率下滑,从常规的百分之九十三点五掉到百分之八十九点二,连续三周没有恢复。这个产品月产量不小,零点几个百分点的良率就是可观的金额,所以优先级被提到最高。以前遇到这种情况,我们的标准动作是组一个跨部门小组,每天开会,每个部门各自跑数据,通常两到三周才能收敛。这次我们决定完全用新看板走一遍。
第一步是趋势层。在Lot趋势图上框选异常的三周,看板自动把过滤器状态更新为这个时间段。同时右侧的Bin Pareto实时刷新,显示Bin7(Open失效)的占比从基线的百分之一点八涨到百分之五点四,是所有Bin里增长最显著的。这一步花了不到一分钟。
第二步是空间层。点击Pareto上的Bin7柱子,触发钻取,Wafer Map区域切换成"仅显示Bin7"模式,同时叠加了这三周所有片子的Bin7命中频次热图。图上非常清楚:Bin7集中分布在半径六十毫米以外的边缘区域,形成一个明显的环状带,中心区域基本干净。这个空间特征立刻把嫌疑范围从"测试问题"缩小到"边缘工艺均匀性问题"。
第三步是履历层。看板的Commonality面板按加工履历字段做分组统计,把这三周的低良片子按每个工序的机台号拆开,计算各组的平均良率和样本量。结果很明确:CMP工序的三号机台,加工过的片子平均良率是百分之八十六点一,而另外两台分别是百分之九十三点二和百分之九十二点八,而且三号机台的样本量有四十七片,统计上不是偶然。这一步从点击到出结论,二十秒。
第四步是验证。我们把CMP三号机台的加工时间和良率做散点,发现良率是随时间缓慢下降的,不是突变。设备工程师据此去查保养记录,发现这台机台的retaining ring已经用到接近寿命上限,边缘压力分布开始漂移。更换之后跟踪两周,该机台加工的片子良率回到百分之九十二点九。整个从发现问题到锁定根因,看板上的操作时间累计不到十二分钟,加上设备侧确认,两天内闭环。
七、实施效果:数据说话
最直观的指标是单次分析耗时。改造前从提出问题到拿到可以下结论的图表,平均四点二小时(统计了改造前三个月的二十六次分析记录);改造后平均零点三五小时,其中大部分时间还是花在人的思考上,工具本身的等待时间累计不到两分钟。这个数字对外汇报时我一直强调一点:省下来的不只是四小时工时,而是让分析可以在一次会议内完成,这个价值远大于工时本身。
第二个指标是异常定位数量。改造前团队平均每月能明确定位并落实改善的良率异常是三起,改造后半年的平均值是十一起。增长的原因不是工程师变强了,而是那些以前"觉得可疑但不值得花四小时去验证"的现象,现在花三分钟就能验证,于是大量的小异常被捞了出来。这些小异常单个影响都不大,但累积起来,产品线整体良率在半年内提升了零点八个百分点。
第三个指标是使用率。看板面向十八个工程师开放,上线第一个月日活是七人,第三个月稳定在十二到十四人。有意思的是使用最频繁的不是数据分析岗,而是设备工程师,他们养成了每天早上先看一眼自己负责机台的良率分布的习惯。这是我们没预料到的收益:可视化工具降低了跨岗位的数据门槛,设备工程师原来是不看良率数据的,因为SQL对他们门槛太高。
成本方面,开发投入是两个人两周,约一百六十工时;硬件是一台八核十六G的内网虚机,没有额外采购;软件全部开源,零License费用。后续维护每月约八工时,主要是新产品接入和字段变更。和商用系统的报价相比,这个投入在第一个月就回本了。当然要说明的是,我们的功能覆盖大概只有商用系统的百分之二十,但恰好是使用频率最高的那百分之二十。
最后说两条教训。第一条是不要在项目初期就追求功能完整,我们第一版只做了趋势图和Wafer Map两个视图,先让工程师用起来,根据真实反馈决定下一个做什么,结果发现Commonality面板的优先级远高于我们原本计划的Overlay叠图。第二条是数据质量的投入要提前,我们在坐标标准化和履历字段清洗上花的时间几乎和写界面一样多,但如果省掉这部分,看板会输出错误结论,那比没有看板更糟糕。
八、常见问题答疑
Q:Plotly和Bokeh、Streamlit相比该怎么选?
A:Streamlit开发最快,适合做一次性的分析脚本可视化,但它的执行模型是每次交互重跑整个脚本,做复杂联动会很吃力,状态管理也比较别扭。Bokeh的服务端模型和Dash类似,性能不错,但生态和文档丰富度不如Plotly。Dash的优势在于回调机制清晰、组件生态成熟、可以直接用HTML/CSS做布局,适合做需要长期维护、多人使用的正式工具。如果只是自己看,用plotly.express导出HTML就够了。
Q:Wafer Map用Heatmap画,die形状是方的,能不能画成实际的圆形排布?
A:go.Heatmap本质是矩形网格,die本身就是矩形,所以die形状没问题。晶圆的圆形边界通过把圆外的网格填NaN来实现,NaN在Heatmap里不渲染,视觉上就是圆形。如果需要更精确的边缘轮廓,可以在layout里加一个shape类型为circle的圆形描边,半径设成wafer半径对应的坐标值。
Q:数据量到什么规模就必须上DuckDB,pandas不够用吗?

A:经验分界线大概在单次查询涉及五百万行左右。低于这个量,pandas配合合理的索引和预筛选完全够用;高于这个量,pandas的内存占用和groupby性能开始成为瓶颈。DuckDB的优势是列式存储加向量化执行,直接查Parquet不需要先加载,而且SQL语法对团队来说学习成本更低。我们的实测是同样一个按机台分组的聚合,两千万行数据pandas要十八秒,DuckDB是一点一秒。
Q:多个用户同时用,会不会互相影响筛选状态?
A:只要所有状态都存在客户端的dcc.Store里,就不会。dcc.Store的数据存在浏览器的内存或localStorage中,是每个会话独立的。要特别避免的是用Python的全局变量存状态,那在多用户多worker环境下一定会串。我们的服务端代码里有一条硬性规范:除了只读的配置和缓存,不允许存在任何可变的模块级变量。
Q:回调很慢的时候,怎么让用户知道系统在工作而不是卡死了?
A:Dash提供了dcc.Loading组件,包裹住图表容器,回调执行期间会自动显示加载动画,这是最低成本的做法。如果回调确实很长(超过五秒),建议用background callback配合进度条,把长任务丢到Celery或者diskcache后台执行,前端轮询进度。不过更根本的做法还是优化到两秒以内,我们的经验是超过两秒用户体验就明显下滑,无论有没有加载动画。
Q:这套看板怎么和现有的MES、YMS共存,会不会重复建设?
A:我们的定位是"分析入口"而不是"数据主库"。所有数据仍然来自MES和测试数据库,看板不存储任何原始数据,只做查询和可视化,关键结论仍然回写到正式系统里走流程。这样定位之后,IT部门反而很支持,因为它减少了工程师直接连生产库跑重查询的行为。汇报时我一直用一个说法:这不是新系统,这是现有数据的一个视图。
十、配图:数据可视化
图1:交互式良率看板的系统数据链路
图2:看板上线前后各工序问题定位效率对比
十一、Plotly交互事件与Dash回调设计对照表
十二、性能优化措施与实测效果对照表
十三、配套资料与实战工具
本文配套完整实战工具包,包含文中涉及的测算模板、参数配置表、排查清单与Python脚本,可直接用于工厂落地实施。
点击上方「VIP资源」下载区,免费获取以下五项配套资料(持续更新中):
Plotly交互式良率钻取看板源码包(含Wafer Map、Pareto联动与Dash部署脚本)
预测性维护窗口推荐模型配置模板(含特征清单、RUL标注规范与阈值参数表)
过程能力研究报告(PCS)可过审模板(含正态性检验、分组策略与审核意见对照表)
Test Program调试排查Checklist与ATE相关性分析脚本(含Guardband测算表)
刻蚀PM后首件恢复SOP与数据湖分层设计说明书(含Season片判定与元数据字典)
────────────────────────────────────────
本文首发于博客:半导体智能制造 | MES工程师实战笔记
你遇到过类似的问题吗?是怎么解决的?欢迎在评论区分享你的实战经验,一起交流进步。
标签:数据工具 | Plotly | Dash | 良率分析 | 交互可视化 | WaferMap | 半导体




