☰
Stable Diffusion底层原理拆解:CLIP、UNet与采样器如何协同工作
2026/10/26 4:03:47 网站建设 项目流程

一个非常普遍的感受是:用 Stable Diffusion 出图很容易,跑个 ComfyUI 工作流或用现成 WebUI 一键生成谁都会,但一旦涉及“为什么这组参数有效”“为什么换了个采样器结果差这么多”“为什么长提示词经常失效”,大多数人就只能靠试错。试错本身没错,但没有原理支撑,试错的成本很高。我最初也是靠背参数、抄作业来出图,直到把 CLIP、扩散模型、UNet 这几个模块彻底啃了一遍,才真正理解整条链路里每个环节到底在干什么。这篇笔记就是我当时刷完四集精讲之后沉淀下来的核心内容,从文本编码、扩散去噪、噪声预测网络到代码落地,一层层拆开讲。适合不想只当“鼠标摄影师”、希望真正掌握文生图底层逻辑的读者。

1. 先从一张图是怎么“算”出来的说起:Stable Diffusion 的整体架构拆解

很多人第一次接触 Stable Diffusion 的原理图,看到 CLIP、UNet、VAE、调度器这些名词堆在一张流程图里,第一反应就是发怵。其实没必要,整条链路可以压缩成一句话:文字先被编码成计算机能理解的条件向量,随机噪声在 UNet 的引导下逐步去噪,最终解码成像素图片。

1.1 隐藏在 Pipeline 里的五个核心模块

为了讲清楚后续的原理,我必须先把整条流水线上的角色都列出来,后面会反复用到这些名词。

VAE(变分自编码器)的 Encoder 部分负责把 512×512×3 的像素图像压缩成 64×64×4 的潜空间特征。这一步很关键,因为它把图像从像素空间搬到了一个更紧凑的数学空间,后续的扩散和去噪全部在这个低维空间进行,计算量大幅下降。为什么要压缩?你可以把它类比成拍完照片后导出手机相册里的“预览图”,虽然分辨率小,但保留了编辑所需的全部关键信息。

CLIP 文本编码器负责把用户输入的自然语言转换成 77 个 token 的向量序列,每个 token 变成一个 768 维(SD 1.x)或 1280 维(SD 2.x/SDXL)的特征向量。这些向量会被送入 UNet 的注意力机制里,作为“作画指令”。注意这里的 77 不是随便定的,它是 CLIP 模型的最大上下文长度,这也是为什么超过 77 个 token 的提示词会被截断。

UNet 是整个扩散模型的主干网络,负责在每一步去噪时预测噪声,并利用文本条件引导噪声向特定语义方向演化。它是一个带有编码器-解码器结构的卷积网络,同时内部嵌入了 cross-attention 模块,专门负责把 CLIP 输出的文本特征和图像特征融合。

调度器(Scheduler)不参与神经网络计算,但决定了去噪的步长和噪声衰减策略。它就像精确的搅拌步骤说明,规定每一步加多少噪声、减多少噪声,不同采样器(如 DDIM、DPM++、Euler)本质上是不同的“搅拌策略”。

1.2 为什么 Stable Diffusion 能在消费级显卡上跑起来

在纯像素空间做扩散不是没有先例,但 512×512×3 的图像要跑几百步去噪,每一步都在海量像素上做计算,对显存和推理时间都是灾难。Stable Diffusion 的核心创新之一,是把扩散过程搬进了 VAE 压缩后的潜空间。

这种设计直接带来了两个显著收益。首先是计算量大幅下降,64×64 分辨率潜空间的特征图,其计算量相比 512×512 像素空间降低了约 64 倍,这让普通人用单张消费级显卡就能完成原本需要数据中心级别硬件才能做的事情。其次是语义与像素解耦,在高维潜空间中,模型更容易学习到图像的语义结构,而琐碎的像素细节交给 VAE 解码器去恢复。

这里有一个初学者常问的问题:为什么不直接用 CLIP 的输出去生成图像?答案在于 CLIP 的本质。CLIP 是一个理解模型,它负责做图文匹配,擅长判断“这张图和这句话是否相关”,但完全不擅长“画出这张图”。Stable Diffusion 中的 UNet 才是真正的“画家”,CLIP 更像一个在旁边不断提供作画指导的“艺术顾问”。

