利用headroom实现模型token用量压缩
背景
我的中转站(基于 new-api)和 Hermes Agent 已经部署很长一段时间了。最近才开始部署试用 headroom-proxy 做上下文压缩,效果还不错,记录一下。
说明:new-api 是 one-api 的活跃 fork,one-api 项目已停止迭代,new-api 在 one-api 基础上持续维护更新。
headroom-proxy 是一个 Go 语言开发的 LLM 代理服务器,支持在模型调用前自动压缩上下文,减少 token 用量。它提供了多种压缩策略,包括语义压缩(kcompress)、前缀冻结、工具输出压缩等,可以在保持对话质量的同时显著降低 token 消耗。
部署架构
graph TD
subgraph "client"
A[Hermes Agent]
B[OpenClaw]
C[OpenCode]
end
subgraph "本地模型链路"
D[headroom-llamaswap 8788]
E[llama-swap]
end
subgraph "中转站链路"
F[headroom 中转站 8787]
G[中转站]
end
A -->|请求| D
B -->|请求| D
C -->|请求| D
D -->|代理请求| E
A -->|请求| F
B -->|请求| F
C -->|请求| F
F -->|代理请求| G
Headroom Proxy 部署踩坑记录
CLI Help 信息压缩问题
headroom-proxy 默认压缩策略(功能全开)会遇到压缩破坏 CLI help 文本的问题,导致 CLI 工具调用受阻。
现象:当模型调用中包含 CLI 工具的 help 信息时,headroom-proxy 的语义压缩会破坏 help 文本的格式和语义,导致 CLI 工具无法正常解析参数说明。
解决方案:在 docker-compose.yaml 中配置排除 CLI help 信息的压缩,具体做法是在 compression.excluded_prefixes 中添加 CLI help 相关的文本前缀。
Kcompress CPU 指令集要求
Kcompress 语义压缩依赖 ONNX 模型,需要 AVX 512 指令集,我的群晖 NAS CPU 指令集不适配,无法在开启 kcompress 的情况下,把 headroom-proxy 部署在群晖 NAS。
分析:群晖 NAS 的 CPU(如 J4125、J5040 等)仅支持 AVX2,不支持 AVX512。Kcompress 的 ONNX 模型编译时使用了 AVX512 指令集,在群晖 NAS 上启动时会报 Illegal instruction 错误。
解决方案:群晖 NAS 上部署 headroom-proxy 时,关闭 kcompress 功能,仅使用前缀冻结和工具输出压缩策略。
Kcompress 计算资源要求
ONNX 推理对 CPU、内存有一定资源占用,也不适合在开启 kcompress 的情况下部署 headroom-proxy 在纯 IO 职责的低资源 VPS 上。
分析:Kcompress 的 ONNX 推理需要消耗额外的 CPU 计算资源和内存,在低资源 VPS 上会导致:
- CPU 占用率升高,影响其他业务
- 推理延迟增加,模型调用响应变慢
- 内存占用增加,可能触发 OOM
解决方案:在低资源 VPS 上,关闭 kcompress 功能,仅使用前缀冻结策略。
AnyLLM Key Fallback 无效
我们尝试过配置 API Key,无鉴权调用 headroom-proxy,发现 AnyLLM backend 的鉴权 fallback 机制目前无效。
现象:配置 headroom-proxy 的 API Key 后,期望在不带 API Key 的模型调用请求中,headroom-proxy 自动添加 API Key。但实际测试发现,AnyLLM backend 的鉴权 fallback 机制无法正常工作。
解决方案:好在带鉴权信息的模型调用请求,API Key 透传是正常的。因此暂时选择弃用 headroom-proxy 的鉴权 fallback 机制,继续在模型调用请求中携带 API Key。
健康检查误打 Anthropic API
现象:一次重启后,docker compose ps 显示两个 headroom 容器都是 unhealthy,但访问 http://127.0.0.1:8787/livez 和 http://127.0.0.1:8788/livez 都是正常的。
原因:headroom 的 /readyz 会触发 upstream 检查,而 upstream URL 默认来自 api_targets.anthropic。如果环境变量里没有显式设置 ANTHROPIC_TARGET_API_URL,它会去探测 https://api.anthropic.com。也就是说,即使我们实际只用 OpenAI-compatible backend(new-api 或 llama-swap),健康检查也会因为 Anthropic API 不可用而误判。
解决方案:不使用 Anthropic API 时,在 headroom 容器里加:
1 | |
同时把 Docker healthcheck 从 /readyz 改成 /livez,避免健康检查被上游慢响应拖死:
1 | |
这样 /livez 只检查 headroom 进程是否存活,/readyz 在 HEADROOM_SKIP_UPSTREAM_CHECK=1 后也会把 upstream 视为 ready。
一次模型超时后连接池被打满
现象:某次模型请求卡住后,后续请求开始出现 ReadTimeout、PoolTimeout,/v1/chat/completions 返回 502,/readyz 也可能持续 503。
原因:headroom 默认 upstream read timeout 较长,慢请求会长时间占用连接;如果同时开启多次重试,失败请求会叠加放大,最后把 httpx 连接池占满,新请求只能等连接释放,表现为 PoolTimeout。
解决方案:
减少重试放大:
1
- HEADROOM_RETRY_MAX_ATTEMPTS=1给连接/连接池超时一个更明确的值:
1
- HEADROOM_CONNECT_TIMEOUT_SECONDS=120跳过 Anthropic upstream 健康检查,避免健康检查本身参与制造假故障:
1
- HEADROOM_SKIP_UPSTREAM_CHECK=1恢复时优先只重启 headroom 容器,不先动 llama-swap 或模型服务:
1
2cd ~/workspace/docker-compose-confs/headroom
docker compose up -d如果重启 headroom 后仍然卡住,再单独验证 llama-swap 上游:
1
curl --max-time 20 -sS http://192.168.31.125:9292/v1/models如果上游
llama-swap自己也不响应,才考虑处理 llama-swap 服务或模型加载问题。
本次验证结果:按上述配置修改 compose 并重启后,两个 headroom 容器均变为 healthy;8787 /readyz 和 8788 /readyz 都返回 200;使用 8788 无鉴权实际调用 qwopus3.6:35b-a3b-q5-128k 返回 200,约 20.5 秒完成。
功能配置优化
根据社区讨论和实际踩坑经验,我们对 headroom-proxy 的功能配置做了以下优化。
主要配置调整
压缩策略配置:根据硬件条件选择压缩策略
- 群晖 NAS:关闭 kcompress,使用前缀冻结 + 工具输出压缩
- 低资源 VPS:关闭 kcompress,仅使用前缀冻结
- 高性能 PC:全功能开启(kcompress + 前缀冻结 + 工具输出压缩)
CLI Help 排除:添加 CLI help 信息的压缩排除规则
健康检查修正:不使用 Anthropic API 时,设置
HEADROOM_SKIP_UPSTREAM_CHECK=1,Docker healthcheck 改用/livez超时/重试收敛:设置
HEADROOM_RETRY_MAX_ATTEMPTS=1和HEADROOM_CONNECT_TIMEOUT_SECONDS=120,避免一次慢请求放大成连接池打满鉴权配置:放弃鉴权 fallback,模型调用请求中携带 API Key
配置示例
1 | |
实测数据
| 指标 | 8787 端口(中转站 nex-agi/nex-n2-pro:free) | 8788 端口(本地 llama-swap qwopus3.6:35b-a3b-q5-128k) |
|---|---|---|
| API 请求数 | 48 | 495 |
| 总输入 token | 936,468 | 21,791,478 |
| 总输出 token | 41,405 | 136,511 |
| 请求压缩次数 | 16 | 353 |
| 压缩节省 token | 622,160 | 23,632,292 |
| 综合压缩率 | 39.92% | 52.03% |
| 平均压缩率 | 41.1% | 38.2% |
| 最佳压缩 | 44.4%(97,688 → 54,280 tokens) | 88.1%(572,012 → 68,132 tokens) |
| 前缀冻结次数 | 17 | 103 |
| 前缀缓存命中率 | 0.5% | 98.3% |
| 请求缓存命中率 | 42.9% | 97.1% |
| 平均延迟 | 31,973ms | 15,242ms |
| 最大延迟 | 217,235ms | 282,213ms |
| 平均 overhead | 290.09ms | 110.93ms |
压缩策略分布:
- 8787 端口:
smart_crusher74 次,text6 次,log6 次 - 8788 端口:
smart_crusher337 次,text65 次,log8 次
最佳压缩示例:
- 8787:97,688 → 54,280 tokens,节省 43,408 tokens(44.4%)
- 8788:572,012 → 68,132 tokens,节省 503,880 tokens(88.1%)
数据解读
综合压缩率:8788 端口(本地 llama-swap)综合压缩率 52.03%,8787 端口(中转站)39.92%。本地 llama-swap 的压缩效果更优,可能因为本地模型上下文更长、压缩机会更多。
最佳压缩:8788 端口最佳压缩达到 88.1%(572,012 → 68,132 tokens),8787 端口最佳压缩 44.4%(97,688 → 54,280 tokens)。长上下文场景下压缩效果显著,8788 端口单次节省近 50.4 万 tokens。
缓存命中率:8787 端点请求缓存命中率 42.9%,前缀缓存命中率 0.5%;8788 端点请求缓存命中率 97.1%,前缀缓存命中率 98.3%。8788 的缓存命中表现更稳定,说明本地 llama-swap 路径更利于复用上下文。
压缩策略:
smart_crusher是主力压缩策略,8788 端口占 337 次,8787 端口占 74 次。8788 端口还有text策略 65 次和少量log策略,说明本地 llama-swap 的上下文类型更丰富。延迟:8788 端口平均 overhead 110.93ms,8787 端口 290.09ms。本地 llama-swap 的额外开销更小,符合预期。
压缩策略:
smart_crusher是主力压缩策略,8788 端口占 95 次(76.6%),8787 端口占 18 次(85.7%)。8788 端口还有text策略(34 次)和少量log、kompress策略,说明本地 llama-swap 的上下文类型更丰富。延迟:8788 端口平均 overhead 89.96ms,8787 端口 192.15ms。本地 llama-swap 的额外开销更小,符合预期。
总结
headroom-proxy 是一个实用的本地 token 压缩代理,但在实际部署中需要根据硬件条件选择合适的压缩策略。主要踩坑点包括 CLI help 文本压缩破坏、kcompress 的 CPU 指令集和资源要求、鉴权 fallback 机制无效、健康检查误打 Anthropic API,以及一次模型超时后连接池被打满等。通过合理配置,可以在保证对话质量的同时有效降低 token 消耗。