阶段 4 · 学会创造

潜空间扩散:没人真的
在像素上跑一千步

上一章《扩散模型 DDPM》,如果直接作用在 512×512 的图上, 光一次前向就要几千 GFLOPs(跑一遍要做几万亿次运算),还要跑一千步。这在工程上根本不可行。 真正的做法是:先用一个压缩器把图缩到 1/48,在那个小空间里扩散,最后再放大回来。

1

像素空间太贵了

回想上一章的前提:扩散模型要反复迭代几十到上千次,每次都是一遍完整的前向。 原版 DDPM 也是在像素空间跑的,只是它实验用的是 32×32 到 256×256 这个量级的小图;图一大,同一笔账就做不动了。 如果前向跑在 512×512×3 = 78 万个元素上,账单是这样的:

看什么像素空间扩散潜空间扩散
每次迭代的输入 512 × 512 × 3 = 786,432 个元素 64 × 64 × 4 = 16,384 个元素
卷积类的计算量 正比于空间位置数 → 贵 64 倍 基准
注意力类(如果全图注意) 正比于空间位置数平方 → 贵 4096 倍 基准
跑 50 步 计算量直接乘 50 计算量同样乘 50,但基数小得多
能不能做 能,但没人这么做 Stable Diffusion、SDXL、FLUX 全部这么做

那张潜表示是 64×64×4:边长缩 8 倍,每个位置从 RGB 三个数变成 4 个数。 这 4 个数是 VAE 学出来的,不对应具体颜色;所以总元素数少 48 倍(786,432 ÷ 16,384)。

互动 · 一千步的账单:每跑一步,整张图都要重走一遍

🎯 类比

像做雕塑。你不会直接对着三吨的大理石反复调整一千次—— 先做一个小比例的泥稿,在小稿上改到满意,最后才放大到大理石上精修。
「小比例泥稿」就是潜空间,「放大精修」就是那个 VAE 解码器。 改一千次都在小稿上做,成本完全不是一个量级。
但泥稿上丢掉的细节,放大时补不回来——这就是 VAE 的画质天花板。

2

压缩 48 倍,到底省了多少

下面这个计算器按结构逐层算 FLOPs(每跑一次前向要多少十亿次运算): 卷积的计算量正比于空间位置数(高×宽),注意力的计算量正比于它的平方。 滑块拖的是边长压缩倍数 F(论文里记作 f;512 ÷ 64 = 8)。

一张图在两种空间里各有几个数

原图 512×512×3 元素数 512×512×3 = 786,432 空间位置数 512×512 = 262,144 每个位置 3 个数(R、G、B) VAE 编码 边长缩 8 倍 潜表示 64×64×4 元素数 64×64×4 = 16,384(少 48 倍) 空间位置数 64×64 = 4,096(少 64 倍) 每个位置 4 个数(学出来的,不是颜色) 卷积的算力跟着空间位置数走 → 省 64 倍;注意力按位置数的平方走 → 省 4096 倍。

互动 · 像素空间 vs 潜空间的算力差

💡 注意两个倍数的差距

压缩 8 倍之后:卷积省 64 倍,注意力省 4096 倍。
因为卷积是 O(n),注意力是 O(n²)。空间尺寸每降一半,注意力便宜 16 倍。
这就是为什么几乎所有图像生成模型都必须在潜空间工作—— 不是因为卷积太贵,是因为注意力太贵。

3

四个部件,各管一段

主干拿到潜表示之后,还要把它切成 p×p 的小块(patch),每块算一个 token,再排成一队。 点下面的按钮换 patch 大小,看 token 数怎么变(注意力矩阵在第 4 节)。

互动 · 数据在管道里被压扁、又被拉长的完整形状链

部件输入 → 输出干什么为什么要单独训
① VAE 编码器 512×512×3 → 64×64×4 把图压成潜表示 它只学"怎么压"和"怎么还原",和扩散完全无关。所以可以复用
② 文本编码器 "一只穿西装的猫" → 77×768 张量
(最多 77 个词,每个词 768 个数)
把提示词变成向量序列 用的就是阶段 3 讲的 Transformer。也是外挂的,可以换(CLIP → T5)
③ 扩散主干 (噪声潜 + 时刻 t + 文本) → 预测噪声 唯一真正要训练的部分 它才是"会画画"的那个。前两个是固定的工具
④ VAE 解码器 64×64×4 → 512×512×3 把干净的潜表示还原成图 和编码器配对训练。它决定了画质的硬上限

