ChordEdit: One-Step Low-Energy Transport for Image Editing

8,300 字 · English 647 词 · 约 27 分钟阅读

论文信息 - 作者:Liangsi Lu(广东工业大学), Xuhang Chen(惠州学院), Minzhe Guo(广东工业大学), Shichu Li(深圳大学), Jingchao Wang(北京大学), Yang Shi(广东工业大学,通讯作者) - 发表:CVPR 2026 Oral - arXiv ID:2602.19083 - 项目主页:https://chordedit.github.io - 代码:随论文提供(ChordEdit/),基于 diffusers 实现


一、核心问题

单步(One-Step)文生图模型实现了实时图像合成,但为什么无法直接用于高质量的文本引导图像编辑?

以 SD-Turbo、SwiftBrush-v2、InstaFlow 为代表的单步生成模型通过蒸馏将多步扩散模型压缩为单步推理,实现了毫秒级图像生成。很自然的想法是:能否利用这些模型的能力,在单步内完成"根据文本指令修改图像"的编辑任务?

论文发现,现有的训练免(training-free)、反转免(inversion-free)编辑方法在多步模型中表现良好,但强制压缩到单步后会崩溃:物体严重畸变(编辑目标扭曲变形、面目全非),非编辑区域一致性丧失(背景和周围结构坍塌、出现伪影)。

图1:ChordEdit 编辑效果展示——基于 SD-Turbo 和 SwiftBrush-v2 的多种语义编辑示例

图 1:ChordEdit 基于 SD-Turbo(上两行)和 SwiftBrush-v2(底行)的跨类型编辑效果总览。TeX caption 明确说明,图中手写标签就是每组样例的目标语义变化。

根因分析

问题出在 naive editing field(简单漂移差分) 的本质上。传统训练免编辑器通过计算目标 prompt 与源 prompt 对应的 drift 场之差来构造编辑控制场:

$$\Delta v(x_t, t) = v(x_t, t, c_{\text{tar}}) - v(x_t, t, c_{\text{src}})$$

在多步扩散模型中 $\Delta v$ 被分成许多小步迭代应用,误差被分散和平均化。但单步模型通过对抗蒸馏学到了从噪声到图像的直接映射——文本→向量场映射高度非线性,源和目标各自由大振幅、方向剧烈变化的高能量场驱动。直接相减得到的 $\Delta v$ 是两个剧烈轨迹的算术残差:高能量、不稳定、大方差。在单步大步长($h=1$)积分下,这个 erratic 的控制场将显著局部截断误差一次性累积,导致轨迹严重偏离预期路径。

图2:单步 Simple Drift 编辑的失败模式

图 2:Simple Drift 在单步下的失败模式及 ChordEdit 修正。


二、核心思路与方法

2.1 背景:什么是动态最优传输(Dynamic OT)?

在讲 ChordEdit 的方法之前,先理解一个看似不相关的问题:最优传输(Optimal Transport)。

最优传输问的是:你有两堆"土"(源分布和目标分布),怎么用最小的力气把源分布铲到目标分布的位置?18 世纪的法国数学家 Monge 第一次提出这个问题。到了 2000 年,Benamou 和 Brenier 把这个问题改写成了一个"流体动力学"版本(Benamou–Brenier 公式),这是 ChordEdit 理论的起点。

Benamou–Brenier 动态 OT 公式:

$$\min_{\rho, u} \int_0^1 \int \frac{1}{2}\|u_t(x)\|^2 \rho_t(x) dx dt \quad \text{s.t.} \quad \partial_t \rho_t(x) + \nabla_x \cdot (\rho_t(x) u_t(x)) = 0$$

逐符号解读:

为什么 OT 天然倾向低能量场?

这个优化问题的目标函数直接惩罚 $\|u\|^2$(场的平方大小)。如果某个候选场 $u$ 在某个位置取值很大(能量高),即使它能把分布送到目标,也会因为 $\|u\|^2$ 大而被目标函数惩罚。所以 OT 的数学结构自动排斥高能量、高波动的场,偏爱平滑、小振幅的场——这正是 ChordEdit 想要的。

2.2 重新定义:编辑作为最优传输问题

ChordEdit 的核心洞察是:不要用向量算术(直接相减)来看编辑,而是从 Benamou–Brenier 动态 OT 的角度来建模——将编辑问题转化为:寻找将源分布传输到目标分布的最低能量向量场。

具体来说,将图像编辑形式化为:

这个视角与 naive 方法的根本区别是:

图3:三种编辑范式的编辑场稳定性对比

图 3:多步 vs 单步 Simple Drift vs ChordEdit 的编辑场行为对比。

2.3 从 OT 到 Chord Control Field —— 逐步推导

上面的 OT 公式很美,但有一个致命问题:理想的编辑场 $u_t$ 是我们不知道的,无法直接计算。我们只能通过操作生成模型来获取关于 $u_t$ 的部分信息。ChordEdit 的推导就是一步步用"能观测到的"量来逼近"想要但看不到的"$u_t$。

