Muse Spark Open 图文输入老是报错 / 是输入格式还是显存不够?

文章导读
这类报错不要先猜。同一份脚本纯文本能跑、加图片就抛异常,说明差异被引入在两个位置之一:图像进入模型前的那段预处理与拼批,或者图像带来的尺寸/批量把显存顶到上限。两条线的区分不靠感觉,靠三样东西:完整堆栈最底层的帧和异常类型、图像张量的 shape 与 dtype、以及运行时显存峰值。先按这三样把报错分流,再动代码,改动量会小很多。
📋 目录
  1. 一 把完整报错堆栈存成文件逐行读
  2. 二 先只喂文本再只喂图片做对照
  3. 三 核对图像读取后的张量形状与类型
  4. 四 用显存监控确认是不是 OOM 伪装成别的错
  5. 五 固定变量重跑并留下结论
A A

这类报错不要先猜。同一份脚本纯文本能跑、加图片就抛异常,说明差异被引入在两个位置之一:图像进入模型前的那段预处理与拼批,或者图像带来的尺寸/批量把显存顶到上限。两条线的区分不靠感觉,靠三样东西:完整堆栈最底层的帧和异常类型、图像张量的 shape 与 dtype、以及运行时显存峰值。先按这三样把报错分流,再动代码,改动量会小很多。

图文输入报错先分流再定位:堆栈最底层帧落在数据读取、预处理或拼批函数,且报错随输入结构(通道数、dtype、维度)变化,优先按输入格式排查;报错含显存分配失败字样,或挂点随图片分辨率、批量大小漂移,按资源不足排查。两者会互相伪装,所以判断要同时看异常类型和显存峰值,不能只看一句错误文案。适用前提是你能复现同一条失败输入;若报错随机出现,先固定随机种子和输入再谈归因。具体框架的隐式类型转换行为,需要结合本地实际报错和版本确认。

把完整报错堆栈存成文件逐行读

终端里只留最后一行错误是最容易误判的做法。先把标准输出和标准错误一起落到文件,保证复现一次就有完整记录:

python run_infer.py `--image` demo.jpg `--prompt` "描述这张图" > run.log 2>&1
wc -l run.log
grep -n "Traceback" run.log
tail -n 60 run.log

读的时候只看两个位置:第一个 Traceback 之后最底层的那一帧(文件、函数名、行号),以及最后一行异常类型与消息。中间的封装层通常是框架自己的 try/except,参考价值有限。

把常见现象按下面的对照表先归一次类。表中“偏向”只是分流起点,最终结论要用后面的复跑实验确认。

堆栈与异常表现偏向输入格式偏向资源不足
最底层帧在 __getitem__、collate、preprocess、resize 之类的数据环节是否
异常为 TypeError、ValueError、AttributeError,消息提到 dtype、size、shape、unexpected type是否
异常为 RuntimeError 且消息含 out of memory、unable to allocate、ALLOC_FAILED否是
最底层帧在模型前向或算子调用里,且消息是尺寸不匹配是否
同一个输入报错行号固定,换图片分辨率不变是否
报错行号随图片分辨率或批量大小变化否是

需要提醒的是,部分运行时会把显存分配失败包装成普通 RuntimeError,甚至包装成算子执行失败。所以异常类型为 RuntimeError 时不要急着定性,直接进入显存监控那一节验证。

先只喂文本再只喂图片做对照

目的是确认问题是否由图像分支引入。保持模型、参数、脚本、环境不变,只改输入,跑三次并按表记录。命令里的参数名按你本地脚本的实际写法替换。

# 第 1 轮:纯文本
python run_infer.py `--prompt` "描述这张图" 2>&1 | tee r1.txt

# 第 2 轮:只给图片,不写指令(若脚本允许)
python run_infer.py `--image` demo.jpg 2>&1 | tee r2.txt

# 第 3 轮:图文混合
python run_infer.py `--image` demo.jpg `--prompt` "描述这张图" 2>&1 | tee r3.txt
轮次输入结果观察点
1仅文本通过 / 失败失败说明与图像无关,先查文本侧
2仅图片通过 / 失败失败且第 1 轮通过,问题进入图像管道
3图文混合通过 / 失败只有第 3 轮失败,重点查拼接与占位符对齐

如果第 2 轮就失败,图像读取、缩放、通道转换这一段是首要嫌疑;如果只有第 3 轮失败而单独喂图片正常,问题更可能出在图文拼接处的张量拼接维度、token 与图像占位数量对不上,或拼批时不同样本文本长度差异导致的填充逻辑。这一步不下最终结论,只缩小范围。

Muse Spark Open 图文输入老是报错 / 是输入格式还是显存不够?

核对图像读取后的张量形状与类型

把图像变成张量后立刻打印,比在模型里加断点省事。下面这段可以单独跑,用来确认输入侧产出的形状、dtype 与取值范围:

from PIL import Image
import numpy as np
import torch

img = Image.open("demo.jpg")
print("mode:", img.mode, "size:", img.size)

img = img.convert("RGB")            # 4 通道的 PNG 必须显式转 RGB
arr = np.array(img)
print("np shape:", arr.shape, "dtype:", arr.dtype,
      "min/max:", arr.min(), arr.max())

x = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).float()
print("tensor shape:", tuple(x.shape), "dtype:", x.dtype)

mean = torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)
x = (x / 255.0 - mean) / std
print("after normalize:", tuple(x.shape), x.dtype, float(x.min()), float(x.max()))

把打印结果和模型输入约定逐项比对,通常要确认四件事:通道顺序是否为 RGB(不是 BGR)、维度排布是否为 NCHW、dtype 是否为 float32 而非 uint8、归一化区间是 [0,1] 还是按均值方差标准化。数值区间不匹配一般不会直接抛异常,而是输出乱码式的回答,容易和格式错误混淆,所以这一步要单独确认。

常见的不匹配点集中在三处:PNG 带透明通道得到 4 通道张量;灰度图读取后只有 2 维、缺通道维;图像尺寸不是模型要求的整数倍导致 patch 切分后数量不对。这三类都会在最底层帧留下明确的 shape 或 dtype 线索。

用显存监控确认是不是 OOM 伪装成别的错

先起一个后台轮询记录显存,再跑同一条失败输入,最后取记录中的最大值:

nvidia-smi `--query-gpu`=timestamp,memory.used,memory.total \
  `--format`=csv -l 1 > gpu_mem.csv &

python run_infer.py `--image` demo.jpg `--prompt` "描述这张图" 2>&1 | tee run_gpu.log

kill %1
sort -t',' -k2 -n gpu_mem.csv | tail -n 5

如果脚本可控,再补一条进程内峰值记录,能区分是“卡上被别的进程占满”还是“本进程自己涨上去”:

Muse Spark Open 图文输入老是报错 / 是输入格式还是显存不够?
import torch
print("peak MB:", torch.cuda.max_memory_allocated() / 1024**2)
print("reserved MB:", torch.cuda.max_memory_reserved() / 1024**2)

判断方法很直接:失败那次运行期间 memory.used 逼近 memory.total,就按资源不足处理;峰值离总量还有明显余量,同时报错里又出现尺寸或类型字样,就回到输入格式那条线。

确认是资源方向的,做一次降规模重试,把变量降到最小:

  1. 批量降到 1,确认单条图文能否跑通。
  2. 把图片长边缩到模型支持的最小尺寸再试一次,排除高分辨率导致的显存放大。
  3. 关掉同卡上的其他进程或换一张空闲卡,排除被挤占的情况。
  4. 上面三步都通过后,再逐级把分辨率和批量加回去,找到开始失败的那个点。

降批量和降分辨率是用来看清原因的止血手段,不是性能方案;长期还是要按显存上限设定输入尺寸与并发。

固定变量重跑并留下结论

到这一步,每次只改一项,其他全部冻结,改完立刻复跑并记录。表里填的是你本地的真实结果,不要凭印象补:

编号只改这一项其余保持不变复跑结果判断
1输入图 convert("RGB")原分辨率、原批量通过 / 失败4 通道问题
2张量 .float() 并除以 255同上通过 / 失败dtype 或取值区间问题
3图片长边缩到模型最小尺寸批量仍为 1通过 / 失败分辨率导致的显存或切分问题
4批量由 N 改为 1原分辨率原格式通过 / 失败显存随批量放大

哪一行从失败变通过,答案就是那一行的改动项;四行都通过说明是多项叠加。最后把复跑通过的那份最小配置落成文件,后续回归直接用,不必再从头试:

# minimal_repro.sh
set -e
python run_infer.py \
  `--image` demo_rgb_short_side_336.jpg \
  `--prompt` "描述这张图" \
  `--batch-size` 1 \
  `--max-image-size` 336 \
  2>&1 | tee minimal_repro.log

留下的结论要写成一句话,例如“图像管道未做 RGB 转换导致 4 通道张量进入模型”,或“原分辨率加批量 4 时显存峰值贴近上限”。一句话结论配上这条最小复现命令和对应日志,才算把报错真正定位完。