整个系统里,扩散主干只是中间那个环节:VAE 和文本编码器 都是独立预训练、然后冻结的。 推论也很实用:换掉文本编码器、再训练一次对齐(对齐 = 让新编码器输出的向量,和画师原来认得的那种向量对上号),同一个画师就能"听懂"另一种语言。 社区能很快做出各种微调版本,就是因为只有主干那部分参数需要动。

4

主干换成 Transformer

Stable Diffusion 1.5 的主干是一个 U-Net(8.6 亿参数): 一堆卷积 + 下采样 + 上采样 + 跳连 (跳连:把浅层的特征直接接到深层,补回下采样丢掉的细节)。 2022 年底,有人问了一个直接的问题(次年发表在机器学习顶会 ICLR): 既然 Transformer 在语言、视觉上都赢了,为什么扩散还要用卷积?

U-Net 和 DiT 的结构差别

U-Net:先下采样再上采样,靠跳连补回细节

DiT:切成小块排成一队,全程同一分辨率

互动 · patch 切多大:token 数、注意力矩阵、显存一起算给你

对比项U-NetDiT
基本运算卷积(局部)自注意力(全局)
分辨率变化先降后升,多个尺度全程不变,靠 patch 化控制长度
归纳偏置强(卷积天生假设「附近像素相关、挪一下还是同一个东西」)几乎没有,全靠数据学
和语言模型的关系两套完全不同的代码几乎就是 GPT 换了个输入
缩放表现加大到一定程度收益变缓论文发现:同 FID 下 GFLOPs 更少,且能持续 scale(FID:生成图和真实图的分布差多远,越低越好;ImageNet 256×256 上 2 左右已经很好)
今天谁在用SD 1.5 / SDXLSD3、FLUX、Sora——新模型基本全是它

U-Net 是一套图像专用的积木,语言模型是另一套,两边的工程经验没法互相迁移。 DiT 把主干变成标准 Transformer 之后:

① 训练技巧可以直接借用,混合精度、梯度检查点、张量并行、FlashAttention, 大模型训练那套现成的工具都能直接用; ② 视频、图像、音频可以用同一个骨架,只要换 patch 化方式; ③ "下一个 token 预测"的直觉可以搬过来,这直接催生了阶段 8 的扩散语言模型。

架构统一带来的复利,远比"这一版指标高一点"重要。

5

条件怎么进去:四种注入方式

U-Net 在几个分辨率上各插一层交叉注意力,条件就有地方进;DiT 的主干每层长得一模一样, 条件从哪个口子塞进去就得专门设计。论文用类别标签在 ImageNet 上试了四种(道理对文本条件一样):

方式怎么做结果
In-context把类别向量当成一个额外的 token,拼在序列最前面 能用,但效果最差。追加的那一个 token 要独自承担全部条件信息
Cross-attention在每个 Block 里加一层交叉注意力,让图像 token 去查文本 经典做法(SD 1.5 就是它)。效果好,但每层都要多算一次注意力
adaLN用条件向量算出每层的缩放 γ 和平移 β,直接调制归一化(只是多算几个缩放、平移的数, 不用像交叉注意力那样让图像和文本两两比一遍) 比 cross-attention 更好,而且几乎不增加计算量
adaLN-Zero在 adaLN 基础上,再把残差门的初始值设为 0(残差门: 这一层的输出加回输入之前先乘上的那个数,0 表示这层什么都不做) 最好。DiT 论文里它是明确的赢家

最后一个「Zero」看起来只是个小改动,但它修复的问题非常真实。拖动下面的滑块模拟训练进度:

互动 · adaLN-Zero 为什么能让 12 层网络稳稳起步(演示用,原理对任意层数成立)

公式 · 把鼠标移到每一项上

