利用headroom实现模型token用量压缩

背景

我的中转站(基于 new-api)和 Hermes Agent 已经部署很长一段时间了。最近才开始部署试用 headroom-proxy 做上下文压缩,效果还不错,记录一下。

说明:new-apione-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/livezhttp://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
- HEADROOM_SKIP_UPSTREAM_CHECK=1

同时把 Docker healthcheck 从 /readyz 改成 /livez,避免健康检查被上游慢响应拖死:

1
2
3
4
5
6
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8787/livez"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s

这样 /livez 只检查 headroom 进程是否存活,/readyzHEADROOM_SKIP_UPSTREAM_CHECK=1 后也会把 upstream 视为 ready。

一次模型超时后连接池被打满

现象:某次模型请求卡住后,后续请求开始出现 ReadTimeoutPoolTimeout/v1/chat/completions 返回 502,/readyz 也可能持续 503。

原因:headroom 默认 upstream read timeout 较长,慢请求会长时间占用连接;如果同时开启多次重试,失败请求会叠加放大,最后把 httpx 连接池占满,新请求只能等连接释放,表现为 PoolTimeout

解决方案

  1. 减少重试放大:

    1
    - HEADROOM_RETRY_MAX_ATTEMPTS=1
  2. 给连接/连接池超时一个更明确的值:

    1
    - HEADROOM_CONNECT_TIMEOUT_SECONDS=120
  3. 跳过 Anthropic upstream 健康检查,避免健康检查本身参与制造假故障:

    1
    - HEADROOM_SKIP_UPSTREAM_CHECK=1
  4. 恢复时优先只重启 headroom 容器,不先动 llama-swap 或模型服务:

    1
    2
    cd ~/workspace/docker-compose-confs/headroom
    docker compose up -d
  5. 如果重启 headroom 后仍然卡住,再单独验证 llama-swap 上游:

    1
    curl --max-time 20 -sS http://192.168.31.125:9292/v1/models

    如果上游 llama-swap 自己也不响应,才考虑处理 llama-swap 服务或模型加载问题。

本次验证结果:按上述配置修改 compose 并重启后,两个 headroom 容器均变为 healthy8787 /readyz8788 /readyz 都返回 200;使用 8788 无鉴权实际调用 qwopus3.6:35b-a3b-q5-128k 返回 200,约 20.5 秒完成。

CCR 原文查询失败:fresh marker 生成后 /v1/retrieve 404

现象:构造一组新的 tool output 让 headroom-proxy 生成 fresh <<ccr:...>> marker,随后立刻用同一个 hash 查询 /v1/retrieve,返回 404 Entry not found (CCR TTL: 1800 seconds)

排查过程

  1. 先排除了 TTL 过期:fresh marker 刚生成就查询,仍返回 404。
  2. 检查两个 headroom 容器日志,没有看到 store 写入失败、SQLite 错误、权限问题或进程异常。
  3. 在两个容器内直接写入最小 CompressionStore.store(..., explicit_hash=...),再查询 live /v1/retrieve,返回 200。
  4. 这说明 Python CompressionStore/v1/retrieve 路由和容器健康状态本身都没问题。

原因判断:更可能是 Rust 生成的 <<ccr:HASH>> 没有成功 mirror 到 Python compression_store。也就是说,marker 成功生成了,但 Python store 里还没有对应 hash。

解决方式

  1. 先确认 CCR marker 注入没有被禁用:

    1
    2
    - HEADROOM_NO_CCR_INJECT_TOOL=0
    - HEADROOM_NO_CCR_MARKER=0
  2. 固定 worker,避免 compress 与 retrieve 落到不同 worker:

    1
    - HEADROOM_WORKERS=1
  3. 将 CCR backend 显式切到 SQLite,便于跨 worker 共享并验证:

    1
    2
    3
    - HEADROOM_CCR_BACKEND=sqlite
    - HEADROOM_CCR_TTL_SECONDS=1800
    - HEADROOM_CCR_SQLITE_PATH=/root/.headroom/ccr_store.db
  4. 用真实长 role=tool 输出复测 /v1/compress,确认会生成 <<ccr:...>> marker,再立刻对 hash 调 /v1/retrieve,两个端口都返回 200。

本次验证结果:重启两个 headroom 容器后,HEADROOM_WORKERS=1HEADROOM_CCR_BACKEND=sqliteHEADROOM_CCR_TTL_SECONDS=1800HEADROOM_CCR_SQLITE_PATH=/root/.headroom/ccr_store.db 均已生效;真实长工具输出场景下可生成 CCR marker,并能通过 87878788 正常 retrieve。

功能配置优化

根据社区讨论和实际踩坑经验,我们对 headroom-proxy 的功能配置做了以下优化。

