FLUX.1 梳理

1. Flow Matching

Flow matching的核心在于——跳过加噪的过程

常识建立:对于扩散模型的生成来说,假设$x$ 是原始分布,$x_1$ 是目标分布,$t$是timestep。则要求$x$ 能在$t$个step内将分布转移到$x_1$内,$x$ $\rightarrow$ $x_1$。

下面给出Flow matching的关键概念和直观理解。

1.1 ODE 与 SDE

ODE 的全称是 Ordinary Differential Equation,即常微分方程
它用于描述一个变量随时间变化的确定性规律。一般形式可以写为: \(\frac{\mathrm{d}x}{\mathrm{d}t} = f(x,t)\)

其中:

  • $x$ 表示系统状态,在扩散模型中可以认为是某个时刻的数据分布;
  • $t$ 表示时间;
  • $f(x,t)$ 表示状态在当前时刻的变化率。

ODE 的特点是:在给定初始条件后,系统的演化轨迹是确定的,不包含随机扰动

SDE 的全称是 Stochastic Differential Equation,即随机微分方程
它用于描述既受到确定性规律影响、又受到随机噪声扰动的动态系统。一般形式可以写为: \(\mathrm{d}x = f(x,t)\,\mathrm{d}t + g(x,t)\,\mathrm{d}w\)

其中:

  • $f(x,t)\,\mathrm{d}t$ 是确定性的漂移项(drift term);
  • $g(x,t)\,\mathrm{d}w$ 是随机扩散项(diffusion term);
  • $w$ 表示 Brownian motion(布朗运动)。

SDE 的特点是:即使初始条件相同,由于存在随机项,系统轨迹通常也不是唯一的。


1.2 从ODE 出发,理解Flow Matching

再来看到ODE的公式: \(\frac{\mathrm{d}x}{\mathrm{d}t} = f(x,t)\) 那么,x对t的求导 $\frac{\mathrm{d}x}{\mathrm{d}t}$ 实际上表示状态变量 $x$ 关于时间 $t$ 的瞬时变化率,也就是$x$的变化方向,规定叫做Vector field。

在下图中,分布周围的蓝色箭头就是$f(x,t)$,可以直观理解为 “风吹着分布$x$从init状态$\rightarrow$target状态”。

规定$t\in [0,1]$,且将上面三张图分别定义init状态(t=0)、中间状态t时刻、和最终状态(t=1)。

则每一个step $t$时刻,数据的分布为$x_t$,vector field为$\frac{\mathrm{d}x_t}{\mathrm{d}t}$。

因此,可以得到一个初步结论:

在$t$个step内,从$x\rightarrow x_1$存在路径path,并且$f(x,t)$决定着$x_t$的走向。

[!Note]

所以,在扩散模型中,如果我们要将$x$生成到某个分布(例如猫和狗),意味着$x$和$x_1$已经确定,我们需要求解出合适的vector field,使得x移动的路径满足要求。

那么,$f(x,t)$该如何求解?先来看下面四张图:

图中的$\mu$就是$f$

  • (a)给定目标$x_1$情况下的Path条件分布,也即x的移动路径;
  • (b)不给定目标情况下,x移动路径的边缘分布,等于全部$x_1$下的期望;
  • (c)给定目标$x_1$情况下的vector field条件分布;
  • (d)不给定目标情况下,vector field的边缘分布;

对于(a),分布变化的过程等价于求解一条从$N(0,\sigma^2I)\rightarrow N(x_1,0)$的条件概率路径,

把正态分布参数化,定义成下面的形式: \(P_t(x|x_1):N(\alpha_{t}\cdot x_1,\beta_t^2\cdot I)\) 这样的好处是,可以将求解的过程转化成线性求解:

可以求出条件 vector field:

\[\begin{aligned} \frac{P_t(x\mid x_1)}{\mathrm{d}t} &= f_t(x\mid x_1) \\ &= \frac{\beta_t^\prime}{\beta_t}(x-\alpha_t) + \alpha_t^\prime \end{aligned}\]

