VerifiedarXiv:2302.0554325 min
Diffusion · Fine-tuning

ControlNet: Adding Conditional Control to Text-to-Image Diffusion Models

Freeze a text-to-image model, train a copy of its encoder on edge maps or poses, and join the two through layers that start at zero.

ControlNet steers Stable Diffusion with an image such as an edge map, a depth map or a pose skeleton. A trainable copy of the U-Net's encoder reads that image and adds its outputs to the frozen model's skip connections. The convolutions that carry those outputs start with all-zero weights, so training begins from the unmodified model.

Explaining the paperAdding Conditional Control to Text-to-Image Diffusion ModelsZhang, Rao, Agrawala · ICCV 2023 · arXiv:2302.05543 ↗

The architectural change fits in one equation. Most of this page is about the initialization in that equation: what a layer with all-zero weights does to the first few gradient steps, and why the paper credits it with keeping the pretrained model intact.

Stable Diffusion turns a sentence into a 512×512 image, and a sentence is a poor way to say where things go. "A dancer, left arm raised, seen from slightly below" leaves the exact pose, the framing and the camera height to the model, and the user's only recourse is to reword the prompt and sample again. Image-to-image models solve the placement problem directly by learning a mapping from one kind of image to another (edge map to photo, pose skeleton to person, depth map to room), but the training sets for any one of those tasks are small. The paper puts the largest at about 100,000 examples, 50,000 times smaller than the LAION-5B collection Stable Diffusion was trained on.

ControlNet, posted by Lvmin Zhang, Anyi Rao and Maneesh Agrawala of Stanford in February 2023, adds such an image as an extra input to a pretrained text-to-image model without changing any of the model's weights. It works with Canny edge maps (the output of a classic edge-detection algorithm), straight-line maps, user scribbles, pose skeletons, segmentation maps, depth and surface-normal maps, with or without a text prompt. The ideas, in order:

  1. freeze each block of the network and give it a trainable twin that also reads the condition image;
  2. connect the twin to the frozen network through convolutions whose weights start at zero, and check that they still train;
  3. apply this to Stable Diffusion's U-Net, which fixes every tensor shape;
  4. train with Stable Diffusion's own loss, unchanged;
  5. at sampling time, decide how the condition interacts with classifier-free guidance, and add several ControlNets together.

Why not fine-tune Stable Diffusion directly

The obvious way to add an input is to widen the model's first layer to accept extra channels and continue training everything on pairs of (condition, image). Stability AI built its depth-to-image model this way, and the v1 of the ControlNet paper uses the same recipe as its baseline. With a large dataset it works. With tens of thousands of examples, continued training of an 860-million-parameter model runs into two problems the paper names: overfitting, where the model memorizes the small dataset, and catastrophic forgetting, where updating the weights for the new task overwrites what they encoded about everything else.

One known remedy is to train only a few new parameters, as LoRA (Hu et al., 2021) does by learning a low-rank change BABA to each weight matrix (see the LoRA explainer). The paper argues that this is too little capacity for in-the-wild condition images: reading a hand-drawn scribble, or a segmentation map with 150 classes, takes a deep network. The paper instead reuses the pretrained model's own encoder, which already turns an image into a stack of features at four resolutions, so a copy of it, initialized with the pretrained weights, makes a deep encoder for the condition image that starts out knowing what images look like.

How ControlNet works on one block

The construction is defined for any network block: a ResNet block, an attention block, a transformer layer. Call the block F\mathcal{F}, its trained parameters Θ\Theta, its input feature map xx and its output yy:

y=F(x; Θ)y = \mathcal{F}(x;\, \Theta)
(1)

In Stable Diffusion, xx and yy are 2D feature maps of shape h×w×ch \times w \times c (height, width, channels), for example 32×32×640 in the second level of the U-Net. ControlNet freezes Θ\Theta, makes a trainable copy with parameters Θc\Theta_c initialized to the same values, and connects the two with two convolution layers Z\mathcal{Z} whose weights and biases are all zero. A condition feature map cc, of the same shape as xx, enters through the first one:

