llama.cpp 多模型路由实测:一个进程管理 5 个 GGUF,按需加载 + LRU 驱逐,附完整配置与源码级排障
摘要
摘要:本文记录在 5700U + 32G 内存(无独显)的迷你主机上,用 llama.cpp 原生 router mode + models preset 实现 5 个 GGUF 模型自由切换的完整实测。核心思路是"一个常驻 llama-server 进程 + INI 模型登记表":前端按模型名点名,进程自动装载对应模型,内存满自动按 LRU 驱逐最久未用的模型,彻底告别"换模型五分钟,聊天两分钟"的旧玩法。文章包含完整可抄作业的 INI 配置与启动脚本、32G 内存的容量账本,以及一个反直觉参数坑(–no-models-autoload 实为"禁止按请求装载"而非"不预热")的源码级定位与修复,并对比了与 Ollama 的取舍。
结论先行:
1. 环境与模型清单
| CPU | AMD 5700U(8 物理核,纯 CPU 推理) |
| 内存 | 32G(可用约 28G,系统留 4G) |
| 系统 | Ubuntu 24.04 |
| llama.cpp | b10472 构建(需支持 –models-preset) |
| 前端 | Cherry Studio(OpenAI 兼容 API) |
5 个模型(均为社区 GGUF 量化版):
| qwen30b-rp | Huihui-Qwen3-30B-A3B-Instruct-2507-abliterated.i1-Q4_K_M | ~17-18G | RP 主力(快一档) |
| qwen35b-rp | Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M | ~20G | 质量档 |
| agent-lfm2 | LFM2-24B-A2B-Abliterated-Q4_K_M | ~15G | Agent 备胎 |
| agent-ling | Ling-3.0-tiny-UD-Q8_K_XL | ~5-6G | Agent 轻量(KDA+MLA 混合架构,加载失败属预期) |
| code-ornith | Ornith-1.5-9B-Q5_K_M | ~5G | 代码,48K 长上下文 |
2. 方案:为什么是"ini + sh"而不是纯 shell
"路由模式"是 llama.cpp server 内置能力,不是 shell 实现的:一个常驻 llama-server 进程内部自带路由器,前端请求带什么 model 名,进程内自动装载对应模型,内存满自动卸载最久未用(LRU)。因此模型清单必须以 server 原生可读的 INI(preset)声明,shell 只剩薄薄一层开关职责。
| start-llama-router.sh | 启停、健康检查、预热/卸载、状态查询、构建能力自检 |
| llama-routers.ini | 模型登记表(每个 [段名] 就是一个对外 API 模型名) |
3. 完整配置
3.1 llama-routers.ini
[*]
threads = 8 # 5700U 物理核数(超线程反而降速)
parallel = 1 # 并发 slot,纯 CPU 单人用
mmap = 1 # 系统页缓存管内存,切模型后旧权重自动换出
rea = off # 关闭思考链输出
ctk = q8_0 # KV cache 量化 K
ctv = q8_0 # KV cache 量化 V
ctx-size = 16384 # 默认上下文
============ 角色扮演 ============
[qwen30b-rp]
model = /home/ligui/models/Huihui-Qwen3-30B-A3B-Instruct-2507-abliterated.i1-Q4_K_M.gguf
temp = 0.85
top-p = 0.8
top-k = 20
min-p = 0.0
[qwen35b-rp]
model = /home/ligui/models/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf
ctx-size = 12288 # 权重大,上下文收着点防挤压系统内存
temp = 0.85
top-p = 0.8
top-k = 20
min-p = 0.0
============ Agent 备胎 ============
[agent-lfm2]
model = /home/ligui/models/LFM2-24B-A2B-Abliterated-Q4_K_M.gguf
[agent-ling]
model = /home/ligui/models/Ling-3.0-tiny-UD-Q8_K_XL.gguf
ctx-size = 8192
============ 代码备胎 ============
[code-ornith]
model = /home/ligui/models/Ornith-1.5-9B-Q5_K_M.gguf
ctx-size = 49152
temp = 1.0
top-p = 0.95
top-k = 20
min-p = 0.0
presence-penalty = 1.5
关键规则:preset 的键 = llama-server 命令行参数去掉横线(-ctk → ctk)。解析器遇到不认识的键会抛错拒绝整个配置,不存在静默忽略。已知易错点:kv-type-k/v 是错误键名(正确为 ctk/ctv);reasoning 同理,正确为 rea。
3.2 start-llama-router.sh
#!/bin/bash
set -u
LLAMA_BIN="${LLAMA_BIN:-/home/ligui/llama-b10472/llama-b10472}"
PRESET="${PRESET:-/home/ligui/models/llama-routers.ini}"
HOST="${HOST:-0.0.0.0}"
PORT="${PORT:-8080}"
MAXMODELS="${MAXMODELS:-1}" # 同时在线模型数上限,32G 默认 1 绝对安全
LOG="${LOG:-/tmp/llama-router.log}"
BASE="http://127.0.0.1:$PORT"
do_start() {
[ -x "$LLAMA_BIN/llama-server" ] || { echo "✗ 找不到 llama-server"; exit 1; }
[ -f "$PRESET" ] || { echo "✗ 找不到 preset: $PRESET"; exit 1; }
构建能力自检
if ! "$LLAMA_BIN/llama-server" –help 2>&1 | grep -q — "–models-preset"; then
echo "✗ 当前构建不支持 –models-preset,请升级 llama.cpp"; exit 1
fi
清残留进程(32G 上双 server 并存 = swap 地狱)
if pgrep -f llama-server >/dev/null 2>&1; then
pkill -f llama-server; sleep 2; pkill -9 -f llama-server 2>/dev/null
fi
⚠️ 不要加 –no-models-autoload:它禁止按请求装载,
导致前端点名未加载模型时报 "model is not loaded"(见第 5 节)
nohup "$LLAMA_BIN/llama-server"
–host "$HOST" –port "$PORT"
–models-preset "$PRESET"
–models-max "$MAXMODELS"
>"$LOG" 2>&1 &
60 秒健康检查
for _ in $(seq 1 60); do
sleep 1
curl -sf "$BASE/health" >/dev/null 2>&1 && { echo "✓ 路由器就绪"; exit 0; }
done
echo "✗ 健康检查未通过:"; tail -20 "$LOG"; exit 1
}
case "${1:-start}" in
start) do_start ;;
stop) pkill -f llama-server ;;
status) curl -s "$BASE/models" | python3 -m json.tool ;;
warm) curl -s -X POST "$BASE/models/load" -H 'Content-Type: application/json'
-d "{"model":"$2"}"; echo ;;
unload) curl -s -X POST "$BASE/models/unload" -H 'Content-Type: application/json'
-d "{"model":"$2"}"; echo ;;
log) tail -n 80 -f "$LOG" ;;
*) echo "用法: $0 {start|stop|status|log|warm <模型名>|unload <模型名>}" ;;
esac
注:Windows 编辑过的文件传到 Ubuntu 后务必 sed -i 's/\\r$//' start-llama-router.sh llama-routers.ini && chmod +x start-llama-router.sh,否则 \\r 会导致解析错误。
3.3 部署与使用
# 传文件到 Ubuntu 后
sed -i 's/\\r$//' start-llama-router.sh llama-routers.ini && chmod +x start-llama-router.sh
cp llama-routers.ini ~/models/
./start-llama-router.sh # 启动并显示模型清单
./start-llama-router.sh warm qwen30b-rp # 手动预热
./start-llama-router.sh unload agent-lfm2 # 手动卸载释放内存
前端(Cherry Studio / SillyTavern / Open WebUI 等)设置 API 地址 http://<小主机IP>:8080/v1,模型名填 ini 段名:qwen30b-rp / qwen35b-rp / agent-lfm2 / agent-ling / code-ornith。
4. 内存账本(32G 的数学)
| Qwen3.6-35B | ~20G | 与 30B 不可共存 |
| Huihui-Qwen3-30B | ~17-18G | 与 35B 不可共存 |
| LFM2-24B | ~15G | 可与小模型共存 |
| Ornith-9B / Ling-tiny | ~5-6G | 轻松 |
- 默认 MAXMODELS=1:任何时刻只有一个模型在线,绝对安全。
- MAXMODELS=2:仅限"小+小"或"大+小";两个 Qwen 大模型同在线(合计 ~38G)必炸。
- router 模式下模型按需加载,不会真同时占内存;切换约十几秒(从盘装载)。
5. 排障实录:model is not loaded 的源码级根因
现象
- Cherry Studio 报错:AI_APICallError: model is not loaded
- 服务端日志完全正常:
starting server in router mode. models will be automatically loaded on-demand
listening on http://0.0.0.0:8080 - 即:路由器正常监听,但前端点名后没有任何装载动作。
排查
直接查源码 server-models.cpp:1813:
if (!models_autoload && !meta->is_running()) {
res_err(res, "model is not loaded"); // ← 报错来源
}
根因:启动脚本中加了 –no-models-autoload 参数。这个标志的真实语义是"禁止按请求自动装载"——当请求点名的模型未加载时,直接报错拒绝,而不是去装载它。此前加它是想"开机不预热模型省内存",但源码显示 load_models() 启动时只把 5 个模型登记为"未加载",并不会实际装载任何东西,所以那个内存担忧根本不存在,该开关纯属自缚手脚。
修复
sed -i '/–no-models-autoload/d' start-llama-router.sh
./start-llama-router.sh stop && ./start-llama-router.sh
前端无需改动(连接和模型名本来就没错)。
顺带确认的三个行为(源码佐证)
验证
日志出现以下行即按需装载生效:
ensure_model: model name=qwen30b-rp is not loaded, loading…
实测行为:
6. 与 Ollama 的取舍
- Ollama:托管式开箱即用,多模型管理省心,但模型文件/服务由它接管,调参自由度受限。
- llama.cpp router:在原有技术栈内升级,保留全部命令行级调参(KV 量化、采样、上下文逐模型覆盖),资源开销低(无转发层)。
- 结论:追求省事选 Ollama;有既有 llama.cpp 工作流、需要细粒度参数控制者,选 router 模式。
7. 备注与免责
- 模型均为社区第三方量化版本(含 abliterated/uncensored 变体),非官方发布;仅供本地技术验证,请遵守所在地区法律法规与平台规则。
- agent-ling(KDA+MLA 混合架构)在部分 llama.cpp 构建上可能加载失败,属预期情况,可先用 agent-lfm2 顶替。
- –models-preset 仅近期 llama.cpp 构建支持,脚本已内置能力自检。
更多交流信息及实测、免费API信息请关注同名公众号获取。