悬停公式里带下划线的部分,看它在上面这张图上对应哪一块。

💡 一句话说清 adaLN-Zero

残差门的初始值设成 0 之后,整个 Block 一开始就是恒等映射—— 输入原封不动地传过去,什么也不做。
这意味着:一个刚初始化的 12 层网络(演示用 12 层,原理对任意层数都成立),等价于一个 0 层网络。 训练从"什么都不做"开始,然后每层的门慢慢打开,逐层学会自己的任务。
这和我们在「初始化与训练稳定性」那一章讲的思路是同一个: 别让网络一开始就乱动,先让它学会"什么都不做"。

⚠️ 但注意:adaLN 有个前提

classifier-free guidance 是这么干的:训练时偶尔把条件换成"空",生成时拿"有条件"和"无条件" 两个输出的差,把画面往条件方向推得更狠。这就要求模型两种输入都能吃。条件空的时候, adaLN 能调制的信息就没了:γ、β 只剩从那组空条件里算出来的一份固定值,正好当那个"无条件"版本用。

M

数学 · 每层条件改了哪些数

上一节说 adaLN 用条件向量算出 γ 和 β。这一步展开有七个符号, 下面把它拆成一个 Block 里真实发生的一遍计算—— 拖下面的滑块,每一行都会跟着变。

动画 · 一个 DiT Block 的真实前向(数字全是当场算的)

🎬 自己验一遍

把「残差门 α」拖到 0:第 4 行整个塌成一条零线,第 5 行和虚线画的第 1 行完全重合—— 这就是上一节那张「12 层网络等于 0 层」的图在一层之内的样子。
再把「条件强度 c」拖到最大:γ 和 β 一起变,第 3 行整排被拉宽、推歪, 而输入 x 一个数都没动过——条件改的从来不是数据,是处理数据的方式。

训练时这些门 α 也是网络从条件 c 里算出来的,梯度会把有用的那几层的门推大。

6

潜空间主干的四种形态

架构核心结构代表模型特点
U-Net
2015 → 2022
卷积 + 下采样/上采样 + 跳连,在最深层插注意力 DDPM、SD 1.5(8.6 亿)、SDXL(26 亿) 归纳偏置强,中小数据也能训好
UViT
2023
保留了 U-Net 的多尺度结构,但把卷积块换成 Transformer 块 研究性质,较少直接使用 是 U-Net 到 DiT 的过渡形态,证明了"跳连仍然有用"
DiT
2023
纯 Transformer,patch 化潜特征,全程单分辨率,adaLN-Zero 注入条件 DiT-XL/2(6.75 亿)、Sora 结构极简、易 scale、和 LLM 共享全部基建
MMDiT
2024
双流:图像和文本各有一套权重,只在注意力处汇合 SD3 / SD3.5 文本理解明显更强(尤其是长提示、文字渲染); 代价是参数量翻倍
DiT + Reflow
2024 →
DiT 骨架 + 流匹配训练目标(下一章的主题) FLUX、SD3.5 今天的最强组合:架构统一 + 采样步数少

互动 · 四种主干的接线差别(点按钮切换)

7

潜空间的三个隐患

隐患说明什么时候真的会痛 → 换什么
① VAE 是画质的天花板 压缩是有损的。VAE 重建时丢掉的细节,扩散主干再怎么努力也补不回来, 它只能生成"能被这个 VAE 表示的东西"。潜空间里不存在的信息,解码器变不出来。 要出小字海报、精细商品图,而字总是糊 → 换压缩倍数更小的 VAE(F=4),代价是算力
② 高频细节系统性偏弱 文本(尤其是小字)、细密的纹理、毛发、远处的人脸,这些正是压缩最容易丢的。 不是模型不行,是潜空间不够用。改进方向是把压缩倍数 F 从 8 降到 4,代价是算力涨回去。 要放大到 4K、要渲染文字排版 → 降压缩倍数,或改用像素空间 / 蒸馏路线
③ 两阶段训练的耦合 VAE 先训好、然后冻死,扩散主干再去适配它,主干只能迁就 VAE 的缺陷。 有人尝试联合训练,但流程复杂很多、也难调。 想端到端改进画质、直接动 VAE 的缺陷 → 联合训练,代价是从头调一套更复杂的流程

