对应 Maps to:CMU 10-423 / 10-623 / 10-723 Generative AI(Spring 2026;Matt Gormley、Aran Nayebi),Lecture 14 · Mon 9-Mar · Cross-Attention / Diffusion Transformer / Prompt-to-Prompt(slides:lecture14-ldm-dit-p2p)。 阅读材料:Rombach et al. (2022), High-Resolution Image Synthesis with Latent Diffusion Models(LDM);Ho & Salimans (2022), Classifier-Free Diffusion Guidance;Peebles & Xie (2023), Scalable Diffusion Models with Transformers(DiT);Hertz et al. (2022), Prompt-to-Prompt Image Editing with Cross Attention Control。 依据说明:本讲按原课第 14 讲公开的 slides 展开:条件图像生成的几类任务与图像编辑;LDM 回顾(自编码器、提示模型、潜空间里的 DDPM、“怎样让均值以提示为条件”);缩放点积注意力与交叉注意力(翻译的例子、LDM 里的交叉注意力、放在 U-Net 里的 Transformer 层);无分类器引导(动机、公式、算法 1、引导强度的例子);扩散 Transformer(时间线、ViT 回顾、DiT 结构、四种注入条件的方式与 adaLN-Zero、GFLOPs 与 FID、补丁大小、三条规模结论);Prompt-to-Prompt(固定种子与掩码编辑两种基线、关键想法、注意力替换的时机、形状不一致时的对齐、算法 1、几类编辑)。所有实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture14/(5 个脚本 + 1 个模块;第一次运行要训练 9 个模型,01约 9 分钟、03约 17 分钟、04约 15 分钟、05约 16 分钟;之后都在 2–8 分钟) 预计阅读时间:3 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲,你应该能够:
- 写出交叉注意力的公式,说清楚查询、键、值各来自哪里、注意力矩阵是什么形状、每一行是什么分布;说明它在 LDM 的去噪网络里放在哪里。
- 从贝叶斯公式推出无分类器引导 ,解释 w 为什么同时提高”听话程度”、降低多样性;说出训练时要做什么、采样时多花多少计算。
- 描述 DiT 的结构(patchify、adaLN-Zero、unpatchify),会算一个 DiT 的 GFLOPs,回答”补丁从 4 降到 2,计算量增加多少”。
- 说出 DiT 的三条规模结论,并能讲清楚我们的小实验复现了哪些、没复现哪些。
- 描述 Prompt-to-Prompt 的算法:为什么原提示和新提示要从同一个噪声出发、为什么只在前一段步数里替换注意力图、提示长度不同时怎样对齐;知道它成立的前提是什么。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 2 讲:缩放点积注意力、多头注意力 | 视频扩散(第 25 讲) |
| 第 5 讲:ViT;第 7、8 讲:DDPM、DDIM、预测噪声 ε | 真实图像的编辑(要先把真图”反演”成噪声:DDIM inversion、null-text inversion) |
| 第 12 讲:带描述的形状世界、KL 自编码器、潜空间扩散 | Q-Former、规模定律(第 15 讲) |
0.3 本讲地图 Map
§1 条件图像生成与"用文字编辑图像"
§2 LDM 回顾:怎样让去噪网络以提示为条件?
§3 交叉注意力 ← 代码 01
§4 无分类器引导 ← 代码 02
§5 扩散 Transformer(DiT) ← 代码 03、04
§6 Prompt-to-Prompt ← 代码 05
§7–11 小结、误区、练习、延伸阅读、下一讲
本讲所有实验都在第 12 讲的带描述的形状世界里:每张 28×28 的图有形状、大小、上下、左右 4 个属性,描述有 4 种说法;图像先用第 12 讲的 KL 自编码器压成 4×7×7 的潜变量,扩散模型在潜空间里训练。训练时去掉 4 种属性组合,用来测”没见过的组合”。评估用两把尺子:用形状拟合读出 4 个属性、和描述比(听不听话),以及和真图比的 FD(像不像、多不多样)。
1. 条件图像生成 · Conditional image generation
slides 列了几类”有条件”的图像生成:按类别生成(“海葵""金翅雀”)、超分辨率、图像编辑、风格迁移、文生图。图像编辑本身又有好几种(Saharia et al., 2022):补全(inpainting:把指定的缺失像素填上)、上色(把灰度图还原成彩色)、扩图(uncropping:把图的一边”想象”出来)。
这一讲最终想做到的是 slides 第一页的那张图:只改提示,就能编辑一张生成的图——把一个词换成另一个(“猫”→“狗”),减弱某个描述词(“毛茸茸的”少一点),插入一个短语改风格(”……,炭笔画”)或改内容(”……,戴着帽子”)。要做到这些,先得搞清楚提示的每个词是怎样影响图像的每个部分的——这就是交叉注意力。
2. LDM 回顾:怎样让去噪网络以提示为条件 · Recall: LDM
第 12 讲的 LDM(Rombach et al., 2022)有三部分,分别活在三个”空间”里(slides 的图):
| 部分 | 空间 | 做什么 | 训练方式 |
|---|---|---|---|
| 自编码器 、 | 像素空间 ↔ 潜空间 | 把 1024×1024 的图压成小得多的潜变量,再还原 | 只用图像(不用提示)事先训练好、冻结;之后所有 LDM 都能复用 |
| 提示模型 | 提示空间 | “就是一个 Transformer”,把提示变成一串向量 | 和扩散模型一起训练 |
| 去噪网络(U-Net) | 潜空间 | 在 的反向过程里一步步去噪 | 预测噪声 ε |
前向过程和第 7 讲完全一样,只是 ;反向过程多了一个条件:
slides 在这里停下来问:均值 怎样”以提示为条件”? 按第 7 讲的选项 C, 由预测噪声的网络 决定,所以问题变成:去噪网络怎样读提示?
第 12 讲用了最偷懒的办法:直接告诉网络 4 个属性的编号,各查一个嵌入、加起来,加到时间嵌入上(一个”词袋”)。它有两个问题:(1) 真实的提示是一句话,没人替你把它解析成”形状 = 圆、位置 = 左上”;(2) 一个整句的向量加到每个像素上,每个像素拿到的条件信息都一样——网络没法表达”这个词管图里这一块”。LDM 的答案是交叉注意力。
3. 交叉注意力 · Cross-attention
3.1 从自注意力到交叉注意力
回顾第 2 讲的缩放点积注意力:一个序列 自己和自己做注意力,、、,第 t 个位置的输出是 ,,。
交叉注意力只改一处:查询来自一个序列,键和值来自另一个序列(slides)。设 是”提问”的一方, 是”被看”的一方:
写成矩阵:,,,
n 和 m 可以不一样: 的每一行是”第 t 个查询在 m 个键上的概率分布”。slides 的翻译例子:把西班牙语 “estoy llegando tarde”(m = 3 个源语言词)翻成英语 “I am running late”(n = 4 个目标语言词)时,每个英语词的注意力是一个在 3 个西班牙语词上的分布——“late” 主要看 “tarde”。
3.2 LDM 里的交叉注意力
slides:在 LDM 里,
- 查询矩阵来自 U-Net 的某一层(图像): 是潜空间的像素数(有压缩时)——如果没有压缩,就是图像的像素数;
- 键 / 值矩阵来自提示编码器的输出(文字): 是提示的 token 数;
- 一个(潜空间)像素的注意力权重,是它在提示的 token 上的一个概率分布;
- 实际用的是多头的注意力和交叉注意力。
它被放在一个更大的 Transformer 层里,这个层插在 U-Net 的各个分辨率上(slides 引用的 LDM 论文图):自注意力(像素之间互相看)→ 交叉注意力(像素看提示)→ 前馈层,每个子层都有残差连接和层归一化。
直觉:交叉注意力让”条件”变成了逐像素的。图里画猫的那一块的像素,会更多地去看 “cat” 这个词、把 “cat” 的值向量加到自己身上;背景的像素可以看别的词。这也是第 6 节 Prompt-to-Prompt 的基础:注意力矩阵 的第 j 列(reshape 成图的形状)就是”第 j 个词被画在了哪里”的一张热力图。
3.3 我们的缩小版(代码 01)
t2i_lab.CrossLDM:潜空间只有 7×7,不需要 U-Net 的上下采样,所以去噪网络是 4 个”卷积残差块 + 交叉注意力块”(49 个潜空间像素当查询);提示编码器是 2 层的小 Transformer(非因果),和去噪网络一起训练(和 LDM 论文一致;Stable Diffusion 用的是冻结的 CLIP 文字编码器)。每句提示前面加一个 <s>,补到 10 个位置。训练 8000 步(和第 12 讲的词袋模型一样),10% 的训练描述换成空提示(§4 要用)。共 338,628 个参数,其中提示编码器 102,016 个;一次前向 21.3 MFLOPs / 张。
手算一遍(第 1 部分):第 1 个交叉注意力层,一句提示 “a big square at the top right”:
| 量 | 形状 | 说明 |
|---|---|---|
| (Y:49 个潜空间像素) | (1, 4, 49, 16) | (批, 头, n = 49, ) |
| (X:提示的 10 个位置) | (1, 4, 10, 16) | 其中 2 个是 <pad> |
| (1, 4, 49, 10) | 每个头一张 n × m 的图 | |
| 输出 | (1, 49, 64) | 回到图像一侧 |
手算和模块的输出最大相差 3.6e-07, 的每一行和都是 1.000000,<pad> 列的注意力是 0(被屏蔽)。中心像素 (3, 3) 在 8 个词上的注意力是 <s> 0.30、a 0.05、big 0.13、square 0.18、at 0.08、the 0.05、top 0.06、right 0.15;左上角的像素则是 0.12 / 0.17 / 0.16 / 0.09 / 0.14 / 0.11 / 0.07 / 0.14——两个像素看提示的方式不一样,这正是”逐像素的条件”。
一句话 + 交叉注意力 vs 词袋(第 2 部分,400 张测试图,不加引导):
| 条件 | 形状 | 大小 | 上下 | 左右 | 4 个全对 | FD↓ |
|---|---|---|---|---|---|---|
| 第 12 讲:4 个属性编号的嵌入之和(词袋) | 0.715 | 0.850 | 0.875 | 0.897 | 0.488 | 37.68 |
| 交叉注意力,说法 0 “a big square at the top right” | 0.728 | 0.837 | 0.820 | 0.817 | 0.442 | 38.87 |
| 交叉注意力,说法 1 “a square , big , top right” | 0.715 | 0.837 | 0.825 | 0.815 | 0.435 | 38.02 |
| 交叉注意力,说法 2 “the square is big and at the top right” | 0.723 | 0.832 | 0.827 | 0.820 | 0.442 | 38.75 |
| 交叉注意力,说法 3 “top right : a big square” | 0.710 | 0.822 | 0.822 | 0.822 | 0.433 | 37.73 |
| 交叉注意力,说法 0,无分类器引导 w = 3(§4) | 0.913 | 0.980 | 0.998 | 1.000 | 0.902 | 19.70 |
- 不加引导时,交叉注意力(0.442)略输给词袋(0.488)。这不奇怪:词袋直接拿到了 4 个属性的编号,相当于有人替它把句子解析好了;交叉注意力要自己从一句话里找出哪个词是形状、哪个是位置。
- 4 种说法的结果几乎一样(0.433–0.442):提示编码器把词序完全不同的句子读成了同样的意思——这是词袋做不到的(它根本不读句子)。
- 加上 §4 的无分类器引导,4 个全对从 0.442 跳到 0.902。
- 没见过的 4 种组合(每种 50 张):交叉注意力平均 0.40、加引导 0.84、词袋 0.47。最难的是 “a small cross at the top right”:0.16 → 0.88。
3.4 交叉注意力图:每个词”被哪些像素看”
第 3 部分把 200 张图(说法 0,引导 w = 3)采样时的交叉注意力记下来,对所有层、所有头、所有步取平均,得到每张图一个 49 × 10 的矩阵。再用形状拟合找出每张图里”物体占了哪些潜空间像素”(平均占 18.2%,约 8.9 个像素)。如果一个词的注意力均匀地来自所有像素,那么它的注意力里”来自物体像素的份额”就等于物体的面积,比值 = 1;比值越大,这个词越是”被物体上的像素看”。
| 词的位置 | 平均注意力(一行里分到的份额) | 来自物体像素的份额 | 比值 |
|---|---|---|---|
<s> | 0.129 | 0.185 | 1.00 |
| a | 0.207 | 0.158 | 0.85 |
| (大小) | 0.148 | 0.193 | 1.07 |
| (形状) | 0.080 | 0.260 | 1.44 |
| at | 0.132 | 0.178 | 0.99 |
| the | 0.113 | 0.165 | 0.91 |
| (上下) | 0.083 | 0.184 | 1.00 |
| (左右) | 0.107 | 0.175 | 0.96 |
- 形状词的注意力集中在物体上(比值 1.44):像素在物体上时,更多地去看”自己该画成什么形状”。代码打印的例子里(“a big square at the top right”),形状词的 7×7 注意力图最亮的地方正是右上角的方块。
- 位置词的比值接近 1:每个像素都要知道物体在哪——背景像素也要知道”这里不该画”——所以大家都看它们。
- 形状词的比值在去噪过程中从 1.05(前 1/3 步,噪声最大)→ 1.40 → 1.69(最后 1/3 步):一开始图里还没有物体,注意力无处可”聚”;物体成形以后,注意力才落到物体上。
小结:交叉注意力图确实带着”哪个词管哪一块”的信息,但在我们的小模型里只是”轻微地集中”(1.44 倍)。§6 会看到,这一点决定了 Prompt-to-Prompt 在我们这里能做什么、不能做什么。
4. 无分类器引导 · Classifier-free guidance
4.1 动机
slides:扩散模型(不像 GAN)很擅长生成多样的样本;但当它以某个输入(文字、类别……)为条件时,这种多样性可能让它偏离提示。§3.3 的表格就是例子:不加引导时,只有 44% 的图 4 个属性全对。无分类器引导(classifier-free guidance,CFG;Ho & Salimans, 2022)让扩散模型更贴近提示,图的质量也更高。
4.2 推导
想要的是”更像 c 的那些图”:除了 本身,再按”一个分类器有多确定这张图是 c”加权。贝叶斯公式:
把目标分布取成 (w 越大,越偏向分类器有把握的图),它的得分函数(score,对数密度的梯度)是
第 8 讲讲过,预测噪声的网络和得分函数只差一个系数:。两边乘上 ,就得到 slides 的公式:
- 是”没有条件”时的预测,代表 ;它和有条件的预测用同一个网络、同一组参数,只是把提示换成一个”空”的嵌入 ∅。训练时以一定概率(我们用 10%)把提示换成空提示,网络就同时学会了两种预测。
- “无分类器”:两者之差 就是一个”隐式分类器”的梯度。之前的 classifier guidance(Dhariwal & Nichol, 2021)要另外训练一个能认带噪声图的分类器 ,再用它的梯度;CFG 不需要它。
- w = 1 是普通的条件采样;w > 1 沿”有条件减去无条件”的方向外推, 更高、多样性更低;w = 0 只用无条件的预测。
- 两种写法:slides 的算法 1 写的是 ,示例里 。展开一下就是 ,所以 : 是普通采样, 对应这里的 。而 Stable Diffusion(diffusers 库)的
guidance_scale = 7.5用的是上面方框里的写法,就是这里的 。两种写法差 1,读论文和代码时要看清是哪一种。代码02第 1 部分数值核对:两种写法最大相差 1.7e-06(float32 的舍入)。 - 代价:每一步要算两次去噪网络(有提示、空提示,拼成一个两倍大的批)。我们实测带引导采一张图约 42 ms、不带约 21 ms(⏱)。
slides 引用的 CFG 论文图:引导强度从 w = 0 增加到 w = 3,同一个类别的样本越来越”典型”、越来越像。
4.3 我们的实验(代码 02)
主模型(§3.3),400 张测试图,说法 0;多样性用 20 句提示 × 每句 20 张,量同一句提示的图里形状拟合出的中心坐标和半径的标准差(像素)。
| w | 形状 | 大小 | 上下 | 左右 | 4 个全对 | FD↓ | x 散布 | y 散布 | 半径散布 |
|---|---|---|---|---|---|---|---|---|---|
| 0(只用无条件) | 0.262 | 0.560 | 0.480 | 0.510 | 0.043 | 60.47 | 3.84 | 3.72 | 1.10 |
| 1(普通采样) | 0.728 | 0.837 | 0.820 | 0.817 | 0.442 | 38.87 | 2.89 | 2.85 | 0.74 |
| 1.5 | 0.858 | 0.940 | 0.930 | 0.925 | 0.712 | 17.30 | 2.37 | 2.30 | 0.59 |
| 2 | 0.902 | 0.983 | 0.978 | 0.985 | 0.863 | 11.57 | 1.93 | 1.94 | 0.46 |
| 3 | 0.913 | 0.980 | 0.998 | 1.000 | 0.902 | 19.70 | 1.51 | 1.66 | 0.40 |
| 5 | 0.930 | 0.967 | 1.000 | 1.000 | 0.925 | 43.62 | 1.24 | 1.34 | 0.41 |
| 8 | 0.928 | 0.960 | 0.995 | 0.998 | 0.910 | 72.97 | 1.35 | 1.52 | 0.49 |
| 12 | 0.908 | 0.955 | 0.988 | 0.990 | 0.868 | 118.91 | 1.62 | 1.84 | 0.53 |
| (真实数据) | — | — | — | — | — | — | 2.05 | 2.01 | 0.58 |
- w = 0:4 个全对 0.043,和随便画差不多(瞎猜是 1/32 ≈ 0.031)——同一个网络也学会了无条件生成,FD 60.47。
- 听话程度先升后平:w = 2 时已经 0.863,w = 5 时最高(0.925),再大反而略降。
- FD 是 U 形:w = 2 最好(11.57),w = 12 时 118.91。适度的引导让图更像真图(不听话的图本身就是”不像真图”的一种);太强时 被外推到训练时从没见过的地方,图开始变形。
- 多样性:不加引导时同一句提示的散布(2.89 / 2.85 / 0.74)比真实数据还大(2.05 / 2.01 / 0.58)——有些图根本没按提示画,散得比真图还开。w = 2 时最接近真实数据,w = 5 时最小(1.24 / 1.34 / 0.41):同一句提示画出来的图越来越像同一张,正是 slides 说的”w 越大, 越高、多样性越低”。w 再往上散布又变大,这时图本身开始变形(FD 也很差),拟合出的位置和半径跟着乱。
- 没见过的组合(每种 50 张):w = 1 / 3 / 8 时平均 0.40 / 0.84 / 0.87。引导对没见过的组合同样有效——它放大的是每个属性词各自的作用,不需要见过这个组合。
怎样选 w:FD 最好的 w(2)和”最听话”的 w(5)不是同一个。实践里也一样:Stable Diffusion 默认 guidance_scale = 7.5(我们的 w = 7.5),偏向听话;想要多样一点就调小。
5. 扩散 Transformer · Diffusion Transformer (DiT)
5.1 时间线
slides 的时间线:U-Net 2015 年为医学图像分割提出;2016–2018 年各种 U-Net 变体在像素级的自回归模型、条件 GAN 里很好用(PixelCNN++ 定下了标准的变体);2020 年 Ho et al. 用它做 DDPM;2021 年 Dhariwal & Nichol 加上 Transformer 块的注意力;2022–2023 年大家都继续用 U-Net,“因为它好用”;2023 年 5 月,扩散 Transformer 表明我们其实不需要 U-Net。
5.2 结构
回顾 ViT(第 5 讲):和 BERT 几乎一样,只是输入换成不重叠的 P×P 图像块(P ∈ {14, 16, 32}),每块线性嵌入成一个向量,加一维的位置嵌入。
DiT(slides)的主干基本就是一个 ViT,加了一点改动:
- 输入:带噪声的潜变量(切成块)、时间步 t、标签(或其他条件信息);
- 输出:噪声的预测(DiT 原文还预测协方差,大小都固定);
- 最后一层 LayerNorm 之后,一个线性层把 T 个 token 的嵌入变回固定形状的输出(unpatchify)。
z_t (4×32×32) ──patchify(P)──▶ T = (32/P)² 个 token ──线性嵌入 + 二维正弦位置编码──▶ N 个 DiT 块 ──▶ LayerNorm ──▶ 线性 ──unpatchify──▶ ε̂
▲
条件 c = 时间嵌入 + 标签(文字)嵌入(注入每个块)
我们的实现 t2i_lab.DiT:潜空间 4×7×7、块大小 1(49 个 token),d = 64、4 层;条件 c = 时间嵌入(经过一个 MLP)+ 整句提示的向量(提示编码器输出的平均)。
5.3 怎样把条件注入每个块:adaLN-Zero
slides:“DiT 块里有意思的地方是怎样以标签为条件”。原论文试了四种:
- 上下文拼接(in-context):把条件当作额外的 token 拼在序列后面,用普通的 Transformer 块(就像 ViT 的 [CLS]);
- 交叉注意力块:普通块里多一层交叉注意力,键和值是条件 token(§3);
- 自适应 LayerNorm(adaLN):LayerNorm 不带自己的缩放 γ、平移 β,改由一个 MLP 从 c 回归出来:;
- adaLN-Zero:adaLN 再加上残差连接前的两个”门”:,;回归 γ、β、α 的线性层初始化为全 0。
slides:“adaLN-Zero 在实验里最好;关键是学一个 MLP,输出 LayerNorm 和残差连接的缩放、平移参数”。
为什么零初始化有用:一开始 α = 0,每个块都是恒等映射(),整个网络从”什么都不做”开始学——深的残差网络在训练初期不会被随机初始化的块”搅乱”。这和第 10 讲 LoRA 的 B = 0、adapter 的 是同一个道理。DiT 还把最后一层的调制和输出层也初始化为 0,所以一开始预测的 ε 就是 0。
代码 03 第 1 部分核对了这一点:刚初始化时,adaLN-Zero 的第 1 个块对输入的改变是 0.00,整个网络输出的 也是 0.00;其余三种方式第 1 个块就把输入改了 0.86–1.00、输出 的最大绝对值 1.73–1.88。
四种方式各训练 4000 步(第 2 部分,d = 64,4 层;400 张测试图):
| 注入条件的方式 | 参数 | MFLOPs / 张 | 最后 200 步的损失 | 4 个全对(w = 1) | 4 个全对(w = 3) | FD(w = 3) |
|---|---|---|---|---|---|---|
| 上下文拼接 | 319,300 | 29.5 | 0.0790 | 0.355 | 0.562 | 49.71 |
| 交叉注意力 | 386,372 | 28.4 | 0.0768 | 0.407 | 0.600 | 24.73 |
| adaLN | 393,028 | 24.0 | 0.0773 | 0.352 | 0.580 | 45.21 |
| adaLN-Zero | 426,308 | 24.0 | 0.0752 | 0.375 | 0.545 | 27.32 |
- 损失上 adaLN-Zero 最低,和论文一致;但听话程度上交叉注意力最好,adaLN-Zero 反而最低——四种方式之间最多只差 0.055,在我们这个小模型、4000 步的训练里,差别不大,排序也不稳定。论文的结论来自大得多的模型和 40 万步的训练。
- 计算量:adaLN 两种方式每张图 24.0 MFLOPs;上下文拼接 29.5(序列从 49 个 token 变长了);交叉注意力 28.4(每层多一个注意力)。adaLN 只多了几个从条件回归 γ、β、α 的线性层,几乎不增加计算——论文选它的另一个原因。
- 交叉注意力对文字条件很自然(一个词一个键);adaLN 只能吃一个整句的向量。实际的文生图 DiT(如 PixArt-α)用 adaLN 注入时间、用交叉注意力注入文字。
5.4 GFLOPs、FID 与补丁大小
GFLOPs(slides):一种与硬件无关的计算代价的度量。一次 FLOP 是一次浮点运算(一次加法、一次乘法);1 GFLOP = FLOPs;GFLOPs 衡量模型做一次前向传播(或生成一张图)要多少计算。FID(第 6 讲):真图和生成图都过一个预训练的 Inception-v3,取中间层特征,各拟合一个多元高斯,算两个高斯的 Fréchet 距离——越小越好。我们用的 FD 是同一个公式,只是特征来自第 6 讲的”形状裁判”网络。
补丁大小:减小 DiT 的补丁大小 P 会增加 GFLOPs,但不增加参数量(只有输入嵌入层和输出层随 变)。所以 DiT 研究的是 FID 和 GFLOPs 的关系,而不是 FID 和参数量的关系。
slides 的问题:补丁从 p = 4 降到 p = 2,总计算量增加多少? 自己算一遍:一个 Transformer 块,T 个 token、宽度 d(前馈层 4d):
p 减半 → T 变成 4 倍:线性部分 × 4,注意力部分 × 16,合起来比 4 倍多一点。代码 04 第 1 部分在 28×28 的像素空间里实测(d = 64,4 层):
| 补丁 | token 数 | 参数 | MFLOPs / 张 | 每个训练步(批 64,⏱) |
|---|---|---|---|---|
| p = 7 | 16 | 432,113 | 9.0 | 21 ms |
| p = 4 | 49 | 427,856 | 24.2 | 39 ms |
| p = 2 | 196 | 426,308 | 118.9 | 169 ms |
p = 4 → 2 计算量是 4.9 倍(公式: 每块 ≈ 29.1 M,4 块 116 M,加上嵌入和 adaLN ≈ 119 M ✓),参数量几乎没变。slides 的数字:DiT-XL/2 是 118.6 GFLOPs。
为什么我们只量计算、不比较补丁大小的生成质量:DiT 原文在 32×32×4 的潜空间上用 p = 2 / 4 / 8;我们的潜空间只有 7×7(7 是质数,只能 p = 1)。像素空间可以切 p = 7 / 4 / 2,但我们试过:像素空间的 DiT 训练 3000 步,连”位置”都学不会(准确率约 0.5,等于瞎猜),比较就没有意义了——这本身也是”潜空间扩散省事”的又一个例子(README)。质量随计算量的变化,我们改用模型宽度来做。
5.5 DiT 的规模结论
slides 的三条结论:
- GFLOPs 和 FID 强相关:增加 GFLOPs,FID 就更好;
- 大模型在训练时更”省计算”:同样的训练计算量,大 DiT 在测试时比小 DiT 好;
- DiT 在差不多的计算量下打败了 U-Net 主干的 LDM:LDM-4(103.6 GFLOPs)vs DiT-XL/2(118.6 GFLOPs)。
代码 04 第 2 部分:潜空间里把 DiT 的宽度从 32 加到 128(深度 4、adaLN-Zero),各训练 4000 步,每 1000 步存一份 EMA 快照,引导 w = 3 评估。训练计算量 ≈ 3 × 每张图的前向 FLOPs × 批大小 64 × 步数(反向传播约是前向的 2 倍)。
| 模型 | 参数 | MFLOPs / 张 | 1000 步 | 2000 步 | 3000 步 | 4000 步 | 4000 步的 FD |
|---|---|---|---|---|---|---|---|
| d = 32 | 186,340 | 8.2 | 0.070 | 0.123 | 0.155 | 0.410 | 154.95 |
| d = 64 | 426,308 | 24.0 | 0.093 | 0.350 | 0.543 | 0.545 | 27.32 |
| d = 96 | 821,924 | 49.7 | 0.180 | 0.572 | 0.623 | 0.670 | 26.14 |
| d = 128 | 1,373,188 | 85.0 | 0.452 | 0.562 | 0.627 | 0.798 | 14.31 |
(中间几列是 4 个属性全对的比例。)
- 结论 1 复现了:训练 4000 步后,GFLOPs 和”4 个全对”的斯皮尔曼秩相关是 +1.00,和”FD 越小越好”也是 +1.00——宽度每加一档,生成就更好。
- 结论 2 没有复现:把计算量相差不到 35% 的快照配成对(共 9 对),大模型只赢了 1 对。例如 d = 128 训练 3000 步(0.049 PFLOPs)是 0.627,d = 96 训练 4000 步(0.038 PFLOPs)是 0.670;d = 128 训练 1000 步(0.016 PFLOPs)是 0.452,d = 64 训练 3000 步(0.014 PFLOPs)是 0.543。我们的模型和训练都太短:早期的快照还在快速下降的阶段(EMA 也有滞后),大模型还没进入”省计算”的区间。
- 结论 3(代码
03第 3 部分):DiT adaLN-Zero 训练 4000 步(0.018 PFLOPs)4 个全对 0.545、FD 27.32;卷积 + 交叉注意力的主模型在同样 4000 步的快照(0.016 PFLOPs)是 0.398、FD 55.48——同样的计算量下 DiT 更好,和 slides 一致。主模型训练满 8000 步(0.033 PFLOPs)是 0.902、FD 19.70。
6. Prompt-to-Prompt
6.1 两种基线
slides 先给两种已有的”用文字编辑图像”的做法:
- 固定随机种子:改一部分提示、保持随机种子(扩散的起点噪声)不变,再跑一遍采样器。问题:整张图的结构可能大变——背景、整个蛋糕还是一块、画面里有多少蛋糕都不一样了。这不像”编辑”,更像”生成一张无关的图”。
- 带掩码的编辑(例如 Blended Diffusion):用户画一个掩码,指定哪部分不变,再用文字提示告诉扩散模型怎样改没被遮住的部分。构图保持住了,但要用户画掩码。
6.2 关键想法
Prompt-to-Prompt(Hertz et al., 2022):只用文字、不要掩码。
- 拿一个预训练好的 LDM;
- 用原提示 y 跑一遍扩散,记下交叉注意力权重(从像素到文字);
- 用新提示 y* 再跑一遍,但(小心地)把原来那一轮的交叉注意力权重抄进来;
- 怎样抄,取决于编辑的类型;
- 只有推理,没有训练:只改了从预训练模型采样的方式。
slides 的步骤:
- 编码原提示 y;
- 在 y 上跑扩散,得到每一步的注意力权重 ;
- 编码新提示 y*;
- 再跑一遍扩散:(a) 复用原来那一轮的噪声 ;(b) 在时间步 τ 之前,用原来那一轮的注意力权重 ;(c) 之后换成这一轮自己的 ;(d) 不论用哪一份注意力权重,乘的都是 y 的值*(attend to y*);
- 在潜空间里的话,用解码器还原成像素。
论文的算法 1:,;每一步 ,,,;其中
(t 从 T 往下数,所以”其他”是前面噪声大的那些步。)两次去噪在同一个批里同步进行,所以不需要先存下所有的 。
6.3 为什么先用一阵原来的注意力图?
slides 的问题。答案:去噪的前几步(噪声大)决定图的构图——东西画在哪、多大;后几步只是补细节。前几步用原来的注意力图,等于强制新图”在原来的地方画东西”,构图就和原图一样;后几步换成新的注意力图,新词的细节(新形状的轮廓、新纹理)才长得出来。τ 越晚,越像原图;τ 太晚,新词就画不出来。slides 引用的论文图正是”改变替换的时刻”:越晚替换,越像原图。
6.4 形状不一致时:对齐
slides:如果 和 形状不一样怎么办?只替换对应的部分:
- 潜空间那一维永远不变(例如 1024 个像素);
- 如果用固定长度的文字编码器(CLIP 补到 77 个 token),提示那一维也不变;
- 但词可能对不齐:把 “orange” 换成 “big tabby” 时,后面的 “cat sitting” 都往后挪了一位。做法:把原来 “orange” 那一列的注意力抄给 “big” 和 “tabby” 两列,其余的词按对应关系抄。
我们的 t2i_lab.align_tokens 用 difflib 求两句话的对齐(代码 05 第 1 部分):
| 原提示 | 新提示 | 新提示每个词的注意力从哪里来 |
|---|---|---|
| orange cat sitting | big tabby cat sitting | <s>←<s>、big←orange、tabby←orange、cat←cat、sitting←sitting |
| a big circle at the top left | a big square at the top left | square←circle,其余一一对应 |
| a circle | a big circle | big←新(插入的词用它自己的注意力),其余一一对应 |
6.5 其他编辑
slides:P2P 不只换词,不同的编辑靠不同的方式操纵交叉注意力:换一个词(如上);减弱 / 加强一个描述词(把这个词那一列的注意力乘以一个系数 c);插入一个短语改风格或内容(新词用自己的注意力,原有的词抄原来的注意力)。
6.6 我们的实验(代码 05)
所有编辑都带引导(w = 3)。量三件事:新形状画对了吗;大小、象限保住了吗;中心和半径移动了多少像素(形状拟合)。另外放一个只用推理的对照:“共享前几步的轨迹”——前 k% 的步数两边都用原提示去噪(轨迹完全相同),之后第二行才换成新提示。它直接检验”构图是不是在前几步定下来的”。
完整的描述,换形状词(主模型,200 句提示):
| 做法 | 新形状画对 | 大小没变 | 象限没变 | 中心移动(像素) | 半径变化 |
|---|---|---|---|---|---|
| 固定种子、只换提示(基线) | 0.905 | 0.965 | 0.990 | 1.28 | 0.64 |
| P2P:前 40% 的步数用原注意力图 | 0.900 | 0.965 | 0.995 | 1.23 | 0.59 |
| P2P:前 60% | 0.910 | 0.955 | 0.995 | 1.10 | 0.56 |
| P2P:前 80% | 0.860 | 0.960 | 0.995 | 0.97 | 0.55 |
| P2P:前 100% | 0.455 | 0.905 | 0.995 | 0.86 | 0.73 |
| 对照:前 60% 的步数两边都用原提示 | 0.880 | 0.975 | 0.995 | 0.84 | 0.42 |
- 描述已经把大小和位置写死了,所以固定种子的基线就几乎保住了构图(象限 0.990、大小 0.965、中心只移动 1.28 像素)——slides 说的那个问题,在”提示把一切都说清楚”的情况下本来就不大。
- P2P 注入得越久,中心移动得越少(1.28 → 0.86 像素);但注入全部步数时新形状只画对了 0.455(基线 0.905):原图的注意力图带着原来形状的轮廓——哪些像素在”看形状词”,本身就勾勒出了一个圆或一个方块——抄得太久,新形状就长不出来。这正是 P2P 只在前 τ 步注入的原因(§6.3)。
- 共享前 60% 的轨迹:移动更少(0.84 像素),新形状照样画对 0.880。
只说形状的提示(“a circle” → “a square”):构图完全由噪声决定,这才是 slides 说的情形。主模型从没见过这么短的提示,所以用一个一半训练描述不完整的对照模型(partial_caption:形状一定说,大小、上下、左右各以 1/2 的概率说;其余配置和主模型相同)。它在完整描述上同样听话(4 个全对 0.830,主模型 0.880)。
| 做法 | 新形状画对 | 大小没变 | 象限没变 | 中心移动(像素) |
|---|---|---|---|---|
| 固定种子、只换提示(基线) | 0.920 | 0.520 | 0.695 | 3.13 |
| P2P:前 20% | 0.915 | 0.540 | 0.700 | 2.97 |
| P2P:前 60% | 0.895 | 0.585 | 0.705 | 2.87 |
| P2P:前 100% | 0.480 | 0.570 | 0.720 | 2.47 |
| 对照:前 20% 的步数两边都用原提示 | 0.915 | 0.570 | 0.750 | 2.71 |
| 对照:前 40% | 0.860 | 0.710 | 0.790 | 2.14 |
| 对照:前 60% | 0.755 | 0.830 | 0.870 | 1.06 |
- 基线的问题现在很明显:只换一个词,30% 的图换了象限、大小保住的只有 0.520(≈ 随机),中心平均移动 3.13 像素——“整张图的结构可能大变”。
- P2P 几乎没有帮助:象限保住的比例最多从 0.695 升到 0.720,而那时新形状只画对了 0.480。
- 共享轨迹有帮助:前 60% 的步数共享,象限保住 0.870、大小 0.830(代价是新形状画对的比例降到 0.755)。
为什么在我们这里 P2P 不灵? P2P 成立的前提是:“哪个词画在哪里”由交叉注意力图决定,所以抄注意力图就等于抄构图。§3.4 量过:我们的形状词的注意力只是轻微地集中在物体上(1.44 倍),位置词的注意力均匀地分给所有像素;去噪网络的卷积主干从同一个噪声里就能决定”把东西画在哪”,构图活在潜变量的去噪轨迹里,而不在注意力图里。P2P 论文用的 Imagen、Stable Diffusion 不一样:它们在 64×64、32×32、16×16 等多个分辨率上都有交叉注意力,名词的注意力图非常集中(P2P 论文画出了 “a furry bear watching a bird” 里每个词的平均注意力图,“bear""bird” 的图几乎就是物体的掩码)。一个方法在大模型上有效,不等于它的前提在小模型上也成立——这是本讲最值得记住的”负结果”。
还有一个细节(slides 的”为什么先用原注意力图一阵”):线性噪声调度下,DDIM 50 步的前 40% 步里 ,图几乎还是纯噪声;构图要到中段才定下来,所以”共享前 20%“几乎没用,要共享到 60% 才明显。
注意力重新加权(第 4 部分,每行 100 张图):
| 提示里的词 × c | × 0 | × 0.5 | × 1 | × 2 | × 4 |
|---|---|---|---|---|---|
| “big”:平均半径(像素) | 7.01 | 7.20 | 7.42 | 7.85 | 8.58 |
| “small”:平均半径 | 6.38 | 5.64 | 5.06 | 4.22 | 4.00 |
| “small”:形状画对 | 0.88 | 0.88 | 0.88 | 0.75 | 0.24 |
| 形状词:形状画对 | 0.26 | 0.67 | 0.91 | 0.96 | 0.85 |
- c 像一个旋钮:把 “big” 的注意力乘 0 → 4,平均半径 7.01 → 8.58;把 “small” 乘 0 → 4,半径 6.38 → 4.00。c = 0 等于把这个词删掉(半径回到”没说大小”的平均),c > 1 比原词更”大”或更”小”。c 太大时形状会被破坏(“small” × 4 时形状只画对 0.24)。
- 把形状词的注意力乘 0,形状就退回瞎猜(0.26 ≈ 1/4):形状的信息确实是**经由”看这个词”**进来的。
- 这一类编辑在我们的模型里很好用,因为它改的是”这个词的内容有多少流进图里”,不依赖”注意力图决定构图”这个前提。
7. 小结 · Summary
- 交叉注意力:查询来自图像(U-Net 的当前层,n 个潜空间像素),键和值来自提示(m 个 token); 的每一行是一个像素在提示的词上的分布。它让条件变成逐像素的。
- 无分类器引导:,来自 的得分函数;训练时以 10% 左右的概率丢掉提示,采样时多算一倍。w 增大 → 更听话、更不多样;FD 是 U 形的。
- DiT:ViT 式的主干 + adaLN-Zero 注入条件(零初始化让每个块从恒等映射开始);补丁越小 GFLOPs 越大、参数不变;GFLOPs 和生成质量强相关。
- Prompt-to-Prompt:同一个噪声、同一个批里,原提示和新提示一起去噪,前 τ 步把新提示的交叉注意力图换成原来的;提示长度不同时按词对齐。它依赖”注意力图决定构图”这个前提。
- 我们的小实验:交叉注意力 + 引导让 4 个属性全对从 0.442 到 0.902;DiT 在同样计算量下比卷积主干好、宽度越大越好,但”大模型更省计算”没有复现;P2P 的换词编辑在我们的模型里保不住构图,重新加权却很好用。
8. 常见误区 · Common pitfalls
- “无分类器引导要训练一个分类器。” 不要。有条件和无条件的预测来自同一个网络(训练时随机丢掉提示),两者之差就是一个隐式分类器的梯度。
- “w 越大越好。” 听话程度先升后平,FD 是 U 形,多样性一直在降;我们的 FD 在 w = 2 最好、听话程度在 w = 5 最好。
- “slides 两页的 w 是同一个数。” 里 w = 1 是普通采样(diffusers 的
guidance_scale就是这个 w); 里 w’ = 0 才是。。 - “补丁大小只影响分辨率。” 它决定 token 数,计算量和 token 数成正比(注意力部分和平方成正比),参数量却几乎不变——DiT 用 GFLOPs 而不是参数量来做规模分析的原因。
- “adaLN-Zero 的零初始化让模型学不动。” 梯度照样能流到那些全 0 的线性层(它们的输入不是 0),只是一开始每个块都是恒等映射。
- “P2P 换词,抄的是注意力图,所以新词的内容也被抄走了。” 不会:抄的是权重 A,乘的仍是新提示的值 V*。正因为如此,抄得太久时新词的内容会被塞进原来那个词的”轮廓”里(我们的 0.455)。
- “一个方法在 Stable Diffusion 上有效,在小模型上也有效。” P2P 依赖”交叉注意力图决定构图”,我们的小模型不满足这个前提。
9. 练习 · Exercises
练习 1(交叉注意力的形状)。 Stable Diffusion 1.x 的潜空间是 4×64×64,提示用 CLIP 编码成 77 个 768 维的向量。在 64×64 那一层(通道数 320,8 个头)的交叉注意力里,Q、K、V、A 各是什么形状?A 一共有多少个数?如果改成”自注意力 + 把提示拼在像素序列后面”(上下文拼接),这一层的注意力矩阵有多大?
参考答案
n = 64 × 64 = 4096 个像素,m = 77 个 token。Q:(8 头, 4096, 40)(320 / 8 = 40);K、V:(8, 77, 40)(由 768 维的文字向量投影到 320 维再分头);A:(8, 4096, 77),共 8 × 4096 × 77 ≈ 252 万个数。上下文拼接后序列长 4096 + 77 = 4173,自注意力矩阵是 (8, 4173, 4173) ≈ 1.39 亿个数——其中像素看像素的部分 (8, 4096, 4096) 本来自注意力就有,多出来的是像素看文字、文字看像素、文字看文字。交叉注意力只算”像素看文字”这一块,便宜得多。
练习 2(引导的推导)。 (a) 从 推出 。(b) 证明 w = 1 时 。(c) 我们的 w = 0 为什么 4 个全对只有 0.043?
参考答案
(a) 见 §4.2:取对数、求梯度,,再用贝叶斯公式 ,最后乘上 把得分换成噪声预测。(b) w = 1 时 。(c) w = 0 时 ,完全不看提示,是在从 采样:4 个属性随机,全对的概率约 1/32 ≈ 0.031;0.043 和它差不多。
练习 3(引导的代价)。 一个模型不带引导采一张图要 50 步。带引导时每步算两次网络。(a) 计算量变成几倍?(b) 有人提出”只在前 25 步加引导,后 25 步不加”,计算量是几倍?你预计效果会怎样?(提示:§6.6 讲过构图在哪几步定下来。)
参考答案
(a) 2 倍(实际实现里把两份拼成一个两倍大的批,时间上通常不到 2 倍,我们实测 42 ms vs 21 ms,正好 2 倍)。(b) (25 × 2 + 25) / 50 = 1.5 倍。引导主要作用在”画什么、画在哪”上,这在噪声大的那些步里决定;后面的步只补细节。所以只在前面(或中间)加引导通常能保住大部分听话程度、还减少过度饱和;这正是后来一些”引导区间”(guidance interval)论文的做法。但要注意线性调度下前 40% 的步几乎是纯噪声,真正决定构图的是中段,所以”前一半”未必是最好的区间——要实验。
练习 4(DiT 的 GFLOPs)。 用 §5.4 的公式 估算 DiT-XL/2 的 GFLOPs:潜空间 32×32×4、p = 2、d = 1152、28 层。和 slides 的 118.6 GFLOPs 比。p = 4、p = 8 时呢?
参考答案
p = 2:T = (32 / 2)² = 256。每层 ,28 层 ≈ FLOPs。这里一次乘加算 2 次 FLOP;DiT 论文按”乘加次数”(MACs)报 GFLOPs,除以 2 ≈ 118 G,和 118.6 吻合(还有 adaLN 的调制层等少量计算)。p = 4:T = 64,每层 ,28 层 ≈ 57.7 G FLOPs ≈ 28.9 G MACs;p = 8:T = 16,≈ 7.2 G MACs。每减半一次补丁,计算量约 × 4。
练习 5(adaLN-Zero)。 (a) 证明:如果回归 γ、β、α 的线性层权重和偏置都是 0,那么 DiT 块的输出等于输入。(b) 既然一开始所有块都是恒等映射、输出层也是 0,网络的输出 ε̂ ≡ 0,梯度能传回去吗?哪些参数第一步就有非零的梯度?
参考答案
(a) γ = β = α = 0,块变成 。(b) 损失 对输出层权重 的梯度是 ,h(最后一层 LayerNorm 的输出)不是 0,所以输出层第一步就有梯度;调制层(adaLN 的线性层)的梯度正比于它的输入 SiLU(c)(不是 0)和下游传回来的梯度——输出层一旦变成非 0,下一步各块的 α 也开始有梯度。所以网络从”恒等”开始,一层一层地”打开”。和 LoRA 的 B = 0 一样:A 第一步没有梯度(因为 B = 0),B 有。
练习 6(P2P 的前提)。 §6.6 里 P2P 在我们的模型上保不住构图,但”共享前 60% 的轨迹”可以。(a) 说出两者的区别:P2P 在注入期间,新图和原图的潜变量 一样吗?(b) 如果想让我们的模型”更适合 P2P”,你会怎样改它的结构或训练?
参考答案
(a) 共享轨迹时,前 k 步两行的输入(噪声、提示)完全相同,所以 完全相同——构图被原样复制。P2P 注入期间,两行的注意力权重相同,但值 V 不同(新提示的 V*),交叉注意力的输出就不同, 从第一步起就和 分开了;只有当”画在哪”完全由 A 决定、V 只决定”画成什么”时,两者的构图才会一致。我们的模型里,卷积主干会根据 V 带来的内容去决定位置和大小,所以构图跟着变。(b) 让”位置”必须经过交叉注意力:比如更高分辨率的潜空间(更多像素、注意力图更细)、在每个分辨率都放交叉注意力、减小卷积的感受野、或者训练时对注意力图加一个”集中”的正则(后来的一些工作正是这样做的)。也可以同时注入自注意力图(像素之间的关系),这在后来的编辑方法里很常见。
练习 7(重新加权)。 P2P 的重新加权不重新归一化:把 “big” 那一列乘以 c 之后,这一行的和不再是 1。(a) 如果重新归一化(除以新的行和),c = 0 和 c = 4 的效果会怎样变?(b) 我们的 “small” × 4 时形状只画对 0.24,为什么?
参考答案
(a) 重新归一化后,其他词的注意力会被等比例放大(c = 0 时)或缩小(c = 4 时),于是”加强 big”同时”减弱了所有其他词”——形状、位置的信息也会变弱。不归一化时只改动了 big 这一列的贡献,其他词的贡献不变,编辑更”局部”。(b) 半径已经被压到形状拟合的下限(4 像素),继续加强 “small” 只会把物体压得更小、更模糊,形状拟合就认不出是什么形状了;同时 small 的值向量被放大了 4 倍,在交叉注意力的输出里压过了形状词的贡献。
10. 延伸阅读 · Further reading
- LDM 与交叉注意力:Rombach et al. (2022), LDM;Saharia et al. (2022), Imagen;Podell et al. (2023), SDXL。
- 引导:Dhariwal & Nichol (2021), Diffusion Models Beat GANs on Image Synthesis(classifier guidance);Ho & Salimans (2022), Classifier-Free Diffusion Guidance;Kynkäänniemi et al. (2024), Applying Guidance in a Limited Interval Improves Sample and Distribution Quality。
- DiT:Peebles & Xie (2023), DiT;Chen et al. (2023), PixArt-α;Esser et al. (2024), Stable Diffusion 3(MM-DiT)。
- 用文字编辑:Hertz et al. (2022), Prompt-to-Prompt;Mokady et al. (2023), Null-text Inversion;Tumanyan et al. (2023), Plug-and-Play Diffusion Features;Avrahami et al. (2022), Blended Diffusion。
11. 下一讲预告 · Next lecture
第 15 讲有两个主题。第一个接着第 13 讲:把图像交给一个冻结的大语言模型时,与其把几十上百个图像块都塞进去,不如用一组可学习的查询向量(Q-Former,BLIP-2)通过交叉注意力去”问”图像编码器,只交出几个 token——这正是本讲交叉注意力的又一个用法。第二个是规模定律:损失怎样随参数量 N、数据量 D、计算量 C 变化;为什么 Kaplan 和 Chinchilla 给出了不同的”最优配比”;数据质量又怎样改变这些曲线。我们会在一个能精确算出”不可约损失”的小世界里,亲手拟合这些幂律。