Muse Spark Open 装完后敲下启动命令,终端长时间没有新输出,常见原因有两类:一类是权重文件还在从磁盘读入内存或显存,进程在干活只是没打印;另一类是显存已经吃紧甚至被 OOM 杀掉,进程在等待、重试或已经半死。这两类的下一步动作完全不同,先分清阶段再动手,比反复重启有效。判断依据只看三样:日志时间戳、进程的 CPU 与磁盘 IO、显存占用曲线。模型文件本身的读取速度取决于磁盘类型和文件布局,不要预设一个“正常应该几秒”的数字,用实际观测到的两段时间差做基线。
装完跑不动时,先给启动命令加时间戳日志,把“加载完成”和“开始推理”两个时刻在日志里分开;加载阶段盯磁盘读取与 RSS 增长,推理阶段盯显存峰值。若日志在数分钟内完全无新增,优先核对权重分片数量与缓存路径是否完整;若显存突增后进程消失,按 OOM 处理。最简文本试跑只用于把问题从输入侧收窄到模型侧,不替代对阶段的判断。
给运行命令加时间戳日志分出两段
很多启动脚本把加载日志和推理日志混在一起,且部分框架在加载权重阶段几乎不打印,导致“没反应”和“正在读盘”无法区分。最省事的办法是让每一行输出都带上墙钟时间,事后用首行与末行的时间差,以及“加载完成”关键字出现的位置,确认进程走到哪一步。
用管道加 tee 既保留终端可见,又落一份带时间的文件。若命令行不支持行缓冲,可以加 stdbuf -oL 强制行刷新;否则重定向到文件时可能整块缓冲,看起来像卡住,其实是被缓冲吃掉了。
# 带时间戳记录,同时终端可见
stdbuf -oL -eL python run_spark.py `--model` ./MuseSparkOpen 2>&1 \
| awk '{ cmd="date +\"%H:%M:%S\""; cmd | getline t; close(cmd); print t, $0 }' \
| tee spark_run_$(date +%Y%m%d_%H%M%S).log
# 事后看两段时间差
grep -n -iE "load|weight|ready|infer|generate" spark_run_*.log | head -n 40
head -n 1 spark_run_*.log
tail -n 1 spark_run_*.log
判读方式:如果最后一行停留在“loading weights / mmap / shard”这类字样,且首行到它已有明显时长,问题在加载段;如果已经出现“ready / start generate”之后再无输出,问题在推理段。记录这个时间差作为本机基线,后续换磁盘或换缓存目录时用同一方法对比,不要把它当成通用标准。
加载阶段盯磁盘与内存,推理阶段盯显存
两个阶段看的计数器不一样。加载阶段进程主要是读文件和涨内存,显存可能只缓慢上升;推理阶段才会出现显存峰值。一直盯着 nvidia-smi 而忽略磁盘,容易把“盘慢”误判成“显存不够”。
- 磁盘:用
iostat -x 1看目标设备(如 nvme0n1)的rMB/s与%util;或pidstat -d -p <PID> 1看该进程的 kB_rd/s。若加载阶段该进程持续有明显读盘速率,说明在正常读权重;若长时间为 0 且日志不动,才考虑文件或路径问题。 - 内存:
ps -o pid,rss,vsz,stat,etime -p <PID>或pidstat -r -p <PID> 1。加载阶段 RSS 应随读入逐步上涨;RSS 停住而磁盘也不动,基本可判定进程在等别的东西。 - 显存:
nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 2。加载阶段显存缓慢上升属于正常;推理阶段出现接近总容量的尖峰并伴随进程退出,按显存不足处理。
切换观察指标的时机以日志为准:看到加载完成字样后,再重点转去看显存;此前优先看磁盘与 RSS。这是判断顺序的建议,具体阈值需要结合本机磁盘和显卡容量确认。
核对权重分片与缓存目录是否完整
文件残缺或路径指错时,进程往往不会立刻报错,而是在等待超时、重试或反复扫描目录,表现为“无声”。先确认目录中分片数量与索引文件声明的数量一致,再看每个分片大小是否有异常偏小。
# 看目录清单与总大小
ls -lh ./MuseSparkOpen
ls ./MuseSparkOpen | wc -l
du -sh ./MuseSparkOpen
# 若存在 index 类 json,核对声明的分片数与实际文件数
python - <<'PY'
import json, os, glob
idx = glob.glob('./MuseSparkOpen/*index*.json')
if idx:
d = json.load(open(idx[0]))
keys = d.get('weight_map') or d.get('shard_map') or {}
print('declared shards:', len(set(keys.values())))
print('actual shards:', len(glob.glob('./MuseSparkOpen/*.bin') + glob.glob('./MuseSparkOpen/*.safetensors')))
PY
缓存路径方面,先确认启动参数中的模型目录是绝对路径,再检查框架默认的缓存目录(如 ~/.cache 下对应项目目录)是否被指向了另一个不完整的位置。常见现象是同一份权重存了两份,实际加载的是小的那一份。核对方法:在启动命令里显式打印解析后的路径,例如 python -c "import os;print(os.path.realpath('./MuseSparkOpen'))",与日志中出现的路径比对是否一致。分片齐全、路径一致、大小合理,才基本排除文件侧问题。
用最短文本输入做一次试跑
日志里已出现推理开始、但正常输入迟迟不返回时,用最短输入把范围从输入侧收窄到模型侧。目的不是测性能,而是看最短路径能否走通,以及返回形态是什么样。
# 最简试跑,固定随机性便于复现;参数名以实际框架为准
python run_spark.py `--model` ./MuseSparkOpen \
`--prompt` "你好" \
`--max-new-tokens` 8 \
`--temperature` 0 2>&1 | tee spark_min_$(date +%H%M%S).log
记录三件事:进程是否正常退出、终端最后打印的是生成的文本还是异常栈、总耗时大致量级。若最短输入能返回、长输入不行,问题更可能在输入长度或显存峰值;若最短输入同样无输出且显存已接近上限,问题在模型侧的显存占用。以实际输出为准,不要用预期文本替代记录。
做一张卡住时的判断清单
下面是三分钟内可执行的判断顺序,先看日志增量,再看资源曲线,最后才重启。
- 无新日志超过数分钟:先
tail -f观察是否真的一行都不动,再ls -l /proc/<PID>/fd看是否卡在某个文件句柄,同时核对分片数量与路径是否完整。若日志停在加载关键字且磁盘读速为 0,按文件或路径问题查。 - 显存突增:用
nvidia-smi -l 2回看是否有接近总容量的尖峰,再看进程是否已消失(ps -p <PID>)。出现尖峰后进程消失,按显存不足处理,先减小批大小或最大输入长度再试,不要靠反复重启。 - 进程 CPU 空转:
top -H -p <PID>看是否有线程持续占满单核,同时磁盘与显存都不动。这种情况多是等待、重试或锁竞争,结合日志中的重试与超时字样判断,必要时把日志级别调高后再跑一次。
三类现象可能同时出现,判断时以“日志是否有新增”为第一分界,再结合磁盘、内存、显存中唯一有明显变化的那个计数器定方向。任何调整后用同一套时间戳日志重跑一次,对比两段时间差与显存曲线,确认问题确实移位或消失。