FlowInOne: Unifying Multimodal Generation as Image-in, Image-out Flow Matching

三种生成范式对比:左 T2I 只用文本编码器条件化 LDM;中 TI2I 需要文本+图像双编码器联合条件;右 FlowInOne 把所有条件统一成视觉输入,单个视觉编码器 → 流匹配模型 → 图像解码器
Fig. 1 — 三种生成范式对比。左:传统 T2I 只用文本编码器条件化 LDM;中:TI2I 需要文本+图像两个编码器联合条件;右(本文):把所有条件(文本/布局/指令)统一渲染成视觉输入,形成极简的「图进-图出」流水线——单个视觉编码器 → 流匹配模型 → 图像解码器。没有文本编码器、没有跨模态桥、没有任务专属分支。

1. 出发点 (Motivation)

多模态生成长期被一条文本主导的假设统治:语言编码意图,视觉执行意图。从 DDPM/LDM/DiT 到各种 T2I,都靠语言 embedding 作为核心条件源。这带来一个根本性的不对称:语言能控制视觉,但视觉自己不能推理、不能生成。理解、编辑、生成因此被劈成三块,很难在一个模型里统一。

近来有一批 vision-centric 工作(把文字渲染成像素再处理,如 "Language modelling with pixels")证明:视觉模态本身足够表达,可以当多模态理解的统一底座。但它们停在感知层面,把 vision-first 的生成潜力留白了。于是本文问了个尖锐的问题:

能不能造一个模型,完全在视觉空间里既推理又生成?

流匹配(Flow Matching)给了原则性答案。相比扩散靠"渐进去噪",FM 直接学一个分布到另一个分布的连续速度场,采样更快、优化更稳,且允许非高斯源分布——只要源和目标同构(isomorphic)。这恰好能把"感知"和"生成"统一到一个确定性传输原理下。

FlowInOne 的做法:把文本、布局、编辑指令全部渲染进一张输入画布 \(I_v\)(visual prompt),它就是流的起点状态;模型学一个连续传输过程,把这个状态演化成目标图。这样一举消掉了三样东西——噪声调度、扩散采样、任务专属条件头。

三个贡献:① 把多模态生成重构成 vision-centric 的「图进图出」范式,删掉文本编码器和模态桥;② 提出 FlowInOne,用统一流匹配把多模态变换建模成共享潜空间里的连续演化;③ 构建 VisPrompt-5M(500 万视觉提示对)和 VP-Bench 评测基准。

2. 方法 (Method)

FlowInOne 有三个环环相扣的部件:(a) 把一切指令渲染进画布 + Janus 视觉编码器提特征;(b) text-image VAE 把统一视觉 token 映射成源潜变量 \(z_0\);(c) 一个 DiT 变体用 Dual-Path Spatially-Adaptive Modulation 在共享潜空间里做流匹配,从 \(z_0\) 演化到目标图潜变量 \(z_1\)。

FlowInOne 架构:输入带视觉指令的图 → Janus 视觉编码器 → Text-Image VAE 产生 ZTI;目标图/输入图经 Image VAE Encoder;DiT 块内 self-attention 后接 dual-path:cross-attention(q 来自状态,k/v 来自输入图潜) → 拼接 → adaptive gating network 产 α → 门控注入 → MLP → ZI → Image VAE Decoder
Fig. 3 — FlowInOne 架构。带视觉指令的输入图经 Janus 视觉编码器 → Text-Image VAE 得到源潜变量 $Z_{TI}$(冻结的 Image VAE 编码目标图得 $Z_I$)。DiT 块内先 self-attention,再走 dual-path:纯文生图时旁路右侧结构分支;编辑时用 cross-attention(query 来自当前状态、key/value 来自输入图潜)取源图结构,经 adaptive gating network 产 token 级权重 α 后有选择地注入。全程冻结的 Image VAE 编解码。

2.1 流匹配预备:线性插值 + 恒定速度