yc=F(x;Θ)+Z(F(x+Z(c;Θz1); Θc); Θz2)y_c = \mathcal{F}(x;\Theta) + \mathcal{Z}\big(\mathcal{F}(x + \mathcal{Z}(c;\Theta_{z1});\, \Theta_c);\, \Theta_{z2}\big)yc=F(x;Θ)+Z(F(x+Z(c;Θz1); Θc); Θz2)\begin{aligned} y_c &= \mathcal{F}(x;\Theta) \\ &\quad + \mathcal{Z}\big(\mathcal{F}(x + \mathcal{Z}(c;\Theta_{z1});\, \Theta_c);\, \Theta_{z2}\big) \end{aligned}
(2)

Read it from the inside out. The condition passes through the first zero convolution, Z(c;Θz1)\mathcal{Z}(c;\Theta_{z1}), and is added to the block's normal input. The trainable copy runs on that sum. Its output passes through the second zero convolution and is added to the frozen block's output. The frozen path, F(x;Θ)\mathcal{F}(x;\Theta), is computed exactly as before; everything ControlNet contributes arrives through the second term.

Each Z\mathcal{Z} is a 1×1 convolution: at every pixel it multiplies the channel vector by a square matrix (as many output channels as input channels) and adds a bias, the same dense layer applied independently at each position. With its weights and biases at zero it outputs zero whatever its input, so before any training step both Z\mathcal{Z} terms vanish and

yc=yy_c = y
(3)

The copy is not idle at this point. Its input is x+0=xx + 0 = x and its weights equal Θ\Theta, so it computes exactly yy internally; the second zero convolution discards the result. A software engineer would recognize the arrangement as a new code path shipped behind a feature flag set to off: the production path is untouched on day one, and the new path is fully built and running. The analogy breaks at the flag: here it is the weights of Z(⋅;Θz2)\mathcal{Z}(\cdot;\Theta_{z2}), which gradient descent grows continuously from zero as training proceeds.

Why zero convolutions still learn

Initializing a layer to zero is normally avoided. If every weight in a network starts at the same value, every hidden unit computes the same function and receives the same gradient, and the units never differentiate. In ControlNet the zeros sit only at the two ends of the branch, and the units between them, the copy, have distinct pretrained weights, so different channels receive different gradients. The remaining question is whether any gradient reaches the branch, and the answer comes from the three partial derivatives of a 1×1 convolution.

Write the convolution at pixel pp and output channel ii, with input II, weight matrix WW and bias BB:

Z(I)p,i=Bi+∑jWi,j Ip,j\mathcal{Z}(I)_{p,i} = B_i + \sum_j W_{i,j}\, I_{p,j}
(4)
∂Zp,i∂Bi=1,∂Zp,i∂Wi,j=Ip,j,∂Zp,i∂Ip,j=Wi,j\frac{\partial \mathcal{Z}_{p,i}}{\partial B_i} = 1, \qquad \frac{\partial \mathcal{Z}_{p,i}}{\partial W_{i,j}} = I_{p,j}, \qquad \frac{\partial \mathcal{Z}_{p,i}}{\partial I_{p,j}} = W_{i,j}∂Zp,i∂Bi=1,∂Zp,i∂Wi,j=Ip,j,∂Zp,i∂Ip,j=Wi,j\begin{gathered} \frac{\partial \mathcal{Z}_{p,i}}{\partial B_i} = 1, \qquad \frac{\partial \mathcal{Z}_{p,i}}{\partial W_{i,j}} = I_{p,j}, \\ \frac{\partial \mathcal{Z}_{p,i}}{\partial I_{p,j}} = W_{i,j} \end{gathered}

At W=0W = 0 the third derivative is zero: no gradient passes backward through the layer to its input. The first two do not depend on WW, so the layer's own weights and bias get a nonzero gradient as long as its input is nonzero and the loss has some gradient at its output. Summing over pixels with the upstream gradient gp,i=∂L/∂Zp,ig_{p,i} = \partial L / \partial \mathcal{Z}_{p,i}:

∂L∂Wi,j=∑pgp,i Ip,j,∂L∂Ip,j=∑igp,i Wi,j=0\frac{\partial L}{\partial W_{i,j}} = \sum_p g_{p,i}\, I_{p,j}, \qquad \frac{\partial L}{\partial I_{p,j}} = \sum_i g_{p,i}\, W_{i,j} = 0∂L∂Wi,j=∑pgp,i Ip,j,∂L∂Ip,j=∑igp,i Wi,j=0\begin{gathered} \frac{\partial L}{\partial W_{i,j}} = \sum_p g_{p,i}\, I_{p,j}, \\ \frac{\partial L}{\partial I_{p,j}} = \sum_i g_{p,i}\, W_{i,j} = 0 \end{gathered}