2. 文本条件从哪来:CLIP 编码机制与 77 Token 的现实含义

这一节我们聚焦文本编码。很多出图效果不理想的根源,其实在文本编码阶段就已经注定了。提示词写得再好,如果编码器没把关键信息提取出来,后面 UNet 再怎么尽力也是巧妇难为无米之炊。

2.1 CLIP 是怎么样把一句话变成向量的

CLIP 模型的训练思路很直观:给模型看成千上万的图文对,让它学会判断哪张图和哪个描述更匹配。训练完成后,CLIP 的文本编码器就具备了一个非常重要的能力:把语义相近的文字映射到向量空间中相近的位置。

举个例子,“一只金色的猫咪”和“金毛的猫”虽然在字面上差异很大,但在 CLIP 的向量空间里,它们会落在附近区域。这种语义向量化的能力,正是后续图像生成所需要的条件输入。你需要理解的是,这里生成的向量矩阵形状是 [77, 768](以 SD 1.x 为例),其中 77 是 token 数上限,768 是每个 token 的特征维度。

这些向量进入 UNet 的 cross-attention 层后,每个 token 向量都会和图像特征计算相关性,并据此分配“注意力权重”。权重高的 token 对生成过程的影响更大。如果提示词里有某个关键物体描述,但它在 77 token 之后被截断了,这个物体就永远不会出现在画面里。

2.2 为什么提示词越长越容易“翻车”:token 截断机制

很多人遇到过这样的情况:写了一大段精心构思的提示词,结果生成的图里只包含了前几句的内容,后边的描述完全没体现。这背后的原因就是 CLIP 文本编码器把输入截断到了 77 个 token。

一个 token 通常对应一个单词或一个词根,但并不是所有语言都按空格切词。英文中 “beautiful” 可能被拆成 “beau” + “tiful”,中文的字 token 化规则又有所不同。实际经验是,英文提示词大概能容纳 60-75 个完整单词,再多就一定会截断。

还有一种常见误区,就是无脑堆砌大量风格词却在最后才放核心主体。我见过不少人把 “masterpiece, best quality, highly detailed, 8k, trending on artstation” 这些质量词放在最前面,等轮到真正想画的物体时,token 配额已经快用完了。合理的做法是把画面主体放在提示词的最前面,然后是主体特征描述,最后才是风格和质量修饰词。这样即使发生截断,丢掉的也是相对次要的修饰成分。

2.3 负面提示词和 CFG Scale:条件控制的“力度旋钮”

CLIP 编码器输出的条件向量,并不会被直接当成“输入图像”那样直接塞给 UNet,而是通过 classifier-free guidance(无分类器引导)机制来发挥作用的。这个机制听起来很学术,但理解起来并不难。

Stable Diffusion 在实际训练时,会随机丢弃一部分文本条件,让 UNet 学会在无文本条件下也能去噪。推理时,模型会同时计算两次预测:一次带文本条件,一次不带文本条件。看到这里你应该就明白了,最终的预测噪声 = 无条件预测噪声 + CFG Scale ×(有条件预测噪声 - 无条件预测噪声)。

CFG Scale 这个参数控制了“无条件预测”和“有条件预测”之间的差值对最终结果的贡献程度。CFG Scale 越大,生成结果越贴合提示词,但过大的话图像会过饱和、细节失真、出现明显的“烧焦感”。经验值一般在 7-12 之间。负面提示词的原理也是通过 CLIP 编码后,在生成时让模型朝着“远离”这些语义特征的方向去噪。

3. 扩散过程与去噪:理解正向加噪、反向采样和调度器的工作逻辑

在 CLIP 把文字变成条件以后,接下来的核心问题就是:一个布满随机噪声的图,是怎样一步步变成一张清晰的图的?这就是扩散模型最核心的机制,也是很多人最容易听得一头雾水的地方。

3.1 前向过程:有序地“毁掉”一张图像