边缘分布等于条件分布的期望,先铺垫以下概念:

  1. Divergence:在当前t step x的位置,流出的量减去流入的量(直观理解为当前位置,可以出去的方向数 减去 可以进来的方向数)
\[\operatorname{div}(v_t)(x)=\sum_{i=1}^{d}\frac{\partial}{\partial x_i}v_t(x)\]
  1. Continuity Equation:质量守恒。
\[\frac{\mathrm{d}}{\mathrm{d}t}p_t(x)+\operatorname{div}\bigl(p_t\cdot f_t\bigr)(x)=0\]

直观理解:

  • 当这个位置的散度(流出的量-流入的量)为0时,也就是$\operatorname{div}\bigl(p_t\cdot f_t\bigr)(x)$=0时,物体的质量随时间不会发生改变。

  • 如果散度大于0,那么质量会随时间减小,$\frac{\mathrm{d}}{\mathrm{d}t}p_t(x)$<0。
  • 如果散度小于0,那么质量会随时间变大,$\frac{\mathrm{d}}{\mathrm{d}t}p_t(x)$>0。

Proof:

\[\begin{aligned} \frac{\mathrm{d}}{\mathrm{d}t}p_t(x) &= \frac{\mathrm{d}}{\mathrm{d}t} \int p_t(x\mid x_1)\,p(x_1)\,\mathrm{d}x_1 \\ &= \int \frac{\mathrm{d}}{\mathrm{d}t}p_t(x\mid x_1)\,p(x_1)\,\mathrm{d}x_1 \\ &= \int \Bigl[ -\operatorname{div}\bigl(p_t(x\mid x_1)\,f_t(x\mid x_1)\bigr) \Bigr] p(x_1)\,\mathrm{d}x_1 \\ &= -\operatorname{div} \int p_t(x\mid x_1)\,f_t(x\mid x_1)\,p(x_1)\,\mathrm{d}x_1 \\ &= -\operatorname{div} \left[ p_t(x) \int f_t(x\mid x_1)\, \frac{p_t(x\mid x_1)\,p(x_1)}{p_t(x)} \,\mathrm{d}x_1 \right] \\ &= -\operatorname{div}\bigl(p_t\cdot f_t\bigr)(x) \end{aligned}\]

Therefore,

\[\begin{aligned} f_t(x) &= \int f_t(x\mid x_1)\, \frac{p_t(x\mid x_1)\,p(x_1)}{p_t(x)} \,\mathrm{d}x_1 \end{aligned}\]

1.3 How to train a Flow-Matching Model

在上一部分中,我们已经得到边缘向量场的表达式:

\[\begin{aligned} f_t(x) &= \int f_t(x\mid x_1)\, \frac{p_t(x\mid x_1)p(x_1)}{p_t(x)} \,\mathrm{d}x_1 \\ &= \mathbb{E}_{x_1\sim p(x_1\mid x,t)}\bigl[f_t(x\mid x_1)\bigr]. \end{aligned}\]

这说明,边缘向量场 $f_t(x)$ 本质上是条件向量场 $f_t(x\mid x_1)$ 关于后验分布的条件期望。

我们的目标是训练一个参数化模型 $f_t^\theta(x)$,使其能够逼近真实的边缘向量场 $f_t(x)$。
一个自然的训练目标是最小化如下损失: \(\begin{aligned} \mathcal{L}_{\mathrm{fm}}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \left\|f_t^\theta(x)-f_t(x)\right\|^2 \right]. \end{aligned}\)

但是这个目标通常是 intractable 的,因为 $f_t(x)$ 本身往往无法直接计算。


为了解决上述问题,考虑定义条件 Flow Matching 损失: \(\begin{aligned} \mathcal{L}_{\mathrm{cfm}}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \left\|f_t^\theta(x)-f_t(x\mid x_1)\right\|^2 \right]. \end{aligned}\)

