装到本机之后第一张图跑不出来,通常不是模型本身坏了,而是分辨率、精度(dtype)、采样步数三项都按默认值给满了:分辨率决定显存占用的基数,精度决定同一张图要用多少显存装权重和中间张量,采样步数决定推理阶段要重复多少次。三项同时改,就分不清是哪一项压垮了显存。稳妥的做法是先固定一组能跑通的最小参数,拿到一张成功的图,再单项往上加。
本机跑 SeFi-Image 的可行路径:先把依赖和权重路径确认到能加载,再用最低分辨率、单张、低步数跑通第一张图作为基线;随后用 nvidia-smi 或 torch 峰值统计确认卡在加载、推理还是解码阶段,按「先分辨率、再精度、最后步数」的顺序单项调整,每次只改一项并记录峰值。适用场景是单机单卡的本地出图;边界是显存不足以外的卡死(路径错、依赖没装、驱动不匹配)不算在内,需要先排除环境问题。
在本机确认依赖与权重目录能被读到
先确认程序真的读到了依赖和权重,否则后面所有分辨率调整都是在给环境问题背锅。依赖清单按脚本实际的 import 逐项核对:Python 解释器、CUDA 运行时与 PyTorch 的匹配关系、图像处理库(Pillow、numpy)、可选的加速组件(accelerate、xformers 之类),以及 safetensors 读取库。判断标准不是版本号,而是 python -c "import torch; print(torch.cuda.is_available())" 能返回 True,并且脚本能在 `--device` cuda:0 下启动而不回落到 CPU。
权重目录结构通常长这样,字段名按你手上的仓库替换:
models/
└── sefi-image/
├── model_index.json
├── unet/
│ └── diffusion_pytorch_model.safetensors
├── text_encoder/
├── vae/
└── tokenizer/
启动时用绝对路径指向 sefi-image 这一层,不要指到 unet 子目录、也不要直接指 safetensors 文件本身,这是最常见的加载失败来源。一次成功加载的日志通常有这些特征:先打印配置或 model_index 被读取,然后逐个列出子权重文件正在加载(形如 Loading checkpoint shards 或 Loading weights),接着打印最终 device 与 dtype,最后进入推理流程且没有 Traceback。如果日志停在某个权重文件名上不再前进,先检查该文件是否完整、路径是否可读,不要继续调分辨率。
用最低分辨率跑通第一次推理
基线要足够低:单张、batch size 取 1、分辨率降到显存明显吃得下的档位、采样步数先给到能看出轮廓的量(例如十步上下),精度先用 fp16 或脚本默认的半精度选项。这个组合通常出不了好图,但它能出图,就有了对照物。
python scripts/txt2img.py \
`--prompt` "a cat sitting on a wooden table" \
`--width` 512 `--height` 512 \
`--steps` 10 `--batch` 1 \
`--dtype` fp16 \
`--device` cuda:0 \
`--seed` 1234 \
`--outdir` ./out
参数名按实际脚本字段替换,不要照抄。判断出图成功的依据有三条:进程退出码为 0;日志末尾打印了保存路径或耗时;输出目录里确实出现了新的图片文件,能打开且不是全黑或全灰。把这张图和它使用的参数一起记下来,它就是后面所有调整的参照点。
读显存峰值定位卡在哪一阶段
开两个终端,一个跑推理,一个盯显存。通用监控方式是 nvidia-smi:watch -n 0.5 nvidia-smi 看整体占用,或 nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 0.5 打时间序列。想看进程内部的真实峰值,在脚本里加 torch.cuda.reset_peak_memory_stats() 与 torch.cuda.max_memory_allocated(),在「加载完成、采样循环中、解码完成」三个位置各打一次。
三个阶段的峰值时机不一样:加载阶段是一次性抬升,权重进显存后占用基本稳住;推理阶段随采样步小幅波动,峰值与分辨率、batch size 正相关;解码阶段(把潜变量还原成图)往往在采样循环结束后再抬一次,高分辨率下这一下经常是最后一根稻草。超限时的报错原文特征通常是 CUDA out of memory. Tried to allocate ... 或 torch.cuda.OutOfMemoryError,括号里会写明当前显卡和已占用、总容量。报错栈的位置能反推阶段:出现在权重加载时是加载阶段,出现在采样循环里是推理阶段,出现在解码或保存前后就是解码阶段。
按显存余量依次调分辨率、精度、采样步数
顺序是分辨率 → 精度 → 采样步数。分辨率对显存接近平方关系,动一次效果最明显;精度更像一道开关(fp32 换 fp16/bf16,或打开 attention slicing、分块解码),改一次占用会台阶式变化;采样步数与显存近似线性,但它恰好是你感知最强的一项,一上手就砍它,结果是花了时间还是爆显存。下面这张表按标称显存分档给起点,实际可用余量要以 nvidia-smi 读数减去系统与桌面占用后再判断。
| 显存档位 | 第一步:分辨率 | 第二步:精度 | 第三步:步数 | 验证方式 |
|---|---|---|---|---|
| 8GB 及以下 | 512×512 起步,先保证能出图 | fp16,必要时开切片或分块解码 | 保持低位,够看轮廓即可 | 跑完后留出余量 |
| 12–16GB | 768 起步,单项往上探 | fp16 / bf16 | 逐步加到二十步上下 | 每加一档看峰值 |
| 24GB 及以上 | 1024 起步 | 保持半精度 | 按画质需求给 | 确认解码阶段不触顶 |
参数片段按脚本实际字段名替换,注意有的仓库叫 `--precision`、有的是 `--half`、步数也可能叫 `--sampler`_steps:
width: 512
height: 512
dtype: fp16 # 或 precision: fp16 / half: true
steps: 10 # 或 sampler_steps
batch_size: 1
seed: 1234
每次只改一项,按下面这张表记一行,改完先把这张图跑出来再动下一项。两轮之间如果同时改了分辨率和精度,峰值变化就没法归因。
| 轮次 | 本次改动项 | 分辨率 | 精度 | 步数 | 峰值显存 | 是否出图 |
|---|---|---|---|---|---|---|
| 1 | 基线,不改 | 512×512 | fp16 | 10 | ||
| 2 | 只提分辨率 | fp16 | 10 | |||
| 3 | 只换精度 | 10 | ||||
| 4 | 只加步数 |
固化一组稳定参数并复现
出图稳定后,把参数落到一份文件(yaml / json 或一段启动脚本),不要留在终端历史里。固定随机种子(`--seed` 或等效字段),同时记下会一起影响结果的项:dtype、调度器或采样器、步数、分辨率、batch size、是否有 LoRA 及其权重目录。
sefi_image:
prompt: "a cat sitting on a wooden table"
seed: 1234
width: 512
height: 512
steps: 10
dtype: fp16
batch_size: 1
复现验证的方法很直接:用同一份配置、同一台机器重跑一次,比较输出的尺寸、种子和图像内容。两次肉眼一致,这组参数就可以固定下来;如果出现细微差异,通常是 cudnn benchmark、部分加速算子或分块解码带来的非确定性,可以先关掉加速项、打开确定性选项再比一次,确认差异来源,而不是回头继续改分辨率。换机器或换驱动之后,这组参数需要按新机器的实际显存读数重新确认一遍。