扩散模型训练时的前向过程,在形式上就是在原始图像上逐步叠加高斯噪声。每一步叠加的噪声量由提前设计好的 schedule 决定,通常前几步加得少,后几步加得多。这个过程被称作“破坏过程”或“加噪过程”,它是训练模型时构造学习目标的基础。

关键点在于,前向过程并不需要一步一步迭代计算。一个特别好的数学性质是:因为高斯噪声的叠加满足正态分布的可加性,我们可以直接从原始图一步跳跃到任意第 t 步的加噪情况。公式上通常写作 x_t = \sqrt{\bar{α}_t} x_0 + \sqrt{1-\bar{α}_t} ε,其中 ε 是标准高斯噪声,\bar{α}_t 由预先配置的 schedule 决定。这大大加快了训练数据预处理的速度。

你可以这样理解前向过程:它有点像一个逆向的延时摄影——原本清晰的画面随着时间推移,被越来越多的“静电雪花”淹没,直到最后完全看不出原始内容。模型要学的,就是如何倒放这个过程。

3.2 反向过程:核心思路是“预测噪声”而不是“直接出图”

扩散模型的原理解释中有个特别反直觉的地方:训练时模型的输出并不是“直接生成一张图”,而是“预测在这步被添加的噪声”。为什么会有这样的设计?这涉及到一种巧妙的数学简化。

设想的逻辑是这样的:如果我们知道了一张图上叠加的是什么噪声,我们就可以通过移除这些噪声来还原上一步的图像。预测添加的噪声,本质上等于学会逆转加噪过程。这个目标在数学上更容易优化,而且和“逐步还原”这个最终目标完美契合。UNet 做的就是这件事,它在每一步去噪时输出一个“噪声图”,然后调度器负责“减去”这个噪声,得到更清晰一步的图像。

这里有个容易混淆的概念:UNet 的输入不只有当前噪声图,还有时间步 t 的信息和文本条件。时间步信息是为了让网络知道目前处于加噪过程的哪个阶段,以便决定该用多大的力度去噪;文本条件则引导整个去噪过程朝向指定的语义方向演化。时间步嵌入在 UNet 中通常通过 FiLM 调制或 AdaGN 机制来影响卷积层的特征缩放。

3.3 采样器之间的差异:DDIM、Euler、DPM++ 各有何优劣

既然反向过程是逐步去噪,那“用多少步去噪”“每一步怎么去噪”就成了一套独立于神经网络的策略,由采样器(Scheduler)决定。不同采样器的数学原理非常深,作为使用者,我建议重点掌握三类。

DDIM 类采样器的特点是去噪步数可以取得很少(比如 10-20 步),却依然保持不错的图像质量,代价是收敛得比较慢、细节可能略逊于其他迭代型采样器。适合快速迭代初稿。Euler 是最基础的采样器,计算简单直接,每一步只用一次网络推理,速度最快,但步数较少时质量下降明显,适合搭配高步数出图。DPM++ 系列是目前质量-速度平衡性最好的选择,能在 20-30 步内达到非常好的细节表现,但对参数变化的敏感度相对较高,不太适合大幅改动。

实际经验告诉我,不要迷信某一个采样器。可以先固定一个你习惯的采样器摸清楚它的参数行为,之后在需要精细细节的情况下切到 DPM++ 类,做快速探索时开回 DDIM,这个工作流目前用下来最舒适。

3.4 实际推导一个简化版的单步去噪逻辑

为了帮助理解,我不展开数学推导,用一段极简文字描述单步去噪的过程。假设初始噪声图像记为 x_T,调度器计算出的噪声强度是较小的数值,UNet 会预测一个“应该被减掉的噪声” ε_pred,这一步的更新逻辑近似地可以写成 x_{t-1} = x_t - ε_pred,其中具体还需要引入一些系数控制步长。

实际采样器远比这个复杂,但这个简化版本已经足够建立直觉:每步去噪本质上就是在当前图上做一次“减去噪声向量”的操作,文本条件通过 cross-attention 改变的是“这个噪声向量朝哪个方向偏移”。采样步数越多,每一次减掉的噪声就越少,越不容易出现过度修正导致的伪影和结构崩坏。

4. UNet 内部到底在做些什么:残差卷积、注意力机制和条件融合

