06 - 画:图像视频怎么被生成
你的语音合成服务跑得好好的:并发 16,吞吐稳定,延迟也能接受。
产品说想换个音质更好的模型。你换了。同样的卡、同样的并发,吞吐掉了一半多。
你按老经验排查:
- 加大批处理?完全没用,吞吐一点没动。
- 看显存?一动不动 —— 不像以前那样随着生成越用越多。
- 踢一个请求出去腾地方?踢了等于白算,它没有中间状态可以留。
三条经验全部失灵。原因是新模型的第二段不是「一次算完」的声码器,而是一类要反复算几十轮的模型 —— 它叫扩散(diffusion)。
而你那套 调度经验,是照着自回归攒出来的。
- 扩散在干什么,为什么值得多算三十遍?
- 你的哪些经验会在它面前失效?
第 2 个才是这一篇存在的理由。你不做图像生成也躲不开它 —— 语音合成、音乐生成的后半段现在大量用扩散。
一、它在干什么
先看它到底多算了什么。
一个普通的声码器:码本进去,波形出来,一次前向搞定。
一个扩散模型:先凭空生成一片纯随机噪声(播出来就是沙沙声),然后拿一个网络反复处理这片噪声,处理二三十轮,每一轮都跑一遍完整的网络,最后才变成能听的声音。
第一反应肯定是:一次算完的事,为什么要算三十次?
答案在于这样更好学。
「从一片噪声直接跳到成品」是个极难的映射,模型学不好。但「把一个有点糊的东西变清楚一点」是个简单任务 —— 扩散把一个难题拆成了三十个易题。
代价就写在推理账单上:拆成三十个易题,推理时就要跑三 十遍网络。
它跟自回归的根本差别在这里:自回归是「一次定一小块,能看见前面定好的全部」,按位置或时间排序;扩散是所有位置一起从糊到清楚,按清晰度排序。文字和音频有天然的先后,适合自回归;一张图的左上角和右下角谁在前?没有答案,所以图像归扩散。
二、成本从哪来:步数
既然要跑 N 遍,N 是多少就直接等于成本。这十年扩散领域的进展,几乎全在回答同一个问题:能不能少跑几步而不掉质量。
标题里的流匹配(flow matching)就是这条线上的一步。
它的想法可以这样理解:从噪声到成品是一条路径。早期方法学的是「这一小步该往哪挪」,路径弯弯绕绕,所以只能小步慢走;流匹配训练时就把这条路径学成近似直线。路越直,一步能跨得越远,需要的步数就越少。
很多模型会开一个叫 CFG 的东西(无分类器引导),做法是每一步都算两遍再外推,让结果更贴合提示词。
所以「30 步」的真实网络前向次数是 60 次。看到「20 步出图」的宣传时,先问一句有没有算 CFG。
顺带说清楚两个会反复出现的词:
- 潜空间(latent space):扩散一般不直接在像素上做,太贵。先用一个压缩网络把 1024×1024 的图缩成 128×128 的特征,去噪全在这个小尺寸上做,最后再解回像素。这个压缩后的空间就是潜空间。
- DiT(Diffusion Transformer):早期扩散的骨干是卷积网络 U-Net,DiT 把它换成了 Transformer——做法跟 02 篇的 ViT 一样,把潜空间切成小块,每块当一个 token。
DiT 这个替换对服务的意义有两条,下一节要用到:没有 KV cache 可复用(注意力是全局双向的,每步都要重算全部 token),以及序列可能极长(一段 5 秒 720p 视频的潜空间可以到数万块)。
三、你的哪些经验会失效
这是这一篇真正要交付的东西。开场那三条失灵的经验,在下表里都能找到出处:
| 你习惯的(自回归) | 扩散 这边 | 后果 |
|---|---|---|
| 步数不定,模型自己决定何时停 | 步数写死在配置里,请求进来就知道要跑多少 | 延迟反而好预测了,这条是好事 |
| 每步计算量很小,瓶颈在指令发得太慢 | 每步都要算整张潜表示 | 优化方向完全相反:那边压启动开销,这边压计算量 |
| 显存随生成不断增长 | 恒定 | 容量好规划,但你那套「留抢占余量」的算法用不上 |
| 前缀相同就能跳过重算,收益巨大 | 没有前缀这个概念,两个提示词毫无共享 | 前缀缓存那套完全失效 |
| 批可以随时进出,谁完事谁走 | 同进同出,一批开跑就锁死 | 加大批处理不但没用,还会让尾延迟翻倍 |
| 显存不够就把请求踢出去,之后重算 | 踢出去等于全部白算 | 抢占这条路没了 |
开场那三条对应的是第 5、3、6 行。
「同进同出」那一行最要命。SGLang 的扩散服务因此把动态批处理做成了默认关闭的开关,而且要求「同模型、同采样形状」才能合批。延迟敏感或者流量形状很杂的场景,官方建议就是别开。
四、它在语音链路里长在哪
你不做图像也会撞上它,因为语音这边现在是两段分工:内容和韵律交给自回归,声音的质感细节交给扩散。
五、加速手段全是「赌」
自回归那边的前缀缓存是精确复用 —— 命中之后结果逐位相同,纯赚。扩散这边没有这种好事,它的加速手段全是跳过计算:
| 手段 | 跳什么 | 靠什么判断能跳 |
|---|---|---|
| Cache-DiT | 跳 Transformer 里的某些块 | 动态判断,要按模型调参 |
| TeaCache | 跳 整步 | 相邻两步的特征差异小于阈值就复用上一步 |
| Spectrum | 跳选定的步 | 预测下一步的特征 |
赌「这一步算了也跟没算差不多」。赌对了省时间,赌错了掉质量。
所以接受扩散加速就是接受输出会变。验收只能靠人听人看,不能拿 04 篇那套「逐位一致」去卡。
六、顺带一提:扩散思路进了语言模型
还有一条更新的路:把扩散的思路搬到离散 token 上,一次确定多个位置,而不是一步一个。sglang-omni 里的 LLaDA2.0-Uni 就是这类。

docs/_static/image/llada2.0_uni_architecture.png。它把理解与生成统一成掩码 token 预测:图像走 SigLIP-VQ 变成离散语义 token,输出既能解回文本,也能过扩散解码器还原成图像。对推理框架来说,它的意义是又一次打破了「一步一个 token」的假设 —— 调度器的组批、停止条件、缓存复用逻辑都得另做一套。SGLang-Omni 目前对它的处理很保守:默认关掉 CUDA Graph,明确标为实验路径。新范式接入的常规姿势就是这样,先跑通,再谈优化。
下一篇:07 - 三种计算为什么合不来。听、想、说三段凑在一起,调度器该听谁的。