2024-07-01
Stable Diffusion 3 是 Stability AI 推出的文本生成图像模型系列。 它和早期 Stable Diffusion 系列最大的区别,不只是“图像质量更好”,而是底层生成框架发生了明显变化:核心从传统 U-Net 思路转向 Multimodal Diffusion Transformer,也就是常说的 `MMDiT`。
Stable Diffusion 3 的模型卡和 Diffusers 文档都提到,它使用的是 Multimodal Diffusion Transformer 架构。
简单理解,MMDiT 把文本和图像 latent 放进更偏 Transformer 的建模方式里处理,而不是继续只依赖传统卷积式 U-Net 主干。
可以把 SD3 的推理链路粗略拆成几段:
Diffusers 里的 StableDiffusion3Pipeline 也能看到这个结构:
SD3Transformer2DModel:负责 denoise latent 的 MMDiT 主体FlowMatchEulerDiscreteScheduler:和 transformer 配合完成采样AutoencoderKL:负责 latent 和图像之间的编码解码CLIPTextModelWithProjection:两路 CLIP 文本编码器T5EncoderModel:一路 T5 文本编码器这里很关键的一点是:SD3 不是只吃一个文本编码器。
模型卡里说明,SD3 Medium 使用了三路固定的预训练文本编码器,包括 OpenCLIP-ViT/G、CLIP-ViT/L 和 T5-XXL。
这也是它在复杂文本描述和图中文字任务上更有发挥空间的原因之一。
diffusers 跑通本地使用 SD3,最常见的方式是通过 Hugging Face diffusers。
模型是 gated 模型,使用前需要在 Hugging Face 页面接受访问条件,并准备好登录 token。
先安装依赖:
python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install torch torchvision torchaudio
python -m pip install -U diffusers transformers accelerate sentencepiece protobuf safetensors
python -m pip install pillow
登录 Hugging Face:
huggingface-cli login
如果你只想在脚本里使用 token,也可以通过环境变量读取:
export HF_TOKEN="你的 Hugging Face Token"
下面是最小可用版本:
import torch
from diffusers import StableDiffusion3Pipeline
model_id = "stabilityai/stable-diffusion-3-medium-diffusers"
pipe = StableDiffusion3Pipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
)
pipe = pipe.to("cuda")
image = pipe(
prompt="A cinematic photo of a glass greenhouse floating above a quiet black lake, soft moonlight, ultra detailed",
negative_prompt="low quality, blurry, distorted",
num_inference_steps=28,
guidance_scale=7.0,
width=1024,
height=1024,
).images[0]
image.save("sd3_greenhouse.png")
如果没有 CUDA,可以先用 CPU offload 或者更小分辨率测试:
import torch
from diffusers import StableDiffusion3Pipeline
pipe = StableDiffusion3Pipeline.from_pretrained(
"stabilityai/stable-diffusion-3-medium-diffusers",
torch_dtype=torch.float16,
)
pipe.enable_model_cpu_offload()
image = pipe(
prompt="A tiny robot painter drawing stars on a dark studio wall",
negative_prompt="bad anatomy, blurry, messy text",
num_inference_steps=24,
guidance_scale=6.5,
width=768,
height=768,
).images[0]
image.save("sd3_robot.png")
如果是 Apple Silicon,可以尝试 MPS,不过 SD3 对显存和算子支持更敏感。
不稳定时建议回到 CPU、降低尺寸,或使用云端 GPU。
device = "mps" if torch.backends.mps.is_available() else "cpu"
pipe = pipe.to(device)
SD3 的生成质量很大程度取决于 prompt 和采样参数。
先看几个高频参数:
prompt:正向描述,决定主体、构图、风格和细节negative_prompt:反向描述,用来压掉明显不想要的特征num_inference_steps:采样步数,越大通常越稳,但速度会下降guidance_scale:文本引导强度,太低容易跑偏,太高可能发硬width / height:输出尺寸,越大越吃显存generator:控制随机种子,方便复现可以写一个简单的生成函数:
from pathlib import Path
import torch
def generate_image(
pipe,
prompt: str,
output: str,
negative_prompt: str = "",
seed: int = 42,
steps: int = 28,
guidance: float = 7.0,
width: int = 1024,
height: int = 1024,
):
generator = torch.Generator(device=pipe.device).manual_seed(seed)
result = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=steps,
guidance_scale=guidance,
width=width,
height=height,
generator=generator,
)
image = result.images[0]
Path(output).parent.mkdir(parents=True, exist_ok=True)
image.save(output)
return output
用法:
generate_image(
pipe,
prompt="A clean product photo of a matte black smart speaker on a stone table, soft studio lighting",
negative_prompt="blur, low quality, extra objects, broken text",
output="outputs/speaker.png",
seed=314,
steps=28,
guidance=7.0,
)
这类封装比在 notebook 里反复复制 Pipeline 调用更利于维护。
后面做批量生成、API 服务或自动化评测时,可以直接复用。
SD3 对复杂 prompt 的理解更好,但这不代表 prompt 可以随便堆词。
更稳的写法是把 prompt 拆成几个层次:
可以写一个 prompt 组装器:
def build_prompt(
subject: str,
scene: str,
style: str,
lighting: str,
details: list[str] | None = None,
text: str | None = None,
):
parts = [
subject,
f"scene: {scene}",
f"style: {style}",
f"lighting: {lighting}",
]
if details:
parts.append("details: " + ", ".join(details))
if text:
parts.append(f'clear readable text: "{text}"')
return ", ".join(parts)
prompt = build_prompt(
subject="a futuristic coffee cup shaped like a small spaceship",
scene="on a dark marble table, minimal background",
style="premium product photography",
lighting="soft rim light and gentle reflections",
details=["black ceramic", "silver edge", "steam glow"],
text="MOON BREW",
)
反向 prompt 也别写成一大串无脑词。
可以先从这些通用项开始:
negative_prompt = "low quality, blurry, distorted hands, unreadable text, watermark, extra objects"
如果发现某类图总是出问题,再针对性追加,而不是一开始就把负面词写成超长列表。
如果你要做一组封面图、商品图、角色设定图,最好把 prompt 放进 JSON 或 CSV,而不是写死在脚本里。
示例 prompts.json:
[
{
"name": "neon-library",
"prompt": "A neon-lit library inside a space station, cinematic composition, rich details",
"negative_prompt": "blurry, low quality, messy shelves",
"seed": 11
},
{
"name": "glass-garden",
"prompt": "A glass garden room floating above a dark ocean, elegant concept art",
"negative_prompt": "bad perspective, noisy",
"seed": 12
}
]
批量生成脚本:
import json
from pathlib import Path
def batch_generate(pipe, config_path="prompts.json", output_dir="outputs"):
jobs = json.loads(Path(config_path).read_text(encoding="utf-8"))
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
results = []
for job in jobs:
name = job["name"]
output_path = output_dir / f"{name}.png"
generate_image(
pipe=pipe,
prompt=job["prompt"],
negative_prompt=job.get("negative_prompt", ""),
seed=job.get("seed", 42),
output=str(output_path),
steps=job.get("steps", 28),
guidance=job.get("guidance", 7.0),
width=job.get("width", 1024),
height=job.get("height", 1024),
)
results.append({"name": name, "file": str(output_path)})
Path(output_dir / "manifest.json").write_text(
json.dumps(results, ensure_ascii=False, indent=2),
encoding="utf-8",
)
return results
这样做有几个好处:
如果你想把 SD3 变成内部小服务,可以先用 Flask 做一个轻量接口。
注意:图像生成任务比较重,这个示例适合单机小工具;如果要多人使用,建议接任务队列。
from pathlib import Path
from uuid import uuid4
from flask import Flask, jsonify, request, send_from_directory
app = Flask(__name__)
OUTPUT_DIR = Path("outputs")
OUTPUT_DIR.mkdir(exist_ok=True)
@app.post("/api/generate")
def api_generate():
data = request.get_json(force=True)
prompt = data.get("prompt", "").strip()
if not prompt:
return jsonify({"error": "prompt is required"}), 400
negative_prompt = data.get("negative_prompt", "")
seed = int(data.get("seed", 42))
width = int(data.get("width", 1024))
height = int(data.get("height", 1024))
width = max(512, min(width, 1024))
height = max(512, min(height, 1024))
name = f"{uuid4().hex}.png"
output_path = OUTPUT_DIR / name
generate_image(
pipe=pipe,
prompt=prompt,
negative_prompt=negative_prompt,
seed=seed,
output=str(output_path),
width=width,
height=height,
)
return jsonify({"image_url": f"/outputs/{name}"})
@app.get("/outputs/<name>")
def outputs(name):
return send_from_directory(OUTPUT_DIR, name)
请求示例:
curl -X POST http://127.0.0.1:5000/api/generate \
-H "Content-Type: application/json" \
-d '{
"prompt": "A calm black studio scene with a glowing crystal camera, product photography",
"negative_prompt": "low quality, blur, watermark",
"seed": 123,
"width": 1024,
"height": 1024
}'
工程上建议再补几件事:
SD3 Medium 并不算轻,尤其是完整文本编码器和较大输出尺寸会吃很多显存。
常见优化手段包括:
torch_dtype=torch.float16enable_model_cpu_offload()width / heightgenerator 固定 seed,方便对比参数变化Diffusers 文档还提到可以用 Tiny AutoEncoder for Stable Diffusion 3,也就是 TAESD3,让 latent 预览解码更快。
import torch
from diffusers import AutoencoderTiny, StableDiffusion3Pipeline
pipe = StableDiffusion3Pipeline.from_pretrained(
"stabilityai/stable-diffusion-3-medium-diffusers",
torch_dtype=torch.float16,
)
pipe.vae = AutoencoderTiny.from_pretrained(
"madebyollin/taesd3",
torch_dtype=torch.float16,
)
pipe = pipe.to("cuda")
如果你要做的是正式出图,建议先比较 Tiny VAE 和原 VAE 的结果差异,再决定是否用于主流程。
SD3 Medium 的 Hugging Face 模型页包含访问条件和授权说明。
使用前要确认自己的用途是否符合对应 license,尤其是商业化、对外服务和再分发场景。
另外,图像生成系统不要只考虑“能不能生成”,还要考虑“生成结果怎么被使用”。
建议至少做这些限制:
这部分不是装饰,而是生成式应用上线时必须认真处理的工程边界。