6.4 扩散模型(二)Latent Diffusion与Stable Diffusion

1.像素空间里跑扩散,图一大就吃不消

上一章我们聊了DDPM,知道了它怎么在前向过程里一步步往图里加噪声,再训练一个网络学着把噪声一点点去掉。思路是干净的,可一旦真拿去生成稍微大一点的图,麻烦就跟着来了。

DDPM直接在像素空间里做扩散。什么叫像素空间呢,就是说网络处理的对象就是一张张完整的图,每个像素都参与运算。我们记输入图的高为 HH、宽为 WW,再记通道数为 CC(彩色图就是RGB三个通道),那么一张图在网络眼里其实就是 H×W×CH\times W\times C 个数排成的张量。拿一张不算大的图举例,H=W=512H=W=512C=3C=3,那就是 512×512×37.9×105512\times512\times3\approx7.9\times10^5 个数。

扩散还得跑很多步。我们把总的去噪步数记作 TTTT 通常取 10001000 这个量级。每一步都要让U-Net在这张维度极大(约80万个数)的张量上跑一次前向。换句话说,每生成一张图,网络就得在这张大图上来来回回算 TT 次。训练的时候更费,每个训练步里,每张样本图都得让网络从头到尾算一遍。算力和显存都吃得很紧,普通研究者或者小团队根本扛不住。

我记得小明刚接触这块的时候,兴冲冲在自己的显卡上跑DDPM生成 256×256256\times256 的小图,一张要等上好几分钟,显存还动不动就报警。想把分辨率提到 512512,那张显卡直接就趴下了。说起来,这套直接在像素上做扩散的搞法,思路本身没什么问题,只是算力成本太重,总得想个法子把它降下来。

2.先压缩再扩散:Latent Diffusion的破局思路

破局的法子其实朴素,就一句话,别在像素上扩散,先压一压再说。

这就轮到自编码器出场了。自编码器我们在6.1 节聊VAE的时候仔细讲过,它的本事就是把一张高维的图压到一个低维得多的潜在表示上,再从这个潜在表示尽量还原出原图。我们记编码器为 E\mathcal{E},它把输入图 xx 压成潜在表示 zz,写成 z=E(x)z=\mathcal{E}(x)。再记解码器为 D\mathcal{D},它把 zz 还原回图,写成 x^=D(z)\hat{x}=\mathcal{D}(z)x^\hat{x} 头顶那个小帽子表示它是还原出来的版本,和原图 xx 会有一点出入,但很接近。

精妙的地方在于,zz 所在的空间叫做潜在空间,它的维度比像素空间小得多。我们记潜在表示的空间高为 hh、宽为 ww,Latent Diffusion通常会做 88 倍的空间下采样,也就是把长宽各压到原来的八分之一,于是 512×512512\times512 的图压下来就是 64×6464\times64,再配上较小的通道数(一般取 44)。这么一算,潜在表示只有 64×64×41.6×10464\times64\times4\approx1.6\times10^4 个数,比像素空间的 7.9×1057.9\times10^5 个数小了将近五十倍。

压缩完了,扩散就换个地方做。前向加噪和反向去噪的整套流程,原封不动地搬进潜在空间里,处理的对象从满屏的像素换成了小巧的 zz。每一步的U-Net都作用在 zz 上,而不是 xx 上。我们用 ztz_t 表示第 tt 步的潜在变量,tt 是扩散步的编号,从 11TT,那么整个去噪过程就是从纯噪声 zTz_T 一路走到 z0z_0,再把 z0z_0 丢给解码器 D\mathcal{D} 还原成图。

这好比什么呢,我说个比方。江南绣娘绣一面大屏风,要是每一针都直接在整面屏风上比划,又费眼又费时。聪明的做法是先把图样描在一方小绢上,针脚走势、配色浓淡都在小绢上反复推敲,等小样定稿了,再放大描回大屏风上正式落针。Latent Diffusion就是这个理,重的活在小空间里磨,磨好了再放回到大空间里去。

