在生成模型和扩散模型的圈子里,如何在不额外训练一个"无条件模型"的前提下拿到类似分类器自由引导(CFG)的采样增强效果,是个一直让人头疼的工程问题。传统 CFG 的思路很直接:你训练一个条件模型,再训练一个无条件的模型,采样时把两者的速度场或分数场做插值。这个方法有效,但训练成本和显存开销几乎是实打实翻倍的。而 MeanFlow 这条技术路线,核心就是用"平均速度场"这个概念做文章,试图把隐式 CFG 变成一个纯数学推导的结果,而不是工程上的凑合。
这篇文章要聊的,就是 MeanFlow 中平均速度与隐式 CFG 的一个理想化推导。我会从流匹配(Flow Matching)的基本形式出发,推一遍平均速度场的构造方式,再解释什么是隐式 CFG、它和显式 CFG 的本质差异,最后给出一套在理想化假设下可以直接落地的采样推导和实操要点。适合手里有扩散模型或流匹配模型基础、正在想办法省掉双模型训练成本的开发者。对纯数学推导不感兴趣、只想要"怎么调参"的读者,也可以直接跳到第四、五节看工程细节。
1. 项目背景与核心问题
1.1 从流匹配到平均速度场
先回顾一下流匹配在做什么。给定数据分布 p₁ 和先验噪声分布 p₀,流匹配希望学一个随时间变化的速度场 v(x, t),用它构造常微分方程(ODE):
dx/dt = v(x, t),x(0) ~ p₀
然后从 t=0 积分到 t=1,把噪声分布逐步形变成数据分布。训练目标是最小化速度场的回归误差:
L = E[ || v_θ(x_t, t) - u(x_t, t) ||² ]
其中 u(x_t, t) 是某个预定义的条件速度场。最常用的构造方式是"插值路径":给定一个数据点 x₁ 和一个噪声点 x₀,定义 x_t = (1-t)x₀ + t·x₁,那么条件速度就是 u(x_t, t) = x₁ - x₀。这个构造看似简单,但它把样本对绑死了,训练时需要每个样本对单独算速度场。
在实现层面,流匹配通常用"条件速度网络"来逼近边际速度场。这里的"边际"很重要:模型最终见到的输入是 x_t,而 x_t 是从所有可能的 x₀、x₁ 组合中采样出来的,所以模型学到的是一个平均意义下的速度场,不是针对某一个具体样本对的。
那和下棋一样,你得先理解"平均速度场"这个词。它指的是:给定当前状态 x 和时间 t,把所有可能经过这个状态的速度取平均,得到的期望速度 E[u(x, t)]。这就是模型要回归的目标。MeanFlow 这个名字,我理解它强调的正是这个"平均"属性——我们把速度场建模当作一个均值回归问题,而不是一对一的映射问题。
1.2 什么是隐式 CFG,为什么需要它
传统 CFG 的采样公式是:
v_guided = v_uncond + w · (v_cond - v_uncond)
这里的 w 是引导权重,w > 1 时生成结果会更"极端"、更符合条件。这个公式要求我们手里有两个模型:一个条件模型 v_cond,一个无条件模型 v_uncond。
问题在于,训练无条件模型的成本不是一个小数目。尤其是当你的骨干网络很大(比如用了 Transformer 或者大规模 UNet),显存和训练时间都要翻倍。于是很多人想绕过这一步,想方设法从条件模型里"抠"出一个近似无条件预测来。
隐式 CFG 的思路正是如此。它的想法是:如果我已经有了一个训练好的条件速度场 v_θ(x, t, c),我能不能从数学上拆出一个"无条件分量"?更进一步,如果模型本身在训练时已经学过数据和噪声之间的平均速度关系,那么无条件信息是不是已经藏在条件速度场的某个统计量里?
这就需要一个理想化假设来做推导。这里的"理想化"指的是:假设条件速度场对条件变量 c 的依赖是可分解的,且我们能够估计出一个与 c 无关的平均速度分量。一旦这个假设成立,隐式 CFG 就是一个纯粹的数学重写,不需要额外训练。
1.3 理想化推导的前提假设
为了把推导做干净,我们需要几个假设:
第一个假设是速度场的条件分解。我们假设学习到的速度场可以写成:
v_θ(x, t, c) = m(x, t) + g(x, t, c)
其中 m(x, t) 是与条件 c 无关的"基础速度场",g(x, t, c) 是条件带来的"引导增量"。
第二个假设是均值回归一致性。我们假设在任意固定 (x, t) 下,条件 c 在训练分布上的期望满足:
E_c[v_θ(x, t, c)] = m(x, t)
这个假设非常关键。它说的是:如果我把所有条件样本的速度场做平均,得到的正是那个与条件无关的基础速度场。换句话说,无条件预测等价于条件预测在条件分布上的期望。
第三个假设是线性可加性,即引导增量 g 对权重 w 的缩放是线性的。
这些假设在实际中当然不会完美成立。但作为理想化推导,它们给出了一条清晰的路:先定义平均速度场,再用它构造隐式 CFG。最后我会讨论违背假设时模型会表现出什么症状。
2. 核心细节解析:平均速度场的构造与含义
2.1 平均速度场的数学形式化
上一节提到,模型学到的边际速度场本质上是一个期望。现在我们把这件事写得更严格一点。
给定条件速度 u(x_t, t, c),数据分布记为 p(x₁ | c),噪声分布为 p(x₀)。构造路径后,边际速度场定义为:
v_marginal(x_t, t) = E_{x₀, x₁ | x_t} [ x₁ - x₀ ]
这里的期望是在所有通过 x_t 点的轨迹上取的。也就是:把所有经过这个点的轨迹的瞬时速度做平均。
这个边际速度场的意义在于,它把"一个轨迹的个体速度"转换成了"一个流场的集体速度"。这正是连续归一化流(Continuous Normalizing Flow)和流匹配之间的关键区别。CNF 需要模拟大量粒子轨迹,而流匹配直接回归这个平均场,训练效率高得多。
用生活里的例子来类比:想象你站在一个繁忙的十字路口,每个人过路口的方向和速度都不一样。你如果统计"当前穿越路口的行人平均速度"和"平均方向",得到的就是一个平均速度场。你不需要知道每个人从哪来、到哪去,只需要知道这个路口当下的整体运动趋势。流匹配模型在做的事情,就是学会预测这个趋势。
2.2 为什么"平均"是一个可用的目标
有人可能会问:平均速度场真的够用吗?不同人的速度方向差那么多,平均之后不是会把细节磨平?
这里的关键在于:流匹配的训练目标不是让模型直接输出某个随机样本的速度,而是让模型输出这个平均速度。因为训练数据里 x_t 的分布是边际分布,模型的预测天然会被"拉向"那个条件期望。这和我们做回归时的均值回归是同一个道理。模型在最小化均方误差时,最优预测就是条件期望 E[y|x],不管 y 本身有多分散。
所以流匹配从原理上就和 GAN 不一样。它不学"生成一个可能的样本",而学"生成所有可能样本的平均流动方向"。这让它在推理时可以稳定地用欧拉法或 RK45 积分器求解 ODE,而不会出现 GAN 那种训练不稳定或模式崩溃的问题。
但反过来说,平均速度场的信息量是有上限的。如果某个时刻从同一个 x_t 出发的轨迹方向差异极大,平均方向可能"稀”掉。流匹配界有篇文章讨论的就是这个问题:模型对每个 x_t 只输出一个平均向量,如果数据分布复杂,单个平均向量可能不足以指引粒子到达正确的目标区域。这也是为什么很多工作会在模型里引入额外的随机性或噪声预测分支,目的正是弥补平均场的信息损失。
2.3 条件与无条件的平均速度关系
我们重新回到隐式 CFG 的核心。
假设我们有一个训练好的条件速度模型 v_θ(x, t, c)。现在我们定义无条件平均速度:
v_uncond(x, t) = E_c[ v_θ(x, t, c) ]
这个定义和"把 c 边缘化掉"是同一个意思。如果训练数据里有条件标签,我们可以在采样时用一个均匀分布或训练分布来控制 c 的取值,然后计算期望。但这里的问题是:采样时你并不想跑很多次模型再取平均。那么理想化推导要做的事情,就是把这个期望公式转化成一个只需要一次前向传播的表达式。
为了做到这一点,我们需要引入一个辅助网络或一个参数化技巧,把条件速度场的条件依赖显式拆开。最常见的做法是让模型输出两个分支:一个分支预测基础速度 m(x, t),另一个分支预测条件修正 g(x, t, c)。在训练时,我们用回归损失同时约束两个分支,并额外加一个约束 E_c[g(x, t, c)] = 0。这个约束保证 m 就是无条件平均速度。
那么隐式 CFG 的采样公式就直接变成:
v_guided = v_θ(x, t, c) + w · g(x, t, c) = m(x, t) + (w+1)·g(x, t, c)
你只需要一次前向传播,就能拿到带引导的速度场。这就是隐式 CFG 的核心思想。
3. 实操过程与核心环节实现
3.1 网络结构上的实现方案
要让"隐式 CFG"在工程上落地,首先得改网络结构。我建议把原来的单输出头改成双输出头。
具体来说,前向传播时模型接收 x_t、时刻 t 和条件 c,输出两个向量:
m_head:预测与条件无关的基础速度; g_head:预测条件引导增量。
然后我们要对 g_head 做零均值约束。这个约束怎么加?我用过两种方案,各有优劣。
第一种是硬约束。在训练时直接对 g_head 的每个 mini-batch 减去均值:
g_head_normalized = g_head - mean(g_head, dim=0)
这种做法简单粗暴,但容易让模型"偷懒":既然每次都会被归一化,g_head 的绝对值就会越训越小,最后整个分支失去意义。
第二种是正则化约束。在损失函数里加一项:
L_aux = λ · || E_c[ g_head(x_t, t, c) ] ||²
希望模型在条件分布上学习到一个期望为零的修正项。这种做法更温和,模型既能学到有意义的差异,又不会被硬约束压制。我实际跑下来,经验是 λ 取 0.1 到 0.5 比较合适。太小约束不住,太大把引导信息磨没了,采样结果和各种提示文本的对齐程度会急剧下降。
还有一个细节:条件编码器需要和 m_head 完全解耦。如果你用的是交叉注意力机制,要确保条件 c 只通过注意力层影响 g_head,而不是把条件信息直接拼接进主干特征里。否则即使你约束了 g_head 的期望,m_head 里依然会残留条件信息,隐式 CFG 的"无条件分支"就名不副实了。
3.2 训练阶段的损失与采样阶段的计算
训练阶段的总损失可以写成:
L_total = E[ || m_head + g_head - u(x_t, t, c) ||² ] + λ · || E_c[g_head] ||²
第一项和普通流匹配没有任何区别,让模型整体的输出逼近条件速度场。第二项是零均值约束。
一个容易踩的坑是:第二项的 E_c 是在一个训练 batch 内计算还是在整个数据集上计算。我建议在 batch 内做近似,但 batch size 不能太小。如果 batch size 只有 16 或者 32,条件标签分布不够均匀,算出来的均值偏差会很大,反而干扰训练。我的经验是至少 64 以上的 batch size,最好让每个 batch 内条件标签大致均匀分布。如果条件标签是连续向量或者文本特征,那就没办法均匀采样了,这种情况下可以放宽 λ,甚至去掉第二项,改用后处理方式的隐式 CFG。
到采样阶段,流程是这样的:
- 前向传播模型,得到 m 和 g;
- 计算最终引导速度;
- 用 ODE 求解器从 t=0 积分到 t=1。
这里有一个我一开始没注意到的细节。如果我们用的是传统的欧拉法,那么每次迭代走一步,前向传播一次,拿 m 和 g,速度计算一次,更新 x。但如果我们想让速度估计更平滑,可以借鉴二阶 Heun 法:先用当前速度推半步,得到中间点,再在中间点算一次速度,然后拿两次速度的加权平均做为最终速度。这样每个采样步需要两次前向传播,和隐式 CFG 前的成本差不多,但生成质量会明显提升。
3.3 关键参数与数值稳定性
隐式 CFG 的引导权重 w 和显式 CFG 很不一样。如果你把它直接类比成 w 倍 CFG,会出问题。
显式 CFG 的公式是 v_cond + w(v_cond - v_uncond)。当 w 很大时,引导方向会被过度放大,生成图像会过饱和、边缘出现伪影。隐式 CFG 的公式是 m + (w+1)g。如果你用同一个 w,效果会显得更强,因为 (w+1) 这个系数比 w 大 1。所以我建议实际使用时把隐式 CFG 的引导权重设得比显式 CFG 小 1 左右。比如你平时做显式 CFG 用 w=3,那么隐式 CFG 用 w=2 就有接近的效果。这事我踩过坑:用同一套调参脚本直接换隐式 CFG,结果先生成的图像全部过曝,透明度都出了问题。
再一个数值稳定性的关键点:g_head 这个分量在高引导权重下会变得很大,和 m_head 的数量级拉开差距。如果 m_head 的范数大概在 1 左右,而 g_head 在 w=5 时可能冲到 5 或者 6,两个向量相加会让整体速度场的范数剧烈变化,ODE 积分时容易发散。
一种解决方式是做速度场的范数裁剪:
v_guided = clip(v_guided, max_norm=N)
我给的建议是 N 取训练时观测到的最大速度范数的一个百分位数。你可以先跑一个验证集的统计,看看速度范数的分布,一般取 95% 分位数作为裁剪阈值。如果裁剪得太激进了,生成结果的细节会缺失,图像整体发灰。
另外,时刻 t 的采样分布也会影响隐式 CFG 的效果。流匹配常用的是一个均匀时间采样,但在隐式 CFG 下,我建议把时间采样稍微偏向 t=0.5 附近。原因很简单:在 t 接近 0 时,x_t 还接近纯噪声,条件信息对速度的影响还比较混沌;t 接近 1 时,x_t 已经接近数据分布,条件修正量 g 会趋于零,此时 m 占主导。t=0.5 附近是条件信息最明显的区域,让模型在这里多学一点,隐式 CFG 的引导效果会更可靠。
4. 常见问题与排查技巧实录
4.1 生成的样本不遵循条件
这个问题在隐式 CFG 里非常常见。如果你发现生成的样本根本不满足条件提示,先不要急着调 w,而是要检查模型结构是否真的做到了条件分支可分解。
我遇到过一种情况:把条件编码结果直接拼接到了主干特征里,结果 g_head 的零均值约束让 m_head 承接了条件信息,模型整体还是能生成"不错"的样本,但g_head 分支失去了大部分条件对应关系,隐式 CFG 怎么引导都没用。
排查方法:跑一个对比实验,固定同一批噪声种子,在纯 m_head 模式下采样一版,在 m_head + g_head 模式下采样一版。如果两版输出的差异很小,说明条件信息没有正确地传导到 g_head。差异大,说明 g_head 确实抓到了条件信息。如果差异大但生成结果不满足条件,那才可能是引导权重匹配问题。
另外一个容易被忽视的问题是条件标签的编码方式。如果你用的是一个宽泛的离散标签,比如分类标签,那 E_c[g_head] = 0 的约束确实能帮助平均速度场更干净。但如果你用的是文本嵌入或者连续条件向量,零均值约束可能不够充分,因为"所有条件取均值"不等于"无条件预测"。这时建议调整损失函数,在辅助约束里加入一个"语义不变性"正则:把条件特征的梯度裁剪掉一部分,强迫 m_head 不依赖条件。
4.2 高频伪影和过饱和
引导权重太大会导致过饱和,这在我前面的推导里已经提到。但还有个不同的问题:如果你发现在低引导权重下依然有高频伪影,问题往往出在速度场的连续性上。
流匹配模型里速度场应该是一个平滑的映射。但双分支网络里 g_head 可能在学习一个很尖锐的条件依赖函数,导致速度场在条件边界处出现跳变。一个简单的缓解方案是在 g_head 的输出上加入时间衰减因子:
g_head_output = g_head_raw · exp(-α·(t-0.5)²)
这个衰减因子会让条件引导在 t 接近 0 和 1 时自然退化为 0。我在实验里用它替代纯裁剪,效果比硬裁剪好很多,生成样本的高频噪声明显减少。
当然,如果你的模型已经收敛了,还是出现高频伪影,那多半是数据集本身有噪声。这种情况下你应该先去检查数据预处理,而不是继续调算法参数。我在自己的数据上就吃过这个亏:换了一版数据增强后,伪影问题直接消失了,前后算法参数完全没动。
4.3 训练不收敛或损失震荡
隐式 CFG 训练最让人抓狂的问题就是损失震荡。我一开始加零均值约束时,把 λ 调到 1.0,结果整个训练过程像过山车一样上下颠簸,完全无法收敛。后来把 λ 降到 0.2,同时用了一个滑动平均来估计 E_c[g_head],损失曲线才稳定下来。
另一个关键是优化器的选择。流匹配本身用 AdamW 没什么问题,但双分支结构里 m_head 和 g_head 是分层更新的,我建议给两个分支设置不同的学习率:m_head 维持正常学习率,g_head 的学习率设为它的 0.1 到 0.3 倍。理由是 g_head 和条件编码器耦合紧密,梯度变化剧烈,用一个较小的学习率可以避免参数在训练初期被扰动得太厉害。
还有个小技巧:在训练初期,把 w 相关的引导项直接关掉一段时间。也就是先只训练 m_head 和一个没有零均值约束的 g_head,等损失降到一定程度,再开启零均值约束。这样模型会先形成一个合理的条件速度估计,再去约束 g_head 的均值,收敛会稳定很多。
5. 衍生思考与工程扩展
5.1 和显式 CFG 的对比实验心得
在我手头的多组对比实验里,隐式 CFG 的生成质量在引导权重中等区间和显式 CFG 基本持平。差距主要体现在尾部行为上:当引导权重拉到很大时,显式 CFG 的过饱和问题更严重,隐式 CFG 的退火行为反而相对温和。这大概是因为隐式 CFG 的引导项 g 在训练时被约束过均值,它天生不会像无条件分支那样产生额外的高频偏置。
不过要说隐式 CFG 完全替代显式 CFG,为时尚早。显式 CFG 的 v_uncond 来自一个真正训练好的无条件模型,它能捕捉的数据分布和条件模型是分开的,这意味着你可以用同一个无条件模型去服务多个不同的条件模型。而隐式 CFG 的无条件分量是条件模型自己"内嵌"的,换一个条件模型就得重新训练整套。从工程架构上讲,显式 CFG 的模块化程度更高。
5.2 可能的扩展方向
一个我看来值得尝试的扩展是把隐式 CFG 的双分支结构和蒸馏技术结合起来。流匹配蒸馏的核心是把多步 ODE 积分压缩成一步或少数几步,而蒸馏时的教师速度场通常就来自一个条件模型。如果你在蒸馏前先把教师模型训练成双分支结构,那蒸馏时就可以直接使用隐式 CFG 构造的速度场作为目标,一步到位的蒸馏结果会自带引导能力。
另一个方向是把它和一致性模型(Consistency Model)结合。一致性模型的目标是让不同时刻的模型输出一致,隐式 CFG 在这里可以当作一种额外的自洽约束,让 m 分支和 g 分支的输出在时间轴上保持平滑。我粗略做过一点实验,感觉有潜力,但还没系统验证。
5.3 什么时候不该用隐式 CFG
老实讲,隐式 CFG 并不是万能的。如果你的应用场景里更重视多任务灵活性——一个模型要应付很多不同的条件、很多不同权重档位的生成——那显式 CFG 的独立无条件分支可能更实用。隐式 CFG 的优势是省训练成本、省显存,但这个省字是需要付出结构性代价的:你需要先确定"条件引导"和"基础速度"是可分离的,然后整个采样质量都押在这个结构假设上。
还有一个场景要谨慎:当条件变量是连续分布的、特别是高维文本嵌入时,零均值约束的数学正确性就变得很脆弱。前面说了,E_c[g] = 0 只能保证条件平均的修正量为零,但如果条件分布本身是稀疏的、多峰的,一个集中在某个区域的修正量在条件平均上恰好为零,不代表它在采样时表现得像真正的无条件速度。这种情况我用过一种补救方案:随机掩盖条件 c,让模型偶见纯无条件的输入。这和 CFG 训练里的 drop-out 思路类似。虽然多了一点训练代码,但确实能把隐式 CFG 拉回正轨。
我个人在实际操作中的体会是,每当数学推导得出的漂亮公式在工程上遇到问题,十有八九不是推导错,而是某个理想化假设在真实数据分布里没有成立。MeanFlow 的平均速度推导和隐式 CFG 也是这样:方向本身很有价值,你需要做的就是把它和你的数据特性、任务需求做适配,必要时补上数据增强、测试期条件丢弃或者自适应引导权重这些"不漂亮但很管用"的工程手段。做生成模型就是这样,理论和工程两头都要兼顾,有一头偏科,最终效果都会给你最直接的反馈。