流匹配把生成建模成从源分布 \(p_0\) 到目标 \(p_1\) 在 \(t\in[0,1]\) 上的连续传输。训练时在样本对 \((z_0, z_1)\) 之间构造可微概率路径 \(z_t\),直接给出监督用的真值速度:

\[z_t = t z_1 + \left(1 - (1-\sigma_{\min})t\right) z_0, \qquad v_t^\star = \frac{\partial z_t}{\partial t} = z_1 - (1-\sigma_{\min}) z_0\]

—— 翻译:在起点 $z_0$ 和终点 $z_1$ 之间拉一条(近似)直线,$t$ 从 0 走到 1 就从源滑到目标。这条线的斜率(速度)几乎是常数 $z_1 - z_0$($\sigma_{\min}$ 是个 $10^{-5}$ 量级的小修正)。网络只要学会在任意 $(z_t, t)$ 预测这个速度,推理时解 ODE 积分回去即可。比扩散"每步猜噪声"直白得多。

关键区别:FlowInOne 显式定义了一个非高斯源分布——\(z_0\) 是统一视觉指令经视觉编码器 + text-image VAE 抽出的潜状态,\(z_1\) 是目标图潜。因为两者在共享空间里同构,推理就是从 \(t{=}0\) 到 \(t{=}1\) 解 ODE \(\frac{dz_t}{dt}=v_\theta(z_t,t)\),把指令确定性地演化成目标图。

代码里 psi 就是上面的插值、Dt_psi 就是真值速度,一一对应:

repo/diffusion/flow_matching.py:L309-L322 — 流匹配的概率路径 psi(=z_t)与真值速度 Dt_psi(=v*)

def psi(self, t, x, x1):        # x=z0(源/noise), x1=z1(目标)
    t = self.expand_t(t, x)
    return (t * (self.sigma_min / self.sigma_max - 1) + 1) * x + t * x1
    #      = (1 - (1-σmin)t) · z0  +  t · z1        ← 正是 Eq.1 的 z_t

def Dt_psi(self, t, x, x1):
    return (self.sigma_min / self.sigma_max - 1) * x + x1
    #      = z1 - (1-σmin) · z0                      ← 正是 Eq.1 的 v*

训练损失是三项之和:流匹配 MSE + text-image VAE 的 KL + CLIP 对比损失(用来对齐视觉/文本特征)。注意 KL 被改成了一个更"激进拉向标准正态"的变体:

repo/diffusion/flow_matching.py:L268-L303 — 组合损失:FM MSE(多尺度) + 魔改 KL + CLIP 对比

# 魔改 KL:把 mu→0 的惩罚从二次改成六次 [(0.3*mu)**6],更"温柔"地约束均值
kld_loss = -0.5 * torch.sum(1 + log_var - (0.3 * mu) ** 6 - log_var.exp(), dim=1)
loss_mlp = recons_loss + kld_loss * 1e-2          # recons_loss 其实是 CLIP 对比损失

x_noisy        = self.psi(t, x=noise, x1=x_start)      # z_t
target_velocity = self.Dt_psi(t, x=noise, x1=x_start)  # v*
prediction = nnet(x_noisy, log_snr=log_snr, image_latent=image_latent,
                  use_cross_atten_mask=use_cross_atten_mask)   # ← 双路开关在此
target = multi_scale_targets(target_velocity, levels=len(prediction), scale_correction=True)
loss_diff = sum(coeff * self.mos(pred - target[pred.shape[-1]])
                for pred, coeff in check_zip(prediction, loss_coeffs))   # 多尺度 FM 损失
loss = loss_diff + loss_mlp

2.2 统一处理:把文字当成视觉几何

离散语言符号和连续视觉纹理天然异质,在流匹配里对齐很难。FlowInOne 的范式转变是:把文本指令和各种视觉线索直接渲染到图像画布上,让空间布局和结构先验被原生保留,不再需要复杂的跨模态对齐模块。

