第 5 章:本地模型与推理引擎 —— 一个模型背后其实是三个进程角色¶
5.1 本地推理的三段式结构¶
Jan 的本地模型链不是“前端直接加载 GGUF”,而是:
模型文件 + model.yml
↓
llamacpp-extension / mlx-extension(TS 编排)
↓ Tauri guest API
Rust plugin:后端选择、preset、router、session
↓ subprocess / HTTP
llama-server 或 MLX server
AIEngine 的统一接口让 Web 层可以用同一套 LanguageModel 方式聊天;差异集中在 ModelFactory 和 engine extension。
5.2 llama.cpp:Router 而不是每次启动一个 server¶
tauri-plugin-llamacpp 中的 router.rs 是本地推理的关键。它维护 router 进程、模型 slots、preset、健康检查和优雅退出。前端可以通过 load_llama_model、unload_llama_model、get_loaded_models、router_health、reload_router_models 等 command 管理它。
sequenceDiagram
participant UI as ModelFactory
participant Ext as llamacpp-extension
participant Plugin as Tauri llama.cpp plugin
participant Router as llama-router
participant Model as GGUF model
UI->>Ext: 选择 model + 发送消息
Ext->>Plugin: ensure_session_ready / load model
Plugin->>Router: 写入 preset,启动或 adopt
Router->>Model: mmap / GPU offload / KV cache
UI->>Router: OpenAI-compatible /v1/chat/completions
Router-->>UI: SSE chunks + timings + prompt_progress
Router 的价值在于多个模型、并发请求和外部 API 共用一个管理面。代价是关闭应用不能简单 kill 一个进程:必须检测 busy models、尝试 graceful stop、必要时再清理进程树。
5.3 backend 与 router 是两个更新维度¶
llama.cpp extension 同时管理:
- backend 二进制:CUDA、Vulkan、Metal 等平台构建。
- router preset:上下文长度、GPU layers、flash attention、batching、采样默认值。
- model metadata:GGUF metadata、embedding、MTP、chat template kwargs。
backend.rs 负责版本发现、下载、校验、安装和旧版本保留;preset.ts/preset.rs 把模型和硬件设置编译成 router 配置。模型文件本身、backend binary、preset 都可能需要独立迁移。
5.4 MLX 是平台条件分支,不是另一个通用 Provider¶
Cargo.toml 只在 macOS target 下引入 tauri-plugin-mlx。非 macOS 平台通过 stub 暴露 field-compatible 类型,让 server proxy 可以编译,但 MLX session map 永远为空。
这是一个很聪明的 Rust 条件编译技巧:上层 server 逻辑不需要写大量 cfg,只需在 macOS 上拿到真实 session,在其他平台拿到空 map。代价是读代码时要意识到“可编译”不等于“该平台可用”。
5.5 模型设置的三层合并¶
一次本地聊天可能综合三个来源:
model.yml / model.settings
↓ 默认 sampler、ctx_len、engine
assistant.parameters
↓ 助手级覆盖
当前请求参数
↓ 用户当前这一轮显式传入
最终 chat completion body
custom-chat-transport.ts 会提取 per-model sampling defaults,model-factory.ts 会按 Provider capability 过滤;llama.cpp 独有参数可以保留,远程 Provider 不接受的字段必须剔除。否则“本地能跑”的设置会在 OpenAI 上变成 400。
5.6 流式输出里的 timings 与状态¶
llama.cpp chunk 可带 timings 与 prompt_progress。model-factory.ts 的 MetadataExtractor 将它们映射为 prompt tokens、completion tokens、tokens/sec,并同步到 useAppState 的 live stats。这说明性能指标不是后置日志,而是流的一部分,直接驱动 UI。
5.7 失败路径比成功路径更值得读¶
- model load 失败:前端需要区分找不到 backend、设备不支持、模型不支持、OOM 和 router unreachable。
- router busy:退出时 Rust 发
llamacpp-busy-on-exit,UI 弹窗等待而不是静默杀进程。 - backend update 失败:保留旧版本,记录 update history,支持 rollback。
- tool schema 不兼容:Web 层会规整 JSON Schema,删除 llama.cpp GBNF 不支持的 pattern/format。
这些代码共同揭示了一个事实:本地 AI 产品的“推理引擎”不仅是生成 token,还包括资源管理、配置编译、进程生命周期和用户可解释的错误。