cache-hit-debug
暂无简介
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-15
- Stars
- ★ 1
- Forks
- 0
03 SKILL.md 正文
LLM 前缀缓存命中排查(cache-hit-debug)
用户/自检发现缓存命中率异常低(面板 cache_hit_rate ~1% 级别)或想降低 prompt 成本时,按以下流程系统排查。核心:先实验验证机制,再查配置链,最后改+重启验证。
为什么需要这个 skill(血泪教训)
反模式(实测踩坑):
- ❌ 凭先验下结论:"这 provider 没有缓存"——短实验(131 token)不命中就误判无前缀缓存,实际是短 prompt 阈值效应
- ❌ 只查 .env 单层配置——预算有多层 min 链(全局/provider/模型窗口),真实压制点在 provider 级
- ❌ 以为 .env LLM_MODEL 就是实际运行模型——session model_override 会覆盖(配额降级残留)
- ❌ refresh_config 后以为预算已生效——provider 级 history_budget_chars 热重载不作用于运行中引擎,必须重启
- ❌ 忽略时间戳时区——event_logs 是 UTC,本地是 CST(+8),统计先对齐
标准流程(5 步)
Step 1: 缓存类型区分实验(先验证机制)
用**长 prompt(>3000 token,建议 20+ 条消息)**发 3 个请求:
- A. warm(初始序列)→ 期望 0 命中
- B. 完全同 payload 重发 → 验证精确缓存
- C. 前缀 + 追加 1 条 → 验证前缀缓存(关键!追加命中率应 ~97%)
# 非流式 + usage 完整字段(流式需 stream_options.include_usage)
r = httpx.post(url, json={"model": model, "messages": msgs}, timeout=120)
u = r.json().get("usage", {})
print(u.get("cached_tokens"), u.get("prompt_tokens"))
判定:B 全命中 = 精确缓存存在;C 命中(仅新增部分未命中)= 前缀缓存存在——追加不破坏命中,修改已提交序列(压缩/重排)必断点。
Step 2: TTL 排除实验(排除次要因素)
同 payload 间隔 30s/60s/120s/180s/300s 重发,看 cached_tokens:
- 全部命中 → TTL ≥ 5 分钟,排除 TTL 因素(会话内调用间隔通常 <60s)
- 超时后 miss → TTL 是因素,需控制调用节奏
Step 3: 预算链定位(查真实生效值)
effective_budget = min(
显式 runtime/history override(如有),
显式全局 HISTORY_MAX_CHARS(如有),
provider 级 history_budget_chars(如有),
当前实际路由模型的物理输入预算(context - output reserve + safety margin)
)
HISTORY_MAX_CHARS 未配置时没有一层隐式 100K/200K cap;不要为了“补齐三层”主动加它。
真相源是 event_logs 的 request.meta(每轮含 model/budget/history_chars/reasoning_chars/provider_visible_chars),不是 .env:
# 查每轮真实 budget
reqs = [(json.loads(l)['ts'], json.loads(l)['payload']) for l in open(log) if 'request.meta' in l]
for ts, p in reqs[-5:]:
print(ts[11:19], p.get('model'), p.get('budget'),
p.get('history_chars'), p.get('reasoning_chars'), p.get('provider_visible_chars'))
注意:request.meta 里 model 字段才是实际运行模型(可能被 session model_override 覆盖);budget 才是真正生效预算(min 链结果)。先查 model 再查对应 provider 的预算配置。
三字符字段含义(务必一起看,别只看 history_chars):history_chars = 本轮提交的 message content 字符数(不含 reasoning_content);reasoning_chars = 本轮重新提交的历史 reasoning_content 字符数;provider_visible_chars = 更接近实际 provider 输入的可见结构规模(messages + tool schemas,不含传输头/凭据)。注意:history_chars 是遥测口径、不含 reasoning_content,单独看会低估实际输入规模——本地实测过 history_chars≈50K、reasoning_chars≈41K 的场景:按 history_chars 看 budget 看似够,但真实提交体积还要加上 reasoning 历史重发。压缩器预算判定其实已含 reasoning_content(history.py::_wire_size 口径),所以问题出在"遥测口径低估"而非"压缩器看不见 reasoning"。排查输入体积/窗口压力时,以 provider_visible_chars 为主要观察量;排查前缀稳定性时,看 request.usage 的 prefix_changed / stable_prefix_fp / prefix_change_reason / cache_prefix_epoch,并结合 cache_hit_rate / uncached_prompt_tokens。history_chars 与 reasoning_chars 用于拆分输入来源。
Step 4: 修复(只改被证据证明是压制点的那一层)
data/providers.json:仅本地慢模型/已证实 endpoint 性能问题才设history_budget_chars.env:HISTORY_MAX_CHARS是可选 operator 全局 cap;无明确目的优先保持未配置adjust_strategy history_budget:运行时临时 override,只在当前任务确有需要时使用
历史量估算:session 总字符 vs 实际 provider payload 不是一回事;以 request.meta / request.usage 为准。字符↔token 只能估算,当前 generic 估算约 0.6 chars/token,不能拿它当 provider tokenizer 的精确事实。
Step 5: 重启 + 验证
bash scripts/restart_system.sh restart(回 y 确认)→ 触发一次调用 → request.meta budget 变为新值才算生效(热重载对 provider 预算无效)。
压缩友好策略(根因修复)
命中率 = 稳定期占比(压缩轮必 miss 一次,物理必然)。优化方向:
- 留缓冲:压缩目标从"裁到预算 100%"改为"预算×0.6-0.7"(留 40% 增长空间 → 稳定期从几轮延长到几十轮)
- 锚定:system + 注入不参与压缩(前缀稳定锚)
- 放大预算:预算 > 会话历史总量 → 不压缩 → 纯追加(命中 ~97%)
归因与恢复(2026-08-17 补充,EVO-20260817-5b991577)
先归因再告警——低命中分两类,处置完全不同:
- 破坏型(窗口内锚点前移 >0):前缀被压缩/注入破坏 → 告警 + 拦截(强制保留锚点头部恢复稳定)
- 设计型(锚点未前移):命中率 = 设计运行点(小窗口下前缀占比 ~9% → 命中率天然 6-8%,物理决定)→ 只观察不告警不拦截
恢复条件用可达成信号:拦截期锚点不再前移连续 N 轮即解除;加超时兜底(N 轮未恢复 → 恢复失败短消息 + 解除,每进程一次防刷屏)。禁止用绝对命中率阈值做恢复判定(如 ≥80%——设计型 8% 永远达不到 → 永久拦截空转死锁)。
注入漂移排查(实测高发):协调通道 inbox(data/interop/lfl_to_dsh/pending/)消息滞留 → 每轮注入 system 段 → 前缀每轮漂移 → 命中率持续低。检查:ls data/interop/lfl_to_dsh/pending/ | wc -l,有堆积即按协议归档(notify 自动归档已上线,coordinate/task 人工处理)。
命中率数字不是目标,成本才是——但必须用实测价目核算,勿凭直觉。"小窗口命中占比小但总成本低"的直觉被实测推翻(2026-08-17 教训):高 miss 占比下全价 miss 才是成本大头。**成本核算(真实 tokens 聚合 + 当前 provider 价目)按 cache-cost skill 执行,勿在本技能内重复维护价格/成本案例。
反模式清单
- ❌ 短 prompt 实验(<3000 token)下缓存结论
- ❌ 只改 .env 不查 provider 级/只改 provider 级不查实际运行 model
- ❌ refresh 后不重启就宣称生效
- ❌ 压缩轮被断点就断言"缓存坏了"——这是物理必然,看稳定期占比
- ❌ 统计事件时忽略 UTC vs 本地时区
- ❌ 用绝对命中率阈值做恢复判定(设计型低命中 → 死锁)
- ❌ 不区分破坏型/设计型就告警(设计型会误报 + 永久拦截空转)
- ❌ 只查 .env 不查 interop pending 注入(注入漂移是高频根因)
实测参考(2026-08-16 本环境)
| 项 | 值 |
|---|---|
| 前缀追加命中率 | 97.3%(9472/9735) |
| 修复前命中率 | ~1%(每轮压缩) |
| 修复后 | 99.2%(长 run 稳定期) |
| TTL | ≥300s(实测 5 分钟全命中) |
| 模型 | kimi/k3、deepseek-v4-flash(窗口均 1M token) |
04 同仓库更多 Skills
security-audit
Audit project for security issues.
metrics
Collect project metrics.
lint
Lint the repo using project config.
report
值班报告生成
deploy
标准部署流程(多行 YAML frontmatter 演示)
web-self-restart-triage
暂无简介