Skip to content

Stable Diffusion

tags:: Generative, AIGC, Tool, Diffusion alias:: SD, StableDiffusion

  • Paper: [2112.10752] High-Resolution Image Synthesis with Latent Diffusion Models
  • Code: GitHub - CompVis/stable-diffusion: A latent text-to-image diffusion model
    • GitHub - Stability-AI/stablediffusion: High-Resolution Image Synthesis with Latent Diffusion Models
    • ⬇️Latent Diffusion Models

      collapsed:: true
      • Latent Diffusion Models
      • 首先是整个大类的定义, 需要UNet, AutoEncoder, CLIPTextEmbedder这几个模型; latent scaling factor用来scale encoder encode的encodings, 再输入到UNet里面, n_steps表示我们实际要进行的diffusion steps, 两个linear是方差beta的schedule起始和结束的位置. Beta会在初始化中定义好, alpha也会定义好为之后做准备
      • image.png
      • Text conditioning 是用CLIP模型得到prompts string list以后得到的. Encode函数会把图片给到autoencoder去得到一个distribution, 然后我们从中sample一个, 但是最后还要乘上一个scaling factor. Decode函数会把反向diffusion sample到的z除以之前scale的参数给解码得到图片. 整个模型的forward就是调用UNet来预测该时间点前所使用的epsilon
    • ⬇️Autoencoder for Stable Diffusion

      collapsed:: true
      • Autoencoder for Stable Diffusion
      • This implements the auto-encoder model used to map between image space and latent space.
      • 这里的autoencoder有两种 embedding space. 一个是embedding space, 一个是quantised embedding space. 这里是因为SD使用了VQ-reg, 用vector quantisation来实现正则, 体现在代码中就使用一个11conv来变换了维度, (真正的VQ应该还会quantise函数调用, 从codebook里找到最接近的) 同时这个类其实是AutoencoderKL, 是一个VAE, 因此还从中采样得到一个posterior. 各自有各自的channels数量, 同时他们会有一半的channels是表示mean, 一半表示vairance
      • image.png
      • Encode 和 Decode过程包括首先使用encoder得到latent vector z, 再对z quantise到 quantised embedding space作为moments, 然后利用这个mean和variance来sample; decode则是得到quantised的z 对他进行反向映射回embedding space, 然后decode到图片
      • image.png
      • Encoder module 负责把给到的图片一点点缩小, 提取到最需要的特征作为latent z, 尺寸变小的过程中, channels数会越来越多, channel的增长是由channel_multipliers控制的
      • image.png
      • Encode过程中, 分辨率的变化取决于有多少个channel_multipliers, 即要变化多少次channel, 每次都会减半分辨率. 首先使用一个3311的conv2d不改变分辨率但是把channel数先变成第一个需要的channels数量. 对于每一个分辨率, 都会有数个定义好数量的resnet_block用来提取特征, 这个resnet list会首先把当前channel变成下一轮需要的channel数量, 然后后面几个resnet就保持channels不变了. 每个分辨率都会有这么一组resnet, 然后在最后一次downsample到一半的分辨率. 最后还会有mid blocks, 不改变分辨率和通道, 利用了两个resnet中间夹了一个attention. 最后normalise了一下然后用11卷积map到了需要的mean+variance embedding channel
      • image.png
      • Encoder的前向过程包括先把图片变成初始channel, 然后对于多个分辨率, 用多个resnet处理以后downsample, 最后经过mid处理 attention最后conv到mean+variance 的 embeeding space输出
      • image.png
      • Decoder 模块由于没有使用convTranspose2D, 所以整个过程就是反转一下encode的过程
      • image.png
      • image.png
      • Decoder的前向过程就是, 先把z变换成最后的那个channel数, 然后经过mid blocks attention然后再提升分辨率的过程中, 经过一系列的resnet以及upsample, 最后norm一下再map到图片的3channel
      • image.png
      • Gaussian Distribution是得到quantised embedding 以后利用其mean和variance来从中sample样本用的
      • image.png
      • Attention block 实现的是把沿着channel维度的像素们作为sequence, 一个feature map是一个sequence, 一共有像素个词, 每个像素词的embedding长度都是channels数. torch.einsum做的也是最后生成ixj的attention matrix, 最后沿着j维做横向的softmax得到加权百分比, 乘上各自的v最后得到att完了的全局att的v. 当然最后还有额外的11conv 和 residual connection.
      • image.png
      • Upsampling 和Downsampling的实现比较简单. Up是先直接使用interpolate nearest来2倍放大, 再用不改变分辩率的conv调整. Down的话在下面和右边填充了0(因为要pad1而不是两边都pad2才能实现刚好/2), 然后用3320conv变小
      • image.png
      • ResNet Block的实现也是简单的, 定义好in和out Channels后, norm, conv(in, out), norm, conv(out, out), 以及shortcut就行了(shortcut需要考虑到inout不同的情况就要11conv调一下). 这里注释一下比较神奇的是act在conv前面
      • image.png
    • ⬇️U-Net for Stable Diffusion

      collapsed:: true
      • U-Net for Stable Diffusion
      • 这个U-Net用到了attention, 是个spatialTransformer, 其构造和DDPM里面用到的很像, 可以去参考那边的也. 多的两个参数是tf
      • image.png
      • 下面这个部分定义了一下U-Net有多少个分辨率level, 造了一个timeembedding的MLP, 然后初始化了input_blocks作为左半边的ModuleList. TimestepEmbedSequential包裹住了第一个用来对齐channel用的conv2d, 原因是这个类可以自动识别里面是什么类型的function, 来自动分配所需要的feature map, 或是fm以及time embedding
      • image.png
      • 下面构造了U-Net的左半边, 一共有level个分辨率层, 每个分辨率层有n_res_block个resnet_block, 其中如果这里是需要用attention的话, 就会在ResBlock后面append上一个attention block, 这里是SpatialTransformer. 然后在最后一层进行downsample
      • image.png
      • 中间层由res, ST, res组成
      • image.png
      • U-Net的右半边就是左半边反一下
      • image.png
      • Time embedding和DDPM里的差不多
      • image.png
      • 前向过程就是左半边input_blocks里面的module一个个处理x, 然后并且记录下来, 用作右边加上去的skip connection. 注意input和output_blocks里的每一个module都会输入到t_emb, 也就是说里面的resblock都会用到t_emb
      • image.png
      • Sequential block for modules with different inputs 可以接收x, t, cond三种, 但是选择性地apply进其中的module中作为输入, 解决了不同signature的对齐输入问题
      • image.png
      • UP和Down sample和autoencoder类似
      • image.png
      • ResNet Block 也大同小异, 定义了in和out layers, 然后是skip_connection
      • image.png
      • forward过程就是先in, 加time embedding, 是一个channel一个值, 广播到这个channel所有像素, 然后out, 最后skip
      • image.png
    • ⬇️Transformer for Stable Diffusion

      collapsed:: true
      • Transformer for Stable Diffusion U-Net
      • Channels就是每个词的feature dim, 也作为模型的dimension, 多头注意力因此有n_heads, n_layers define the number of transformer layers, d_cond定义了conditional embedding的大小. 其中又定义了1x1的卷积用来处理进入的tensor, 以及堆砌了n_layers层的transformer block, 最后再加上一个线性变换, 只不过也用conv来进行了
      • image.png
      • Forward部分, spatialTransformer 只利用到了前一步resblock处理完的x和condition, 先对x norm 再线性变换, 再把形状变换成transformer能够处理的形式[batch, sequence, embedding], 然后输入到transformer_blocks中经过多层transformer处理, 最后变回原来的模样, 并且加一个residual
      • image.png
      • Transformer layer的basic block用的是cross attention, 但是在没有cond的情况下, 就会变成self-attention. 这个basic block由两个attention层组成, 第一个固定进行self-attention, 第二个则是会考虑到cond, 做的是cross-attention, 最后再有一个FFN. 和最普通的transformer非常类似, 类似的是decoder部分, 也是一个selfattention, cross attention, ffn, 然后每一层做完都会有add & norm
      • image.png
      • CrossAttention的定义主要还是包含了和传统transformer一样的部分, 有三个FC来map到qkv. 其中需要注意的是attention的dimension是head*d_head, 也就是model dimension
      • image.png
      • CrossAttention的forward过程就是先把x和cond变成qkv, 然后进行常规的attention操作, 这里把attention的具体过程定义到了一个另外的函数里面, 包括normal_attention 和另一个flash attention(会更快一点)
      • image.png
      • FlashAttention 会比较快一点
      • image.png
      • Normal Attention, 由于之前的qkv的fc map到的是n_heads x d_head大小的向量, 因此这里要把每个head的给分开来, 从 [batch_size, seq, d_attn] ->[batch_size, seq_len, n_heads, d_head], 然后生成的attention matrix也要是b个h个seq*seq. softmax后乘v得到最终的seq个h个v, 这里还是d_attn, 最后有一个mlp map回d_model来使得可以放到下一个attention里面
      • image.png
      • FFN, 对每个sequence里的词作同样的上下变换
      • image.png
    • ⬇️Base class for sampling algorithms

      collapsed:: true
      • Sampling algorithms for stable diffusion
      • 下面是基础Stable Diffusion sampler的构造模式
      • image.png
      • 首先有一个函数用于获取模型估计的epsilon, 输入的是x, t, c, CFG scale, 和uncond的cond例如None. x和t都会duplicate两份, 传入模型中获取con的估计的epsilon和uncon估计的epsilon, 然后由CFG公式得到最后的epsilon, 即指向x0的方向.
      • image.png
      • Sampling 和 painting的loop 定义是下图这样, 具体在DDIM中有解释过
      • image.png

    • ⬇️Stable Diffusion Code的全局认知

      collapsed:: true
      • Stable Diffusion在各个元件的实现上有一些细节上的差异
      • 需要UNet, AutoEncoder, CLIPTextEmbedder这几个模型;
      • latent scaling factor用来scale encoder encode的encodings, 再输入到UNet里面
      • Autoencoder 得到的latent z也是多channel的, 且设定上z有dim_z, 实际上会给到2*dim_z用来分别容纳mean和variance; 而且这个z还不是最终给到的latent embedding, 还要经过quantisation到quantised embedding space, 并且还要根据这个quantised embedding space 的mean和variance再sample 一个sample, 这个才是用来diffusion的东西 collapsed:: true
        • image.png
      • U-Net也是一个大改过的模型. 与DDPM相似的地方在于, 两个模型的结构写法都非常相似, 都是左半边, 中间, 右半边(几乎是左半边的反转版). 每个半边的循环都是resolution level为主要循环, 每个循环里有定义次数的resblock, 如果启用了attention的话就会在每个resblock后面紧跟着一个attention模块, 只不过两个版本的attention模块有点不一样. DDPM的attention是完全的selfattention, 每个像素通道作为一个词, 词向量长度就是channel数, 像素们作self-attention. 而SD中, attention化身为了spatialTransformer, 类似于传统的transformer decoder, 作为一整个模块出现, 而不是单单只是做一次attention就结束了, 包含了一次self-attention, 一次cross-attention(与cond做), 以及一个FFN, 基本上和transformer decoder一模一样 collapsed:: true
        • image.png
      • Autoencoder只是用来转换成潜在空间, U-Net是用来预测epsilon, 真正sampling地时候是可以使用不同的sampler的, sampler会从训练好的模型(LatentDiffusion model, 包含了定义好的总diffusion步数, beta, alpha等参数, 以及三个主要模型unet, autoencoder, clip)处得到定义好的总diffusion步数, beta, alpha等参数, 再用各个sampler的算法如DDPM, DDIM等来进行sample
    • ⬇️Text2Img

      collapsed:: true
      • Generate images using stable diffusion with a prompt
      • 读入模型, 和sampler. 用了utils里面的load_model函数, 这个函数里面加载了所有需要的sub model比如说autoencoder的encoder和decoder类, clip和unet, 然后实例化了一个latentDiffusion model其中包含了这些部分, 加载的statedict也是包含了这些子模型的参数的
      • image.png
      • call的时候需要给到 文件路径, 一次生成多少张图片(batch_size), prompt, 以及图片宽高数据和CFG scale. 有多少batch就copy多少个prompt来重复生成, CFG scale也在这里处理到, 大于1会有强guidance, 等于1的时候就是单单cond, uncon这里是“”. 用sampler sample就得到了latent space的去噪结果. 这里注意有一个很神奇的地方就是f是Image to latent space resolution reduction, 也就是说我们提前知道了img到latent会有八倍缩放, 所以我们用来sample地noise就是长宽除以f的大小, autoencoder会负责把1/8的latent恢复到原始的尺寸, 是自适应的, 由于是全卷积, autoencoder和unet都是自适应的
      • image.png
      • 主函数
      • image.png
    • ⬇️Img2Img

      collapsed:: true
      • Generate images using stable diffusion with a prompt from a given image
      • 与上面类似的初始化
      • image.png
      • call中增加了original image和strength, 也就是一个0-1的比例, 要从diffusion的第几步开始denoise 加噪的原图, 与之前的区别在于, 要用q_sample获取一个初始的noised original image, 然后把它放到paint里面sample
      • image.png
      • 主函数
      • image.png
    • ⬇️Impaint

      collapsed:: true
    • ⬇️Utils

      collapsed:: true