跳转到内容
Yuuki学习手记
GitHub

Pi:缓存用量、保留偏好与预热

专题入口 · 上一篇 DeepSeek Harness · 下一篇 知识地图

Pi 在这里连接两件事:把 Agent 的上下文交给不同模型服务,再把服务返回的缓存用量转换成应用可读的数据。研究它,可以从一份 usage 经转换后,数字有没有被重复计算开始。

你可能用过不同后端:一个响应叫 list,另一个叫 items,页面希望都读成同一种数据。适配层负责转换,但还得核对字段含义,不能只改名称。

Pi 的模型层也承担类似工作。这里先沿 DeepSeek → Pi → dsh 这一条用量路径看图;图中的 dsh 关系指它的 Pi 适配路径。

DeepSeek 报告总输入和命中量,Pi 的 parseChunkUsage 转成分桶用量,dsh 的 mapUsage 再映射到 Harness 字段;这条路径不传递 KV 张量

先只认三个动作:服务端报告,Pi 统一口径,Harness 接收统计。 它们没有把一万 token 的 KV 数组传回浏览器。

第一局:把 10,000 个输入 token 对上账

Section titled “第一局:把 10,000 个输入 token 对上账”

想核对实现时,沿 deepseekProvider 进入 Chat Completions 适配,再定位 parseChunkUsage。第一遍可以先看下面的数字。

假设 DeepSeek 返回:总输入 10,000,命中 8,000,未命中 2,000,输出 100。Pi 把缓存读取单独分桶:

同一批一万输入 token,被 Pi 分成八千 cacheRead 和两千 input,输出一百另计

概念DeepSeek 原始字段Pi 归一化结果
全部输入prompt_tokens = 10000input + cacheRead + cacheWrite = 10000
命中输入prompt_cache_hit_tokens = 8000cacheRead = 8000
未缓存输入prompt_cache_miss_tokens = 2000input = 2000
缓存写入此例没有单独报告cacheWrite = 0,不能据此推断没有发生服务端写入
输出completion_tokens = 100output = 100
总量total_tokens = 10100totalTokens = 10100

在该适配器中,命中计数依次读取 prompt_tokens_details.cached_tokens、prompt_cache_hit_tokens、顶层 cached_tokens,取可用值;不会把这些同义字段相加。未缓存输入由总输入减去缓存读写得到,并钳制到非负值。这里的例子与上一篇 API 口径保持一致。

这一站完成了:DeepSeek 总输入 10,000,Pi 的 input 却可以是 2,000,因为缓存读取另列 8,000。 指着图把这句话说通,就可以停下。下次用知识地图选一个问题,不必把后面的机制一次读完。

选读:上下文、适配器与缓存观测的源码入口

从上下文到缓存观测,源码怎样分工

Section titled “从上下文到缓存观测,源码怎样分工”
入口负责什么阅读时追问
agent-loop.ts先 transformContext,再 convertToLlm发送前有没有改写历史前部,工具消息是否合法
types.ts定义 cacheRetention、sessionId 和用量类型哪些是调用偏好,哪些是服务返回的观测
openai-completions.ts协议兼容、请求参数和用量归一化实际发出哪些缓存参数,供应商是否支持
cache-stats.ts从会话记录估算额外缓存未命中诊断的前提和噪声阈值是什么
cache-warmer.ts决定是否额外发请求维持缓存TTL、可重放性和预期节省是否成立
compaction.md说明历史压缩与摘要压缩改变了哪个前缀,原始历史怎样保留

这些模块处理消息、参数与统计。托管服务中的 K/V 张量由推理服务维护;convertToLlm 转换的是消息。

选读:保留多久,参数说了算吗?

cacheRetention 是偏好,具体效果沿适配器核对

Section titled “cacheRetention 是偏好,具体效果沿适配器核对”

Pi 的公共选项有 none、short、long,同时提供 sessionId。类型说明明确:供应商把偏好映射到自身支持的能力,不支持的会话特性可被忽略。选项定义

因此,看到 cacheRetention: 'long',还不能得出“DeepSeek 缓存会保持一小时”的结论。需要继续读当前模型的兼容配置、buildParams 和服务文档;none 也不能一概理解成关闭了供应商默认运行的全部缓存。sessionId 可以参与支持它的请求路由或缓存亲和,也不会代替请求历史或保存一份客户端 KV。

研究时可以逐项记下:调用选项 → 适配器产生的请求参数 → 官方支持范围 → 响应中的实际用量。这四项齐全,才知道配置究竟影响了什么。

选读:为什么失效诊断只能提供线索

缓存失效诊断,包含应用自己的假设

Section titled “缓存失效诊断,包含应用自己的假设”

cache-stats.ts 比较前一次的输入规模和当前缓存读取量,估算已有输入重新按较高成本处理的部分。它会跳过一些情况,包括首轮、尚无缓存活动证据和低于噪声阈值的差异;遇到 compaction 或 branch summary,则重置比较基线。诊断源码

这类诊断很适合提醒“去检查这轮请求”,但它无法直接观测服务端淘汰原因。文件中的 5 分钟 TTL 提示来自特定供应商的默认值;不能把这个常量当作 DeepSeek 的有效期。更换模型、裁剪上下文、修改前部和长时间空闲,需要分别保留线索。

选读:预热能省钱吗?先算额外调用的成本

TTL 表示有效期,预热在这里指过期前再发一次请求,尝试让服务端缓存继续可用。额外请求本身也可能产生费用。

CacheWarmer 可以在缓存预计过期前重放请求,并把输出上限设得很小,以尝试维持服务端缓存。它先检查模型是否声明了对应保留档位的 TTL,以及请求能否安全重放,再判断预期节省是否足够。预热实现

其经济判断可以概括为:

预期节省 = 继续请求的估计概率 × 丢失缓存后的额外费用 − 本次预热费用

这些概率和价格是决策输入,不是保证。该快照的默认设置为 streaming 模式,实际发送仍受上述条件限制;设置读取只接受全局预热配置,因为它会花钱。研究这项机制时,要把预热用量与正常任务用量一起记账。

对 Yuuki 的第一轮观测,先研究自然发生的命中即可;预热是否值得引入,应由实际停顿、命中和费用记录决定。

选读:压缩历史与最高命中率,怎样取舍

Compaction 在这里指把较长历史整理成较短的上下文,例如用摘要替换一段旧记录。它改变了模型实际读取的内容。

压缩历史会改变模型输入,也可能减少下一次可复用的前缀;它同时能降低后续输入规模,避免上下文超限。研究时比较接下来多轮的总费用与任务质量,不能只追求某一轮的最高命中率。

Pi 的压缩文档还说明,摘要调用避免为一次性提示写缓存。压缩说明这是一项调用策略,实际控制能力仍要按供应商适配器核对。把所有历史永远留在请求里,即使命中率很高,也仍会占用输入窗口并产生处理成本。

下一步按关联知识与研究路线选一个实验:核对用量、观察前缀变化,或测一次压缩前后的多轮开销。

来源与核对范围

核对日期:2026-10-24。对象为官方 earendil-works/pi 的 136b87e8a7f5 主干快照,未运行 Pi 或付费 API。这是常被称为 pi / pi-mono 的工具链;仓库早期的 PI-Desktop 调研研究的是另一层桌面应用,观察日期也不同。