换机器或换目录后脚本找不到权重,通常不是模型本身的问题,而是脚本里把权重路径写成了某个固定的绝对路径,且没人清楚目录里各文件分别喂给哪一部分。处理顺序建议是:先列目录、按体积和命名把文件归到模块,再把加载路径抽成命令行参数或环境变量,加载前加存在性校验,最后用相对路径和绝对路径各跑一次做交叉确认。这样换环境时出错信息会落在“哪个模块缺哪个文件”,而不是加载到一半才中断。
先拆清单再改路径:用ls -lh的输出把权重目录里每个文件对应到文本、视觉、音频等模块,形成一张对照表;脚本把目录改成`--model-dir`加环境变量兜底,覆盖顺序为命令行 > 环境变量 > 默认值;加载前跑一遍 glob 校验,缺文件在启动阶段直接退出并打印缺失模块名;最后分别用相对路径和绝对路径各跑一次,以日志里每个模块的首个文件路径和进程退出码作为判断依据。整个过程只依赖配置、日志和命令输出,不涉及任何性能结论。
按体积和命名规律列出权重目录里的每个文件
这一步的目的不是清理目录,而是建立“文件 → 模块”的对应关系,后面改路径时才知道每个路径该指向什么。建议先看整体体积分布,再看文件名的公共前缀。
# 1) 目录内每个文件的体积与时间,按体积倒序
ls -lh `--sort`=size /path/to/ming3-weights
# 2) 只看文件名,快速找公共前缀和分片编号
ls -1 /path/to/ming3-weights | sort
# 3) 统计各前缀的文件数量,判断是否分片
ls -1 /path/to/ming3-weights | sed 's/[0-9]\+\..*$//' | sort | uniq -c
输出骨架通常长这样,具体行数以实际目录为准:
-rw-r`--r--` 1 u u xxxM Jan 1 00:00 text_model-00001-of-0000N.safetensors
-rw-r`--r--` 1 u u xxxM Jan 1 00:00 text_model-00002-of-0000N.safetensors
-rw-r`--r--` 1 u u xxxM Jan 1 00:00 vision_encoder.safetensors
-rw-r`--r--` 1 u u xxxK Jan 1 00:00 tokenizer.json
-rw-r`--r--` 1 u u xxxK Jan 1 00:00 config.json
把上面的输出填进下面这张对照表。体积区间一列先用目录内的相对位次描述,等真实 ls -lh 结果出来再替换成实际区间;命名特征一列写你目录里真实的前缀,不要照抄示例。
- 命名特征:带
-0000X-of-0000N后缀的通常是同一模块的分片,前缀相同即同一模块。 - 体积位次:目录内体积最大的一批一般是主干权重;体积很小(几十到几百 KB)的多为配置或分词器文件,路径写错时同样会让脚本报错。
- 对应模块:按前缀归到文本、视觉、音频等模块;无法从前缀判断的文件,先在加载代码里搜索该文件名,再回填到表里。
- 确认方式:文件能否被加载代码引用,是判断归属的唯一硬依据;体积和命名只是线索。
把加载路径抽成命令行参数或环境变量
路径失效的根因是路径写进了代码。做法是把“根目录”和“按模块覆盖”分开:根目录给一个默认值,模块级参数留空,为空时回落到根目录下的约定子目录。覆盖顺序建议固定为命令行 > 环境变量 > 脚本默认值,这样临时换盘只加一个参数,长期换盘则设环境变量。
import argparse, os
def build_args():
p = argparse.ArgumentParser()
# 默认值优先读环境变量,读不到才用脚本内置默认
p.add_argument("`--model-dir`",
default=os.environ.get("MING3_MODEL_DIR", "./weights"))
# 模块级覆盖,留空表示沿用 model-dir 下的约定子目录
p.add_argument("`--text-dir`", default=os.environ.get("MING3_TEXT_DIR"))
p.add_argument("`--vision-dir`", default=os.environ.get("MING3_VISION_DIR"))
p.add_argument("`--dtype`", default="auto")
return p.parse_args()
def resolve_dir(model_dir, override, sub):
return override if override else os.path.join(model_dir, sub)
使用时的写法:临时指定用 `--model-dir` /data/ming3-weights;固定机器上把 MING3_MODEL_DIR 写进启动脚本或服务配置。需要注意 argparse 的 default 本身就会被命令行覆盖,所以不必再写一层 if args.model_dir is None 的分支,少一层判断也少一个出错的点。相对路径会按脚本的当前工作目录解析,如果脚本由 systemd、cron 或容器拉起,工作目录往往不是你以为的那个,这一条要在最后一步验证。
在加载前加一段文件存在性校验
目标是让“缺文件”在启动阶段以非零退出码结束,而不是等加载到第三个模块才抛异常。校验只需要模块名、匹配模式和目录三样东西。
import glob, os, sys
REQUIRED = {
"text": "text_model-*.safetensors",
"vision": "vision_*.safetensors",
"config": "config.json",
}
def check_required(model_dir, required=REQUIRED):
missing = []
for module, pattern in required.items():
hits = sorted(glob.glob(os.path.join(model_dir, pattern)))
if not hits:
missing.append((module, pattern))
else:
print(f"[weight-check] ok module={module} files={len(hits)} "
f"first={os.path.basename(hits[0])}")
if missing:
for module, pattern in missing:
print(f"[weight-check] MISSING module={module} "
f"pattern={pattern} dir={model_dir}", file=sys.stderr)
raise SystemExit(2)
return True
缺文件时的输出大致如下,模块名和模式都直接可读,方便对照上一步的对照表定位:
[weight-check] ok module=text files=4 first=text_model-00001-of-00004.safetensors
[weight-check] MISSING module=vision pattern=vision_*.safetensors dir=./weights
校验函数放在模型对象构造之前调用,别放在 load_state_dict 附近。分片文件这里只做数量与首个文件检查,分片是否完整(比如 1 到 N 是否连续)属于另一层校验,可以在确认目录稳定后再补。
分别用相对路径和绝对路径各跑一次
两次运行的参数只有路径形式不同,其余保持一致,这样日志差异只可能来自路径解析。第一次在权重目录的上一级执行,用相对路径:
cd /data
python infer.py `--model-dir` ./ming3-weights `--dtype` auto
# 退出码应为 0
第二次在任意目录执行,用绝对路径:
cd /tmp
python /data/infer.py `--model-dir` /data/ming3-weights `--dtype` auto
# 退出码应为 0
日志对照看三处:其一,两次的 [weight-check] 行里 dir= 是否分别显示相对与绝对形式,而 files= 数量一致;其二,每个模块的 first= 文件名两次应完全相同,不同就说明某次落到了别的目录(常见于默认值生效);其三,加载完成后脚本自己打印的模型目录一行,是否与传入值一致。
判断加载成功的依据建议定为:所有模块都有 ok 行、没有 MISSING 行、进程退出码为 0,且推理输出正常返回。如果相对路径那次失败而绝对路径成功,问题基本落在工作目录,而不是权重本身;这种情况优先把启动脚本里的 cd 或服务配置的 WorkingDirectory 补齐,而不是把绝对路径再写回代码里。