算力和显存的下降是实打实的。同一张显卡,跑潜在空间里的扩散能轻松上 512×512512\times512 甚至更大的分辨率,这在像素空间里是想都不敢想的事。说穿了,Latent Diffusion把扩散的场地从像素空间挪到了潜在空间,扩散的内核原样保留。

3.Stable Diffusion:把Latent Diffusion做成能用的文生图

Latent Diffusion是框架,是思路。真正让这套思路飞入寻常百姓家的,是Stable Diffusion。

Stable Diffusion把Latent Diffusion做成了一个完整开源的文生图模型。所谓文生图,就是用户输入一句描述,模型输出一张符合描述的图。这里的关键,是怎么把用户的这句话变成模型能听懂的引导信号。

答案是用文本做条件,靠CLIP的文本编码器来帮忙。CLIP我们在4.10 节讲过,是OpenAI提出的一个把图像和文本拉到同一个向量空间里的模型,图像和文本挨得近的,就被认为语义上是匹配的。它里面有一个文本编码器,能把一段文字变成一组向量。我们记用户输入的描述为 pppp 就是我们常说的提示词,文本编码器把它编码成条件向量 cccc 就是用来引导扩散的那个条件。

引导具体是怎么发生的呢。去噪用的U-Net在这一步不再只看噪声,还要看 cc。我们记去噪网络为 ϵθ\epsilon_\thetaϵ\epsilon 表示它要预测的噪声,θ\theta 是网络里所有可学习参数的统称,下标注明这是一个带参数 θ\theta 的网络。加入条件之后,它在第 tt 步预测的是给定条件 cc 下的噪声,记作 ϵθ(zt,t,c)\epsilon_\theta(z_t, t, c),括号里依次是当前的潜在变量 ztz_t、当前的步数 tt、以及条件 cc。这样一来,去噪的每一步都被往符合提示词的方向轻轻推一把,跑完 TT 步,得到的潜在图就长成了用户描述的那个样子。

文本编码器之所以能挑大梁,背后还藏着3.4 节讲过的Transformer。CLIP的文本编码器本质上就是一个Transformer,靠自注意力把一句话里前后相关的词都串起来理解。所以一句看似简单的描述,到了模型里其实经过了相当细致的语义解析,哪个词修饰哪个词,模型都得琢磨清楚。

4.整条文生图流程串一遍

我们把整条链路从头到尾走一遍,这一段值得你自己也多过几遍。

用户在输入框里敲下提示词 pp,CLIP的文本编码器把它变成条件向量 cc。与此同时,潜在空间里准备好一份纯噪声 zTz_T,这就是扩散的起点。然后去噪开始,从第 TT 步往第 11 步走,每一步U-Net都同时吃进当前的 ztz_t、步数 tt 和条件 cc,预测出本步添加的噪声,再从 ztz_t 里把这个噪声减掉一小份,得到稍微干净一点的 zt1z_{t-1}。这样一步一步走完 TT 步,噪声几乎被磨干净,得到一张干净的潜在图 z0z_0。最后,把 z0z_0 交给解码器 D\mathcal{D},还原成像素空间里人眼能看的图 x^\hat{x}。整个过程里,文本向量 cc 始终在场,每一步都在悄悄校准方向。

这条链路其实你去大厂面试时多半会被问到。能把上面这几步说顺,从文本编码到潜在去噪再到解码还原,面试官大概就觉得你对Stable Diffusion算是真懂了。我之前翻过一本讲生成模型的入门读物,作者把这套流程比作一位画师作画,文本编码器是听委托人讲诉求的耳朵,潜在空间里一步一步的去噪是勾形上色的画笔,解码器则是把草稿誊成成品的装裱工序,我觉得这比喻贴切得很。

5.开源之后:一个生态的勃发

Stable Diffusion真正了不起的地方,在于它把一整套训练好的权重和代码都开源了出来。在这之前,能做文生图的模型大多攥在大公司手里,外面的人只能看演示,没法上手。Stable Diffusion一开源,研究者、设计师、独立开发者,一下子全都涌进来了。

