☰
Stable Diffusion原理拆解:从CLIP到UNet的AI绘画全流程
2026/10/26 14:38:52 网站建设 项目流程

把一套四集的 Stable Diffusion 原理精讲从头到尾刷完,整理出来这篇笔记。内容正好覆盖了标题里的四条主线:CLIP 怎么让模型“听懂”文字、扩散模型是怎么一步步把噪声还原成图像的、UNet 在其中承担什么角色,以及最后如何用代码把整套流程跑起来。现在这套模型已经是 AI 绘画的事实标准,但大多数人停留在“会调参数出图”的阶段,不知道每一步发生了什么,遇到手部崩坏、提示词不生效、显存不足这类问题就只能靠猜。这篇笔记的价值在于把黑盒拆开,让你能定位问题到底出在哪一环,是文本没被理解,还是去噪过程细节丢失,还是解码环节出了问题。

不管是刚入门的玩家、想自己微调模型的开发者,还是只在 ComfyUI 里拖节点的工作流爱好者,这篇笔记都能给你一套可以对照的坐标系。我会尽量用通俗的语言把数学原理还原成直觉,再补上代码和实际经验,保证你看完既能理解、也能动手。

1. 先搭框架:Stable Diffusion 整体由哪几块拼成

在学习任何复杂系统之前,最好的方式不是直接钻进细节,而是先在脑子里搭一个粗略的骨架。Stable Diffusion 虽然名字里带着“扩散模型”,但它并不是一个单一模型,而是由多个组件拼装起来的完整系统。第一集里讲师反复强调一句话:Stable Diffusion 本质上是“文本编码器 + 生成网络 + 图像解码器”的组装体,这句话值得先记下来。

1.1 文生图流程拆成三段,每一段各司其职

我们从一个最简单的文生图过程来拆解。你输入一句提示词“a photo of a cat sitting on a windowsill”,Stable Diffusion 内部会依次发生这些事情:

  • 提示词先被一个文本编码器处理,变成一组向量。这组向量不是简单的词向量,而是经过大规模图文对比学习得到的“语义向量”,它知道“猫”和“窗台”这两个词之间的相对关系。
  • 这组向量被送入生成网络,也就是 UNet,作为“条件”来控制生成方向。与此同时,生成网络还会接收一个纯噪声的潜变量,两者一起经过几十步迭代去噪,逐步把噪声整理成有结构的图像潜变量。
  • 最后,图像潜变量不能直接当像素图看,要经过 VAE 解码器,把 64x64 尺寸的潜变量扩张成 512x512 的 RGB 像素图。

这段流程里,CLIP 负责“理解文字”,UNet 负责“执行绘制”,VAE 负责“把压缩的表示还原成图片”。三者都不复杂,但连起来就是一个完整的生产链路。我最早看这段的时候还在想,VAE 是不是多余的,能不能直接让 UNet 输出像素图?答案是不行,因为 UNet 在潜空间里计算本身就是整个模型能跑起来的核心取舍。

1.2 为什么要在潜空间里扩散?潜在扩散模型的核心取舍

要理解为什么必须有 VAE,得先知道“潜在扩散模型”这个概念的来历。最早的扩散模型,比如 DDPM(Denoising Diffusion Probabilistic Models),直接在像素空间里操作。一张 512x512 的 RGB 图像就有 786432 个数据点,如果每一步去噪都要对这么高维的数据做卷积和注意力运算,一顿操作下来显存直接爆掉,训练和推理都慢得没法用。

Stable Diffusion 聪明的地方在于:先用 VAE 的编码器把图像压缩成潜空间表示,512x512x3 的像素图被压缩成 64x64x4 的潜变量。别小看这个压缩比,数据量降到原来的 1/48,但对人类视觉来说,潜空间保留的信息足够还原出与原图高度相似的画面。于是扩散过程就在这 64x64 的小尺寸潜变量里进行,计算量大幅下降,这就是“潜在扩散模型”区别于“像素空间扩散模型”的地方。

