技术2026 · 07 / 3012 分钟♡ 178评论 34

从零搭一条多模态内容流水线:把七个模型串成一条产线

语音、口型、动画、成片——如何把一堆各自为战的模型,编排成一条能批量出片的流水线。含调度、显存、失败恢复的完整设计。

这块卡,一年跑了四万多分钟

先说清楚这条线要解决什么问题:输入一份结构化的脚本,输出一条成片,中间不需要人。

听起来简单,实际做起来,难点几乎全部不在模型本身,而在模型之间。这篇把我这半年趟出来的编排结构完整写一遍。

一、先画出真实的依赖图,别画成一条直线

我最开始把它想成一条直线:脚本 → 语音 → 口型 → 画面 → 合成。

真跑起来才发现,它其实是个有向无环图,而且里面有大量可以并行的部分:

脚本解析 ─┬─→ 语音合成 ──→ 时长对齐 ─┐
          │                          ├─→ 口型驱动 ─┐
          ├─→ 分镜拆解 ──→ 画面生成 ─┘             ├─→ 合成 → 成片
          │                                        │
          └─→ 字幕排版 ──────────────────────────┘

关键在于时长对齐这个节点:画面要多长、字幕怎么断,全都取决于语音实际生成出来有多长。你没法在语音出来之前就把画面的时长定死——但你可以先把画面生成了,只是不定时长。

把这一层看清楚之后,整条线的墙钟时间少了将近一半,因为画面生成和语音合成是完全并行的,它们只在时长对齐这个点上汇合。

教训:先画依赖图,再写调度。我第一版是照着「人做这件事的顺序」写的,那个顺序里有大量的假串行。

二、调度:不要用「阶段屏障」

第一版我写的是分阶段执行:所有任务先做完第一步,再一起做第二步。

这个写法的问题是最慢的那个拖死所有人。第二步要等第一步全部完成,而第一步里只要有一个任务卡住,其余任务就都在等它。

后来改成了流水线式:每个任务独立地走完自己的全部阶段,互不等待。任务 A 已经在第三步的时候,任务 B 可能还在第一步。

阶段屏障:墙钟 = Σ(每阶段最慢的那个)
流水线:  墙钟 = max(单个任务的完整链路)

什么时候才真的需要屏障?只有当下一阶段需要跨任务的全局信息时。比如「所有分镜都生成完之后,统一做一次风格一致性校正」——这个确实需要全部到齐。除此之外,一律流水线。

三、显存:一台机器上跑不下七个模型

这是最现实的约束。单卡 32G,七个模型全常驻是不可能的。

我试过三种策略:

策略一:全部按需加载,用完卸载。显存永远够用,但每个任务要付好几次模型加载的时间,实测占了总耗时的三分之一以上。不划算。

策略二:全部常驻,用量化压缩。压到能塞下,但几个关键环节的质量掉得肉眼可见。放弃。

策略三(最终方案):按调用频率分层。

关键是第三层的判断:不是所有环节都值得放在本地。我一开始有种「都在本地跑才叫自建」的执念,后来算了笔账——某个环节一天只调用几十次,为它常驻两个 G 的显存,不如让它走远端,省下的显存给主力环节提高吞吐。

分层之后,整机吞吐涨了约六成,而模型一个都没换。

四、失败恢复:断点要落在阶段边界上

批量跑的时候,中途失败是常态。重跑整条链路的代价太高——尤其当失败发生在最后一步。

我的做法是每个阶段结束就落盘中间产物,并且记一条状态。重跑时从最后一个成功的阶段继续。

这里有个容易写错的地方:中间产物的命名必须是确定性的

我一开始用时间戳 + 随机后缀命名,结果重跑时根本对不上——磁盘上躺着一堆文件,但我不知道哪个属于哪个任务的哪个阶段。等于白存。

现在的规则是:<任务 ID>/<阶段名>.<扩展名>,没有时间戳、没有随机数。同一个任务的同一个阶段,重跑多少次都是同一个路径,直接覆盖。想留历史另开归档目录,不要污染工作路径。

顺带说一个相关的坑:我曾经用一个并发脚本批量生成资产,工具本身用「时间戳 + 哈希」命名输出文件,并发跑完之后,一堆文件躺在同一个目录里,我无法反推哪个文件对应哪个输入。只能全部作废重来。

解法很简单:给每个并发任务一个独立的临时输出目录,任务结束后把目录里唯一的产物改成你要的名字。零竞态,完全确定。

五、参数不要写在代码里

七个模型,每个都有一堆参数,而且各家的参数名和取值范围都不一样。

我现在的做法是每个模型一份配置:能力边界、参数范围、哪些参数会直接报错、默认值。调度层读配置来决定怎么调用,不在代码里写任何一个具体模型的特判。

这样做的直接好处是换模型不用改代码。上个月主力生成模型换了一次,我改的是配置文件,调度逻辑一行没动。

间接好处更大:那份配置文件本身,慢慢变成了我对这批模型的知识库。什么参数组合会崩、什么输入必然失败、哪个模型在什么场景下比别人强——全部写在那儿。半年后回来看,这份文件比我的记忆可靠得多。

六、现在的数字

跑了半年之后的实际情况:

指标第一版现在
端到端成功率~37%94%
单条平均耗时8 分 40 秒2 分 10 秒
需要人工介入约 7 次 / 天约 2 次 / 周
单条成本基准约 0.4×

成功率那一栏的涨幅,八成来自「契约校验前置」和「降级路由」这两件事,跟换模型基本无关。

还没做好的部分

第三条我正在改。做成本优化最怕的就是拍脑袋——没有归因的优化,等于换个地方浪费。


如果你也在搭类似的东西,我的建议是:先花一天把依赖图和契约画清楚,再开始写代码。我在这上面省过时间,后来加倍还了回去。

这篇里的照片Frames全部相册 →
深夜的生产线 · 三块屏都在跑
深夜的生产线 · 三块屏都在跑家 · 书房 · Sony A7C II · 24mm f/4
想不通就摊开纸画,画到第三版才对
想不通就摊开纸画,画到第三版才对家 · 书桌 · Sony A7C II · 50mm 微距
← 更早一篇
为什么我把「等一等」写进了架构

关注公众号「十年追随」

新文章第一时间推给你。留言我都会回,公众号里也能收到回复通知。 不发广告,一周最多两条。

十年追随 公众号二维码
评论Comments34 条 · 匿名也能评,昵称头像会记住
评论服务(Waline)待部署 —— 香港 VPS 起来后填 SERVICES.waline 即生效。
想说话可以先去 留言板,那边我每条都回。
关注公众号「十年追随」更新第一时间推给你 · 留言也能收到我的回复
看二维码