5 月 13 日夜里,我新建了一份项目计划文档,按老习惯把想做的事分成三栏:基础目标、核心目标、演示目标,末尾还特意划出一块“这一期先不折腾的”。那时候脑子里的画面很简单:输入一段描述,三维模型生成出来,摆到网页上能拿鼠标转着看。接口一串通,这事就算成了大半。
等到六月中旬真扎进代码里日夜联调(一路改到 6 月 14 日的 4ce0bf1d 那次提交),我才发觉力气全花在了当初计划书里几乎没提过的地方。显卡算力娇贵,远端队列漫长,网络轻轻抖一下,几十分钟的等待就打了水漂。那一个月,我的心思整个挪到了另一件事上:怎么让人在开跑前先看一眼参考图,怎么把悬在半空的任务状态落到硬盘里,怎么卡住单机资源的闸门,以及万一生成在半夜断了线,第二天早上起来还能从哪儿把活儿接下去。

可恢复的 3D 生成链路:先确认参考图并保存任务状态,再按远端记录和资源条件选择续接、重提或轻量着色。
图:整理自提交 4ce0bf1d 的任务恢复流程。“复用参考图”与“轻量着色”分别由不同的中断条件触发,彼此独立。
开跑前,先看一眼参考图
如果要给这个工具写一句最短的介绍,大可以说“输入一段文字,得到一个 3D 模型”。可真把底下的执行链路拆开,文字和三维网格之间还隔着一张绕不开的参考图。刚开始调的时候,我图省事让文生图的结果直接往建模队列里冲,好几次等了半天,端出来的网格缺胳膊少腿。回头一查原图,才发现生成的图片本身就把主体切掉了一角(对着那个畸形的白模,我真是哭笑不得)。
后来我干脆在界面上加了一道“待确认”的关卡。不管是调用接口刚生成的图,还是从本地传上来的图,都不会直接塞进建模队列,得先让人眼看清几件事:画面里是不是只有一个干净的主体,结构剖面清不清楚,四周有没有留出足够的空白,朝向适不适合拿去建模。亲手点过“接收图片”之后,下一步确认建模的按钮才会真正亮起来;而在后端接口那头,任何没带有效 referenceId 的自部署任务都会被直接挡回去。
这一步让原本一气呵成的操作多了一次停顿,却把成本最低的人眼判断,挪到了最吃算力的重任务前面。在动手建模前花三秒钟退回去重选一张好图,总比等显卡吭哧吭哧算完半天、到头来对着一个残缺模型叹气要痛快得多。
更实在的是,这张被点头确认过的图片,后面会成为恢复任务时的锚点。系统把确认后的参考图单独存进一个独立目录,把它的 ID 写进任务记录里。万一远端计算在半路断了线,只要这份底稿还安稳躺在磁盘上,后续的恢复路径就能顺藤摸瓜找回当初确认的那张原图,犯不着重新抽盲盒生成新图,糊里糊涂把另一个长相完全不同的东西当成原任务的延续。
任务状态不能只挂在网页上转圈
跑一次完整的三维生成,要经历等待远端服务、排队、跑通工作流再到拉取输出文件好几个阶段,哪一环打个嗝都可能中断。要是前端页面上只有一个转圈的加载图标,手滑刷新一下,连刚才那次点击到底有没有发出去都成了糊涂账。
为了不再对着转圈图标瞎猜,我把任务的当前快照实时写进本地的 JSON 文件,同时把每一次状态跳变逐行追加到一份 JSONL 日志里。一条任务除了挂着 queued、processing、completed 或 failed 这几个大状态,还会记下它停在哪个具体阶段、走到了百分之多少。这相当于给系统准备了两本账:JSON 快照回答“它这一秒停在哪儿”,JSONL 里的事件轨迹回答“它是怎么一步步挪到这儿的”。同样是在界面上挂出一个红色的 failed,背后可能是请求压根没出本机网卡,也可能是远端早就算完了,偏偏在收尾拉取结果文件时断了网。这两种死法的善后天差地别,不把过程记下来,根本无从下手。
吃资源的重任务,还得先穿过一层单机保护队列。默认配置下它只老老实实串行跑,等候区里最多排一个(队列深度留了环境变量可以改)。只要发现远端 ComfyUI 正忙,或者干脆探不到远端队列的状态,新提交就会被当场拦在门外,免得一时手快点多了,把自己机器的显存撑爆。
靠本地账本做的恢复,我也卡了明确的时间窗口。后端服务重启时,自动恢复默认只捞最近 3 小时内有过更新的 queued 或 processing 任务;自部署(self-host)任务要手动续接,不光要核验记录里有没有合法的 providerJobId 等关键字段,默认也只认最近 24 小时内的旧账。超出窗口的文件就算还完好地躺在硬盘里,也不会再被放进恢复通道。在单机跑服务的日子里,靠本地文件撑起这么一套窗口,既轻量,也够用。
prompt_id 是办事窗口给的回执单
任务真正送进 ComfyUI 后,远端接口会先吐回一个 prompt_id。在代码里,我没有急着马上去轮询远端的 history,而是先把这个 ID 稳稳当当地写进本地任务记录的 providerJobId 字段。这个先后顺序非常要紧:万一服务在轮询等待的那十几秒里突然重启,醒来翻一下本地记录,就能立刻认出上一轮计算在远端对应的是哪一号牌子。
走进续接路径后,系统先翻本地缓存的 history;没找着,再拿当前的、上一轮留下的、或刚恢复出来的 prompt ID 去问远端要 history 和生成好的 GLB 模型。只要远端那边还留着现成的可用输出,任务就顺着原来的输入把收尾做完,整条路径干干净净,绝不会多事去重新生成参考图。
说白了,prompt_id 就像去办事窗口交材料时拿到的那张纸质回执。远端 ComfyUI 一重启,history 清空,这张纸自然作废;可只要那边的账还在,这张小小的回执就能把本地挂着的任务和远端跑过的算力严丝合缝对上号,让程序心里有数:是直接去窗口把做好的东西领回来,还是得重新排一次队。
远端记录丢了,最多重提一回
最尴尬的情况是:本地还没来得及把输出存下来,再去问 ComfyUI,那头的 history 已经空空如也。这时候死心眼地拿旧 ID 轮询,等三天三夜也等不来 GLB;可要是放任系统自动重新提交,一旦碰上必现的坏图或环境故障,后台就会悄无声息地陷进死循环,把显卡烧得嗡嗡直响。
所以写“重提”这条分支时,我一连卡了几个硬前提:任务手里必须还攥着那张确认过的参考图;当前后端得是自部署的 TripoSG;这个任务之前从没试过“从参考图重提”;而且报错原因确实落在 history 缺失、远端队列为空、找不到 GLB 产物这几类里。几把锁全对上了,系统才获准拿那张参考图再敲一次门,而且只许这一回。
真正发请求之前,代码会先把旧的 providerJobId 备好份,把 restartFromReferenceAttempted 标记为已尝试并落盘,然后才去调用远端。ComfyUI 明确收下请求、吐回新的 prompt_id 之后,主记录里的 ID 才会被替换。请求半路折了,账本上留着的还是旧 ID、当时的报错和卡住的阶段;拿到新 ID 后又在后半程栽了跟头,一旧一新两次提交的痕迹都能在日志里翻出来对账。坚持“先落盘、再碰重任务”,给自动重试戴上了紧箍咒,也没把上一次失败的现场脚印抹掉。
拆到这一步,平时随口说的“让任务继续”,在代码里其实劈成了三条路。“续接”是拿着回执去领原先已经算出来的结果;“重提”是攥着同一张确认过的参考图,重新发起一轮计算;“降级”是在已经到手的稳定 GLB 白模上,退一步做一层轻量的本地处理。三条路花的算力和端出来的东西截然不同,事件日志把它们分得清清楚楚,翻记录时一眼就知道模型是怎么来的。
显存吃紧时,先保住手头的白模
到了贴图这一步,又是一道容易翻车的坎:远端接口眼下能连通,可显存不一定够跑完精细贴图。好几次眼睁睁看着几何白模都算出来了,偏偏在收尾贴图时爆了显存,前功尽弃。
后来我让工作流在推重任务之前先摸一遍底:服务状态、剩余内存、可用显存、远端队列长度挨个查。发现显存或内存吃紧,先请 ComfyUI 清一清缓存,喘口气再复查一遍;清完还是悬,就干脆刹车,不再硬往 Hunyuan3D-Paint 里挤。
这时如果前面已经顺利产出了稳定的 GLB 几何模型,fallback-color 分支就接过接力棒:直接复用现成的几何体和当初确认的参考图,在本地写入轻量纹理、基础材质或顶点颜色,处理完再校验一遍 GLB 文件的完整性。这条退路的目的很单纯,先把千辛万苦算出来的形状和前端展示链路保住;至于那次没能跑通的重贴图,日志里照样老老实实记作失败。
于是最后能摆进三维查看器的结果分出了三种成色:走完原生贴图工作流的完整模型,fallback-color 兜底出来的轻量着色模型,以及只有几何结构、看着还是素白的结果。它们都能在同一个窗口里拖着转,页面上也会标明各自的来路,不拿轻量上色的去冒充精修贴图。
留在硬盘里的脚印
8 月 9 日那个周末,我把 4ce0bf1d 检出来,重跑了一轮本地验证:后端 109 项 API 测试齐刷刷飘绿,前端生产构建也顺顺当当打完了包。看着终端里一排排绿勾,心里挺踏实——状态流转、恢复窗口、资源门禁和 GLB 降级这些逻辑都咬合得好好的。那天手边没连远端显卡,live smoke 没跑,留到下回接上机器再补;不过本地这套调度骨架,已经能稳稳兜住各种意外分支了。
把五月那份有点天真的计划书和六月的代码库叠在一起看,挺让人感慨。列提纲时,我想的是怎么把输入、生成、缓存、展示串成一条漂亮的直线;真在代码里摸爬滚打完,留下的却是一连串应付意外的小预案:参考图在哪一步必须等人点头,长任务怎么在磁盘上留好快照和流水账,进程重启后哪些任务能进默认 3 小时的自动恢复窗口,self-host 任务怎么在默认 24 小时内顺着 prompt_id 找回旧账,远端 history 都没影了时满足什么条件才许重提那唯一一次,显存见底时怎么退一步保住手里的白模。
磁盘上最后那个 .glb 文件,只是某一次运气不错跑通了的结果。围着它存下来的输入原图、远端回执、中断时停在的阶段、报错的缘由,还有系统判断该续接、重提还是降级的那套尺子,才让这个小工具真正立了起来。这些狼狈的脚印留得够清楚,半夜意外中断的任务,第二天早上掀开屏幕时,就不至于又变成一桩让人挠破头的悬案。