先降批量还是先降分辨率,判断顺序建议是:把批量数降到 1,分辨率保持不动,看是否还报错。批量降下来只是把一次出图拆成多次跑,画面本身基本不变;分辨率一降,细节和构图能力就跟着丢了,而且很难再调回来。所以先用最小代价把这两项分离开:先确认批量是不是放大项,再谈分辨率要不要让步。
MOSS-Image 报显存不足时,用单变量法定位:固定分辨率、把批量降到 1 再跑一次,仍然报错说明瓶颈在分辨率、精度设置或显存碎片,与批量关系不大;能跑通则说明批量是主要放大项。接着保持批量 1 逐档降分辨率,记录每档的显存峰值和出图耗时,再反过来逐档上调,取第一个报错组合的前一档作为本机稳定上限。所有结论以本机读数为准,显卡型号、精度和是否启用分块推理都会改变结果。
记录报错时的分辨率、批量数与显存占用,作为基线
没有一组可比对的数字,后面每次调参都只能靠感觉。报错出现的那一次,先把三样东西原样留下来:完整报错文本、当时的参数取值、以及运行期间的显存读数。报错文本要整段复制,不要只记一句“显存不足”,因为典型的 OOM 会写明想分配多少、已占用多少、还剩多少,这几行往往直接指向问题。
# 现象示例:保留你实际控制台输出的原文,下面只是需要关注的信息类型
RuntimeError: CUDA out of memory. Tried to allocate X GiB
GPU 0 has a total capacity of Y GiB of which Z GiB is free
显存读数建议在推理前后各取一次。系统层面用 nvidia-smi 观察,脚本层面打印峰值分配量,两者对上以后再写进表里。
# 另开一个终端,1 秒一次,观察 used 的变化峰值
nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 1
import torch
torch.cuda.reset_peak_memory_stats()
# 在这里调用 MOSS-Image 的出图流程
print(torch.cuda.max_memory_allocated() / 1024**3, 'GiB peak')
把读数填进同一张表里,后续每次只改一个变量并追加一行,基线才算建立起来。
| 分辨率 | 批量数 | 显存峰值 | 出图耗时 | 结果 |
|---|---|---|---|---|
| (报错时的取值) | (报错时的取值) | (nvidia-smi 峰值) | — | OOM |
| (同上) | 1 | (待填) | (待填) | (待填) |
只把批量数降到 1,其他不动,看是否还报错
这一步的目的不是解决问题,而是判断批量到底占多大权重。分辨率、精度、采样步数、是否加载安全过滤器等全部保持和报错那次一致,只动批量数。如果你用的是配置文件,改动点通常只在一处。
# 改动前
resolution: 1024
batch_size: 4
# 改动后:只改这一行
resolution: 1024
batch_size: 1
运行后按两种情况分叉,不要急着继续调其他参数:
- 能跑通:批量确实是把显存推过线的那一项。此时分辨率不用动,先把批量 1 的显存峰值记下来,再进入下一节单独评估分辨率。
- 仍然报错:批量的贡献可以排除。问题更可能出在分辨率本身、加载精度(fp32 与半精度的差别往往很大)、同时驻留的其他进程,或长生命周期进程里的显存碎片。可以先重启进程再跑一次,排除碎片因素,再考虑降分辨率或启用分块推理。
对比时把改动前后的参数和结果并列写清楚,避免后面回看时记不清哪次改了哪项。
| 对比项 | 改动前 | 改动后 |
|---|---|---|
| 分辨率 | 不变 | 不变 |
| 批量数 | (原值) | 1 |
| 显存峰值 | — | (待填) |
| 运行结果 | OOM | (待填) |
批量保持 1、只降分辨率,再对比显存变化幅度
批量固定为 1 之后,分辨率成为唯一变量。这一节的读法不是“降到不报错为止”,而是看显存随分辨率变化的幅度有多大。显存占用通常近似跟随像素面积增长,边长翻倍时像素数是四倍,占用也会明显上一个台阶;具体倍数以你表里的读数为准,不同实现会有差异。
降分辨率时要按模型支持的档位逐步来,不要一次跳太多,否则定位不到临界点,也容易把画面质量一次砍掉太多。每一档都同时记录显存峰值和出图耗时:耗时这一列不能省,因为分辨率降低后单张更快,但你往往需要更多次重试或者更长的提示词调整才能达到原来满意的效果,实际总成本不一定下降。
| 分辨率 | 批量数 | 显存峰值 | 出图耗时 | 画面是否可接受 |
|---|---|---|---|---|
| (高档,如原报错档位) | 1 | (待填) | (待填) | (待填) |
| (中间档) | 1 | (待填) | (待填) | (待填) |
| (最低档) | 1 | (待填) | (待填) | (待填) |
如果两档之间显存几乎没变化,说明分辨率不是主导项,要回头检查精度设置、是否同时加载了多个模型组件,或者进程里残留了上一次的占用。这种情况下继续往下压分辨率,只会白白损失画质。
从能跑通的最低配置往上加,找到本机的稳定上限
低配置能跑通之后,方向反过来:一次只上调一项,先抬分辨率再抬批量。先抬分辨率是因为它决定画面下限,批量则更适合作为吞吐调节项。每一步都跑一张真实图片,记录峰值,不要只看能不能加载模型。
| 步骤 | 分辨率 | 批量数 | 显存峰值 | 结果 |
|---|---|---|---|---|
| 1(起点) | 最低档 | 1 | (待填) | 通过 |
| 2 | 上调一档 | 1 | (待填) | (待填) |
| 3 | 同上 | 2 | (待填) | (待填) |
| 4 | 原目标档位 | 1 | (待填) | (待填) |
第一次开始报错的那一行就是临界点,稳定上限取它的前一档,并让峰值离显卡总显存留出一段余量——因为同一张卡上还会有桌面环境、其他进程和显存碎片占用,跑满不等于稳定。把这一档固定写进日常使用的配置文件,之后只有在换模型、换精度或者换卡时才需要重测。
如果目标分辨率下的批量 1 就已经贴近上限,不必强行提批量:串行多次单张出图,或者启用分块推理降低单次占用,都比把参数顶到报错边缘更可靠。批量数是吞吐手段,不是必须项。