当 Stable Diffusion 还在跑 50 步采样时,ZipVoice 已经做到了 4 步出语音。这背后的算法演进,比看上去要直观得多。

引言

熟悉文生图的开发者都知道一个事实:生成一张高质量图片需要几十次去噪迭代。从 DDPM 论文里的 1000 步,到后来 DDIM 的 50 步、100 步,再到 LCM、Turbo 模型的 1-4 步,扩散模型的"提速"一直是研究热点。

但当读者第一次接触 ZipVoice 的源码时,一个数字会让人眼前一亮:

num_steps = 4

不是优化版、不是蒸馏版、不是某种特殊配置——这就是论文推荐的默认值。一个 Flow Matching 架构的 TTS 模型,4 步迭代生成 8 秒语音。

读者难免会问:

  • Flow Matching 和扩散模型是什么关系?
  • 为什么只要 4 步?多步质量不会更好吗?
  • numSteps 这个参数到底改变了什么?为什么改成 2 质量就变差?
  • 训练时是怎么学会"4 步等效原本几十步"的?

本文尝试用尽可能少的数学语言,把这些问题讲清楚。需要读者对扩散模型有基础印象——知道它是"加噪-去噪"的过程就够了。


一、回顾:扩散模型在做什么

扩散模型的核心思想,用一句话概括:把"无中生有"的难题,变成"逐步修正"的简单题

具体来说,分两个阶段:

前向过程(训练用):拿一张真实图片 x₀,逐步往里加高斯噪声,加 1000 步后图片完全变成纯噪声 x_T。

x₀ (真实图片) → x₁ → x₂ → ... → x_T (纯噪声)
        加噪    加噪          加噪

反向过程(推理用):从纯噪声 x_T 出发,逐步去噪,最终还原一张图片。每一步都让网络预测"这一步应该减掉多少噪声"。

x_T (纯噪声) → x_{T-1} → ... → x₁ → x₀ (生成图)
        去噪         去噪    去噪

这里的关键认知是:训练时网络学的不是"如何画一张图",而是"在加噪过程中的任意位置,原始图片是什么样"。推理时,沿着反向链一步步问网络"再去一点点噪声后应该是什么样",最终就能从噪声还原出图。

为什么需要这么多步?因为每一步去掉的噪声很少,模型预测的目标比较接近输入,难度低,预测准。如果一步去太多噪声,相当于让模型从"几乎全是噪声"直接预测"几乎全是图片",难度太大,预测会偏。

所以步数越多,每步的目标越温和,质量越好;但步数越多,推理越慢。这是扩散模型的核心权衡。


二、扩散模型的几何视角:在概率空间中走路

把上面的过程换一个数学语言。

数据空间(图片空间)里有两个分布:

  • 真实数据分布 p_data:所有可能的真实图片在这个分布里
  • 噪声分布 p_noise:所有可能的纯噪声图(即标准高斯分布)

扩散模型本质上在学习一条从 p_noise 到 p_data 的路径

p_noise                              p_data
  ●────────────────────────────────→●
   x_T  x_{T-1} x_{T-2}    x₁ x₀
        每一步都向 p_data 靠近一点

DDPM 的路径很曲折——每一步是带随机性的小跳跃(SDE,随机微分方程)。DDIM 把它"拉直"了——变成确定性的小跳跃(ODE,常微分方程),同样的起点和路径会得到同样的终点。

DDIM 是扩散模型走向"少步生成"的关键一步。一旦确定了路径是 ODE 而不是 SDE,就可以用更少的采样点近似这条路径。


三、Flow Matching 登场:直接学路径本身

DDIM 已经把扩散过程变成了 ODE,但训练目标依然是"预测加进来的噪声",间接地定义了 ODE 的形状。

Flow Matching(流匹配)做了一个更直接的事情:让模型直接学习这条路径上每一点的"运动方向"

类比一下:

  • 扩散模型说:“这是图片,这是噪声,路径在中间。给我一个中间点,告诉我现在的位置离图片有多远。”
  • Flow Matching 说:“这是图片,这是噪声,路径在中间。给我一个中间点,告诉我此刻应该往哪走。”

