Diffusers 模型迁移实战:将 Stable Diffusion 文生图模型改造为图像修复(Inpainting)模型
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
许多扩散系统共享相同的组件架构,例如 UNet、VAE 与文本编码器,这使得针对某一任务预训练的模型可以迁移到完全不同的新任务上。本指南基于 Diffusers 官方训练文档(docs/source/ko/training/adapt_a_model.md),完整演示如何通过初始化并修改预训练UNet2DConditionModel的架构,将文生图(text-to-image)预训练模型改造成图像修复(inpainting)模型。读完本文,你将掌握in_channels通道数的修改方法、ignore_mismatched_sizes与low_cpu_mem_usage的加载技巧,并理解为什么输入卷积层权重必须重新训练。
为什么扩散系统可以被"迁移"到新任务
扩散模型通常采用模块化的组件组合:UNet(去噪主干)、VAE(图像与潜空间互转)、文本编码器(条件注入)以及调度器(Scheduler)。Diffusers 仓库把这些组件设计成可独立加载、独立替换的结构,UNet2DConditionModel便是其中的核心去噪网络。
由于不同任务(文生图、图生图、图像修复、超分辨率等)共享同一套组件骨架,唯一的本质差异往往只在于输入端的数据组织方式。因此,只要调整 UNet 的输入通道数并适配相应的输入拼接逻辑,就可以把预训练权重"嫁接"到新任务上,再经过少量微调即可投入使用。这正是本文迁移方案的底层原理。
UNet2DConditionModel 参数配置:从 4 通道到 9 通道
默认的文生图模型输入:4 个通道
UNet2DConditionModel在输入样本(input sample)上默认接受 4 个通道。查看其构造签名(src/diffusers/models/unets/unet_2d_condition.py#L181):
in_channels: int = 4,这 4 个通道对应 VAE 编码器输出的潜空间表示(latent):SD 1.x 系列 VAE 的latent_channels为 4,包含 RGB 三通道编码加一个额外的结构通道。加载一个典型的文生图预训练模型,例如stable-diffusion-v1-5/stable-diffusion-v1-5,并查看其 UNet 配置:
from diffusers import StableDiffusionPipeline pipeline = StableDiffusionPipeline.from_pretrained( "stable-diffusion-v1-5/stable-diffusion-v1-5", use_safetensors=True, ) pipeline.unet.config["in_channels"] # 4pipeline.unet.config保存了 UNet 的完整配置字典,其中in_channels直接决定了第一层卷积conv_in的输入维度。
图像修复模型需要 9 个通道
图像修复(inpainting)任务要求模型同时感知三部分信息:
- 带噪声的潜空间图像(4 通道);
- 掩码(mask,1 通道),标记需要重绘的区域;
- 被掩码覆盖的图像潜空间(masked image latent,4 通道)。
三者沿通道维度拼接后正好是4 + 1 + 4 = 9个通道。可以在预训练修复模型stable-diffusion-v1-5/stable-diffusion-inpainting中验证这一点:
from diffusers import StableDiffusionPipeline pipeline = StableDiffusionPipeline.from_pretrained( "stable-diffusion-v1-5/stable-diffusion-inpainting", use_safetensors=True, ) pipeline.unet.config["in_channels"] # 9因此,要将文生图模型改造为修复模型,核心操作就是把in_channels从 4 改为 9。
通道数修改在源码中的实际体现
修改in_channels后,UNet 内部唯一受影响的层是位于网络入口的卷积层conv_in。在 src/diffusers/models/unets/unet_2d_condition.py#L272-L274 中可以看到其定义:
self.conv_in = nn.Conv2d( in_channels, block_out_channels[0], kernel_size=conv_in_kernel, padding=conv_in_padding )即conv_in的第一个维度由in_channels决定;正向传播时输入样本也首先经过self.conv_in(sample)(unet_2d_condition.py#L1108)。其余所有下采样、注意力、上采样模块的输入输出维度只与block_out_channels相关,不受in_channels影响——这正是仅需改动一个参数即可完成结构适配的根本原因。
加载并初始化:ignore_mismatched_sizes 与 low_cpu_mem_usage 的作用
标准加载代码
用文生图模型的预训练权重初始化UNet2DConditionModel,同时把in_channels改为 9:
from diffusers import AutoModel model_id = "stable-diffusion-v1-5/stable-diffusion-v1-5" unet = AutoModel.from_pretrained( model_id, subfolder="unet", in_channels=9, low_cpu_mem_usage=False, ignore_mismatched_sizes=True, use_safetensors=True, )为什么必须设置 ignore_mismatched_sizes=True
加载时 Diffusers 会按权重张量形状逐一比对预训练权重与新建模型。由于in_channels从 4 变为 9,conv_in.weight的形状由(block_out_channels[0], 4, 3, 3)变成(block_out_channels[0], 9, 3, 3),属于"形状不匹配"的权重。
在 src/diffusers/models/modeling_utils.py#L1733-L1845 的_load_pretrained_model实现中,Diffusers 会为每个形状不匹配的键给出错误信息,并提示:
You may consider adding
ignore_mismatched_sizes=Truein the modelfrom_pretrainedmethod.
设置ignore_mismatched_sizes=True后,加载逻辑会跳过这些形状不一致的权重(保持随机初始化),而其余形状匹配的权重(如注意力层、残差块、时间嵌入等)仍会正常载入预训练值。
为什么必须设置 low_cpu_mem_usage=False
low_cpu_mem_usage=True(在较新 PyTorch 版本中为默认值)依赖accelerate以"元设备 + 延迟加载"的方式加载权重,该路径对权重形状的严格一致性有额外要求,无法与"跳过部分权重"的ignore_mismatched_sizes语义很好地兼容。因此官方文档明确要求显式设置low_cpu_mem_usage=False,走传统的直接加载路径,从而避免尺寸不匹配错误。相关参数处理逻辑同样位于 src/diffusers/models/modeling_utils.py(如第 1035 行、第 1113-1116 行对low_cpu_mem_usage与device_map的约束)。
加载后各组件权重的命运
加载完成后,权重处于"混合状态":
- 文本编码器、VAE、UNet 中除
conv_in外的所有层:完整保留文生图模型的预训练权重; conv_in.weight与conv_in.bias:因形状不匹配被跳过,保持随机初始化。
这一点至关重要:如果直接拿这个模型做推理,随机初始化的输入卷积会把输入噪声化,模型将只会输出噪声。
图像修复管线的通道拼接原理
为了理解"9 通道"的来龙去脉,可以查看 Diffusers 内置的修复管线 pipeline_stable_diffusion_inpaint.py 的实现:
- 在第 1224-1239 行,管线会校验
num_channels_latents + num_channels_mask + num_channels_masked_image是否等于unet.config.in_channels,并要求 UNet 的输入通道只能是 4 或 9; - 在第 1273-1274 行的去噪循环中,9 通道模型会执行通道级拼接:
if num_channels_unet == 9: latent_model_input = torch.cat([latent_model_input, mask, masked_image_latents], dim=1)可见,9 通道输入 = 4 通道潜变量 + 1 通道掩码 + 4 通道被掩码图像,拼接顺序与数量缺一不可。这正是文生图模型(4 通道)无法直接处理修复任务的直接原因,也是本文迁移方案要解决的核心结构差异。
迁移之后的必要步骤:微调
通道改造只是完成了"结构适配",距离真正可用还差最后一步——针对修复任务微调。原因在上文已阐明:conv_in的权重是随机初始化的,而它恰好是模型理解"掩码 + 被掩码图像"语义的第一道闸门。若不训练,UNet 从第一步起就在处理完全无意义的输入特征,去噪过程退化为输出噪声。
Diffusers 训练生态中已有大量可复用的修复微调入口,例如 examples/inpainting 目录下的训练脚本,它们接收一个已改造好的 9 通道 UNet 作为起点。微调时通常的做法是:
- 使用包含成对(原图、掩码、修复目标)的数据集;
- 冻结或低学习率训练文本编码器,重点更新 UNet;
- 由于只有
conv_in需要"从零学起",其余层已有良好初始化,训练收敛通常远快于从随机初始化开始。
从源码结构看,仅输入卷积层的权重随机初始化意味着:迁移模型的"知识断层"集中在入口层,这让微调任务相对轻量,也是这种迁移方案在社区中被广泛采用的原因。
完整流程回顾与注意事项
把整个迁移流程串起来,共四步:
- 确认通道差异:文生图 UNet 默认
in_channels=4(unet_2d_condition.py#L181),修复模型为 9; - 改造结构:
AutoModel.from_pretrained(..., in_channels=9)或UNet2DConditionModel.from_pretrained(..., in_channels=9),两者等价; - 正确加载:必须同时传入
ignore_mismatched_sizes=True与low_cpu_mem_usage=False,否则会触发权重形状不匹配错误; - 微调启用:对修复任务进行微调,让随机初始化的
conv_in学会解读掩码信息,否则模型输出噪声。
注意事项汇总:
| 参数 | 取值 | 作用 |
|---|---|---|
in_channels | 9 | 修改 UNet 输入卷积通道数,适配修复任务的 4+1+4 拼接输入 |
ignore_mismatched_sizes | True | 允许跳过形状不匹配的权重(即conv_in),其余权重正常加载 |
low_cpu_mem_usage | False | 使用传统加载路径,与ignore_mismatched_sizes兼容,避免尺寸错误 |
use_safetensors | True | 优先加载.safetensors格式权重(更安全、更快的加载方式) |
小结
本文完整还原了 Diffusers 官方迁移指南(docs/source/ko/training/adapt_a_model.md)的核心流程:通过修改UNet2DConditionModel的in_channels(4 → 9),配合ignore_mismatched_sizes=True与low_cpu_mem_usage=False完成带结构变化的权重加载,将文生图预训练模型无缝改造成修复模型。其背后原理是扩散系统的高度组件化:任务差异集中体现在输入通道组织方式上,而修复管线的源码(pipeline_stable_diffusion_inpaint.py)也印证了 9 通道拼接逻辑。最后请务必记住:改造后的模型只有经过修复任务微调才能产出有效结果,因为conv_in的权重在加载时已被随机初始化。
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考