下面说明两个损失函数 L_fmL_cfm 仅相差一个与参数 theta 无关的常数,因此二者具有相同的最优解。

1. 展开 L_fm

\[\begin{aligned} \mathcal{L}_{\mathrm{fm}}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \|f_t^\theta(x)-f_t(x)\|^2 \right] \\ &= \mathbb{E}_{t,x_1,x} \left[ \|f_t^\theta(x)\|^2 -2f_t^\theta(x)^\top f_t(x) +\|f_t(x)\|^2 \right]. \end{aligned}\]

2. 展开 L_cfm

\[\begin{aligned} \mathcal{L}_{\mathrm{cfm}}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \|f_t^\theta(x)-f_t(x\mid x_1)\|^2 \right] \\ &= \mathbb{E}_{t,x_1,x} \left[ \|f_t^\theta(x)\|^2 -2\,f_t^\theta(x)^\top f_t(x\mid x_1) +\|f_t(x\mid x_1)\|^2 \right]. \end{aligned}\]

3. 利用边缘向量场的定义

由上一节结论,

\[\begin{aligned} f_t(x) &= \int p(x_1\mid x,t)\,f_t(x\mid x_1)\,\mathrm{d}x_1 \\ &= \mathbb{E}_{x_1\sim p(x_1\mid x,t)}\bigl[f_t(x\mid x_1)\bigr]. \end{aligned}\]

因此,对于固定的 $x,t$,有

\[\mathbb{E}_{x_1\sim p(x_1\mid x,t)} \left[ f_t(x)-f_t(x\mid x_1) \right] =0.\]

进一步可得

\[\mathbb{E}_{t,x} \left[ f_t^\theta(x)^\top \mathbb{E}_{x_1\sim p(x_1\mid x,t)} \bigl[f_t(x)-f_t(x\mid x_1)\bigr] \right] =0.\]

也就是

\[\begin{aligned} \mathbb{E}_{t,x_1,x} \left[ f_t^\theta(x)^\top f_t(x) \right] &= \mathbb{E}_{t,x_1,x} \left[ f_t^\theta(x)^\top f_t(x\mid x_1) \right]. \end{aligned}\]

4. 两个损失之差

于是, \(\begin{aligned} \mathcal{L}_{\mathrm{fm}}(\theta)-\mathcal{L}_{\mathrm{cfm}}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \|f_t(x)\|^2-\|f_t(x\mid x_1)\|^2 \right]. \end{aligned}\)

右边不含参数 $\theta$,因此它只是一个常数。于是有

\[\begin{aligned} \mathcal{L}_{\mathrm{fm}}(\theta) &= \mathcal{L}_{\mathrm{cfm}}(\theta)+\text{constant}. \end{aligned}\]

因此,

\[\begin{aligned} \arg\min_\theta \mathcal{L}_{\mathrm{fm}}(\theta) &= \arg\min_\theta \mathcal{L}_{\mathrm{cfm}}(\theta). \end{aligned}\]

所以,虽然我们原本想训练的是边缘向量场 $u_t(x)$,但实际中可以转而优化如下可计算的目标:

\[\begin{aligned} \mathcal{L}(\theta) &= \mathbb{E}_{t,x_1,x} \left[ \left\|f_t^\theta(x)-f_t(x\mid x_1)\right\|^2 \right]. \end{aligned}\]

这就是 Flow Matching / Conditional Flow Matching 的核心训练形式。

它的含义是:

  • 我们真正想拟合的是边缘向量场 $f_t(x)$;
  • 但由于 $f_t(x)$ 不易直接计算,于是改为拟合条件向量场 $f_t(x\mid x_1)$;
  • 由于二者对应的损失只差一个与参数无关的常数,因此训练条件目标就等价于训练边缘目标。

1.4 Pseudo codes of training and inference

线性情况下, $\frac{P_t(x|x_1)}{\mathrm{d}t}=f_t(x|x_1)=\frac{\beta_t^\prime}{\beta_t}(x-\alpha_t) + \alpha_t^\prime$

