ControlNet, Inpainting and Image Editing
Diffusers calls for text to image, img2img, inpainting, LoRA and ControlNet. Knobs, defaults and fixes.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
Control signals into the U-Net#
Text to image#
pipe = StableDiffusionPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16,halves VRAM).to("cuda")pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)image = pipe( prompt="<prompt>", guidance_scale=7.5,CFG scale w, SD default num_inference_steps=25,matches 50 steps of DDIM generator=torch.Generator("cuda").manual_seed(42),).images[0]image.save("dog.png")Img2img, inpaint and LoRA#
out = img2img(prompt="<prompt>", image=init_image, strength=0.6, guidance_scale=7.5).images[0]0.0 unchanged, 1.0 newmask = Image.open("dog_mask.png").convert("L").resize((512, 512))out = inpaint(prompt="<prompt>", image=image, mask_image=mask, guidance_scale=7.5).images[0]white pixels regeneratepipe.load_lora_weights("<repo>", weight_name="<file>")pipe.fuse_lora(lora_scale=0.8)bakes in, no swappipe.unfuse_lora()before another adapterpipe.set_adapters([...], adapter_weights=[...])per-request LoRA mixTasks in code#
| Task | Code |
|---|---|
| Install the stack | pip install diffusers transformers accelerate |
| Image to image | StableDiffusionImg2ImgPipeline |
| Inpaint with SD | StableDiffusionInpaintPipeline |
| Inpaint with Flux | FluxFillPipeline |
| Stochastic sampler | EulerAncestralDiscreteScheduler |
| Encode to latents | pipe.image_processor.preprocess(img) |
| Reuse old latents | pass latents=... and skip one VAE encode |
| Scale VAE latents | latent_dist.sample() * 0.18215 |
Mask-aware reverse loop#
Conditioning tools side by side#
| Factor | ControlNet | LoRA | IP-Adapter | DreamBooth |
|---|---|---|---|---|
| Controls | spatial structure | style, subject, concept | look of a reference image | one subject |
| Size | 70-360MB | 20-200MB | 20MB | 2-5GB |
| Mechanism | cloned encoder, zero-conv skips | low-rank delta on attention | CLIP image tokens in cross-attention | full fine-tune |
| Use when | exact layout or composition | personalization or style | no text describes the look | strong identity, high compute |
Formulas and numbers#
- CFG
- \epsilon = \epsilon_{\text{uncond}} + w \cdot (\epsilon_{\text{cond}} - \epsilon_{\text{uncond}})
- LoRA
- W' = W + \alpha \cdot B A with r \ll d. New parameters: 2 \cdot d \cdot r instead of d^2.
- LoRA size
- d=640, r=16 gives 20k parameters instead of 410k.
- ControlNet mix
features += weight_a * control_a(depth) + weight_b * control_b(pose)- Latent diffusion
- 3x512x512 to 4x64x64 cuts compute by 48x.
- Inpaint U-Net
- 9 input channels. Noisy latent 4, encoded image 4, mask 1.
- SDEdit t/T
- 0.3 nearly the source. 0.6 keeps coarse structure. 0.9 near noise.
- Zero conv
- ControlNet starts as identity. It does no harm before training.
Editing methods side by side#
| Factor | Inpaint model | SDEdit or img2img | InstructPix2Pix | RePaint |
|---|---|---|---|---|
| Input | image, mask, prompt | image, noise level, prompt | image and instruction | image and mask |
| Training | 9-channel U-Net | none | fine-tune on triples | none, unconditional model |
| Keeps | pixels outside the mask | coarse structure | structure of the input | pixels outside the mask |
| Main knob | mask and CFG | t/T or strength | image scale and text scale | resample steps |
| Use for | remove object, replace sky | style of one region | "make it sunset" | no inpaint model at hand |
Defaults#
| Setting | Default | Note |
|---|---|---|
guidance_scale | 7.5 | SD default CFG scale |
num_inference_steps | 25 | With DPM-Solver++ |
img2img strength | 0.6 | 0.5-0.7 for style transfer |
| Prompt drop in CFG training | 10% | Gives the unconditional branch |
| VAE scale factor | 0.18215 | SDXL uses 0.13025 |
LoRA rank r | 4-16 | 64-128 for heavy fine-tunes |
| Sum of ControlNet weights | 1.0 | Safe default |
| Mask dilation in pixels | 8-16 | Hides seams |
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| Over-stylized or broken output | LoRA α at 2 or 3 | Keep α at 1.5 or below |
| Pose plus depth overshoots | Two ControlNets at weight 1.0 | Make the weights sum to 1.0 |
| LoRA has no effect | SDXL LoRA on SD 1.5 | Match the base model |
| Cannot load another LoRA | fuse_lora baked it in | Call pipe.unfuse_lora() first |
| Visible seams at the mask | Naive reinjection | Dilate the mask or use a 9-channel model |
| Saturated patch | High CFG on a small mask | Reduce CFG for small edits |
| Subject loses identity | SDEdit t/T from 0.5 to 0.6 | Sweep and checkpoint |
| Fill ignores the scene | Prompt names only the new content | Describe the whole image |
Use X when#
- ControlNet-Openpose + SDXL + text a specific pose with a prompt.
- ControlNet-Scribble or ControlNet-Canny an exact layout.
- LoRA on ~30 curated images at rank 32 a brand art style.
- SD-Inpaint or Flux-Fill remove an object under a small mask.
- SAM mask, then SD-Inpaint background replacement.
- InstructPix2Pix or Flux-Kontext an instruction edit with no mask.
- LCM-LoRA latency under 1s.
Questions#
- Why init the ControlNet gate at zero?
- At step 0 the output is identical to the base.
- Merge LoRAs or hot-swap them?
- Hot-swap. A merge is 3-5% faster but freezes α and the base.
- Why does strength 1.0 ignore the input?
- It adds full noise. The result is a full regeneration.
- Why use a 9-channel model, not reinjection?
- The model sees the context around the mask.
- Can I swap the scheduler after training?
- Yes. Scheduler state is decoupled from U-Net weights.
- What does an extra ControlNet cost?
- Budget about 1.5× step cost for each active ControlNet.