硬件党狂喜:Stable Diffusion跑不动?手把手教你榨干显卡性能
硬件党狂喜:Stable Diffusion跑不动?手把手教你榨干显卡性能
引言:蓝屏那一刻,我差点把显卡扔出窗外
真不是段子。去年双十一我咬牙上了块3080,兴冲冲装好WebUI,啪一点“Generate”,屏幕一蓝,直接给我来段电音。我当时脑子嗡的一声:完了,双十一白忙活了。后来折腾了三天两夜,才发现问题压根不是显卡坏了,而是我压根没搞懂这破模型到底怎么“吃”硬件。今天我就把踩过的坑、翻过的墙、偷过的懒,一次性打包给你,能救一个算一个。咱不聊虚的,全程土味吐槽,代码管饱,能复制粘贴绝不动嘴。
Stable Diffusion到底在折腾啥?别被“画画”俩字骗了
你以为它就是个AI画师,其实人家背后是一套组合拳:UNet负责去噪、VAE负责编解码、CLIP负责把“黑丝御姐”翻译成向量。三步走,每一步都能把你显存啃得渣都不剩。
最贱的是,它默认把整张512×512的图一次性搬进显存做矩阵乘法,显存不够就直接OOM,连招呼都不打。搞清楚这一步,你就知道为啥8G卡跑原生模型像在看PPT。
先上个最简流程图,代码里都有对应钩子,看完再去翻脚本,秒懂:
# 伪代码:一次完整推理的三连击
with torch.cuda.amp.autocast(): # FP16省显存
z = vae.encode(img) # 1. VAE编码,吃显存
c = clip("黑丝御姐,赛博朋克") # 2. CLIP文本编码,吃显存
x_T = scheduler.add_noise(z) # 3. 调度器灌噪声,吃显存
for i in range(50):
noise = unet(x_T, c, t=i) # 4. UNet去噪,显存炸裂
x_T = scheduler.step(noise) # 5. 调度器更新,继续炸裂
img = vae.decode(x_T) # 6. VAE解码,显存再次炸裂
看明白没?第4步循环50次,每次都要把16×64×64的feature map塞显存,8G卡直接原地去世。下面教你咋“骗”过它。
GPU不是万能的,但没GPU你就是想peach
别信那些“CPU也能跑”的鬼话,除非你是时间管理大师。i9-13900K全核拉满,一张512×512出图5分钟,去倒杯咖啡回来才跑一半;同一张图3060只要8秒。
N卡凭啥横着走?三点:
- CUDA核多,矩阵乘法并行度爆炸。
- Tensor Core专门干FP16/BF16混合精度,一条指令顶FP32四条。
- 显存带宽高,3080有760 GB/s,DDR5内存才80 GB/s,差一个量级。
总结:谁快谁说了算,钱包认命。
8G显存别哭,三行命令让你起死回生
先把启动参数贴这儿,能救一个是一个:
# webui-user.bat 经典乞丐版
set COMMANDLINE_ARGS=--xformers --medvram --opt-split-attention
解释人话:
--xformers:把Attention拆成一块块,显存占用直接砍30%。--medvram:模型分层搬显存,CPU当缓存,速度换空间。--opt-split-attention:再把Attention矩阵切块,老卡福音。
如果你连6G都不到,直接上 --lowvram,再配梯度检查点:
# 在脚本里手动开检查点,再省20%
from torch.utils.checkpoint import checkpoint
def checkpointed_unet(x, c, t):
return checkpoint(unet, x, c, t, use_reentrant=False)
注意:检查点会掉速10%左右,但显存从7G掉到5G,还要啥自行车。
显存魔术第二招:模型切片+lazy load
有时候你非跑768×768,怎么挤都挤不下,那就把模型横着切,用到哪块搬哪块。HuggingFace Accelerate封装好了,白嫖即可:
from accelerate import init_empty_weights, load_checkpoint_and_dispatch
with init_empty_weights():
model = UNet2DConditionModel.from_config(config)
model = load_checkpoint_and_dispatch(
model, "runwayml/stable-diffusion-v1-5",
device_map="auto", offload_folder="offload"
)
device_map="auto" 会帮你把权重按显存大小切片,吃不满就扔内存,速度砍一半但能跑,总比蓝屏强。
CPU:别拿村长不当干部
见过太多冤种,显卡4090,CPU i3-10100,内存16G,跑起来还没我笔记本快。为啥?
- 文本编码CLIP在CPU上跑,提示词一多直接100%单核,GPU干等。
- VAE解码也吃CPU,一张512×512图要跑1000×1000次矩阵,单线程直接拉胯。
- 内存带宽低,数据搬运成瓶颈。
血泪建议:
- CPU至少6核12线程,内存32G起步,双通道插满。
- 给CLIP整上
open_clip的ONNX版本,GPU一把梭:
import open_clip
model, _, preprocess = open_clip.create_model_and_transforms(
"ViT-B-32", device="cuda"
)
tokenizer = open_clip.get_tokenizer("ViT-B-32")
text = tokenizer(["黑丝御姐,赛博朋克"]).cuda()
with torch.no_grad():
text_features = model.encode_text(text)
- VAE解码也扔GPU,别让它在CPU养老:
vae.decode(z.cuda(), return_dict=False)[0]
Windows还是Linux?别在环境上耗掉一周青春
WSL2现在确实香,内核直通、CUDA子系统一键装,但有两个坑:
- 显存回收有bug,长期跑会泄露,OOM概率+20%。
- Windows自带显卡驱动更新,一重启就把CUDA搞崩。
生产环境直接裸Ubuntu 22.04,驱动锁死525版本,一条命令搞定:
sudo apt install nvidia-driver-525 nvidia-dkms-525
Docker党也别高兴太早,镜像一层层套娃,体积50G起步,NAS用户流泪。我最后折中方案:
- Conda环境隔离,一个项目一个env,升级炸也只炸一个。
- 依赖锁死
requirements.freeze.txt,别手贱乱升级。
conda create -n sd python=3.10
conda activate sd
pip install -r requirements.txt
量化压缩:让模型瘦成闪电,但别崩了画风
FP16是基操,直接省一半显存,画质肉眼几乎无损。再狠点,上INT8:
from optimum.intel import INCStableDiffusionPipeline
pipe = INCStableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", quantization_config={
"algorithm": "quantization",
"preset": "mixed",
"dtype": "int8"
}
)
速度+30%,显存再省20%。但注意:
- 人脸模型别轻易INT4,眼珠子会飞出来。
- 二次元模型胆子大,INT4都能用,色块崩了反正看不出。
量化完一定跑一遍单元测试:
prompt = "1girl, solo, upper body, looking at viewer"
image = pipe(prompt, num_inference_steps=20).images[0]
image.save("test_int8.png")
肉眼+FID指标双保险,别偷懒。
多卡并联?先问问电源答不答应
家用插座极限3500W,双4090满载800W,再加CPU、主板、RGB灯带,分分钟跳闸。
真要上,先搞清两种并行:
- Data Parallel:同一张图复制到多张卡,batch_size翻倍,速度几乎线性。适合出图接单,一张图5毛,批量100张血赚。
- Model Parallel:把UNet横着切开,单张图分布式跑,显存叠加。适合训练微调,推理反而更慢。
# Data Parallel一行代码
unet = torch.nn.DataParallel(unet)
但注意:
- 显存镜像复制,主卡还是8G就白搭。
- 需要NVLink,否则PCIe带宽成瓶颈,3060双卡还不如单卡3080。
结论:99%的人单卡优化到位比瞎堆卡更香,先把xformers、量化、检查点玩明白再说。
Out of Memory急救手册:别一崩溃就重启
遇到OOM先打这三连,90%能救回来:
# 1. 看是谁偷吃显存
nvidia-smi
# 2. 清掉PyTorch缓存
python -c "import torch; torch.cuda.empty_cache()"
# 3. 查僵尸进程
fuser -v /dev/nvidia*
代码层面再检查:
- batch_size是不是手滑写成了8?
- 是不是忘了
with torch.no_grad()?训练图推混用会爆。 - 生成完图没及时
del image?循环推理一定手动gc:
import gc
for prompt in prompt_list:
image = pipe(prompt).images[0]
image.save(f"{hash(prompt)}.png")
del image
gc.collect()
torch.cuda.empty_cache()
开发骚操作:缓存、复用、懒加载,省下的时间够三杯咖啡
- 文本编码缓存:提示词不变就不用重跑CLIP,哈希当key,10ms变1ms:
import hashlib, pickle, os
def hash_prompt(p): return hashlib.md5(p.encode()).hexdigest()
cache_file = f"cache/{hash_prompt(prompt)}.pkl"
if os.path.exists(cache_file):
text_embeddings = pickle.load(open(cache_file, "rb"))
else:
text_embeddings = pipe.encode_prompt(prompt)
pickle.dump(text_embeddings, open(cache_file, "wb"))
- VAE预热:解码最慢,提前把VAE扔GPU并开cudnn基准:
torch.backends.cudnn.benchmark = True
vae = vae.cuda().half()
- 懒加载提示词库:别把1000条提示词一次性全读内存,用生成器按需读:
def prompt_reader(path):
with open(path) as f:
for line in f:
yield line.strip()
for p in prompt_reader("prompts.txt"):
image = pipe(p).images[0]
版本控的坑:别当最新版的舔狗
WebUI一天三更新,ControlNet、T2I-Adapter、IP-Adapter轮番上阵,老模型直接罢工。血泪教训:
- 生产环境锁死commit id,git checkout别手软。
- 新功能开feature分支,炸也只炸测试。
- 模型库用软链,版本切换1秒完成:
ln -sf stable-diffusion-v1-5 models/Stable-diffusion/sd-webui.ckpt
清灰玄学:技术再牛,也怕灰尘一厚
见过最离谱的,GPU温度95℃,风扇狂转,推理速度腰斩,拆机一看,散热片变成毛毯。
每月例行:
- 关机拔电源,毛刷+吸尘器,温柔点。
- 换硅脂,信越7921,温度立降10℃。
- 风道别瞎装,前进后出,玻璃侧板闷罐必翻车。
结语:显卡是你亲兄弟,别让它吃灰又吃土
写完收工,回头看看,我从蓝屏小白到能靠SD接单,中间踩的坑足够拍部《流浪显卡》。技术细节千变万化,但核心就一句:别瞎堆硬件,先榨干现有每一滴性能。
今晚别刷短视频了,把xformers打开、量化整上、缓存加上,跑一张768×768,听显卡风扇高歌,那就是你钱包在鼓掌。

1948

被折叠的 条评论
为什么被折叠?