Training

1. 从数据集中采样一个图像 $x_1$。

2. 随机采样一个 timestep $t\in[0,1]$,以及一个 noise $\sigma\in N(0,I)$。

3. 给定 $x_1$ 下的条件路径分布为

\[p_t(x\mid x_1)=t x_1 + (1-t)\sigma\]

它服从分布

\[N(\alpha_t\cdot x_1,\beta_t^2\cdot I),\]

所以 $\alpha_t=t,\beta_t=1-t$。

4. 因此

\[f_t(x\mid x_1)=x_1-\sigma\]

或者你直接对 $t$ 求导,也是这个结果,不用管服从什么分布。

5. 所以损失函数为

\[\mathcal{L}(\theta)=\left\|f_t^\theta(x)-(x_1-\sigma)\right\|^2.\]

6. BP优化,最终模型能够拟合vector field。可以注意到不论 $t$ 采样多少,监督信号都是不变的。

Inference

1. 初始化 $t=0$,step size 为 $h=\frac{1}{n}$,其中 $n$ 为离散步数。

2. 令 $x_0$ 为初始状态。

3. 对 $i=1,\dots,n-1$ 循环执行:

\[x_{t+h}=x_t+f_t^\theta(x_t)\cdot h\] \[t\leftarrow t+h\]

4. 返回 $x_1$。

2. DiT

主要从这张图看一下时间步这种条件是如何影响模型的生成的,也就是$f_t^\theta(x_t)$怎么接受$x$和$t$。

在 DiT 中,若采用 adaLN-Zero 机制,则模型虽然形式上可写为 $f_t^\theta(x_t)$, 但更准确地说,它实际还依赖于时间步条件以及其他外部条件,因此应理解为 $f_\theta(x_t, t, c)$。

其中:

  • $x_t$ 表示时刻 $t$ 的带噪 latent;
  • $t$ 表示时间步或噪声强度;
  • $c$ 表示附加条件,例如类别标签或文本条件。
  1. $x_t$ 如何进入模型

输入的带噪 latent $x_t$ 先经过 patchify,被划分为一系列 token,作为 Transformer 主干网络的输入。
因此,$x_t$ 提供的是当前样本的空间内容信息,是模型生成更新的基础。

  1. $t$ 和条件 $c$ 如何进入模型

在 adaLN-Zero 中,时间步 $t$ 以及其他条件 $c$ 不直接作为 token 与输入序列拼接,也不通过 cross-attention 进入主干,而是先经过 embedding 和 MLP,生成一组用于调制 Transformer block 的参数。

设条件向量记为

\[e = \phi(t, c),\]

其中 $\phi(\cdot)$ 表示由时间步嵌入、条件嵌入及后续 MLP 构成的映射。
随后,模型根据该条件向量生成每个 block 所需的调制参数,例如:

\[(\alpha_1,\beta_1,\gamma_1,\alpha_2,\beta_2,\gamma_2)=\mathrm{MLP}(e).\]

这些参数分别作用于 block 中的 attention 分支和 feedforward 分支。

  1. adaLN-Zero 的具体作用方式

对于输入特征 $h$,普通 LayerNorm 可写为

\[\mathrm{LN}(h).\]

在 adaLN 中,LayerNorm 的输出会被条件相关参数进一步调制:

\[\mathrm{adaLN}(h,e)=\gamma(e)\odot \mathrm{LN}(h)+\beta(e),\]

其中:

  • $\gamma(e)$ 表示缩放参数(scale);
  • $\beta(e)$ 表示平移参数(shift);
  • $\odot$ 表示逐元素乘法。

此外,残差分支还会通过门控参数 $\alpha(e)$ 进行控制,因此对应的更新形式可以写为

\[h+\alpha(e)\cdot F\bigl(\mathrm{adaLN}(h,e)\bigr),\]

其中 $F(\cdot)$ 表示 self-attention 或 pointwise feedforward 等子模块。

  1. Zero 的含义

