生成视频卡在半途时,界面往往不给明确错误,只报一句超时或一直转圈。与其直接调参,不如先把“停住”拆成三种可观察现象来验证:显存是否触顶、进程是否还活着、生成循环是否在等某个资源。可操作顺序是:先看日志最后几行确定停在第几阶段,再用秒级采样把显存峰值和进程存活状态对齐到同一时间线,最后查系统日志里有没有进程被结束的记录。如果证据不全就改配置,多半只是把同一个问题换了个位置复现。
判断方向:显存峰值贴近上限且进程仍在,多半是显存触顶;进程在卡住时刻消失、退出码为 137 一类,多半是被内核或容器运行时结束;进程存活、显存不高却长时间没有日志输出,多半是生成循环在等 I/O、队列或某一步算子。这三类判断只适用于单机或容器内跑生成任务,操作上要先采集、后改动,验证方式以“同一改动下是否还复现”为判据;日志路径和参数名随启动方式差异较大,需要结合自身环境确认。
复现卡住并记下停住所处阶段
先固定一次可复现的输入(同一段提示词、同一分辨率、同一帧数),把整次运行的 stdout/stderr 和时间点落到文件里。停住时要能回答两个问题:日志最后几行是什么,最后一个时间点对应的动作是什么。
mkdir -p ~/mg4-trace
( date '+start %F %T' ; \
python run_matrixgame.py `--prompt` '你的提示词' 2>&1 ; \
echo "exit=$?" ) | tee ~/mg4-trace/run-$(date +%H%M%S).log
上面只是通用骨架,启动脚本名、参数名要换成你自己的。走到界面卡住时,另开一个终端执行:
tail -n 50 ~/mg4-trace/run-*.log
date '+stuck %F %T'
看日志最后一行停在哪一类输出上,据此区分阶段:
- 加载阶段:仍在打印权重、VAE、文本编码器加载或显存预分配信息,说明还没进采样循环。
- 生成阶段:有步数计数、帧进度或采样循环输出,卡在某个 step 不再前进。
- 保存阶段:已经打印生成完成,卡在写帧、编码 mp4、落盘到挂载盘。
同时把“卡住时你在做什么”一并记下来:是否切了窗口、是否又提交了第二个任务、是否同时开了别的占显存程序。这类动作经常是触发条件,不记就再也还原不出来。日志详细程度不够时,可以在启动命令里加框架自带的 debug 或 verbose 开关,具体名称需要按你使用的推理框架确认。
同步采集显存峰值与进程状态
只截一张 nvidia-smi 截图说明不了问题,需要连续采样,并且和进程状态用同一台机器的时钟对齐。开跑前先启动采样,不要让采样晚于任务。
nvidia-smi `--query-gpu`=timestamp,index,memory.used,memory.total,utilization.gpu \
`--format`=csv -l 1 | tee ~/mg4-trace/gpu.csv
要判断“显存是哪个进程占的”,再加一路按进程维度的采样:
nvidia-smi `--query-compute-apps`=timestamp,pid,process_name,used_memory \
`--format`=csv -l 2 | tee ~/mg4-trace/gpu-proc.csv
进程侧用 PID 盯住,而不是靠肉眼数窗口:
PID='你的生成进程 PID'
while : ; do
printf '%s ' "$(date +%H:%M:%S)"
ps -o pid,stat,rss,vsz,etime -p $PID | tail -n +2 || echo GONE
sleep 1
done | tee ~/mg4-trace/proc.log
在容器里跑时,容器内 nvidia-smi 看到的可能是整机视图,进程名也可能显示不全,这时用宿主侧的 compute-apps 采样和 docker stats 交叉验证更可靠。
时间线对齐的做法:把 run-*.log、gpu.csv、proc.log 放在同一目录,取日志最后一行的时间作为 T0,然后查两件事——T0 前后几秒的 memory.used 是否接近 memory.total;proc.log 在 T0 之后是继续输出还是变成 GONE。注意显存占用在进程消失后常有一段滞后才回落,看到 GONE 后多观察一两秒再下判断,避免把“已被结束”误判成“还在跑但显存满了”。
查系统日志确认是否有进程被结束记录
这一步是把“程序自己退出或死等”和“系统主动干预”分开。先看内核环形缓冲区,再看 systemd 和容器运行时记录。
dmesg -T | grep -iE 'out of memory|oom-kill|killed process'
journalctl -k `--since` '30 min ago' | grep -iE 'oom|killed process'
journalctl `--since` '30 min ago' | grep -iE 'killed|oom|segfault'
如果是通过 systemd 托管的服务,再看单元自身的记录和最终状态:
systemctl status '你的单元名' `--no-pager`
journalctl -u '你的单元名' `--since` '30 min ago' `--no-pager` | tail -n 50
容器场景先看退出状态里的 OOM 标记和退出码:
docker inspect '容器名' `--format` '{{.State.OOMKilled}} {{.State.ExitCode}}'
cgroup 层面的内存事件计数也可以看,路径随发行版和容器运行时不同,需要结合环境确认,常见为 /sys/fs/cgroup/memory.events 或对应子组的同名文件。
结果怎么读:命中 OOM 或 killed process 记录,基本可以归到“被系统结束”;日志里没有任何命中,但退出码为 137(对应 SIGKILL)或 143(对应 SIGTERM),说明结束信号来自运行时就编排层,而不是内核 OOM,需要继续往上查是谁发的信号;既没有命中、退出码也正常,或者进程根本没退出,那更可能是生成循环在等东西。以上是通用语义,具体编码含义以你所在运行时的文档为准。
把现象归到三类原因
到这一步不要再凭感觉猜,把前面采集到的证据填进同一张表,一行对应一类原因,能对上哪一行就按哪一行处理。
| 现象 | 证据 | 下一步动作 |
|---|---|---|
| 显存触顶或碎片化导致分配失败 | gpu.csv 在 T0 前后 memory.used 贴近 memory.total;process 侧显示进程仍存活;日志停在某个 step 或某次张量分配前 | 降低单次占用:分辨率、帧数、batch 只改一项;打开框架提供的分块或低显存选项;清理同卡上的其他进程后复跑 |
| 进程被系统或运行时结束 | proc.log 在 T0 后变为 GONE;dmesg 或 journalctl 命中 oom-kill;docker inspect 的 OOMKilled 为 true 或退出码为 137/143 | 先确认是内存还是显存触发:内核 OOM 看系统内存与 cgroup 上限,容器被杀看运行时内存限制;再调整并发数或上限,而不是先动模型参数 |
| 生成循环在等待(I/O、队列、单步卡死) | 进程存活、GPU 利用率长期接近 0、显存不高;日志停在同一行且无新输出;系统日志无命中 | 看是否卡在写盘或读数据:检查输出目录所在磁盘的可用空间与挂载状态;把输出改到本地盘试一次;必要时用 py-spy 一类工具看主线程停在哪一帧 |
表格填不满说明证据还不够,回到第二、第三节补采样,而不是先改参数。
调整一个参数后再跑一次
复跑的目的是验证判断,不是碰运气。一次只改一个参数,其余条件保持一致:同一提示词、同一分辨率、同一帧数、同一输出目录。改哪一项取决于上表的归类——判到显存相关,就降分辨率或帧数、或开启分块;判到进程被结束,就先把并发或内存上限调到一个更保守的值;判到循环等待,就先把输出目录换到本地盘并清理空间。
复跑时把三项结果记下来,形成可比较的记录:
改动项 :只降低帧数(其他不变)
是否通过原卡点 :是 / 否,卡在同一阶段同一行则记为否
峰值显存 :gpu.csv 中 T0 附近的最大 memory.used
进程是否存活 :proc.log 结束时仍有输出
是否复现 :是 / 否
结论怎么下:改动后不再卡住且峰值明显下移,说明显存判断成立;改动后进程仍被杀、日志仍出现 OOM 记录,说明瓶颈在系统内存或运行时限制,继续回到第三节;改动后依然停在同一行、显存和进程都正常,基本可以排除显存,按“循环等待”方向继续查 I/O 和算子。只跑一次不复现不等于问题解决,建议用同一组条件再跑一到两次确认稳定性。