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}\]边缘分布等于条件分布的期望,先铺垫以下概念:
- Divergence:在当前t step x的位置,流出的量减去流入的量(直观理解为当前位置,可以出去的方向数 减去 可以进来的方向数)
- Continuity Equation:质量守恒。
直观理解:
当这个位置的散度(流出的量-流入的量)为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_fm 和 L_cfm 仅相差一个与参数 theta 无关的常数,因此二者具有相同的最优解。
1. 展开 L_fm
2. 展开 L_cfm
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$ 表示附加条件,例如类别标签或文本条件。
- $x_t$ 如何进入模型
输入的带噪 latent $x_t$ 先经过 patchify,被划分为一系列 token,作为 Transformer 主干网络的输入。
因此,$x_t$ 提供的是当前样本的空间内容信息,是模型生成更新的基础。
- $t$ 和条件 $c$ 如何进入模型
在 adaLN-Zero 中,时间步 $t$ 以及其他条件 $c$ 不直接作为 token 与输入序列拼接,也不通过 cross-attention 进入主干,而是先经过 embedding 和 MLP,生成一组用于调制 Transformer block 的参数。
设条件向量记为
\[e = \phi(t, c),\]其中 $\phi(\cdot)$ 表示由时间步嵌入、条件嵌入及后续 MLP 构成的映射。
随后,模型根据该条件向量生成每个 block 所需的调制参数,例如:
这些参数分别作用于 block 中的 attention 分支和 feedforward 分支。
- 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 等子模块。
- Zero 的含义
adaLN-Zero 中的 “Zero” 表示:在初始化时,调制相关的残差门控通常被初始化为接近零,使得网络初始状态更接近恒等映射。
这样做的作用是:
- 降低训练初期条件注入对主干特征的剧烈扰动;
- 提高深层 Transformer 训练的稳定性;
- 使模型逐步学会如何利用时间步和条件信息调制生成过程。
- 对生成过程的理解
因此,在 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 Matching 和 DiT 中条件如何注入 两件事讲清楚了。
接下来就要回答真正和 FLUX.1 本身有关的问题:
- 输入文本是怎么进入模型的?
- 图像为什么不是直接在像素空间生成,而是在 latent 空间生成?
- FLUX.1 和普通 DiT 到底有什么结构差别?
- 模型每一步预测的到底是什么量?
如果把这些问题串起来,FLUX.1 的整体逻辑就会变得非常清楚。
3.1 总体架构总览
先给出一句最核心的概括:
FLUX.1 本质上是一个 latent rectified-flow transformer。
这句话可以拆成三层含义:
-
latent
模型不是直接在原始像素空间上生成图像,而是先在压缩后的 latent 空间中做生成,最后再 decode 回图像。
-
rectified flow
模型学习的是从噪声分布流向目标图像分布的 vector field,因此训练目标更接近前面讲的 Flow Matching,而不是传统 DDPM 里一步步预测噪声的写法。
-
transformer
主干网络不再是 UNet,而是将图像 latent 划分成 token,再和文本 token 一起送入 Transformer 中建模。
从 diffusers 的参考实现来看,一个完整的 FLUX pipeline 主要包括下面几个部分:
VAE:负责图像和 latent 之间的编码/解码;text_encoder:CLIPTextModel,可以理解为提供更偏全局的文本语义;text_encoder_2:T5EncoderModel,可以理解为提供更细粒度、更长序列的文本条件;transformer:真正负责预测 latent 更新方向的FluxTransformer2DModel;scheduler:FlowMatchEulerDiscreteScheduler,负责按照 flow matching 的离散更新方式推进采样过程。
因此,FLUX.1 并不是“只换了个 loss 的 DiT”。
更准确地说,它是:
- 在 latent 空间中进行生成;
- 用 文本-图像联合 Transformer 作为主干;
- 用 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 的形状是:
并且参考实现中的 in_channels=64。
这说明进入 Transformer 时,图像已经不再是二维 feature map,而是被整理成一串 image tokens,每个 token 对应一个 latent 网格位置上的特征。
所以可以把 FLUX.1 的图像输入理解为:
- 空间结构仍然保留在 token 的排列关系中;
- 但网络实际处理的是一串序列,而不是传统卷积网络里的 feature map。
3.2.2 文本侧:不是一条文本通路,而是两条
FLUX pipeline 的另一个关键点在于:它并不是只用一个 text encoder。
在 diffusers 参考实现中,它同时使用:
CLIPTextModelT5EncoderModel
可以把这两条文本通路粗略理解为:
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 = 19num_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 的策略是:
- 先保留 text/image 各自的表示流;
- 在较浅层完成跨模态对齐;
- 等两种 token 已经“知道彼此是谁”之后,再进入更深的统一建模。
3.3.2 single stream:后面再做深度融合
在 dual stream blocks 之后,FLUX.1 还有更多的 single stream blocks。
这可以理解为:
- 前面解决“对齐”问题;
- 后面解决“联合生成”问题。
一旦 text token 和 image token 已经在浅层对齐完成,后面的单流建模就可以更专注于:
- 细化空间结构;
- 强化 prompt 对局部内容的控制;
- 输出更准确的 vector field。
因此,FLUX.1 的主干不是“从头到尾都一个结构”,而是分成两个阶段:
- dual stream 阶段:做跨模态对齐;
- 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 来说,至少有三类条件会参与计算:
- 当前 latent 状态 $x_t$;
- 时间步
timestep; - 文本条件(包括序列级文本表示和 pooled 文本表示)。
从 FluxTransformer2DModel.forward 的输入可以看到,主干会接收:
hidden_statesencoder_hidden_statespooled_projectionstimestep- 以及和位置编码有关的
img_ids、txt_ids
这说明 FLUX.1 的条件注入并不是“只有一条 text embedding”这么简单,而是至少分成了两条路径:
3.5.1 一条进入 attention
encoder_hidden_states 可以理解为文本 token 序列对应的条件表示。
这部分信息参与 joint attention,使图像 token 在更新时能够感知 prompt 的细粒度语义。
3.5.2 一条进入 block 调制
pooled_projections 和 timestep 则更像全局条件。
它们不会只告诉模型“要画什么”,还会告诉模型:
- 当前处于采样轨迹的哪个阶段;
- 当前 block 应该更偏向语义布局,还是更偏向细节修正。
因此,如果把 FLUX.1 看成一个受条件控制的动力系统,那么:
encoder_hidden_states更像局部语义约束;pooled_projections更像全局控制信号;timestep更像系统当前所处的时间位置。
这三者一起决定了最终的 vector field。
3.6 dev 和 schnell 的差别
FLUX.1 常见的开源版本里,最常被拿来比较的就是:
FLUX.1-devFLUX.1-schnell
二者都基于 FLUX.1 主干,但推理习惯和蒸馏方式不同。
3.6.1 dev
根据 diffusers 文档,dev 是 guidance-distilled 版本。
它的典型特点是:
- 生成质量更稳;
- 常见设置下大约需要 50 steps 才能比较充分地发挥效果;
- 没有
max_sequence_length=256这类限制; - 推理时通常会配合非零
guidance_scale使用。
因此,dev 更像是:
- 速度不是第一优先级;
- 更注重质量、细节和 prompt 跟随能力。
3.6.2 schnell
根据 diffusers 文档,schnell 是 timestep-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 从当前时刻推进到下一个时刻:
重复多次之后,latent 会逐渐从随机噪声流向目标图像分布。
3.8.7 decode 成最终图像
当采样结束后,得到最终 latent $x_1$,再送入 VAE decoder,输出最终图像。
3.9 从工程角度如何理解 FLUX.1
如果从工程实现的角度看,FLUX.1 之所以强,主要是因为它同时把三件事做到了:
-
文本理解强
双文本编码器 + 联合 Transformer,使 prompt 中的复杂语义能够更充分地注入图像生成过程。
-
主干表达能力强
12B 级别的 Transformer 主干,加上先 dual-stream 再 single-stream 的结构,使它在跨模态对齐和图像细节生成上都有很高容量。
-
采样路径更直接
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。