adaLN-Zero 中的 “Zero” 表示:在初始化时,调制相关的残差门控通常被初始化为接近零,使得网络初始状态更接近恒等映射。
这样做的作用是:

  • 降低训练初期条件注入对主干特征的剧烈扰动;
  • 提高深层 Transformer 训练的稳定性;
  • 使模型逐步学会如何利用时间步和条件信息调制生成过程。
  1. 对生成过程的理解

因此,在 adaLN-Zero 中:

  • $x_t$ 决定当前 noisy latent 的内容;
  • $t$ 告诉模型当前处于去噪过程的哪个阶段;
  • 条件 $c$(例如 text)提供目标语义信息。

但这些条件并不是直接替代输入特征,而是通过生成 scale、shift 和 gate 等参数,逐层调制 Transformer block 对 $x_t$ 的处理方式。
因此,$f_t^\theta(x_t)$ 的本质是一个在条件控制下对输入 latent 进行变换的函数,其条件依赖性由 adaLN-Zero 注入到每一层的归一化与残差更新过程中。

3. FLUX.1 Model

前面已经把 Flow MatchingDiT 中条件如何注入 两件事讲清楚了。

接下来就要回答真正和 FLUX.1 本身有关的问题:

  • 输入文本是怎么进入模型的?
  • 图像为什么不是直接在像素空间生成,而是在 latent 空间生成?
  • FLUX.1 和普通 DiT 到底有什么结构差别?
  • 模型每一步预测的到底是什么量?

如果把这些问题串起来,FLUX.1 的整体逻辑就会变得非常清楚。

3.1 总体架构总览

先给出一句最核心的概括:

FLUX.1 本质上是一个 latent rectified-flow transformer

这句话可以拆成三层含义:

  1. latent

    模型不是直接在原始像素空间上生成图像,而是先在压缩后的 latent 空间中做生成,最后再 decode 回图像。

  2. rectified flow

    模型学习的是从噪声分布流向目标图像分布的 vector field,因此训练目标更接近前面讲的 Flow Matching,而不是传统 DDPM 里一步步预测噪声的写法。

  3. transformer

    主干网络不再是 UNet,而是将图像 latent 划分成 token,再和文本 token 一起送入 Transformer 中建模。

从 diffusers 的参考实现来看,一个完整的 FLUX pipeline 主要包括下面几个部分:

  • VAE:负责图像和 latent 之间的编码/解码;
  • text_encoderCLIPTextModel,可以理解为提供更偏全局的文本语义;
  • text_encoder_2T5EncoderModel,可以理解为提供更细粒度、更长序列的文本条件;
  • transformer:真正负责预测 latent 更新方向的 FluxTransformer2DModel
  • schedulerFlowMatchEulerDiscreteScheduler,负责按照 flow matching 的离散更新方式推进采样过程。

因此,FLUX.1 并不是“只换了个 loss 的 DiT”。

更准确地说,它是:

  1. latent 空间中进行生成;
  2. 文本-图像联合 Transformer 作为主干;
  3. flow / rectified flow 的目标来学习生成轨迹。

3.2 输入表示:text、latent 与 token

在真正进入 Transformer 之前,FLUX.1 需要先把“文本”和“图像”都表示成 token 形式。

3.2.1 图像侧:先变成 latent,再变成 image tokens

图像侧的流程可以理解为:

image -> VAE encoder -> latent grid -> image tokens

为什么不直接在像素空间生成?

原因很简单:像素空间太大,直接建模的计算量和显存压力都非常高。
因此和 Stable Diffusion 系列一样,FLUX.1 也是先在更低维的 latent 空间中完成主要生成过程,再由 VAE decoder 还原出最终图像。

在 diffusers 的 FluxTransformer2DModel 中,图像输入 hidden_states 的形状是:

\[(\text{batch},\ \text{image\_sequence\_length},\ \text{in\_channels})\]

