专业书籍精读 · Generative AI System Design Interview · 第 8 章
Generative AI System Design Interview · Ch 8 · Ali Aminian, Hao Sheng & Alex Xu · 2024
你在手机上敲一句「雪山下的小木屋,黄昏」,几秒后出来一张能当壁纸的图——这一章讲的就是这类系统背后那套东西。它要回答一个很具体的工程问题:怎么让机器画出一张又大又清楚的图,同时别把算力烧光。
教机器画画最管用的办法,居然是先教它怎么把画毁掉。拿一张真照片,一点点往上撒雪花点,撒上一千次,最后变成纯雪花屏。这个「毁掉」的过程简单到不用学,照着固定配方撒就行。然后训练一个网络只干一件事:看着一张带雪花的图,指出哪些是雪花。指出来减掉,图就干净一点点。
因为它把一件难事拆成了一千件容易事。让机器「一口气从空白画出一整张脸」太难了——上一章那套让两个网络互相较劲的老办法,经常练着练着就崩。但让它「把一张有点糊的图擦干净一点点」,这件事容易得多,而且练习材料要多少有多少:随便找张照片,自己撒一把雪花,标准答案就是你刚撒的那把,根本不用请人来标。
画画的过程于是变成:从一屏纯雪花出发,让网络擦一下、擦一下、再擦一下……几十下之后,一张图就从雪花里「显影」出来了。
问题来了:一张 512×512 的图有几十万个点,擦一下就要把这几十万个点全过一遍网络,要擦几十上百下。放在原尺寸上折腾,显卡直接跪。
这一章的关键招数是换张草稿纸:先另外训练一对「压缩员 / 还原员」——压缩员把照片压成一张巴掌大的小图(只有原来约五十分之一的信息量),还原员负责把小图放回原尺寸。然后那几十下擦拭全都在小草稿上做,做完再让还原员放大一次。算一次的开销就此掉了一个量级,这才让「家用显卡也能出图」成为可能。
代价也很实在:压缩是有损的,最细的笔画会被抹掉——所以这类图里的小字、手指、密集格栅特别容易画坏。
擦得多,细节稳、耗时长;擦得少,出图快、偶尔构图翻车。所以做产品时这是一个明码标价的旋钮:预览图少擦几下求快,最终稿多擦几下求稳。这比「换个更大的模型」要好用得多——它不用重训,线上随时能调。
把「画一张图」倒过来做成「一步步擦掉雪花」,一件难事就变成了一千件容易事;再把这些擦拭全搬到一张小草稿纸上完成,算力才降得下来。擦几下,是你能随时调的那个旋钮。
想进到具体机制、记号和示意图? → 切到精读版
这一章把扩散模型(diffusion model)做成一个能出高分辨率图的系统,核心命题反直觉:高分辨率的瓶颈不在「画得够不够细」,而在「你在多大的画布上重复那几十上百步计算」。答案因此分两半——先用「加噪 → 去噪」把一次性的生成拆成一长串容易学的小步(换来训练稳定),再用一个自编码器把这串小步整体搬进一张 64×64 的潜表示上算(换来算力可承受)。读者要建立的认知是:生成系统的设计变量不是「用哪个模型」,而是「画布多大、走多少步」。
t 越大图越糊,t=T 时基本是纯噪声。E 把图压成一小团数,解码器 D 再还原回图;训练目标就是「还原得像」。本章在全书 Part II「十个案例」里排第 7 个。上承 Ch7 用 GAN 做人脸——那条路让生成器与判别器对抗,训练不稳、容易模式崩塌;本章换成扩散路线,把「对抗」换成了一个朴素的回归任务。下启 Ch9 文生图:那一章是在本章这副骨架上接文本条件与无分类器引导。对应现实系统:Stable Diffusion、DALL·E 2、Imagen、Midjourney 这一整代出图产品。
两个痛,一个关于训不稳,一个关于算不起。
训不稳是上一章留下的:GAN 的目标是两个网络的博弈,谁强一点另一边的梯度就失效,分辨率越高越难配平,还可能模式崩塌(只会画那几张脸)。工程上的后果是训练过程不可预测——同一份代码同一份数据,换个随机种子可能就练废了,这对要排期、要复现的团队是致命的。
算不起是本章自己的:一张 512×512 的 RGB 图是 512×512×3 = 786,432 个数。扩散要把去噪网络跑 T 步,而最早的 DDPM 用的是 T=1000——意味着出一张图要做一千次全尺寸网络前向。更糟的是 U-Net 里的自注意力开销随位置数呈平方增长(O(n²),白话说就是「位置翻一倍,开销翻四倍」):512×512 有 26 万多个位置,在这个尺度上做全局注意力在工程上根本不成立。
不解决会怎样:你只能训出 64×64 的小图模型,再拿一串超分模型硬撑门面——每一级都要单独训练、单独部署,误差还会逐级累积。出图这件事会一直是「几家有大机房的公司才玩得起」。
扩散模型定义了两个方向相反的过程。前向:拿一张真图 x₀,按一份固定配方逐步掺进高斯噪声,走 T 步变成纯噪声 x_T。这一步不含任何可学参数——它就是个加法公式。反向:从一团纯噪声出发,一步步把噪声减掉,直到一张图显影出来;这一步才是网络要学的。
为什么这个改写是整章的地基?因为它让监督信号自带答案:随机抽一张图、随机抽一个时间步 t、自己把噪声掺进去——网络该输出什么(刚才掺进去的那团噪声)是已知的。不需要人工标注,也不需要一个对手网络来当裁判。GAN 之所以难训,正是因为它的「标准答案」来自另一个同时在变的网络;扩散把这个动态目标换成了静态目标。
训练目标朴素到不像一个「生成模型」:L = ‖ε − ε_θ(x_t, t)‖²。白话说就是——真正掺进去的那团噪声 ε,和网络看着带噪图 x_t 猜出来的 ε_θ,两者差多少,就是损失。一道普通的回归题,梯度稳、可复现、能按曲线排期,这正是它相对 GAN 的工程优势。
骨架用 U-Net:先一路下采样抓大结构、再一路上采样还原细节,同尺度之间用跳连把高频信息直接接过去——因为输入输出尺寸相同,它天然适合「逐像素预测」。时间步 t 被编成一个向量注入每个模块,等于告诉同一套权重:现在是第几步,该擦得多狠。这一点很关键:一千步共用一套参数,否则模型大小要乘以一千。
这是本章真正的转折。做法是先单独训一个自编码器:编码器 E 把 512×512×3 压成 64×64×4,解码器 D 负责还原。信息量从 786,432 个数降到 16,384 个数,压缩比 48:1。然后整个扩散过程只在潜表示上跑,出图时才解码一次。
为什么可行?自然图像的绝大部分比特花在人眼不敏感的高频纹理上,这部分交给一个确定性解码器复现就够了;扩散网络的容量应该花在语义和构图——画的是什么、摆在哪——而不是去复刻毛孔。顺带还解决了注意力那笔账:位置数从 26 万降到 4,096,平方级的开销跟着掉了三个数量级,全局注意力这才重新变得可行。
代价必须写清楚:编解码是有损的,压缩越激进(下采样因子 f=16、f=32)细小结构越容易糊掉。小字、人手、密集格栅这类高频规则纹理是重灾区——这一代出图模型的典型瑕疵,有相当一部分不是扩散网络画错了,而是解码器还原不回来。
潜空间把 512×512 变得可行,但产品要的是 1024×1024 甚至更大。两条路。
路线 A · 级联(cascaded):先在低分辨率生成一张小图,再串一到两级扩散超分模型逐级放大(如 64 → 256 → 1024)。好处是每一级都小、可以分别训练和替换,低分辨率那级能把算力集中在构图上。代价是误差逐级传递——第一级把手画错了,后面两级只会把错误的手放得更清楚——而且总耗时是各级步数之和,部署上也要管三个模型。
路线 B · 放大潜画布:管线不变,把潜表示从 64×64 换成 128×128(对应出图 1024×1024)。好处是一条管线端到端一致,没有级间接缝。代价是显存与每步耗时随潜表示面积线性涨——边长翻倍即面积四倍,而且模型往往要在目标分辨率上再微调一轮,否则会出现构图重复(同一个主体画两遍)。
现实里两条常混着用:主管线走路线 B 出到 1024,再挂一级轻量超分兜到 2048 以上。
原始 DDPM 采样要老老实实走完 T=1000 步。DDIM 把采样改写成一个可跳步的确定性过程,用 20–50 步就能拿到接近的质量——注意模型不用重训,换的只是采样器。
这对系统设计的意义被低估了:步数是整条管线里唯一一个能按请求实时调、且几乎线性换延迟和成本的参数。同一个权重,预览图走 20 步、最终稿走 50 步,成本差 2.5 倍,不需要部署第二个模型。相比之下换模型要重新加载权重、重新预热,分钟级起步。
另一半是批处理(batching):扩散的每一步都是同构的大矩阵运算,把多个请求的同一步打包送进 GPU 能把利用率拉满、显著抬高吞吐;但排批要等,会把单请求的 p99 抬上去。典型做法是给批设一个几十毫秒的等待上限,宁可批小一点也别让尾延迟失控。
这一章的灵魂是两个选择:在哪张画布上算,和算多少步。前者定成本上限与画质天花板,后者定单次请求的延迟。
表 1 · 四条生成路线:什么场景该选哪条
| 路线 | 训练稳定性 | 出图成本 / 延迟 | 画质上限 | 什么时候选它 |
|---|---|---|---|---|
| GAN | 差:对抗博弈难配平,易模式崩塌 | 最低:一次前向出图,毫秒级 | 单一领域(如人脸)可做到极高,开放领域吃力 | 领域窄、延迟苛刻、要跑在端上(实时换脸、头像风格化) |
| 像素空间扩散 | 好:普通回归目标 | 最高:每步都在 786k 个数上跑 | 高,但分辨率一上去就被算力卡死 | 基本只剩研究与低分辨率基线,生产上已被潜扩散取代 |
| 潜空间扩散 | 好:与像素扩散同一目标 | 中:每步约为像素空间的 1/48 | 高;细小高频细节受解码器限制 | 默认选项:开放领域文生图 / 图生图,单卡可部署 |
| 级联扩散 | 好,但要训 2–3 个模型 | 中高:总步数是各级之和 | 很高,超分级能补回锐度 | 要极高分辨率、且能接受多模型部署与级间误差传递 |
表 2 · 采样步数怎么定(同一份权重,线上可切)
| 步数档位 | 相对成本 | 典型问题 | 适合的场景 |
|---|---|---|---|
| 1–4 步(蒸馏模型) | ~1/25 | 细节与构图明显退化,需专门蒸馏过的权重 | 实时预览、边打字边出图、端上推理 |
| 20–30 步 | ~1/2 | 偶发构图不稳,复杂场景丢元素 | 交互式生成的默认档,用户在等的那一张 |
| 50 步以上 | 1× 基准 | 边际收益迅速递减,再加步数基本白花 | 最终稿、批量离线出图、对细节敏感的商用素材 |
还有一个容易被跳过的选型:下采样因子 f。f=4 还原更准但潜表示更大(算力涨 4 倍),f=8 是业界的平衡点,f=16 以上省算力但细节塌得厉害。选 f 等于在「解码器能还原多少」和「扩散要算多久」之间划线,而且一旦定下就很难改——它决定了整套扩散权重的输入形状。
这一章讲的不是某个模型的内部细节,而是2022 年之后整代出图产品共用的那副骨架:一个自编码器定画布,一个 U-Net 做去噪,一个采样器定步数。Stable Diffusion、DALL·E 2、Imagen、Midjourney 的差别主要落在数据、条件注入方式和分辨率策略上,这三块骨头是一样的。把这副骨架讲清楚,就能顺着往下解释 Ch9 的文生图、Ch10 的个性化、Ch11 的文生视频——它们都是往同一副骨架上挂东西。
面试里这一章最常被追问的是成本:给你每天 100 万张出图请求、单张 1024×1024,怎么定步数、怎么批、什么时候该上蒸馏权重、超分放在管线哪一级。能把「步数 × 潜表示面积 × 批大小」这笔账算到量级正确,比背出 DDPM 的推导有用得多。
guided-diffusion 开篇即写明「This is the codebase for Diffusion Models Beat GANs on Image Synthesis」,并公布各分辨率的 FID:64×64 为 2.07、256×256 为 4.59、512×512 为 7.72(经上采样后最好可到 3.85)。这是「扩散取代 GAN 成为主流路线」最常被引的那组数字。OpenAI · guided-diffusion 官方仓库(2021)· github.com/openai/guided-diffusion512×512 图上微调,并明确写着「runs on a GPU with at least 10GB VRAM」。10GB 显存这个数字,就是本章那套潜空间设计在现实里的兑现。CompVis · Stable Diffusion 官方仓库(2022)· github.com/CompVis/stable-diffusionf 或换解码器。① 核心命题:高分辨率的瓶颈不是画得细,而是你在多大的画布上重复几十上百步计算。
② 扩散把「一次生成」改写成「逐步去噪」:前向加噪不含可学参数,所以训练答案免费、不需要对手网络——这是它比 GAN 稳的根因。
③ 网络学的是一道减法题(预测被加进去的噪声),骨架用 U-Net,时间步 t 以嵌入注入,所有步共用一套权重。
④ 潜空间是本章转折:512×512×3 压到 64×64×4,48:1;注意力位置数从 26 万降到 4,096,平方级开销随之塌缩。
⑤ 代价是有损:小字、手指、密集纹理还原不回来——这类瑕疵常在解码器,不在扩散网络。
⑥ 上 1024 有两条路:级联超分(可分治、但误差逐级传递)vs 更大的潜画布(端到端一致、但显存与每步耗时按面积涨)。
⑦ 采样步数是唯一能在线实时调、线性换延迟的旋钮:预览 20 步、终稿 50 步,同一份权重;批处理拉吞吐但抬 p99。
⑧ 实证锚点:OpenAI 用 ImageNet FID(256×256 为 4.59)证明扩散胜过 GAN;Stable Diffusion 用 f=8 潜空间 + 860M U-Net 把出图拉到 10GB 显存的消费级显卡上。