AI 工具

用 Stable Diffusion Turbo 生成可复现图片:固定种子并记录 PNG 元数据

Stable Diffusion 可以在自己的电脑上运行。sd-turbo 是一个蒸馏版本,1~4 步就能出图,没有显卡也能用 CPU 跑。 这篇做 txt2img.py:固定种子生成可复现的图片,把提示词、种子、模型版本和许可证写进 PNG 元数据,并自动检查“同一种子是否得到同一张图”。

BINBHO · 图文教程

Stable Diffusion 可以在自己的电脑上运行。sd-turbo 是一个蒸馏版本,1~4 步就能出图,没有显卡也能用 CPU 跑。 这篇做 txt2img.py:固定种子生成可复现的图片,把提示词、种子、模型版本和许可证写进 PNG 元数据,并自动检查“同一种子是否得到同一张图”。

先看清成品

  • CPU 上加载约 1.5 秒(已下载时),生成一张 512×512 约 5 秒。
  • 同一种子两次生成的像素哈希一致(5434373c6f30f978);不同种子不同。
  • 发现并修复:diffusers 0.41 中 torch_dtype 已弃用,改用 dtype。pytest 3 项通过(真实加载模型)。

合规提醒:sd-turbo 使用 Stability AI Community License:研究、非商业用途,以及年收入低于 100 万美元的个人或组织的商业用途免费;超过的需要企业许可,以模型页面和 stability.ai/license 为准。sd-turbo 仓库不含安全检查器(diffusers 加载时会提示):不要把未经过滤的生成服务公开给他人使用。不生成真实人物的误导性图像,不对他人照片做未经同意的修改;图生图只处理你有权使用的图片。模型从 Hugging Face 官方仓库下载,不使用来路不明的模型文件。

运行说明:本篇在 box 上真实运行了 Stable Diffusion:stabilityai/sd-turbo(revision b261bac6),diffusers 0.41.0,torch 2.14.1(CPU 版),8 核 CPU、无显卡。512×512、1 步生成一张约 4~5 秒。文中的图片都是这次运行生成的。没有测试 GPU,也没有运行 ComfyUI 和 SD 3.5(SD 3.5 需要在 Hugging Face 上同意许可后才能下载,且 CPU 上太慢)。

做完之后你会得到什么?

成果 说明
txt2img.py 生成并写入元数据
test_txt2img.py 3 项测试(真实运行模型)
example.png 本文生成的示例图(种子 42)

需要准备哪些环境?

项目 说明
Python 3.10 以上
PyTorch CPU:pip install torch --index-url https://download.pytorch.org/whl/cpu;有 NVIDIA 显卡时按 PyTorch 官网选择 CUDA 版本
diffusers 等 pip install diffusers transformers accelerate safetensors pillow pytest
磁盘 sd-turbo 的 fp16 权重约 2.5 GB
内存 CPU 运行时建议 8 GB 以上(本文机器 15 GB)

关键事实

事实 来源
SD-Turbo 不使用 guidance_scale 和 negative_prompt,用 guidance_scale=0.0 关闭;1~4 步生成 sd-turbo 模型页面
训练分辨率 512×512;更大的尺寸也能运行,但效果不保证 同上
许可:Stability AI Community License 同上 / LICENSE.md
diffusers 0.41 中 from_pretrained 的 torch_dtype 参数已弃用,提示 “Please use dtype instead.” 本文实测

文件结构

75-stable-diffusion-A-src/
├── txt2img.py
├── test_txt2img.py
└── example.png

第 1 步:安装

python -m venv sd && . sd/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install diffusers transformers accelerate safetensors pillow pytest
# 第一次运行时会从 Hugging Face 下载约 2.5 GB 的 fp16 权重

第 2 步:脚本

"""本地运行 Stable Diffusion(diffusers + stabilityai/sd-turbo),生成可复现的图片:
- 固定种子 → 同一台机器、同一版本下得到同一张图
- 把提示词、种子、步数、模型和版本号写进 PNG 的文本元数据,以后能查到图是怎么来的
sd-turbo 使用 Stability AI Community License(研究、非商业,以及年收入低于 100 万美元的商业使用免费),以模型页面为准。
sd-turbo 仓库不含安全检查器(diffusers 加载时会提示):不要把未经过滤的生成服务公开给他人使用。"""
import argparse, hashlib, json, sys, time
import torch
from diffusers import AutoPipelineForText2Image
from huggingface_hub import model_info
from PIL import PngImagePlugin

MODEL = "stabilityai/sd-turbo"

def load(model=MODEL, device=None):
    device = device or ("cuda" if torch.cuda.is_available() else "cpu")
    dtype = torch.float16 if device == "cuda" else torch.float32           # CPU 上用 float32;fp16 权重会在加载时转换
    # diffusers 0.41 起参数名是 dtype;旧教程里的 torch_dtype 仍可用,但会打印弃用警告
    pipe = AutoPipelineForText2Image.from_pretrained(model, dtype=dtype, variant="fp16").to(device)
    pipe.set_progress_bar_config(disable=True)
    return pipe

def generate(pipe, prompt, seed, steps=1, size=512):
    # sd-turbo 是蒸馏模型:官方用法 guidance_scale=0.0、1~4 步;它是按 512x512 训练的
    g = torch.Generator(device="cpu").manual_seed(seed)
    return pipe(prompt, num_inference_steps=steps, guidance_scale=0.0, width=size, height=size, generator=g).images[0]

def save(img, path, meta):
    info = PngImagePlugin.PngInfo(); info.add_text("parameters", json.dumps(meta, ensure_ascii=False))
    img.save(path, pnginfo=info)
    return hashlib.sha256(img.tobytes()).hexdigest()[:16]