围绕它长出了很多二次开发的方向。ControlNet能在生成时额外约束构图,比如沿着用户给的草图线条走,或者按指定的人体姿态摆造型,对设计师来说太趁手了。LoRA则是一种轻量的微调方法,只需要在原模型上挂一小块参数,就能把画风换成水墨、二次元或某种特定的摄影质感。1.4 节我们讲KL散度的时候提过,衡量两个分布的差异是生成模型里的家常便饭,LoRA训练时也离不开这类工具。

应用层面更是百花齐放。做插画的小张拿它批量出草稿,搞影视的概念设计师用它做分镜前的视觉探索,医学影像的研究者尝试拿扩散模型去补全残缺的片子,艺术圈甚至有人专门办了AI生成的展览。说起来,一个开源模型能在这么多行当里都激起水花,这件事本身就值得玩味。

当然,问题也跟着来了。版权归属、训练数据里的人脸隐私、生成内容被拿来造假,这些争议至今没个定论。技术给了我们一把趁手的工具,怎么用好它,是另一门更需要耐心的学问。

小结

这一篇我们顺着Latent Diffusion和Stable Diffusion这条线,把扩散模型从像素空间搬到了潜在空间,又把文本条件接了进来。要点记一下:自编码器先把图压到低维潜在空间,扩散在潜在空间里跑完,CLIP文本侧把提示词变成条件向量去引导每一步去噪,最后解码器还原出图。算力省下来是这一切成立的底子,开源的格局则让它真正长成了一个生态。

下一篇我们继续往更新的方向走,聊聊扩散之后的又一支新生力量。今天就先到这儿,下一篇见。

练习

Q1. Latent Diffusion为什么要"先压缩再扩散",而不是直接在像素空间做扩散?

因为像素空间太大、太贵。一张 512×512×3512\times512\times3 的图在网络眼里是约80万个数,扩散还要跑 TT(约1000)步,每步U-Net都要在这张大图上来回算一次,训练时每张样本图都得从头算一遍,算力和显存都吃得很紧,普通研究者根本扛不住。Latent Diffusion先用一个自编码器把图压到低维潜在空间(8倍下采样后 64×64×41.664\times64\times4\approx1.6万个数,比像素空间小近五十倍),再把整套加噪去噪搬进潜在空间做,处理对象从满屏像素换成小巧的 zz,算力显存大幅下降,512×512512\times512 甚至更大分辨率都能轻松上。

Q2. Stable Diffusion生成图时,文本提示词 pp 是怎么参与到每一步去噪的?

文本提示词 pp 先过CLIP的文本编码器变成条件向量 cc(本质是个Transformer,靠自注意力把一句话里前后相关的词串起来理解)。然后去噪U-Net在第 tt 步不再只看噪声,而是同时吃进当前的潜在变量 ztz_t、步数 tt 和条件 cc,预测的是给定条件 cc 下的噪声 ϵθ(zt,t,c)\epsilon_\theta(z_t,t,c)。这样每一步去噪都被往符合提示词的方向轻轻推一把,跑完 TT 步,得到的潜在图就长成用户描述的样子。最后 z0z_0 再交给解码器还原成像素空间的图。

Q3. 易错点:Stable Diffusion和原始DDPM在"在哪做扩散"和"怎么把文本接进来"这两点上有什么根本不同?

两个根本不同。第一,在哪做扩散:原始DDPM直接在像素空间做扩散,处理对象是整张图(维度极大、算力显存吃紧);Stable Diffusion(Latent Diffusion的落地)先压缩再扩散,在低维潜在空间里做,处理对象是 zz,省算力。第二,怎么接文本:原始DDPM是无条件生成,给个噪声就出图、生成什么全看运气;Stable Diffusion把CLIP文本编码器接进来,提示词 pp 编码成条件向量 cc,每一步去噪都受 cc 引导,让图长成用户描述的样子。所以Stable Diffusion=Latent Diffusion(换地方做)+CLIP文本条件(接引导),是在DDPM骨架上的工程改良。

相关标签
深度学习生成模型Stable DiffusionLatent Diffusion