简介:这份资源围绕稳定扩散(Stable Diffusion)展开,面向机器学习研究者、算法工程师及希望深入理解扩散模型稳定性与鲁棒性评估的开发者。它提供了一套可运行的稳定性分析工具与框架,通过调整模型参数或数据噪声水平来计算稳定性指标,适用于模型研发、实验对比与鲁棒性验证等场景。压缩包共136个文件,约42.52MB,以Python脚本(43个)和YAML配置(35个)为核心,辅以PNG、JPG等图像结果与预览、Shell脚本、Markdown说明文档及Jupyter Notebook,覆盖从环境配置、模型推理到结果可视化的完整流程。目前已有641人学习下载,说明其在相关社区中具备一定参考价值。读者可借助其中的示例代码、模型卡片与配置模板,快速复现稳定性评估实验,理解扩散模型在不同噪声与参数扰动下的表现,并在此基础上开展自己的研究或工程实践。
1. 从一份 stable-diffusion 仓库文件清单说起:它到底能跑出什么
如果你在 GitHub 上搜 stable diffusion,大概率会落到一个文件列表长得像这样的仓库:latent_imagenet_diffusion.ipynb、results.gif、rick.jpeg、rdm-preview.jpg、sketch-mountains-input.jpg、v1-variants-scores.jpg、sample_0.jpg,外加LICENSE、README.md和Stable_Diffusion_v1_Model_Card.md。很多人第一次看到这份清单是懵的——没有requirements.txt,没有setup.py,只有一个 notebook 和几张示例图,这玩意儿真能跑起来吗?能。而且它跑出来的东西,正是后来让「stable diffusion 文生图」变成日常操作的那套潜空间扩散逻辑。这份资源的核心价值不在于它多完整,而在于它把「扩散模型怎么在潜空间里做条件生成」这件事,用一份可读的 notebook 和一组对照图摊开给你看。适合谁?适合已经会写 Python、想搞明白 stable diffusion 底层到底在算什么、而不是只会点 WebUI 按钮的从业者。你拿到的不是一键包,是一份能拆开看的实现样本。
2. 潜空间扩散与条件生成:这份 notebook 里的核心机制
2.1 为什么是「潜空间」而不是像素空间
扩散模型最早在像素空间做,比如latent_imagenet_diffusion.ipynb这个名字里的 ImageNet 扩散,就是直接在 256×256 或 512×512 的像素上一步步加噪、去噪。问题是像素空间维度太高,一张 512×512×3 的图就是 786432 维,每一步去噪都要在这个维度上算,显存和算力都吃不消。stable diffusion 的关键改动是先把图像压到一个低维潜空间,通常用 VAE 的编码器把 512×512×3 压成 64×64×4,维度直接降到原来的 1/48。扩散过程在这个 64×64×4 的潜变量上做,最后再用 VAE 解码器还原成像素图。notebook 里sample_0.jpg和results.gif展示的就是这个过程的中间态和最终态。你如果只记一件事:stable diffusion 的「stable」不是指训练稳定,而是指它把扩散过程稳定地约束在潜空间里,让消费级显卡也能跑。
2.2 条件注入:文本怎么变成去噪的方向
光有潜空间扩散还不够,你得让模型听你的话。sketch-mountains-input.jpg这张图很说明问题——它是一张草图,对应的是条件输入的一种。stable diffusion 的条件注入靠的是 cross-attention:文本经过 CLIP 文本编码器变成 77×768 的 token 序列,然后在 U-Net 的每个注意力层里,潜变量作为 query,文本 token 作为 key 和 value,做交叉注意力。这样每一步去噪时,模型都会「看一眼」文本条件,决定往哪个方向去噪。v1-variants-scores.jpg里展示的不同变体评分,其实就是不同条件强度下生成结果的差异。你在 notebook 里会看到类似uncond和cond两个分支的拼接,这就是 classifier-free guidance 的实现方式:同时算有条件预测和无条件预测,然后按 guidance scale 外推。
2.3 从 notebook 到可复现:环境与依赖的落地步骤
这份仓库没有requirements.txt,所以你得自己搭环境。常见做法是建一个 Python 3.8 到 3.10 的虚拟环境,然后按 notebook 里的 import 反推依赖。下面这段是我一般会先跑的安装命令,注意 torch 版本要和你的 CUDA 对齐,不然dlopen那类 ImportError 就会找上门。
# 创建虚拟环境,Python 版本建议 3.8-3.10 python -m venv sd_env source sd_env/bin/activate # Windows 用 sd_env\Scripts\activate # 安装 PyTorch,CUDA 11.8 为例,其他版本去官网查对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 notebook 里会用到的核心依赖 pip install numpy pillow matplotlib tqdm transformers diffusers accelerate safetensors逻辑说明:torch和torchvision是扩散模型计算的底座,transformers提供 CLIP 文本编码器,diffusers提供 U-Net 和调度器的现成实现,accelerate负责设备分配。参数上,--index-url后面的 CUDA 版本必须和你nvidia-smi里看到的驱动支持版本匹配,驱动版本低就选 cu117 或 cu116。装完先跑python -c "import torch; print(torch.cuda.is_available())",返回 True 再往下走。
2.4 加载模型与跑通第一张图
notebook 里没有直接给模型下载链接,但Stable_Diffusion_v1_Model_Card.md说明了这是 v1 系列。常见做法是从 Hugging Face 拉runwayml/stable-diffusion-v1-5或CompVis/stable-diffusion-v1-4。下面这段代码是我验证环境是否通的最小闭环。
import torch from diffusers import StableDiffusionPipeline # 指定模型,v1-5 是社区常用版本 model_id = "runwayml/stable-diffusion-v1-5" # 加载管线,fp16 省显存,device_map 自动分配 pipe = StableDiffusionPipeline.from_pretrained( model_id, torch_dtype=torch.float16, safety_checker=None # 本地调试可关,生产环境建议保留 ) pipe = pipe.to("cuda") # 文生图,guidance_scale 控制条件强度 prompt = "a sketch of mountains, clean lines, high contrast" image = pipe(prompt, num_inference_steps=30, guidance_scale=7.5).images[0] image.save("my_first_sd.png")逻辑说明:from_pretrained会下载 U-Net、VAE、CLIP 文本编码器和调度器四部分。torch_dtype=torch.float16把权重降到半精度,显存占用大约从 8GB 降到 4GB 左右。num_inference_steps=30是去噪步数,步数太少图会糊,太多收益递减。guidance_scale=7.5是 classifier-free guidance 的强度,低于 5 条件约束弱,高于 12 容易过饱和。跑通后你会得到一张和sketch-mountains-input.jpg风格接近的图,说明整条链路通了。
3. 参数调优与显存控制:让 6GB 显卡也能出图
3.1 显存不够时的三档降级策略
很多人卡在第一步不是代码错,是显存爆了。stable diffusion 在 512×512 分辨率下,fp32 需要约 10GB,fp16 约 5GB,再加上文本编码器和中间激活,6GB 卡很容易 OOM。我一般按三档降级:第一档开 fp16 和 attention slicing,第二档开 xformers 或 sdp attention,第三档直接上 CPU offload。下面这段是第二档的写法。
from diffusers import StableDiffusionPipeline import torch pipe = StableDiffusionPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16 ) # 开启注意力切片,用时间换显存 pipe.enable_attention_slicing() # 如果装了 xformers,开启内存高效注意力 try: pipe.enable_xformers_memory_efficient_attention() except Exception as e: print("xformers 未安装,跳过:", e) pipe = pipe.to("cuda")逻辑说明:enable_attention_slicing()把注意力计算切成多块,显存峰值能降 30% 左右,代价是速度慢 10% 到 20%。enable_xformers_memory_efficient_attention()需要先pip install xformers,它优化了注意力核,速度和显存都有改善。如果这两招还不够,就用pipe.enable_model_cpu_offload(),把不用的模块挪到内存,但每步会有 PCIe 传输开销,出图时间可能翻倍。
3.2 采样器选择:不同调度器对结果的影响
notebook 里没有显式写采样器,但v1-variants-scores.jpg暗示了不同配置下的评分差异。diffusers 里常用的调度器有 PNDM、DDIM、Euler、DPM-Solver 等。我的经验是:DDIM 适合快速预览,20 步就能看个大概;DPM-Solver++ 在 20 到 25 步时细节最好;Euler a 带随机性,适合创意发散。切换方式很简单:
from diffusers import DPMSolverMultistepScheduler # 替换调度器,use_karras_sigmas 让噪声调度更平滑 pipe.scheduler = DPMSolverMultistepScheduler.from_config( pipe.scheduler.config, use_karras_sigmas=True ) image = pipe(prompt, num_inference_steps=25, guidance_scale=7.0).images[0]逻辑说明:from_config保留原调度器的配置,只改算法。use_karras_sigmas=True是 Karras 等人提出的噪声调度改进,在低步数下更稳定。参数上,步数和采样器要搭配调,DPM-Solver 在 20 步的效果可能比 DDIM 50 步还好,别死守一个组合。
3.3 种子与可复现性:为什么你的图每次都不一样
扩散模型默认每次采样都引入随机噪声,所以同一 prompt 出图不同。要复现,得固定种子。notebook 里sample_0.jpg这种命名通常对应固定种子。做法是给 pipeline 传generator:
import torch # 固定种子,CPU 上生成随机数保证跨设备一致 generator = torch.Generator(device="cpu").manual_seed(42) image = pipe( prompt, num_inference_steps=30, guidance_scale=7.5, generator=generator ).images[0]逻辑说明:manual_seed(42)里的 42 可以换成任意整数,同一个种子加同一个 prompt 加同一个模型版本,结果才可复现。注意device="cpu"是为了避免 CUDA 随机数生成器在不同显卡上的差异。如果你换了采样器或步数,即使种子相同,结果也会变,因为噪声调度序列变了。
4. 避坑与排查:那些让 notebook 跑不起来的常见问题
4.1 ImportError: dlopen 相关错误
现象:在 Mac 或 Linux 上import torch或加载模型时报ImportError: dlopen(...),提示找不到某个.so文件。原因通常是 PyTorch 版本和系统架构不匹配,比如 Apple Silicon 上装了 x86 版 torch,或者 CUDA 版本和驱动对不上。解决:Mac 用户确认装的是torch的 arm64 版本,用pip install torch --index-url https://download.pytorch.org/whl/cpu或对应 MPS 版本;Linux 用户用nvidia-smi看驱动支持的最高 CUDA 版本,再选对应的 torch 安装命令。别混用 conda 和 pip 装的 torch,容易冲突。
4.2 模型下载卡住或超时
现象:from_pretrained卡在下载进度条不动,或者报连接超时。原因:Hugging Face 的模型文件有几个 GB,网络不稳时容易断。解决:先pip install huggingface_hub,然后用huggingface-cli download runwayml/stable-diffusion-v1-5 --local-dir ./sd-v1-5手动下载到本地,再把from_pretrained的参数改成"./sd-v1-5"。如果公司网络有限制,找能访问的镜像站或让运维开白名单,别硬等。
4.3 出图全黑或全灰
现象:跑完没报错,但保存的图是纯黑或纯灰。原因:常见是 VAE 解码时数值溢出,fp16 下 VAE 容易出问题。解决:把 VAE 单独转成 fp32,或者用pipe.vae.to(torch.float32)再解码。另一个原因是guidance_scale设得太高,比如超过 20,导致条件外推过头。先降到 7.5 试,再检查 prompt 里有没有特殊字符。
4.4 显存够但速度极慢
现象:6GB 卡跑 512×512 要两三分钟一张。原因:可能没开 fp16,或者 attention slicing 和 xformers 都没开,或者模型在 CPU 上跑。解决:确认torch_dtype=torch.float16和pipe.to("cuda")都生效,用print(pipe.device)检查。如果开了 CPU offload,速度慢是正常的,那是用时间换显存。另外,num_inference_steps设到 100 以上也会线性变慢,先降到 30 看基线速度。
4.5 notebook 里的旧 API 和新版 diffusers 不兼容
现象:照着latent_imagenet_diffusion.ipynb里的代码跑,报AttributeError或TypeError。原因:notebook 可能是早期版本写的,diffusers 的 API 改过好几轮。解决:以 diffusers 官方文档的当前版本为准,把StableDiffusionPipeline.from_pretrained的参数对照新版调整。旧代码里的ethereum或ldm相关调用,在新版里对应diffusers的UNet2DConditionModel和AutoencoderKL,别硬套。
5. 进阶:用这份资源做条件生成与批量验证
5.1 图生图与草图条件:把 sketch-mountains-input.jpg 跑起来
sketch-mountains-input.jpg这张图不是摆设,它是图生图(img2img)的输入示例。图生图的逻辑是:把输入图用 VAE 编码到潜空间,加一部分噪声,然后按 prompt 去噪。strength参数控制加噪比例,0.0 是原图不动,1.0 是完全重画。下面这段是标准写法。
from diffusers import StableDiffusionImg2ImgPipeline from PIL import Image # 加载图生图管线 pipe = StableDiffusionImg2ImgPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16 ).to("cuda") # 读入草图,转 RGB init_image = Image.open("sketch-mountains-input.jpg").convert("RGB") init_image = init_image.resize((512, 512)) # strength 0.6 保留部分结构,prompt 引导风格 image = pipe( prompt="a detailed mountain landscape, oil painting style", image=init_image, strength=0.6, guidance_scale=7.5 ).images[0] image.save("mountains_painted.png")逻辑说明:strength=0.6表示加噪到 60% 的程度,保留 40% 的原始结构。这个值太低会几乎不改图,太高会完全无视输入。resize((512, 512))是因为 v1 模型训练分辨率是 512,输入尺寸不匹配会出鬼影。prompt 要描述你想要的最终风格,而不是描述输入图。
5.2 批量生成与评分:复现 v1-variants-scores.jpg 的对照逻辑
v1-variants-scores.jpg展示的是不同变体的评分对比,这提示我们可以做批量生成加自动评分。评分可以用 CLIP score,算生成图和 prompt 的相似度。下面是一个批量跑并打分的骨架。
import torch from PIL import Image from transformers import CLIPProcessor, CLIPModel # 加载 CLIP 评分模型 clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to("cuda") clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") def clip_score(image, prompt): inputs = clip_processor( text=[prompt], images=image, return_tensors="pt", padding=True ).to("cuda") outputs = clip_model(**inputs) # 图像和文本特征的余弦相似度 return outputs.logits_per_image.item() # 批量生成不同 guidance_scale 下的图并打分 prompt = "a sketch of mountains, clean lines" for gs in [5.0, 7.5, 10.0, 12.0]: gen = torch.Generator(device="cpu").manual_seed(42) img = pipe(prompt, num_inference_steps=30, guidance_scale=gs, generator=gen).images[0] score = clip_score(img, prompt) print(f"guidance_scale={gs}, clip_score={score:.2f}") img.save(f"variant_gs{gs}.png")逻辑说明:clip_score返回的是图像和文本特征的相似度对数,值越高说明生成图越贴合 prompt。guidance_scale从 5 到 12 扫一遍,你能看到分数先升后降,峰值附近就是适合这个 prompt 的取值。注意每次生成都用同一个种子,排除随机性干扰。这套方法可以直接用来验证v1-variants-scores.jpg里的评分趋势,也能帮你给自己的 prompt 调参。
5.3 一个我踩过的坑:别在 notebook 里直接跑全量
latent_imagenet_diffusion.ipynb如果按顺序全跑,中间某一步 OOM 之后,前面的变量还在显存里,后面重跑会叠加占用。我现在的习惯是:每跑完一个阶段就torch.cuda.empty_cache(),并且把不用的模型del掉再清。另外,notebook 里的results.gif如果是用多帧拼的,注意帧数和分辨率,别在 6GB 卡上一次性生成几百帧。从那以后我每次跑 notebook 都强制走一遍「先小分辨率试通、再放大、最后批量」的流程,省得中途翻车重来。希望帮到你。
本文还有配套的精品资源,点击获取