摘要
本报告记录并分析了一次针对"Hermes Agent + 本地 Ollama + qwen3.8"三元栈的系统性效能优化。
研究起点是一个朴素的问题------"如何让这套组合发挥最大潜力";终点却是一个与起点假设相反的结论。
核心发现 :在这套架构中,决定端到端延迟的主要变量不是被调用的模型,而是调用方发送的提示宽度。
实测表明 Hermes 在该配置下每轮固定发送 36,109 tokens (系统提示 18.7k + 工具 schema 10.1k 等),
在本机 qwen3.8 实测 1,713 tok/s 的预填充速率下,意味着每轮 21.1 秒的纯预填充 ------在任何输出
token 产生之前。而这一固定载荷中,技能索引一项即占系统提示的 60% (56,345 B),
其成本又高度集中在四个"批量迁移"引入的技能包上(443 个技能,占索引 81%),
而使用者自己创建的技能仅占 0.8%。
研究过程中形成了五个递进阶段,每一次都对前一阶段的结论构成修正甚至否定:
- 递进一:成本结构的重新定位。 从"调 num_ctx / num_batch / 温度"转向"审计调用方提示宽度"。
模型参数调优的收益在数量级上小于提示削减。 - 递进二:驻留与缓存经济学。 发现
OLLAMA_KEEP_ALIVE=2m造成 12.7 秒的冷加载税;
发现 Ollama 桌面版静默覆盖注册表环境变量 ;发现请求级keep_alive优先生效(保温触碰仅 0.004 s);
发现前缀缓存使同会话第二轮延迟从 24.7 s 降至 1.3 s(仅新增 64 tokens)。 - 递进三:混合路由体系。 确立"远程大脑 / 本地手"三层路由(含被普遍忽略的 Tier-0"不调模型"层)。
发现工具集宽度 是本地执行的第一杠杆(32,216 → 11,632 tok,−64%;31.1 → 12.2 s,−61%);
发现本地为单并发串行队列 (同一请求 12.2 s vs 55.1 s,劣化 4.5 倍且随机);
反向验证出不应把delegation.provider指向本地 (子智能体继承完整工具集);
成本证据显示远程与本地 token 消耗比为 66 : 1。 - 递进四:动态加载与"伪两难"的识别。 拒绝了"剪枝技能"路线(Hermes 源码自身的注释即写明
"pruning caused silent capability loss" ),转向"降级为目录"。在四份候选降级名单之间发现
一切权衡都源于粒度错配 (按类别匹配),遂提出第三条路径:按进程定菜单粒度。 - 递进五:决策融合。 证明两个原以为独立的选择实为一个伪二维问题,通过"进程级作用域 +
惰性变更 + 大脑保菜单/手拿目录"的融合设计,同时取得最高收益(省 77% 索引、6.5 s/轮)
与零能力损失(技能名 100% 保留、skills_list/skill_view全可用、disabled = 0)。
最终验证 :端到端实测本地执行者提示从 26,087 → 15,907 tokens(−39%) ,
而执行者仍成功调用 skills_list(category=...) 并返回正确结果------能力保留得到实证。
方法论层面的产出 :本报告记录了研究过程中的 7 处自我纠错 ,其中 2 处是对外发布建议的
方向性错误,2 处是数值/单位错误,1 处是自建补丁的逻辑缺陷,1 处是测量口径错误,
1 处是对实验环境稳定性的误判。报告主张:在效能优化类研究中,纠错记录的密度是可靠性的
直接指标,因为此类研究的结论极易被单一噪声点、单位混淆或非受控并发所污染。
关键词 :提示词经济学;渐进式披露;伪两难;粒度错配;无损劣化;前缀缓存;
串行队列;惰性变更;本地推理;agent 编排
关键发现速览(15 条)
| # | 发现 | 数值证据 |
|---|---|---|
| 1 | 每轮固定载荷是本机最大的单点成本 | 36,109 tok ≈ 21.1 s 预填充 |
| 2 | 技能索引占系统提示的 60% | 56,345 B / 94,254 B |
| 3 | 索引成本 81% 来自四个迁移包 | 443 技能 / 10,545 tok / 6.2 s·轮 |
| 4 | 使用者自建技能仅占索引 0.8% | ~105 tok |
| 5 | 64k 上下文是显存硬顶 | 20,480 MiB 占用 / 余 3,658 MiB |
| 6 | 128k 上下文会杀死推理进程 | RemoteDisconnected + 服务端自我重启 |
| 7 | KV 成本仅 ~4.0 KB/token | 混合注意力红利 |
| 8 | 冷加载 12.4--12.7 s,KEEP_ALIVE=2m 使其反复发生 |
桌面版覆盖注册表 |
| 9 | 保温触碰成本 0.004 s(暖机) | 等效解决 2m 限制 |
| 10 | 前缀缓存使第二轮降至 1.3 s | in 11,633 → 11,697(+64 tok) |
| 11 | 工具集宽度是第一杠杆 | 32,216 → 11,632 tok(−64%) |
| 12 | 本地是单并发串行队列 | 12.2 s vs 55.1 s(同一请求) |
| 13 | 远程:本地 token 消耗 66:1 | 13,863,060 vs 208,757 |
| 14 | 关闭 thinking 反而更慢 | 4.92 s/374 tok vs 3.98 s/266 tok |
| 15 | 按进程定菜单粒度优于按类别挑选 | −39% 提示,零类别误伤,能力 100% 保留 |
五条深层元规律(报告的核心理论产出)
| 元规律 | 一句话表述 | 证据锚点 |
|---|---|---|
| M1 成本引力在调用方 | 被调用方的参数不是主要变量;调用方发送的固定载荷才是 | 模型参数调优收益 ≪ 提示削减收益 |
| M2 粒度错配制造伪两难 | 许多"必须权衡"的两难源于实现粒度选错,而非问题固有 | 类别级降级 → 进程级降级后两难消失 |
| M3 可发现性 / 可加载性 / 可用性三分 | "模型不知道"与"模型不能做"是完全不同的失效模式,不能混同 | 降级保留全部名字但丢描述 |
| M4 无损劣化优于有损优化 | 能"退回现状"的变更,优于"破坏能力"的优化 | 惰性补丁;降级≠禁用 |
| M5 口径决定结论 | 不控制测量口径时,噪声会生成与事实相反的结论 | 墙钟 17/20/32/61 s vs in= 恒定 |
第一章 引言与问题陈述
1.1 研究背景
1.1.1 三元栈的构成
本次研究的对象是一个三层协同的本地智能体运行栈:
| 层 | 组件 | 版本 / 规格 | 角色 |
|---|---|---|---|
| 编排层 | Hermes Agent | v0.21.1(2026.9.7,upstream ad03f20d,git 安装) |
提供系统提示、工具协议、技能体系、后台系统 |
| 推理层 | Ollama | 0.33.2 | 提供本地模型服务(OpenAI 兼容 /v1) |
| 模型层 | qwen3.8 |
架构 qwen35,27.3B 参数,Q4_K_M 量化,原生上下文 262,144 |
执行本地推理 |
术语校正(重要) :
qwen3.8这一模型标识极易被误读为"Qwen3-8B"(80 亿参数的小模型)。实测其元数据为:
architecture=qwen35、parameters=27.3B、context length=262144、
quantization=Q4_K_M,并具备completion / vision / tools / thinking四项能力,附带
clip架构的多模态投影器(460.73M 参数)。它是一个 273 亿参数、带视觉与工具调用能力的 Qwen3.5 系模型 ,而非小型模型。
本报告全文使用其原始标识
qwen3.8,但在容量讨论中一律以其真实参数量 27.3B 为准。
1.1.2 硬件平台
| 项 | 规格 |
|---|---|
| GPU | NVIDIA GeForce RTX 5090 Laptop,24,463 MiB 显存,WDDM 驱动模式 |
| 驱动 / CUDA | 616.92 / CUDA 13.4(Ollama 使用 cuda_v13 后端) |
| 系统内存 | 31.4 GiB 总量,实测空闲约 12.4 GiB |
| CPU | Intel Core Ultra 9 275HX(24 逻辑核) |
| 操作系统 | Windows 11(build 10.0.26200) |
WDDM 模式的特殊性 在此必须前置说明:Windows 的 WDDM 显示驱动模型下,
显示子系统可与 CUDA 计算共享显存,导致"理论可用显存"与"实际可用显存"出现差异。
本研究的容量实验均以 nvidia-smi 与 Ollama 服务端日志的实测值为准,不采用理论值。
1.1.3 研究起点
研究的原始提问非常朴素:"如何让 Herman(Hermes)+ 本地 ollama + qwen3.8 发挥最大潜力"。
这一提问隐含了三个未经验证的假设:
- 假设 A:瓶颈在模型的参数配置(上下文长度、批大小、采样温度、GPU 卸载层数等)。
- 假设 B:"最大潜力"等于"本地模型跑得尽量快"。
- 假设 C:本地模型应当承担主要工作负载。
本报告的第一项贡献,就是逐条证伪这三个假设 。研究在推进过程中经历了一次根本性的
问题重述:从"如何让本地模型更快"变为"在给定硬件上,如何最小化每轮固定成本并
把能力放在最合适的位置"。
1.2 问题陈述的演化
研究过程中,问题陈述发生了三次明确的重述。这一演化过程本身是重要的研究材料,
因为它记录了如何识别一个被错误框定的问题。
第一版问题陈述(原始)
如何调优本地 ollama 与 qwen3.8,使其在 Hermes 中跑得最快?
缺陷 :把"调优"默认理解为"调整被调用方的参数"。这会导致研究者把注意力放在
num_ctx、num_batch、num_gpu、采样参数上,而这些参数的可得收益在实测中
远小于提示削减的收益(详见第四章)。
第二版问题陈述(瓶颈识别后)
Hermes 每轮发送的固定载荷有多大?其中哪一部分占比最高?
进展 :从"调参数"转向"量成本"。这一转向立即产出了本报告最重要的单个数字------
36,109 tokens 的固定每轮载荷 ,折合本机 21.1 秒预填充。
新缺陷 :识别出成本后,研究者的第一反应是"削减成本",而削减的方式被误选为
"裁剪能力"(最小化工具集 / 禁用技能包)。这一反应犯了两类错误:
- 技术错误:裁剪工具集确实快,但会静默移除能力(详见第七章)。
- 认识错误:把"降级"与"禁用"混为一谈(详见第八章)。
第三版问题陈述(本报告的最终框定)
在保持能力完整的前提下,如何把每轮固定载荷压到最低?能力应当分布在哪个层级
(远程 / 本地 / 无模型)?削减成本的操作应当以何种粒度、何种作用域实施?
这一版本的关键进步 在于把"成本"与"能力"从对立面变为两个独立可控的维度:
| 低成本 | 高成本 | |
|---|---|---|
| 高能力 | ✅ 目标(通过降级而非裁剪达成) | 现状(36,109 tok) |
| 低能力 | 裁剪后的机械执行者(被否定的路线) | --- |
在第二版问题陈述下,研究者只能在左上到右下的对角线上移动(即所谓"权衡");
在第三版问题陈述下,研究者意识到该对角线并非必然 ------它是由"削减手段的粒度"造成的
人为约束。这一认识直接导向了本报告第七章与第八章的核心成果。
1.3 研究问题
基于最终的问题框定,本报告回答以下六个研究问题(RQ):
| 编号 | 研究问题 | 对应章节 |
|---|---|---|
| RQ1 | 本机平台上,本地模型的真实性能边界是什么(吞吐、上下文容量、能力可用性)? | 第三章 |
| RQ2 | Hermes 每轮固定载荷的成本结构如何?瓶颈在哪一层? | 第四章 |
| RQ3 | 在保持能力完整的前提下,有哪些机制可以降低每轮成本?它们的收益与代价分别是什么? | 第五、六章 |
| RQ4 | 远程模型与本地模型的正确分工是什么?判据是什么?有哪些反直觉的正确结论? | 第七章 |
| RQ5 | 削减提示成本的"粒度"与"作用域"如何选择?是否存在优于"按类别挑选"的路径? | 第八、九章 |
| RQ6 | 这类效能优化研究中,哪些测量纪律是必需的?哪些错误模式是高频的? | 第二章、第十章 |
1.4 研究边界与非目标
为避免读者误读本报告的适用范围,以下明确列出不在本次研究范围内的事项:
| 非目标 | 说明 |
|---|---|
| 模型微调 / 量化对比 | 本研究使用既有模型与既有量化(Q4_K_M),未做任何权重层面改动 |
| 云端 / 集群扩展 | 全部实验限于单机单卡 |
| 安全加固 | 未评估提示注入、工具越权等安全议题 |
| 商业成本测算 | 仅给出 token 消耗比(66:1)作为量级证据,未做货币换算(远程 provider 单价未公开) |
| 用户工作流的正确性 | 未评估"哪些技能对使用者真正有用"------该判断属业务决策,报告只提供成本侧数据 |
| 上游代码质量评价 | 对 Hermes 源码的引用仅为机制说明,不含质量评判 |
1.5 报告的读者与使用方式
本报告面向三类读者,建议的阅读路径如下:
- 运维/工程读者 :从第三章(环境与基线)→ 第四、七、八章(瓶颈与优化)→ 第九章(融合方案),
可直接落地;脚本与补丁见配套交付目录。 - 方法学读者 :从第二章(方法论与口径纪律)→ 第九章(深层逻辑)→ 第十章(失败与自我纠错),
关注可迁移的研究纪律。 - 决策读者:从摘要的"关键发现速览"与"五条深层元规律"入手,再按需查阅具体章节。
报告中的每一项数值断言都可回溯到配套的原始输出文件(见各章末的"数据出处"小节)。
凡属推断而非实测的结论,均以 【推断】 显式标注;凡证据不足者,均列入第十一章"待核清单"。
第二章 方法论与测量口径纪律
2.1 研究方法的总原则:实测优先
本次研究在方法上遵循一条压倒性的原则:任何进入结论的数值,必须有本机实测输出支撑;
任何未实测的推断,必须显式标注为推断;任何证据不足者,不得进入结论。
这条原则在本研究中并非形式主义。研究过程中出现的 7 处错误 (见第十一章),
其中 5 处 如果严格遵循"实测优先"本可避免,另外 2 处 恰恰是被实测发现的。
换言之:实测优先既是最有效的防错机制,也是唯一能暴露既有错误的机制。
为落实这一原则,本研究对所有断言做了三级可信度标注:
| 级别 | 定义 | 标注方式 | 示例 |
|---|---|---|---|
| L1 实测 | 有本机命令的原始输出可直接复核 | 直接陈述 + 引用原始文件 | "冷加载 12.66 s"(server.log 的 load_duration) |
| L2 推算 | 由 L1 数据经明确公式导出,公式与输入均公开 | 陈述时给出公式与输入 | "KV ≈ 4.0 KB/token"(三点线性推算) |
| L3 推断 | 由机制推理导出,无直接观测 | 显式标注 【推断】 | 上游若改文件则重打可能冲突 |
2.2 技能编排方法:G-D-C-M-E-O-V
本研究的执行过程采用 G-D-C-M-E-O-V 技能编排法(目标类型 → 领域指纹 → 要素分解 →
匹配矩阵 → 扩展门禁 → 编排 → 校验)。该方法在本任务中的作用并非"选技能",
而是强制在动手前把工作流拆解并做覆盖率审计。
2.2.1 本任务的组合卡(门禁版)
目标类型:G8 混合 = 主 G4 构建/执行 + G1 分析/审计 + G5 改进/优化
领域指纹:devops(本地推理运维)· autonomous-ai-agents(Hermes 编排)· 方法学(跨域)
要素分解:
① 能力盘点 → ② 机制核验 → ③ 实测标定 → ④ 策略成型 → ⑤ 工程化 → ⑥ 逐项复验
主 skill :ollama-tuning(覆盖 ②③)
支撑 skill:hermes-local-providers(④),hermes-agent(②④)
质量门 :以 agent.log 的 in=/latency= 为唯一延迟口径
工具链 :terminal / python 脚本文件 / Hermes CLI / 源码检索
2.2.2 E 门禁的三项闭合情况
| 门禁 | 要求 | 实际执行 | 结果 |
|---|---|---|---|
| E1 覆盖率审计 | 每个工作流阶段须有 PRIMARY skill 或脚本兜底 | ② 有 ollama-tuning;③ 有实测脚本;④ 有 hermes-local-providers;⑤ 无 skill 覆盖 → 真缺口 |
触发 E2/E3 |
| E2 外拓检索 | 必须跑 15 源 registry | hermes skills search "model routing hybrid remote local" --source all → 明确返回 No skills found matching your query. (非超时)→ 降级本地图谱(575 skills)→ 仅软命中 automation-workflows |
无合适外部技能 → E3 |
| E3 构建/升级 | 核心缺口必须自建或升级 | 自建:local_exec.sh、verify_patch.py、reapply_patch.sh、demote_savings.py、hermes_bench.py;升级:ollama-tuning、hermes-local-providers |
闭合 |
人工段:无。
2.2.3 门禁制在本任务中的真实价值
E2 门禁的价值不在于"找到了什么",而在于它排除了"没找到是因为没找"的可能性 。
hermes skills search 明确返回空结果(而非超时、非静默),使"不存在现成技能"
成为一个有证据的结论,而不是"我以为没有"的猜测。
2.3 测量口径纪律(本研究的核心方法学贡献)
2.3.1 问题:墙钟时间在并发环境下不可用
研究早期使用墙钟(wall-clock)作为性能指标,得到了自相矛盾的结果:
| 实验 | 配置 | 墙钟 |
|---|---|---|
| 第 1 次 | 完整工具集 | 39 s |
| 第 2 次 | -t terminal,file |
17 s |
| 第 3 次 | -t terminal |
16 s |
| 第 4 次 | 同 -t terminal,file(不同档案) |
20 s |
| 第 5 次 | 同 -t terminal,file(回原档案) |
61 s |
第 2 次与第 5 次是同一个档案、同一个模型、同一个工具集、同一条工单 ,
墙钟却是 17 s 与 61 s,相差 3.6 倍。若以此为据,任何结论都不可靠。
根因定位 :本地服务以 OLLAMA_NUM_PARALLEL=1 运行,单并发串行队列 。
当同时存在其他本地请求(例如辅助通道的标题生成、视觉调用)时,请求进入队列,
延迟由"服务时间"变为"服务时间 + 排队时间"。排队时间取决于当时系统恰好在做什么,
因此完全不可预测。此外,本研究的主会话本身会持续产生辅助调用,构成持续的干扰源。
2.3.2 解法:使用服务端与 agent 端的确定性计数器
有效的替代口径有两个层次:
(a) Ollama 服务端返回的计数器 (/api/generate 与 /api/chat 的响应体):
| 字段 | 含义 | 用途 |
|---|---|---|
prompt_eval_count |
本次实际评估的提示 token 数 | 前缀缓存命中率 |
prompt_eval_duration |
提示评估耗时(纳秒) | 计算预填充速率 |
eval_count / eval_duration |
生成 token 数与耗时 | 计算生成速率 |
load_duration |
模型加载耗时 | 冷加载代价 |
(b) Hermes agent 端的每次 API 调用记录 (<profile>/logs/agent.log):
API call #1: model=qwen3.8-hermes provider=custom in=11632 out=46 total=11678 latency=55.1s
API call #2: model=qwen3.8-hermes provider=custom in=11694 out=22 total=11716 latency= 1.0s
in=是发送给模型的提示 token 数 ------这是本研究最重要的单一指标,
因为它直接反映"提示宽度",且不受并发影响(确定性)。latency=是每次调用的延迟,受并发影响,仅用于同批次横向对比。
结论性纪律 :凡涉及"提示规模/成本结构"的结论,一律以 in= 为口径;
凡涉及"延迟"的结论,必须在同批次、无并发干扰的条件下测量,否则不予采信。
本研究在第七、八、九章的所有优化结论均以 in= 为主口径。
2.3.3 三源交叉验证
对关键结论,本研究采用三个独立数据源交叉验证:
| 源 | 提供的信息 | 独立性 |
|---|---|---|
nvidia-smi |
显卡占用/剩余显存 | 独立于 Ollama |
Ollama server.log |
服务端实际生效环境、启动参数、显存分解、请求日志 | 服务端视角 |
Hermes agent.log |
实际发送的提示规模、延迟、辅助通道路由 | 客户端视角 |
例如"64k 是显存上限"这一结论同时得到三方支持:
nvidia-smi 显示 20,480 MiB 占用 / 余 3,658 MiB;
server.log 给出显存分解 model 15,339 + context 2,924 + compute 400 ≈ 18,663 MiB;
而 128k 尝试触发了 RemoteDisconnected 与服务端自我重启。
三者互相印证,结论可信度显著高于单一来源。
2.4 变更管理纪律
本研究对生产环境(使用者正在使用的 Hermes 与 Ollama)实施变更,因此建立了四项纪律:
2.4.1 惰性变更(inert change)优先
定义 :新引入的代码路径在未显式启用前,行为必须与变更前逐字节一致。
本研究对 Hermes 源码的唯一补丁即遵循此原则。实测验证:在未设置任何配置键与环境变量时,
compact_skill_categories() 返回 ∅,渲染结果与打补丁前一致。
这使得"补丁已应用"与"功能已启用"成为两个可独立决定的事,任一不启用都不影响使用者。
这一纪律的价值在第十一章有直接体现:补丁本身在验证过程中被发现存在缺陷,
但由于处于惰性状态,缺陷从未对抗使用者产生任何实际影响。
2.4.2 三重保险(备份 + 语法检查 + 异常隔离)
对源码的修改同时施加三重保护:
| 层 | 措施 | 实测结果 |
|---|---|---|
| 1 | 变更前复制原文件(.bak-20260912)+ 记录上游 HEAD 与文件 sha256 |
回滚锚点 HEAD 6c3d4a4a;原文件 4921bad3... |
| 2 | 变更后立即 py_compile |
通过 |
| 3 | 新增逻辑包裹在 try/except Exception: pass 中 |
即使新逻辑失败也不影响原行为 |
2.4.3 可逆性优先于正确性
对每一项变更预先设计回滚路径,且要求回滚为一条命令。本研究的三级回滚为:
bash
hermes config set skills.demote_categories '[]' # L1 立即停用(保留补丁)
cp agent/coding_context.py.bak-20260912 agent/coding_context.py # L2 源码还原
git checkout -- agent/coding_context.py # L3 git 原生还原
2.4.4 隔离验证(不触碰实时配置)
验证新功能时,不修改使用者的实时配置。做法是利用函数已支持的显式参数:
python
coding_compact_skill_categories(platform="cli", cwd=os.getcwd(),
config={"skills": {"demote_categories": ["gaming","media"]}})
这一做法在研究中直接暴露了补丁的一个真实缺陷(详见第十一章第 5 项),
若当时选择"直接改实时配置来测",该缺陷会被掩盖。
2.5 伪优化的剔除机制
本研究对每一项候选优化施加三道检验,任一不通过即剔除:
| 检验 | 问题 | 被剔除的候选项 |
|---|---|---|
| 机制检验 | 该优化所依赖的机制,在源码层面是否存在? | 通过环境变量 OLLAMA_GPU_OVERHEAD 预留显存(0.32+ 已不读取) |
| 因果检验 | 观测到的改善是否真由该变更引起?有无混杂变量? | 早期"17 s vs 61 s"的墙钟对比(实为并发噪声) |
| 成本检验 | 该优化的代价(能力/风险/维护)是否小于收益? | 最小化工具集(省 12.0 s 但静默失去全部技能能力) |
经此机制,本研究明确剔除了 6 项诱人但无效(或有害)的"优化" ,
详见第六章末节的"负结果清单"。这一清单与方法学上的正结果同等重要:
它防止使用者重复踩踏同样的坑。
2.6 可复现性
本研究的所有测量脚本、原始输出与配置均随报告交付,目录结构如下:
| 目录 | 内容 |
|---|---|
01_实测报告/ |
性能基线与瓶颈实测报告 + raw/ 全部原始输出 |
03_配置与脚本/ |
hermes_bench.py(性能基准)、verify.sh(健康自检)、keepwarm.ps1(驻留保温) |
06_混合路由策略/ |
local_exec.sh(本地执行者封装) |
07_动态加载优化/ |
verify_patch.py(补丁验证)、reapply_patch.sh(升级后重打)、demote_savings.py(收益测算) |
09_研究报告/ |
本报告 |
复现须知 :本报告的延迟类结论依赖具体硬件(RTX 5090 Laptop 24 GB)与具体模型
(27.3B Q4_K_M)。in= 类结论(提示规模)与硬件无关,可跨平台复现。
第三章 实验环境与性能基线标定
本章回答 RQ1:本机平台上,本地模型的真实性能边界是什么?
所有数据均来自本机实测,原始输出见 01_实测报告/raw/。
3.1 环境指纹
| 项 | 实测值 | 来源命令 |
|---|---|---|
| GPU | NVIDIA GeForce RTX 5090 Laptop,24,463 MiB | nvidia-smi |
| 驱动 / CUDA UMD | 616.92 / 13.4 | nvidia-smi |
| 驱动模式 | WDDM | nvidia-smi |
| 系统内存 | 31.4 GiB 总 / 空闲 12.4 GiB | server.log 的 system memory |
| CPU | Intel Core Ultra 9 275HX(24 逻辑核) | server.log |
| 交换空间 | 空闲 35.1 GiB | server.log |
| Ollama | 0.33.2 | GET /api/version |
| 推理后端 | libdirs=ollama,cuda_v13,compute=12.0 |
server.log 的 inference compute |
| Hermes | v0.21.1(2026.9.7)· upstream ad03f20d · git 安装 |
hermes --version |
| 模型目录 | D:\ollama\models(43 个 blob) |
server.log / ollama list |
3.1.1 模型元数据(关键校正)
ollama show qwen3.8:latest
architecture qwen35
parameters 27.3B
context length 262144
embedding length 5120
quantization Q4_K_M
requires 0.32.12
Capabilities completion / vision / tools / thinking
Projector architecture=clip, parameters=460.73M, dimensions=5120
三点必须强调:
qwen3.8不是 8B 级小模型,而是 27.3B (Qwen3.5 家族)。标识中的 "3.8"
是版本号而非参数量,这一命名极易导致容量规划错误。- 原生上下文 262,144 ,但本机显存只允许其实用上下文 65,536 (见 3.5)。
模型能力上限 ≠ 部署上限。 - 具备
vision(多模态投影器clip460.73M)与tools(工具调用)能力------
这两项能力在后续章节的路由设计中起决定作用。
3.1.2 服务端实际启动参数(从 server.log 提取)
llama-server.exe --model <blob> --port 59949 --host 127.0.0.1 --no-webui --offline
-c 65536 -np 1 --log-verbosity 4 --no-log-prefix --no-log-timestamps --no-jinja
--chat-template chatml
--mmproj <clip blob>
--spec-type draft-mtp --spec-draft-n-max 4 --spec-draft-backend-sampling
--load-mode none --cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on
-b 512 -ub 512 --context-shift --keep 4
这一行包含了本研究需要解释的多个现象的直接证据(详见 3.6 与第八章):
-c 65536:上下文由上层的num_ctx决定(本次为 65536)。-np 1:单并发槽------本研究后续所有"串行队列"结论的源头。--mmproj <blob>:多模态投影器被加载,占用独立显存(见 3.5)。--spec-type draft-mtp:MTP 投机解码已启用(见 3.6)。--flash-attn on、--cache-type-k/v q8_0:KV 缓存量化与 FlashAttention 生效。--load-mode none与disabling mmap for llama-server load by default, reason=windows_cuda:
Windows CUDA 下禁用内存映射加载。
3.2 吞吐基线
3.2.1 预填充速率与生成速率(qwen3.8:latest,num_ctx=8192)
| 提示长度 | 预填充 token | 预填充耗时 | 预填充速率 | 生成 token | 生成速率 |
|---|---|---|---|---|---|
| ~576 | 576 | 0.79 s | 726 tok/s | 16 | 65.5 tok/s |
| ~2,246 | 2,246 | 1.78 s | 1,263 tok/s | 16 | 57.5 tok/s |
| ~6,686 | 6,686 | 3.90 s | 1,713 tok/s | 16 | 68.0 tok/s |
观察 1:预填充速率随提示长度上升而非下降。
从 726 → 1,263 → 1,713 tok/s,存在明显的规模效应。原因是每批计算存在固定开销
(kernel 启动、批次调度、显存访问建立),在短提示下该开销无法被摊薄。
到 6,686 token 时仍未看到饱和迹象。
方法论含义 :用短提示测出的预填充速率会低估 长提示的真实速率。
本研究在后续折算中一律采用 1,713 tok/s (实测峰值,对应最大提示),
这一选择对优化结论是保守 的(若真实速率更高,则"每轮固定开销"的绝对耗时更低,
但相对排序不变)。
3.2.2 生成速率(64k 上下文,暖机)
| 输出预算 | 生成 token | 耗时 | 生成速率 |
|---|---|---|---|
| 60 | 60 | 0.93 s | 64.4 tok/s |
| 275 | 275 | 5.07 s | 54.2 tok/s |
基线取 54--68 tok/s,中位数约 60 tok/s。 500 token 的回答约需 8.3 s。
3.2.3 冷加载代价
| 场景 | 实测耗时 | 来源 |
|---|---|---|
| 冷加载(17 GB 模型从磁盘) | 12.36 s | load_duration |
| 冷加载 + 首个响应 | 12.70 s | 总墙钟 |
| 64k 首次加载(含参数拟合) | 13.1 -- 13.6 s | load_duration |
| 64k 加载 + 首次响应(含拟合计算) | 18.6 -- 20.9 s | 总墙钟 |
观察 2:冷加载 12.4--12.7 秒是一个不可忽略的常数。
在交互式 agent 使用中,会话内思考间隔常超过数分钟。若模型在此期间被卸载,
则每一轮的第一个 token 都要额外等待约 12.7 秒。这一发现直接导出了第六章
的"驻留经济学"分析。
3.3 能力可用性验证
优化必须在"能力可用"的前提下进行,因此在优化前先确认基线能力。
3.3.1 工具调用(OpenAI 兼容端点)
向 /v1/chat/completions 发送带 tools 数组的请求:
json
{"model":"qwen3.8-hermes","stream":false,"max_tokens":300,
"messages":[{"role":"user","content":"What is the weather in Beijing right now? Use the available tool."}],
"tools":[{"type":"function","function":{"name":"get_weather", ...}}, ...]}
实测返回:
finish_reason = tool_calls
tool_calls = [{"id":"call_b1f8dl4e","index":0,"type":"function",
"function":{"name":"get_weather","arguments":"{\"city\":\"Beijing\"}"}}]
reasoning field: yes, 146 chars
content: ''
结论 :工具调用协议完全可用,finish_reason 正确,参数 JSON 正确。
同时观察到模型返回独立的 reasoning_content 字段(非 OpenAI 标准字段,
Hermes 会忽略,无害)。
延迟 :暖机 4.2 s ;冷机 15.4 s ------差别全部来自模型加载 ,
与工具调用协议本身无关。这一点在后续优化中很关键:
"工具调用慢"是一个假象,真实原因是模型换载。
3.3.2 视觉能力
向模型发送程序生成的 128×128 PNG(白色背景 + 居中红色实心圆 + 左下蓝色色块):
VISION -> 'The image contains a large red circle in the center and a smaller
blue square in the bottom-left corner.'
结论 :视觉能力可用,描述与图像真实内容一致(含位置信息)。
暖机延迟 1.0--1.2 s ;换载模型后 26.7--33.0 s(同样全部是加载代价)。
重要副发现 :早期一次视觉测试返回空字符串 '',一度被误判为"视觉能力缺失"。
深入排查后确认根因是 token 预算耗尽 :在 max_tokens=60 下,
思考型模型先把预算消耗在推理上,导致 finish_reason=length 而 content 为空。
将预算提高到 600 后立即返回正确结果。
方法论含义 :"输出为空"不等于"能力缺失"。 对思考型模型,
必须给足 max_tokens 才能区分"不支持"与"预算不足"。
本研究将此列为 AI 能力测试的标准检查项。
3.3.3 思考模式:一个反直觉的测量结果
| 设置 | 思考长度 | 输出 token | 耗时 |
|---|---|---|---|
think: true(默认) |
301 字符 | 266 | 3.98 s |
think: false |
0 | 374 | 4.92 s |
关闭思考后,模型反而输出了更多 token 且更慢。 这一结果违反直觉
(通常认为跳过推理链会更快),但与 3.6 节的发现相吻合:本机启用了 MTP 投机解码,
思考 token 在投机解码下具有高接受率,属于"顺带产出"。
关闭思考后模型改用冗长的直答方式,反而增加了 token 数。
结论 :不要关闭思考。 这是本研究剔除的一项"伪优化"。
3.4 上下文容量实验
3.4.1 实验设计
固定模型(qwen3.8 及其 -opt 派生标签),逐一设定 num_ctx,
测量加载后的显存占用与推理可行性。
3.4.2 结果
num_ctx |
权重+KV 驻留 | 全卡占用 | 剩余显存 | 结果 |
|---|---|---|---|---|
| 8,192 | 16.11 GiB | 18,080 MiB | 6,058 MiB | ✅ 可用 |
| 32,768 | 16.20 GiB | 19,094 MiB | 5,044 MiB | ✅ 可用 |
| 65,536 | 16.33 GiB | 20,480 MiB | 3,658 MiB | ✅ 实用上限 |
| 131,072 | --- | --- | --- | ❌ 推理进程崩溃 |
3.4.3 KV 缓存成本推算(L2 推算)
以三点线性推算:
(16.33 − 16.11) GiB = 0.22 GiB,对应 (65,536 − 8,192) = 57,344 token
0.22 × 1024 × 1024 KB / 57,344 ≈ 4.0 KB/token
KV 成本仅约 4.0 KB/token ,远低于常规稠密注意力模型。
这是 Qwen3.5 采用混合注意力(部分层使用线性注意力)的直接红利。
实践含义 :在本机型上,把上下文从 8k 提到 64k 只需多付 0.22 GiB ,
因此"为了省显存而压小上下文"在本例中是错误的优化方向 ------
真正的约束在 64k 与 128k 之间的悬崖处。
3.4.4 128k 崩溃的根因分析(L1 实测 + L2 推算)
现象 :请求 qwen3.8-hermes(num_ctx=131072)后,
后续请求返回 http.client.RemoteDisconnected: Remote end closed connection without response,
nvidia-smi 显示显存回落至 0 MiB,Ollama 服务端日志出现自我重启记录。
根因 :server.log 的显存分解提供了直接证据:
common_memory_breakdown_print:
| CUDA0 (RTX 5090 Laptop GPU) | 24435 = 22401 + (18663 = 15339 + 2924 + 400) + (-16629)
| Host | 766 = 682 + 0 + 84
即 64k 上下文时:model 15,339 + context 2,924 + compute 400 ≈ 18,663 MiB。
若上下文翻倍至 128k,context 项约需 5.8 GiB (2,924 × 2),
总量约 21.6 GiB ;再叠加日志中记录的多模态投影器最坏情况
[mtmd] estimated worst-case memory usage of mmproj is 1161.02 MiB,
合计约 22.7 GiB ,超出报告可用显存 22.6 GiB。
结论 :64k 是本机型的硬上限,128k 不可用。
这不是模型能力限制(原生支持 262,144),而是显存容量限制。
且由于 WDDM 模式的存在,实际可用显存还会随显示负载浮动,
65,536 已属顶格部署(余量仅 3,658 MiB)。
方法论含义 :扩容前的容量估算必须先于实验。
本次 128k 的尝试把一个正在提供服务的推理进程打挂(服务端需自我重启),
属于"破坏性试错"。正确流程应为:先按 KV/token × 目标上下文 + 权重 + mmproj 估算,
确认余量后再 ollama create。
3.5 模型标签的实例共享现象
实验中观察到一项容易造成误判的行为:
请求 qwen3.8:latest + num_ctx=32768 → wall 0.3 s
ollama ps → RESIDENT qwen3.8:opt ctx=32768
请求的标签是 qwen3.8:latest,但实际驻留的是 qwen3.8:opt。
原因是两个标签共享同一份权重 blob (sha256-f5f1dd89...),
Ollama 直接复用已加载实例,只调整上下文参数。
推论:
- 共享权重的不同标签之间切换不会触发重载------前提是权重 blob 相同。
- 但若标签对应的权重不同(例如不同量化版本),则切换代价为 13--20 秒的完整重载。
OLLAMA_MAX_LOADED_MODELS=1意味着任何真实的权重切换都会卸载前一个模型。
方法论含义 :在多模型工作流中,标签切换的频率直接决定延迟 。
本研究的优化方案(统一使用 qwen3.8-hermes 单一标签,并让辅助通道与执行者
共用同一模型)正是基于这一机制。
3.6 投机解码的发现(对既有认知的修正)
本研究在 server.log 中发现了投机解码的直接证据:
--spec-type draft-mtp --spec-draft-n-max 4 --spec-draft-backend-sampling
运行期统计:
spec common_specu: statistics draft-mtp: #calls(b,g,a) = 5 186 186
draft acceptance = 0.52163 (217 accepted / 416 generated), mean len = 3.09
acc per pos = (0.827, 0.567, 0.433, 0.260)
draft 接受率 0.52,平均接受长度 3.09,逐位置接受率递减(0.827 → 0.260)。
这一发现修正了先前结论。 本研究使用的 ollama-tuning 技能中原有一行断言:
"投机解码:Ollama 0.32 不支持(PR 被拒),要 draft 模型得换 llama.cpp server / LM Studio"。
实测证明 Ollama 0.33.2 已原生支持 MTP 投机解码 ,且这正是本机生成速率达到
54--68 tok/s 的重要来源。该技能已据此修正。
机制说明 :模型 Modelfile 中的 PARAMETER draft_num_predict 4 触发 Ollama
附加上述投机解码参数,由 llama.cpp 以 MTP(多 token 预测)方式实现。
方法论含义 :"某工具不支持 X"这类否定性断言必须定期重新验证。
工具的迭代会持续推翻旧结论,而旧结论一旦写入知识库就会长期污染后续判断。
3.7 基线小结
| 指标 | 基线值 | 后续章节的用途 |
|---|---|---|
| 预填充速率 | 1,713 tok/s | 折算"每轮固定载荷"的时间成本(第四、七章) |
| 生成速率 | 54--68 tok/s | 折算回答时间(第四章) |
| 冷加载 | 12.4--12.7 s | 驻留经济学(第六章) |
| 上下文上限 | 65,536 | 硬约束,所有方案必须在其内(第四、七章) |
| KV 成本 | ~4.0 KB/token | 容量规划公式(第八章) |
| 工具调用 | ✅ 可用,暖机 4.2 s | 混合路由的可行性前提(第七章) |
| 视觉 | ✅ 可用,暖机 1.0--1.2 s | 辅助通道本地化的可行性前提(第六章) |
| 思考模式 | 开启更优 | 剔除"关闭思考"伪优化(第六章) |
| 投机解码 | 启用,接受率 0.52 | 解释生成速率;修正既有知识 |
| 单并发 | 已验证(-np 1) |
串行队列结论(第七章) |
第四章 瓶颈解剖:每轮固定载荷的成本结构
本章回答 RQ2:Hermes 每轮固定载荷的成本结构如何?瓶颈在哪一层?
4.1 两种独立测量方法及其交叉验证
为避免单一测量方法引入的系统偏差,本研究用两条完全独立的路径测量同一对象。
4.1.1 方法 A:Hermes 原生离线统计
hermes prompt-size
该方法不发起任何 API 调用 (帮助文本明确说明 "Runs offline (no API call)"),
由 Hermes 在本地构建一份"新会话的系统提示"并统计其组成。实测输出:
Prompt-size breakdown (platform=cli, model=DeepSeek-V4-Flash-0731)
System prompt total : 94,254 B (92.0 KB, 74,903 chars)
Major blocks:
skills index : 56,345 B (55.0 KB)
memory : 3,618 B (3.5 KB)
user profile : 2,551 B (2.5 KB)
Prompt tiers:
stable (identity/guidance/skills) : 28,799 B (28.1 KB)
context (AGENTS.md/cwd files) : 0 B (0.0 KB)
volatile (memory/profile/timestamp) : 65,455 B (63.9 KB)
Tool schemas : 40,300 B (39.4 KB, 25 tools)
4.1.2 方法 B:真实请求转储
Hermes 在遇到不可重试的客户端错误时会把完整请求体写入
sessions/request_dump_*.json。本研究解析了一份实际的历史转储:
dump: request_dump_20260902_163255_41e6bb_20260902_165402_314468.json
request.url = https://ark.cn-beijing.volces.com/api/v3/chat/completions
error.type = Unauthorized (401) "The API key format is incorrect"
body keys = [model, messages, tools, max_tokens, reasoning_effort]
messages : 2
system messages : 1 -> 78,008 chars (~19,502 tok)
tools : 38 -> 66,431 chars (~16,607 tok)
ALL messages : 78,855 chars (~19,713 tok)
FIXED PER-TURN : ~36,109 tok (system + tools, resent every turn)
4.1.3 交叉验证的结论
| 量 | 方法 A | 方法 B | 一致性 |
|---|---|---|---|
| 系统提示规模 | 74,903 chars | 78,008 chars | 同量级(不同会话,技能集略有差异) |
| 工具 schema | 40,300 B / 25 tools | 66,431 chars / 38 tools | 不同! 见下 |
| 固定载荷折合 token | ≈ 33,600 | 36,109 | 同量级 |
工具数量的差异(25 vs 38)需要解释,且这一差异本身极具信息量:
- 方法 A 统计的是当前 平台(
platform=cli)在新配置下的工具集,此时
tools.tool_search.enabled: auto已生效,部分工具被"延迟加载" ,
因此进入 schema 的只有 25 个。 - 方法 B 是 2026-09-02 的历史转储,当时
tool_search的延迟阈值与工具集
配置不同,因此有 38 个工具全量进入。
方法论含义 :"固定载荷"并非恒定,它随配置漂移。
因此任何"提示成本"的结论都必须绑定其测量时的配置状态。
本研究后续一律以方法 A 的 prompt-size 作为当前状态的权威口径,
以方法 B 作为"未优化状态下可以达到多高"的参照。
4.2 系统提示的组成解剖
4.2.1 按块划分(方法 A,当前配置)
| 块 | 体积 | 占系统提示 | 折合 token(÷4) | 作用 |
|---|---|---|---|---|
| 技能索引 | 56,345 B | 60% | ~14,086 | 列出所有可用技能及其描述 |
| 记忆 | 3,618 B | 4% | ~905 | 跨会话持久事实 |
| 用户画像 | 2,551 B | 3% | ~638 | 使用者身份与偏好 |
| 其余(身份/指令/工具指引) | 31,740 B | 33% | ~7,935 | 角色定义与行为规范 |
4.2.2 技能索引为什么是最大块
本研究进一步解剖了技能索引的内部结构,来源为
.skills_prompt_snapshot.json(348 KB,566 个技能条目)与
hermes prompt-size --json 的 skills_breakdown 数组。
每个技能在索引中占一行,格式为:
- <skill-name>: <description 截断至 57 字符>
实测平均每行约 85 字节 (4 字节缩进 + 名称 + ": " + 至多 57 字节描述 + 换行)。
566 行合计 51,999 字节;差额 4,176 字节为分类标题行与样板文字。
关键推论 :描述占据了索引的约 68% 。
若只保留技能名(去掉描述),每行约 27 字节。这一事实是第七章"降级为目录"
方案的量化基础。
4.2.3 技能索引的类别分布:一个意外发现
按类别统计索引成本(实测 prompt-size --json):
| 类别 | 技能数 | 索引成本 | 折合 | 占索引 |
|---|---|---|---|---|
| scientific-skills/skills | 157 | 12,705 B | 3,176 tok | 24% |
| scienceclaw-migrated | 133 | 10,747 B | 2,687 tok | 21% |
| bmad-migrated | 54 | 4,796 B | 1,199 tok | 9% |
| research/workbuddy-migrated | 33 | 4,662 B | 1,166 tok | 9% |
| autonomous-ai-agents/workbuddy-migrated | 25 | 3,524 B | 881 tok | 7% |
| productivity/workbuddy-migrated | 20 | 3,075 B | 769 tok | 6% |
| creative/workbuddy-migrated | 13 | 1,795 B | 449 tok | 3% |
| productivity | 23 | 1,729 B | 432 tok | 3% |
| software-development | 19 | 1,664 B | 416 tok | 3% |
| creative | 20 | 1,485 B | 371 tok | 3% |
| research | 14 | 1,175 B | 294 tok | 2% |
| ...(其余 33 个类别) | 105 | ~2,600 B | ~650 tok | 5% |
| 合计 | 566 | 51,999 B | ~13,000 tok | 100% |
四个"批量迁移"引入的技能包合计 443 个技能、10,545 tok、占索引 81%。
而使用者亲手创建 的技能(intellectual-property、rd-management、
patents-search、regulatory-affairs-head 等)合计约 105 tok,占索引 0.8%。
这是本研究报告最重要的单项发现之一 :
索引的 81% 消耗在批量迁移产物上,而使用者自己的知识仅占 0.8%。
这一比例关系直接决定了后续所有优化的着力点------问题不在"技能太多",
而在"这个档案为什么要装 443 个迁移技能"。
4.2.4 "迁移产物"的性质辨析
在优化讨论中,"迁移"(migrated)这一来源标记极易被误读为"无用"。
本研究明确反对这一推论,并给出证据:
| 含有关键能力的迁移包 | 其中的关键技能 | 对该档案的价值 |
|---|---|---|
| scientific-skills | uspto-database |
专利检索------IP 档案的立身之本 |
| scientific-skills | openalex-database、literature-review、peer-review、scientific-critical-thinking、scholar-evaluation、citation-management |
学术检索与审稿 |
| scientific-skills | docx、pdf、xlsx、pptx、markitdown |
交付格式 |
| scientific-skills | rdkit、chembl-database、pubchem-database |
化学/配方 |
| scientific-skills | iso-13485-certification、fda-database、market-research-reports |
法规与研报 |
| scienceclaw-migrated | crossref-search、semantic-scholar |
文献核验 |
| scienceclaw-migrated | legal-analysis、regulatory-drafting |
法务与法规撰写 |
| scienceclaw-migrated | nano-banana-pro、scientific-diagram-generation、data-visualization-expert |
顶刊级配图 |
| scienceclaw-migrated | pdf-processing、pdf-processing-pro |
文档处理 |
结论 :"来源是迁移" ≠ "内容无用"。 真正的无用部分是同一批迁移里
与档案主题无关的消费级/外设/远缘学科技能,它们只是混在同一批迁移中 。
正确的判据是内容相关性 ,不是来源 。这一认识在第五章与第七章
的路线选择中起了决定性作用。
4.3 工具 schema 的成本结构
prompt-size 提供按工具集(toolset)的成本分解:
| 工具集 | 工具数 | schema 体积 |
|---|---|---|
| file | 4 | 5,404 B |
| browser | 5 | 4,359 B |
| delegation | 1 | 4,032 B |
| skills | 3 | 3,650 B |
| browser-use | 1 | 3,498 B |
| terminal | 1 | 3,371 B |
| (unknown) | 3 | 3,328 B |
| memory | 1 | 3,296 B |
| code_execution | 1 | 3,009 B |
| tts | 1 | 1,912 B |
| web | 2 | 1,910 B |
| clarify | 1 | 1,636 B |
| vision | 1 | 845 B |
| 合计 | 25 | 40,300 B |
观察 3:单个工具的平均 schema 成本约 1,600 字节。
其中 delegation(1 个工具 / 4,032 B)、memory(1 个 / 3,296 B)、
browser-use(1 个 / 3,498 B)是单个工具最昂贵 的三项,
因为它们的参数说明与文档字符串很长。
机制说明 :Hermes 的 tools.tool_search.enabled: auto 提供工具延迟加载:
日志显示 tool_search activated (tier 1): 22 core/visible tools kept, 5 deferred (~4452 tokens)。
在收窄工具集后,该机制的效果更显著:
tool_search activated (tier 1): 5 core/visible tools kept, 1 deferred (~380 tokens)。
推论 :工具 schema 的成本可以通过两条途径降低------
(a)减少启用的工具集;(b)依靠 tool_search 把非核心工具转为延迟加载。
本研究在第七章的实验中同时利用了这两条途径。
4.4 固定载荷的时间折算
以第三章测得的 1,713 tok/s 预填充速率为准:
| 组成 | token | 预填充耗时 | 占比 |
|---|---|---|---|
| 技能索引 | ~14,086 | 8.2 s | 39% |
| 工具 schema | ~10,075 | 5.9 s | 28% |
| 身份/指令(含 SOUL.md 人格) | ~4,664 | 2.7 s | 13% |
| 记忆 + 用户画像 | ~1,542 | 0.9 s | 4% |
| (按 36,109 tok 口径的其余部分) | ~5,742 | 3.4 s | 16% |
| 合计 | 36,109 | 21.1 s | 100% |
再加上一次 500 token 的回答(8.3 s 生成):
一次"全新轮次"(前缀未命中缓存)约需 29 秒纯计算,不含工具执行 I/O。
这是本研究的核心量化结论。 它意味着:在本地模型上,
使用者感知到的"卡顿"主要不是模型在思考,而是在重复读取一份每轮都不变的文本。
4.5 前缀缓存:救赎及其严格条件
4.5.1 实测证据
API call #1: model=qwen3.8-hermes in=11632 out=46 total=11678 latency=55.1s
API call #2: model=qwen3.8-hermes in=11694 out=22 total=11716 latency= 1.0s
第二轮 in= 仅比第一轮多 62 tokens ,而延迟从 55.1 s 降至 1.0 s。
另一次独立验证(重复同一 6,686 token 提示):
prompt_eval_count = 23 ← 提示共 6,686 token,仅重算 23 个
结论 :Ollama 在第 2 轮及之后复用 KV 前缀缓存,只计算新增部分。
4.5.2 严格条件
前缀缓存的生效需要满足两个条件的联合:
- 模型保持驻留------模型一旦卸载,KV 缓存随之销毁(见第六章)。
- 提示前缀字节级不变------系统提示中任何字符变化都会使缓存失效。
推论(本报告的关键实践结论):
| 会话中的操作 | 对本地模型的影响 |
|---|---|
加载一个技能(skill_view) |
若技能内容进入系统提示 → 缓存失效 |
| 写入记忆(memory) | 记忆块变化 → 缓存失效 |
| 切换工具集 | 工具 schema 变化 → 缓存失效 |
| 切换模型标签(不同权重) | 模型重载 + 缓存失效 |
| 纯粹继续对话 | ✅ 缓存命中,仅付增量 |
因此,本地模型上的"提示缓存纪律"价值 21 秒/轮。
这也解释了 Hermes 硬性不变量"绝不在会话中破坏提示缓存"在本地场景下的极高价值------
在远程模型上,破坏缓存只影响成本;在本地模型上,它直接影响使用者的等待时间。
4.6 本章小结
| 结论 | 数值 | 可信度 |
|---|---|---|
| 每轮固定载荷 | 33,600--36,109 tok | L1 实测(两法交叉验证) |
| 折合预填充时间 | 21.1 s | L2 推算(1,713 tok/s) |
| 技能索引占系统提示 | 60% | L1 实测 |
| 索引中描述占比 | ~68% | L2 推算(行结构分析) |
| 四个迁移包占索引 | 81%(443 技能 / 10,545 tok) | L1 实测 |
| 使用者自建技能占索引 | 0.8%(~105 tok) | L1 实测 |
| 工具 schema 成本 | 40,300 B / 25 工具,均 1,600 B/工具 | L1 实测 |
| 前缀缓存收益 | 第二轮延迟 ÷55(55.1 s → 1.0 s) | L1 实测 |
| 缓存失效的触发条件 | 系统提示字节变化 / 模型卸载 | L2 推算(机制) |
瓶颈定位 :瓶颈不在模型,在于"每轮都要重发的固定文本",其中技能索引是最大单项。
第五章 递进一:从"模型参数调优"到"提示词经济学"
本章记录优化的第一次范式转变 ,以及伴随而来的 6 项被实测剔除的伪优化。
5.1 起点假设及其来源
研究开始时的默认假设是:"发挥最大潜力"等于"把模型参数调对"。
这一假设并非凭空产生,它有三个看似合理的来源:
- 领域惯例 :本地推理社区的常规讨论集中于
num_ctx、num_batch、
num_gpu、KV cache 类型、量化等级等参数。 - 工具提供的显式旋钮 :Ollama 提供约 10 个环境变量与 8 个请求级参数,
这些"可见的旋钮"天然吸引注意力。 - 可解释性:参数与性能之间的因果关系直观(更大的批 → 更快的预填充)。
因此,研究的第一阶段系统性地检视了这些旋钮的实际可得收益。
5.2 参数旋钮的实测可得性
5.2.1 环境变量
| 变量 | 实测状态 | 结论 |
|---|---|---|
OLLAMA_KV_CACHE_TYPE=q8_0 |
已设,server.log 确认 --cache-type-k/v q8_0 |
有效,但已是最优 |
OLLAMA_FLASH_ATTENTION=1 |
已设,确认 --flash-attn on |
有效,但已是最优 |
OLLAMA_NUM_PARALLEL=1 |
已设,确认 -np 1 |
有效但性质是约束------它造成了串行队列(第七章) |
OLLAMA_MAX_LOADED_MODELS=1 |
已设 | 有效,防止显存抖动 |
OLLAMA_CONTEXT_LENGTH=8192 |
已设 | 数值偏低,是隐患(见 5.3.3) |
OLLAMA_KEEP_ALIVE=2m |
被桌面版覆盖为 2m | 关键问题(第六章) |
OLLAMA_NUM_THREAD=24 |
已设,实际 n_threads = 12 / 24 |
有效,但内核自选 12 |
OLLAMA_GPU_OVERHEAD |
--- | 已废弃,0.30+ 不读取 |
OLLAMA_MAX_VRAM |
--- | 已从代码移除 |
观察 :环境变量层面的"优化空间"几乎为零------现有设置已接近本机最优 。
唯一有实质影响的两项(KEEP_ALIVE、CONTEXT_LENGTH)都属于配置正确性问题
而非性能调优问题,且其中一个还被上层软件覆盖。
5.2.2 请求级参数
| 参数 | 实验 | 结论 |
|---|---|---|
num_batch |
512 → 2048 | 无增益(默认 512 已饱和:865 token 预填充 1,389 tok/s 持平) |
num_ctx |
8,192 → 65,536 | 大上下文不降低预填充速率(反而因摊薄固定开销而略升) |
num_gpu |
默认(999 = 全量) | 8/10 个测试模型可全量入显存,无需手工降层 |
temperature |
1.0 → 0.3 | 不影响速度;影响工具调用的确定性(已采纳 0.3) |
think |
true / false | 关闭反而更慢(3.98 s → 4.92 s),见 3.3.3 |
观察 4:请求级参数的可得收益同样趋近于零。
其中 num_batch 的"无增益"结论尤为重要------它是社区中被最广泛推荐的调优手段之一,
但在本机实测中完全无效。
5.2.3 一个被误设为"参数"的正确性问题
OLLAMA_CONTEXT_LENGTH=8192 这一设置初看是"保守的参数选择",
但结合第四、七章的发现,它是一个隐患:
- Hermes 的固定载荷为 36,109 tokens;
- 若某个模型未在 Modelfile 中声明
num_ctx,Ollama 会回退到全局默认值; - 则该模型的可用上下文(8,192)小于固定载荷,系统提示根本无法装入。
这一隐患的实际触发条件 是"使用未声明 num_ctx 的模型作为主模型"。
本研究据此把该变量调整为 32,768 (与本机自动推断值一致,见 5.4.2),
并明确记录:Hermes 主模型必须使用在 Modelfile 中声明 num_ctx ≥ 65,536 的模型。
5.3 剔除清单(负结果)
本研究明确剔除以下 6 项候选优化。负结果与方法学上的正结果同等重要 ,
因为它们防止使用者重复投入。
| # | 被剔除的候选 | 剔除依据 | 证据来源 |
|---|---|---|---|
| N1 | 调 num_batch 至 1024/2048 以提速预填充 |
实测无增益,默认 512 已饱和 | 865 token 预填充 1,389 tok/s,两档持平 |
| N2 | 关闭思考模式以减少输出 token | 实测更慢(4.92 s vs 3.98 s),且 token 数更多(374 vs 266) | 隔离测试 |
| N3 | 通过 OLLAMA_GPU_OVERHEAD 预留显存给桌面 |
该变量在 0.30+ 已不被读取 | 源码审计 + 实测设置无效 |
| N4 | 通过 OLLAMA_MAX_VRAM 限制显存占用 |
该变量已从代码移除 | 源码审计 |
| N5 | 建 131,072 上下文模型以获得"更大能力" | 直接导致推理进程崩溃 | RemoteDisconnected + 服务端自我重启 |
| N6 | 关闭 HAGS / 调整页面文件以"释放显存" | HAGS 不影响 CUDA/VRAM(且关掉会断 DLSS);页面文件只影响系统内存交换 | 机制分析与既有实测 |
方法论提炼 :这 6 项的共同特征是它们都是"看起来最像优化"的操作 。
N1 是社区共识、N2 是直觉推断、N3/N4 是"文档里写着的变量"、N5 是"越大越好"、
N6 是"操作系统层面的大动作"。它们全部失败于同一个原因:没有先测量。
5.4 范式转变:成本方程
在参数旋钮的收益被判定为趋近于零之后,研究转向提示侧,并建立了三条成本方程。
5.4.1 方程一:轮次成本分解
T_turn = T_prefill(固定载荷) + T_prefill(对话增量) + T_generate(输出) + T_tool + T_queue
其中:
| 项 | 本机基线 | 是否可控 | 控制手段 |
|---|---|---|---|
T_prefill(固定载荷) |
21.1 s | ✅ 可压缩 | 削减提示宽度(第七、八章) |
T_prefill(对话增量) |
每轮 60--300 token | ✅ 可压缩 | 上下文压缩、控制历史长度 |
T_generate |
500 token ≈ 8.3 s | ⚠️ 受模型能力限制 | 控制输出长度 |
T_tool |
由工具本身决定 | ⚠️ 有限 | 减少工具往返次数 |
T_queue |
0--43 s 且随机 | ✅ 可消除 | 串行化调用(第七章) |
关键认识 :T_prefill(固定载荷) 与 T_queue 是两个可被工程手段消除的量级项 ,
而其余三项受物理或模型能力约束。因此优化的正确着力点在前两项。
5.4.2 方程二:固定载荷的构成方程
L_fixed = L_persona + L_skills_index + L_tools + L_memory + L_profile
= 4,664 + 14,086 + 10,075 + 905 + 638 (token)
≈ 33,600--36,109
该方程的偏导决定优化优先级 :
∂L/∂L_skills_index 最大(14,086 tok),其次是 ∂L/∂L_tools(10,075 tok)。
因此技能索引与工具 schema 是仅有的两个值得削减的项。
5.4.3 方程三:索引的宽度-信息量权衡
设索引中第 i 个技能行的成本为:
c_i = 4 + len(name_i) + 2 + min(len(desc_i), 57) + 1
则索引总成本 C = Σ c_i。若把描述全部去掉:
c_i' = 4 + len(name_i) + 1
C' / C ≈ 27 / 85 ≈ 32%
即去掉描述可把索引压到约三分之一 ,理论节省约 9,000--11,000 token 。
这一方程是第七章"降级为目录"方案的数学基础。
但方程三只回答了"能省多少",没有回答"省了会失去什么" ------
后者是能力层的问题,正是第七章的核心议题。
5.5 范式转变的表述
| 优化前范式 | 优化后范式 | |
|---|---|---|
| 关注对象 | 被调用的模型 | 调用方发送的内容 |
| 主要变量 | num_ctx / num_batch / 量化 / 卸载层数 |
提示宽度、工具集宽度、索引粒度、作用域 |
| 优化手段 | 调整参数 | 重构信息架构 |
| 收益量级 | ~0(实测趋近于零) | 21.1 s → 可压缩至数秒 |
| 风险 | 低(参数可回退) | 中(可能触及能力,需谨慎) |
| 验证方式 | 吞吐对比 | 提示规模(in=)+ 能力回归测试 |
转变的本质 :从"让引擎转得更快 "转向"少运一点货 "。
在本地推理场景中,货物(每轮重发的固定文本)的重量远大于引擎的效率差异。
5.6 本章小结
| 结论 | 内容 |
|---|---|
| 参数旋钮的可得收益 | 趋近于零(6 项候选全部剔除) |
| 真正的主导变量 | 每轮固定载荷的宽度 |
| 最大单项 | 技能索引(14,086 tok,占系统提示 60%) |
| 第二单项 | 工具 schema(10,075 tok) |
| 可被工程消除的两个量级项 | T_prefill(固定载荷) 与 T_queue |
| 三条成本方程 | 轮次分解 / 载荷构成 / 宽度-信息量权衡 |
| 由此确立的优化顺序 | 削减提示 → 保住缓存/驻留 → 路由分工 → 粒度设计 |
下一章 将处理一个与"削减"同等重要的变量:
即使提示再短,若模型被反复卸载,每轮仍要付 12.7 秒。
第六章 递进二:驻留经济学与缓存经济学
本章处理两个与"削减提示"正交、但同等重要的变量:模型是否驻留 与前缀是否命中缓存 。
二者都受一条不起眼的环境变量支配,而这条变量又被上层软件静默覆盖。
6.1 问题:一个每次停顿都要重付的常数
6.1.1 现象
在第三章已测得:冷加载一个 17 GB 的 27B 模型需 12.36--12.70 秒。
而本机的 Ollama 服务端环境为:
OLLAMA_KEEP_ALIVE: 2m0s
KEEP_ALIVE 的含义是模型在最后一次请求后保持驻留的时长 。
2 分钟意味着:只要使用者思考超过 2 分钟再发下一条消息,模型就会被卸载,
下一条消息要先付出约 12.7 秒的加载代价。
6.1.2 成本模型
T_first_token = (模型未驻留 ? T_cold_load : 0) + T_prefill(固定载荷) + T_queue
在本机基线下:12.7 s + 21.1 s + T_queue ≈ 33.8 s + T_queue。
这是一个"设计事故"级别的默认值 :交互式 agent 的使用节奏(思考、查阅、
离开工位)与 2 分钟的窗口严重不匹配。该默认值使"冷加载"从异常路径变成了常规路径。
6.1.3 为什么"改环境变量"没有生效
研究者按常规做法修改了 Windows 用户级环境变量:
powershell
[Environment]::SetEnvironmentVariable('OLLAMA_KEEP_ALIVE','30m','User')
[Environment]::SetEnvironmentVariable('OLLAMA_CONTEXT_LENGTH','32768','User')
随即重启 Ollama 并复查注册表:
OLLAMA_KEEP_ALIVE : 30m ← 注册表已改
OLLAMA_CONTEXT_LENGTH : 32768 ← 注册表已改
但服务端日志显示的实际生效值为:
OLLAMA_KEEP_ALIVE:2m0s
OLLAMA_CONTEXT_LENGTH:65536
注册表被忽略,且服务端的值是注册表中从未出现过的数字(65536)。
6.2 根因定位:上层软件的静默覆盖
6.2.1 排查路径
| 步 | 假设 | 检验 | 结果 |
|---|---|---|---|
| 1 | 环境变量未写入 | 复查注册表 HKCU\Environment |
❌ 假设错误,已写入 |
| 2 | 服务端未重启 | 强制结束 + 重启,再读日志 | ❌ 假设错误,新实例仍为 2m |
| 3 | 有其他环境源 | 检查机器级变量、启动器脚本、shell 导出 | ❌ 未发现 |
| 4 | 上层软件注入 | 检查 %LOCALAPPDATA%\Ollama\ 下的数据文件 |
✅ 命中 |
6.2.2 证据链
在 %LOCALAPPDATA%\Ollama\db.sqlite 中发现 settings 表:
sql
CREATE TABLE settings (
id, device_id, has_completed_first_run, expose, survey, browser, models,
agent, tools, working_dir, context_length, window_width, window_height, ...
);
查得的实际数据:
context_length = 65536
selected_model = 'qwen3.8'
think_enabled = 0
这与服务端日志中的 OLLAMA_CONTEXT_LENGTH:65536 完全吻合。
结论(L1 实测) :Ollama 0.33 的桌面版在启动 ollama serve 时,
会以自己的 SQLite 设置为准 注入环境变量,用户级注册表中的 OLLAMA_* 被静默忽略 。
context_length 明确来自该表;keep_alive 虽无对应列,但服务端固定呈现 2m,
说明该值由桌面版以硬编码或内部默认的方式注入。
6.2.3 这一发现的一般意义
| 层面 | 含义 |
|---|---|
| 运维 | "改了环境变量却没生效"的排查必须包含"上层软件是否覆盖"这一分支,且需直接读取上层的配置存储,而非只看自己写入的位置 |
| 方法论 | 验证配置是否生效,必须读"消费者"的日志,而不是"写入者"的界面 。注册表是写入者视角;server.log 的 server config 行才是消费者视角 |
| 工具 | 本研究据此新增了一条标准自检:grep 'server config' server.log,逐项核对 6 个关键变量的实际生效值 |
6.2.4 一个附带的稳定性问题
排查过程中研究者多次强制结束 Ollama 进程以应用环境变量,观察到两个副作用:
- 桌面版会自我重启 :强制结束后,
ollama app.exe会被重新拉起,
与被结束的动作形成竞争。多次操作后一度出现
3 个llama-server+ 2 个ollama+ 2 个ollama app并存的污染状态。 - 托盘版在非交互上下文中会退出 :从终端以分离方式启动
ollama app.exe时,
它在调用返回后即退出(推测与需要交互式桌面会话有关),
导致本地推理反复不可用。
修正措施:
- 清理为单一实例,并新增
verify.sh的第一步即为"进程唯一性检查"。 - 提供脱离式启动脚本(使用
DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB
三个标志组合以逃出父 shell 的 job object)。 - 明确纪律:诊断完成后不再反复结束该服务。
方法论含义 :排查过程中的"反复重启"本身就是一种破坏性操作。
它把一个确定的状态变为不确定的状态,且可能掩盖原始问题。
本研究将此列为负面教训(见第十一章第 7 项)。
6.3 解法:请求级保温
6.3.1 机制
既然服务端默认值难以持久提高,改从请求级 入手。
Ollama 的 /api/generate 接受 keep_alive 参数,且请求级优先于服务端默认值。
验证实验:
bash
curl --noproxy "*" -s -o /dev/null http://127.0.0.1:11434/api/generate \
-d '{"model":"qwen3.8-hermes","prompt":"","stream":false,
"keep_alive":"45m","options":{"num_predict":1}}'
随即查询驻留状态:
RESIDENT qwen3.8-hermes ctx=65536 UNTIL=2026-09-12T00:48:06
(当时时间为 00:03:07 ------ UNTIL 被推后了 45 分钟)
请求级 keep_alive 确实优先生效。
6.3.2 成本实测
| 场景 | 单次触碰耗时 |
|---|---|
| 模型已驻留(暖机) | 0.0057 s / 0.0035 s |
| 模型已卸载(冷机) | 27.4 s(此时触碰本身就包含了加载) |
暖机状态下,一次"保温触碰"的成本是毫秒级------因为它只评估 1 个 token。
等效性论证 :以每 60 秒触碰一次计算,
若因此避免了每小时 5 次冷加载,则节省 5 × 12.7 = 63.5 s,
而触碰成本 60 × 0.0057 ≈ 0.34 s。投入产出比约 190:1。
6.3.3 交付形态
keepwarm.ps1:支持 -Model / -IntervalSeconds / -Minutes / -Once,
每次触碰后输出当前上下文长度、显存占用与到期时间,便于持续观测。
亦可交由 Hermes 的定时任务执行,无需常驻窗口。
6.3.4 一条被否定的替代路径
研究者曾考虑"直接改用 headless ollama serve 以完全掌握环境变量"。
该路径技术上可行(已交付 start_ollama_serve.py),但未被采纳为默认方案 ,
理由:
| 优点 | 缺点 |
|---|---|
环境变量完全可控(可把 KEEP_ALIVE 设为 -1 或 30m) |
改变使用者的既有启动方式 |
| 无 GUI 依赖,稳定性更好 | 若托盘版后续启动会争抢端口 |
| 便于脚本化 | 会话结束后进程可能随父进程退出 |
折中 :把"保温触碰"作为默认方案(零侵入),
把 headless 启动脚本作为使用者自主选择的备选。
6.4 缓存经济学:前缀缓存的严格条件
6.4.1 收益已知,条件需明确
第四章已实测前缀缓存的收益:第二轮延迟从 55.1 s 降至 1.0 s ,
in= 仅增加 62 token。但缓存不是无条件生效的。
6.4.2 失效条件矩阵
| 操作 | 是否使前缀缓存失效 | 机制 |
|---|---|---|
| 继续对话(追加消息) | ❌ 不失效 | 前缀未变,仅增量计算 |
| 系统提示中任何字符变化 | ✅ 失效 | 前缀字节不匹配 |
| 加载技能(内容进入系统提示) | ✅ 失效 | 技能索引块变化 |
| 写入记忆 / 用户画像 | ✅ 失效 | 记忆块变化 |
| 切换工具集 | ✅ 失效 | 工具 schema 变化 |
| 切换模型标签(不同权重) | ✅ 失效 + 重载 | KV 缓存随模型销毁 |
| 模型被卸载(超时) | ✅ 失效 | KV 缓存随模型销毁 |
| 切换上下文长度参数 | ✅ 失效 | KV 形状改变 |
6.4.3 推论:纪律即性能
上表把"使用习惯"变成了可量化的性能参数:
| 习惯 | 代价 |
|---|---|
| 在一个会话内连续完成多步工作 | ✅ 每轮仅付增量(约 1--3 s) |
| 每步都新开一个进程 | ❌ 每步重付 21.1 s |
| 在会话中途加载技能/写记忆 | ❌ 该轮重付 21.1 s |
| 长时间停顿导致模型卸载 | ❌ 额外 12.7 s(除非保温) |
这解释了一个此前只被视为"设计洁癖"的构架约束 :
Hermes 的硬性不变量"绝不在会话中破坏提示缓存 (不改变历史上下文、工具集或系统提示)",
在远程模型上只影响成本,在本地模型上直接影响使用者的等待时间 ------
它值 21 秒/轮。
6.4.4 多步任务的最优形态
由上述两条经济学合并得出:
多步任务应交给同一个本地会话连续执行,而不是每步新起进程。
实测支持:同一会话内第 2 次 API 调用 in=11694(仅 +62 tok)、latency=1.0 s;
而新起进程的首次调用恒为 in≈11,600--32,000、latency=12--31 s。
这一结论在第七章被进一步细化 :当任务是"批量独立小任务"时,
单会话串行 vs 多进程并行的取舍需考虑本地串行队列的存在(第七章)。
6.5 本章小结
| 结论 | 数值 / 内容 | 可信度 |
|---|---|---|
| 冷加载代价 | 12.36--12.70 s | L1 |
服务端 KEEP_ALIVE 默认值 |
2m0s | L1 |
| 该值的来源 | Ollama 桌面版注入(db.sqlite 的 settings) |
L1 |
| 注册表环境变量是否生效 | 否(被静默覆盖) | L1 |
请求级 keep_alive 是否优先 |
是(UNTIL 推后 45 分钟) | L1 |
| 保温触碰成本(暖机) | 0.004--0.006 s | L1 |
| 保温的投入产出比 | 约 190:1 | L2 推算 |
| 前缀缓存收益 | 第二轮延迟 ÷55 | L1 |
| 缓存失效的触发条件 | 9 类操作,其中 6 类属于"使用习惯" | L2 |
| 由缓存导出的纪律 | 单会话连续执行 + 不改系统提示 + 保持驻留 | L2 |
本章确立的第二条优化主线 :提示削减之外,还有"驻留与缓存"这条被低估的战线。
两条主线的收益量级相当(各约 12--21 秒/轮)。
第七章 递进三:混合路由体系与工具集宽度的发现
本章回答 RQ4:远程模型与本地模型的正确分工是什么?判据是什么?
并记录一次重要的方向性自我纠错(工具集宽度)。
7.1 三层路由:被普遍忽略的 Tier-0
7.1.1 初始框定及其不完整
使用者给出的初始需求是两层结构:
远程 API 模型负责设计、规划、推理;本地模型只做任务执行。
这一框定正确且有价值,但它遗漏了成本最低的一层。
7.1.2 补全后的三层模型
| 层 | 执行者 | 适用任务 | 实测成本 |
|---|---|---|---|
| Tier 0 无模型 | 远程大脑直接写脚本 / 命令执行 | 能被完全指定的确定性任务 | 0 token,毫秒级 |
| Tier 1 本地手 | qwen3.8-hermes(单并发串行) |
需语言理解、不需权衡、量大/私密/重复 | 11.6k--16k 提示,9--12 s |
| Tier 2 远程大脑 | 远程主模型 | 规划、设计、歧义消解、高风险判断、验收 | in=218k--294k/轮 |
Tier-0 的存在是一个常被忽略的盲点。
"把 200 个 CSV 合并并报告行数"不需要任何模型------
远程大脑自己调用 terminal 即可完成。只有当任务无法用确定性命令表达时,
才值得动用模型(无论远程还是本地)。
7.1.3 路由判据(可执行的决策清单)
按顺序提问,命中即停:
- 能写成一条确定性的命令或脚本吗? → Tier 0(不调模型)
- 需要权衡、歧义消解、多步设计或高风险判断吗? → Tier 2 远程
- 需要语言理解但答案唯一,且量大 / 涉隐私 / 高度重复吗? → Tier 1 本地
- 要一次处理很多项、彼此独立吗? → Tier 2 并行 (
delegate_task,远程)
⚠️ 绝不要扇出到本地(原因见 7.3)
7.1.4 典型分派表
| 任务 | 层 | 机制 |
|---|---|---|
| 批量重命名 / 格式转换 / grep / 合并文件 | 0 | 远程大脑 → terminal |
| 专利权利要求布局设计、创造性判断 | 2 | 主循环 |
| 并行调研多篇文献做方案对比 | 2 | delegate_task(远程) |
| 把 200 个 PDF 抽成结构化 JSON | 1 | 本地执行者 |
| 长文压缩、标题生成、视觉识图 | 1 | auxiliary.* 辅助通道 |
| 每日定时抓取并清洗数据 | 1 | 定时任务(本地模型) |
| 远程 API 断线时继续工作 | 1 | 兜底链 |
7.2 工具集宽度:本阶段最重要的发现(含一次方向性纠错)
7.2.1 实验设计
控制变量 :同一模型(qwen3.8-hermes)、同一档案、同一工单
("运行 echo hello 并只回报输出")、同一时间窗口。
唯一自变量 :-t 指定的工具集。
测量口径 :以 agent.log 的 in= 为主(确定性),墙钟为辅。
7.2.2 结果
| 工具集 | 真实提示 in= |
墙钟 | tool_search 状态 |
|---|---|---|---|
| 完整(25 工具) | 32,216 tok | 31.1 s | 22 core + 5 deferred(~4,452 tok) |
-t terminal,file |
11,632 tok | 12.2 s | 5 core + 1 deferred(~380 tok) |
-t terminal |
10,375 tok | 11.2 s | --- |
净效果:提示 −64%(32,216 → 11,632 tok),延迟 −61%(31.1 → 12.2 s)。
7.2.3 机制分析:为什么降幅远超工具 schema 本身
工具 schema 从约 10,075 tok 降至约 380 tok,只能解释约 9,700 tok 的降幅;
而实际降幅为 20,584 tok。缺口约 10,900 tok,来源是:
源码级机制 (agent/system_prompt.py):
python
def _skills_prompt(agent: Any) -> str:
"""Skills index (empty without skills tools). Focus mode demotes non-coding
categories to names-only --- never hidden, every name stays visible."""
if not any(name in agent.valid_tool_names
for name in ['skills_list', 'skill_view', 'skill_manage']):
return ""
当工具集里不存在技能工具时,整个技能索引块返回空字符串。
本档案的技能索引为 14,086 tok,正是缺口的主体。
叠加机制 :tool_search 在工具集收窄后把工具块压到 380 tok
(日志:5 core/visible tools kept, 1 deferred (~380 tokens))。
合计 :14,086(技能索引)+ 9,700(工具 schema)≈ 23,800,
与实测 20,584 tok 同量级(差异来自分类标题与样板文字的连带消失)。
7.2.4 方向性纠错(本研究第 2 项错误)
研究者最初的结论 是:"给手最小工具集是本地执行优化的第一杠杆" ,
并据此建议本地执行者使用 -t terminal,file。
该结论被使用者当场质疑,并被源码证实为方向性错误:
被 -t terminal,file 移除的能力 |
后果 |
|---|---|
skills_list / skill_view / skill_manage |
完全无法使用任何技能(含 SOP、模板、references) |
web_search / web_extract |
无法联网查证(如 DOI/OA 核验) |
vision_analyze |
无法识图(专利附图、扫描件) |
patch / search_files |
只能整文件覆盖,不能精确修改 |
memory / session_search |
不持久化、不留检索痕迹 |
todo / delegate_task |
无任务规划、无自我分派 |
cronjob / skill_manage |
不能定时、不能固化经验 |
使用者的表述准确地指出了问题:
"手不是要做机械手,而是具有职能的手。"
研究者的自我批评 :该建议把"速度 "当成了唯一目标函数,
而忽略了优化对象的角色定义 。一个没有技能能力、没有联网能力、没有识图能力的执行者,
在本研究的使用场景中不是"更快的执行者",而是"不能完成工作的执行者"。
该结论已被下调并修正 (见第九章的融合方案)。
此错误的完整记录见第十一章第 2 项。
7.3 串行队列:一个被结构性忽略的约束
7.3.1 现象
在测量工具集宽度时,研究者遭遇了自相矛盾的墙钟数据(详见第二章 2.3.1)。
深挖后发现完全相同的一个请求呈现出极端的时间差异:
| 场景 | 该请求的延迟 |
|---|---|
| 无并发干扰 | 12.2 s |
| 有其它本地请求在跑 | 55.1 s |
劣化 4.5 倍,且随机不可预测。
7.3.2 根因
服务端启动参数中明确标注:
-np 1
对应环境变量 OLLAMA_NUM_PARALLEL=1------单并发槽 。
任何并发请求都会进入队列,其延迟变为"服务时间 + 排队时间"。
7.3.3 实验证据(补强)
研究者安排了两个并发任务(A 与 B)以验证队列行为:
[lock] 本地执行者忙,排队中(NUM_PARALLEL=1,串行执行)...
A: WALL = 11 s
B: WALL = 9 s(等待 A 完成后执行)
日志中出现了预期的排队提示,且 B 在 A 之后才执行。
7.3.4 推论与工程设计
| 推论 | 工程措施 |
|---|---|
| 不要并发扇出到本地(扇出 = 排队,不会加速) | 并行任务留在远程(delegate_task) |
| 本地调用必须串行化 | local_exec.sh 内建 mkdir 原子锁 + 死锁回收 + 排队提示 |
| 本地辅助通道会与本地执行者抢槽 | 大批本地执行前可临时把辅助通道收回远程 |
| 延迟类结论必须在无并发条件下测量 | 以 in= 为主口径 |
实测到的抢槽证据 :辅助通道的标题生成出现
Auxiliary title_generation: transient transport error (attempt 1/2); retrying same provider after 1.0s before fallback: Request timed out。
7.4 反向验证:一个"看起来应该做"但不该做的配置
7.4.1 假设
既然本地模型可用,且子智能体(delegate_task)适合做执行------
那么把 delegation.provider 指向本地模型,岂不就实现了"远程规划 + 本地执行"?
7.4.2 验证与否定
源码 tools/delegate_tool.py:557 明确说明:
"Children inherit the parent model unless pinned via
delegation.provider/delegation.modelin config.yaml."
即该配置确实能把子智能体路由到本地。但进一步的源码检查发现关键限制:
python
child_toolsets, child_disabled_toolsets = _resolve_child_toolsets(parent_agent, toolsets, effective_role)
_resolve_child_toolsets() 不接受按调用方指定工具集的参数 ------子智能体继承父级的完整工具集。
因此 :一个本地子智能体携带的是完整 25 工具 的提示,
实测为 32,216 tok / 约 31 s,且因单并发而排队。
结论:不要将 delegation.provider 指向本地模型。
- 保留
delegation.*指向远程强模型,用于并行研究类任务; - 本地执行 改由"生成独立进程 + 指定最小职能工具集"的路径承担
(即local_exec.sh,它通过-t绕过了"子智能体无法指定工具集"的限制)。
方法论含义 :"配置项存在"不等于"该配置是最优解"。
该配置项在功能上可用,但在成本结构上劣于替代方案。
7.5 成本证据:为什么这套分工值得做
hermes insights --days 1(官方 CLI 口径):
| 模型 | 会话数 | token 数 |
|---|---|---|
远程(DeepSeek-V4.1-Flash) |
1 | 13,863,060 |
本地(qwen3.8-hermes / qwen3.8-opt-64k) |
5 | 208,757 |
比例约 66 : 1。
远程单轮的输入规模亦被记录:
API call #21: model=DeepSeek-V4.1-Flash in=283723 out=1719 total=285442 latency=10.3s cache=283
API call #1: model=DeepSeek-V4.1-Flash in=218355 out=1995 ... cache=217344/218355 (100%)
观察 5 :远程模型的 in= 达到 20 万--29 万 token/轮 ,
且缓存命中率 100%(cache=217344/218355)。
缓存显著降低了成本 ,但上下文占用 依然存在------
每一轮都要在 20 万 token 的上下文中定位。
这是一条对"是否值得把负载移到本地"的强证据 :
把机械性、高体积的任务移到本地,可同时降低远程费用与远程上下文压力。
7.6 辅助通道的本地化(11 项)
7.6.1 决策依据
依据"能力---频次---判断强度"三维打分,把 13 个辅助通道分类:
| 通道 | 判断强度 | 频次 | 决策 |
|---|---|---|---|
| vision | 低 | 中 | ✅ 本地 |
| compression | 低 | 高 | ✅ 本地 |
| skills_hub | 低 | 中 | ✅ 本地 |
| mcp | 低 | 中 | ✅ 本地 |
| title_generation | 极低 | 高 | ✅ 本地 |
| tts_audio_tags | 极低 | 低 | ✅ 本地 |
| triage_specifier | 低 | 中 | ✅ 本地 |
| profile_describer | 极低 | 低 | ✅ 本地 |
| curator | 低 | 低 | ✅ 本地 |
| monitor | 低 | 中 | ✅ 本地 |
| web_extract | 低 | 中 | ✅ 本地 |
| approval | 高(安全相关) | 低 | ❌ 保留远程 |
| kanban_decomposer | 高(分解质量) | 低 | ❌ 保留远程 |
7.6.2 端到端验证(活体探针)
调用视觉工具分析本机生成的测试图,并以服务端日志确证路由:
| 证据 | 内容 |
|---|---|
| 功能返回 | "A red circle and a blue square are shown on a white background." ✅ 与图像一致 |
| 服务端日志 | `[GIN] 2026/09/12 - 00:06:37 |
| 推理统计 | total time = 6471.75 ms / 381 tokens,draft acceptance = 0.52163 |
确证:该次视觉调用确实由本地模型完成,而非远程。
7.6.3 已知代价(如实标注)
本地辅助通道依赖 Ollama 存活。测试期间 Ollama 曾意外退出,
同一次视觉调用返回 Connection error(openai.APIConnectionError,见 logs/agent.log),
服务恢复后即成功。
该事件最初被误判为"配置错误",属于本研究第 7 项错误(见第十一章)。
缓解措施:为关键辅助通道配置远程兜底链:
yaml
auxiliary:
vision:
provider: ollama-launch
model: qwen3.8-hermes
base_url: http://127.0.0.1:11434/v1
fallback_chain:
- provider: blsc
model: Qwen3.8-Flash
base_url: https://llmapi.blsc.cn
值得注意的是:hermes config set 会对 fallback_chain 报
"not a recognized config key"(CLI 白名单陈旧),但实测已正确落盘为 YAML 列表 ,
且源码 if isinstance(chain, list) 的校验通过。
7.7 兜底链:把本地作为保险
bash
hermes config set fallback_model '{"provider":"ollama-launch","model":"qwen3.8-hermes"}'
hermes fallback list 实测输出:
Primary: DeepSeek-V4-Flash-0731 (via blsc)
Fallback chain (1 entry):
1. qwen3.8-hermes (via ollama-launch)
Tried in order when the primary fails (rate-limit, 5xx, connection errors).
为什么需要它 :历史请求转储显示,该档案的会话确实曾因远程 401 中断:
request_dump_20260902_163255_41e6bb_*.json
error.type = Unauthorized (401)
message = "The API key format is incorrect"
配置兜底链后,此类中断不再导致会话终止,而是退回本地继续工作。
7.8 工程化:本地执行者封装
local_exec.sh 的核心设计(各要素均来自本章的实测结论):
| 要素 | 依据 |
|---|---|
工具集默认 terminal,file,skills |
保留执行 + 技能职能(第 7.2.4 节纠错后) |
HERMES_SKILLS_DEMOTE_ALL=1 默认 |
第八章的整表降级方案 |
mkdir 原子锁 + 排队提示 |
第 7.3 节的串行队列约束 |
自动回贴 in= / latency= |
第 2.3 节的口径纪律 |
--dry 干跑 |
降低误发工单的风险 |
| 超时与退出码分级 | 0 成功 / 1 参数错 / 2 锁超时 / 3 调用失败 |
工单协议(因手在"仅目录"模式下没有技能描述):
目标:<一句话,可验证>
输入:<绝对路径 / 具体数据>
约束:<编码、格式、不能碰什么>
输出:<精确格式,便于父会话直接消费>
验收:<如何自检>
完成即止:不要探索、不要额外优化、不要询问
验收实测:
$ bash local_exec.sh "运行 echo router-ok 并只回报输出"
| router-ok
WALL = 32 s exit=0
API call #1: in=11633 out=47 latency=24.7s
API call #2: in=11697 out=25 latency= 1.3s
7.9 本章小结
| 结论 | 内容 |
|---|---|
| 路由层数 | 三层(含被忽略的 Tier-0"不调模型") |
| 工具集宽度的收益 | 提示 −64%,延迟 −61%(但也曾因此犯方向性错误) |
| 本地并发性质 | 单并发串行队列,扇出只是排队 |
delegation 指向本地 |
❌ 不应做(子智能体继承完整工具集) |
| 成本证据 | 远程:本地 token = 66:1 |
| 辅助通道 | 11 项本地化,2 项保留远程;已端到端验证 |
| 兜底链 | 已配置并验证;有历史 401 中断作为必要性证据 |
| 工程化产出 | local_exec.sh(串行锁 + 审计 + 工单协议) |
回到"提示削减"这条主线,处理一个更深的问题:
当削减触碰到能力边界时,应当选何种粒度。
第八章 递进四:动态加载、粒度错配与伪两难的消解
本章回答 RQ5:削减提示成本的"粒度"与"作用域"如何选择?
这是本报告在方法学上最有价值的章节。
8.1 问题的提出:当削减触碰能力边界
第七章确立了一个事实:收窄工具集可省 20,584 token,但会静默移除能力。
使用者明确否定了这条路线,并提出替代方向:
"不要通过剪枝技能来优化,而是通过优化 skills 的动态加载调用来达到
最小投入最大效果的目的。"
这一指示把问题重新定位于:如何在"模型不知道有某技能"与"模型不能做某事"之间做出区分?
8.2 能力的三分:可发现性 / 可加载性 / 可用性
本研究首先建立了一个三分模型,用以精确描述"能力损失"的不同形态:
| 层次 | 含义 | 失效表现 |
|---|---|---|
| 可发现性(discoverability) | 模型在系统提示中看到该能力的存在 | 模型不会主动想到使用它 |
| 可加载性(loadability) | 模型可以按名称取得该能力的完整内容 | 即使知道名字也无法调用 |
| 可用性(usability) | 该能力所依赖的工具/环境确实存在 | 加载成功但执行失败 |
这一三分模型的价值在于 :它把"削减"这件事从二元(保留 / 移除)
变为可选择在哪个层次上削减:
| 削减手段 | 可发现性 | 可加载性 | 可用性 |
|---|---|---|---|
| 保留完整描述(现状) | ✅ | ✅ | ✅ |
| 降级为仅目录(本章方案) | ✅(名字保留) | ✅ | ✅ |
| 移除技能工具(第七章的剪枝) | ❌ | ❌ | ❌ |
禁用技能(skills.disabled) |
❌ | ❌(skill_view 直接报错) |
❌ |
| 移除某工具集 | 视情况 | 视情况 | ❌ |
关键发现 :"降级为仅目录"是唯一能在保持可加载性与可用性的同时降低成本的层级。
这一发现并非本研究原创------Hermes 自身已实现该机制,且其源码注释即为此写的。
8.3 Hermes 既有的渐进式披露机制
8.3.1 三处源码证据
证据一 :技能工具的自我定义(tools/skills_tool.py:2)
python
"""Skills Tool --- list and view skill documents (progressive disclosure)."""
证据二 :skills_list 支持类别过滤(skills_tool.py:600)
python
SKILLS_LIST_SCHEMA = {
"name": "skills_list",
"description": "List available skills (name + description). Use skill_view(name) to load full content.",
"parameters": {"properties": {"category": {"type": "string",
"description": "Optional category filter to narrow results"}}}}
证据三 :索引行截断至 57 字符描述(agent/curator.py:275 的说明)
"are truncated to 57 chars in the system prompt skill index --- keep the "
结论 :Hermes 的系统提示索引是一份"菜单" ,而不是能力本身。
真正的能力在 skill_view(name) 与 skills_list(category=...) 中按需加载。
"动态加载"并非需要新建的机制,而是既有的设计。
8.3.2 Hermes 作者已经踩过同一个坑
agent/coding_context.py 中有一段注释,直接确认了本研究的结论方向:
python
def compact_skill_categories(self) -> frozenset[str]:
"""Skill categories to demote to names-only in the skill index. Gated on ``focus``
like the toolset collapse (index changes under ``auto`` proved too surprising).
Demoted, never hidden: pruning caused silent capability loss."""
以及 _skills_prompt 的文档字符串:
"Focus mode demotes non-coding categories to names-only --- never hidden,
every name stays visible."
"Demoted, never hidden: pruning caused silent capability loss."
这句话是本研究最重要的外部印证:Hermes 的维护者选择"降级而非裁剪",
并且明确记录了裁剪的代价是"静默的能力损失"------与使用者提出的关切完全一致。
8.4 降级机制的工作原理与实测
8.4.1 渲染分支
agent/prompt_builder.py 中的渲染逻辑:
python
# Demoted categories collapse to one names-only line. NEVER drop entries ---
# agent-created skills are the model's project memory and it won't rediscover
# them via skills_list. Nested categories follow their parent.
demoted = frozenset(cat for cat in skills_by_category
if cat.split("/", 1)[0] in (compact_categories or frozenset()))
...
for category in sorted(skills_by_category):
entries = skills_by_category[category]
if category in demoted:
index_lines.append(
f" {category} [names only]: {', '.join(sorted({n for n, _ in entries}))}")
continue
三个要点:
- 降级类别的全部技能名 被拼成一行列出------没有任何条目被丢弃。
- 匹配是前缀式 的:
cat.split("/", 1)[0],因此写scientific-skills
即可覆盖scientific-skills/skills。 - 匹配单位是类别------这是本章后续问题的根源。
8.4.2 内置白名单及其"打偏"
内置的降级白名单为:
python
_NON_CODING_SKILL_CATEGORIES = (
"apple", "communication", "cooking", "creative", "email", "finance", "gaming",
"gifs", "health", "media", "music", "note-taking", "productivity", "shopping",
"smart-home", "social-media", "travel", "yuanbao",
)
该名单按"是否与编程相关"分类 (因为它服务于 coding 姿态)。
在本档案上实测其效果:
| 项 | 数值 |
|---|---|
| 命中技能数 | 54 |
| 索引成本变化 | 1,003 → 221 tok |
| 节省 | 782 tok ≈ 0.5 s/轮 |
结论:内置机制打偏了。 本档案索引成本的两个最大来源
(scientific-skills 3,176 tok、scienceclaw-migrated 2,687 tok)
不在白名单内,因此内置机制只值 0.5 s/轮,占可得收益的 8%。
8.4.3 四份候选名单的实测收益
研究者据此提出四份候选降级名单(实测于隔离渲染环境):
| 方案 | 内容 | 省 tok | 省 s/轮 | 省% | 名字保留 |
|---|---|---|---|---|---|
| ① | scientific-skills、scienceclaw-migrated |
4,474 | 2.6 | 31% | 100% |
| ② | ① + bmad-migrated、workbuddy-migrated |
5,376 | 3.1 | 37% | 100% |
| ③ | ② + creative/media/email/productivity/gaming/... |
7,406 | 4.3 | 52% | 100% |
| ④ | 除 IP 核心外全部(含 research/software-development/mlops) |
10,797 | 6.3 | 75% | 100% |
所有四份方案的名字保留率均为 100% ------这是降级机制的性质保证。
skills_list 可见技能数与 disabled 计数均未改变(实测 disabled = 0)。
8.5 伪两难的识别
8.5.1 表面的权衡
四份名单呈现出一个清晰的权衡曲线:
省得越多 ←──────────────────────────────→ 误伤越大
①2.6s ②3.1s ③4.3s ④6.3s
无 无 有 严重
而"有误伤"的具体内容经实测为:
| 类别 | 其中被误伤的关键能力 |
|---|---|
productivity |
docx、pdf、xlsx(使用者的交付格式) |
creative |
baoyu-article-illustrator、baoyu-infographic、claude-design(插图线) |
research |
academic-manuscript-audit、grounded-citations(论文线) |
software-development |
skill-orchestration-strategy、subagent-driven-development |
即:用户无法既拿到高收益又避免误伤。 这看起来是一个必须做出的取舍。
8.5.2 关键洞察:该权衡源于粒度错配
研究者对上述权衡做了归因分析,发现它并非问题固有,而是"匹配粒度"造成的:
| 环节 | 现状 | 后果 |
|---|---|---|
| 匹配单位 | 类别 (scientific-skills 是原子的) |
无法在类别内部区分"关键技能"与"无关技能" |
| 作用域 | 档案级(配置写在 profile 的 config.yaml) | 无法让"主会话"与"本地手"取不同值 |
| 类别内部同质性 | 不成立 (productivity 同时含 docx 与 airtable) |
任何类别级决策都会误伤 |
因此,这个"收益 vs 误伤"的权衡是一个伪两难(false dilemma) :
它不是由"成本与能力的内在矛盾"产生,而是由实现粒度的选择产生。
8.6 第三条路径:把粒度从"按类别"改为"按进程"
8.6.1 设计
既然问题出在粒度,就在粒度上求解。研究者提出的替代方案是:
不挑类别。整表降级为仅目录,用"作用域"决定谁生效。
8.6.2 实现
对既有补丁(8.4.3 节的配置驱动能力)扩展两个作用域开关:
python
# Whole-index demotion (names-only for EVERY category). Two scopes:
# skills.demote_all: true -> this profile always
# HERMES_SKILLS_DEMOTE_ALL=1 -> this PROCESS only
_all = _skills_cfg.get("demote_all") or os.getenv("HERMES_SKILLS_DEMOTE_ALL")
if str(_all or "").strip().lower() in {"1", "true", "yes", "all"}:
from agent.skill_utils import get_skills_dir
base = base | frozenset(p.name for p in get_skills_dir().iterdir() if p.is_dir())
关键点是"进程级作用域" :同一个档案下,主会话(无该环境变量)
保持完整描述,而由 local_exec.sh 生成的本地执行者(带该环境变量)
得到仅目录的菜单。
这不需要:新建档案、挑选类别、禁用任何技能、删除任何文件。
8.6.3 实测收益
| 方案 | bytes | tok | 省 tok | 省 s/轮 | 省% | 名字保留 |
|---|---|---|---|---|---|---|
| ② 按类别挑 | 35,970 | 8,992 | 5,360 | 3.1 | 37% | 100% |
| ③ 按类别挑 | 27,849 | 6,962 | 7,390 | 4.3 | 51% | 100% |
| ④ 按类别挑(最激进) | --- | --- | 10,797 | 6.3 | 75% | 100% |
| ⑤ 整表降级 | 13,122 | 3,280 | 11,072 | 6.5 | 77% | 100% |
整表降级比最激进的 ④ 还多省(6.5 vs 6.3 s/轮),且零类别误伤。
8.6.4 机制验证
| 检验 | 结果 |
|---|---|
py_compile |
✅ 通过 |
| 惰性(无配置、无 env) | ✅ 返回 ∅,渲染与打补丁前一致 |
skills.demote_all: true |
✅ 返回 47 个类别 |
HERMES_SKILLS_DEMOTE_ALL=1 |
✅ 返回 47 个类别(进程级作用域可用) |
| 整表降级后的名字保留 | 563 个名字 vs 基线 560,丢失 0 |
[names only] 行数 |
53 |
skills_list 可见数 |
560(未减少) |
disabled 计数 |
0 |
8.6.5 端到端实测(决定性证据)
实验设计:同一档案、同一模型、同一工单,仅改变执行者的菜单模式。
| 配置 | skills 工具 |
菜单 | in= |
|---|---|---|---|
| A. 现状 | ✓ | 完整描述 | 26,087 |
| B. 整表降级 | ✓ | 仅目录(env) | 15,907(−39%) |
| C. 剪枝参照 | ✗ | 无 | 11,632 |
并从 agent.log 抽取执行者的实际调用记录:
API call #1: in=15907 out=53 latency=31.2s
API call #2: in=15969 out=30 latency=31.6s
能力保留的实证 :以 local_exec.sh 派发工单
"用 skills_list 列出 intellectual-property 类别的技能名,只回报数量",
执行者在仅目录模式 下成功调用 skills_list 并返回 2(该类别技能数的正确值),
exit=0,in=15,914。
结论:
整表降级方案在保留全部技能职能(
skills_list与skill_view均可用、
disabled = 0、名字 100% 保留)的前提下,把执行者的提示从 26,087 降至 15,907 tok(−39%),
相当于拿到了"剪枝路线"收益(A→C 的 14,455 tok)的 70%。
而"保留职能"的价格仅为 4,275 tok ≈ 2.5 s。
8.7 "伪两难"的消解结果
回顾本章开头的权衡曲线,融合方案消解了它:
| 方案 | 收益 | 误伤 | 是否需要挑类别 |
|---|---|---|---|
| 按类别挑(①--④) | 2.6--6.3 s | 有 / 无(取决于档位) | 需要 |
| 按进程定(⑤) | 6.5 s | 零 | 不需要 |
即:收益最高的方案,也是误伤为零的方案,且决策成本最低。
这直接证明了前述权衡是人为的。
8.8 实践配置建议
| 角色 | 菜单模式 | 理由 |
|---|---|---|
| 主会话(大脑) | 完整描述 | 规划者需要描述来做技能路由 |
| 本地执行者(手) | 仅目录(local_exec.sh 默认开启) |
执行者只需知道名字 + 按需加载 |
| 需要描述的特殊工单 | --full-menu 临时关闭降级 |
保留逃逸舱口 |
设计律:
大脑拿菜单,手拿目录。
8.9 本章小结
| 结论 | 内容 |
|---|---|
| 能力三分模型 | 可发现性 / 可加载性 / 可用性,三者可独立削减 |
| 最优削减层级 | 仅降级描述(唯一保持后两层的层级) |
| 外部印证 | Hermes 源码注释:"pruning caused silent capability loss" |
| 内置白名单 | 有效但打偏,只值 0.5 s/轮(占可得收益 8%) |
| 四份类别名单 | 2.6 / 3.1 / 4.3 / 6.3 s,均 100% 保留名字 |
| 伪两难的成因 | 粒度错配(按类别匹配 + 档案级作用域) |
| 第三条路径 | 按进程定菜单粒度,6.5 s/轮,零误伤 |
| 端到端效果 | 26,087 → 15,907 tok(−39%),能力 100% 保留 |
| 设计律 | 大脑拿菜单,手拿目录 |
第九章 递进五:决策融合与深层逻辑提炼
本章回答两个问题的合并形式:两个原本独立的选择能否被融合?其背后的深层逻辑是什么?
9.1 原初的两个独立选择
在第八章之后,使用者面临两个看似独立的决定:
| 选择 | 选项 |
|---|---|
| 选择一:补丁去留 | 留下补丁 / 回滚补丁 |
| 选择二:降级名单 | ① / ② / ③ / ④ / 不启用 |
表面上这是一个 2 × 5 = 10 种组合的决策空间。
9.2 选择一的优劣分析
| 留下补丁 | 回滚补丁 | |
|---|---|---|
| 优势 | ① 获得"按进程/按档案定菜单宽度"的能力 ② 惰性------不设配置/不设 env 时行为与打补丁前逐字节一致 ③ 三级回滚,随时可撤 ④ diff 与重打脚本已固化 | ① 零源码改动 ② 零 hermes update 风险 ③ 不触碰上游文件 |
| 劣势 | ① hermes update 会 git stash 掉它 → 优化静默停止生效 ② 上游修改该文件时重打可能冲突 |
① 失去第三条路径 → 只能回到①②③④的误伤两难 ② 无法让"主会话与手共用档案却各取粒度" |
关键认识 :补丁本身不省时间,它只是一个"能力开关"。
因此"忘记重新打补丁"的代价不是"系统变坏",而是"优化暂时不生效"。
这是一种无损劣化 (lossless degradation),与"有损优化"(先有效果、后失能力)
在风险性质上完全不同。
裁定:留下补丁。
9.3 选择二的优劣分析
| 选项 | 收益 | 误伤 | 决策成本 |
|---|---|---|---|
| ① 两个大包 | 2.6 s(38%) | 无 | 低 |
| ② 4 个迁移包 | 3.1 s(49%) | 无 | 低 |
| ③ +无关生活类 | 4.3 s(51%) | ⚠️ 有 | 中(须逐类核对) |
| ④ 除核心外全部 | 6.3 s(75%) | ❌ 严重 | 高(须逐类核对) |
| 不启用 | 0 | 无 | 零 |
关键认识 :这五个选项全部处在同一条权衡曲线 上------
"省多少"与"误伤哪个类别"成反比。而第八章已证明该曲线由实现粒度造成,不是问题固有。
9.4 融合设计
9.4.1 融合矩阵
| 维度 | 融合做法 | 消掉了什么劣势 |
|---|---|---|
| 补丁 | 留下,但只当能力开关,不指望它省时间 | 消掉选择一的劣势①:忘重打 = 退回现状(无损),而非系统变坏 |
| 菜单宽度 | 按进程定(env),不按类别挑 | 消掉①②③④的"收益 vs 误伤"权衡 |
| 主会话 | 完整描述(不动) | 保住大脑做技能路由所需的描述信息 |
| 本地手 | 仅目录 + 保留 skills 工具 |
省 39% 提示,手仍能按需加载技能 |
| 类别白名单 | 留空 | 消掉"选哪份名单"这个决定本身 |
| 升级维护 | 升级后执行 reapply_patch.sh --check |
把"静默失效"变为"一条命令自检" |
9.4.2 融合后的决策空间坍缩
融合把 2 × 5 = 10 种组合坍缩为一个二值问题:
要不要让"本地手"使用仅目录模式?
(
local_exec.sh已默认开启,--full-menu可临时关闭)
| 选择 | 后果 |
|---|---|
| 要 | 手的提示 −39%;技能职能全保留;零类别误伤;零剪枝 |
| 不要 | 一切照旧;补丁保持惰性;无任何副作用 |
| 主会话 | 始终 保持完整描述,不受该选择影响 |
9.4.3 消融验证表
| 原劣势 | 是否被消融 | 消融机制 |
|---|---|---|
| 选择一①:update 会 stash | ✅ 降级为无损 | 惰性补丁 + 固化 diff + 重打脚本 |
| 选择一②:上游冲突 | ⚠️ 部分(预检 + 手工 11 行) | git apply --check |
| 选择二:误伤关键类别 | ✅ 消除 | 不挑类别,整表降级 |
| 选择二:决策成本高 | ✅ 消除 | 无需选类别 |
| 选择二:收益上限 | ✅ 反而提高 | 6.5 s > ④ 的 6.3 s |
| 选择二:能力损失 | ✅ 无 | 名字 100% 保留、disabled=0、工具全可用 |
结论 :融合方案不仅叠加了两者的优势,而且收益高于原有任何单一选项 。
这在决策分析中是一个强信号:当"融合"严格优于所有"择一"选项时,
通常说明原始选项集合本身建立在错误的维度的上。
9.5 深层逻辑提炼(本报告的理论产出)
本节把上述五个递进中所隐含的规律抽象为五条可迁移的元规律。
M1 成本引力在调用方
表述 :在"编排器 + 推理服务 + 模型"这类三层架构中,
决定端到端成本的主要变量不是被调用方的参数,而是调用方每轮发送的固定载荷。
证据链:
- 参数旋钮的可得收益趋近于零(6 项候选全部剔除)
- 固定载荷 36,109 tok ≈ 21.1 s/轮,占单轮成本的主体
- 收窄工具集(调用方侧变更)使提示降低 64%,远大于任何参数调优
可迁移形式 :面对"某组件很慢"的报障,先问
"每一轮有多少字节是固定重发的?"而不是"该组件的参数是否最优?"
M2 粒度错配制造伪两难
表述 :当决策者面对"收益与代价必须权衡"的两难时,
应首先检验该权衡是否由实现粒度的选择造成 。
若粒度可选,则两难可能是人为的。
证据链:
- 类别级降级 → 四份名单构成"收益 vs 误伤"曲线
- 进程级降级 → 曲线消失,且取得更高收益
- 误伤的根源:类别内部不同质 (
productivity同时含docx与airtable)
可迁移形式 :遇到"鱼与熊掌"式取舍时,追问
"这个取舍的单位是什么?能否换一个单位重新表述?"
M3 可发现性 / 可加载性 / 可用性三分
表述 :"能力损失"不是单一维度。"模型不知道"与"模型不能做"是两种不同的失效 ,
成本与后果都不同。
证据链:
- 移除技能工具 → 三层全失(
_skills_prompt返回空) - 降级为仅目录 → 仅丢描述,名字、加载、执行全保留
- 实测
disabled = 0、skills_list可见数不变、skill_view可用
可迁移形式 :设计削减方案时,先问
"我要削掉的是'它被想起的概率',还是'它被调用的能力'?"
M4 无损劣化优于有损优化
表述 :在变更管理中,"最坏情况是退回现状"的变更 ,
在风险性质上优于"最坏情况是失去能力"的优化------即使后者的期望收益更高。
证据链:
- 惰性补丁:未启用时行为与变更前一致(实测返回
∅) - 补丁在验证阶段被发现存在缺陷,但因处于惰性状态从未对使用者产生实际影响
- 三级回滚均为一命令
可迁移形式 :设计变更时强制回答
"如果这个变更失效(被覆盖、忘记维护、上游冲突),系统会怎样?
是退回现状,还是带着半启用状态运行?"
M5 口径决定结论
表述 :在效能研究中,若测量口径未受控,噪声会生成与事实相反的结论 ,
且这类错误不会自我暴露。
证据链:
- 同一配置的墙钟序列:17 / 20 / 32 / 61 s(3.6 倍差异)
- 若据此下结论,会得出"配置 B 优于配置 A"的错误判断
- 改用
in=后,同日同配置的数值完全确定
可迁移形式 :在任何对比实验前,先定义
"哪个量是被测对象本身决定的(确定性),哪个量会被环境调制(随机性)? "
后者不得作为结论依据。
9.6 五条元规律之间的关系
这五条并非并列,而存在依赖结构:
M5 口径决定结论
↓(前提:只有可信的口径才能暴露真实成本结构)
M1 成本引力在调用方
↓(发现成本在提示而非模型)
M3 三层次三分
↓(区分"削减成本"与"损失能力")
M2 粒度错配制造伪两难
↓(识别权衡是人为的)
M4 无损劣化优于有损优化
(在可行的最优方案上施加风险管理)
解释 :M5 是前提 (没有可信口径,后续全部失效);
M1 是发现 (把注意力引到正确的对象);
M3 是分辨 (区分削减的不同后果);
M2 是重构 (找到更优的决策空间);
M4 是落地(在新方案上管理风险)。
9.7 本章小结
| 结论 | 内容 |
|---|---|
| 选择一的裁定 | 留下补丁(它是能力开关,不是省时手段) |
| 选择二的本质 | 五个选项同处一条人为的权衡曲线 |
| 融合结果 | 决策空间由 2×5 坍缩为 1 个二值问题 |
| 融合的异常性质 | 严格优于所有单一选项(收益更高、代价为零) |
| 该异常的含义 | 原始选项集合建立在错误的维度上 |
| 五条元规律 | 成本引力在调用方 / 粒度错配制造伪两难 / 三层次三分 / 无损劣化优于有损优化 / 口径决定结论 |
| 元规律的依赖结构 | M5 → M1 → M3 → M2 → M4 |
第十章 失败与自我纠错记录
本章记录研究过程中的 9 处错误 。本报告主张:
在效能优化类研究中,纠错记录的密度是可靠性的直接指标。
理由是结构性的:这类研究的结论由测量驱动,
而测量极易被单位混淆 、非受控并发 、平台默认值 与工具版本漂移 污染。
一名研究者的结论若从未被推翻过,通常不是因为他正确,
而是因为他没有做足以暴露错误的检验。
10.1 错误清单总览
| # | 错误 | 性质 | 发现机制 | 是否影响过使用者 |
|---|---|---|---|---|
| 1 | 把两个迁移包当作"纯负担" | 判断错误(来源≠价值) | 逐技能清单核对 | 否(未实施) |
| 2 | 推荐"最小工具集"为第一杠杆 | 方向性错误 | 使用者质疑 + 源码核实 | 否(未实施) |
| 3 | 降级收益报 3.1 s(实为 3.4 s) | 数值错误(早期快照) | 全量 prompt-size --json 重算 |
否(低估,偏保守) |
| 4 | demote_savings.py 单位错误 |
计算错误 | 人工复核折算公式 | 否(已修正重算) |
| 5 | 自建补丁的 config 未透传 |
代码缺陷 | 隔离验证脚本报 FAIL | 否(惰性状态) |
| 6 | 用墙钟对比性能 | 口径错误 | 同配置出现 3.6 倍自相矛盾 | 否(结论被推翻) |
| 7 | 把"服务已挂"误判为"配置错误" | 归因错误 | 直接探测端口连通性 | 否(已澄清) |
| 8 | 把"输出为空"误判为"视觉缺失" | 归因错误 | 提高 max_tokens 后复测 |
否(已修正) |
| 9 | 未估算容量即试 128k,打挂服务 | 破坏性试错 | 现象本身即证据 | 是(服务短暂中断) |
关于"是否影响过使用者"这一列 :9 项中有 8 项未对使用者产生实际影响 ,
其中 3 项是因为变更处于惰性状态 (错误被隔离在未启用的代码路径中),
5 项是因为错误被在实施前发现。第 9 项是唯一造成实际影响的错误。
这一统计本身是本报告方法论的成果 :
惰性变更(M4)与隔离验证(2.4.4 节)两项纪律,直接阻断了 3 项错误的影响路径。
10.2 逐项详述
错误 1:把两个迁移包当作"纯负担"
内容 :在识别出技能索引占系统提示 60% 之后,研究者的第一反应是
"砍掉 scientific-skills 与 scienceclaw-migrated 两个包",理由是它们是"批量迁移来的"。
错误性质 :把来源属性 误当作价值属性。
发现机制 :使用者要求"详细分析解释原因,说清楚目的、作用、理由、根据、影响后再决定",
迫使研究者逐项核对两个包内的技能清单。核对结果推翻了原判断:
| 包内关键技能 | 价值 |
|---|---|
uspto-database |
专利检索------IP 档案的立身之本 |
crossref-search、semantic-scholar |
文献核验(使用者的既定习惯) |
docx、pdf、xlsx |
交付格式(使用者的硬要求) |
nano-banana-pro |
AI 生图通道 |
legal-analysis、regulatory-drafting、iso-13485-certification |
法务与法规 |
rdkit、chembl-database、pubchem-database |
化学/配方 |
scientific-diagram-generation、data-visualization-expert |
顶刊级配图 |
修正 :确立判据------按内容相关性切,不按来源切 。
(该判据后被写入 04_瓶颈解剖 的 4.2.4 节与组合卡。)
教训 :"这是导入的/生成的/遗留的"是溯源信息,不是价值判断。
在削减决策中,必须逐项核对内容,而不是依据来源标签批量处理。
错误 2:推荐"最小工具集"为本地执行的第一杠杆(方向性错误)
内容 :研究者测得 -t terminal,file 使提示从 32,216 降至 11,632 tok(−64%)、
延迟从 31.1 s 降至 12.2 s(−61%)后,把它确定为"本地执行优化的第一杠杆",
并写入交付方案与技能文档。
错误性质 :方向性错误------把"速度"当作唯一目标函数,忽略了优化对象的角色定义。
发现机制 :使用者直接质疑:
"手不是要做机械手,而是具有职能的手,不要通过剪枝技能来优化。"
研究者随即做源码核实,确认该建议的后果:
python
def _skills_prompt(agent: Any) -> str:
"""Skills index (empty without skills tools)."""
if not any(name in agent.valid_tool_names
for name in ['skills_list', 'skill_view', 'skill_manage']):
return ""
移除技能工具 = 整个技能索引返回空 = 执行者无法看到或加载任何技能。
此外还同时失去了 web、vision、patch、memory、todo、delegate 等能力。
修正:
- 把该建议从"第一杠杆"下调为不推荐;
- 在
06_混合路由策略文档顶部加勘误块; - 把执行者的默认工具集从
terminal,file改回terminal,file,skills; - 在技能文档中自我纠正(该技能先前已被研究者写入错误建议);
- 建立替代方案(第八章的降级路线)。
教训 :优化前必须先确定目标函数,而不是先找最大的数字。
"提示减少 64%" 是一个数字;"执行者能否完成工作"才是一个目标。
本例中,前者与后者冲突,而研究者选择了前者。
这是本研究中最严重的一处错误 ,因为它是唯一一处与外发交付物绑定 的错误------
它会通过技能文档影响未来的会话。所幸使用者在实施前拦截。
错误 3:降级收益报 3.1 s(实际 3.4 s)
内容 :早期基于 .skills_prompt_snapshot.json 的估算给出"整砍两个包省 3.1 s/轮"。
错误性质 :数值错误------源自估算口径与实测口径不一致 (快照推算 vs prompt-size --json 实测)。
发现机制 :改用 hermes prompt-size --json 获取逐技能精确成本后重算,
得到两个包合计 5,863 tok(而非 5,340 tok),实测值 3.4 s/轮。
修正:报告统一采用实测口径,并在文档中显式标注"更正:我上次说 3.1 s 是早期快照估算(偏低)"。
教训 :估算与实测必须显式区分 。本报告为此建立 L1/L2/L3 三级可信度标注(2.1 节),
其中"快照推算"属 L2,"实测"属 L1。
错误 4:demote_savings.py 的单位错误
内容:在对比"剪枝路线"与"降级路线"的收益时,脚本中写了:
python
print(f"裁剪工具集省 {(32216-11632)/CPT/PRE:.1f} s/轮")
其中 CPT = 4.0(设计为"字节/字符 → token"的换算系数),
但 32,216 与 11,632 本身就是 in= 字段的 token 值 (已经过换算),
再次除以 4 导致结果被低估为 3.0 s ,而正确值为 12.0 s。
错误性质 :单位/量纲错误------对"已是目标量纲的数值"重复套用了换算。
发现机制 :人工复核折算公式时发现量纲不一致;
随后在报告中显式记录并修正:
对照:-t terminal,file 省 12.0 s/轮(单位已是 tok,不再 /4)
影响分析 :该错误使"降级路线"看起来更有吸引力(因为把对照组的收益算小了),
属于有利于自己结论的方向性偏差。若未发现,会导致方案选择被误导。
修正:修正脚本、重算、并在交付文档中说明该 bug。
教训 :单位错误是本领域最高频、最隐蔽、且最可能与研究者动机一致的一类错误。
必须对每一个除法/换算标注量纲。本报告建议:
在脚本中把变量名包含量纲(如 tokens_fixed、bytes_index),并在打印时标注单位。
错误 5:自建补丁的 config 参数未透传
内容:研究者为 Hermes 编写的补丁初版如下:
python
def compact_skill_categories(self) -> frozenset[str]:
...
try:
from hermes_cli.config import load_config_readonly
_extra = (load_config_readonly().get("skills") or {}).get("demote_categories") or []
它忽略了调用方传入的 config 参数 ,总是读取全局配置。
而调用链为:
python
def coding_compact_skill_categories(*, platform=None, cwd=None, config=None) -> frozenset[str]:
return resolve_runtime_mode(platform=platform, cwd=cwd, config=config).compact_skill_categories()
即:签名声明支持传入配置,但实现忽略它。
错误性质 :代码缺陷------"签名与行为不一致"。
发现机制 :隔离验证脚本报 FAIL。验证脚本以显式配置调用:
python
got = coding_compact_skill_categories(platform="cli", cwd=os.getcwd(),
config={"skills": {"demote_categories": ["gaming", "media"]}})
# 期望 ['gaming','media'],实际返回 []
影响分析 :生产路径当时"恰好"能工作(因为 system_prompt.py 调用时不传 config,
总是走全局读取),所以这是一个不会在日常使用中暴露的缺陷 ------
它只在被显式传参时(例如测试、多档案场景)才显现。
修正:
python
def compact_skill_categories(self, config: Optional[dict[str, Any]] = None) -> frozenset[str]:
...
_cfg = config if config is not None else load_config_readonly()
并将调用方改为 .compact_skill_categories(config=config)。
教训:
- "生产路径能跑"不等于"实现正确"。 一个只在非默认参数下暴露的缺陷,
会长期潜伏并在未来的重构中突然爆发。 - 隔离验证(不碰实时配置、用显式参数)不仅更安全,而且能发现实时验证发现不了的问题。
若当时选择"改实时配置来测",该缺陷将完全不可见。
错误 6:用墙钟作为性能对比口径
内容:研究早期以墙钟(wall-clock)对比不同配置,得到:
| 实验 | 配置 | 墙钟 |
|---|---|---|
| 第 2 次 | -t terminal,file |
17 s |
| 第 5 次 | 同配置(同档案/同模型/同工单) | 61 s |
同一配置出现 3.6 倍差异。
错误性质 :口径错误------在未控制并发的环境中使用受并发调制的量。
发现机制 :自相矛盾本身被发现 。研究者注意到"同配置两次结果差 3.6 倍",
无法用任何合理解释覆盖,遂转向深挖。
根因 :OLLAMA_NUM_PARALLEL=1 单并发队列。当同时存在其它本地请求
(本会话自身的辅助通道调用构成持续干扰源)时,延迟变为"服务时间 + 排队时间"。
修正 :改用 agent.log 的 in=(确定性)为主口径,latency= 仅在无并发批次内比较。
该纪律成为本报告第二章的核心内容,并沉淀为元规律 M5。
教训 :对比实验前必须定义"哪个量是被测对象决定的"。
本例中,in= 由输入决定(确定),latency 由系统瞬时状态决定(随机)。
用随机量做对比,等于用噪声做证据。
错误 7:把"服务已挂"误判为"配置错误"
内容 :研究者把视觉辅助通道切换到本地模型后,调用 vision_analyze 返回:
Error analyzing image: Connection error.
openai.APIConnectionError: Connection error.
研究者当时的第一反应是"我的配置改动破坏了视觉功能",并开始排查配置。
错误性质 :归因错误------把依赖服务不可用 误判为自身配置错误。
发现机制:直接探测服务连通性:
bash
curl --noproxy "*" -s http://127.0.0.1:11434/v1/models -o /dev/null -w "http=%{http_code}"
# → http=000
# 报错:URLError [WinError 10061] 由于目标计算机积极拒绝,无法连接
Ollama 服务当时已经退出。 配置本身完全正确------服务恢复后同一次调用立即成功
(并返回了正确的图像描述)。
修正:
- 澄清该事件与配置无关;
- 为关键辅助通道增加远程兜底链(
fallback_chain); - 在技能文档中记录该错误的正确归因 ,以及一条判断规则:
"症状指向某功能失败时,先探测其依赖服务的连通性,再怀疑自己的配置。"
教训 :在"自己刚改了配置"与"观察到失败"相邻发生时,存在极强的因果误判倾向。
必须用独立探测打破这一倾向。
错误 8:把"输出为空"误判为"视觉能力缺失"
内容 :视觉能力测试在 max_tokens=60 下返回空字符串 content: '',
研究者据此一度认为"-opt 派生模型丢失了视觉能力"。
错误性质 :归因错误------把预算不足 误判为能力缺失。
发现机制:提高预算复测:
| 模型 | max_tokens=60 |
max_tokens=600 |
|---|---|---|
qwen3.8:latest |
finish=stop,'The shape is red.' |
同 |
qwen3.8-opt-64k |
finish=length,'' |
'The shape is red.' |
qwen3.8-hermes |
finish=stop,'The shape is red.' |
同 |
根因 :思考型模型先消耗 token 预算于推理,finish_reason=length 时
content 尚为空。
修正 :把"给足 max_tokens"写入能力测试的标准流程;
本报告的 3.3.2 节明确记录该发现。
教训 :"输出为空"是一个多义症状 (能力缺失 / 预算不足 / 模板错误 / 停止符误触发)。
诊断为"能力缺失"之前,必须先排除预算与模板因素。
错误 9:未做容量估算即尝试 128k(唯一造成实际影响的错误)
内容 :研究者为获得"更大上下文能力",创建 qwen3.8-hermes(num_ctx 131072)并加载。
错误性质 :破坏性试错------在提供服务的过程中进行未经容量估算的资源扩张。
后果 :推理进程崩溃(RemoteDisconnected),Ollama 服务端自我重启;
期间本地推理不可用,且后续排查叠加了进程污染问题。
发现机制 :现象本身即为证据,但根因需事后从日志恢复:
common_memory_breakdown_print:
| CUDA0 | 24435 = 22401 + (18663 = 15339 + 2924 + 400) + (-16629)
[mtmd] estimated worst-case memory usage of mmproj is 1161.02 MiB
修正:
- 重建为
num_ctx 65536(安全上限); - 建立扩容前的容量估算流程 (见 3.4.4 节):
权重 + KV/token × 目标上下文 + mmproj 最坏值 ≤ 可用显存; - 在报告中把 64k 标注为硬上限,并明确警告"不要建 128k 模型"。
教训 :"先估后试"在单卡推理中不是优化建议,而是安全要求。
一次未估算的扩张尝试可以打挂一个正在提供服务的进程。
10.3 错误的分布规律
对 9 项错误按类型归类:
| 类型 | 数量 | 编号 |
|---|---|---|
| 归因错误 | 3 | 7, 8, 9(9 兼为破坏性) |
| 数值 / 单位错误 | 2 | 3, 4 |
| 判断 / 方向性错误 | 2 | 1, 2 |
| 口径错误 | 1 | 6 |
| 代码缺陷 | 1 | 5 |
观察:
- 归因错误最多(3 项) ,且全部发生在"事件相邻"的场景
(刚改配置就失败、测试没输出就下结论、扩张后就崩)。 - 方向性错误最少但最严重 ------一旦与外发交付物绑定,
会通过技能文档污染未来的会话。
10.4 由错误导出的实用检查清单
本报告建议在任何类似的效能优化研究中,强制回答以下 8 个问题:
| # | 检查项 | 对应错误 |
|---|---|---|
| 1 | 我优化的是数字 还是目标函数?两者是否冲突? | 2 |
| 2 | 这个"无用/低价值"的判断,是基于来源 还是基于逐项内容核对? | 1 |
| 3 | 这个量是被测对象决定 的,还是环境调制的? | 6 |
| 4 | 这个数值的量纲是什么?有没有重复换算? | 3, 4 |
| 5 | "生产路径能跑"是否掩盖了签名与行为的不一致? | 5 |
| 6 | 我是否刚改了配置?失败能否用独立的连通性/健康探测复现? | 7 |
| 7 | "输出为空"是能力问题,还是预算/模板问题? | 8 |
| 8 | 这个资源扩张的容量估算做了吗?失败会打挂服务吗? | 9 |
10.5 本章小结
| 结论 | 内容 |
|---|---|
| 记录的错误数 | 9 项 |
| 唯一造成实际影响的错误 | 错误 9(未估容量即试 128k) |
| 错误被隔离的原因 | 惰性变更(3 项)、实施前发现(5 项) |
| 最高频错误类型 | 归因错误(3 项),均源于"事件相邻"的因果误判 |
| 最严重错误类型 | 方向性错误(1 项,与外发交付物绑定) |
| 最有价值的发现路径 | 使用者的质疑(直接拦截了错误 2) |
| 方法论主张 | 纠错密度是可靠性的直接指标 |
第十一章 局限性、未验证项与待核清单
任何实证研究都有边界。本章明确列出本报告的适用边界 、
证据缺口 与待核清单,以便读者正确使用结论,并为后续工作提供起点。
11.1 适用边界
11.1.1 硬件边界
| 维度 | 本研究的值 | 结论的敏感性 |
|---|---|---|
| GPU | RTX 5090 Laptop,24,463 MiB | 高------上下文上限(64k)与并发能力均由此决定 |
| 驱动模式 | WDDM | 高------可用显存随显示负载浮动,TCC 模式下边界会不同 |
| 系统内存 | 31.4 GiB | 中------影响 CPU 回退与堆碎片行为 |
| CPU | Core Ultra 9 275HX(24 核) | 低------仅影响纯 CPU 路径 |
明确说明 :本报告所有"延迟类"与"容量类"结论(如"64k 是硬上限")
不可直接外推到其他显存规格的机器。24 GB 与 48 GB 的结论会不同。
11.1.2 与硬件无关的结论(可跨平台复现)
以下结论不依赖具体硬件,具有更强的可迁移性:
| 结论 | 为何与硬件无关 |
|---|---|
| 固定载荷为 36,109 tok(本档案配置下) | 由提示内容决定,与执行硬件无关 |
| 技能索引占系统提示 60% | 同上 |
_skills_prompt 在无技能工具时返回空 |
源码逻辑,平台无关 |
| "降级保留名字、不保留描述" | 源码逻辑,平台无关 |
| 移除技能工具的后果清单 | 源码逻辑,平台无关 |
| 远程:本地 token 比 66:1 | 由任务分工决定,与执行硬件无关 |
| 五条元规律(M1--M5) | 方法学层面,与实现无关 |
11.1.3 软件版本边界
| 组件 | 本研究版本 | 版本敏感性 |
|---|---|---|
| Ollama | 0.33.2 | 高------MTP 投机解码、桌面版覆盖注册表等行为均可能随版本变化 |
| Hermes | v0.21.1(upstream ad03f20d) |
中------本研究的补丁绑定于特定文件版本 |
qwen3.8 |
27.3B Q4_K_M | 高------换模型则吞吐与容量结论全部改变 |
重要提示 :本研究已发现一处版本漂移导致旧结论失效 的实例
("Ollama 不支持投机解码"在 0.32 成立、在 0.33.2 已不成立)。
因此读者在引用本报告结论前,应先核对上述三个版本。
11.2 证据缺口(明确标注)
以下事项本研究未能 取得证据。它们不是"可以不谈"的细节,
而是会影响结论适用范围的空白。
缺口 1:技能的历史使用率
缺口内容:本研究无法给出"这 566 个技能中,哪些在历史上真正被加载过"的统计。
原因:两个数据源均不可得:
| 尝试 | 结果 |
|---|---|
挖掘 state.db 的 messages 表搜索 skill_view( 调用 |
命令被审批机制拦截(超时未获同意),且提示"不得重试或换命令绕过" |
| 读取 curator 的用量遥测 | 同类命令同样被拦截 |
影响 :本报告对"哪些技能可以降级"的建议基于内容相关性判断,不含使用频次证据 。
理论上存在这样的可能:某个被判为"无关"的技能实际高频使用,反之亦然。
缓解 :本研究选择了不依赖使用频次 的方案(整表降级,而非挑类别),
因此该缺口未影响最终方案的成立性 ------这是"选择对证据要求更低的方案"的一个正面案例。
若未来需要"选择性启用"某些技能,则必须先补上该缺口。
后续获取路径 (待核):Hermes 的 curator 提供 hermes curator usage 与
skills/.usage.json(含 use_count / view_count / last_activity_at),
应在获得授权后由使用者自行运行。
缺口 2:远程 provider 的实际计费
缺口内容:本报告只给出 token 消耗比(66:1),未给出货币成本对比。
原因 :远程 provider(blsc)的单价未公开,hermes insights 亦报告
Cost: Unknown --- 5 session(s) (no pricing data)。
影响:无法量化"把负载移到本地"的经济收益,只能给出 token 量级证据。
后续获取路径 (待核):查阅 provider 的计费文档;或在 hermes 的
模型价格表中补充该 provider 的定价条目。
缺口 3:KEEP_ALIVE 值的来源未完全确证
缺口内容 :本研究确认服务端固定呈现 KEEP_ALIVE:2m0s,
并确认 CONTEXT_LENGTH:65536 来自桌面版 db.sqlite 的 settings 表;
但**2m 这一具体数值的来源未在数据库中找到对应列**。
当前认识 :推测由桌面版以硬编码或内部默认方式注入。【推断】
影响:若该值可经界面配置,则"保温脚本"这一缓解措施可被更简单的配置替代。
后续获取路径 (待核):检查 Ollama 桌面版的设置界面是否存在该选项;
或审计其二进制/资源文件。
缺口 4:未验证"技能索引"在超长会话中的压缩行为
缺口内容 :本研究验证了单轮的前缀缓存行为,但未测量 在会话历史增长到
接近上下文上限时,压缩(compression)对技能索引的处置方式。
已有认识 (L1,来自源码):
Hermes 对 <512K 上下文的模型施加上限 75% 的压缩阈值下限:
(65,536 − 4,096) × 0.75 = 46,080 tok > 36,109 tok(固定载荷)
因此在 64k 上下文下不会因固定载荷而触发过早压缩。但接近 46k 时的实际行为未实测。
后续获取路径 (待核):构造长会话,观测 compression_count 与压缩后的提示构成。
缺口 5:未做"降级后能力回归测试"的完整矩阵
缺口内容 :本研究验证了降级后技能能力 保留(skills_list 成功调用、
disabled=0、名字 100% 保留),但未逐一验证 每个被降级技能仍可正常 skill_view 加载。
现有证据 :机制上,降级只影响 _render_skills_index 的渲染分支,
不触碰 skills.disabled,因此 skill_view 的禁用闸门(_is_skill_disabled)不会拦截。
该推理属 L2(机制推断),非 L1 实测。
后续获取路径 (待核):抽样若干个被降级技能,逐一执行 skill_view(name) 验证。
缺口 6:单次测量,无重复实验的统计
缺口内容 :本报告的多数性能数值来自单次测量,未做重复实验与方差分析。
影响 :无法给出置信区间。部分数值(如预填充速率)可能随设备温度、
功耗状态(笔记本 TGP 145W 限制)而变化。
缓解 :本研究对关键结论采用了多源交叉验证 (nvidia-smi / server.log / agent.log),
并以两次独立运行的一致性 作为可靠性证据(例如 64k 占用两次测得
20,472 MiB 与 20,480 MiB,差 8 MiB)。
11.3 待核清单(行动项)
| # | 待核项 | 方法 | 优先级 |
|---|---|---|---|
| 1 | 技能历史使用率 | hermes curator usage(需授权) |
中 |
| 2 | 被降级技能仍可 skill_view |
抽样 5--10 个被降级技能逐一加载 | 高 |
| 3 | 远程 provider 单价 | 查计费文档 + 补模型价格表 | 低 |
| 4 | KEEP_ALIVE=2m 的可配置性 |
检查桌面版设置界面 | 中 |
| 5 | 长会话下的压缩行为 | 构造接近阈值的会话并观测 | 中 |
| 6 | 性能数值的重复性 | 每个关键指标重复 3 次,报告均值与极差 | 中 |
| 7 | 补丁在 hermes update 后的实际行为 |
在下一次升级时观测 git stash 并以 reapply_patch.sh 恢复 |
高(需等待升级事件) |
| 8 | 多档案下的降级作用域隔离 | 在主会话与执行者之间对比 prompt-size |
中 |
11.4 方法学局限
| 局限 | 说明 | 缓解 |
|---|---|---|
| 单案例研究 | 全部结论来自单一档案(intellectual-property)与单一硬件 |
明确标注诊断性而非统计性 |
| 研究者即实施者 | 研究者同时承担测量与变更,存在动机性偏差风险 | 通过"隔离验证 + 使用者复核 + 错误记录"降低;但仍不可完全排除 |
| 未做对照实验的随机化 | 变更按序实施,非同时对照 | 关键对比均采用"同档案/同模型/同工单"控制变量 |
| 依赖源码阅读的部分结论 | 少数结论基于源码推理而非运行观测 | 已逐条标注 L2/L3 |
11.5 一个值得强调的正面局限
本研究的最大方法学优势 在于:存在一位会质疑的使用者。
回顾 9 项错误,错误 2(方向性、已绑定交付物)是被使用者当场拦截的 ,
而它恰恰是危害最大的那一项------其余错误或未实施、或被自身检验发现。
这一事实对 AI 辅助工程实践有直接含义 :
人类复核不是流程的冗余环节,而是特定类型错误的唯一发现通道。
本研究能用源码证明错误 2 的危害,但是在使用者指出方向之后才去证明的。
因此本报告主张:
对本类研究,应把"人类对方向的质疑"设计为流程中的显式关口(gate),
而不是把它当作"如果有空就做"的附加步骤。具体做法是:任何将被写入交付物或长期知识库的建议,必须先说明
"它优化的是哪个目标函数,以及是否牺牲了其他目标"。
这正是本报告第一章所述"问题陈述三次重述"的价值所在:
每一次重述都由一次质疑触发,而每一次重述都实质性地改善了方案。
11.6 本章小结
| 类别 | 内容 |
|---|---|
| 与硬件无关的结论 | 7 项(固定载荷、索引占比、源码机制、成本比、元规律) |
| 证据缺口 | 6 项(使用率、计费、KEEP_ALIVE 来源、长会话压缩、回归矩阵、重复测量) |
| 高优先级待核 | 2 项(降级技能仍可加载的抽样验证;升级后补丁行为) |
| 最大方法学优势 | 有会质疑的使用者;人类复核是特定错误类型的唯一发现通道 |
| 核心建议 | 把"人类对方向的质疑"设计为显式关口;任何写入交付物的建议必须先声明其目标函数 |
第十二章 结论与后续工作
12.1 六个研究问题的回答
RQ1:本机平台上,本地模型的真实性能边界是什么?
回答:
| 维度 | 边界值 | 约束性质 |
|---|---|---|
| 预填充吞吐 | 1,713 tok/s(实测峰值,6,686 token 提示) | 硬件(GPU 算力) |
| 生成吞吐 | 54--68 tok/s | 硬件 + 模型规模(27.3B Q4_K_M) |
| 上下文上限 | 65,536(131,072 导致进程崩溃) | 显存容量(硬约束) |
| KV 成本 | ~4.0 KB/token | 架构(Qwen3.5 混合注意力) |
| 冷加载 | 12.36--12.70 s | 磁盘 I/O + 显存分配 |
| 并发 | 1 (-np 1,串行队列) |
配置 + 显存 |
| 能力 | 工具调用 ✅ / 视觉 ✅ / 思考 ✅(开启更优) | 模型能力 |
关键区分 :模型能力 上限(原生 262,144 上下文、视觉、工具)与
部署容量 上限(65,536)相差 4 倍。规划必须以部署容量为准。
RQ2:Hermes 每轮固定载荷的成本结构如何?瓶颈在哪一层?
回答 :固定载荷 33,600--36,109 token (两法交叉验证),折合本机 21.1 秒预填充。
| 组成 | token | 占比 |
|---|---|---|
| 技能索引 | ~14,086 | 39% |
| 工具 schema | ~10,075 | 28% |
| 身份/指令(含人格文件) | ~4,664 | 13% |
| 记忆 + 用户画像 | ~1,542 | 4% |
| 其余 | ~5,742 | 16% |
瓶颈在"调用方发送的固定文本",其中技能索引是最大单项。
更深一层的发现是:索引的 81% 消耗在四个批量迁移包上(443 技能 / 10,545 tok),
而使用者自建技能仅占 0.8%。
RQ3:在保持能力完整的前提下,有哪些机制可以降低每轮成本?
回答:本研究识别并验证了四条机制:
| 机制 | 收益 | 能力影响 | 可信度 |
|---|---|---|---|
| 整表降级为仅目录 | −39% 提示(26,087 → 15,907 tok) | 无(名字 100%、工具全可用) | L1 |
| 保持模型驻留 | 免 12.7 s 冷加载 | 无 | L1 |
| 保持前缀缓存 | 第二轮延迟 ÷55(55.1 → 1.0 s) | 无 | L1 |
| 收窄工具集(不推荐单独使用) | −64% 提示 | ❌ 技能能力全失 | L1 |
核心结论 :"降级"与"裁剪"在成本上效果相近,在能力后果上截然不同。
降级拿到裁剪收益的 70%,代价为零能力损失。
RQ4:远程与本地模型的正确分工是什么?有哪些反直觉的正确结论?
回答 :三层路由(含被普遍忽略的 Tier-0):
| 层 | 适用 | 判据 |
|---|---|---|
| Tier-0 无模型 | 可完全指定的确定性任务 | 能写成一条命令吗? |
| Tier-1 本地手 | 需语言理解、不需权衡、量大/私密 | 答案唯一且可自检吗? |
| Tier-2 远程大脑 | 规划、设计、歧义、高风险判断 | 需要权衡或多步设计吗? |
三条反直觉的正确结论:
| # | 结论 | 直觉为何出错 |
|---|---|---|
| 1 | 不要把 delegation.provider 指向本地 |
配置项"存在且可用"≠"该配置最优"------子智能体继承完整工具集(32,216 tok) |
| 2 | 不要关闭 thinking 以提速 | 关闭后 token 更多、更慢(4.92 s vs 3.98 s),因 MTP 使思考 token 顺带产出 |
| 3 | 不要扇出到本地 | NUM_PARALLEL=1 → 扇出只是排队(12.2 → 55.1 s),不产生并行度 |
成本证据 :远程与本地 token 消耗比 66 : 1 ;远程单轮 in= 达 20 万--29 万。
RQ5:削减提示成本的"粒度"与"作用域"如何选择?
回答:这是本报告最重要的方法学结论。
发现 :在"按类别降级"的粒度下,四份候选名单构成一条
"收益(2.6 / 3.1 / 4.3 / 6.3 s)vs 误伤(无 / 无 / 有 / 严重)"的权衡曲线。
关键洞察 :该权衡源于实现粒度,不是问题固有。
| 错配的环节 | 后果 |
|---|---|
| 匹配单位 = 类别 | 类别内部不同质(productivity 同时含 docx 与 airtable)→ 必然误伤 |
| 作用域 = 档案级 | 无法让主会话与本地手取不同值 |
解法(第三条路径) :按进程定粒度 ------不挑类别,整表降级;
用 HERMES_SKILLS_DEMOTE_ALL=1 限定作用域到本地执行者进程。
| 结果 | 数值 |
|---|---|
| 收益 | 6.5 s/轮(高于最激进的类别方案 6.3 s) |
| 误伤 | 零 |
| 能力 | 名字 100% 保留、skills_list/skill_view 全可用、disabled=0 |
| 端到端 | 26,087 → 15,907 tok(−39%) |
| 决策成本 | 不需要挑类别 |
RQ6:这类研究中哪些测量纪律是必需的?哪些错误模式是高频的?
回答:
必需的测量纪律(三条):
- 口径分层 :区分"被测对象决定的量"(
in=,确定性)与"环境调制的量"(latency,随机)。
后者不得作为结论依据。 - 多源交叉验证 :至少两个独立数据源(本研究用
nvidia-smi+server.log+agent.log)。 - 三级可信度标注:L1 实测 / L2 推算 / L3 推断,逐条标注。
高频错误模式(按本研究 9 项错误的分布):
| 错误类型 | 占比 | 典型触发场景 |
|---|---|---|
| 归因错误 | 3/9 | 事件相邻(刚改配置就失败、没输出就下结论、扩张后就崩) |
| 数值/单位错误 | 2/9 | 重复换算、估算与实测混用 |
| 判断/方向性错误 | 2/9 | 把数字当目标函数、按来源而非内容判断价值 |
| 口径错误 | 1/9 | 用随机量做对比 |
| 代码缺陷 | 1/9 | 签名与行为不一致(仅非默认参数下暴露) |
12.2 核心结论(一段话概括)
在这套"编排器 + 本地推理 + 27B 模型"的架构中,决定使用者体验的主要变量不是模型的参数,
而是调用方每轮发送的固定文本宽度(36,109 token ≈ 21.1 秒)。削减它存在两条路线:
裁剪能力(快但静默失去职能)与降级粒度(同样快且能力无损)。
后者的实现关键在于选择正确的"粒度"与"作用域"------把决策单位从"类别"改为"进程",
就同时取得了最高收益与零能力损失。此外,驻留与缓存两条次要战线的收益量级与提示削减相当
(各约 12--21 秒/轮),但它们由环境默认值与使用习惯决定,容易被忽略。
12.3 本研究的可迁移产出
12.3.1 五条元规律
| 编号 | 元规律 | 一句话 |
|---|---|---|
| M1 | 成本引力在调用方 | 先问"每轮有多少字节是固定重发的",而不是"组件参数是否最优" |
| M2 | 粒度错配制造伪两难 | 遇到"鱼与熊掌",先问"这个取舍的单位是什么?能否换单位重述?" |
| M3 | 可发现性/可加载性/可用性三分 | 先问"我要削掉的是'被想起的概率'还是'被调用的能力'?" |
| M4 | 无损劣化优于有损优化 | 先问"这个变更失效时,是退回现状,还是带着半启用状态运行?" |
| M5 | 口径决定结论 | 先定"哪个量是被测对象决定的,哪个是被环境调制的" |
依赖结构:M5(前提)→ M1(发现)→ M3(分辨)→ M2(重构)→ M4(落地)
12.3.2 可复用的工程资产
| 资产 | 用途 |
|---|---|
hermes_bench.py |
归因"固定载荷 → 预填充成本"的标准测量 |
verify.sh |
本地推理栈健康自检(6 项检查) |
keepwarm.ps1 |
模型驻留保温(0.004 s/次) |
local_exec.sh |
本地执行者封装(串行锁 + 审计 + 工单协议) |
verify_patch.py |
补丁的隔离验证(不触碰实时配置) |
reapply_patch.sh |
升级后重打补丁 |
demote_savings.py |
索引成本的精确测算 |
skills-demote-categories.patch |
固化的源码 diff(可重放) |
12.3.3 知识库修正
本研究修正了两处已写入技能库的错误或过时结论:
| 技能 | 原结论 | 修正 |
|---|---|---|
ollama-tuning |
"Ollama 不支持投机解码(0.32)" | 0.33.2 原生支持 MTP,实测接受率 0.52 |
hermes-local-providers |
(研究者写入的)"最小工具集是本地第一杠杆" | 下调并修正为"绝不剪技能工具",并补充降级路线 |
12.4 后续工作
按优先级排列:
优先级高(应立即执行)
| # | 工作 | 理由 |
|---|---|---|
| 1 | 抽样验证被降级技能仍可 skill_view 加载 |
填补缺口 5,把 L2 推断升为 L1 实测 |
| 2 | 在下一次 hermes update 时观测并验证 reapply_patch.sh |
唯一未经验证的关键运维流程 |
| 3 | 把"整表降级"在本机正式启用(local_exec.sh 已默认) |
收益已量化,风险已控制 |
优先级中(可在有资源时执行)
| # | 工作 | 理由 |
|---|---|---|
| 4 | 获取技能历史使用率(hermes curator usage) |
填补缺口 1;为未来的"选择性启用"提供依据 |
| 5 | 关键性能指标做 3 次重复测量并报告极差 | 填补缺口 6 |
| 6 | 观测长会话下的压缩行为 | 填补缺口 4 |
| 7 | 检查 KEEP_ALIVE=2m 是否可经界面配置 |
填补缺口 3;可能简化缓解措施 |
优先级低(方向性探索)
| # | 工作 | 说明 |
|---|---|---|
| 8 | 探索 context.engine 插件机制 |
源码显示 context.engine 可选择 plugins/context_engine/<name>/ 插件,可能提供更规范的上下文控制路径 |
| 9 | 探索 register_system_prompt_section() |
官方插件 API 可注入系统提示段;当前不能移除内容,但可用于补充精简索引 |
| 10 | 评估把"整表降级"作为上游建议反馈 | 当前需本地补丁;机制本身与 Hermes 的"never hidden"设计原则一致,具备上游化价值 |
一项明确不推荐的方向
| 方向 | 不推荐理由 |
|---|---|
| 建"执行者专用精简档案" | 该路径的目的是"少装技能",而 -t 与整表降级已用零维护成本达到同样效果。多维护一个档案反而增加不一致风险。 |
12.5 结语
本研究从一个朴素的问题出发------"如何让这套组合发挥最大潜力"------
最终得到一个与最初假设相反的答案:瓶颈不在模型,而在调用方发送的内容。
这一结论的取得并非线性推导,而是经过三次问题重述 与九次自我纠错 。
其中最关键的两次转折都源于使用者的质疑:
- 第一次质疑("手不应是机械手")拦截了一项已绑定交付物的方向性错误;
- 第二次质疑("应通过动态加载而非剪枝")直接导向了本报告的核心成果。
因此在结语中值得再强调一次本研究最实用的方法学建议:
任何将被写入交付物或长期知识库的建议,必须先声明
"它优化的是哪个目标函数,以及它牺牲了什么"。因为数字的改善与目标的达成是两件不同的事,
而前者极易伪装成后者。
附录:篇幅与文件索引
| 章 | 文件 |
|---|---|
| 摘要 | 00_摘要与关键发现.md |
| 一 | 01_引言与问题陈述.md |
| 二 | 02_方法论与测量口径纪律.md |
| 三 | 03_实验环境与性能基线标定.md |
| 四 | 04_瓶颈解剖_固定载荷成本结构.md |
| 五 | 05_递进一_从参数调优到提示词经济学.md |
| 六 | 06_递进二_驻留与缓存经济学.md |
| 七 | 07_递进三_混合路由体系.md |
| 八 | 08_递进四_动态加载与伪两难.md |
| 九 | 09_递进五_决策融合与深层逻辑.md |
| 十 | 10_失败与自我纠错记录.md |
| 十一 | 11_局限性与待核清单.md |
| 十二 | 12_结论与后续工作.md(本章) |
| 全文 | 研究实验报告_全文.md(各章合并) |
| 字数 | _count.py / 字数统计.txt |
附录 A 原始数据索引与复现指南
本附录的作用是让报告中的每一个数值断言都可被独立复核 。
凡正文引用的数值,其对应的原始输出文件均列于此。
A.1 数值---原始文件对照表
| 正文数值 | 所在章节 | 原始文件 | 提取方式 |
|---|---|---|---|
| 预填充 726 / 1,263 / 1,713 tok/s | 3.2.1 | 01_实测报告/raw/bench_out.txt |
/api/generate 响应的 prompt_eval_count ÷ prompt_eval_duration |
| 生成 65.5 / 57.5 / 68.0 / 54.2 tok/s | 3.2.2 | 同上 | eval_count ÷ eval_duration |
| 冷加载 12.36--12.70 s | 3.2.3 | bench_out.txt 的 PART 7 |
响应的 load_duration |
| 16.11 / 16.20 / 16.33 GiB 驻留 | 3.4.2 | bench_out.txt 的 PART 4 |
/api/ps 的 size_vram |
| 20,480 MiB / 余 3,658 MiB | 3.4.2 | 同上 + nvidia-smi |
交叉验证 |
| 128k 崩溃 | 3.4.4 | model_setup.txt |
RemoteDisconnected 异常 |
| 显存分解 15,339 + 2,924 + 400 | 3.4.4 | 03_配置与脚本/verify.sh 关联的 server.log |
common_memory_breakdown_print |
| mmproj 最坏 1,161.02 MiB | 3.4.4 | server.log |
[mtmd] estimated worst-case |
| 投机解码接受率 0.52 | 3.6 | server.log |
spec common_specu: statistics |
| 工具调用返回结构 | 3.3.1 | bench_out.txt 的 PART 5 |
tool_calls 字段 |
| 视觉描述正确性 | 3.3.2 | bench_out.txt 的 PART 6 |
content 字段 |
think 对比 3.98 / 4.92 s |
3.3.3 | _count.py 同类隔离测试输出 |
单次隔离调用 |
| 固定载荷 36,109 tok | 4.1.2 | 01_实测报告/raw/dump_stats.txt |
解析 request_dump_*.json 的 request.body |
| 系统提示 94,254 B / 74,903 chars | 4.1.1 | 04_实证核验/verify_output.txt |
hermes prompt-size |
| 技能索引 56,345 B | 4.1.1 | 同上 | 同上 |
| 工具 schema 40,300 B / 25 工具 | 4.1.1 | 同上 | 同上 |
| 逐类索引成本 | 4.2.3 | hermes prompt-size --json(%LOCALAPPDATA%\Temp\psize.json) |
skills_breakdown[].index_line_total_bytes |
| 迁移包占索引 81% | 4.2.3 | 07_动态加载优化/final_decision_table_output.txt |
类别聚合 |
| 自建技能占 0.8% | 4.2.3 | 同上 | 同上 |
| 工具集 32,216 / 11,632 / 10,375 tok | 7.2.2 | <profile>/logs/agent.log 的 API call #1: in= |
按会话时间戳切分 |
| 同请求 12.2 s vs 55.1 s | 7.3.1 | 同上 | latency= |
| 串行排队日志 | 7.3.3 | 06_混合路由策略/router_test.txt |
[lock] 本地执行者忙,排队中 |
delegation 继承规则 |
7.4.2 | tools/delegate_tool.py:557(源码) |
直接引用 |
| 成本比 66:1 | 7.5 | hermes insights --days 1 输出 |
按模型聚合 token |
| 远程单轮 218k / 294k | 7.5 | agent.log 的 DeepSeek-V4.1-Flash 行 |
in= |
| 辅助通道路由证据 | 7.6.2 | server.log 的 POST /v1/chat/completions + agent.log 的 Auxiliary ... using ollama-launch |
时间戳对齐 |
| 兜底链配置 | 7.7 | hermes fallback list 输出 |
直接引用 |
| 历史 401 中断 | 7.7 | sessions/request_dump_20260902_*.json 的 error 字段 |
直接引用 |
| 四份名单收益 2.6 / 3.1 / 4.3 / 6.3 s | 8.4.3 | 07_动态加载优化/demote_savings_output.txt |
隔离渲染 |
| 整表降级 13,122 B / 6.5 s | 8.6.3 | 07_动态加载优化/patch_verify_output.txt |
同上 |
惰性验证(返回 ∅) |
8.6.4 | 同上 第 2 节 | 隔离调用 |
| 端到端 26,087 → 15,907 tok | 8.6.5 | e2e_demote_test.txt + agent.log |
in= |
执行者成功调用 skills_list |
8.6.5 | local_exec_last.log + agent.log |
返回值 2 |
| 前缀缓存 55.1 s → 1.0 s | 4.5.1 / 6.4 | agent.log 的连续两行 API call #1/#2 |
in= 与 latency= |
| 缓存复用 23 / 6,686 | 4.5.1 | bench_out.txt 同类重复请求 |
prompt_eval_count |
KEEP_ALIVE 对照 |
6.2.2 | 06_递进二 关联的注册表读取 + server.log |
两侧对照 |
db.sqlite 的 context_length=65536 |
6.2.2 | Ollama db.sqlite 的 settings 表(只读查询) |
直接查询 |
| 保温触碰 0.0057 / 0.0035 s | 6.3.2 | model_fix.txt |
连续两次触碰的 time_total |
A.2 复现步骤
A.2.1 前置条件
bash
# 确认栈可用
curl --noproxy "*" -s http://127.0.0.1:11434/api/version # 期望 {"version":"0.33.2"}
hermes --version # 期望 v0.21.1 或更高
hermes config get skills.demote_categories # 期望 "Config key not set"
A.2.2 测量提示规模(与硬件无关,可跨平台复现)
bash
hermes prompt-size # 人类可读
hermes prompt-size --json > p.json # 机器可读(含逐技能成本)
注意 :hermes prompt-size 没有 --toolsets 参数,
因此它无法 显示"收窄工具集"的效果------
该效果必须从 agent.log 的 in= 读取。
A.2.3 测量本地执行者的提示规模
bash
# 记录起点行数
n0=$(wc -l < <profile>/logs/agent.log)
# 派发一条简单工单
hermes -p <profile> chat -q "运行 echo probe 并只回报输出" \
-m qwen3.8-hermes --provider ollama-launch -t terminal,file,skills -Q --oneshot
# 读取本次的 in=
tail -n +$((n0+1)) <profile>/logs/agent.log | grep "API call #1:"
A.2.4 验证整表降级
bash
# 完整菜单
in_full=$(... 不带 env ...)
# 仅目录
HERMES_SKILLS_DEMOTE_ALL=1 hermes -p <profile> chat -q "..." ... -t terminal,file,skills -Q --oneshot
# 对比两次的 in=,期望约 26,087 → 15,907
A.2.5 隔离验证补丁(不触碰实时配置)
bash
SRC=<hermes-agent>
HERMES_HOME=<profile> "$SRC/venv/Scripts/python.exe" \
"07_动态加载优化/verify_patch.py"
关键 :该脚本用显式 config= 参数 测试,因此不会改写使用者的配置。
这一做法是发现补丁 config 透传缺陷的关键(见 10.2 之错误 5)。
A.2.6 健康自检
bash
bash 03_配置与脚本/verify.sh
检查 6 项:进程唯一性、服务可达性、服务端生效环境、驻留与显存、
工具调用冒烟、视觉冒烟、Hermes 侧配置(兜底链/载荷/辅助槽位)。
A.3 已应用的配置变更清单
| # | 变更 | 位置 | 回滚命令 |
|---|---|---|---|
| 1 | 新建 qwen3.8-hermes(num_ctx 65536,temp 0.3) |
Ollama | ollama rm qwen3.8-hermes |
| 2 | providers.ollama-launch.default_model / .model → qwen3.8-hermes |
profile config | hermes config set 回原值 |
| 3 | 11 个辅助通道 → 本地 | profile config | revert.sh |
| 4 | fallback_model → 本地 |
profile config | hermes fallback clear |
| 5 | auxiliary.vision.fallback_chain → 远程兜底 |
profile config | 删除该键 |
| 6 | OLLAMA_CONTEXT_LENGTH 8192 → 32768 |
Windows 注册表 | setx 回原值 |
| 7 | 源码补丁:agent/coding_context.py(2 hunk) |
hermes-agent | cp .bak-20260912 覆盖 |
| 8 | local_exec.sh 默认整表降级 + skills 工具 |
交付目录 | --full-menu 单次关闭 |
A.4 交付目录索引
Hermes-本地模型最大化/
├── 00_项目总览/README.md 项目索引
├── 01_实测报告/ 性能基线与瓶颈 + raw/(全部原始输出)
├── 02_优化方案/ 组合卡 + 已应用优化与验证证据
├── 03_配置与脚本/ 模型定义 / 基准 / 自检 / 保温 / 回滚 / 启动
├── 04_实证核验/ 端到端验收记录 + verify.sh 输出
├── 05_技能索引决策/ 技能索引瘦身决策书 + 分析脚本
├── 06_混合路由策略/ 三层路由 + 执行器封装(local_exec.sh)
├── 07_动态加载优化/ 降级方案 + 补丁 + 验证 + 重打脚本
├── 08_决策融合/ 两个选择的优劣 + 第三条路径
└── 09_研究报告/ 本报告(13 章 + 全文合并 + 字数统计)
A.5 引用本报告时的注意事项
- 区分"能力上限"与"部署上限" :模型原生支持 262,144 上下文,
本机部署上限 65,536。引用时须说明是哪一者。 - 区分实测与推算:正文已用 L1/L2/L3 标注,引用时应保留该区分。
- 核对版本 :Ollama / Hermes / 模型三者版本变化可能使结论失效
(已有先例:投机解码支持性随版本翻转)。 - 硬件强相关项:所有延迟与容量结论绑定 RTX 5090 Laptop 24 GB + WDDM。
- 不与"使用频次"混淆 :本报告未获得技能使用率数据(缺口 1),
因此任何"某技能不重要"的推断均为基于内容的判断,非基于使用统计。
附录 B 术语表与缩略语
本附录统一报告中的关键术语,避免读者因同名异义产生误读。
每个术语给出"本报告中的定义"与"为何需要区分"。
B.1 性能与推理类
| 术语 | 本报告定义 | 易混点 |
|---|---|---|
| 预填充(prefill) | 模型处理输入提示的过程,逐 token 计算并建立 KV 缓存 | 与"生成"区分:预填充是并行的、吞吐以千 tok/s 计;生成是串行的、以十 tok/s 计 |
| 生成(decode / generate) | 模型逐 token 产生输出的过程 | 同上 |
| 固定载荷(fixed payload) | 每一轮都原样重发的提示部分:系统提示 + 工具 schema | 与"对话历史"区分:历史是增量的,固定载荷是恒定的 |
in= |
Hermes agent.log 中每次 API 调用记录里发送给模型的输入 token 数 |
本报告的主口径;由输入决定,不受并发影响,具确定性 |
latency= |
同一条记录中的调用延迟 | 不可作为对比依据------受本地单并发队列调制 |
| 前缀缓存 | 服务端复用已计算过的提示前缀 KV,只计算新增部分 | 条件严格:前缀字节不变 + 模型驻留 |
| KV 缓存 | 自注意力机制的键值缓存,随上下文长度线性增长 | 本机实测约 4.0 KB/token(因混合注意力而远低于常规模型) |
| 冷加载 | 将模型权重从磁盘载入显存的过程 | 本机 12.36--12.70 s,是"停顿惩罚"的主要来源 |
| 驻留 | 模型在显存中保持加载的状态 | 由 KEEP_ALIVE 控制,本机默认仅 2 分钟 |
| 串行队列 | 因 NUM_PARALLEL=1,本地服务的请求逐条执行 |
使并发请求的延迟变为"服务时间 + 排队时间" |
| 投机解码 / MTP | 用模型自身的多 token 预测头草拟多个候选 token,再一次性校验 | 本机实测接受率 0.52,是生成吞吐的重要来源 |
B.2 架构与硬件类
| 术语 | 定义 | 说明 |
|---|---|---|
| WDDM | Windows 显示驱动模型 | 显示子系统与 CUDA 共享显存,导致"理论可用"与"实际可用"不一致 |
| 混合注意力 | 部分层使用线性注意力的架构 | Qwen3.5 采用;这是 KV 成本极低(~4 KB/token)的原因 |
mmproj |
多模态投影器(本项目为 clip 架构,460.73M 参数) |
占用独立显存,容量估算时必须计入(最坏 1,161 MiB) |
| Tier-0 / 1 / 2 | 本报告提出的三层路由:无模型 / 本地手 / 远程大脑 | Tier-0 是常被忽略的一层:确定性任务不需要任何模型 |
| 工单(work order) | 远程大脑交给本地执行者的自包含任务说明 | 必须包含目标/输入/约束/输出格式/自检/终止条件 |
| 辅助通道(auxiliary slot) | Hermes 中由独立(可不同)模型承担的辅助任务,如视觉、压缩、标题生成 | 本报告将 11 项本地化、2 项保留远程 |
B.3 知识与削减类(本报告的核心术语)
| 术语 | 定义 | 为何关键 |
|---|---|---|
| 技能索引(skills index) | 系统提示中列出全部可用技能及其描述的区块 | 本机占系统提示 60%,是本轮最大的固定成本 |
| 渐进式披露(progressive disclosure) | 只发送"目录",需要时再按需加载完整内容 | Hermes 技能系统的既有设计;"动态加载"并非需新建的机制 |
| 降级(demote) | 把索引中的描述去掉,保留技能名 | 本报告的中心手段:省成本而不损失可加载性 |
| 裁剪(prune) | 使技能或工具不可见、不可加载 | 本报告明确不推荐------"pruning caused silent capability loss" |
| 可发现性 | 模型在系统提示中"看到"该能力存在 | 三分模型之一 |
| 可加载性 | 模型能按名字取得该能力的内容 | 三分模型之二 |
| 可用性 | 该能力所依赖的工具/环境确实存在 | 三分模型之三 |
| 粒度(granularity) | 削减决策的匹配单位(本报告的案例:类别 vs 进程) | 粒度错配是伪两难的成因 |
| 作用域(scope) | 削减决策生效的范围(档案级 vs 进程级) | 进程级作用域是融合方案的关键机制 |
| 惰性变更(inert change) | 未显式启用时行为与变更前完全一致的变更 | 使"已应用"与"已启用"成为两个独立决定 |
| 伪两难(false dilemma) | 看似必然的权衡,实由实现选择造成 | 本报告识别并消解的核心对象 |
| 无损劣化 | 变更失效时退回现状,而非留下半启用状态 | 优于"有损优化"的风险性质 |
B.4 测量与验证类
| 术语 | 定义 | 说明 |
|---|---|---|
| 可控口径 | 由被测对象决定、不受环境调制的测量量 | 如 in=;唯有此类量可作为结论依据 |
| 隔离验证 | 以显式参数测试新功能,不改动使用者实时配置 | 本报告借此发现补丁的 config 透传缺陷 |
| 活体探针 | 通过一次真实调用 + 服务端日志确证路由 | 视觉辅助通道的本地化即以此验证 |
| L1 / L2 / L3 | 实测 / 推算 / 推断三级可信度标注 | 报告中逐条标注 |
| 三道检验 | 机制检验 / 因果检验 / 成本检验 | 本研究用以剔除伪优化的流程 |
| 负结果 | 被实测否定的候选优化 | 与正结果同等重要:防止重复投入 |
B.5 五条元规律(提法索引)
| 编号 | 名称 | 一句话 |
|---|---|---|
| M1 | 成本引力在调用方 | 先问固定重发的字节数,而非组件参数是否最优 |
| M2 | 粒度错配制造伪两难 | 遇到取舍,先问"能否换一个单位重述" |
| M3 | 可发现性 / 可加载性 / 可用性三分 | 先问要削的是"被想起的概率"还是"被调用的能力" |
| M4 | 无损劣化优于有损优化 | 先问失效时是退回现状还是半启用 |
| M5 | 口径决定结论 | 先定哪个量由被测对象决定、哪个被环境调制 |
B.6 缩略语
| 缩略语 | 全称 | 说明 |
|---|---|---|
| KV | Key-Value(缓存) | 注意力机制的缓存张量 |
| MTP | Multi-Token Prediction | 多 token 预测,用于投机解码 |
| FA | FlashAttention | 注意力计算优化内核 |
| WDDM | Windows Display Driver Model | Windows 显示驱动模型 |
| CJK | Chinese / Japanese / Korean | 汉字字符集,本报告字数统计口径之一 |
| SOP | Standard Operating Procedure | 标准作业程序 |
| IP | Intellectual Property | 知识产权(本档案的主题领域) |