显存不够的报错,多数情况下不是“某一项参数太大”这么简单,而是分辨率和精度这两个变量被同时改动过,导致每次重跑看到的报错都是新的,无法判断是哪一步把它推过了线。更省事的判断顺序是反过来的:先把精度切到更省显存的一档跑通一次,确认模型本身能在这张卡上加载并前向;然后在精度固定的前提下,单独调分辨率或帧数,一次只动一个值。这样每一步的显存变化都能归因,而不是靠猜。
遇到 UniWorld-View 显存不足,先别急着降分辨率。建议按“记录当次报错与显存数字 → 切到半精度/混合精度跑一次 → 精度固定后单变量降分辨率或帧数 → 把可行组合固化成启动脚本默认值”的顺序排查。判断依据是退出码、日志中打印的 dtype 和采样到的峰值显存,而不是“这次好像没报错”。低精度可能带来数值差异或算子不支持,若报错从显存不足变成 dtype 不匹配,应先修精度路径再动分辨率。
把报错原文和当时的显存数字一起记下来
只记得“报错了”是最常见的信息丢失。显存不足的报错本身通常只给一行 CUDA out of memory,真正有用的是它前后的调用栈、当时申请的显存大小,以及那一刻整卡已用了多少。建议每次运行都把 stdout 和 stderr 一起落到文件,并单独记退出码。
set -o pipefail
RUN=runs/$(date +%Y%m%d-%H%M%S)
mkdir -p "$RUN"
# 运行前先记一次基线显存
nvidia-smi `--query-gpu`=index,memory.total,memory.used,memory.free \
`--format`=csv > "$RUN/vram_before.csv"
# 后台按秒采样,跑完再 kill
nvidia-smi `--query-gpu`=index,memory.used,memory.free \
`--format`=csv,noheader -l 1 > "$RUN/vram_peak.csv" &
VRAM_PID=$!
python run.py `--config` configs/view.yaml 2>&1 | tee "$RUN/run.log"
echo "exit=${PIPESTATUS[0]}" | tee -a "$RUN/run.log"
kill $VRAM_PID 2>/dev/null || true
注意 2>&1 | tee 之后直接取 $? 拿到的是 tee 的退出码,所以要开 pipefail 或者读 ${PIPESTATUS[0]},否则“是不是跑完了”这个最基本的信号就丢了。采样文件里取最大值近似当作峰值,用来和后面几档做横向比较。
每次实验在记录表里补一行,字段建议固定,方便后面回溯:
- 时间与运行目录名(对应
run.log) - 分辨率、帧数、batch size、精度字段的实际取值
- 退出码;若失败,粘贴报错首行与调用栈最后几行
- 峰值显存与显存总量(来自
vram_peak.csv) - 改动项:只允许一行里出现一个被改的参数
先切到更省显存的精度跑一次
这一步的目的是用最小代价排除“精度模式”这个变量。方向上是把 fp32 降到 fp16 或 bf16,或者启用自动混合精度;不同仓库的落点不一样,常见的是配置文件里的 dtype / precision / half 字段,也可能是命令行开关。具体字段名以仓库当前的配置和启动脚本为准,不要凭记忆改。
切换后必须确认真的生效,而不是“改了配置就算降了”。可验证的信号有三个:启动日志里打印的 dtype 或精度模式;模型加载后参数的实际类型;以及加载完成后的显存占用是否明显低于 fp32 那次。
# 加载完成后打印一行,确认精度路径真的换了
print("param dtype:", next(model.parameters()).dtype)
print("allocated MB:", torch.cuda.memory_allocated() / 1024**2)
风险边界要提前知道:低精度可能让部分算子报 dtype 不匹配,或者某些自定义注意力实现只接受特定精度。如果报错从“显存不足”变成类型错误,说明这一步还没走通,此时继续降分辨率只是把问题藏起来,应该先把精度路径修好。
固定精度后,再单独降分辨率或帧数
精度确定之后,剩下的显存消耗主要来自输入张量规模,通常随分辨率增长得比帧数更快。做法是固定输入样本、batch size 和随机种子,每次只改一项,从低档往上加,直到出现失败,再退回上一档。
- 先固定帧数,只降分辨率:从明显偏小的值开始,逐档上调。
- 分辨率定住后,再单独加帧数:每次只加一档。
- 每档至少跑两次,排除偶发的显存碎片影响。
判断“这一档已经能跑”,不要只看有没有报错,同时看这几点:退出码为 0;输出文件的数量和命名与预期一致;峰值显存没有贴着卡的上限(留一点余量给同一张卡上的其他进程);换成另一段输入样本仍然能跑完。显存不足经常在反向传播或注意力计算时才触发,所以加载成功不等于这一档可行。
把可行组合写进启动脚本默认值
调通之后最容易发生的事,是下次换个人、换台机器又从头试一遍。建议把验证过的组合写成脚本里的档位,默认指向已经跑通的那一档,并在参数区注明每个取值来自哪次记录。
#!/usr/bin/env bash
set -euo pipefail
# PROFILE 从低到高:low / mid / high
# 依据:runs/ 下对应目录的记录表,high 为当前已验证可跑的最高档
PROFILE=${PROFILE:-mid}
case "$PROFILE" in
low)
RES=512; FRAMES=8; DTYPE=bfloat16 ;; # 依据:runs/xxx-low
mid)
RES=768; FRAMES=16; DTYPE=bfloat16 ;; # 依据:runs/xxx-mid,峰值留有余量
high)
RES=1024; FRAMES=24; DTYPE=bfloat16 ;; # 依据:runs/xxx-high,仅本机单卡独占时可用
*)
echo "unknown PROFILE: $PROFILE" >&2; exit 2 ;;
esac
python run.py \
`--config` configs/view.yaml \
`--resolution` "$RES" \
`--frames` "$FRAMES" \
`--dtype` "$DTYPE" 2>&1 | tee "runs/$(date +%Y%m%d-%H%M%S)-$PROFILE.log"
上面数值只是档位组织的示例,实际取值按你自己记录表里能跑通的那一档填。回退方式保持简单:PROFILE=low ./run.sh 就能退回上一档,不需要改脚本;同时把旧的配置备份成 configs/view.yaml.bak 一并提交,出问题时能整体回到上一个可用版本,而不是逐项回忆改过什么。