很多人把 UNet 当作一个黑盒,这其实有点可惜。UNet 是整个 Stable Diffusion 里用来进行语义控制的核心引擎,对它了解得越深入,你调试提示词和参数时就越有底气。这一节我们把它拆开来看。

4.1 UNet 的编码器-解码器结构:U 形特征流的精妙之处

UNet 的结构图看起来像一个 U 字形:左侧是编码器,不断下采样提取高层语义特征;右侧是解码器,不断上采样恢复空间细节。中间还通过 skip connection(跳跃连接)把编码器的浅层特征图直接拼接到解码器对应层,解决深层特征丢失细节的问题。

这种结构对图像生成任务非常合适,因为它天然地兼顾了“全局语义”和“局部细节”两个层面的信息。编码器深层学到的是“这是什么物体”“整体构图如何”这类高层信息,解码器浅层则保留了“边缘在哪儿”“材质质感如何”这类细节信息。通过跳跃连接合并两者,UNet 才能在去噪过程中同时做到语义准确和细节丰富。

在 Stable Diffusion 的 UNet 中,整个网络被划分成多个 stage,每个 stage 内包含若干 ResNetBlock 和 TransformerBlock。前者负责基本的卷积特征变换,后者负责注意力计算。整体遵循典型的缩放配置从上到下的分辨率依次降低、通道数依次升高。

4.2 二大机制:为什么需要 cross-attention

UNet 中的注意力机制分为两类。一种是 self-attention(自注意力),让图像特征图内部不同位置之间互相建立关联,帮助模型理解空间一致性、生成合理的构图。另一种是 cross-attention(交叉注意力),指的是让图像特征和文本特征之间做注意力计算。

cross-attention 的具体过程是:从当前图像特征生成 query,从 CLIP 文本编码器的输出生成 key 和 value。通过 query 与 key 的相似度计算,得出“当前图像区域应该关注哪些文本 token”,再依据这个注意力权重加权求和所有 value。这个机制的直观效果是——图像的不同区域会自动匹配不同语义描述,说“一只穿红色毛衣的猫”,那猫的区域就会强关注 “red sweater” 这几个 token。

理解了这一层,你就会明白为什么很多 ComfyUI 的高级工作流里有人会去可视化 cross-attention 的权重图。他们其实是在看 UNet 在每一步生成时,到底把注意力放在了哪些 token 上。当某个区域的语义不明确时,调整提示词顺序让关键 token 获得更高注意力,远比盲目重来更有效。

4.3 时间步和文本条件如何同时输入 UNet

UNet 的输入并不只有一个噪声图像,它还同时接收时间步 t 和 CLIP 文本向量。时间步信息在送入 ResNetBlock 时通过一种叫做 adaptive normalization(自适应归一化)的机制起作用——具体来说,时间步被编码成向量后,用来调节每个特征的缩放和偏移系数。

用生活化的类比来解释:同一个“减去噪声”的操作,在加噪初期的图像上应该更勇猛一些,在接近清晰的图像上应该更保守一些。时间步条件告诉网络“现在该用哪种力度”,不同时间步对应的特征缩放参数自然不同。文本条件则进入 cross-attention 层,从语义方向上影响特征演化。

这个设计是整个扩散模型训练中一个很微妙但极关键的细节。如果缺少时间步信息,网络就不知道当前应处于哪个去噪阶段,同一个输入就可能被输出两次截然不同的结果,训练根本不能收敛。

4.4 为什么 UNet 是扩散模型的“主战场”

尽管 UNet 的结构看起来复杂,但你要注意一点:整个 Stable Diffusion 的推理成本里有大约 90% 以上都消耗在 UNet 上。CLIP 只推理一次,VAE 编码一次解码一次,而 UNet 要去噪迭代 20-30 次。每一次迭代都要完整跑一遍全网络。

这也是为什么显存不够时,很多人会选择降低输出分辨率、使用 gradient checkpointing,或者使用模型量化。了解 UNet 的算力主导地位很重要,它帮助你理解为什么 Stable Diffusion 系列的性能瓶颈几乎总是卡在 UNet 的推理速度上,而不是文本编码或 VAE 上。