第一步:建立观测模型——我们能"看到"什么?

你有一个单步生成模型(比如 SD-Turbo)。给一张源图 $x_{\text{src}}$、一个 prompt $c$、一个时间 $t$,模型可以告诉你:如果把这张图加噪到 $t$ 时刻的水平,然后用 $c$ 条件去噪,噪声/速度/干净图的预测是什么。

用源 prompt 和目标 prompt 各跑一次,取差,就能了解"目标方向"和"源方向"的差异有多大。这就是可观测代理场的概念:它不需要知道真实的 $u_t$,只需要利用模型在两个 prompt 下的行为差异。

形式化地,定义可观测代理场(Observable Proxy Field):

$$\mathbf{R}(x_\tau, t) = \mathbb{E}_{z \sim K_t(\cdot|x_\tau)}[\mathcal{B}_t(Q(z, t, c_{\text{tar}}) - Q(z, t, c_{\text{src}}))]$$

逐符号解读这个看似复杂、实则直观的公式:

这个公式的整体意思:在源图上、时间 $t$ 处,用模型在目标 prompt 和源 prompt 下的输出差异(转换到速度域后)来近似"这个点应该往哪个方向移动"。

观测噪声假设:$\mathbf{R}(x_\tau, t) = u_t(x_\tau) + \varepsilon_t$,其中 $\mathbb{E}[\varepsilon_t] = 0$。即论文把可观测代理场看成“理想场 + 零均值扰动”。这是一种理论分析用的测量模型,目的是说明为什么对 $\mathbf{R}$ 做时间平滑可以降低高频噪声和能量;不是说实际模型输出一定严格满足无偏观测。

第二步:MAP 估计——如何从含噪观测得到平滑估计?

现在有了观测 $\mathbf{R}$,但它含噪声、能量高、不稳定。我们需要一种方法从含噪数据中提取一个"干净版本"。

一个自然的思路类似统计学里的正则化最小二乘 / MAP 估计:我们想要一个既接近新观测(数据保真度)、又不能偏离上一时刻估计太远(平滑先验)的折中解。具体做法是: 1. 在短时间窗口 $[t-\delta, t]$ 内,假设真实的 $u$ 近似不变($\delta$ 很小,比如 0.15)。 2. 构造一个代价函数(在 MAP 框架中等价于负对数后验概率),使"好"的 $u$ 值得分低,"差"的 $u$ 值得分高。 3. 找到使代价最小的 $u$——这就是 MAP 估计。

在短窗口 $[t-\delta, t]$ 内构建凸二次目标函数:

$$\Phi_t(u) = t\|u - \hat{u}_{t-\delta}\|^2 + \int_{t-\delta}^{t} \|u - \mathbf{R}(\xi)\|^2 d\xi$$

逐项解读这个代价函数:

为什么是凸二次? 因为平方函数 $\|u-a\|^2$ 就像抛物面(碗状)——"碗底"就是最优解。无论从哪个方向开始搜索,都会滑到同一个碗底。这在数学上意味着解是稳定且唯一的,不会因为初始猜测不同而得到不同答案。

第三步:求解——令导数为零

令 $\Phi_t(u)$ 对 $u$ 的导数(梯度)等于零。对于二次函数,这等价于求解一个线性方程组:

$$2t(u - \hat{u}_{t-\delta}) + 2\int_{t-\delta}^{t}(u - \mathbf{R}(\xi))d\xi = 0$$

展开整理(将含 $u$ 的项移到一边):

$$t \cdot u + \int_{t-\delta}^{t} u\,d\xi = t \cdot \hat{u}_{t-\delta} + \int_{t-\delta}^{t} \mathbf{R}(\xi)d\xi$$

左边 $t \cdot u + \delta \cdot u = (t+\delta)u$(因为 $\int_{t-\delta}^{t} u\,d\xi = u \cdot \delta$,窗口长度就是 $\delta$),因此:

$$u_t^\star = \frac{t}{t+\delta}\hat{u}_{t-\delta} + \frac{1}{t+\delta}\int_{t-\delta}^{t}\mathbf{R}(\xi)d\xi$$

直观解读:最优估计 $u_t^\star$ 是前一估计 $\hat{u}_{t-\delta}$ 和窗口内平均观测 $\frac{1}{\delta}\int\mathbf{R}$ 的加权平均(注意第二项是 $\frac{\delta}{t+\delta}$ 乘以窗口观测的均值)。这里的 $t$ 和 $\delta$ 不是任意经验权重,而是来自论文设定的二次 surrogate:$t$ 越大,递归先验项占比越高;$\delta$ 越大,新窗口观测的总权重越高。

第四步:因果一阶近似——让公式可计算

上面仍然有一个计算困难:$\int_{t-\delta}^{t}\mathbf{R}(\xi)d\xi$ 需要在 $[t-\delta, t]$ 的每一个时间点都查询模型——这在单步方法中不可行(那需要多步迭代)。