互动 · 压缩掉的到底是什么:同一张 200×200 的小图,压了再放回来

⚠️ 一个常被误解的点

"潜空间扩散质量比像素扩散差"这句话是不准确的。
正确的说法是:在相同算力预算下,潜空间扩散的质量好得多, 因为省下来的 48 倍算力可以直接换成更大的模型、更多的步数。
只有在算力无限的假设下,像素空间扩散的理论上限才更高(这是本站的推论,论文没这么说)。 而算力永远有限,这个理论优势从来没有真正兑现过。

8

各种配置的规模对照

主干参数量对比

互动 · 这些参数长在谁身上

SD 1.5 的 8.6 亿参数里,大部分花在了多尺度的卷积块上;DiT-XL 的 6.75 亿参数里, 绝大部分花在了自注意力的 QKV 和 FFN 上。参数总量差不多,但结构完全不同: U-Net 用"不同尺度重复处理"换取效率,DiT 用"全局注意力 + 更深的堆叠"换取上限。 这也解释了为什么 DiT 更能 scale:它的参数结构和大语言模型同构,而我们知道怎么把那种结构做大。

9

小结

这一章是全站对「学习即压缩」最直白的一次展示—— 而且它把「压缩」拆成了两层,第一层才是这一章的主角。

它对应哪条线 ④ 学习即压缩——这一章里同时跑着两层压缩: 第一层是 VAE 对像素的压缩(8 倍边长、48 倍元素数,有损), 第二层是扩散主干对数据分布的压缩(一千步去噪学到的方向场:每个位置该往哪走的一片箭头,压进 6.75 亿参数里)。 第二层是上一章讲的,第一层是这一章讲的——这一章做的事,是让第二层在一个小得多的场地里跑。
一句话 潜空间扩散把「画一张图」的场地从 786,432 个元素搬到 16,384 个元素(边长缩 8 倍); 省下来的算力没有退还给用户,是被拿去买了更大的模型、更多的步数,画质因此更好。
它牺牲了什么 牺牲了信息的上限:VAE 丢掉的东西,解码器和主干一起也补不回来—— 潜空间里不存在的像素,谁都变不出来(回扣暗线 B)。而且这份损失是不均匀的, 它专门砍在高频上:小字、毛发、远处的人脸。 于是画质卡在了一个不是模型能力、而是表示能力的天花板上。 第二样被牺牲的是解耦的自由:VAE 一旦冻死,主干就只能迁就它的缺陷。
🎬 自己验一遍

回到第 2 节那张「像素空间 vs 潜空间的算力差」。图像边长保持 512, 把「VAE 压缩倍数」从 8 拖到 16:卷积那个数从 64 倍跳到 256 倍, 注意力那个从 4096 倍直接跳到 65536 倍;再拖回 4,两笔账一起塌下去。

你拖的这一个滑块,直接决定了主干能买多大的模型、能跑多少步。

它在暗线里站在哪

