MiDashengLM-Spatial 推理跑不起来 / 先分清是环境还是音频输入

文章导读
推理跑不起来时,先别急着重装环境。把失败位置分成三段看:导入依赖 → 加载权重 → 读取音频,报错堆栈的第一行基本能告诉你卡在哪一段。缺包、版本不匹配、权重路径写错、音频文件本身读不了,这四类问题的修法完全不同;一次重装往往只解决了其中一类,还把原本能用的依赖弄乱。下面按报错位置给出一套逐层缩小范围的流程,每一步都有可执行的验证动作。
📋 目录
  1. Ⅰ 从报错第一行确认失败发生在哪一步
  2. Ⅱ 用最小自检命令验证依赖与运行环境
  3. Ⅲ 单独验证音频文件能被正常读取
  4. Ⅳ 核对模型文件路径与加载参数
  5. Ⅴ 把环境信息固化成自检脚本
A A

推理跑不起来时,先别急着重装环境。把失败位置分成三段看:导入依赖 → 加载权重 → 读取音频,报错堆栈的第一行基本能告诉你卡在哪一段。缺包、版本不匹配、权重路径写错、音频文件本身读不了,这四类问题的修法完全不同;一次重装往往只解决了其中一类,还把原本能用的依赖弄乱。下面按报错位置给出一套逐层缩小范围的流程,每一步都有可执行的验证动作。

判断顺序建议固定为:先看报错第一行的模块名或文件路径,再跑最小自检确认依赖与版本,然后单独用 soundfile/torchaudio 读一遍音频,最后核对权重目录与加载参数。适用场景是本地或容器内首次启动推理;每一步都以命令输出为准,不要凭印象跳步。边界在于:如果报错出现在模型前向计算内部(shape、dtype 不匹配),那已经不属于环境与输入的排查范围,需要回到模型配置与输入预处理检查。

从报错第一行确认失败发生在哪一步

Python 的报错栈从上往下读,最上面那一行通常已经写明失败类型。先判断它落在哪个阶段,再决定下一步动作,能避免在错误的方向上反复折腾。

  • 导入依赖阶段:报错形如 ModuleNotFoundError: No module named 'xxx'、ImportError: cannot import name ...,或 undefined symbol 这类动态库错误。特征是脚本还没开始加载模型就退出。下一步动作是打印版本、单独导入该模块,确认是缺失还是版本不匹配。
  • 加载权重阶段:报错形如 FileNotFoundError、OSError: Error no file named pytorch_model.bin / model.safetensors、ValueError: ... is not a valid model identifier,或 config 解析失败。特征是路径出现在报错里。下一步动作是核对目录内容与加载参数,确认指向的是本地真实存在的目录。
  • 读取音频阶段:报错形如 RuntimeError: Error opening ...、soundfile.LibsndfileError、ffmpeg 相关提示,或读出来是空数组、时长为零。特征是路径是音频文件而不是模型目录。下一步动作是脱离模型,单独用音频库打开该文件。

如果第一行是 KeyError、RuntimeError: mat1 and mat2 shapes cannot be multiplied 之类的张量错误,说明环境与文件都已经通过,问题在输入张量的形状、采样率或特征维度,按第 3 节检查音频属性即可。

用最小自检命令验证依赖与运行环境

在推理脚本所在的环境里(确认 which python 与 pip -V 指向同一个解释器)直接粘贴执行,不要先改任何配置:

MiDashengLM-Spatial 推理跑不起来 / 先分清是环境还是音频输入
python - <<'PY'
import sys, platform
print("python   ", sys.version.split()[0])
print("exe      ", sys.executable)
print("platform ", platform.platform())
mods = ["torch", "torchaudio", "transformers", "numpy",
        "soundfile", "librosa", "sentencepiece", "accelerate"]
for m in mods:
    try:
        mod = __import__(m)
        print(f"OK   {m:14s} {getattr(mod, '__version__', 'unknown')}")
    except Exception as e:
        print(f"FAIL {m:14s} {type(e).__name__}: {e}")
print("cuda available:", getattr(__import__('torch'), 'cuda', None) and __import__('torch').cuda.is_available())
PY

输出里重点看三处:一是 FAIL 开头的行,它直接指出缺失或导入失败的模块;二是 python 与 exe 是否指向你预期的虚拟环境,很多“装了却找不到”都是解释器不一致;三是 torch 与 torchaudio 的版本号是否同一大版本,两者版本错位时,导入阶段可能通过,但加载音频后端时会报符号错误。C 扩展类问题(undefined symbol)通常与安装方式有关,可以先确认该包是不是从与当前 Python 版本匹配的渠道安装的。