并且参考实现中的 in_channels=64
这说明进入 Transformer 时,图像已经不再是二维 feature map,而是被整理成一串 image tokens,每个 token 对应一个 latent 网格位置上的特征。

所以可以把 FLUX.1 的图像输入理解为:

  • 空间结构仍然保留在 token 的排列关系中;
  • 但网络实际处理的是一串序列,而不是传统卷积网络里的 feature map。

3.2.2 文本侧:不是一条文本通路,而是两条

FLUX pipeline 的另一个关键点在于:它并不是只用一个 text encoder。

在 diffusers 参考实现中,它同时使用:

  • CLIPTextModel
  • T5EncoderModel

可以把这两条文本通路粗略理解为:

  • CLIP 更偏向于提供一个全局、压缩后的语义摘要;
  • T5 更偏向于提供细粒度的文本序列信息,保留 prompt 中更多 token 级别的内容。

这两部分信息在后续主干中扮演的角色并不完全一样:

  • 一部分作为 序列条件 参与 joint attention;
  • 一部分作为 pooled condition,进入时间步/条件调制通路,用于控制 block 的行为。

这也是为什么 FLUX.1 的 prompt adherence 很强。
因为它并不是简单地把一句文本编码成一个向量,而是同时保留了:

  • 全局语义;
  • 长文本细节;
  • token 级别的结构信息。

3.3 FLUX.1 主干:先双流,再单流

如果只看名字,很多人会觉得 FLUX.1 就是一个普通 DiT。
但从 diffusers 中 FluxTransformer2DModel 的结构参数来看,它其实有一个非常鲜明的组织方式:

  • num_layers = 19
  • num_single_layers = 38

文档中把前者称作 dual stream DiT blocks,后者称作 single stream DiT blocks

这件事非常重要,因为它揭示了 FLUX.1 的核心结构逻辑。

3.3.1 dual stream:先分别处理 text 和 image

所谓 dual stream,可以把它理解成:

  • 文本 token 有自己的流;
  • 图像 token 也有自己的流;
  • 但二者并不是完全隔离,而是在 attention 中交换信息。

为什么要这样设计?

因为 text token 和 image token 的统计特性并不一样:

  • text token 更偏离散语义;
  • image token 更偏连续空间特征。

如果一上来就把两者完全混在一起做统一建模,模型需要在很浅层就同时解决:

  • 文本语义理解;
  • 图像结构建模;
  • 跨模态对齐;

这会让优化变得更困难。

因此 FLUX.1 的策略是:

  1. 先保留 text/image 各自的表示流;
  2. 在较浅层完成跨模态对齐;
  3. 等两种 token 已经“知道彼此是谁”之后,再进入更深的统一建模。

3.3.2 single stream:后面再做深度融合

在 dual stream blocks 之后,FLUX.1 还有更多的 single stream blocks。

这可以理解为:

  • 前面解决“对齐”问题;
  • 后面解决“联合生成”问题。

一旦 text token 和 image token 已经在浅层对齐完成,后面的单流建模就可以更专注于:

  • 细化空间结构;
  • 强化 prompt 对局部内容的控制;
  • 输出更准确的 vector field。

因此,FLUX.1 的主干不是“从头到尾都一个结构”,而是分成两个阶段:

  1. dual stream 阶段:做跨模态对齐;
  2. single stream 阶段:做联合生成与精细更新。

这也是它和“普通一串统一 Transformer block”的一个重要差别。


3.4 FLUX.1 预测的到底是什么

这一点必须和传统 diffusion 区分开。

在很多 DDPM / latent diffusion 模型中,网络常见的预测目标是:

  • 噪声 $\epsilon$;
  • 或者某种等价的重参数化目标,例如 $v$。

但在 FLUX.1 里,更自然的理解方式是:

模型预测的是当前 latent 状态应该往哪个方向走,也就是 vector field / velocity

因此,输入当前时刻的 latent $x_t$ 后,模型输出可以理解为:

\[f_t^\theta(x_t,\text{text})\]

