前阵子群里一个朋友又跑来问,说他的8G显存显卡跑Stable Diffusion,分辨率稍微拉上去就提示CUDA out of memory,试了几次之后已经准备下单换卡。我拦了他一句:先别急着花钱,跑图爆显存不一定全是硬件不够,有时候是PyTorch的显存分配器在捣乱。PYTORCH_CUDA_ALLOC_CONF这个环境变量,改几个参数就能让显存分配逻辑变得聪明不少,6G、8G显卡的体验差距可能非常明显。这篇文章不绕弯子,直接把参数讲透,给可以直接抄作业的配置。
这篇文章适合什么人群?如果你用的是8G甚至更低显存的显卡,装的是WebUI、ComfyUI或者秋叶整合包,跑图时经常遇到OOM,又不想马上换卡,那这篇文章正好对应你的需求。当然,如果你已经有12G以上的显存,我也会给一些碎片化优化的思路,这类用户同样会遇到内存使用率忽高忽低的问题。我会先从原理讲起,再给实际配置,最后是踩坑实录,保证你能照着操作。
1. 爆显存的真相:先搞清楚显存花在了哪
1.1 一张图看懂跑图时的显存流向
很多人一看到CUDA out of memory就觉得是显卡不行,其实你打开任务管理器或者GPU-Z看一眼就会发现问题没那么简单。Stable Diffusion在推理过程里占显存的部件主要分成几块:模型权重、采样时的中间张量、潜在空间变量、以及各种缓存。以SD1.5为例,fp16精度下UNet权重接近2G,VAE和CLIP加起来又有1G左右,模型加载完之后剩下的显存才真正属于“可用的推理空间”;如果是SDXL,光模型权重就逼近7G,8G卡想跑起来就得抠细节了。这里还没算ControlNet、多个LoRA叠加、高分辨率修复这些常见操作,它们每个都会往显存里塞额外的计算图和特征图。
推理和训练的内存消耗模型不一样,训练需要保留计算图用于反向传播,显存占用会高好几个量级;而推理虽然不需要反向传播,但采样器每走一步都要在UNet里完整前向一次,中间特征图要反复分配释放。如果分配器每次大块请求都去和CUDA驱动打交道,或者释放后留着巨大的空洞,显存容量明明够,却总是报OOM。这种情况我见得太多了,尤其是装了ControlNet和一堆LoRA之后,显存碎片化特别严重。
1.2 为什么说换显卡不一定是最好的解法
换显卡当然能解决问题,但它是所有方案里成本最高的一个。现在市面上8G到16G的显卡价格差可能超过两千块,而很多时候你差的并不是那几G显存,而是分配器不会合理复用已经被释放的内存块。PyTorch自带的缓存分配器设计目标是没有先后台的推理服务器场景,它会在第一次分配时向CUDA驱动申请一大块显存,之后把释放的块扔进自己的自由列表,下次要的时候优先从自由列表里复用。这听起来没问题,但一旦请求的尺寸和自由列表里的块尺寸对不上,分配器就会申请新内存,老块继续留在列表里,碎片就产生了。
我用一个生活化的比喻来解释:默认分配器就像一个只会把快递盒原样丢进仓库的管理员,仓库里堆满了大小不一的空盒子,下次来一个形状不一样的货物,他宁可去外面再买一个新盒子,也不愿意把旧盒子拆开重组。结果就是仓库明明还有很多空间,却放不下新东西。显卡显存也是同理,总容量没变,可用块却越来越碎,最终表现就是跑图到一半突然爆显存,或者同样一张图,这次能跑,下次换个步数就崩了。
2. PYTORCH_CUDA_ALLOC_CONF 参数到底改了什么
2.1 PyTorch 缓存分配器的工作机制
PYTORCH_CUDA_ALLOC_CONF是PyTorch提供给开发者的一把“扳手”,用来调整底层缓存分配器的行为。默认情况下,PyTorch会预分配一块较大的显存,然后通过自己的缓存机制管理分配与释放,目的是减少和CUDA驱动交互的耗时。这个策略在频繁分配小张量的场景下很高效,但在Stable Diffusion这种“一下子要一个大特征图、然后又释放”的模式下,很容易产生大量碎片。
碎片问题最直接的表现是:程序报告显存不足,但如果你用命令查一下torch.cuda.memory_summary(),会发现很多显存其实被free list给滞留了。PyTorch官方也承认,在内存碎片严重的情况下,缓存分配器可能比实际需要的用量多保留10%到20%的显存。对8G显卡来说,这被“滞留”的1到2G完全可以让一张图从OOM变成顺利出图。
2.2 核心参数逐个解析
PYTORCH_CUDA_ALLOC_CONF支持多个参数,用英文逗号分隔,一次性传给分配器。这里挑几个和Stable Diffusion关系最大的来说。
max_split_size_mb是最常用的参数,它决定了多大的显存块允许被分割。当显存块大于这个阈值时,分配器会尽量保留整块,而不是把它切成小碎片;反过来,如果设得太小,大块显存会被切得很碎,后续申请大张量时反而更难找到连续空间。这个参数没有绝对合理的默认值,一般建议在64到256之间试。
garbage_collection_threshold的作用更直接。它接受一个0到1之间的小数,代表当已分配显存占总显存的比例超过这个值时,分配器会主动执行一次垃圾回收,释放缓存中的空闲块。注意,这个参数如果设得太低,分配器会频繁清理缓存,导致推理速度变慢;如果设得太高,显存又可能已经撑不住了才开始回收。我一般从0.8起步,遇到OOM再逐步下调。
expandable_segments是PyTorch 2.1之后加入的能力,它让显存段可以动态扩展,而不是一开始就向驱动申请一整块固定大小的显存。这么做的好处是显存利用率更高、碎片更少,但在某些旧版CUDA驱动或者特殊环境下兼容性不佳,启动时可能直接报错。我把它放在最后尝试,因为一旦开不起来,错误提示对新手不太友好。
roundup_pow2_mb可以强制让分配大小向上取整到2的幂次,比如申请33MB就实际分配64MB。这个参数在极小显存场景偶尔有用,但会浪费空间,不推荐在跑图时使用。
3. 不同显存容量的推荐配置,直接抄
3.1 8G显存显卡怎么配最多
8G显存是目前运行Stable Diffusion最主流的“准入门槛”,但也是最容易出现OOM的容量。我自己8G这张卡长期用的配置是PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128,garbage_collection_threshold:0.8。这个组合的意思是:128MB以上的显存块尽量保持完整,不参与切割;当已用显存达到总量的80%时,自动触发一次缓存清理。实测SD1.5加两个LoRA,1024x1024分辨率,步数放宽到30步,基本不再半路崩掉。
如果你装了多个ControlNet模型,或者经常跑高分辨率修复,可以把garbage_collection_threshold降到0.7,牺牲一点速度换取更保守的内存管理。我见过有人直接开到128+0.9,结果Edgen部位还是在进VAE的时候爆了,这就是阈值设置太高,垃圾回收触发太晚导致的。
3.2 6G及更低显存的极限模式
6G显存跑Stable Diffusion是有点紧张的,尤其是SDXL几乎不可能完整加载。这种容量下我建议组合使用低显存启动参数和分配器参数:一方面是max_split_size_mb:64,garbage_collection_threshold:0.6,另一方面配合WebUI的--medvram或--lowvram。这两个启动参数会把模型分阶段加载,部分注意力模块放到CPU上计算,虽然速度会慢,但能确保不直接OOM。
在ComfyUI里,低显存用户还可以搭配DynamicVram节点或者显存清理节点,在关键节点之间手动释放不需要的缓存,效果最明显的是加载VAE和放大模型时。我用这个组合试图在6G卡上跑SDXL时,极限能跑到1024x1024加一个LoRA,速度快慢就不追求了,至少能出图。
3.3 12G及以上还要不要调
大显存用户是不是就不用管这个环境变量了?未必。12G以上的卡跑SD1.5通常不会整体OOM,但显存碎片化依然存在,表现是长时间连续出图后,同样的参数突然开始报错,或者显存占用曲线不断爬升。对于这种场景,我建议设置max_split_size_mb:256,必要时再加上expandable_segments:True。动态段扩展能明显减少碎片化,缺点是首次加载会多花一点时间,但换来稳定持续的出图体验还是划算的。
下面是我自己在不同显存容量下的推荐配置参数组合,直接复制即可使用。
| 显存容量 | 推荐配置 | 备注 |
|---|---|---|
| 4G-6G | max_split_size_mb:64,garbage_collection_threshold:0.6 | 配合--lowvram使用,SDXL基本别想 |
| 6G-8G | max_split_size_mb:128,garbage_collection_threshold:0.7 | 可用SD1.5,SDXL需极限低显存设置 |
| 8G-12G | max_split_size_mb:128,garbage_collection_threshold:0.8 | 最常用组合,稳定性和速度平衡较好 |
| 12G以上 | max_split_size_mb:256 或 expandable_segments:True | 主要解决连续出图的碎片化问题 |
3.4 参数组合里最容易忽略的优先级
有件事必须单独拿出来说:PYTORCH_CUDA_ALLOC_CONF的生效优先级并不高,如果你的启动脚本里已经固定设置了这个环境变量,那么你在系统环境变量里怎么改都可能不生效。秋叶整合包这类一键启动器尤其容易出现这个问题,因为它内部会有一套自己的环境变量写入逻辑。我建议花一分钟确认一下启动器的设置面板里有没有“自定义环境变量”这一类入口,有的话直接填进去,没有的话再去改系统变量或bat文件。
另外,千万不要把garbage_collection_threshold理解成“真的只用80%显存”,它只是一个触发回收的阈值,不是硬性上限。实际分配行为依然取决于模型和分辨率,参数只是让分配器在接近极限时更勤快一点。想靠它把8G当16G用是不现实的,但很多时候它足够把“差一点就OOM”变成“顺利出图”。
4. 实操配置:三步把参数装进你的SD环境
4.1 Windows与Linux下的环境变量设置
最直接的设置方式是在命令行里先声明环境变量,再启动对应的启动脚本。我自己在Windows上跑WebUI时,习惯写一个专门的启动bat,里面第一行就是这个环境变量。
set PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128,garbage_collection_threshold:0.8 webui.bat如果你用的是PowerShell,语法稍微有点不一样,用$env:来赋值:
$env:PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:128,garbage_collection_threshold:0.8" .\webui.batLinux和macOS上用export导出即可:
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:128,garbage_collection_threshold:0.8" ./webui.sh注意,用命令行设置的环境变量只在当前终端会话内生效,重启电脑或者重新打开终端后就会消失。如果你想一劳永逸,可以去Windows的系统设置里找到“高级系统设置”->“环境变量”,新建一个系统级别的变量,或者写入Shell的配置文件。这样做的好处是以后不管启动哪个环境都不会漏掉参数。
4.2 在WebUI、ComfyUI、秋叶整合包中落地配置
说完了通用方法,来说每个具体环境怎么落地。WebUI用户如果不想改系统环境变量,可以直接编辑webui-user.bat,在调用webui.bat之前加上set指令。这样每次启动都自动生效,而且如果换了电脑,拷贝bat文件过去也能带上配置。
ComfyUI用户的操作类似,直接在启动命令里加上环境变量设置就行。如果你用ComfyUI的桌面版或启动器,需要留意设置面板里是否有“环境变量”“启动参数”这类自定义项。我见过有人改了启动脚本,结果管理器自动更新脚本时把改动覆盖了,最好把参数固化在系统环境变量里,而不是启动脚本里。
秋叶整合包的情况稍微特殊,它把很多启动逻辑封装在启动器里,直接改启动脚本可能被重置。我建议在启动器的高级设置中寻找环境变量配置入口,实在找不到,就通过系统环境变量添加。注意,重复设置同一个变量时,启动器内部的优先级可能覆盖系统变量,所以需要验证最终是否生效。
这里给一个通用的验证方法:在Python环境里直接运行下面这段代码,看输出内容。如果返回的字符串中包含你设置的关键参数,说明环境变量已经注入成功。
import torch print(torch.cuda.memory_summary())更简单的方法是看一眼程序启动日志。PyTorch在部分版本里会在初始化时打印当前使用的缓存分配器配置,如果看到了你设置的参数,那就说明生效了。
4.3 和其他显存优化手段怎么配合
PYTORCH_CUDA_ALLOC_CONF不是万能的,它处理的是分配器的“时空调度”,但模型权重、激活值、VAE解码这些内容该占多少还是占多少。所以我通常把它和另一套显存优化手段组合使用。
WebUI用户常见的组合是--medvram --xformers。前者让模型分模块加载,后者优化注意力层计算,两者都能显著减少显存占用。ComfyUI用户则喜欢挂一个显存清理节点,在流程的不同阶段主动调用torch.cuda.empty_cache()释放空闲缓存,再配合garbage_collection_threshold自动回收,效果加倍。如果你已经用了这些手段还OOM,再回来调分配器参数,基本就是最后一块拼图了。
4.4 实测过程记录:从“步骤10/30崩掉”到稳定出图
这里放一个真实的实测过程,参数配置是8G显存、SD1.5、两个LoRA、1024x1024、采样步数30。没有设置PYTORCH_CUDA_ALLOC_CONF之前,跑到第10步左右就报OOM,显存占用在GPU-Z里可以看到一条接近满格的直线,但问题是曲线回落后再次爬升时会突然爆掉。
设置PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128,garbage_collection_threshold:0.8之后,同样的参数跑到25步才出现OOM。把阈值降到0.7后,整张图顺利出图,虽然总耗时多了大概5%到8%,但至少能完整出结果。对于经常半途而废的跑图环节,能稳定出图比快几秒重要得多。
5. 常见问题与排查技巧实录
5.1 设置了参数之后反而更慢或者崩溃
这是一个高频问题。如果你设置expandable_segments:True后程序启动直接报错,或者出图速度大幅下降,大概率是当前PyTorch或CUDA驱动对动态段的支持不完善。这个参数对运行环境有要求,不是所有显卡驱动都能很好配合。遇到这种情况,最快的解决办法是去掉expandable_segments,只保留max_split_size_mb和garbage_collection_threshold。
另一个导致变慢的原因是garbage_collection_threshold设置过低。比如你设成0.5,意味着显存用到一半就开始频繁清理缓存,分配器不断做无用功,速度自然掉下来。我建议从0.8往下降,每次降0.1,找到那个“能出图且速度还能接受”的临界点,不要一上来就搞个极端值。
5.2 显存占用曲线忽高忽低,是不是设置出错了
显存占用曲线波动大,其实不一定是坏事。PyTorch的缓存分配器本来就会在块与块之间复用内存,垃圾回收触发时显存占用会断崖式下降,这一切都属于正常现象。如果曲线是缓慢爬升、从不下降,那才需要担心,说明缓存中有大量空闲块没有被回收,碎片化正在悄悄积累。
我排查的时候习惯用NVIDIA官方工具或者GPU-Z看显存占用的实时曲线,同时观察一个现象:连续跑多张图,同样的分辨率,第一张能过,第二张开始OOM。这种情况几乎可以断定是碎片化问题,而不是容量不够。此时把max_split_size_mb调大,或者开启expandable_segments,通常能解决。
5.3 还是OOM怎么办,给你一份排查清单
如果调完参数还是OOM,那就说明软件层面的调度空间已经压榨得差不多了,需要回到根本问题。我给自己写了一份排查清单,按顺序执行,可以省很多试错时间。
第一步,确认模型和运行精度。是否用了fp16/bf16,如果还在用fp32跑SDXL,那8G显存不管怎么调都无解。第二步,观察OOM发生在哪个阶段。如果是在VAE解码或者高清放大时爆,优先启用VAE Tiling;如果是在采样过程中爆,重点调分配器参数。第三步,降低并发和缓存。关掉浏览器、关掉后台其它占用显存的程序,ComfyUI里还能手动挂载显存清理节点,在每个流程节点之间释放缓存。第四步,如果以上都不行,那就是硬件容量确实不够,该考虑换卡或者减少模型规模了。
5.4 几个踩坑踩出来的细节
最后分享几个平时不会写在官方文档里的细节。第一,max_split_size_mb并不是越大越好,设置成512以上,大块显存虽然不会被切碎,但小张量的分配就容易留下更多空洞,反而加剧碎片化。我见过有人抄了一个大数值设置导致6G卡连512x512都跑不动,这种情况只能把值降回来。
第二,不同的启动器之间,环境变量的注入方式不同,设置完一定要用torch.cuda.memory_summary()验证一次。不要觉得改完设置就一定生效了,启动器更新覆盖脚本是常有的事。
第三,ComfyUI的动态显存节点和garbage_collection_threshold配合使用时,要注意触发频率。节点主动清理加自动回收,如果频率太高,可能造成显存分配器频繁空转,速度反而下降。我自己通常把节点清理放在VAE解码和图像放大这两个高消耗节点之后,其它地方交给PyTorch自动管理。
对我来说,用PYTORCH_CUDA_ALLOC_CONF调优最大的价值不是把8G卡变成16G卡,而是在不花钱的前提下,把手上显卡的潜力再挤出来一块。很多时候,跑图崩在半路并不是真的显存不够,而是分配策略不够聪明。先把这个参数试明白,再去考虑硬件升级,我觉得是比较理性的路径。
最后再分享一个小技巧:当你终于设好了一组满意的参数后,记得把配置存成一套自己的启动模板,不管是bat文件还是环境变量备份。这样系统重装、换电脑、或者给朋友推荐的时候,复制粘贴几分钟就能搞定,不用再从头踩一遍我这篇文章里的所有坑。