def main(argv=None):
    ap = argparse.ArgumentParser(); ap.add_argument("prompt"); ap.add_argument("--seed", type=int, default=42); ap.add_argument("--steps", type=int, default=1)
    ap.add_argument("--out", default="out.png"); ap.add_argument("--check", action="store_true", help="用同一种子再生成一次,确认结果一致"); a = ap.parse_args(argv)
    t = time.time(); pipe = load(); print(f"加载模型 {time.time() - t:.1f} 秒")
    try: rev = model_info(MODEL).sha
    except Exception: rev = "unknown"
    import diffusers
    meta = {"prompt": a.prompt, "seed": a.seed, "steps": a.steps, "guidance_scale": 0.0, "size": 512, "model": MODEL, "revision": rev,
            "diffusers": diffusers.__version__, "torch": torch.__version__, "license": "Stability AI Community License"}
    t = time.time(); img = generate(pipe, a.prompt, a.seed, a.steps); dur = time.time() - t
    h = save(img, a.out, meta); print(f"生成 {dur:.1f} 秒 → {a.out}(像素哈希 {h})")
    if a.check:
        h2 = hashlib.sha256(generate(pipe, a.prompt, a.seed, a.steps).tobytes()).hexdigest()[:16]
        print("复现检查:", "一致" if h == h2 else f"不一致({h2})")
    return 0

if __name__ == "__main__":
    sys.exit(main())

第 3 步:测试

"""会真实加载 sd-turbo 并生成图片(CPU 上每张约 5 秒);首次运行需要先下载模型。"""
import hashlib, json, pytest
from PIL import Image
import txt2img as T

@pytest.fixture(scope="module")
def pipe(): return T.load()

P = "a watercolor illustration of a quiet mountain village at sunrise, soft light, mist"
def h(img): return hashlib.sha256(img.tobytes()).hexdigest()

def test_same_seed_same_image(pipe):
    a, b = T.generate(pipe, P, 42), T.generate(pipe, P, 42)
    assert a.size == (512, 512) and h(a) == h(b)

def test_different_seed(pipe):
    assert h(T.generate(pipe, P, 42)) != h(T.generate(pipe, P, 43))

def test_metadata_roundtrip(pipe, tmp_path):
    meta = {"prompt": P, "seed": 7, "model": T.MODEL}
    T.save(T.generate(pipe, P, 7), tmp_path / "x.png", meta)
    assert json.loads(Image.open(tmp_path / "x.png").text["parameters"]) == meta

第 4 步:运行

$ python txt2img.py "a watercolor illustration of a quiet mountain village at sunrise, soft light, mist" --seed 42 --check
加载模型 1.2 秒
生成 5.5 秒 → out.png(像素哈希 5434373c6f30f978)
复现检查: 一致
$ python -c "from PIL import Image; print(Image.open('out.png').text['parameters'])"
{"prompt": "a watercolor illustration of a quiet mountain village at sunrise, soft light, mist", "seed": 42, "steps": 1, "guidance_scale": 0.0, "size": 512, "model": "stabilityai/sd-turbo", "revision": "b261bac6fd2cf515557d5d0707481eafa0485ec2", "diffusers": "0.41.0", "torch": "2.14.1+cpu", "license": "Stability AI Community License"}
$ python -m pytest -q
3 passed in 28.60s

生成的图片:

种子 42 生成的水彩山村

(以上为本文实际生成结果)

怎么检查结果?

  • 同一台机器、同一版本下,同一种子得到同一张图(像素哈希一致)。
  • 换显卡、换 torch 或 diffusers 版本后,同一种子的结果可能不同,所以元数据中记下了版本。
  • 用任意 PNG 查看工具或 Image.open(...).text 都能读到参数。
  • 图片被社交平台压缩或另存为 JPG 后,元数据通常会丢失。

调试示例

1. torch_dtype 弃用警告。 照很多教程写 from_pretrained(..., torch_dtype=torch.float32),diffusers 0.41.0 打印:

deprecate("torch_dtype", "1.0.0", _TORCH_DTYPE_DEPRECATION_MESSAGE)     # 内容是 "Please use `dtype` instead."

现在还能运行,但计划在 1.0.0 移除。改为 dtype=。

2. 安全检查器提示。 加载时 diffusers 提示 “You have disabled the safety checker…”。原因是 sd-turbo 仓库本身不带安全检查器,不是我关掉的。自己本地使用没有问题,但如果要做成给别人用的服务,必须自己加内容过滤(见合规提醒)。

常见问题

现象 原因 处理
下载很慢或中断 网络问题 重新运行会续传;也可以先用 huggingface-cli download 下载
CPU 上每张几十秒 线程少或内存不足 减少其他程序;先用 1 步
同一种子结果不同 换了设备或版本 对照元数据中的版本
图片发糊 sd-turbo 只适合 512×512 保持 512;需要大图用其他模型

模板规则

  • 固定种子,记录参数和版本。
  • sd-turbo 用 guidance_scale=0.0、1~4 步、512×512。
  • 遵守许可证;对外服务必须加内容过滤。

项目资源

用途 地址
sd-turbo 模型页面(官方) https://huggingface.co/stabilityai/sd-turbo
sd-turbo 许可证 https://huggingface.co/stabilityai/sd-turbo/blob/main/LICENSE.md
Stability AI 许可说明 https://stability.ai/license
diffusers 文档 https://huggingface.co/docs/diffusers/index

能复现的图,才说得清它是怎么来的。