数学上,Flow Matching 的训练目标是直接拟合一个速度场 v(x, t):

对路径上每一点 (x, t):
  v(x, t) = 在此点的瞬时速度
  
推理时:
  从 x_0 (噪声) 出发
  按 dx/dt = v(x, t) 求解 ODE
  到 t=1 时得到生成结果

如果路径设计得好(比如 Rectified Flow 用直线连接 p_noise 和 p_data 的对应点),那么这个 ODE 的解可以用很少的步数近似得到。这就是 Flow Matching 能做到少步生成的根源。


四、什么叫"路径设计得好"

一条路径走起来快不快,取决于它有多直。

情况 A:曲折山路           情况 B:直线公路
                                          
    起点                       起点
     ↓                          ↓
     ●                          ●
      ╲                          ╲
       ●─╮                        ╲
          ╲                        ╲
       ╭──●                         ●  终点
      ╱                            
     ●                          
   终点                        

A 这种曲折路径必须用很多小步才能跟上每个转弯。B 这种直线路径可以一步从头跨到尾。

DDPM 学到的路径是 A:因为前向过程加了 1000 步小噪声,每步都是小幅度的转向,整条路径弯弯曲曲。

Rectified Flow 学到的路径是 B:训练时直接强制路径是直线(噪声点和图片点用直线连接),所以推理时少几步也能保持精度。

ZipVoice 用的就是 Rectified Flow 思想。它的理论速度场满足:

v(x, t) = (x_target - x_noise)

也就是说,每一点的速度都指向同一个方向。理论上,1 步就能从噪声到达目标:

x_target = x_noise + v × 1.0

但实际中由于:

  • 速度场是用神经网络近似的,存在误差
  • 1 步迈得太大会被误差放大

所以推荐用 4 步,每步走一段,让误差有机会被后面的步骤修正。


五、numSteps 改变了什么

读到这里,应该可以回答 numSteps 这个参数的含义了。

它决定了 ODE 数值积分的步长

# numSteps = 4 时
t_values = [0, 0.143, 0.333, 0.6, 1.0]   # 5 个点
# 步长 = [0.143, 0.190, 0.267, 0.4]
# 4 次前向,每次走一段
# numSteps = 2 时
t_values = [0, 0.333, 1.0]                # 3 个点
# 步长 = [0.333, 0.667]
# 2 次前向,每次走更大一段
# numSteps = 1 时
t_values = [0, 1.0]
# 步长 = [1.0]
# 1 次前向,一步到位

t_values 不是均匀的,因为 ZipVoice 用了一个 t_shift = 0.5 的非线性时间映射:

def get_t(i, num_steps, t_shift=0.5):
    raw_t = i / num_steps
    return t_shift * raw_t / (1.0 + (t_shift - 1.0) * raw_t)

这个映射让前几步在 t 较小处更密集,后几步在 t 较大处稀疏。原因是 Flow Matching 的速度场在不同 t 值处变化幅度不同——靠近噪声端(t=0)变化剧烈,需要更密的采样;靠近目标端(t=1)变化平稳,可以稀疏采样。

实际工程经验

  • numSteps = 1:速度场误差被放大成 1 倍,质量明显下降,几乎不可用
  • numSteps = 2:质量勉强可接受,速度提升一倍
  • numSteps = 4:质量与稳定性的最佳平衡点(论文推荐)
  • numSteps = 8 或更多:边际收益小,速度变慢

笔者实测,从 4 改到 2 后,合成出来的语音字数对得上但音色明显失真,发音不连贯。从 4 改到 1 直接变成不可识别的乱码。这印证了 Flow Matching 不是真的能"任意减步",而是在训练时已经为某个特定步数(通常是 4-8)做了优化。


六、ZipVoice 的"蒸馏"是怎么回事

ZipVoice 的发布版本叫做 zipvoice-distill,distill 就是蒸馏的意思。

完整的训练过程是:

第一阶段:训练标准 Flow Matching 模型。这个模型需要 16-32 步推理,质量好但慢。