这个取舍也不是没有代价。潜空间是一个经过压缩的抽象空间,很多高频细节(尤其是手指、文字这类细小结构)在压缩过程中容易丢失。这也是为什么后面会专门讲手部崩坏的问题——它不完全是模型“笨”,而是潜伏空间表达细节的天然局限。理解了这一层,你排查问题的时候就不会盲目责怪某一个组件,而是能判断“这个现象大概率是哪个环节造成的”。

1.3 三个“翻译官”的分工:文字、图像、噪声之间的桥梁

为了加深记忆,我把这套系统比作一个创作团队:

  • CLIP 文本编码器是“翻译官”。它把人类的自然语言翻译成模型能理解的条件向量,翻译质量直接决定生成内容符不符合你的描述。同一个提示词,换个 CLIP 模型,结果可能完全不同。
  • UNet 是“执行画师”。它接收一个噪声画布和翻译官给出的文字说明,一步一步把噪声擦掉,重新组织出符合语义的画面结构。
  • VAE 是“照片冲洗师”。潜空间里的数值最终要回到像素空间,VAE 解码器负责把这个抽象编码放大成具体的图像。压缩过程中的信息取舍也发生在这里。

整个 Stable Diffusion 模型,无论你用 WebUI、ComfyUI,还是直接调 diffusers 库,本质都是这三块在协作。下面的几节,我会按顺序把这三块逐层拆开,再补上扩散过程和代码实战,让整个模型从“了解”变成“掌握”。

2. 从语言到条件:CLIP 如何让模型“听懂”提示词

第二集花了很长篇幅讲 CLIP。很多人对 CLIP 的印象停留在“它是个能匹配图片和文字的模型”,但到 Stable Diffusion 里,它具体做了什么、为什么要这样做,大多数资料都没讲透。

2.1 CLIP 的双塔对比学习到底学出了什么

CLIP 的核心结构是“双塔”,一个图像编码器、一个文本编码器,两个编码器分别把图片和文字映射到同一个向量空间。训练方式是对比学习:给一批图文对,模型要能分辨出哪张图和哪段文字是配对的,哪些不是。通过这种最朴素的监督信号,CLIP 学会了一个极其重要的能力——把语义相近的内容映射到空间中相近的位置。

“一只猫坐在窗台上”的文字向量,和一张实际拍摄的猫坐在窗台上的图片向量,在这套空间里距离很近。反过来,“一只狗在公园跑步”的向量就会落在另一个区域。这套对齐能力是 Stable Diffusion 能“看图写字”的基础,因为文本编码器输出的向量决定了 UNet 往哪个方向去噪。

我在实际使用中发现一个很有意思的现象:如果你的提示词里包含 CLIP 训练数据里少见的概念,比如冷门的材质名词、特定的艺术风格术语,模型往往画不准。这不是扩散模型的问题,而是 CLIP 根本没有为这个概念学习到足够稳定的向量。解决思路通常是换一个在相关领域数据上微调过的 CLIP 模型,或者用 Textual Inversion 把新概念注入到文本向量空间。所以CLIP 微调才是一项有价值的工作,只是微调的往往是文本编码器的输出层,而不是整个双塔结构。

2.2 CLIP Text Encoder 在 Stable Diffusion 里的实际作用

在 Stable Diffusion 里,我们用的并不是完整的 CLIP 模型,而是它的文本编码器部分。PyTorch 加载模型时你会发现,模型仓库里包含text_encoder、unet、vae、scheduler四个文件,其中text_encoder就是 CLIP 的文本分支。

实际的执行流程是:提示词先被分词器(Tokenizer)切成 token 序列,映射为 token ID。这些 token 输入进 CLIP 文本编码器,经过多层 Transformer 编码,输出一个形状为(77, 768)或(77, 1280)的向量矩阵(不同版本维度不一样)。这个矩阵不是简单地被“塞进”UNet 里,而是在 UNet 中通过交叉注意力(Cross-Attention)机制与图像特征交互,作为 Key 和 Value 来调制图像生成方向。下面讲 UNet 的时候你就能看到具体怎么交互的。