论文做了一个关键的实践近似——因果一阶近似:

代入整合得到 Chord Control Field:

$$\boxed{\hat{u}_t(x_\tau) = \frac{t \cdot \mathbf{R}(x_\tau, t-\delta) + \delta \cdot \mathbf{R}(x_\tau, t)}{t + \delta}}$$

逐项解读最终的 Chord 公式:

为什么 transport 仍算 1 NFE? 严格说,$\mathbf{R}(t)$ 和 $\mathbf{R}(t-\delta)$ 涉及两个时间点、源/目标 prompt 两种条件,一共是 4 个条件查询:[z_t(src), z_t(tar), z_{t-\delta}(src), z_{t-\delta}(tar)]。代码把这 4 个样本 concat 到同一个 batch 里做一次 UNet forward,所以 wall-clock 和 NFE 记作 1 次 transport,而不是顺序跑 4 次。

图4:2D 合成分布传输——Chord vs Naive

图 4:在 2D 合成分布上可视化三种传输行为,直观展示 Chord Field 的低能量、小偏差特性。

2.4 理论性质:为什么 Chord 场更稳?

上面的推导给出了 Chord Control Field 的公式,但还需要回答一个问题:凭什么说它比 naive 场稳定? 论文给出了一系列数学定理来回答这个问题。下面用通俗语言解释每一条的含义,详细证明在论文附录中。

(1) $L^2$ 动能收缩

$$\int_0^1 \|\hat{u}(t)\|^2 dt \le \int_0^1 \|\mathbf{R}(t)\|^2 dt$$

(2) $L^\infty$ 范数收缩(逐点最大值也不增加)

$$\|\hat{u}\|_\infty \le \|\mathbf{R}\|_\infty, \quad \|\partial_t\hat{u}\|_\infty \le \|\partial_t\mathbf{R}\|_\infty, \quad \|\nabla_x\hat{u}\|_\infty \le \|\nabla_x\mathbf{R}\|_\infty$$

(3) 更紧的 Euler 误差界

(4) 方差缩减(为什么 $n=1$ 就够)

2.5 Proximal Refinement(可选)

Transport 阶段保守优先(高 PSNR、保结构),可选择性追加 1 NFE 的语义精炼:

$$\text{prox}(x^{\text{pred}}, t_c, c_{\text{tar}}) = \mathcal{B}_{t_c} Q(x^{\text{pred}}, t_c, c_{\text{tar}})$$

通俗理解:transport 已经把结果推到目标语义附近,prox 是做最后的一步微调——代码里会把 $x^{\text{pred}}$ 用同一个噪声加到 $t_c=0.30$,再只用目标 prompt 做一次 predict-x0。论文也强调它不是 transport 的一部分,能量分析都在 prox 之前完成;prox 的作用主要是增强颜色、纹理、风格等目标语义。

图5:Proximal Refinement 定性消融

图 5:Proximal Refinement 的定性消融,编辑任务为蝴蝶翅膀 orange → green。

2.6 算法

Input:  x_src, c_src, c_tar, t(=0.90), δ(=0.15), λ(=1.00), t_c(=0.30)

1. û = (t·R(x_src, t-δ) + δ·R(x_src, t)) / (t + δ)     // 1 NFE, batch内并行4queries
2. x_pred ← x_src + λ·û                                 // Euler step
3. x_tar ← prox(x_pred, t_c, c_tar)                     // 1 NFE (optional)

三、模型适配:统一的 $\mathcal{B}_t$ 映射

ChordEdit 的模型无关性通过时间纯量线性映射 $\mathcal{B}_t$ 实现,不同参数化的模型只需不同的标量系数:

模型类型 输出 $Q$ $\mathcal{B}_t$ 系数 适用范围
噪声预测 $\hat{\epsilon}_\theta$ $-\dot{\alpha}(t)/(\alpha(t)\sigma(t))$ SD-Turbo 等
速度预测 $\mathbf{v}_\theta$ $I$(恒等) InstaFlow 等
$x_0$ 预测 $\hat{x}_0$ $\dot{\alpha}(t)/\sigma(t)^2$ Consistency models
$v$ 预测 $\hat{v}$ $-\dot{\alpha}(t)/\sigma(t)$ Rectified flow 变体
Score 预测 $\hat{s}$ $\beta(t)$ Score-based SDE

查询时间点($t=0.90$、$t-\delta=0.75$)远离 $\alpha(t)\to 0$ 的奇点,保证计算稳定。


四、实验与结果

4.1 设置

4.2 主要结果

图6:PSNR vs CLIP-Edited vs Runtime 三维对比散点图

图 6:ChordEdit 与单步/少步/多步方法的三维对比。横轴 PSNR↑(背景保真),纵轴 CLIP-Edited↑(语义对齐),圆点大小 = Runtime(越大越慢)。

完整定量表(PIE-bench,MSE 和 LPIPS 单位 $10^3$,粗体为列内 top-3):

