defmodule Bumblebee.Diffusion.Layers.UNet do @moduledoc false alias Bumblebee.Layers alias Bumblebee.Diffusion import Bumblebee.Utils.Model, only: [join: 2] @doc """ Adds a feed-forward network on top of U-Net timestep embeddings. ## Options * `:name` - the base layer name * `:activation` - the activation used in-between the dense layers. Defaults to `:silu` """ def timestep_embedding_mlp(sample, embedding_size, opts \\ []) do opts = Keyword.validate!(opts, [:name, activation: :silu]) name = opts[:name] sample |> Axon.dense(embedding_size, name: join(name, "intermediate")) |> Axon.activation(opts[:activation]) |> Axon.dense(embedding_size, name: join(name, "output")) end @doc """ Adds a U-Net downsample block of the given type to the network. """ def down_block_2d( :cross_attention_down_block, sample, timestep_embedding, encoder_hidden_state, opts ) do down_block_2d(sample, timestep_embedding, encoder_hidden_state, opts) end def down_block_2d(:down_block, sample, timestep_embedding, _encoder_hidden_state, opts) do down_block_2d(sample, timestep_embedding, nil, opts) end @doc """ Adds a U-Net upsample block of the given type to the network. """ def up_block_2d( :cross_attention_up_block, sample, timestep_embedding, residuals, encoder_hidden_state, opts ) do up_block_2d(sample, timestep_embedding, residuals, encoder_hidden_state, opts) end def up_block_2d( :up_block, sample, timestep_embedding, residuals, _encoder_hidden_state, opts ) do up_block_2d(sample, timestep_embedding, residuals, nil, opts) end @doc """ Adds U-Net downsample block to the network. When `encoder_hidden_state` is not `nil`, applies cross-attention. """ def down_block_2d(hidden_state, timestep_embedding, encoder_hidden_state, opts \\ []) do in_channels = opts[:in_channels] out_channels = opts[:out_channels] dropout = opts[:dropout] || 0.0 depth = opts[:depth] || 1 activation = opts[:activation] || :swish norm_epsilon = opts[:norm_epsilon] || 1.0e-6 norm_num_groups = opts[:norm_num_groups] || 32 num_attention_heads = opts[:num_attention_heads] || 1 use_linear_projection = Keyword.get(opts, :use_linear_projection, false) output_scale_factor = opts[:output_scale_factor] || 1.0 downsample_padding = opts[:downsample_padding] || [{1, 1}, {1, 1}] add_downsample = Keyword.get(opts, :add_downsample, true) name = opts[:name] state = {hidden_state, {}} {hidden_state, output_states} = for idx <- 0..(depth - 1), reduce: state do {hidden_state, output_states} -> in_channels = if(idx == 0, do: in_channels, else: out_channels) hidden_state = Diffusion.Layers.residual_block( hidden_state, in_channels, out_channels, timestep_embedding: timestep_embedding, norm_epsilon: norm_epsilon, norm_num_groups: norm_num_groups, dropout: dropout, activation: activation, output_scale_factor: output_scale_factor, name: name |> join("residual_blocks") |> join(idx) ) hidden_state = if encoder_hidden_state do transformer_2d(hidden_state, encoder_hidden_state, hidden_size: out_channels, num_heads: num_attention_heads, use_linear_projection: use_linear_projection, depth: 1, name: name |> join("transformers") |> join(idx) ) else hidden_state end {hidden_state, Tuple.append(output_states, {hidden_state, out_channels})} end if add_downsample do hidden_state = Diffusion.Layers.downsample_2d(hidden_state, out_channels, padding: downsample_padding, name: join(name, "downsamples.0") ) {hidden_state, Tuple.append(output_states, {hidden_state, out_channels})} else {hidden_state, output_states} end end @doc """ Adds U-Net upsample block to the network. When `encoder_hidden_state` is not `nil`, applies cross-attention. """ def up_block_2d( hidden_state, timestep_embedding, residuals, encoder_hidden_state, opts ) do in_channels = opts[:in_channels] out_channels = opts[:out_channels] dropout = opts[:dropout] || 0.0 depth = opts[:depth] || 1 activation = opts[:activation] || :swish norm_epsilon = opts[:norm_epsilon] || 1.0e-6 norm_num_groups = opts[:norm_num_groups] || 32 num_attention_heads = opts[:num_attention_heads] || 1 use_linear_projection = Keyword.get(opts, :use_linear_projection, false) output_scale_factor = opts[:output_scale_factor] || 1.0 add_upsample = Keyword.get(opts, :add_upsample, true) name = opts[:name] ^depth = length(residuals) hidden_state = for {{residual, residual_channels}, idx} <- Enum.with_index(residuals), reduce: hidden_state do hidden_state -> in_channels = if(idx == 0, do: in_channels, else: out_channels) hidden_state = Axon.concatenate([hidden_state, residual], axis: -1) |> Diffusion.Layers.residual_block( in_channels + residual_channels, out_channels, timestep_embedding: timestep_embedding, norm_epsilon: norm_epsilon, norm_num_groups: norm_num_groups, dropout: dropout, activation: activation, output_scale_factor: output_scale_factor, name: name |> join("residual_blocks") |> join(idx) ) if encoder_hidden_state do transformer_2d(hidden_state, encoder_hidden_state, hidden_size: out_channels, num_heads: num_attention_heads, use_linear_projection: use_linear_projection, depth: 1, name: name |> join("transformers") |> join(idx) ) else hidden_state end end if add_upsample do Diffusion.Layers.upsample_2d(hidden_state, out_channels, name: join(name, "upsamples.0")) else hidden_state end end @doc """ Adds a U-Net middle block with cross attention to the network. """ def mid_cross_attention_block_2d( hidden_state, timestep_embedding, encoder_hidden_state, opts \\ [] ) do channels = opts[:channels] dropout = opts[:dropout] || 0.0 depth = opts[:depth] || 1 norm_epsilon = opts[:norm_epsilon] || 1.0e-6 activation = opts[:activation] || :swish norm_num_groups = opts[:norm_num_groups] || 32 num_attention_heads = opts[:num_attention_heads] || 1 use_linear_projection = Keyword.get(opts, :use_linear_projection, false) output_scale_factor = opts[:output_scale_factor] || 1.0 name = opts[:name] residual_block_opts = [ epsilon: norm_epsilon, num_groups: norm_num_groups, dropout: dropout, activation: activation, output_scale_factor: output_scale_factor ] hidden_state = Diffusion.Layers.residual_block( hidden_state, channels, channels, residual_block_opts ++ [timestep_embedding: timestep_embedding, name: join(name, "residual_blocks.0")] ) for idx <- 0..(depth - 1), reduce: hidden_state do hidden_state -> hidden_state |> transformer_2d( encoder_hidden_state, hidden_size: channels, num_heads: num_attention_heads, use_linear_projection: use_linear_projection, depth: 1, name: name |> join("transformers") |> join(idx) ) |> Diffusion.Layers.residual_block( channels, channels, residual_block_opts ++ [ timestep_embedding: timestep_embedding, name: name |> join("residual_blocks") |> join(idx + 1) ] ) end end defp transformer_2d(hidden_state, cross_hidden_state, opts) do hidden_size = opts[:hidden_size] num_heads = opts[:num_heads] use_linear_projection = opts[:use_linear_projection] depth = opts[:depth] || 1 dropout = opts[:dropout] || 0.0 name = opts[:name] shortcut = hidden_state flatten_spatial = &Axon.layer( fn hidden_state, shortcut, _opts -> {b, h, w, c} = Nx.shape(shortcut) Nx.reshape(hidden_state, {b, h * w, c}) end, [&1, shortcut] ) unflatten_spatial = &Axon.layer( fn hidden_state, shortcut, _opts -> Nx.reshape(hidden_state, Nx.shape(shortcut)) end, [&1, shortcut] ) hidden_state |> Axon.group_norm(32, epsilon: 1.0e-6, name: join(name, "norm")) |> then(fn hidden_state -> if use_linear_projection do hidden_state |> flatten_spatial.() |> Axon.dense(hidden_size, name: join(name, "input_projection")) else hidden_state |> Axon.conv(hidden_size, kernel_size: 1, strides: 1, padding: :valid, name: join(name, "input_projection") ) |> flatten_spatial.() end end) |> Layers.Transformer.blocks( cross_hidden_state: cross_hidden_state, num_blocks: depth, num_attention_heads: num_heads, hidden_size: hidden_size, query_use_bias: false, key_use_bias: false, value_use_bias: false, layer_norm: [ epsilon: 1.0e-5 ], dropout_rate: dropout, ffn: &ffn_geglu(&1, hidden_size, dropout: dropout, name: &2), block_type: :norm_first, name: join(name, "blocks") ) |> then(fn %{hidden_state: hidden_state} -> if use_linear_projection do hidden_state |> Axon.dense(hidden_size, name: join(name, "output_projection")) |> unflatten_spatial.() else hidden_state |> unflatten_spatial.() |> Axon.conv(hidden_size, kernel_size: 1, strides: 1, padding: :valid, name: join(name, "output_projection") ) end end) |> Axon.add(shortcut) end # A feed-forward network with GEGLU nonlinearity as in https://arxiv.org/abs/2002.05202 defp ffn_geglu(x, size, opts) do name = opts[:name] dropout = opts[:dropout] || 0.0 intermediate_size = 4 * size {x, gate} = x |> Axon.dense(intermediate_size * 2, name: join(name, "intermediate")) |> Axon.split(2, axis: -1) x = Axon.multiply(x, Axon.gelu(gate)) x |> Axon.dropout(rate: dropout, name: join(name, "dropout")) |> Axon.dense(size, name: join(name, "output")) end end