它描述的是:在当前时间步,latent 应该沿哪个方向更新,才能逐渐流向目标图像分布。

所以推理时的核心更新形式就是:

\[x_{t+h}=x_t+h\cdot f_t^\theta(x_t,\text{text})\]

这和前面讲的 ODE 完全对应。

也就是说,FLUX.1 的生成过程更像是:

  • 在 latent 空间里定义一条从 noise 到 image 的轨迹;
  • Transformer 在每个时刻给出局部方向;
  • scheduler 按照这个方向做离散积分。

从这个角度看,FLUX.1 和“逐步去噪”的直觉并不完全一样。
它更像是在学习一张“速度场地图”,然后沿着这张地图把噪声流到图像。


3.5 条件信息到底怎么进入 FLUX.1

前面在 DiT 一节已经介绍过 adaLN-Zero 的基本思想。
到了 FLUX.1 这里,可以把条件注入方式理解得更具体一些。

对于 FLUX.1 来说,至少有三类条件会参与计算:

  1. 当前 latent 状态 $x_t$;
  2. 时间步 timestep
  3. 文本条件(包括序列级文本表示和 pooled 文本表示)。

FluxTransformer2DModel.forward 的输入可以看到,主干会接收:

  • hidden_states
  • encoder_hidden_states
  • pooled_projections
  • timestep
  • 以及和位置编码有关的 img_idstxt_ids

这说明 FLUX.1 的条件注入并不是“只有一条 text embedding”这么简单,而是至少分成了两条路径:

3.5.1 一条进入 attention

encoder_hidden_states 可以理解为文本 token 序列对应的条件表示。
这部分信息参与 joint attention,使图像 token 在更新时能够感知 prompt 的细粒度语义。

3.5.2 一条进入 block 调制

pooled_projectionstimestep 则更像全局条件。
它们不会只告诉模型“要画什么”,还会告诉模型:

  • 当前处于采样轨迹的哪个阶段;
  • 当前 block 应该更偏向语义布局,还是更偏向细节修正。

因此,如果把 FLUX.1 看成一个受条件控制的动力系统,那么:

  • encoder_hidden_states 更像局部语义约束;
  • pooled_projections 更像全局控制信号;
  • timestep 更像系统当前所处的时间位置。

这三者一起决定了最终的 vector field。


3.6 devschnell 的差别

FLUX.1 常见的开源版本里,最常被拿来比较的就是:

  • FLUX.1-dev
  • FLUX.1-schnell

二者都基于 FLUX.1 主干,但推理习惯和蒸馏方式不同。

3.6.1 dev

根据 diffusers 文档,devguidance-distilled 版本。

它的典型特点是:

  • 生成质量更稳;
  • 常见设置下大约需要 50 steps 才能比较充分地发挥效果;
  • 没有 max_sequence_length=256 这类限制;
  • 推理时通常会配合非零 guidance_scale 使用。

因此,dev 更像是:

  • 速度不是第一优先级;
  • 更注重质量、细节和 prompt 跟随能力。

3.6.2 schnell

根据 diffusers 文档,schnelltimestep-distilled 版本。

它的特点更激进:

  • 通常 1 到 4 步 就能出图;
  • guidance_scale 需要设为 0
  • max_sequence_length 不能超过 256

因此,schnell 的本质是:
用额外蒸馏把原本多步的采样轨迹压缩到极少数步骤里,从而换取极快的生成速度。

可以简单理解为:

  • dev:更偏质量;
  • schnell:更偏速度。

3.7 FLUX.1 和 SDXL / 普通 DiT 的差别

如果要快速定位 FLUX.1 在整个生成模型谱系里的位置,可以这样看:

3.7.1 和 SDXL 的差别

SDXL 的主干仍然是 UNet 思路:

  • 图像特征在多尺度卷积结构里流动;
  • 条件通常通过 cross-attention 等方式注入。

而 FLUX.1 的主干已经变成:

  • 图像先转成 token;
  • 文本也转成 token;
  • 再用 Transformer 做统一建模。