类型 方法 PSNR↑ MSE↓ LPIPS↓ CLIP-W↑ CLIP-E↑ T-free I-free NFE↓ Runtime↓ VRAM↓
多步(≥30) DDIM+MasaCtrl 21.25 8.58 106.6 24.13 21.13 ✓ ✗ 100 55.20s 12272
DirectInv+PnP 21.43 8.10 106.3 25.48 22.63 ✓ ✗ 100 28.03s 9262
FlowEdit(SD3) 22.17 7.69 104.8 26.64 23.69 ✓ ✓ 33 7.22s 17140
少步(4) InfEdit 24.14 6.82 55.7 24.89 21.88 ✓ ✓ 4 1.41s 6502
InstantEdit 23.80 4.21 60.9 24.97 21.82 ✓ ✗ 8 1.30s 16270
单步 SwiftEdit(SBv2) 21.71 8.22 91.2 24.93 21.85 ✗ ✗ 2 0.54s 15060
ChordEdit(w/o prox) 23.89 5.05 88.4 24.97 21.87 ✓ ✓ 1 0.20s 6988
ChordEdit(SD-Turbo) 22.20 6.84 128.3 25.58 22.96 ✓ ✓ 2 0.38s 6988
ChordEdit(SwiftBrush-v2) 22.04 7.13 111.2 25.12 22.58 ✓ ✓ 2 0.38s 6988

核心发现:Transport-only(NFE=1)以 23.89 PSNR 取得单步方法最优背景保真,直接验证 Chord Field 稳定性。完整版(NFE=2)CLIP-Edited 22.96 领先所有单步方法,接近多步 FlowEdit(23.69),速度快 19×。VRAM 仅 6988 MiB,不到 SwiftEdit(15060 MiB)的一半。

图7:与多步/少步方法的编辑结果定性对比网格

图 7:多步/少步/单步方法的编辑结果逐行对比。第一列为原始输入,后面按 Multi-steps、Few-steps、One-step 分组。

4.3 模型无关性

T2I 模型 Naive PSNR↑ ChordEdit PSNR↑ Naive CLIP-E↑ ChordEdit CLIP-E↑
InstaFlow 22.05 23.05 (+1.00) 20.19 21.39 (+1.20)
SwiftBrush-v2 20.52 22.04 (+1.52) 21.06 22.58 (+1.52)
SD-Turbo 21.38 22.20 (+0.82) 21.96 22.96 (+1.00)

三种架构上一致 +0.8~1.5 dB PSNR 和 +1.0~1.5 CLIP-Edited 提升,证明方法跨模型鲁棒。


五、消融实验

5.1 Chord Control Field 稳定性分析

图8:能量和 PSNR 随积分步数 S 的变化——Chord vs Naive

图 8:编辑场能量定性可视化与编辑效果对比。图中每个案例都按“原图 → Naive 能量/结果 → Ours 能量/结果”组织,颜色条显示紫色代表高能量、浅橙色代表低能量。


(a) 离散 Benamou–Brenier 动能 vs 积分步数 S

(b) 编辑区域 PSNR vs 积分步数 S

(c) LPIPS vs CLIP-Edited 散点分布

(d) LPIPS vs CLIP-Edited Pareto 前沿线

图 9:Chord Control Field 稳定性与 Pareto 支配性的定量分析。蓝色 = naive($\delta=0$),红色 = ChordEdit($\delta=0.15$)。

5.2 噪声采样分析


(a) LPIPS vs CLIP-Edited Pareto 前沿(按噪声样本数 n 分层)

(b) n=1 下 20 种子的 CLIP-Edited(左)和 PSNR(右)分布直方图

图 10:噪声样本数对编辑稳定性的影响。实线 = ChordEdit($n=1,2,3,4$),虚线 = naive baseline。

图11:不同噪声样本数 n 的编辑结果定性对比

图 11:不同噪声样本数的 ChordEdit 输出($\delta=0.15$,其余参数固定)。图中列顺序是 noise=4,3,2,1,不是从 1 到 4。

5.3 Transport 与 Refinement 解耦

方法 Naive PSNR↑ Naive CLIP-E↑ Ours PSNR↑ Ours CLIP-E↑ NFE
w/o prox(纯 transport) 21.89 20.83 23.89 21.87 1
w/ prox(完整版) 21.38 21.96 22.20 22.96 2

Chord 场将 PSNR 从 21.89 提升到 23.89(+2.00 dB),同时 CLIP-Edited 也从 20.83 提升到 21.87——不是牺牲语义换 PSNR,而是全方位提升。加 prox 后 CLIP-Edited 进一步提升到 22.96(+1.09),代价 PSNR 降至 22.20(-1.69 dB),体现了语义强度 vs 背景保真的自然折中。

5.4 超参数分析

图12:平滑窗口 δ 的定性消融——从 δ=0 崩溃到 δ>0 立即稳定

图 12:固定其他参数、变化 $\delta$ 的编辑结果。三行分别是 sad → happy、tiger → cat、apple → cat。