Apply this to the two zero convolutions of equation (2) on the first training step:

  1. Z2, the output convolution. Its input is the copy's output, which equals yy and is nonzero. The upstream gradient is the loss gradient at ycy_c, nonzero unless the frozen model already fits the training target. So Wz2W_{z2} and Bz2B_{z2} get a nonzero gradient and move on step 1.
  2. The trainable copy. Its gradient arrives through Z2, whose derivative with respect to its input (the third one above) is Wz2=0W_{z2} = 0. The copy gets exactly zero gradient on step 1.
  3. Z1, the input convolution. Its own weight gradient is (upstream gradient) times cc, and its upstream gradient comes through the copy and Z2, so it is also zero on step 1.

After step 1, Wz2W_{z2} is no longer zero (with plain gradient descent at learning rate η\eta it equals −η-\eta times the first expression above), so from step 2 on gradient flows through it into the copy and on into Z1. The paper's first version derives the first-step update for a single zero convolution; the ordering across the two of them follows from the same derivatives. LoRA's initialization behaves the same way: with B=0B = 0 the product BABA is zero, BB gets a gradient on the first step, and AA gets none until BB has moved. The zero-initialized layer itself predates both papers; the ADM code of Dhariwal and Nichol wraps the last convolution of every residual block in a function called zero_module, which the ControlNet paper cites.

Figure 1 trains a toy ControlNet block in your browser: a 4-channel block standing in for F\mathcal{F}, a 2-channel condition, and 24 training pairs whose target is built from the pretrained block's own features. At init scale 0 and step 0, read the gradient under each box: Z2 has one and the other two print exactly 0. Step to 1 and all three are nonzero, while the copy's distance from the pretrained weights (right chart) is still 0, because its first update had a zero gradient. Then raise the init scale, which draws the two connecting layers from a standard random initialization scaled by the slider.

Figure 1 · the first gradient steps of a zero convolution
step 0
0 (zero conv)
Toy ControlNet block, Eq. (2), trained by plain gradient descent. Under each trainable box: the gradient norm it receives at the selected step. Teal curves are the run at the chosen init scale, amber dashed is the zero-init run for comparison. At scale 0 the output starts exactly at the frozen block's output (|y_c − y| = 0); any other scale starts it elsewhere and sends gradient into the copy on step 0.

With a random initialization, the copy's first gradient is computed through random weights in Z2, so the direction it is pushed in depends on that random draw rather than on what the condition requires, and it is pushed from the very first step, before Z2 has learned anything. At scale 1.5 in the toy, the loss at step 0 is twice the zero-init loss and the copy ends 40 steps about 2.7 times further from its pretrained weights than the zero-init run does. The paper calls this effect harmful noise reaching the pretrained backbone. Its evidence is an ablation on Stable Diffusion itself, in Section 4.2: replacing both zero convolutions with standard convolutions initialized with Gaussian weights dropped the results on a sketch condition to about the level of ControlNet-lite, a variant whose trainable copy is replaced by a single convolution layer per block. The authors attribute the drop to the pretrained backbone of the copy being destroyed during fine-tuning. The ablation is qualitative (random batches of six images for each setting, no metric), and the toy is too small to reproduce it; at small init scales the toy trains as well as zero init or better.

Attaching it to Stable Diffusion

Stable Diffusion is a latent diffusion model. A variational autoencoder compresses a 512×512×3 image into a 64×64×4 latent, and a U-Net learns to remove noise from latents; the text prompt enters through cross-attention as 77 token embeddings of size 768 from the CLIP text encoder, and the noise level enters as a sinusoidal timestep embedding. The U-Net has an encoder of 12 blocks that halves the resolution three times (64, 32, 16, 8), a middle block at 8×8, and a decoder of 12 blocks that doubles it back. Each encoder block's output is also sent across a skip connection and concatenated onto the input of the matching decoder block.

