llama.cpp 本地运行 9B 模型:部署、工具调用与微调数据
把本地助手接起来,先做三件事:让推理服务加载权重,让对话外壳正确发送工具定义,让应用执行工具并回传结果。最后再决定是否需要微调。本文沿这条顺序,用 Odysseus、llama.cpp 与 9B 模型说明各层的接口、排错方法和训练数据要求。
阅读要点
- Odysseus 是自托管 AI 工作区;本地界面与本地推理是两个独立选择。
- llama.cpp 负责加载模型和提供推理接口,工具的实际执行仍由应用负责。
- 换后端要同时核对模型名、聊天模板、工具消息和采样参数。
- 内存预算要包括权重、KV cache、运行缓冲和应用占用。
- 工具轨迹应记录请求、结果、错误与最终回答,写操作必须得到真实结果才能宣称完成。
先分清外壳、推理服务与工具
对话界面负责显示消息,会话管理器保存上下文,推理服务生成下一步输出,工具执行器访问文件或外部服务。这几部分可以在一台机器上,也可以分布部署。用 HTTP 协议连接它们,能减少直接依赖,但接口兼容仍要通过请求和响应核对。
本地界面与本地模型的差别
浏览器打开本机界面,只能说明界面入口位于本机。若后端仍指向云端 API,提示词与工具消息仍可能发往外部。即使推理在本机,搜索、邮件、日历、MCP 服务及遥测也可能联网。要判断数据走向,应列出所有后端地址、工具连接与日志目的地。
| 组件 | 主要职责 | 换模型时要核对的内容 |
|---|---|---|
| 对话外壳 | 会话、界面、上下文组织 | 消息角色、模型选择、错误显示 |
| 推理服务 | 权重加载、模板、生成与采样 | 模型架构、量化、聊天模板、工具调用解析 |
| 工具执行器 | 校验参数并执行外部操作 | 权限、参数范围、超时、幂等与结果回传 |
这样的拆分给排错提供了顺序:先确认请求是否到达推理服务,再确认模型是否生成了工具调用,最后核对工具执行器是否收到并完成该调用。模型能聊天,并不能推出整条工具链已经可用。
Odysseus:给不同模型一个共同工作区
Odysseus 官方仓库将项目定位为自托管 AI 工作区,包含聊天与 Agent、文档、研究、邮件、笔记和日历等功能。模型可以使用本地服务或 API。项目名与目录名均为 odysseus,下载后应在该目录执行部署命令。
依照仓库启动
下面沿用仓库 README 的基本启动步骤;运行前检查 .env 中的服务配置和认证设置:
git clone https://github.com/odysseus-dev/odysseus.git
cd odysseus
cp .env.example .env
docker compose up -d --build
# 容器健康后访问 http://localhost:7011
应用镜像与模型权重是不同的部署材料。模型服务可以独立运行,应用再通过配置连接。记录应用版本、镜像标识与模型文件摘要,之后才能判断一次行为变化来自外壳升级、模板变化,还是权重替换。
哪些模块值得分别检查
| 模块 | 阅读或配置重点 |
|---|---|
| Chat 与 Agents | 本地/API 模型、工具、MCP、记忆如何进入同一会话 |
| Compare | 同一输入在不同后端上的回答与工具行为 |
| Cookbook | 与硬件相关的模型选择、下载和服务入口 |
| Documents 与 Research | 文档内容和检索结果怎样进入模型上下文 |
| 邮件、笔记与日历 | 每项连接使用的账号、权限与读写范围 |
同一个界面可以减少重复开发,却不会消除模型能力差异。模型后端切换后,应重新测试工具名称、参数格式、结果回传及终止行为,而不能只观察问候语是否正常。
代理循环:请求、工具执行与结果回传
模型读到系统提示、历史消息和工具定义后,可能返回文本,也可能返回结构化工具调用。应用校验调用,执行工具,把结果写回消息列表,再发起下一轮生成。每一轮都应设定调用预算与退出条件,防止工具错误造成无限循环。
工具描述是一份可检查的接口
工具名称、参数 schema、描述、示例与系统指令共同影响模型选择。下面是本文定义的搜索工具结构,重点是让参数有明确范围:
TOOL_DEFINITION = {
"name": "search_pages",
"description": "按关键词搜索工作区页面,返回标题与链接摘要",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "minLength": 1},
"limit": {"type": "integer", "minimum": 1, "maximum": 20}
},
"required": ["query"],
"additionalProperties": False
}
}
模型生成符合 schema 的 JSON,也不代表操作就应该执行。应用仍须检查调用者权限、资源是否存在,以及本轮是否允许写入。对写操作,可以要求明确的确认步骤;重试时还要避免重复发送邮件、重复改日历等后果。
保存可排错的轨迹
一份轨迹至少应保留系统与用户输入、可用工具定义、模型调用、执行结果、错误和最终答复。框架知道的超时、剩余预算与重试次数,如果需要模型据此判断,就应显式放进可见上下文。隐藏的内部推理并不是调试工具调用的必要材料;首先需要的是可以与实际操作逐项对应的协议记录。
| 观察对象 | 要回答的问题 |
|---|---|
| 工具请求 | 是否选对工具,参数是否完整? |
| 执行结果 | 操作是否真的完成,是否包含错误? |
| 后续消息 | 模型是否读取了结果,还是直接编造完成状态? |
| 退出条件 | 是否超出轮次、时间或费用预算? |
工具结果很长时,应限制返回字段、截断无关日志,并说明截断发生在哪里。否则有效信息会被噪声淹没,前后两次测试也难以比较。
llama.cpp 服务化:先核对接口,再接外壳
llama.cpp 是 C/C++ 推理项目,支持 CPU 与多种 GPU 后端。其 HTTP 服务提供兼容接口,但模型架构、聊天模板和工具解析支持必须与实际版本对应。
启动本地服务
准备好兼容当前构建的 GGUF 文件后,将下面的 model.gguf 替换为实际路径。示例绑定回环地址,便于先在本机完成接入测试:
./build/bin/llama-server --model ./models/model.gguf --host 127.0.0.1 --port 8080 --ctx-size 8192 --jinja
--ctx-size 应与模型能力和内存预算一起选择。工具调用还依赖合适的 Jinja 聊天模板;需要覆盖模板时,应采用与该模型对应的模板文件,参见 llama.cpp 工具调用文档。
接入测试的最小顺序
先让客户端发送普通消息,再发送只读工具请求,最后测试有明确失败结果的工具请求。base_url 可以指向 http://127.0.0.1:8080/v1,同时核对请求中的 model 名称与服务配置。确认助手工具请求、工具消息及调用标识能正确往返,再接完整的 Agent 循环。
对照云端和本地后端时,尽量固定用户输入、系统指令、工具集合和采样参数;记录两端版本和默认行为。仅更换 URL 不能证明其他条件相同,尤其是模板与工具格式可能随模型变化。
| 检查项 | 失败现象 | 优先排查 |
|---|---|---|
| 模型加载 | 启动失败或内存不足 | 架构支持、文件完整性、设备预算 |
| 消息模板 | 回答混入角色标记 | 模板与模型是否对应 |
| 工具解析 | 返回文本却没有结构化调用 | 工具格式、模板与服务解析能力 |
| 执行回传 | 反复调用同一工具 | 调用标识、结果消息与失败处理 |
本地运行省去了推理请求到远端的网络往返,也引入权重、驱动、内存与版本维护工作。响应快慢仍取决于设备、模型、量化、上下文和并发,应测量首 token 延迟、生成速度与整条任务耗时。
9B 模型的硬件与量化预算
模型参数量不能直接换算为整机最低内存。Qwen3.5-9B 模型卡提供模型与使用说明,实际运行还要选取适配推理后端的权重格式。36GB 设备只是一个容量条件,不是所有 9B 配置都能顺畅运行的保证。
先算权重,再留运行余量
粗略估算时,9B 参数以每参数 2 字节存储,权重本身约为 18GB;理想化的每参数 4 bit 则约为 4.5GB。量化块、元数据及部分高精度张量会改变实际文件大小。这是存储估算,运行中还要加入 KV cache、计算缓冲、系统与外壳占用。
| 预算项 | 需要记录什么 |
|---|---|
| 权重 | 实际 GGUF 文件大小与量化类型 |
| 上下文 | 最大长度、并发数和 KV cache 配置 |
| 推理后端 | CPU/GPU 分层与缓冲占用 |
| 应用 | 会话、检索、浏览器和工具进程占用 |
把这些项目加在一起,再与设备可用内存比较,比套用“消费级只能跑某个参数上限”更可靠。启动成功后,还要在目标上下文和并发下运行一次完整任务,观察峰值占用。
小模型与低比特量化怎样比较
缩小模型和降低量化精度是两条不同路径。前者改变基础模型容量,后者改变权重表示;哪一种更合适,应使用同一批工具任务比较。至少测调用选择、参数合法性、结果利用与任务完成情况,不能只凭模型页推荐就决定。
设备与模型页面的推荐可以帮助缩小候选范围。最终记录应落到具体模型版本、权重文件、量化、上下文和实测结果;这样换机器或换模型时,才能知道哪一项条件发生了变化。
把工具轨迹整理成训练样本
工具调用微调首先需要质量可靠的可见轨迹。样本应该说明用户目标、可用工具、调用参数、真实返回结果和最后答复。训练前检查许可证、授权范围和隐私信息;公开下载的数据也可能包含错误、敏感字段或不适用的使用条件。
完成状态必须有工具结果支持
下面是本文构造的教学样例,工具名与参数由应用约定。查询日程后,要先执行移动并收到成功结果,才可以回复“已移动”:
sample = {
"messages": [
{"role": "user", "content": "把明天的评审挪到后天"},
{"role": "assistant", "content": None, "tool_calls": [
{"id": "call_1", "type": "function", "function": {
"name": "get_schedule", "arguments": '{"when":"tomorrow"}'
}}
]},
{"role": "tool", "tool_call_id": "call_1",
"content": '{"event_id":"review_1","time":"14:00"}'},
{"role": "assistant", "content": None, "tool_calls": [
{"id": "call_2", "type": "function", "function": {
"name": "move_schedule",
"arguments": '{"event_id":"review_1","when":"day_after_tomorrow"}'
}}
]},
{"role": "tool", "tool_call_id": "call_2", "content": '{"ok":true}'},
{"role": "assistant", "content": "评审已移到后天。"}
]
}
这份样例中的日历记录和成功结果是模拟数据,用来说明消息结构。真实数据采集应保存工具实际返回值,错误与拒绝也要保留;不能把失败请求手工改成成功答复,再把它当作正确示范。
数据清洗与划分
按任务或会话划分训练集和评测集,避免同一轨迹的近重复片段流入两边。删除密钥、账号标识与无关私有内容;需要保留关联关系时,用一致的代号替换。记录数据版本和处理脚本,便于排查某类行为来自哪些样本。
公开痕迹与自采数据各有成本:公开数据需要检查授权、格式与任务匹配;自采数据需要执行环境和质量评估。选择标准应是“结果是否可信、是否有权使用、是否覆盖目标任务”,而不是把公开与合规直接画等号。
微调之前,先定位工具调用失败
接入本地模型后,如果出现漏调用、选错工具或参数错误,先排查模板、工具定义、上下文、结果回传与生成配置。只有排除接入问题,再考虑数据和训练。把所有失败归结为模型能力,会把可以修复的协议错误带进训练集。
用四项结果建立基线
| 维度 | 记录方式 |
|---|---|
| 是否应调用 | 对需要外部信息与不需要工具的输入分别记录 |
| 工具选择 | 比较实际工具与任务所需工具 |
| 参数有效性 | schema 校验、必填字段和业务范围检查 |
| 结果使用 | 最终答复是否与工具结果一致,失败是否被正确说明 |
先保存一份固定评测集,微调后用相同协议再跑。增加“不应调用”“参数缺失”“工具失败”“写操作被拒绝”等反例,避免训练只教会模型遇到问题就调用工具。
训练格式与推理格式保持一致
TRL 的 SFT 工具调用文档说明了带工具调用与工具响应的会话训练方式。落地时要准备匹配的聊天模板和数据字段,确认损失覆盖的目标。训练期采用一种调用格式,推理端改成另一种格式,会增加额外适配工作。
监督微调适合先从明确的工具选择和参数示范开始。强化学习也可以使用可验证工具任务,但需要额外定义奖励、执行环境和预算;不能仅凭任务含工具调用就断言某一种训练方法必然有效。
训练产物怎样回到服务
保存模型、适配器和 tokenizer/模板版本,按推理后端支持的路线转换与部署。LoRA 合并、GGUF 转换和量化是不同步骤,每一步都要核对兼容性并重新评测。运行时加载的应是确认过的产物,不能只因为文件名类似就认定它来自本次训练。
模型是否改善,应由保留的评测任务和真实执行结果判断。本文的部署命令、工具 schema 与会话数据用于说明这条工程路径,训练步骤应在准备好数据、环境和基线后执行。
交付前,把接口与测量结果放在一起
一份可以复核的本地 Agent 配置,至少包含:应用版本、模型与量化文件、推理参数、工具 schema、工具权限、数据版本和评测输入。这样既能定位故障,也能判断更换模型后的变化。
| 环节 | 先确认的内容 | 成功依据 |
|---|---|---|
| 启动 | 权重与运行时兼容 | 服务健康、模型加载完成 |
| 接入 | 消息角色和模板对应 | 普通消息、只读工具、错误结果均可往返 |
| 执行 | 参数与权限受控 | 真实工具结果支持最终答复 |
| 训练 | 数据授权与目标明确 | 固定评测集上的前后对照 |
| 维护 | 各层版本可定位 | 新环境能重放相同请求并解释差异 |
先把一条只读工具任务跑通,再加入写操作、失败恢复与训练。每增加一层,就保留对应的输入、结果和版本;这比只记“某个模型在某台机器能跑”更便于下一次排错。