图13:步长缩放 λ 的定性消融——作为直觉化编辑强度旋钮

图 13:固定其他参数、变化步长缩放 $\lambda$。三行分别是 mountain → volcano、milk → beer、woman → sculpture。


六、核心洞察与技术亮点

  1. 范式转换:从"向量算术"(drift 差分)到"最优传输控制"(低能量场),从数学本质解决单步不稳定性
  2. 极小实现代价:Chord Control Field 仅是两个时间点的可观测场的加权平均——零额外参数、零额外模块、零额外内存
  3. 理论-实验闭环:动态 OT → $L^2$ 收缩 → $L^\infty$ 收缩 → 误差收紧 → Pareto 支配的完整证明链
  4. 反直觉的 $n=1$ 发现:核平滑天然降方差,MC 多噪声平均变得冗余
  5. 分离式设计:Transport(结构保持)+ Refinement(语义增强)解耦,用户可按需选择
  6. 真正实时:0.38s / 6988 MiB,可在消费级 GPU 上运行交互式 demo

七、代码实现解读

7.1 项目结构

ChordEdit/
├── pipeline_chord.py    # 核心 pipeline:ChordEditPipeline 类(524 行)
│                        #   - 模型加载(from_local_weights)
│                        #   - VAE 编解码 + CLIP 文本编码
│                        #   - _u_estimate:Chord Control Field 计算
│                        #   - _run_edit:Euler transport 主循环
│                        #   - _pred_x0:Tweedie's formula 单步去噪
├── utils.py             # 工具(177 行):EditRecord、LocalEditDataset、
│                        #   load_yaml_config、first_param_point
├── app.py               # Gradio Web Demo(438 行):参数滑块 UI + 内置示例
├── run_pie_bench.py     # PIE-Bench 批量评测(452 行):CLI 参数覆盖、
│                        #   多进程安全导出、PIE 格式输出
├── images/              # 8 张内置示例图(001-008 子目录,含 meta.jsonl)
└── requirement.txt      # torch 2.5+cu124, diffusers, transformers, gradio 等

注意:ChordEdit 是训练免方法,代码中没有训练脚本,所有功能均为纯推理。

7.2 环境与依赖

项目 说明
Python 3.12
PyTorch 2.5.0+cu124
核心库 diffusers, transformers, torchvision
预训练模型 SD-Turbo(stabilityai/sd-turbo,HuggingFace)
模型文件 unet/、scheduler/、text_encoder/、tokenizer/、vae/(5 个 HF 子目录)
Web Demo gradio
评测数据 PIE-bench(需额外下载,目录含 annotation_images/ + mapping_file.json)
硬件 单张 NVIDIA Titan 24GB;VRAM 峰值 ~7GB

关键依赖版本(requirement.txt):

torch==2.5.0+cu124
diffusers          # UNet2DConditionModel, DDPMScheduler, AutoencoderKL
transformers       # CLIPTextModel, AutoTokenizer
gradio             # Web interactive demo
torchvision        # transforms, InterpolationMode
pyyaml, Pillow     # 配置解析, 图像 I/O
numpy, matplotlib, seaborn   # 论文图表绘制