暗线这一章的回答
A 信息流动 三条流在中间汇合。图像流:512×512×3(786,432 个元素)→ VAE 编码器 → 64×64×4(16,384 个元素);文本流:「一只穿西装的猫」→ 文本编码器 → 77×768 的向量序列;时刻流:一个标量 t 展开成向量。
主干内部:64×64 切成 2×2 的小块 → 每块拉成 16 维 → 投影成 (1024, 1152) (DiT-XL/2 的 token 数与宽度)→ 28 层全程形状不变 → 反 patch 化 → VAE 解码器 → 512×512×3。和 U-Net 最本质的差别就在这里: U-Net 的形状越走越小,DiT 从头到尾只有一种形状。
B 什么被牺牲了 换来一个数量级的算力和「能 scale 的形状」,付出三样:① 细节上限:VAE 是天花板, 丢了就补不回来,而且专砍小字、毛发、远景人脸;② 压缩倍数的两难:想省算力就压到 16 倍, 想保细节就得回到 4 倍;③ 两阶段耦合:VAE 先训好再冻死,主干只能绕着它的缺陷学。
C 参数账本 SD 1.5 的 10 亿多参数里,真正在训练的只有 8.6 亿的 U-Net;VAE 约 0.83 亿 (编码器 0.34 + 解码器 0.49)、文本编码器约 1.23 亿,两个都冻结。 DiT-XL/2 把主干砍到 6.75 亿(28 层、宽 1152、16 头),却在 ImageNet 256×256 上 把 LDM-4 的 FID 从 3.60 打到 2.27;SD3 的 MMDiT 是双流设计,规模有 20 亿 / 80 亿两档。
算力账:元素数少 48 倍;空间位置数少 64 倍,卷积少 64 倍、注意力少 4096 倍(F = 8)。 显存上,512×512×3 在 fp16(每个数 2 字节)下是 1.5 MB,压成 64×64×4 是 32 KB; 训练时每一层激活都要存一份,这个倍数会一路乘下去。
D 跑在什么上 带宽是这一章被逼向潜空间的原因之一。
最贵的动作不是矩阵乘,是让那个 N² 的注意力矩阵真的落到显存里。 512×512 的图切成 1×1 的 token 做全图注意力,矩阵有 262,144² ≈ 687 亿个元素, fp16 下就是 137 GB,单卡装不下。压到 64×64 后 token 数是 4,096(p=1), 矩阵约 1,678 万个元素、33 MB;DiT-XL/2 用 2×2 的 patch,token 只有 1,024, 矩阵约 105 万个元素、2 MB,这才回到「可以算」的范围。 每一次省下来的读写,都是显存上的真实时间。完整的硬件账在 《硬件与算力账本》。
E 它假设了什么 三条假设,每条都能被追问。
① 像素的冗余远大于信息:照片里相邻像素几乎一样,所以固定编码器 压 48 倍仍然够用;这条在密集文字、工程图纸上就不成立。
② 「重要」和「高频」可以分开处理:压缩砍掉的主要是高频细节,语义内容被保住, 所以伤害集中在小字和纹理上。
③ 「一开始什么都不做」是最好的初始化:adaLN-Zero 的残差门从 0 开始, 刚初始化的 12 层网络等价于 0 层,这和「初始化与训练稳定性」那一章是同一个信念。
F 违背了哪个直觉 三条。
① 「画质要靠更多像素」是反的:先把 786,432 个元素扔掉 48 分之 47,画质反而更好, 因为真正的约束是算力预算。
② 「结构越贴合图像越好」也是反的:U-Net 的图像先验比 DiT 强得多, 算力充足时却被更弱先验的 DiT 反超。
③ 「算力堆到同一档,旧结构也能追上」也反了:LDM-4 用 103.6 GFLOPs 拿到 FID 3.60, DiT-XL/2 用差不多的 118.6 GFLOPs 拿到 2.27,多一成多算力、质量差一个档。
🎯 前后钩子

它接住了上一章的什么:上一章讲扩散时那句「要在像素上跑一千步」, 在这一章被直接判了死刑——不是理论错,是工程上根本不成立。

它给后面留了什么:主干一旦变成标准 Transformer, 它就和大语言模型共享了同一套形状和同一套工程。 下一章《流匹配与整流流》把路径掰直、少跑几步(流匹配); 更后面的《扩散语言模型》再把同样的主干用在离散文本上。

一句话带走潜空间扩散与 DiT

没人真的在像素上跑一千步扩散——先用 VAE 压到 1/48,在那个小空间里扩散,最后放大回来。 省下的算力:卷积类 64 倍、注意力类 4096 倍。代价是 VAE 成了画质天花板。
DiT 把主干从 U-Net 换成了纯 Transformer:patch 化潜特征、全程单分辨率、 用 adaLN-Zero 注入条件。把残差门的初始值设为 0,等于让 12 层网络一开始就是 0 层—— 这是它训练稳定的关键。
真正的大事件不是"指标涨了",而是扩散模型的主干变成了一个标准 Transformer—— 从此可以共享 LLM 的全部工程基建;只是路径弯、步数多,下一章把路掰直。

10

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式