joelpaulkoch

joelpaulkoch

I’ve been working on adding ControlNet support to bumblebee.

This picture shows how this works. Basically, a ControlNet is only the encoding part of a UNet, and its residuals get passed as additional input to the regular UNet of Stable Diffusion.

The point I struggle with is how to pass the residuals to the UNet.
The ControlNet outputs a tuple of residuals of different shape, something like:

{
  #Nx.Tensor<f32[1][64][64][320]>,
  #Nx.Tensor<f32[1][64][64][320]>,
  #Nx.Tensor<f32[1][64][64][320]>,
  #Nx.Tensor<f32[1][32][32][320]>,
  #Nx.Tensor<f32[1][32][32][640]>,
  #Nx.Tensor<f32[1][32][32][640]>,
  #Nx.Tensor<f32[1][16][16][640]>,
  #Nx.Tensor<f32[1][16][16][1280]>,
  #Nx.Tensor<f32[1][16][16][1280]>,
  #Nx.Tensor<f32[1][8][8][1280]>,
  #Nx.Tensor<f32[1][8][8][1280]>,
  #Nx.Tensor<f32[1][8][8][1280]>,
}

The exact tuple size and tensor shapes depend on the configuration.
Now, I want to pass the tuple as input to the UNet, but with Axon.input I can only specify the input shape to be like a tensor.

For now, I solved this by calculating the shapes of the tensors of the tuple from the configuration and then creating a list with a single input per tensor:

  defp inputs_with_controlnet(spec) do
    sample_shape = {nil, spec.sample_size, spec.sample_size, spec.in_channels}

    {mid_spatial, out_shapes} = mid_spatial_and_residual_shapes(spec)

    down_residuals =
      for {shape, i} <- Enum.with_index(out_shapes) do
        Axon.input("controlnet_down_residual_#{i}", shape: shape)
      end

    mid_dim = List.last(spec.hidden_sizes)

    mid_residual_shape = {nil, mid_spatial, mid_spatial, mid_dim}

    Bumblebee.Utils.Model.inputs_to_map(
      [
        Axon.input("sample", shape: sample_shape),
        Axon.input("timestep", shape: {}),
        Axon.input("encoder_hidden_state", shape: {nil, nil, spec.cross_attention_size}),
        Axon.input("controlnet_mid_residual", shape: mid_residual_shape)
      ] ++ down_residuals
    )
  end

However, I feel like I’m missing something that would make this part way more “idiomatic”.
Is there a better way?

Marked As Solved

jonatanklosko

jonatanklosko

Creator of Livebook

@joelpaulkoch the input can be a tuple (more generally, any Nx.Container). Note that the :shape option is just an additional validation, but it’s not required : )

Also Liked

jonatanklosko

jonatanklosko

Creator of Livebook

Ah yeah, there are two contexts, the outer Axon graph and the inner Nx. If you have an Axon.container node and pass it as input to Axon.layer, the Nx function gets the actual container content (like a tuple):

x = Axon.input("x")
container = Axon.container({x, x})

Axon.layer(
  fn {x, x}, _opts ->
    x + x
  end
  [container]
)

Going the other way is a bit more awkward. If the current Axon node returns a map, you can “pick” a specific field with |> Axon.nx(& &1.key), and now it returns the specific key. However, there is no way to automatically unwrap the inner map to a map of Axon nodes, you need to do by hand with %{key1: Axon.nx(x, & &1.key1), key2: Axon.nx(x, & &1.key2)}. With tuples it’s analogous, except you use elem/2.

The reason we can’t unwrap the “inner” tuple is that we don’t actually know the shape, we only know when compiling. Consider this contrived example:

x = Axon.input("x")

model =
  Axon.layer(
    fn x, _opts ->
      case Nx.shape(x) do
        {_} -> %{key1: x}
        {_, _} -> %{key2: x}
      end
    end
    [x]
  )

There is no way we could do Magic.unwrap(model), because we have no idea how the map looks. It is only known once we compile the model with a specific input template.

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews