本地跑 Matrix-Game 4.0 时出现“画面出得来但不流畅”,通常不是某一个参数的问题,而是三类瓶颈叠在一起:显存容量不够触发回退或换页、显存读写压力大让利用率顶满、帧生成节奏参数让单帧耗时抖动。建议把处理顺序固定下来:先用日志把现象归类,再用同一时间轴的显存与利用率读数判断是容量受限还是读写受限,最后每次只改一项帧生成相关参数,并用同一段输入复跑确认。跳过前两步直接调参,容易把容量问题当成节奏问题,改了很久只换来更长的等待。
判断顺序建议是“日志归类 → 读数定位 → 单参数调整 → 同输入复跑”。日志里出现内存分配失败或回退到 CPU,优先按容量处理;显存占用没贴顶但 GPU 与显存控制器利用率长期偏高,优先按读写压力处理;两者都平缓而帧间隔大,再看生成节奏参数。任何改动只在前述验证方式下成立,换分辨率、换帧数或换驱动环境都需要重新确认,不要把某一次的顺畅当成稳定结论。
在日志里区分加载慢、生成慢与显存不足
日志是第一手分类依据。先确认日志落在哪里:多数本地运行会在启动目录下生成 logs/,也可能写在 ~/.cache/<应用名>/log 或 ~/.local/share/<应用名>/logs;如果直接用 Python 或可执行文件启动,建议把标准输出和错误一起重定向到文件,避免只看到终端最后几行。
cd 你的运行目录
ls -lt logs/ ~/.cache/*/log 2>/dev/null | head
tail -n 200 -f run.log
把关键词和现象对上号,再决定要不要继续往下查:
- 容量类:出现
out of memory、allocate failed、falling back to cpu、offload,现象是生成到某一帧或某一步直接中断,或单帧耗时突然拉长。这类先按显存容量处理。 - 读写类:日志里没有分配失败,但
step time、took ... ms这类耗时行数值抖动明显,且抖动和显卡利用率同步。这类先按显存读写压力处理。 - 加载类:长时间停在
loading checkpoint、load weights、mmap附近,之后生成并不慢。加载慢和生成慢是两件事,不要混在一起调生成参数。 - 沉默型:没有报错也没有明显耗时行,只是帧间隔大。这种情况必须结合下一节的读数,单看日志无法判断。
同步记录显存占用与利用率读数
用一条轮询命令把时间和读数一起采下来,采样间隔建议 1 秒;如果要看单帧级别的峰值,可以先降到 0.5 秒跑短时段,间隔太细会拖慢采样本身。
nvidia-smi `--query-gpu`=timestamp,memory.used,memory.total,utilization.gpu,utilization.memory \
`--format`=csv -l 1 > gpu_samples.csv
采完画两条曲线:第一条是 memory.used 随时间的走势,第二条是 utilization.gpu 与 utilization.memory 两条线放在同一时间轴。判断方式可以先用保守规则:
- 显存占用贴着上限、呈锯齿并有回落,回落后帧时间变长:优先怀疑容量受限。
- 显存占用稳定在中段,但 GPU 或显存控制器利用率长期偏高,帧时间抖动与利用率波动同步:优先怀疑读写受限。
- 两条曲线都平缓,帧间隔却很大:先怀疑生成节奏参数、CPU 侧预处理或前后处理,而不是显卡本身。
记录时至少留下峰值显存、稳态显存、利用率均值与峰值四组读数,后面调整参数才能做前后对比。
调整与帧生成节奏相关的参数
这一节的要求是每次只动一项,并观察帧间连续性和显存峰值是否同步变化。如果两项一起变,读数变化无法归因。建议从影响峰值显存的参数开始,再动影响吞吐的参数,最后才动精度。
| 参数名 | 建议改动方式 | 观察点 | 前后结果记录 |
|---|---|---|---|
| 分辨率 / height、width | 先降一档,不要一次减半 | 峰值显存是否下降、单帧耗时是否变稳 | 峰值显存、帧间隔均值、日志告警条数 |
| num_frames / 帧数 | 缩短单次生成段数 | 是否仍在中途触发容量回退 | 是否出现分配失败、峰值显存 |
| tile size / vae tiling | 开启分块或调小分块边长 | 读写压力是否被摊开、边缘是否出现接缝 | 显存控制器利用率、画面一致性 |
| batch / chunk size | 降为原来的一半 | 峰值显存与吞吐是否同时变化 | 单帧耗时、峰值显存 |
| 精度 fp16 / bf16 | 在硬件支持前提下切换其中一种 | 显存峰值与数值稳定性 | 是否出现 NaN、画面异常、耗时变化 |
每改一项,按“参数名、改动值、峰值显存、帧间隔均值、日志新增告警”五个字段记一行。如果改动后帧间连续性变好但峰值显存反而升高,说明是在用容量换节奏,需要回到上一节的读数确认余量。
比较容量优先与读写优先两种取舍
不同档位的卡先保什么并不一样。容量优先指先保证不触发回退、把峰值压到安全余量内,代价是分辨率和帧数受限制;读写优先指保住分辨率和帧数,用分块、精度等方式降低单位时间的数据搬运压力。判断依据仍然是上一节采到的读数,而不是凭型号猜。
| 卡型档位 | 先保什么 | 先动哪些参数 | 判断依据 |
|---|---|---|---|
| 8GB 及以下 | 容量优先 | 分辨率、帧数、开启 tiling、必要时 offload | 显存占用频繁贴顶并回落,日志有分配失败或回退 |
| 12–16GB | 容量优先,留出余量后再看读写 | 分辨率与分块一起小步调,精度作为后手 | 峰值接近上限、利用率中等偏高,帧时间呈间歇抖动 |
| 24GB 左右 | 读写优先 | tile size、chunk、精度,尽量不动分辨率和帧数 | 显存有余量但显存控制器利用率长期偏高 |
| 40GB 及以上 | 读写优先 | 步数、chunk、前后处理,容量一般不设为主要约束 | 两条读数曲线都未触顶而帧间隔偏大 |
取舍的核心是别同时牺牲两端:容量优先下再叠加过小的分块,往往让读写更碎;读写优先下把帧数拉满,则可能重新回到容量回退。调整时保留一份可回退的配置,改坏了能退回去。
用同一段输入复跑验证取舍
验证不是看一次跑得顺,而是看同一段输入下读数是否稳定。固定随机种子、固定输入文件、固定分辨率与帧数,把两次运行分别输出到不同目录,再对比关键读数。
# 第一次
python run.py `--input` sample_input `--seed` 1234 `--frames` 24 \
`--out` runs/check_a 2>&1 | tee runs/check_a.log
# 第二次,配置不变
python run.py `--input` sample_input `--seed` 1234 `--frames` 24 \
`--out` runs/check_b 2>&1 | tee runs/check_b.log
两次运行各记录四项:首帧耗时、平均帧间隔、峰值显存、日志中容量类告警条数。差异说明建议写成短句,例如“第二次峰值显存更低但平均帧间隔更长”,或“两次容量告警都为 0,帧间隔差在可接受波动内”。如果两次差异明显,先确认是否被其他进程占用了显存,或者在后台跑了别的任务,再决定是否把这次改动保留为默认配置。