
我前段时间把 Stable Diffusion 的文生图原理完整过了一遍从 CLIP 文本编码、扩散模型的正反向过程、UNet 去噪网络一直到跑通最小文生图代码做了四集精讲笔记。今天把笔记重新整理出来希望能帮正在看原理但还没上手的读者打通这条链路。这套笔记主要适合两类人一类是已经在用 WebUI 或 ComfyUI 出图、但总感觉在“调参调个寂寞”的玩家另一类是刚接触生成模型、想搞懂 Stable Diffusion 底层到底怎么工作的开发者。我不会堆一堆公式而是尽量用可理解的方式把每个模块讲明白再配上一段能直接复现的代码。看完之后同一张提示词图你能知道它为什么长这样也知道往哪个方向调。1. 先搭一张全局流程图从提示词到画面的完整流水线很多人学 Stable Diffusion 容易一头扎进某个细节里比如研究采样器或者纠结某个 LoRA 权重。我的建议是先别管那些先把整条文生图流水线装进脑子里。你要知道你输入一段提示词之后程序到底在做哪些事。简单说整条链路是这样的提示词先经过一个文本编码器把自然语言变成一组向量这组向量作为条件引导一个名叫 UNet 的去噪网络在所谓“潜在空间”里从一个满是噪声的矩阵开始逐步还原出一个图像矩阵最后再由一个解码器把矩阵变成你能看到的像素图。在 Stable Diffusion 里这个文本编码器用的就是 CLIP 的文本分支。它负责把“a cat sitting on a sofa”这种话转成模型能理解的数字表示。UNet 则是整个扩散过程的核心引擎它在每一个去噪步骤里接收带噪声的图像和文本条件预测当前噪声然后剥掉一部分噪声反复几十步后得到干净图像。最后一环是 VAE 解码器它把压缩过的图像潜变量放大还原成 512×512 或 1024×1024 的真实图片。1.1 三次关键形态转换整条流程可以看成三次形态转换。第一次转换是文字变成向量。CLIP 把提示词拆成 token再映射成一个 token 序列这相当于把人类的语言“翻译”成了模型能读的数字语言。第二次转换是随机噪声变成图像潜变量。你在生成时一开始会拿到一张纯随机的高斯噪声图它在潜在空间里是一个低分辨率的小矩阵经过 UNet 反复去噪这个小矩阵逐渐从雪花点变成一张有语义内容的“压缩草图”。第三次转换是潜变量变成像素图像。VAE 解码器把这张 64×64 的低分辨率草图放大到 512×512 甚至更高补上颜色、纹理、光影细节。我当初最大的误区就是以为 Stable Diffusion 是在 512×512 的像素图上直接做扩散。其实不是。它先把训练图压缩到潜在空间再在压缩后的矩阵上做扩散这样计算量小非常多也正因此普通显卡才有机会跑起来。1.2 四集笔记为什么要按这个顺序讲我这套四集笔记的顺序不是随手定的。第一集讲 CLIP是因为它是整条链路的入口提示词的所有规则、截断问题、风格偏向都是在这一步产生的影响。如果不先搞懂 CLIP你会发现很多提示词技巧听起来像玄学为什么关键词放前面更有效为什么长提示词容易被忽略这些都能在 CLIP 的 token 限制里找到解释。第二集讲扩散模型因为它回答了最基本的问题为什么从一张噪声图出发最终能得到一张有意义的图像。前向加噪、反向去噪、采样器、CFG这些概念都源于扩散模型本身。第三集讲 UNet其实是把扩散模型里的“去噪主体”单独拎出来解剖看它怎么接收噪声图、怎么接收时间步、怎么接收文本条件。最后一集才是代码实战因为当你理解了前面的原理代码里的每个参数就不再是魔法数字而是你能预判结果的操作。1.3 “潜在空间”这个词为什么绕不开只要学 Stable Diffusion你一定会看到“潜在扩散模型Latent Diffusion Model”这个说法。Stable Diffusion 本质上就是潜在扩散模型这里的“潜在”指的不是空气里的潜在而是把图像压缩到低维空间里操作。你可以把 VAE 编码器想象成一个压缩算法一张 512×512×3 的图片经过编码器变成 64×64×4 的潜变量。我们不去像素空间里直接扩散而是在这个 64×64 的小矩阵上做去噪。你输入提示词之后扩散模型实际上是在这一小块压缩地图上施工。之所以能这么干是因为图像的语义信息在低维空间里保留得足够好同时计算量大幅下降。这也是为什么早期纯像素扩散模型很难普及而 Stable Diffusion 能用普通消费级显卡生成高分辨率图片。2. 第一集精讲CLIP 文本编码器如何把提示词变成特征向量CLIP 是 OpenAI 提出的一个多模态预训练模型Stable Diffusion 用的是它的文本编码器部分。这一部分看似简单其实藏着很多实际入门的坑。你写提示词时遇见的“失效”“权重漂移”“前后不搭”十有八九都出在 CLIP 身上。2.1 CLIP 在 Stable Diffusion 里的真实定位只用文本分支完整 CLIP 包含一个文本编码器和一个图像编码器训练时用海量图文对做对比学习让匹配的文本和图片在向量空间里靠近。但 Stable Diffusion 在推理时并不需要 CLIP 来识别图片它只把文本编码器拿出来将提示词转成特征向量。这一点必须先明确你在 WebUI 里看到“CLIP skip”调的就是文本编码器的层数你提到“clip模型应用”它其实还有很多别的用途比如零样本分类和图文检索但在生成模型里它就是一支文本翻译笔。为什么偏偏用 CLIP而不是常见的 BERT因为 Stable Diffusion 需要的条件向量最终要和图像特征对齐。CLIP 在图文配对数据上做对比学习它输出的文本表示天然贴近图像语义表示。你在提示词里写“a red car”CLIP 的向量会更靠近实际红车图片的向量空间而 BERT 更擅长处理通用语言任务和图像空间的直接对齐性偏弱。这也是为什么后来很多融合模型仍然把 CLIP 作为默认文本编码器。2.2 77 个 token 的上限为什么你的提示词后半段经常失效这是我在笔记里单独标红的地方。标准 Stable Diffusion 1.x 的 CLIP 文本编码器一次最多处理 77 个 token。如果你输入的提示词超过了这个长度超出部分会被直接截断而不是被“理解”后再放进去。很多人写了一大长串英文描述生成出来的图却只体现了前半段的含义就是因为后半段在进入网络前就被扔掉了。实际写提示词时核心概念一定要放在前面并用逗号做语义切分。比如你需要“光影、构图、主体、风格”四类因素尽量把主体和风格放在头部修饰性内容往后放。另一种常用技巧是拆成多个提示词段配合一些扩展节点来突破 77 token 的限制但最终每段编码结果还是要拼进文本序列里。CLIP 还会做 padding句子不足 77 个 token 时会用空白符补齐到固定长度。每个 token 会被映射成一个 768 维向量SD1.x 的情况SDXL 的文本编码器更大所以其实你每次给模型的不是一句话而是一张 77×768 的“文本特征表格”。你可以把这个表格打印出来观察会发现相同语义的不同表达方式在向量空间里位置会很接近。2.3 CLIP 理解的是语义不是语法CLIP 是被图文对比信号“喂”出来的它对词与词组合背后的语法逻辑理解得很浅。它不是真的读了句子而是把整段话当成一组 token 的特征集合。因此提示词写法更接近“关键词堆叠”而非“完整句子”时效果往往更好。我见过不少新手把提示词写成“In the style of a highly detailed oil painting, a young woman with long flowing hair and bright blue eyes sits beside a window”。这个句子语法完全正确但 CLIP 会把注意力分散到各个修饰词上导致主体不够突出。更常用的是“masterpiece, oil painting, 1girl, long hair, blue eyes, sitting beside window”。这不是玄学是 CLIP 对离散概念的响应方式更直接。2.4 关于 CLIP 的几个常见误区第一个误区以为 CLIP 是识别画面内容的模型。实际上在 Stable Diffusion 流程里它只负责编码文本不负责看图像。第二个误区以为换一个更强的新版 CLIP画质就能提升。文本编码器影响的是语义理解上限画风、细节和分辨率主要由 UNet 和 VAE 决定。第三个误区以为长句一定比短句好。文本越长越容易被截断也可能稀释关键概念。第四个误区把“CLIP 询问机”这类调试工具当成必装组件。这类工具更像黑盒检查器能让你看到某个词对应在哪个 token 附近但调出满意的图终究要靠提示词结构和参数不是靠一次编码查询就能解决问题。3. 第二集精讲扩散模型的前向加噪与反向去噪扩散模型是整个 Stable Diffusion 的生成内核。如果你能理解它采样器、步数、CFG 这些东西你自然而然就懂了。3.1 先从一杯清水滴墨水说起前向过程想象你有一杯清水你在里面滴一滴墨水静置一会墨水开始扩散水变浅灰再滴一滴颜色更深持续滴很多次之后整杯水变成完全均匀的黑色再也看不到原来的透明状态。前向加噪过程就是这个“逐步滴墨水”的过程只不过加的不是墨水而是高斯噪声。数学上从一张干净图像 x0 出发经过 t 步加噪得到带噪图像 x_t可以直接用一个公式表达x_t √(α_t) x0 √(1 - α_t) ε。这里的 ε 是从标准高斯分布里采样的噪声α_t 是一个随时间 t 递减的系数t 越大噪声占比越高。之所以能用这个公式一步算出任意 t 时刻的结果是因为每一步加噪都满足高斯噪声的叠加性质不需要真的一步一步跑。3.2 从雪花还原到照片反向去噪的训练目标前向过程是确定的反向过程才是模型要学的核心。反向时要从一个几乎全是噪声的 x_T 出发一步步还原出 x0。如果每一步你都知道前一步加的噪声长什么样就能把它剥掉。所以训练时我们让 UNet 去预测噪声目标函数非常简单就是让模型预测出来的噪声 ε_θ(x_t, t) 和真实噪声 ε 尽可能接近。在推理时我们从纯噪声开始让 UNet 预测出这一步的噪声然后从当前图像里减掉预测的噪声再加上一点随机扰动重复若干次。这就是为什么生成过程能看到画面从“雪花点”逐渐变成轮廓、再到清晰细节。实际代码里每一步的具体更新公式会因为采样器的不同而变化但底层的“预测噪声—减去噪声”逻辑是一致的。这也解释了为什么模型里往往输出的是噪声图而不是直接输出干净图训练目标决定了它的角色。3.3 采样器、步数与 CFG实际生成里的三大调节旋钮训练好的扩散模型固定之后采样器就是“去噪方式”的选择。不同采样器对步数和随机性处理不同导致同样的模型、同样的提示词用 Euler、DDIM、DPM 出图风格会有差异。我常用的经验是SD1.5 模型用 DPM 2M Karras步数 20 到 30SDXL 用 DPM 2M SDE Karras步数 30 上下。步数太少会明显欠采样画面偏糊步数太多则边际收益很低还拉长生成时间。CFGClassifier-Free Guidance是另一个关键参数。它的思路是同时计算无条件噪声和带提示词条件的噪声然后按 strength 拉大两者差距。CFG 越大画面越贴提示词但过高会过饱和、出现伪影。常见范围是 5 到 9。负面提示词可以理解成把“无条件方向”换成了“不希望的方向”让模型同时避开某些内容。这就是为什么负面提示词能帮你防畸形、防模糊。3.4 潜在扩散模型 LDM 解决了什么问题最早期的扩散模型在像素空间上运行直接对 256×256 的 RGB 图像加噪去噪显存和计算开销都很大。LDM 的核心思路是先通过 VAE 编码器把图像压到 64×64×4 的潜变量在潜变量上跑扩散。这里的“潜在”是把高维像素压缩成了低维语义特征计算量降低一个数量级同时保留足够信息。Stable Diffusion 就是 LDM 的一个具体实现。所以你看很多教程里说“去噪空间是 64×64 而不是 512×512”这不是说最终图像是 64×64而是去噪过程发生在低维空间最后由 VAE 解码器拉回高分辨率。理解了这点你就知道为什么 VAE 对出图效果影响很大它负责高维还原如果 VAE 有问题画面就会灰、糊、色彩不对。4. 第三集精讲UNet 凭什么胜任噪声预测器扩散模型的反向过程需要一个网络来预测噪声。Stable Diffusion 选择的是 UNet这个选择不是随意的结构和任务之间高度匹配。4.1 编码器、解码器和跳跃连接U 型结构为什么适合图像到图像UNet 最早来自医学图像分割结构上是一个 U 字形左侧编码器一路下采样提取越来越抽象的语义特征右侧解码器一路向上采样把抽象特征逐步还原回原始分辨率中间通过跳跃连接把编码器的同尺寸特征直接传给解码器。为什么这种结构适合预测噪声因为预测噪声本质上是一个图像到图像的任务输入一张噪声图输出一张噪声图输入输出分辨率一样。UNet 的下采样路径可以放大感受野让网络看到全局结构知道自己现在处理的是人还是猫上采样路径可以恢复细节把边缘、纹理等局部信息补回来。跳跃连接的存在让浅层细节不会因为多轮下采样而丢失。这也是为什么 UNet 能同时抓住“这是一个人”的语义和“这里手指应该有一根”的细节。4.2 时间步和文本向量是怎么注入网络的UNet 在推理时要接收两个额外信息当前去噪时间步 t以及提示词的条件向量。时间步 t 先被编码成一个 embedding类似 Transformer 里的位置编码然后在各个 ResNet 块里作为偏置加进去。它的作用是告诉网络“你现在看到的噪声图是在第几步的状态剩下的噪声还占多少比例。”不同时间步对应不同的噪声强度网络需要据此调整去噪力度。文本向量则是通过交叉注意力注入图像特征作为 QueryCLIP 输出的 token 序列作为 Key 和 Value。网络在去噪时每次都会“看”一眼提示词决定当前区域该画什么。你可以这样理解时间步像是给画师的进度提醒文本条件像是放在旁边的参考草图。没有时间步同一张带噪图像在不同阶段会得到一样处理这不对没有文本条件UNet 就只能生成随机图像。两者缺一不可。4.3 从结构看手部修复难题为什么手总画崩有关“ai绘画手部修复难题”的讨论非常多网上最常见的梗就是 Stable Diffusion 画手必翻车。从结构上看手部画崩有几个原因。第一手在整幅图像里占的面积通常很小经过 UNet 下采样后手部区域只剩很少的像素细节信息自然不够。第二手部关节非常复杂手指之间相互遮挡和姿态变化空间极大训练数据里的手部状态也往往标注噪声很大。第三交叉注意力机制更容易把注意力分配给面积大、语义强的区域比如脸、身体手部容易被忽视。我见过不少“三招搞定手部修复”的说法核心大致是负面提示词排除畸形手指局部重绘只重画手部区域ControlNet 用 OpenPose 或深度图锁定手部姿态。具体操作我会在第六节细讲但先记住一点手部难题不是单纯加几个提示词能彻底解决的它是模型结构和数据分布的短板只能靠工具链弥补。4.4 不同版本 UNet 的差异有多大SD1.5 的 UNet 参数规模在 860M 左右输入输出是 64×64 的潜变量SDXL 用了更大的 UNet并额外集成一个文本编码器支持原生 1024×1024 出图结构上增加了更多 Transformer 块。你换模型时其实很大程度上就是换 UNet 的权重不同权重的 UNet 训练数据和迭代方式不同画风、概念理解能力自然差异巨大。使用 UNet 有个重要注意事项它是去噪网络不是图像分类器。不要在推理时指望它直接告诉你“这张图是什么”它只负责在给定噪声图和条件时输出预测噪声。另外不同底模型之间混用 UNet 权重很容易出现通道不匹配或语义错乱所以下载模型时最好配套使用对应的 text encoder 和 VAE。5. 第四集精讲最小文生图代码实战原理看得再多不如亲手跑一段代码。这一集我会带你从零搭好环境写一个最小可用的文生图脚本。你不需要 WebUI只需要一个 Python 环境。5.1 环境准备diffusers 全家桶怎么装我用的是 Hugging Face 的 diffusers 库它把管线封装得相当干净适合学习过程。建议 Python 3.10有 NVIDIA 显卡且装好 CUDA 驱动。安装依赖时在终端执行pip install --upgrade diffusers transformers accelerate torch Pillow如果已经装过 torch可以先检查一下版本是不是支持你的 CUDA。第一次运行脚本时会从模型仓库下载权重SD1.5 的基础模型大概 4GB所以要预留好磁盘空间。网络慢的话可以提前用模型下载工具把权重放到本地之后直接把本地路径传给 from_pretrained。这一步和显卡驱动一样属于环境问题我建议先处理顺再说生成。5.2 一段直接能跑的生成脚本下面这段代码是完整的复制保存成generate.py然后运行就能得到一张 512×512 的图import torch from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler pipe StableDiffusionPipeline.from_pretrained( runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16 ) pipe.scheduler DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) pipe pipe.to(cuda) prompt a beautiful painting of a girl with cat ears, soft lighting, highly detailed negative_prompt ugly, blurry, low quality, bad hands generator torch.Generator(cuda).manual_seed(42) image pipe( promptprompt, negative_promptnegative_prompt, num_inference_steps25, guidance_scale7.5, generatorgenerator ).images[0] image.save(output.png)这里我从StableDiffusionPipeline加载模型并把默认调度器换成了 DPM。torch_dtypetorch.float16能让显存占用减半大部分显卡都建议开。negative_prompt传入文本模型会把这些词当作“不希望出现的内容”来避开。num_inference_steps是去噪步数25 是一个比较稳的中间值guidance_scale是 CFG 强度7.5 是我常用的默认值。生成结果是一张 PIL.Image 对象save一下就能用。5.3 显存不够怎么办float16、切片注意力与 offload如果你的显卡只有 6GB 显存直接跑上面的脚本可能报 CUDA out of memory。先不要急着换卡有几个办法依次尝试。第一确认已经用torch_dtypetorch.float16这通常能省一半显存。第二在pipe.to(cuda)之前加上pipe.enable_attention_slicing()它会将注意力计算切成小块显著减少峰值显存代价是速度稍慢。第三用pipe.enable_model_cpu_offload()替代pipe.to(cuda)这个模式会自动把部分模型留在 CPU用的时候再搬到 GPU显存占用更低但第一次生成会有额外延迟。还不行就把分辨率降到 384×384 来测试脚本里通过height和width参数控制。实在不够社区里有“stable diffusion cpp”这类量化解方案适合低配环境但我个人还是建议先把标准 diffusers 流程跑通再考虑这些进阶玩法。5.4 固定随机种子与负面提示词让结果可控代码里generator torch.Generator(cuda).manual_seed(42)的作用是固定随机种子。这样一来同样提示词、同样参数每次生成的结果都一致。这个能力很重要因为很多调参实验必须控制变量你先固定种子改 one 个参数才看得出是哪个变量导致变化。负面提示词同样重要。它利用 CFG 机制把模型往反方向推能帮你避开低质量、畸形的结果。但负面提示词不是越长越好我曾经堆了一长串负面词结果画面构图反而越来越僵硬。比较通用的是“ugly, blurry, low quality, bad anatomy, bad hands, extra fingers”。具体项目可以再结合题材调整。6. 四集笔记之外的实战经验手部修复与翻车排障原理和代码都跑通之后真正进入日常使用你会遇到各种玄学问题。这一节我把自己踩过的坑和常用解法整理出来尤其是手部修复和常见翻车场景。6.1 手部修复三招提示词、局部重绘、姿态控制先说第一招提示词控制。在负面提示词里加“bad hands, extra fingers, fused fingers, deformed hands”能明显降低手指畸形的概率。注意不要把所有负面词都堆在一起每条用逗号分开并且不要加入太抽象的词CLIP 对这些短语义的反应最直接。第二招是局部重绘。如果整张图只有手部崩了你可以用图生图模式把手部区域圈出来然后把 mask 区域内重新生成。关键是重绘步数不要太高模糊度和原始图要接近否则手部区域会跟周围风格不一致。WebUI 里直接用“局部重绘”上传图片画一下 mask 就行diffusers 里则对应StableDiffusionInpaintPipeline。第三招是姿态控制。使用 ControlNet 的 OpenPose 模型你可以提供一个手部骨架图让 UNet 在去噪时被“强制”按这个姿态来画手。这个办法对复杂手势特别有效代价是你要有一个像样的骨架图而且 ControlNet 权重参数要调适度太强会压制画风。三招可以组合使用先加负面提示词降低概率再用 ControlNet 锁定姿势最后用局部重绘修复明显的残余瑕疵。6.2 出图灰蒙蒙、噪点多先查 VAE 和采样参数如果你发现生成的图整体灰蒙蒙或者颜色饱和度很低大概率是 VAE 的问题。Stable Diffusion 1.x 默认自带的 VAE 在某些版本上色彩偏淡社区常用的vae-ft-mse-840000会明显改善色彩。你可以把 VAE 文件下载到本地然后在 WebUI 的设置里加载或者写成StableDiffusionPipeline.from_pretrained(..., vae...)。SDXL 则一般自带新 VAE问题少一些。如果画面噪点多、细节碎先检查 CFG。CFG 太高会导致画面过饱和、不自然CFG 太低则容易糊。另一个常见原因是采样步数太低SD1.5 用 DPM 时尽量不低于 20 步。还有一个容易被忽略的点负向提示词里如果写了太多“detailed texture, sharp focus”之类的正向词会干扰模型反而让细节变得杂乱。6.3 显存溢出的排障顺序很多人在生成中断时只会看到 CUDA out of memory但原因可能不一样。我的排障顺序是先看分辨率512×512 已经能跑还溢出再说显存再看是否开了 fp16然后确认有没有开启切片注意力或 CPU offload最后检查批量大小是不是大于 1。通常到这步问题就解决了。如果是在 WebUI 里运行还可以加启动参数--medvram或--lowvram它们会把模型调度得更保守。但要记住显存优化往往会降低速度所以如果显存够用尽量不要同时开太多优化。另一个容易忽视的问题是后台进程占显存比如浏览器硬件加速、另一个正在运行的生成进程都会挤占显存空间。6.4 一点学习路线的个人建议如果你正准备按四集笔记的顺序学习我的建议是先跑通第 5 节的代码看到一张图真的从文字里生成出来再回头消化第 2 到第 4 节的原理。原因很简单没有经过实践反馈很多概念很难真正记住。跑完代码后你可以故意改参数、换提示词、换采样器去观察每一步变化。公式不需要全推但建议把第 3 节提到的加噪公式和损失函数抄下来自己手写一遍。写的时候你会意识到噪声预测和图像生成之间的直接关系。最后每个失败案例都值得记录图像崩溃、手部畸形、过曝、模糊把这些样本和当时的提示词、参数、种子放一起你很快就能积累出属于自己的“调参直觉”。