单独验证音频文件能被正常读取

把模型完全放在一边,只测文件本身。下面这段在任意目录都能跑,把 path 换成你的音频:

import soundfile as sf
import numpy as np

path = "/path/to/your/clip.wav"
info = sf.info(path)
print("samplerate ", info.samplerate)
print("channels   ", info.channels)
print("frames     ", info.frames)
print("duration_s ", round(info.frames / info.samplerate, 3))
print("format     ", info.format, info.subtype)

data, sr = sf.read(path, dtype="float32", always_2d=True)
print("shape      ", data.shape)
print("peak       ", float(np.abs(data).max()))

正常时应满足:duration_s 明显大于 0,channels 为 1 或 2,shape 是 (帧数, 声道数),peak 不为 0。若 peak 为 0,文件能打开但内容是静音,模型侧往往表现为输出无意义或直接断言失败;若 format 显示的是你意料之外的编码,说明文件扩展名与实际格式不一致,建议先用 ffmpeg 转成标准 PCM wav 再试。对于多声道或高采样率素材,需要结合模型要求确认是否要在读入时做下混与重采样,这一步不要靠猜,看模型配置里的采样率字段。

核对模型文件路径与加载参数

先确认目录里到底有什么,再看加载参数指向哪里:

MiDashengLM-Spatial 推理跑不起来 / 先分清是环境还是音频输入
MODEL_DIR=/path/to/MiDashengLM-Spatial
ls -al "$MODEL_DIR"
find "$MODEL_DIR" -maxdepth 2 \( -name "config.json" -o -name "*.safetensors" -o -name "*.bin" -o -name "*.pt" \) -print
python -c "import os,sys; p=sys.argv[1]; print('abspath', os.path.abspath(p)); print('isdir  ', os.path.isdir(p)); print('files  ', len(os.listdir(p)) if os.path.isdir(p) else 0)" "$MODEL_DIR"

加载日志里要盯住三行:实际解析出的模型路径、读取到的 config 文件名、以及权重文件列表。路径被解析成相对路径或带 ~ 未展开时,日志里会显示成一个你没预期的位置,这是最常见的一类。参数写错的典型表现是:把本地目录写成了模型名,报 is not a valid model identifier 并尝试联网;把目录写到上一层,报缺 config.json;只给了单个权重文件而没给目录,报缺配置。容器场景还要区分宿主机路径与容器内挂载点,日志里打印的路径以容器内为准。

把环境信息固化成自检脚本

把上面几段合并成一个 selfcheck.py,放在项目根目录,下次启动前先跑它,能省掉大量来回试错。骨架如下,检查项可按需增删:

import os, sys, platform, importlib

MODEL_DIR = os.environ.get("MODEL_DIR", "/path/to/MiDashengLM-Spatial")
AUDIO = os.environ.get("AUDIO", "/path/to/your/clip.wav")

def check_env():
    print("[env]", sys.executable, sys.version.split()[0], platform.platform())
    for m in ["torch", "torchaudio", "transformers", "soundfile", "librosa", "numpy"]:
        try:
            mod = importlib.import_module(m)
            print(f"  OK   {m:12s} {getattr(mod, '__version__', 'unknown')}")
        except Exception as e:
            print(f"  FAIL {m:12s} {type(e).__name__}: {e}")

def check_model():
    print("[model]", os.path.abspath(MODEL_DIR))
    if not os.path.isdir(MODEL_DIR):
        print("  FAIL 目录不存在"); return
    for name in os.listdir(MODEL_DIR):
        print("  ", name)
    print("  config.json exists:", os.path.exists(os.path.join(MODEL_DIR, "config.json")))

def check_audio():
    print("[audio]", os.path.abspath(AUDIO))
    try:
        import soundfile as sf
        info = sf.info(AUDIO)
        print("  samplerate", info.samplerate, "channels", info.channels,
              "duration_s", round(info.frames / info.samplerate, 3))
    except Exception as e:
        print("  FAIL", type(e).__name__, e)

if __name__ == "__main__":
    check_env(); check_model(); check_audio()

各项的正常输出形态是:env 段每个模块都是 OK 行并带版本号;model 段能列出配置文件与权重文件,且 config.json exists: True;audio 段能打印出采样率、声道数与大于 0 的时长。任何一段出现 FAIL,就先修那一段,再回到推理脚本。脚本里用环境变量传入路径,避免把本机路径写死后在另一台机器上再次踩坑。