ControlNet treats the whole encoder plus middle block as the block F\mathcal{F} of equation (2). The trainable copy is a copy of all 13 (348,712,960 parameters, 40.6% of the U-Net's 859,520,964), receiving the same noisy latent, timestep and text embedding as the frozen encoder. Instead of one output convolution there are 13, one per copied block, each a 1×1 zero convolution with as many input as output channels. Their outputs are added to the 12 skip connections and to the output of the middle block, so every resolution of the decoder receives a condition-dependent correction.

The condition image has to be brought to the copy's working shape first. A 512×512×3 edge map goes through a small convolutional encoder E\mathcal{E}:

cf=E(ci)c_f = \mathcal{E}(c_i)
(5)

In the released code E\mathcal{E} is eight 3×3 convolutions with SiLU activations, three of them with stride 2 (512 → 256 → 128 → 64), ending in a zero-initialized 3×3 convolution that outputs 320 channels. Its output, 64×64×320, is added to the copy's features right after the copy's first convolution, which has the same shape. That final zero layer plays the part of Z1 in equation (2), and it is the only place the condition enters. (The paper's text describes E\mathcal{E} differently, with four stride-2 layers; four halvings of 512 give 32, not 64, and the Provenance panel has the details.)

Figure 2 lays out the 13 connections. Each row is one encoder block of the frozen U-Net, its skip connection to the decoder, the ControlNet's copy of the same block, and the zero convolution between them. Click a row for its shapes and the size of its zero convolution, and switch to the gradients view to see which parts of the network the backward pass touches.

Figure 2 · ControlNet on Stable Diffusion 1.5's U-Net
4
One row per connection: input blocks 0–11 and the middle block. The trainable copy's output passes a zero convolution and is added (the ⊕) to the skip connection from the frozen encoder to decoder block 11 − i. Gradients shows what the backward pass reaches; inference scales compares the paper's resolution weights with the code's guess-mode scales (discussed below).

The frozen encoder never needs a backward pass. Nothing upstream of it is trainable, so the official code runs it inside torch.no_grad() and stores none of its activations for the backward pass. The frozen decoder is different: the control is added to its inputs, so the backward pass has to run through the decoder to reach the ControlNet, even though no decoder weight is updated. The paper measures the net cost on one A100 PCIe 40GB against training Stable Diffusion itself: about 23% more GPU memory and 34% more time per iteration. The whole ControlNet for SD 1.5, copy plus zero convolutions plus E\mathcal{E}, has 361,279,120 parameters, all of them trained. The 13 zero convolutions account for 11,479,680 of those, from 102,720 each at 320 channels to 1,639,680 each at 1,280.

Training: the same loss, and a sudden jump

ControlNet is trained with the loss Stable Diffusion was trained with. Take a training image's latent z0z_0, pick a timestep tt between 0 and 999, and mix in Gaussian noise ϵ\epsilon at the level the schedule assigns to tt:

zt=αˉt z0+1−αˉt ϵz_t = \sqrt{\bar\alpha_t}\, z_0 + \sqrt{1-\bar\alpha_t}\,\epsilon

Here αˉt\bar\alpha_t falls from nearly 1 at t=0t = 0 to 0.0047 at t=999t = 999 under Stable Diffusion's schedule; at t=500t = 500 the latent is 0.526 parts signal to 0.851 parts noise. The network predicts the noise, given the timestep, the text prompt ctc_t and the condition cfc_f, and is scored by squared error, as in DDPM:

L=Ez0,t,ct,cf,ϵ∼N(0,I)[∥ϵ−ϵθ(zt,t,ct,cf)∥22]\mathcal{L} = \mathbb{E}_{z_0, t, c_t, c_f, \epsilon \sim \mathcal{N}(0, I)}\Big[\big\| \epsilon - \epsilon_\theta(z_t, t, c_t, c_f) \big\|_2^2\Big]L=Ez0,t,ct,cf,ϵ∼N(0,I)[∥ϵ−ϵθ(zt,t,ct,cf)∥22]\begin{aligned} \mathcal{L} &= \mathbb{E}_{z_0, t, c_t, c_f, \epsilon \sim \mathcal{N}(0, I)} \\ &\quad \Big[\big\| \epsilon - \epsilon_\theta(z_t, t, c_t, c_f) \big\|_2^2\Big] \end{aligned}
(6)

This is equation (5) of the paper (renumbered here because this page numbers the 1×1 convolution separately). Only ϵθ\epsilon_\theta has changed: it is now the frozen U-Net plus the ControlNet, and only the ControlNet's parameters are updated. A dataset is a set of triples (image, caption, condition) where the condition is computed from the image by an off-the-shelf detector: Canny edges, HED soft edges, MiDaS depth, OpenPose skeletons, and so on. For the Canny model, the v1 paper reports 3 million such triples and 600 A100 GPU-hours; for surface normals, 25,452 triples from the DIODE dataset and 100 GPU-hours.

The training data differs from Stable Diffusion's in one respect: 50% of the captions are replaced by the empty string. With no prompt, the only evidence about what the image shows is the condition image, so the ControlNet must learn to recognize that an edge map depicts a house or that a depth map depicts a room. The paper reports that this is what lets the model produce sensible images from a condition with no prompt at all.

The authors also report an observation they call the sudden convergence phenomenon. Because the zero convolutions leave the model unchanged at the start, samples drawn during training are ordinary Stable Diffusion images from the first step on, and for thousands of steps they ignore the condition. Then, within a short span, they start following it. In the run the paper shows (learning rate 1e-5, AdamW, no exponential moving average), samples at 6,100 steps still ignore the condition and samples at 6,133 steps follow it; the paper says this usually happens before 10,000 steps. It is an observation from sample grids, with no loss curve or explanation offered.

Guidance, strength and stacking ControlNets

Stable Diffusion samples with classifier-free guidance. Each denoising step runs the network twice, once with the prompt (ϵc\epsilon_c) and once with an empty or negative prompt (ϵuc\epsilon_{uc}), and extrapolates from the second past the first:

ϵprd=ϵuc+βcfg (ϵc−ϵuc)\epsilon_{prd} = \epsilon_{uc} + \beta_{cfg}\,(\epsilon_c - \epsilon_{uc})
(7)

The paper's experiments use βcfg=9\beta_{cfg} = 9 with 20 steps of DDIM, a deterministic sampler that skips most of the 1,000 noise levels used in training. A ControlNet raises the question of which of the two passes receives the condition image. If both do, the condition is present on both sides of the difference ϵc−ϵuc\epsilon_c - \epsilon_{uc}, which then measures the prompt's effect with the condition held fixed, and the condition's own effect is applied once, unamplified. If only ϵc\epsilon_c does, the condition's effect sits inside the difference and is multiplied by βcfg\beta_{cfg}. With an empty prompt and the condition in both passes, the two passes get identical inputs, so ϵc=ϵuc\epsilon_c = \epsilon_{uc} exactly and guidance does nothing, which the paper describes as completely removing guidance (its Figure 5b). With an empty prompt and the condition only in ϵc\epsilon_c, the condition's effect is multiplied by 9, which the paper describes as very strong guidance (Figure 5c).

Figure 3 makes this concrete with a toy linear model in two dimensions, where the prompt pushes the prediction right and the condition pushes it up. It opens with a prompt and the condition in both branches; switch the prompt to empty and move the guidance slider, and nothing moves. Then put the condition in ϵc\epsilon_c only and watch the condition's multiplier track βcfg\beta_{cfg}.

Figure 3 · where the condition goes under guidance
9.0
1.00
Toy linear model: ε_uc (white), ε_c and the guided ε_prd on the ray from ε_uc through ε_c. The readout gives how many times the prompt's and the condition's effects appear in ε_prd. Real denoisers are not linear; the exact case is the empty prompt with the condition in both branches, where both passes are identical.

The paper's fix, CFG resolution weighting, feeds the condition to ϵc\epsilon_c only and multiplies each of the 13 connections by a weight wi=64/hiw_i = 64/h_i, where hih_i is the block's resolution: 1 for the 64×64 connections, 2, 4, and 8 for the 8×8 ones. The released demo implements the same idea under the name guess mode, with a different formula: the condition is dropped from the unconditional pass and connection ii (0 to 12, the middle block last) is scaled by 0.82512−i0.825^{12-i}, from 0.0994 at the first 64×64 block to 1.0 at the middle block. Both give the low-resolution connections roughly 8 to 10 times the weight of the high-resolution ones. The absolute scales differ, since the paper's weights are at least 1 and the code's at most 1, and the paper gives no reason for its choice of 64/h beyond the images in its Figure 5d. The inference-scales view of Figure 2 plots both. Outside guess mode, the demo puts the condition in both passes and multiplies all 13 outputs by a single control strength, 1.0 by default.

Several ControlNets combine by addition. A pose ControlNet and a depth ControlNet each produce their 13 maps; the maps are summed per connection and added to the same skip connections. The paper reports that no extra weighting is needed for this. The same additivity lets a ControlNet trained on SD 1.5 drive community models fine-tuned from SD 1.5, which keep its architecture: the paper shows this for Comic Diffusion and Protogen 3.4 without retraining, as images only.

What the experiments show

The paper's quantitative evaluation is three tables and a user study. Figure 4 plots the three tables.

Figure 4 · results
The paper's Tables 1–3. Whiskers are the reported standard deviations. Click a bar for what the method is. In the CLIP view the aesthetic score is divided by 25 to share the axis; the labels give the raw values.

The paper also compares against an industrial model. Stability AI's SD 2 depth-to-image model was trained on more than 12 million images with thousands of A100 GPU-hours. The authors trained a depth ControlNet for SD 2 on 200,000 images on one RTX 3090 Ti for 5 days, showed 12 users 100 images from each model to learn the difference, then asked them to attribute 200 new images. The users' average precision was 0.52 (standard deviation 0.17), about what guessing gives.

On training-set size, ControlNets trained on as few as 1,000 images do not collapse and still produce a recognizable lion for a lion edge map, and results improve as the subset grows; the paper shows this as sample images, without a metric.

What the paper leaves open

The sudden convergence is unexplained. The zero initialization accounts for the clean images before the jump (the model is plain Stable Diffusion until the connections grow), but the paper offers no reason for the transition being abrupt rather than gradual, and it shows no loss curve.

The human evaluations are small. Table 1 rests on 12 users and 20 sketches; the text calls the metric Average Human Ranking and the table caption calls it Average User Ranking. The text says the study yields 100 rankings per criterion, a count equal to 20 sketches times 5 methods; it does not say how the 12 users' rankings combine into it. The key ablations, removing the zero convolutions or shrinking the copy, are shown as image grids without a number.

The ControlNet adds inference cost. It reads the current noisy latent and timestep, so it runs at every denoising step, adding a 361-million-parameter encoder pass to each call of an 860-million-parameter U-Net, and with the condition in both guidance branches it runs twice per step; stacking two ControlNets doubles the added cost.

When the condition is ambiguous and the prompt does not resolve it, the model guesses: the paper's Figure 11 shows it interpreting a bare shape as different objects, and v1 lists generating the wrong content after a wrong reading of the condition as its limitation.

Training and sampling in pseudocode

The pseudocode below is one training step with the shapes of the official SD 1.5 setup. The VAE and the CLIP text encoder are frozen and run without gradients; the weight update touches only the ControlNet.

# One ControlNet training step (SD 1.5, 512x512, batch of 4)
img, edges, caption = batch()          # img [4,512,512,3] in [-1,1]
                                       # edges [4,512,512,3] in [0,1]
if rand() < 0.5: caption = ""          # 50% empty prompts (Sec. 3.3)
with no_grad():
    z0  = 0.18215 * vae.encode(img)    # [4,64,64,4]  latent image
    txt = clip_text(caption)           # [4,77,768]
t   = randint(0, 1000, size=4)         # one noise level per image
eps = randn(4, 64, 64, 4)
zt  = sqrt(abar[t]) * z0 + sqrt(1 - abar[t]) * eps
# ControlNet: copy of the encoder + middle block, reads zt, t, txt and edges
ctrl = controlnet(zt, t, txt, hint=edges)   # 13 maps, 64x64x320 ... 8x8x1280
# locked U-Net: encoder under no_grad, ctrl added to its 13 connections
eps_hat = unet(zt, t, txt, control=ctrl)    # [4,64,64,4]
loss = mean((eps - eps_hat) ** 2)           # Eq. (5), SD's own loss
loss.backward()            # weight gradients reach the ControlNet only
adamw_step(controlnet.parameters(), lr=1e-5)

Sampling runs the ControlNet inside every guided step; the two branches below are the demo's default and its guess mode.

# Sampling with one ControlNet: DDIM, 20 steps, guidance 9.0
txt_c  = clip_text(prompt)
txt_uc = clip_text(negative_prompt)          # often ""
z = randn(1, 64, 64, 4)
for t in ddim_timesteps(20):                 # 999 ... 0
    ctrl = [s * m for s, m in zip(scales, controlnet(z, t, txt_c, edges))]
    eps_c = unet(z, t, txt_c, control=ctrl)
    if guess_mode:                            # control in eps_c only
        eps_uc = unet(z, t, txt_uc, control=None)
    else:                                     # default: control in both
        ctrl_u = [s * m for s, m in zip(scales,
                  controlnet(z, t, txt_uc, edges))]
        eps_uc = unet(z, t, txt_uc, control=ctrl_u)
    eps = eps_uc + 9.0 * (eps_c - eps_uc)     # classifier-free guidance
    z = ddim_update(z, eps, t)
image = vae.decode(z / 0.18215)
# scales: [strength] * 13 by default;
#         strength * 0.825 ** (12 - i) for i in 0..12 in guess mode

In the code, the condition image is scaled to [0, 1] while the training image is scaled to [−1, 1] before the VAE, and the optimizer is AdamW over control_model.parameters() only. The v1 paper describes a "large-scale training" option for datasets of a million pairs or more: train the ControlNet alone for more than 50,000 steps, then unlock Stable Diffusion's weights and train jointly. In the code, setting sd_locked = False adds the decoder's weights (output blocks and final layer) to the optimizer; the encoder stays frozen.

Provenance Verified against primary literatureHow we verify
Zhang, Rao & Agrawala (2023), arXiv v3Eqs. (1)-(5); the 12 + 1 copied blocks and their four resolutions; 23% more memory and 34% more time per iteration on one A100 PCIe 40GB; 50% empty prompts; CFG resolution weighting w_i = 64/h_i; Tables 1-3 and the 0.52 +/- 0.17 depth study. All quoted from v3 (November 2023).
Zhang & Agrawala (2023), arXiv v1The zero-convolution gradient derivation (Sec. 3.1, Eqs. 5-8), dropped from the v3 main text; per-task datasets and A100 hours (Sec. 3.5); CFG 9.0, DDIM, 20 steps (Sec. 4.1); the sudden-convergence run at learning rate 1e-5 with AdamW and no EMA (Fig. 21). Its Eq. (5) writes the 1x1 convolution as B_i + sum_j I_{p,i} W_{i,j}; the input index must be j, as on this page.
Official code, lllyasviel/ControlNetcldm/cldm.py: the locked encoder runs inside torch.no_grad() (lines 25-32); control is added as hs.pop() + control.pop() on each skip and to the middle output (lines 34-41); input_hint_block is eight 3x3 convs with SiLU ending in a zero-initialized conv to 320 channels (lines 147-163); its output is added after the copy's first conv (lines 293-297); zero convs are 1x1 (lines 281-282); AdamW over the ControlNet parameters only unless sd_locked is False (lines 416-422). tutorial_train.py: learning rate 1e-5, batch size 4, sd_locked True.
Official code, gradio_canny2image.pyLine 46: guess mode passes no control to the unconditional branch. Line 52: scales are strength * 0.825^(12 - i) in guess mode and strength on all 13 connections otherwise. Its comment says 0.825^12 < 0.01; the value is 0.0994, so the threshold it describes is 0.1. Defaults: strength 1.0 (slider 0-2), guidance scale 9.0, 20 DDIM steps.
Released checkpoint (safetensors headers)lllyasviel/sd-controlnet-canny: 361,279,120 parameters = 348,712,960 (copied conv_in, time embedding, 12 encoder blocks, middle block) + 11,479,680 (thirteen 1x1 zero convs) + 1,086,480 (condition encoder). Stable Diffusion 1.5 U-Net: 859,520,964. Counted from the tensor shapes in both files.
Ho & Salimans (2022)Classifier-free guidance as (1 + w) eps_c - w eps_uc, which is the paper's eps_uc + beta (eps_c - eps_uc) with beta = 1 + w.
Hu et al. (2021); Dhariwal & Nichol (2021)LoRA starts its update BA at zero by zero-initializing B, which gives the same first-step gradient pattern as a zero convolution. The guided-diffusion code wraps the last convolution of each residual block in zero_module, the precedent the ControlNet paper cites.
This page's toy (Figure 1)A 4-channel block trained with plain gradient descent in the browser. It reproduces the exact facts (output unchanged at init, Z2 first, the copy from step 2). It is far too small to reproduce the paper's ablation of Gaussian-initialized connections, and the page does not claim it does.
correctionSection 3.2 describes the condition encoder E as four convolution layers with 4x4 kernels and 2x2 strides (16, 32, 64 and 128 channels, ReLU) that turn a 512x512 condition image into a 64x64 feature map. Four stride-2 layers divide the resolution by 16 and give 32x32, so that description cannot produce the stated output. The released code uses eight 3x3 convolutions, three of them with stride 2 (512 to 256 to 128 to 64), with 16, 16, 32, 32, 96, 96 and 256 channels and SiLU activations, followed by a zero-initialized 3x3 convolution into 320 channels. That last layer plays the role of Z1 in Eq. (2), so E outputs exactly zero at initialization. This page teaches the code's version.

Questions you might still have

?

Is ControlNet a kind of LoRA?
Both start as an exact no-op on a frozen model, and both use a zero-initialized factor to get there. They differ in size and placement. LoRA adds a low-rank change BA to individual weight matrices inside the existing layers. ControlNet adds a separate full-size copy of the U-Net encoder, 361 million parameters for Stable Diffusion 1.5, whose outputs are added to the skip connections. The LoRA explainer covers the low-rank version.

?

Why not initialize the connecting convolutions with small random weights instead of zeros?
Zero gives two exact properties: the combined model's output equals the frozen model's output at step 0, and no gradient reaches the trainable copy until the output convolution has learned something. The paper's ablation replaced the zero convolutions with standard Gaussian-initialized ones and found quality dropped to about the level of ControlNet-lite. It did not test very small random initializations. In the toy of Figure 1, a small random scale behaves much like zero.

?

Does the ControlNet run at every denoising step?
Yes. It reads the noisy latent and the timestep, both of which change at every step, so its outputs cannot be computed once and reused. Each step therefore runs the 361-million-parameter copy in addition to the 860-million-parameter U-Net, twice per step under classifier-free guidance unless guess mode removes the control from the unconditional pass.

?

Can a ControlNet trained for Stable Diffusion 1.5 be used with another base model?
With a fine-tuned derivative of SD 1.5 that keeps the same architecture, yes; the paper shows this for Comic Diffusion and Protogen 3.4 without retraining. With a different architecture, no: the copy's layers and the 13 output shapes must match the base model. SD 2.1 keeps the U-Net layout but uses a different text encoder with 1024-dimensional embeddings, so it needs ControlNets trained for it, as the paper's SD 2 depth model was.

?

Why are half the training prompts replaced by empty strings?
Without a prompt, the only information about the image's content is in the condition image, so the ControlNet has to learn to recognize what an edge map or a depth map depicts. The paper reports that this lets the model generate sensible content from the condition alone, with the empty string as the prompt.

?

What does the control strength setting change?
It multiplies all 13 ControlNet outputs before they are added to the U-Net. At 0 the model is plain Stable Diffusion; at 1 it is the trained model; the official demo allows up to 2. Guess mode instead multiplies the outputs by values that fall from 1.0 at the middle block to about 0.1 at the first 64x64 block, and removes the control from the unconditional branch.

?

Why copy the encoder and not the whole U-Net?
The U-Net decoder already receives the encoder's features through the skip connections, so adding the copy's outputs there changes what the decoder sees without changing the decoder. Copying only the encoder and middle block keeps the trainable part at 361 million parameters instead of the U-Net's 860 million. The paper does not report an ablation that copies the decoder too.

Footnotes & further reading

  1. The paper: Zhang, Rao & Agrawala, Adding Conditional Control to Text-to-Image Diffusion Models (ICCV 2023; arXiv v3, November 2023). The first version (February 2023, authors Zhang & Agrawala) has the zero-convolution gradient derivation and the per-task training details in its main text.
  2. Official code and models: github.com/lllyasviel/ControlNet (cldm/cldm.py for the architecture, gradio_canny2image.py for sampling and guess mode).
  3. Stable Diffusion's architecture: Rombach et al., High-Resolution Image Synthesis with Latent Diffusion Models (CVPR 2022), covered in the latent diffusion explainer; the U-Net shape comes from Ronneberger et al. (2015), covered in the U-Net explainer.
  4. Classifier-free guidance: Ho & Salimans, Classifier-Free Diffusion Guidance (2022). The noise-prediction loss: Ho, Jain & Abbeel, Denoising Diffusion Probabilistic Models (2020).
  5. Zero-initialized and low-rank fine-tuning: Hu et al., LoRA (2021), and the zero_module helper in openai/guided-diffusion (Dhariwal & Nichol, 2021).
  6. A concurrent, lighter design: Mou et al., T2I-Adapter (2023), which conditions Stable Diffusion through a small separate encoder instead of a copy of the U-Net encoder.