端侧跑 Step Edge 时“跑不动”,通常不是单一原因。报错退出多半发生在权重加载阶段,而“能启动但极慢”多半发生在推理阶段,两件事要分开测。判断顺序建议这样走:先记录推理进程的内存峰值和系统可用内存的差距,确认是不是内存这条线本身就不够;再关掉后台应用重跑同一输入,看差距是否被释放出来;最后核对量化权重文件的格式与加载器声明的格式是否一致。这三步都不需要改代码,只要命令和日志,做完基本能确定该换档位、换线程还是换设备。
如果关掉后台应用、用最短输入重跑后仍然在加载阶段失败,问题在模型侧(量化格式或档位与加载器不匹配、后端不支持该格式),不在内存被占;如果关后台后能跑通但速度没改善,先调线程数而不是换设备;只有当系统可用内存峰值长期低于进程峰值需求,且降低量化档位仍不满足时,才考虑换设备。上述判断需在同一台设备上做前后对比,不同机型的绝对值不可互相套用。
抓取推理进程的内存峰值与系统剩余内存
先在设备上按固定间隔同时采样两件事:推理进程的 RSS 和系统可用内存。把采样结果按行为分段,加载阶段(日志出现加载权重的提示到加载结束)和推理阶段(开始输出 token 之后)分别看峰值,不要只取全程最大值。
# 设备为 Linux 或 adb shell 环境时通用,PID 换成实际推理进程号
PID=$(pgrep -f step-edge | head -n1)
while true; do
ts=$(date +%s)
rss=$(awk '/VmRSS/{print $2}' /proc/$PID/status 2>/dev/null)
avail=$(awk '/MemAvailable/{print $2}' /proc/meminfo)
echo "$ts rss_kb=${rss:-gone} avail_kb=$avail"
sleep 1
done
Android 上另有更细的口径,进程内存用 PSS 更贴近实际占用:adb shell dumpsys meminfo <pid>,系统侧用 adb shell cat /proc/meminfo。注意 MemAvailable 是内核估算值,和 dumpsys 报出的 free 不完全一致,所以只做同一台设备的前后对比,不做跨机型对照。判断标准很朴素:加载阶段 rss_kb 的峰值是否接近甚至超过当时的 avail_kb。如果加载峰值就已经顶到可用内存上限,后面的推理阶段基本没有腾挪空间。
关掉后台应用后重跑同一输入
这一步是为了确认内存是否被其他进程挤占。先用系统工具按内存倒序列出高占用进程,再逐个关闭,重跑完全相同的输入和参数。
# Linux / adb shell
ps -eo pid,rss,comm `--sort`=-rss | head -n 15
# 或
top -b -n 1 -o %MEM | head -n 20
# Android 上查看整体内存分布
adb shell dumpsys meminfo | head -n 40
关闭后台应用时优先处理常驻的桌面组件、相册索引、即时通讯类进程和相机服务,这些往往在后台维持较大占用。重跑后把两次采样的加载峰值、推理峰值和总耗时并排比较:如果可用内存明显回升而进程峰值不变,说明之前确实是资源被挤占,问题是内存预算而不是量化格式;如果可用内存回升但进程峰值同步放大、依然失败,说明加载器按设备可用内存做了动态分配或缓存策略,问题仍要回到模型侧核对。
核对量化权重是否与加载器声明的格式一致
现象上,格式不符和档位不匹配很像:都在加载阶段报错,或者加载成功但输出乱码、崩在第一次前向。先看文件本身,再让加载器自报一次它识别到的格式。
# 看文件头,GGUF 通常以 47 47 55 46(GGUF)开头
head -c 8 model/step-edge.bin | xxd
# 看文件大小,和量化档位推算的权重体积是否量级一致
ls -lh model/
# 让加载器打印它识别到的格式与参数(开关名称随后端不同,按实际二进制的帮助信息替换)
./step-edge-run `--model` model/step-edge.bin `--print-model-info`
./step-edge-run `--help` | grep -i -E 'format|quant|backend'
核对两点:一是加载器启动参数里声明的格式,与文件头或加载器探测结果是否一致;二是量化档位在设备上是否被后端真正支持,有些后端只接受特定几档。格式不符时只做通用方向的转换:统一到加载器明确支持的那一种容器与量化档位,转换后重新核对文件头和探测输出,不要在同一份权重上反复改参数试错。具体转换工具按你使用的推理框架选择,不要跨框架混用产物。
用最小输入跑通单次推理
把输入长度这个变量先排除掉。用一条最短的输入、把上下文长度和新生成 token 数压到最小,只验证“能否完成一次前向并输出一个 token”。
printf 'hi' > /tmp/min.txt
./step-edge-run `--model` model/step-edge.bin `--prompt-file` /tmp/min.txt \
`--ctx-size` 128 `--max-new-tokens` 1
成功判定信号要写清楚:进程退出码为 0,标准输出里有至少一个生成的 token,并且日志中加载阶段与推理阶段的分界提示完整出现。如果这条命令在加载阶段就失败(报分配失败、格式不支持、找不到算子),问题在加载侧,与输入长度无关,不必再去缩短 prompt 或切分上下文;如果在加载后、生成前失败,再看上下文长度和批大小相关参数。这一条跑通之后再逐步加长输入,才有对照意义。
按差异给出换档位、换线程或换设备三类动作
把前面几步的记录并排,按差异落到具体动作,不要笼统地“再优化一下”。
| 动作 | 触发条件 | 验证方式 |
|---|---|---|
| 换量化档位 | 加载器探测结果与权重格式/档位不一致,或格式支持但加载阶段就因内存不足失败,且关后台后仍失败 | 转换到加载器明确支持的档位后重跑最小输入,加载峰值应低于系统可用内存,退出码为 0 |
| 换线程数 | 最小输入能跑通,加载峰值有余量,但推理阶段耗时明显偏长,且采样期间 CPU 未跑满 | 固定输入与参数,只改线程数重跑,比较同一输入的总耗时;线程数超过物理核数后耗时通常不再下降 |
| 换设备 | 已降到加载器支持的最低档位,关后台后加载峰值仍接近或超过可用内存上限 | 在新设备上重复第一步采样,加载峰值与可用内存之间留有可见余量,最小输入与目标长度输入均可跑通 |
三类动作的边界要分清楚:换档位影响的是内存峰值和输出质量,换线程影响的是耗时而不改变内存峰值,换设备是当前两者都到顶后的选择。任何一步都不要用 swap 或临时缓存来“跑起来”当作解决方案,那只是把失败推迟到推理阶段。设备内核版本、后端实现和并发应用都会影响上述数值,结论需在目标设备上重新确认。