一个常见的误解是,提示词里每个词权重一样。其实 CLIP 输出的是整个句子的上下文化表示,每个 token 的向量已经结合了它在句中的位置和前后关系。(cat:1.3)这类权重语法之所以有效,本质上是人为放大了特定 token 对注意力机制的贡献强度,并不是模型原生自带的功能。理解这一层,你在调提示词权重的时候就不会盲目堆数值了。

2.3 提示词长度与权重:CLIP limit 的真相

“CLIP limit”这个词经常出现在社区讨论里,其实指的是CLIP 文本编码器能接受的最大 token 长度。SD 1.5 系列是 77 个 token,SDXL 经过优化后长一些,但默认情况下同样有长度限制,配合 LongCLIP 才能处理 225 个 token。

超过长度的部分会发生什么?直接截断,被静默忽略。这是个很隐蔽的坑——你写了一段 300 字的提示词,后面的文字模型根本就没看到,但界面上不会提示你任何错误。我都干过这种傻事,写了半天细节,结果后面的关键词一个都没生效。判断方法很简单:观察生成结果中是否出现了提示词后半段的元素,如果完全没有,基本就是截断了。

提示词权重语法的作用原理也因此清楚了:权重越高的 token,在交叉注意力中的影响系数越大。不过权重不是越高越好,1.5 到 1.8 之间通常比较稳妥,超过 2.0 容易让该区域产生过曝、油腻的伪影。下面这个表是我自己的经验值,不同模型略有差异:

权重值实际效果建议
0.5-0.8弱化概念,甚至完全忽略用于去除画面中不想要的元素
1.0-1.3正常或略微强调大多数关键词的默认范围
1.3-1.8明显强化该概念用于突出主体、风格关键词
2.0+容易出现过曝、变形、伪影非必要不建议

2.4 CLIP 微调与“询问机”:什么时候才值得动 CLIP

CLIP 参数级别比较大,普通人直接微调成本高、容易过拟合,而且对 SD 生成质量的提升往往不如换一个好的底模明显。我身边有人试过在少量数据上微调 CLIP,结果生成效果反而变差了,因为微调破坏了原本稳定的语义对齐空间。真正有效且轻量的做法是上面提过的 Textual Inversion,只需要在文本向量空间学习一个“新词”的 embedding,成本极低,也方便分享。

另外在 ComfyUI 里有个“CLIP Text Encode”节点,社区里也有人把它叫“询问机”——你输入什么,它就去 CLIP 里询问出一组向量来。它对应的就是这段讲的文本编码过程。理解了 CLIP 的工作方式,你在 ComfyUI 里单独拉一个 CLIP 节点连接文本提示时,就不会觉得它只是个玄学组件了。

3. 去噪背后的 UNet:扩散模型的核心执行者

第三集进入了整个 Stable Diffusion 最核心的部分:扩散模型和 UNet。说实话这部分数学味道最重,但核心思想很简单——训练一个神经网络,让它学会“猜测”一张图里混入了多少噪声,然后减去这些噪声,一步步还原出干净图像。

3.1 前向过程:给图片一点点加噪声,直到彻底模糊

扩散模型的前向过程(Forward Process)其实是一个被设计好的“破坏过程”。给定一张干净图片,我们按照一个预定义的噪声调度(Noise Schedule),逐步向它加入高斯噪声。每一步加的量由一个时间步(Timestep)控制,时间步越大,噪声越强,图像越模糊。经过足够多的时间步之后,图像会完全变成一个纯噪声,和原本的内容再无关系。

这个过程的数学描述是马尔可夫链,每一步只依赖前一步的状态。训练的时候不需要真的把 1000 步都跑完,而是随机采样一个时间步,直接从干净图和该步对应的噪声强度出发,拼出一张“加了部分噪声的图”,然后让模型预测噪声。这种随机训练策略让模型效率极高,几行代码就能在 GPU 上训练出有效果的扩散模型。

3.2 反向过程:UNet 预测噪声,而不是直接预测图像

生成过程是反向的,也就是从纯噪声出发,逐步去噪。但 UNet 在这里做了一件反直觉的事情——它不直接输出一张去噪后的清晰图,而是输出“当前图中混入的噪声”。得到噪声预测之后,再从当前图里减去这个预测,就得到了更干净的一步。