7.3 推理完整流程

                用户输入                          ChordEditPipeline
         ┌──────────────────┐          ┌──────────────────────────────────┐
         │  source image     │          │                                  │
         │  source prompt    │─────────►│  __call__(image, src, tgt, cfg)  │
         │  target prompt    │          │                                  │
         │  edit_config      │          └──────────────┬───────────────────┘
         └──────────────────┘                         │
                                                      ▼
         ┌────────────────────────────────────────────────────────────────┐
         │  Step 1: 预处理(pixel → latent, text → embedding)              │
         │                                                                │
         │  PIL Image                                                     │
         │    │ _CenterSquareCropTransform()   居中裁剪为正方形             │
         │    │ transforms.Resize(512,512)     缩放                        │
         │    │ transforms.ToTensor()          归一化 [-1,1]               │
         │    ▼                                                           │
         │  pixel_values  ──► VAE.encode() ──► z_src (4×64×64 latent)     │
         │                                                                │
         │  source_prompt ──► CLIP tokenizer + text_encoder ──► e_src      │
         │  target_prompt ──► CLIP tokenizer + text_encoder ──► e_tgt      │
         └────────────────────────────┬───────────────────────────────────┘
                                      │
                                      ▼
         ┌────────────────────────────────────────────────────────────────┐
         │  Step 2: Chord Transport(_run_edit, 1 NFE)                    │
         │                                                                │
         │  t_grid = [0.90]        # n_steps=1, 单步                      │
         │                                                                │
         │  _u_estimate(x, e_src, e_tgt, noise, t=0.90, δ=0.15):         │
         │    ┌─────────────────────────────────────────────────────┐     │
         │    │ ⑴ 获取 schedule 参数                                │     │
         │    │   α_s, σ_s = _get_alpha_sigma(x, t_idx_s)           │     │
         │    │   α_prev, σ_prev = _get_alpha_sigma(x, t_idx_s0)    │     │
         │    │                                                     │     │
         │    │ ⑵ 构造含噪代理(共享噪声)                          │     │
         │    │   z_s    = α_s    * x + σ_s    * noise              │     │
         │    │   z_prev = α_prev * x + σ_prev * noise              │     │
         │    │                                                     │     │
         │    │ ⑶ Batch UNet 推理(1 NFE, 4 queries 并行)          │     │
         │    │   samples = concat([z_s(src), z_s(tar),             │     │
         │    │                     z_prev(src), z_prev(tar)])       │     │
         │    │   conds   = concat([e_src, e_tgt, e_src, e_tgt])     │     │
         │    │   ε_pred = unet(samples, timesteps, conds)           │     │
         │    │                                                     │     │
         │    │ ⑷ x₀ 预测(Tweedie's formula)                      │     │
         │    │   x̂₀ = (z - σ * ε_pred) / α                         │     │
         │    │                                                     │     │
         │    │ ⑸ 计算可观测代理场(x₀ 空间差分 + MC 平均)         │     │
         │    │   dv_s  = mean(x̂₀_tar@t    - x̂₀_src@t)              │     │
         │    │   dv_s0 = mean(x̂₀_tar@t-δ  - x̂₀_src@t-δ)            │     │
         │    │                                                     │     │
         │    │ ⑹ Chord Control Field(时间加权平均)               │     │
         │    │   û = (δ * dv_s + t * dv_s0) / (t + δ)              │     │
         │    └─────────────────────────────────────────────────────┘     │
         │                                                                │
         │  Euler step: x = x + step_scale * û   # λ=1.0                  │
         └────────────────────────────┬───────────────────────────────────┘
                                      │
                                      ▼
         ┌────────────────────────────────────────────────────────────────┐
         │  Step 3: Proximal Refinement(可选, +1 NFE)                     │
         │                                                                │
         │  if cleanup:                                                   │
         │    z_tc = α_tc * x + σ_tc * noise[0]     # 加噪到 t_c=0.30     │
         │    ε_pred = unet(z_tc, t_idx_tc, e_tgt)   # 目标 prompt 去噪    │
         │    x = (z_tc - σ_tc * ε_pred) / α_tc      # Tweedie's formula  │
         └────────────────────────────┬───────────────────────────────────┘
                                      │
                                      ▼
         ┌────────────────────────────────────────────────────────────────┐
         │  Step 4: 后处理(latent → pixel)                                │
         │                                                                │
         │  latent ──► VAE.decode() ──► clamp[-1,1] ──► [0,1] ──► PIL    │
         └────────────────────────────────────────────────────────────────┘

7.4 公式→代码映射

论文公式   $\hat{u}_t(x_\tau) = \frac{t\cdot\mathbf{R}(x_\tau, t-\delta) + \delta\cdot\mathbf{R}(x_\tau, t)}{t+\delta}$  在 _u_estimate 方法(pipeline_chord.py:428–486)中的逐行对应:

论文符号 代码位置 代码变量 / 逻辑
$x_\tau$(锚点) L:429 x_anchor(源图 VAE latent,torch.Tensor [1,4,64,64])
$t$(编辑时间) L:429 t_s(float,默认 0.90,按论文取接近噪声端的 t=1→0 方向)
$\delta$(平滑窗口) L:429 delta(float,默认 0.15)
$\alpha_t, \sigma_t$(noise schedule) L:430-431 _get_alpha_sigma() → alpha_s, sigma_s(来自 DDPMScheduler.alphas_cumprod)
$z \sim K_t(\cdot\|x_\tau)$(含噪代理) L:447-448 z_s = alpha_s * x_anchor + sigma_s * noise(共享噪声,$n$ 个样本)
$\textit{UNet}(z, t, c)$(模型推理) L:469-474 self.unet(sample=samples, timestep=timesteps, encoder_hidden_states=conds)
Tweedie's formula($\hat{x}_0$) L:476 x0_all = (samples - sigma_cat * noise_pred) / alpha_cat
$\Delta Q$($x_0$ 空间差分) L:478-481 dv_s = (x_tar_p_s - x_src_p_s).mean(dim=0)(除噪声维度取平均)
$\mathbf{R}(x_\tau, t)$ L:480 dv_s(当前时间 $t$ 的可观测代理场)
$\mathbf{R}(x_\tau, t-\delta)$ L:481 dv_s0(窗口前端 $t-\delta$ 的可观测代理场)
Chord Control Field L:483-486 (delta * dv_s + t_s * dv_s0) / (t_s + delta)

关键实现细节:

7.5 关键代码摘录

_u_estimate —— Chord Control Field 核心(pipeline_chord.py:428–486):

def _u_estimate(self, x_anchor, src_embed, edit_embed, noise, t_s, delta):
    batch, device = x_anchor.shape[0], x_anchor.device
    t_idx_s  = self._time_to_index(batch, t_s, device=device)
    t_idx_s0 = self._time_to_index(batch, max(0.0, t_s - delta), device=device)

    noises = noise if isinstance(noise, (list, tuple)) else [noise]
    alpha_s, sigma_s = self._get_alpha_sigma(x_anchor, t_idx_s)
    alpha_prev, sigma_prev = self._get_alpha_sigma(x_anchor, t_idx_s0)

    num_noises = len(noises)
    noise_stack = torch.stack(noises, dim=0)          # [n, 1, 4, 64, 64]
    x_anchor_b = x_anchor.unsqueeze(0).expand(num_noises, -1, -1, -1, -1)
    alpha_s_b = alpha_s.unsqueeze(0).expand(num_noises, -1, -1, -1, -1)
    alpha_prev_b = alpha_prev.unsqueeze(0).expand(num_noises, -1, -1, -1, -1)
    sigma_s_b = sigma_s.unsqueeze(0).expand(num_noises, -1, -1, -1, -1)
    sigma_prev_b = sigma_prev.unsqueeze(0).expand(num_noises, -1, -1, -1, -1)

    # 含噪代理(共享噪声)
    z_s    = alpha_s_b    * x_anchor_b + sigma_s_b    * noise_stack
    z_prev = alpha_prev_b * x_anchor_b + sigma_prev_b * noise_stack

    # 4 queries 拼接 → 1 次 batch forward
    samples = torch.stack([z_s, z_s, z_prev, z_prev], dim=1)
    samples = samples.reshape(num_noises * 4 * batch, *x_anchor.shape[1:])
    conds = torch.cat([src_embed, edit_embed, src_embed, edit_embed], dim=0)
    conds = conds.repeat(num_noises, 1, 1)
    timesteps = torch.cat([t_idx_s, t_idx_s, t_idx_s0, t_idx_s0], dim=0)
    timesteps = timesteps.repeat(num_noises)

    # UNet 推理
    noise_pred = self.unet(
        sample=samples, timestep=timesteps,
        encoder_hidden_states=conds, return_dict=False,
    )[0]

    # Tweedie's formula → x₀ 预测
    x0_all = (samples - sigma_cat * noise_pred) / alpha_cat
    x0_all = x0_all.reshape(num_noises, 4, batch, *x_anchor.shape[1:])
    x_src_p_s, x_tar_p_s, x_src_p_s0, x_tar_p_s0 = x0_all.unbind(dim=1)

    # x₀ 空间差分 + MC 平均
    dv_s  = (x_tar_p_s  - x_src_p_s ).sum(dim=0) / float(num_noises)
    dv_s0 = (x_tar_p_s0 - x_src_p_s0).sum(dim=0) / float(num_noises)

    # Chord Control Field
    denom = (t_s + delta)
    if denom <= 1e-6:
        return dv_s
    return (delta * dv_s + t_s * dv_s0) / denom

_pred_x0 —— Tweedie's formula(pipeline_chord.py:416–426):

def _pred_x0(self, x_anchor, timesteps, cond, noise):
    alpha_t, sigma_t = self._get_alpha_sigma(x_anchor, timesteps)
    z_t = alpha_t * x_anchor + sigma_t * noise           # 前向加噪
    noise_pred = self.unet(
        sample=z_t, timestep=timesteps,
        encoder_hidden_states=cond, return_dict=False,
    )[0]
    x0_pred = (z_t - sigma_t * noise_pred) / alpha_t      # Tweedie: x̂₀ = (z-σ·ε̂)/α
    return x0_pred

_run_edit —— Euler transport 主循环(pipeline_chord.py:488–523):

def _run_edit(self, x_src, src_embed, edit_embed, noise, params):
    if params["n_steps"] == 1:
        t_grid = [params["t_start"]]          # 默认 [0.90]
    else:
        t_grid = torch.linspace(               # 多步时均匀划分
            params["t_start"], params["t_end"],
            steps=params["n_steps"],
        ).tolist()

    x_curr = x_src
    for t_s in t_grid:
        u_hat = self._u_estimate(              # Chord Control Field
            x_curr, src_embed, edit_embed,
            noise, float(t_s), params["t_delta"],
        )
        x_curr = x_curr + params["step_scale"] * u_hat   # Euler step: x += λ·û

    if params["cleanup"]:                      # Proximal Refinement
        t_end_idx = self._time_to_index(x_src.shape[0], params["t_end"], device=device)
        x_curr = self._pred_x0(x_curr, t_end_idx, edit_embed, noise[0])

    return x_curr

__call__ —— 完整编辑入口(pipeline_chord.py:187–244):

@torch.no_grad()
def __call__(self, image, *, source_prompt, target_prompt,
             edit_config=None, seed=None, output_type="pil"):
    # 1. 预处理
    pixel_values = self._prepare_image_tensor(image)       # PIL → [-1,1] Tensor
    latents = self._encode_image_to_latent(pixel_values)   # VAE encode
    src_embed = self.encode_prompt([source_prompt])         # CLIP text → embedding
    tgt_embed = self.encode_prompt([target_prompt])

    # 2. Chord transport + optional refinement
    x0_pred = self._run_edit(
        x_src=latents, src_embed=src_embed, edit_embed=tgt_embed,
        noise=self._prepare_noise_list(latents, seed, cfg["noise_samples"]),
        params=edit_params,
    )

    # 3. 后处理
    decoded = self._decode_latent_to_image(x0_pred)         # VAE decode → [0,1]
    images = self._tensor_to_pil(decoded) if output_type=="pil" else decoded
    return ChordEditPipelineOutput(images=images, latents=x0_pred)

7.6 PIE-Bench 评测流程

run_pie_bench.py 提供标准化的批量评测入口(pipeline_chord.py 中无评测逻辑,评测 PSNR/CLIP/LPIPS 需用户按 PIE-bench 官方流程自行计算):

usage: python run_pie_bench.py --model-root /path/to/sd-turbo --pie-root /path/to/pie_bench

PIE-Bench 目录结构(需自行准备):
pie_bench/
├── annotation_images/      # 700 张原始图像(子目录按 PIE 官方命名)
│   ├── 0/
│   │   ├── 0_0_0.png      # 编辑前原图
│   │   └── ...             # 变体、mask 等
│   └── ...
└── mapping_file.json       # 映射元数据:{sample_id: {image_path, original_prompt,
                            #   editing_prompt, editing_instruction, mask_path}}

评测脚本的数据流:

CLI args / YAML config
  │
  ├─► load_pipeline_config()     解析 editor.seed, editor.params_grid
  ├─► apply_cli_overrides()      --noise-samples 等 CLI 覆盖
  ├─► load_pie_records()         解析 mapping_file.json → List[PieRecord]
  │
  └─► for each PieRecord:
        ├─► Image.open() → RGB
        ├─► pipeline(image, src_prompt, tgt_prompt, seed)
        └─► save_prediction() → output/<method>/annotation_images/<rel_path>

7.7 Web Demo 交互设计

app.py 基于 Gradio 构建交互式编辑界面(pipeline_chord.py 无内置 UI):

┌─ Left Panel ─────────────────────┐  ┌─ Right Panel ────┐
│  [Source Image]  (upload/drag)   │  │  [Editing Result] │
│  [Source Prompt] (textarea)      │  │                   │
│  [Target Prompt] (textarea)      │  │                   │
│                                  │  │                   │
│  Parameters:                     │  │                   │
│  Seed: [42]           (number)   │  │                   │
│  n_samples: [1]       (1-16)     │  │                   │
│  step_scale: [1.0]    (0.1-5.0)  │  │                   │
│  t_start: [0.90]      (0.01-1.0) │  │                   │
│  t_end: [0.30]        (0-0.99)   │  │                   │
│  t_delta: [0.15]      (0-0.5)    │  │                   │
│                                  │  │                   │
│  [Run Edit] (button)             │  │                   │
└──────────────────────────────────┘  └───────────────────┘

Examples: 8 张内置示例图,点击自动填充 src prompt + tgt prompt
特殊玩法:将 t_delta 拖到 0 → 观察 naive baseline 崩溃,直观感受 Chord 场作用

八、局限性

  1. 极端编辑边界:主要在 PIE-bench 10 类常规编辑上评估,未测试大幅视角变化、物体姿态重构等极端场景。底层 backbone 仍是天花板
  2. 超参数 trade-off:$\delta$ 非零即稳定,但 $t$、$\lambda$、$t_c$ 的调节需理解 trade-off 空间
  3. LPIPS 偏高:完整版 LPIPS 128.25($10^3$),prox refinement 的语义增强不可避免引入感知差异
  4. Backbone 依赖:编辑质量受限于底层模型在特定概念上的生成能力
  5. 安全风险:可用于 deepfake(面部属性修改、人物替换),开源应配套使用条款

九、关键概念速查

概念 英文 简洁定义
Chord Control Field CCF $\hat{u}=K_\delta*\mathbf{R}$,两个时间点的可观测场的加权平均
Naive editing field — $\Delta v = v(c_{\text{tar}}) - v(c_{\text{src}})$,无正则化漂移差分
$\mathbf{R}$ Observable proxy 锚点处模型源/目标差分输出的共享噪声 MC 期望
Benamou–Brenier OT Dynamic OT 在连续方程约束下最小化传输动能的动态最优传输
Proximal Refinement Prox step 目标 prompt 对 transport 结果的单步 $x_0$ 预测
$\delta$ Smoothing window $\delta=0$ 退化为 naive,$\delta>0$ 开启平滑
$\mathcal{B}_t$ Comparison-domain map 参数化→velocity 域的时间纯量线性映射
$L^2$ contraction 能量收缩 $\int\|\hat{u}\|^2 \le \int\|\mathbf{R}\|^2$,严格当 $\delta>0$
$n=1$ Single-noise 默认单噪声设置,核平滑已天然降方差