对这张统一图 \(I_v\),用 Janus-Pro-1B 的视觉编码器(SigLIP ViT)抽 patch 级语义特征,再经 MLP projector 投到目标嵌入空间:

\[X_{\text{fuse}} = \mathrm{MLP}(\mathrm{SigLIP}(I_v)) \in \mathbb{R}^{N\times D}\]

—— 翻译:一张"写满指令的图"进去,出来一串 patch token,每个 token 同时编码了"这块区域画了什么"和"这里写了什么字/画了什么箭头"。文字不再走 tokenizer,而是作为图像几何的一部分被视觉编码器看见。

然后 text-image VAE 不直接预测源潜,而是参数化一个分布再采样源状态(这是把非高斯源接进流匹配的关键):

\[q_\psi(z\mid X) = \mathcal{N}\!\left(\bar\mu_X,\ \mathrm{diag}(\bar\sigma_X^2)\right),\qquad z_0 \sim q_\psi(z\mid X)\]

—— 翻译:视觉编码器给的特征 $X$ 先被压成一个高斯分布的均值和方差,再从里面采一个 $z_0$ 当流的起点。对称地,冻结的 Image VAE 把目标图编码成 $z_1$。两者在同一个潜空间里,流匹配才能直接在它们之间连线。

2.3 Dual-Path Spatially-Adaptive Modulation:本文真正的"聪明处"

问题:视觉编码阶段有信息压缩,初始潜 \(Z_{TI}\) 常抓不住源图的细粒度结构。纯文生图无所谓(本来就不该保留结构),但图像编辑必须保住背景、只改指定区域。于是作者设计了一个能按任务类型切换计算路径的机制。

设第 \(l\) 层隐状态 \(H^{(l)}\),先过 self-attention 拿全局上下文:

\[\tilde H^{(l)} = \mathrm{LayerNorm}\!\left(H^{(l)} + A_{\text{self}}(H^{(l)})\right)\]

编辑分支:用 Image VAE 把源图 \(I_{src}\) 编码成 \(z_{src}\),reshape 成参考序列 \(S\),以 \(\tilde H^{(l)}\) 为 Query、\(S\) 为 Key/Value 做交叉注意力,取出结构增量 \(\Delta H_{\text{struct}}\):

\[\Delta H_{\text{struct}} = \mathrm{Softmax}\!\left(\frac{(\tilde H^{(l)} W_Q)(S W_K)^\top}{\sqrt{d_k}}\right)(S W_V)\]

然后是核心:一个轻量门控网络,把当前状态和结构增量拼起来,预测一个 token 级、各向异性的权重向量 \(\Lambda\in[0,1]^N\)——它能在像素级区分"属于背景、要严格保留"和"属于编辑区、要重建":

\[\Lambda = \sigma\!\left(\mathrm{MLP}_\theta\big[\tilde H^{(l)}\ \|\ \Delta H_{\text{struct}}\big]\right)\]

—— 翻译:把"我现在生成到哪了"和"源图这里长啥样"两串特征并排喂给一个小 MLP,它给每个 token 打一个 0~1 的分。分高=这块要照抄源图结构,分低=这块要按指令重画。这就是"空间自适应":不同位置注入源图信息的力度不一样。

最后用一个二值任务指示器 \(I_{\text{edit}}\in\{0,1\}\) 控制这条结构分支通不通:

\[H_{\text{out}}^{(l)} = \tilde H^{(l)} + I_{\text{edit}}\cdot\left(\Lambda \odot \Delta H_{\text{struct}}\right)\]

—— 翻译:纯文生图时 $I_{\text{edit}}{=}0$,整个结构注入项归零,模型严格跟着文本语义演化,不被源图"污染";编辑时 $I_{\text{edit}}{=}1$,门控 $\Lambda$ 精细地把源图结构掺进来。一个开关 + 一个门控,统一了"从无到有生成"和"改一张已有图"两种本质不同的任务。