所以二者最大的差别不是“loss 不一样”,而是:

  • SDXL 更像卷积式的 latent diffusion;
  • FLUX.1 更像 token-based 的 latent flow transformer。

3.7.2 和普通 DiT 的差别

普通 DiT 的核心思想是:
用 Transformer 代替 UNet 来处理图像 token。

而 FLUX.1 在此基础上又往前走了一步:

  • 不只是“图像 token + 条件”;
  • 而是显式地做 文本流 / 图像流的分阶段融合
  • 再配合 rectified flow / flow matching 目标来学习生成轨迹。

所以从直观上说:

  • DiT 解决的是“能不能用 Transformer 生成图像”;
  • FLUX.1 更进一步解决的是“怎么让文本条件和图像 latent 在 Transformer 里更高效地协同工作”。

3.8 一个完整的 forward pass

最后,把前面的所有内容串起来,看一遍 FLUX.1 的完整前向流程。

假设输入是一句 prompt,例如:

"a tiny astronaut hatching from an egg on the moon"

则一次典型生成可以理解为下面几步:

3.8.1 文本编码

prompt 先分别送入文本编码器,得到两类文本条件:

  • 序列级文本 token 表示;
  • pooled 的全局文本表示。

3.8.2 初始化 latent

在图像侧,先采样一个初始噪声 latent,记作 $x_0$。
此时它还不对应任何真实图像,只是 latent 空间中的随机起点。

3.8.3 整理成 image tokens

这个 latent 会被整理成 image token 序列,作为 Transformer 的 hidden_states 输入。

3.8.4 注入时间步和条件

当前的 timestep、pooled 文本条件、文本 token 条件一起进入主干网络:

  • 一部分调制 block;
  • 一部分进入 attention;
  • 一部分决定 text/image 的对齐关系与融合方式。

3.8.5 Transformer 预测 vector field

经过 dual stream blocks 和 single stream blocks 后,模型输出当前时刻的更新方向,也就是:

\[f_t^\theta(x_t,\text{text})\]

3.8.6 scheduler 更新 latent

随后,FlowMatchEulerDiscreteScheduler 根据这个方向,把 latent 从当前时刻推进到下一个时刻:

\[x_{t+h}=x_t+h\cdot f_t^\theta(x_t,\text{text})\]

重复多次之后,latent 会逐渐从随机噪声流向目标图像分布。

3.8.7 decode 成最终图像

当采样结束后,得到最终 latent $x_1$,再送入 VAE decoder,输出最终图像。


3.9 从工程角度如何理解 FLUX.1

如果从工程实现的角度看,FLUX.1 之所以强,主要是因为它同时把三件事做到了:

  1. 文本理解强

    双文本编码器 + 联合 Transformer,使 prompt 中的复杂语义能够更充分地注入图像生成过程。

  2. 主干表达能力强

    12B 级别的 Transformer 主干,加上先 dual-stream 再 single-stream 的结构,使它在跨模态对齐和图像细节生成上都有很高容量。

  3. 采样路径更直接

    flow matching / rectified flow 的目标让模型更自然地学习“从噪声走向图像”的方向场,因此在推理时可以用更直接的轨迹更新 latent。

当然,它的代价也很明显:

  • 模型非常大;
  • 显存压力高;
  • 推理成本高;
  • consumer GPU 上部署并不轻松。

所以可以把 FLUX.1 看成一个典型的“拿更大算力和更复杂结构,换更强 prompt adherence 与更高图像质量”的模型。


到这里再回头看,你会发现 FLUX.1 的逻辑其实很统一:

  • 理论层面:用 Flow Matching 学 vector field;
  • 网络层面:用 Transformer 建模 text/image token;
  • 条件层面:用时间步和文本条件调制主干;
  • 推理层面:在 latent 空间中沿着 learned flow 逐步更新。

这四件事合在一起,才构成了完整的 FLUX.1。