ControlNet与LoRA条件控制 本节摘要:光靠文本是个笨拙的控制信号——「红裙女人在繁华街头遛狗」这句话完全没告诉模型狗在哪里、女人是什么姿态、街道是什么透视。文本只钉死了指定一张图所需信息的约 10%,其余的都是视觉的、用文字描述效率极低。ControlNet 让你克隆一个预训练扩散模型,用深度图、姿态骨架、涂鸦或边缘图去引导它;LoRA 让你只训 1000 万参数就能微调一个 20 亿参数的模型(低秩适配器)。两者合在一起,把 Stable Diffusion 从玩具变成了 2026 年每家代理商都在交付的图像流水线。
本节摘要:光靠文本是个笨拙的控制信号——「红裙女人在繁华街头遛狗」这句话完全没告诉模型狗在哪里、女人是什么姿态、街道是什么透视。文本只钉死了指定一张图所需信息的约 10%,其余的都是视觉的、用文字描述效率极低。ControlNet 让你克隆一个预训练扩散模型,用深度图、姿态骨架、涂鸦或边缘图去引导它;LoRA 让你只训 1000 万参数就能微调一个 20 亿参数的模型(低秩适配器)。两者合在一起,把 Stable Diffusion 从玩具变成了 2026 年每家代理商都在交付的图像流水线。本节讲透 ControlNet 的「克隆编码器 + 零卷积跳跃连接」为何训练初期就是恒等映射、LoRA 的
W' = W + BA低秩分解如何把参数砍 100 倍、以及 IP-Adapter 如何用一张参考图控制风格。生产栈通常在一个 SDXL/SD3/Flux 底座上叠 2-5 个 LoRA + 1-3 个 ControlNet + 一个 IP-Adapter。
对应原课程:Phase 08 · Lesson 08 ·
controlnet-lora-conditioning(原英文phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md)。
阅读完本节,你应当能够:
W' = W + BA,A ∈ R^{r×d}、B ∈ R^{d×r}、r << d,把可训参数从 d² 降到 2dr,并解释运行时 α 缩放。为每个信号(姿态、深度、Canny、分割)从零训一个条件模型,代价高得离谱。你想要的是:保持 2.6B 参数的 SDXL 骨干冻结,接一个小型侧网络读取条件,让它去轻推骨干的中间特征。这就是 ControlNet。
你还想教模型新概念(你的脸、你的产品、你的风格),又不想重训整个模型。你想要一个 100 倍更小的增量。这就是 LoRA——插进现有注意力权重的低秩适配器。
ControlNet + LoRA + 文本 = 2026 年从业者的工具箱。大多数生产图像流水线,在一个 SDXL / SD3 / Flux 底座上叠 2-5 个 LoRA、1-3 个 ControlNet 和一个 IP-Adapter。
取一个预训练 SD,克隆 U-Net 的编码器半边,冻结原始网络,把克隆训练成能接受额外条件输入(边缘、深度、姿态)。用零卷积跳跃连接(初始化为零的 1×1 卷积)把克隆接回原始的解码器半边:
SD U-Net 解码器: ... ← 原始编码器特征 + zero_conv(controlnet编码器(条件))
零卷积初始化意味着 ControlNet 一开始就是恒等映射——即使还没训练,也不会伤害底座。然后在 100 万个 (提示, 条件, 图像) 三元组上,用标准扩散损失训练。
💡 零卷积的天才之处:如果用随机初始化的卷积把克隆接回解码器,训练第一步就会给底座注入随机噪声,把预训练知识冲毁。零初始化让 ControlNet 从「完全透明」起步,梯度慢慢学会「该在多大程度上、以什么方式介入」——这是它能安全地接在冻结底座上的根本原因。
对模型里任意线性层 W ∈ R^{d×d},冻结 W,加一个低秩增量:
W' = W + ΔW, ΔW = B @ A, A ∈ R^{r×d}, B ∈ R^{d×r}
r << d。注意力用秩 4-16 是标准,重度微调用秩 64-128。新增参数:2·d·r 而非 d²。对 SDXL 注意力(d=640,r=16):每个适配器 20k 参数而非 410k,20 倍缩减。整个模型看下来:LoRA 通常 20-200MB,而底座 5GB。
推理时可以缩放 LoRA:W' = W + α·B @ A。α = 0.5-1.5 正常。多个 LoRA 可加性叠加(但它们会以非线性方式相互作用,这是常见陷阱)。
💡 LoRA 为何有效——「内在维度」假说:经验上,微调一个大模型时,有意义的参数更新集中在一个低维子空间里(「内在维度」远小于参数总数)。LoRA 直接在这个低维子空间里优化,所以
r很小就够。这一现象在 LLM 和扩散模型上都成立,是「低秩适配」能跨领域工作的根本原因。
一个小适配器,接受图像作为条件(与文本并列)。用 CLIP 图像编码器产出图像 token,与文本 token 一起注入交叉注意力。每个底座约 20MB。让你做「以这张参考图的风格生成」,无需 LoRA。
| 工具 | 控制什么 | 大小 | 何时用 |
|---|---|---|---|
| ControlNet | 空间结构(姿态、深度、边缘) | 70-360MB | 精确布局、构图 |
| LoRA | 风格、主体、概念 | 20-200MB | 个性化、风格 |
| IP-Adapter | 来自参考图的风格或主体 | 20MB | 文字无法描述那种外观时 |
| Textual Inversion | 单个概念编码为新 token | 10KB | 遗留方案,多被 LoRA 取代 |
| DreamBooth | 在某主体上的全量微调 | 2-5GB | 强身份、高算力 |
| T2I-Adapter | 更轻的 ControlNet 替代 | 70MB | 边缘设备、推理预算紧 |
一句话口诀:ControlNet≈空间,LoRA≈语义,两者一起用。
code/main.py 在一维上模拟这两个机制:
LoRA:一个预训练线性层 W,冻结它,训一个低秩 B @ A,使 W + BA 匹配目标线性层。展示 r = 1 就足以完美学到一个秩 1 修正。
ControlNet-Lite:一个「冻结底座」预测器和一个读取额外信号的「侧网络」。侧网络输出由一个初始化为零的可学习标量门控(我们的零卷积版本)。训练时观察门慢慢爬升。
def lora(W, A, B, x, alpha=1.0): # W 冻结;A、B 是可训的低秩因子 return matmul(W, x) + alpha * matmul(B, matmul(A, x))
side_out = control_net(x, condition) gated = gate * side_out # gate 初始化为 0 h = base(x) + gated
在第 0 步,输出与底座完全相同。早期训练慢慢更新 gate——不会出现灾难性漂移。
过度缩放 LoRA:α = 2 或 3 是常见的「让它更强」土办法,产出过度风格化 / 损坏的输出。保持 α ≤ 1.5。
ControlNet 权重冲突:姿态 ControlNet 用权重 1.0、深度 ControlNet 也用 1.0 通常过冲。权重之和 ≈ 1.0 是安全默认。
LoRA 用错底座:SDXL 的 LoRA 在 SD 1.5 上会静默失效,因为注意力维度不匹配。Diffusers 0.30+ 会警告。
Textual Inversion 漂移:在某 checkpoint 上训的 token,换到另一个 checkpoint 上漂移严重。LoRA 更可移植。
LoRA 权重合并与存储:你可以把 LoRA 烘焙进底座权重以加速推理(无运行时加法),但失去运行时缩放 α 的能力。两份都留。
⚠️ 多个 LoRA 叠加不是纯线性:
W' = W + α₁·B₁A₁ + α₂·B₂A₂在权重层面是线性的,但因为后续的非线性激活(softmax、GeLU),两个 LoRA 在输出层面会以非线性方式相互作用。两个各自完美的风格 LoRA 叠在一起可能产出垃圾——这是为什么实践中要逐一调α并用种子做消融。
| 目标 | 2026 年流水线 |
|---|---|
| 复现某品牌美术风格 | 在 ~30 张精选图上训秩 32 的 LoRA |
| 把我的脸放进生成图 | DreamBooth 或 LoRA + IP-Adapter-FaceID |
| 特定姿态 + 提示 | ControlNet-Openpose + SDXL + 文本 |
| 深度感知构图 | ControlNet-Depth + SD3 |
| 参考图 + 提示 | IP-Adapter + 文本 |
| 精确布局 | ControlNet-Scribble 或 ControlNet-Canny |
| 背景替换 | ControlNet-Seg + 修复(第 09 节) |
| 快速一步风格 | LCM-LoRA 叠在 SDXL-Turbo 上 |
一个真实的文生图 SaaS 在同一个底座 checkpoint 上服务数百个 LoRA 和十几个 ControlNet。服务问题很像 LLM 多租户:
热替换 LoRA,不要合并:把 W' = W + α·BA 合并进底座能让每步推理快 3-5%,但冻死了 α 和底座。把 LoRA 作为秩 r 增量热驻 VRAM;diffusers 暴露 pipe.load_lora_weights() + pipe.set_adapters([...], adapter_weights=[...]) 做按请求激活。换 LoRA 的成本是 2·d·r·层数 个权重——MB 级,亚秒级。
ControlNet 当作第二条注意力通道:克隆的编码器与底座并行跑。两个权重各 1.0 的 ControlNet = 每步两次额外前向,不是一次合并前向。批大小余量二次下降。预算上每个激活的 ControlNet 约 1.5 倍单步成本。
LoRA 也要量化:如果你量化了底座(见第 07 节,8GB 上的 Flux),LoRA 增量也干净地量化到 8-bit 或 4-bit。QLoRA 式加载让你在一个 4-bit Flux 底座上叠 5-10 个 LoRA 而不爆内存。
Flux 特定:Niels 的 Flux-on-8GB 笔记本把底座量化到 4-bit;在那个量化底座上叠风格 LoRA(pipe.load_lora_weights("user/style-lora"),weight_name="pytorch_lora_weights.safetensors")依然工作。这是 2026 年大多数 SaaS 代理商交付的配方。
本节产出一个工具组合技能文件(位于原课程 outputs/skill-sd-toolkit-composer.md)。
skill-sd-toolkit-composer.md:输入任务(输入资产:提示、可选参考图、可选姿态、可选深度、可选涂鸦),输出:工具栈、权重、可复现的种子协议。简单。 在 code/main.py 里把 LoRA 秩 r 从 1 变到 4。在哪个秩下 LoRA 精确匹配一个秩 2 的目标增量?
中等。 在两个目标变换上分别训两个 LoRA。一起加载,展示它们的加性相互作用。在什么时候相互作用打破线性?
困难。 用 diffusers 叠:SDXL-base + Canny-ControlNet(权重 0.8)+ 风格 LoRA(α 0.8)+ IP-Adapter(权重 0.6)。随栈权重变化,测量 FID 对提示遵从度的权衡。
W' = W + α·BA,r << d,参数从 d² 降到 2dr(SDXL 注意力 20 倍缩减);有效是因为微调的「内在维度」远小于参数总数。α(≤1.5),ControlNet 权重之和 ≈ 1.0;多 LoRA 在权重上线性但输出上非线性,要逐一调。下一节,我们进入图像编辑——修复(Inpainting)、扩展(Outpainting)与编辑:在保持未遮罩区域不变的前提下重画遮罩区,把 ControlNet、LoRA 与潜在扩散的潜在空间操控结合起来,实现换背景、补全、扩图。