为什么选择预测噪声而不是直接预测图像?从数学上看,噪声预测对应的是对数概率的梯度,这个梯度信息更稳定、更容易学习,而且模型在预测噪声时能把更多精力放在高频结构的恢复上。我在自己跑实验的时候发现,如果修改模型尾部让它直接输出图像,训练损失下降更快,但生成质量反而明显下降,这就是预测目标和输出分布不匹配导致的问题。

整个去噪循环数量取决于采样器设定,常见的是 20-50 步。每走一步,图像潜变量就越清晰一些,最终得到一个近似干净的潜变量表示。这个循环就是你在网页端等待出图时,进度条上那几十次迭代背后发生的事情。

3.3 UNet 内部结构:下采样、上采样、跳跃连接与交叉注意力

UNet 名字来源于它的 U 形结构。左边是编码器路径,逐步下采样,尺寸从 64x64 缩到 8x8 甚至更小,通道数逐渐增加;右边是解码器路径,逐步上采样,恢复回原来的尺寸。下采样能帮助模型看到更大范围的全局信息,上采样则负责恢复细节。

关键的机制是两处。一处是跳跃连接(Skip Connection),让编码器每个阶段的特征直接拼接到解码器对应阶段,这样上采样时能复用底层的精细特征,避免信息丢失;另一处就是交叉注意力(Cross-Attention),UNet 在处理每个位置的图像特征时,会计算它与 CLIP 文本向量之间的相关性。这就是文生图里“文字控制画面”的物理实现。

高分辨率的 UNet 模型通过在多个分辨率阶段反复应用注意力和卷积,逐步从结构到纹理重建画面。很多模型改进工作也集中在这个网络上,比如引入更强大的注意力变体、改进下采样结构、加入控制条件等。UNet 模型改进之所以能显著改变画风,原因就在这里:同一个文本条件,UNet 的内部结构决定了它如何把语义转化成像素分布。

3.4 使用 UNet 的实操注意:步数、CFG、采样器

原理归原理,真正出图时那几个关键参数都是作用在 UNet 与调度器协同过程中的。

  • 步数(Steps):去噪迭代次数。太少(比如低于 10 步)会导致结构不完整,太多(比如超过 50 步)则边际收益趋零,还可能引入额外伪影。我实测 20-30 步是性价比最高的区间。
  • CFG Scale(无分类器引导强度):决定了“条件”对去噪过程的控制强度。推导上它其实是一个调节公式:noise_pred = noise_uncond + cfg * (noise_cond - noise_uncond)。当 CFG 为 7 到 12 时效果通常较好;超过 15,画面会出现过曝和色彩断层。
  • 采样器(Sampler):不同的采样器是用不同方式从噪声预测推导下一步,速度和画质差异明显。比如 DPM++ 系列的收敛质量好,Euler 速度快,DDIM 可在少步数下保持结构。下面的表是我自己的常用参数组合,可以直接抄:
用途采样器步数CFG备注
快速出草稿Euler a207速度快,构图稳定
高质量成图DPM++ 2M Karras307.5细节好,纹理自然
动漫风格Euler a288线条干净
与 ControlNet 配合DPM++ SDE Karras307结构跟随精准

写到这里,扩散模型和 UNet 的原理就基本清晰了。下一节我会把这些概念全部落到代码上,看它们如何对应到一次真实的文生图调用。

4. 从原理到实战:文生图代码与流程复现

第四集是代码实战,也是我自己认为收获最大的一集。知识如果只停留在概念层面,很快就会忘。实际跑一遍代码,把所有张量形状和中间变量对照着原理看一遍,才真正算学会了。

4.1 5 行代码跑通文生图

现在最成熟的库是 Hugging Face 的diffusers,几行代码就能完成一次文生图:

from diffusers import StableDiffusionPipeline import torch pipe = StableDiffusionPipeline.from_pretrained( "stable-diffusion-v1-5/stable-diffusion-v1-5", torch_dtype=torch.float16 ).to("cuda") image = pipe( prompt="a photo of a cat sitting on a windowsill", num_inference_steps=30, guidance_scale=7.5 ).images[0] image.save("cat_window.png")