5. 从原理到代码:用开源库跑通一个最小文生图项目

讲了这么多理论,最终还是要落回代码上。我推荐从 diffusers 库入手,它是目前最接近底层原理的工具库,API 设计把整个流水线的每个环节都暴露出来了,调试时能看到每一步输出的 shape,非常适合原理学习。

5.1 环境准备里的三个容易踩的坑

准备环境时我踩过几个坑,这里直接列出来帮你避开。第一个坑是版本兼容性,diffusers、transformers、torch 三个库之间的版本依赖很紧,建议直接用官方提供的安装指令在虚拟环境里安装,不要逐一手动安装。第二个坑是模型下载路径,默认会下载到用户目录下的缓存文件夹,如果磁盘空间紧张,建议提前设置环境变量指向大分区。第三个坑是显存控制,默认配置可能默认加载浮点精度模型,显存小的机器建议先指定 torch_dtype 和低显存模式再加载。

我自己的操作习惯是这样的:先建一个干净的虚拟环境,然后安装 diffusers 库和配套的依赖,再单独安装对应框架版本。等首次跑通后再考虑安装额外的图像后处理工具包。初期不要乱装一堆插件,否则排查问题的时间会比运行时间还长。

5.2 一个最小可复现的 Stable Diffusion 文生图代码

下面这段代码是我当时跑通的最小示例,用的都是社区里最常用的组件。把模型路径换成你之前下载的位置,直接复制即可用。

from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler import torch # 使用半精度加载模型,显存占用大约减半 pipe = StableDiffusionPipeline.from_pretrained( "模型路径", torch_dtype=torch.float16 ) pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) pipe = pipe.to("cuda") prompt = "a cute corgi sitting on the grass, best quality, sharp details" negative_prompt = "lowres, bad anatomy, blurry, jpeg artifacts" # 固定随机种子,方便对比调试,保证结果可复现 generator = torch.Generator("cuda").manual_seed(42) image = pipe( prompt=prompt, negative_prompt=negative_prompt, height=512, width=512, num_inference_steps=25, guidance_scale=7.5, generator=generator, ).images[0] image.save("output.png")

这大概 20 行,但它把前面讲到的所有模块全部串起来了。StableDiffusionPipeline 内部会依次执行三个步骤:加载 CLIP 并编码文本、初始化随机噪声、循环调用 UNet 进行去噪、最后 VAE 解码输出图像。你不一定要点进源码看每一行,但建议把其中几个关键参数亲自改一改,看看画面变化。

5.3 三个高价值参数的含义和推荐起始值

num_inference_steps 代表去噪步数。在 DPM++ 采样器下 20-30 步通常就能得到不错的效果,过少会出现结构崩坏,过多则容易引入额外伪影且耗时增加。guidance_scale 是 CFG 力度,数值越高越贴合提示词但越容易过曝,7-8 作为起点比较安全。seed 是随机种子,固定种子可以让你在调整其他参数时排除随机性干扰,这一点在对比实验时非常重要。

这里再分享一个针对负向提示词的小建议:不要只写 “bad quality” 这类笼统的词,模型会把注意力放在判断“什么是 bad”上,效果很弱。更有效的写法是列出可能导致构图崩坏的具体要素,比如畸形手、多余肢体、文字水印、低分辨率等,按语义强烈程度递减排列。我在实测中,负向提示词的措辞对成图率的影响,有时比正向提示词还明显。

5.4 跑通代码后的下一步:可视化理解注意力机制

如果你读完原理后想进一步印证 cross-attention 的理解,我建议在跑通基础项目后,去尝试可视化 UNet 在每一步生成时的 cross-attention 权重图。diffusers 社区里有现成的注意力可视化脚本,它会告诉你每张特征图在生成时主要“看”了提示词中的哪些 token。

我当时的操作是:拿一张生成结果比较满意的图,把每一层每一头的注意力图保存下来,拼成网格图观察。看到猫的区域确实主要关注了 “cat”、背景区域的主要注意力落在 “grass” 上时,前面学的原理一下子就串起来了。这种可视化的价值在于,它把你的理论知识和实际生成结果建立起了直觉联系,之后调试提示词时你的操作会更有方向感。