主要配置调整

  1. 压缩策略配置:根据硬件条件选择压缩策略

    • 群晖 NAS:关闭 kcompress,使用前缀冻结 + 工具输出压缩
    • 低资源 VPS:关闭 kcompress,仅使用前缀冻结
    • 高性能 PC:全功能开启(kcompress + 前缀冻结 + 工具输出压缩)
  2. CLI Help 排除:添加 CLI help 信息的压缩排除规则

  3. CCR 显式配置:确认 CCR marker 注入开启,并固定 worker 与 SQLite backend

    • HEADROOM_NO_CCR_INJECT_TOOL=0:允许注入工具调用
    • HEADROOM_NO_CCR_MARKER=0:允许注入压缩标记
    • HEADROOM_WORKERS=1:避免 compress 与 retrieve 分散到不同 worker
    • HEADROOM_CCR_BACKEND=sqlite:使用 SQLite 作为 CCR 存储后端
    • HEADROOM_CCR_TTL_SECONDS=1800:显式固定 CCR TTL
    • HEADROOM_CCR_SQLITE_PATH=/root/.headroom/ccr_store.db:显式指定 SQLite 路径
  4. 健康检查修正:不使用 Anthropic API 时,设置 HEADROOM_SKIP_UPSTREAM_CHECK=1,Docker healthcheck 改用 /livez

  5. 超时/重试收敛:设置 HEADROOM_RETRY_MAX_ATTEMPTS=1HEADROOM_CONNECT_TIMEOUT_SECONDS=120,避免一次慢请求放大成连接池打满

  6. 鉴权配置:放弃鉴权 fallback,模型调用请求中携带 API Key

配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# docker-compose.yaml 关键配置节
services:
headroom:
environment:
- HEADROOM_DISABLE_KOMPRESS=1 # 群晖/低资源VPS上关闭
- HEADROOM_NO_CCR_INJECT_TOOL=0 # 允许注入工具调用
- HEADROOM_NO_CCR_MARKER=0 # 允许注入压缩标记
- HEADROOM_WORKERS=1 # 固定单 worker,避免 CCR 跨 worker 查询失败
- HEADROOM_CCR_BACKEND=sqlite # 使用 SQLite 作为 CCR backend
- HEADROOM_CCR_TTL_SECONDS=1800 # 显式固定 CCR TTL
- HEADROOM_CCR_SQLITE_PATH=/root/.headroom/ccr_store.db # 显式指定 SQLite 路径
- HEADROOM_PROTECT_RECENT=5 # 保护最近5轮对话不被压缩
- HEADROOM_COMPRESS_SYSTEM_MESSAGES=false # 系统消息不压缩
- HEADROOM_SKIP_UPSTREAM_CHECK=1 # 不用 Anthropic 时跳过 upstream 健康检查
- HEADROOM_RETRY_MAX_ATTEMPTS=1 # 避免慢请求重试放大
- HEADROOM_CONNECT_TIMEOUT_SECONDS=120 # 连接/连接池超时
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8787/livez"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s

实测数据

指标 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_crusher 74 次,text 6 次,log 6 次
  • 8788 端口:smart_crusher 337 次,text 65 次,log 8 次

最佳压缩示例:

  • 8787:97,688 → 54,280 tokens,节省 43,408 tokens(44.4%)
  • 8788:572,012 → 68,132 tokens,节省 503,880 tokens(88.1%)

数据解读

  1. 综合压缩率:8788 端口(本地 llama-swap)综合压缩率 52.03%,8787 端口(中转站)39.92%。本地 llama-swap 的压缩效果更优,可能因为本地模型上下文更长、压缩机会更多。

  2. 最佳压缩:8788 端口最佳压缩达到 88.1%(572,012 → 68,132 tokens),8787 端口最佳压缩 44.4%(97,688 → 54,280 tokens)。长上下文场景下压缩效果显著,8788 端口单次节省近 50.4 万 tokens。

  3. 缓存命中率:8787 端点请求缓存命中率 42.9%,前缀缓存命中率 0.5%;8788 端点请求缓存命中率 97.1%,前缀缓存命中率 98.3%。8788 的缓存命中表现更稳定,说明本地 llama-swap 路径更利于复用上下文。

  4. 压缩策略smart_crusher 是主力压缩策略,8788 端口占 337 次,8787 端口占 74 次。8788 端口还有 text 策略 65 次和少量 log 策略,说明本地 llama-swap 的上下文类型更丰富。

  5. 延迟:8788 端口平均 overhead 110.93ms,8787 端口 290.09ms。本地 llama-swap 的额外开销更小,符合预期。

  6. 压缩策略smart_crusher 是主力压缩策略,8788 端口占 95 次(76.6%),8787 端口占 18 次(85.7%)。8788 端口还有 text 策略(34 次)和少量 logkompress 策略,说明本地 llama-swap 的上下文类型更丰富。

  7. 延迟:8788 端口平均 overhead 89.96ms,8787 端口 192.15ms。本地 llama-swap 的额外开销更小,符合预期。

总结

headroom-proxy 是一个实用的本地 token 压缩代理,但在实际部署中需要根据硬件条件选择合适的压缩策略。主要踩坑点包括 CLI help 文本压缩破坏、kcompress 的 CPU 指令集和资源要求、鉴权 fallback 机制无效、健康检查误打 Anthropic API、CCR fresh marker 原文查询失败,以及一次模型超时后连接池被打满等。通过合理配置,可以在保证对话质量的同时有效降低 token 消耗。


利用headroom实现模型token用量压缩
https://bipedalbit.net/2026/06/18/利用headroom实现模型token用量压缩/
作者
Bipedal Bit
发布于
2026年6月18日
许可协议