第二阶段:蒸馏到 4 步。用第一阶段的模型作为"老师",训练一个新模型作为"学生"。学生的目标是:用 4 步推理产生与老师 16-32 步推理相同的结果。

蒸馏的具体做法(简化描述):

对每个训练样本:
    用老师模型跑 32 步生成 mel_teacher
    用学生模型跑 4 步生成 mel_student
    损失 = ||mel_teacher - mel_student||²
    反向传播更新学生

蒸馏完成后的学生模型,速度场被调整成"4 步走完正好等于老师 32 步的效果"。这就是为什么蒸馏版可以稳定地用 4 步推理。

关键认知:蒸馏后的模型不能直接用于其他步数。如果用 zipvoice-distill 跑 8 步,质量并不会比 4 步更好——因为模型的速度场已经为 4 步做了优化,再多走步数反而可能"过冲"。

读者在调试时如果发现增加 numSteps 没有改善音质,原因就在这里。


七、和图像扩散的对比

读者可能会想,为什么 Stable Diffusion XL Turbo 才把图像生成压到 1-4 步,而 ZipVoice 一开始就用 4 步?

几个原因:

第一,音频比图像简单。一张 1024×1024 图片有 100 万像素,而 8 秒音频的 mel 只有 1136×100 = 11.4 万个值。生成空间小一个数量级,模型容量需求小,Flow Matching 学起来更容易。

第二,TTS 的随机性低。给定文本和参考音频,理想的 mel 输出应该是相对确定的(音色、节奏由输入决定)。而文生图给一个 prompt,可以画出无数张合理的图。低随机性的任务更适合 Flow Matching 这种确定性 ODE 路径。

第三,TTS 模型小。ZipVoice 的 decoder 只有约 100 MB(FP32),而 SDXL 的 UNet 有 6.5 GB。小模型每步推理快,加上少步数,端到端就能做到接近实时。

但这并不意味着所有音频生成任务都能用 4 步。比如音乐生成、语音变声等更复杂的任务,仍然需要更多步数。Flow Matching 的"少步"优势依赖于:路径设计(直线性)、模型容量(足够拟合)、任务确定性(输入约束充分)。


八、工程上的取舍

理解了 Flow Matching 后,从工程角度看 ZipVoice,可以总结出几个权衡:

速度 vs 质量:4 步是平衡点。少于 4 步质量崩溃,多于 4 步意义不大。

模型大小 vs 推理速度:蒸馏版只有 100MB,全量版可能更大。FP32 vs INT8 也是一个量化精度与速度的权衡。

通用性 vs 专用性:蒸馏版只能 4 步,不能在推理时灵活调步数。如果想自适应(弱网络环境用 2 步、强算力用 8 步),就要保留全量版本,但失去蒸馏带来的速度优势。

Decoder 推理 vs 端到端时间:在中低端 ARM 芯片上,Decoder 4 步推理仍然要 50+ 秒,是端到端瓶颈。哪怕优化了步数,单步时间不降下来也没用。这就是为什么在端侧场景下,硬件加速(FP16、INT8 dot product 指令、专用 NPU)比算法优化更重要。


九、结语

Flow Matching 在 ZipVoice 上的成功表明,扩散类生成模型在算法层面已经接近"实时可用"。从 1000 步到 4 步,量级的飞跃发生在过去三年内,速度仍在加快。

但这套技术能不能在端侧普及,仍然取决于硬件。在最新的旗舰移动芯片(骁龙 8 Gen3、麒麟 9020、苹果 A17 Pro 等)上,Decoder 单步推理已经能压到几秒甚至秒级。配合 4 步的 Flow Matching,端侧 8 秒语音的合成时间可以做到 10-15 秒——已经接近"用户可以容忍的等待"门槛。

下一个突破点可能是:

  1. 进一步蒸馏到 1-2 步:让模型在保证质量的前提下减少步数
  2. 更好的硬件支持:ARM SME2 指令集、各家手机的 NPU 加速
  3. 流式推理:边生成边播放,让用户感知不到等待

现在做端侧 TTS 还需要在产品层面做妥协(比如限制文本长度),但相信再过一两代芯片,端侧实时声音克隆就会变成标配能力。


附:相关阅读

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