代码里 CrossAttention 干的正是 Eq.4 + Eq.5——交叉注意力算出 ca_output,再把它和输入拼起来过 weight_mlp(末尾就是 nn.Sigmoid())得到 token 级门控 ca_weights:

repo/libs/model/dimr.py:L198-L248 — 交叉注意力 + token 级 Sigmoid 门控(Eq.4 / Eq.5)

self.weight_mlp = nn.Sequential(              # 门控网络:2C → C/4 → 1 → Sigmoid
    Linear(config.dim * 2, config.dim // 4),
    nn.GELU(),
    Linear(config.dim // 4, 1),
    nn.Sigmoid(),                             # → Λ ∈ [0,1]^N,每个 token 一个标量
)

def forward(self, x, image_latent, pos=None):
    q  = ...(x)                                # Query 来自当前生成状态 H̃
    k, v = ...(image_latent)                   # Key/Value 来自源图潜 S
    atten_output = F.scaled_dot_product_attention(q, k, v, attn_mask=None)  # ΔH_struct
    ca_output = self.dense(atten_output...)
    combined  = torch.cat([x_input, ca_output], dim=-1)   # [H̃ ‖ ΔH_struct]
    ca_weights = self.weight_mlp(combined)     # Λ = σ(MLP[·])
    return ca_output, ca_weights

而 Eq.6 的"开关 + 门控注入"在 TransformerBlock 里:use_cross_atten_mask 就是 \(I_{\text{edit}}\) 的取反——对纯生成任务把交叉注意力整支跳过:

repo/libs/model/dimr.py:L283-L297 — Eq.6:用 use_cross_atten_mask 切换双路,门控相乘注入

if self.use_cross_attention and image_latent is not None and not should_skip_cross_attn:
    ca_output, ca_weights = self.block1_cross(x, image_latent, pos)
    x = x + self.dropout(ca_output * ca_weights)   # H̃ + Λ ⊙ ΔH_struct
elif should_skip_cross_attn:
    pass                                            # I_edit = 0:结构分支彻底旁路
x = x + self.dropout(self.block2(x))                # MLP

推理侧对上了:use_cross_atten_mask 对 class2image/text2image 置 True(旁路结构分支),其余编辑任务保持交叉注意力活跃——这正是论文"纯生成 \(I_{\text{edit}}{=}0\)、编辑 \(I_{\text{edit}}{=}1\)"主张的代码实证:

repo/i2i.py:L205-L233 — 推理:按任务名切换双路;image_latent 是输入图的 VAE 潜(交叉注意力的 K/V 源)

mask_list = [('class2image' in parts or 'text2image' in parts) for parts in ...]
use_cross_atten_mask = torch.tensor(mask_list, dtype=torch.bool)  # 纯生成 → 旁路 CA
...
input_moments = autoencoder(batch_tensors_vae, fn='encode_moments')
input_latent  = autoencoder.sample(input_moments)     # 源图潜 = 交叉注意力的 K/V
samples = ode_fm_solver_sample(nnet, context=contexts, image_latent=input_latent,
                               use_cross_atten_mask=use_cross_atten_mask)

3. 结论 (Key findings)

数据集 VisPrompt-5M:8 类任务、共 500 万对(Iv, I⋆)。占比:文生图 42.24%、text-in-image edit 36.39%、class-to-image 15.58%、visual marker edit 4.69%、force understanding 0.61%、trajectory 0.19%、text bbox edit 0.45%、doodles edit 0.02%。每个样本把所有文本/空间指令直接嵌进输入画布,消除辅助文本通道、强制几何对齐。

VisPrompt-5M 八类任务示例网格:text-in-image edit(颜色/复合/去物/表情/关系/inpainting/canny/depth/restoration)、visual marker edit、textbbox edit、trajectory、class-to-image、force、text-to-image、doodles edit,右下角是各类占比环形图
Fig. 2 — VisPrompt-5M 覆盖 8 类图进图出任务,从基础文生图到复合编辑再到物理感知指令跟随(力/轨迹)。所有操作意图都以视觉文本/箭头/框/涂鸦的形式**显式渲染在输入画布上**。右下环形图为各类占比。

主结果(VP-Bench,四类评委的总成功率 Total):1.2B 的 FlowInOne 在 Gemini3/GPT5.2/Qwen3.5/人工 下分别拿到 54.0% / 39.2% / 50.3% / 44.9%,全面碾压所有开源基线(OmniGen2 ~23–25%、FLUX.1-Kontext ~24–27%、Qwen-IE-2509 ~23–28%),并逼近商业模型 Nano Banana(在 GPT5.2 下反超,其余小幅落后)。

七列定性对比(Inputs/Kontext/OmniGen2/Qwen-Image-Edit/Nano Banana/FlowInOne/Ground Truth),五行任务:doodle、力、text bbox(橙汁)、轨迹(小车)、visual marker(橙子换柠檬)
Fig. 4 — 五类任务定性对比。基线即便喂了 Qwen3-VL 扩写的详细文本提示,仍常把箭头/涂鸦/框误解成场景元素或违反空间约束;FlowInOne 直接读统一画布,能把箭头转成物理合理的运动、遵守 bbox 大小位置、并保住背景一致。

分维度看(每维满分 5):FlowInOne 在空间精度(Spatial Precision)上三个评委全场最高(3.42 / 3.24 / 3.30),指令忠实度和内容一致性也很强;唯一明显短板是**视觉真实感(Visual Realism)**低于 Nano Banana(这是 1.2B 小模型 + 256×256 分辨率的代价)。

定量指标(Table 3):FlowInOne 的 DINOv3 方向相似度均值 48.7% 超过 Nano Banana 的 47.3%——即细粒度空间/物理结构变化的准确性是全场第一,尤其在力/轨迹理解和 text-bbox 编辑上优势明显。CLIP-IQA/CLIP Score 显著超所有开源基线、接近商业模型。

关键消融:① 压缩方式——MLP+MLP(序列长和特征维都用 MLP 投)最好(18.12%),简单截断会丢边缘信息;② 调制方式——无交叉注意力 18.12% → 加双路交叉注意力 21.40% → Dual-Path SAM 23.1%,证明自适应门控确实在动态平衡一致性与指令跟随;③ 训练策略——联合训练 47.8% 碾压两阶段 29.1%,两阶段会灾难性遗忘,联合训练逼模型在同一视觉流空间里同时学语义生成+几何变换+物理规律。

4. 实现细节 (Implementation notes)

  1. 建立在 CrossFlow 之上。1.2B 的 FlowInOne 从 CrossFlow(直接把文本 embedding 流匹配到图像、无噪声无 cross-attn 的工作)初始化,在其 Transformer 块上新增交叉注意力层。这解释了为什么它能用相对小的模型和算力起步——继承了 CrossFlow 的流匹配先验。

  2. 三个 VAE / 编码器,冻结与否要分清。Janus-Pro-1B 视觉编码器(SigLIP ViT)抽统一图特征;text-image VAE 可训练(把视觉 token 参数化成源分布 \(z_0\));Image VAE(来自 LDM)全程冻结——既编码目标图 \(z_1\)(训练监督),又编码源图 \(z_{src}\) 供交叉注意力当 K/V(推理见 i2i.py:L224)。

  3. 训练配置:256×256 分辨率、240k 步、WebDataset 均衡采样、组合损失(FM + KL + CLIP 对比)。消融为省算力用 100k 步、batch 512。全部对齐预训练潜空间——序列长度和特征维必须匹配 CrossFlow 的潜空间,否则先验用不上。

  4. 魔改 KL 是个不显眼但实在的细节。代码把标准 KL 里 \(\mu^2\) 的均值惩罚换成了 \((0.3\mu)^6\)(flow_matching.py:L268),注释说是"让 mu→0 更温柔、var→1"。这类偏离教科书的小改动在正文里没提,是交叉读代码才能捞到的。

  5. 多尺度流匹配损失。预测不是单一分辨率,而是 multi_scale_targets 产生多个尺度、各配一个 loss_coeff 加权(flow_matching.py:L296-L300)——继承自 DIMR 风格的多尺度 DiT,正文同样一笔带过。

  6. CFG 的实现:训练时按 cfg_indicator 概率把一批样本的条件"错位打乱"制造 null 条件(flow_matching.py:L276-L285),推理用 unconditional_guidance_scale 做 classifier-free guidance。

  7. 评测用了 2026 年的前沿 VLM 当评委(Gemini3 / GPT5.2 / Qwen3.5),外加 10 人交叉复核 250 个分层随机样本。因为 FID 对复杂视觉指令无能为力,作者转向"四条标准同时满足才算成功"的 VLM+人工 pass rate,并为公平把画布里的文字指令手动抽出来补给 VLM。

论文 vs 代码:一致性高,无重大出入。Eq.1/4/5/6 都能在 flow_matching.py 和 dimr.py 里找到逐行对应,双路开关 use_cross_atten_mask 的语义(纯生成旁路、编辑激活)与正文 \(I_{\text{edit}}\) 完全吻合。唯一"正文没细说、代码才有"的是魔改 KL、多尺度损失、CFG 错位打乱这几处工程细节。

5. 批判性总结 (Critical assessment)

Strengths

Limitations / open questions

When to use / not use

延伸阅读 (Further reading)

交叉验证比较(Cross-validation against similar work) —— FlowInOne 的"统一"和"vision-centric"主张,和本仓另外几篇的路线正好构成对照:

工作 核心结论 关键观察 与本文的异 / 同
本文 (FlowInOne) 把文本/布局/指令全渲染成视觉输入,单个流匹配模型「图进图出」即可统一生成+编辑 vision-centric 在空间/物理精度上超文本条件管线;编辑是甜区、纯 T2I 是短板 —
boogu-image-2026 用 1/10 数据也能做「统一生成+编辑」大模型 走的仍是文本+图像条件的主流范式,靠数据/架构效率取胜 同:都追求"一个模型统一生成与编辑"。异:Boogu 保留文本条件桥,FlowInOne彻底删掉文本编码器、把条件视觉化
jit-2025 何恺明 JiT:朴素 ViT 直接在像素空间做扩散,x-预测在流形假设下本质更优 质疑"必须在 latent + 复杂调度里做生成" 同:都在"简化生成管线、挑战既有范式"上同向。异:JiT 简化的是去噪空间(像素 vs latent),FlowInOne 简化的是条件模态(视觉 vs 文本)
asymflow-2026 秩-非对称速度参数化,把 latent 流匹配 lift 到像素空间 流匹配的速度场参数化方式直接影响能否跨空间迁移 同:都基于流匹配、都在"源/目标空间同构"上做文章。异:AsymFlow 改的是速度参数化,FlowInOne 改的是源分布的来源(视觉指令而非噪声)

分歧的可能成因:根子在**"统一"统一的是哪一层**。Boogu 统一的是任务接口(仍靠文本条件),FlowInOne 统一的是模态本身(全部视觉化)——所以后者在空间控制上赢、在纯文本生成上亏。而 JiT/AsymFlow 关心的是生成过程(去噪空间、速度参数化)而非条件来源,与 FlowInOne 正交:理论上可以把 FlowInOne 的视觉条件接到 JiT 的像素扩散或 AsymFlow 的速度参数化上,是互补而非竞争。

相关工作(外部):CrossFlow(直接文本→图像流匹配,本文的初始化底座)、"Language modelling with pixels"(文字渲染成像素的 vision-centric 感知先驱)、Janus-Pro(视觉编码器来源)、OmniGen2 / FLUX.1-Kontext / Qwen-Image-Edit(编辑基线)、Nano Banana(商业对照)。

讨论 / Comments

评论托管在本仓库的 GitHub Discussions, 需 GitHub 账号。