6. 实战中的参数选择与常见问题排查:我的经验值推荐与踩坑记录

原理和实践都讲完了,最后这部分全是个人经验,属于那种如果没人提醒你,你可能要花很多时间才能发现的细节。

6.1 分辨率、采样器和 CFG 的推荐组合

很多新人在 512×512 出图正常之后直接拉高分辨率,结果图像结构崩掉。原因在于模型训练时的原生分辨率是 512×512(SD 1.x),直接增大分辨率会让 UNet 看到明显不同于训练分布的输入尺度,影响其去噪判断。稳定做法是先以原生分辨率生成图中主要内容,再使用放大模型或图生图的方式实现高分辨率输出,而不是最初就用高分辨率直接生成。

采样器的选择上,个人主力配置是 DPM++ 2M Karras,步数 28-30,CFG 7.5。这个组合在质量与速度的平衡上表现稳定。如果做快速概念验证,则切换到 DDIM 并降低到 15 步;如果做最终精修,会调成 DPM++ SDE Karras 且步数提到 35-40。这里你们要明白一个原则:采样器和步数的合理搭配比单独追求高步数重要得多。

6.2 两个高频问题的排查思路

第一个问题是“生成的人像手部扭曲严重”。直接在正极提示词里加 “perfect hands” 通常效果很差,因为它改变了注意力分布又没有明确的目标方向。更好的方案是保持提示词干净、显式地写清楚手的姿态,或者在图生图阶段对脸部、手部做局部重绘,而不是指望一次生成。第二个问题是“生成结果雷同,同一构图反复出现”。这大概率是种子固定成了相同数值,或者是模型在此提示词下倾向于对称构图。先随机换几个种子观察整体分布,再决定是否继续调提示词。

还有个容易忽略的问题是首批生成的“偶然相似度”。因为总在最外层做 512×512 的缩小,很多小尺寸模型对细节语义的敏感度不够,同一个提示词在不同批次里可能只改变填充内容而保留整体结构框架。排查的方法是增大步数并配合 CFG 调整,若仍无变化,说明达到模型能力上限,需要换更大模型。

6.3 从原理出发的三个提高出图质量的小技巧

提升质量往往不是靠更长的提示词,而是靠更合理的注意力引导。第一个技巧是调整 token 顺序并减少冗余词,让模型把注意力放在核心内容上;第二个技巧是使用提示词加权语法,比如在目标词上做权重提升,让 cross-attention 对特定 token 的强度倾斜;第三个技巧是善用采样器和步数组合,寻找当前模型和主题下最合适的去噪配置。

这些技巧都可以用前面讲的原理推导出来,而不是玄学。每次改完参数出图,我都建议保留同一个种子对比前后结果,这样你能明确知道是哪个参数起了作用。这也是原理学习最大的回报:你可以带着预判去调试,而不是瞎试。

6.4 我会用到的“省心”参数模板

下面是一个我日常用的参数模板,它不一定适合所有场景,但作为初始化点非常省心:采样器选 DPM++ 2M Karras,步数 25-30,CFG 7.5,默认尺寸 512×512,种子随机但记录下来,负向提示词固定一套通用的缺陷词列表。主体提示词按“主体+动作/姿态+环境+光照+风格+质量词”的顺序写。

模板的意义在于降低变量维度。先固定一组稳定参数,只专注调提示词内容;等提示词思路稳定后,再去微调采样器和步数等参数。我自己的经验是,一次只动一个变量,记录变化,是最快的学习路径;同时动多个变量,大概率得到一堆无效的对比结果。

最后说一个这段时间反复验证的体会:理解原理最大的好处,不是让你变“理论派”,而是让你少做无用实验。以前我改 CFG、换采样器、换模型经常全凭运气,现在每一步调整基本知道影响哪儿、应该望哪个方向调。如果你刚开始看这类原理内容,别急着把所有细节背下来,先把“CLIP 编码文本、UNet 去噪、调度器控制步长、VAE 解码成像”这四句话刻在脑子里,然后跑通上面那个最小代码,再回头看本文的细节,你会发现一切都顺了很多。

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

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

立即咨询