你会看到代码里出现了num_inference_steps(去噪步数)和guidance_scale(CFG),这两个参数就是刚才在实操注意事项里讲过的。第一次跑通代码的时候,那种“原来原理真的对应到代码里了”的感觉很奇妙。

4.2 拆解 Pipeline:text encoder、unet、vae、scheduler 各做了什么

pipe(...)这一行调用内部隐藏的步骤远不止一个。pip 对象内部一共有四个关键组件:

  • pipe.text_encoder:调用 CLIP,把 prompt 转成(1, 77, 768)的文本向量矩阵;
  • pipe.unet:执行几十步的去噪循环,每一步都接收latents、timestep、encoder_hidden_states三个输入;
  • pipe.scheduler:控制噪声调度的采样算法,负责根据噪声预测计算下一步的潜变量;
  • pipe.vae:解码器,最终把(1, 4, 64, 64)的潜变量放大为(1, 3, 512, 512)的像素图。

理解了 pipeline 的四个组件,你也就理解为什么下载一个模型通常会有多个文件。这些文件各司其职,缺一不可。

4.3 手动实现一次去噪循环,理解采样过程

为了真正看清内部运作,最好绕过 pipeline 手写一遍去噪循环。核心代码并不多:

prompt_embeds = pipe.text_encoder(pipe.tokenizer(prompt, return_tensors="pt").input_ids.to("cuda"))[0] latents = torch.randn( (1, 4, 64, 64), generator=torch.manual_seed(42), device="cuda", dtype=torch.float16 ) latents = latents * pipe.scheduler.init_noise_sigma pipe.scheduler.set_timesteps(30, device="cuda") for t in pipe.scheduler.timesteps: latent_model_input = torch.cat([latents] * 2) latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t) with torch.no_grad(): noise_pred = pipe.unet( latent_model_input, t, encoder_hidden_states=prompt_embeds ).sample noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) noise_pred = noise_pred_uncond + 7.5 * (noise_pred_text - noise_pred_uncond) latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample image = pipe.vae.decode(latents / pipe.vae.config.scaling_factor, return_dict=False)[0]

这段代码里,torch.cat([latents] * 2)是为了同时计算无条件和有条件两个方向的噪声预测,CFG 公式在noise_pred_uncond + 7.5 * (noise_pred_text - noise_pred_uncond)这一行被精确还原。

如果你仔细跟着跑一遍,会发现原理阶段学到的每个概念都是真的。扩散模型确实在这里预测噪声,UNet 确实在这里反复迭代,scheduler 确实在这里控制去噪节奏,VAE 确实在最后把潜变量解码成像素图。

4.4 对照 ComfyUI 节点图理解流程

如果你平时更多用 ComfyUI,这套原理同样可以直接映射。ComfyUI 的节点图本质就是可视化的 pipeline:加载 checkpoint 节点相当于加载 text encoder + unet + vae 三个模型;CLIP Text Encode 节点就是调用 text encoder;KSampler 节点内部就是那个 for 循环;VAE Decode 节点就是最后的解码。

我建议你把上面的 Python 代码和 ComfyUI 工作流对照着看一遍,尤其是 KSampler 节点里的steps、cfg参数。ComfyUI 文生图提示词写不好,很多时候不是节点用错了,而是用户不理解每个参数背后的数学含义。当你把代码和界面对应起来,调起参数会更有依据,不会再靠玄学。

5. 从原理出发的常见问题排查:手部、提示词、性能

知识到这一步已经齐了,最后一节我把实际使用中最常遇到的几个问题,结合原理做个排查实录。这些问题都可以直接用前面的框架定位到具体组件。

5.1 手部崩坏难题:为什么 AI 画手容易翻车,实测有效的“三招”

AI 绘画手部修复难题是社区里最经典的问题。从原理看不难理解:手部在画面里像素占比小、结构复杂、关节自由度极高,潜空间压缩时容易丢失细节,而 CLIP 对“手”这类具象词汇的语义理解又不如“猫”“树”那么稳定,UNet 在生成时就容易出现手指数量错误、关节扭曲。

我实测下来有效的三招:

  • 第一招:用 ControlNet 的 OpenPose 手部关键点控制。在生成前先用姿态估计提取目标手部骨架,让 UNet 严格参照关键点绘制,手的结构会稳定很多。
  • 第二招:局部重绘(Inpainting)。下载一个带手部修复能力的修复模型,或者直接在 ComfyUI 里面把废手区域用遮罩选出来,在局部重绘流程里单独迭代,只修复手部而不动其他画面。
  • 第三招:用细节修复插件或脚本。比如 After Detailer 这类工具,首先生成低分辨率整体图,再用专门的检测模型把手部区域裁出来重绘,整个过程全自动。

三招交叉使用,基本能解决 90% 的手部问题。我自己的习惯是出图时先看整体构图,随后单独跑手部修复,效率比反复整体重出高得多。

5.2 提示词很长却不生效:踩到 CLIP limit 了

前文提到的 CLIP limit 已经不是冷知识,但每天还是能看到有人问“我写了 400 个词,为什么后面全没生效”。这里面有两个层面:

  • 如果提示词总 token 数超过 77,超出的部分会被直接截断,自然无法影响结果。SDXL 配合 LongCLIP 能到 225,但也有限制。
  • 如果没超限但感觉某些关键词没生效,可以检查权重。低权重会被模型视为“次要信息”,在交叉注意力中被其他高权重词淹没。

解决办法也很简单:把重要的主体词放在前 30 个 token 内,用逗号分隔语义块,辅助修饰词放在后面。我见过好的提示词几乎都是“主体+风格+光照+细节”的清晰结构,很少真有需要 300 个 token 的场景。

5.3 显存不够、CPU 只能跑:stable diffusion cpp 和部署优化

本地跑 Stable Diffusion 对硬件确实有门槛,但不是没有变通方案。在 NVIDIA GPU 上,开启 attention slicing、使用 fp16 精度、安装 xformers 或 FlashAttention,都能显著降低显存占用。8GB 显存跑 512x512 完全没问题,6GB 也勉强能跑,关键是要开启切片。

如果你完全只有 CPU,stable diffusion cpp是一个值得关注的方向。它把模型量化后交给 CPU 推理,低配电脑也能跑出图,只是速度慢很多。在 8 核处理器的 Mac 上生成一张 512 的图大概要一到两分钟,作为尝鲜或者无 GPU 环境下的验证是够用的,但别指望它能当生产力工具。

5.4 问题定位速查表

最后做成一个速查表,遇到问题按表排查,能快速定位是哪一环出错。

现象可能原因排查方向
提示词后半段完全不生效超过 CLIP token 上限精简提示词,主体前置
画面结构崩坏、主体模糊去噪步数太少增加步数到 25-30,换 DPM++ 采样器
色彩过曝、边缘发光CFG 过高降低 CFG 到 7-9
手部指尖结构混乱潜空间细节丢失ControlNet 手部关键点 + 局部重绘
多次生成结果完全不变随机种子固定更换种子或启用随机种子
显存溢出注意力机制占用过高开启 attention slicing,使用 xformers

这套排查思路是我在实际折腾中一点一点总结出来的。它的好处在于不是教你一个个孤立的小技巧,而是让你能把每个问题映射回原理链路,自然知道该改哪个环节。

我个人实际操作中的体会是:Stable Diffusion 最值得投入时间去学的不是最新炫酷的插件,而是它底层的这几个核心概念。CLIP 决定模型听不听得懂你的话,UNet 决定它画得好不好,scheduler 决定它画得快不快,VAE 决定你最终看得清不清。把这四件事想清楚,遇到再新的技术名词也能快速归类。

最后再分享一个小技巧:每当你调出一个满意的作品,顺手把当时的采样器、步数、CFG、种子和提示词结构记录下来,积累一套自己的参数库,比反复试错有效率得多。这个思路不仅适用于 Stable Diffusion,后来的其他生成模型,包括现在火热的文生视频方向,本质上也都遵循“条件编码、潜空间扩散、解码输出”的框架。把一套核心机制吃透,再迁移到新模型上,你会发现自己上手的速度快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询