【免费下载链接】pxpipe
cut Claude Code token usage by rendering text context as images
本指南解析 pxpipe(将 Claude Code 文本上下文渲染为图片以削减 token 用量)在"对话不断增长、新内容不断变旧"的前提下,如何保证历史图片压缩始终开启却不破坏 Anthropic 前缀提示缓存。读完本文,你将理解 pxpipe 的量化折叠边界(quantized boundary)为何是"阶梯"而非"斜坡"、一次性
cache_create燃尽(one-time burn)如何被双重门控约束,以及调用方的cache_control标记如何被"搬迁"(relocate)而非"新增"(add),从而让缓存断点恰好落在稳定与易变内容的接缝上。
本文是 CACHING_AND_SAVINGS.md(承载定价数学)的概念伴侣。二者若有不一致,以代码为准:
- src/core/history.ts —
collapseHistory、量化边界、blocksToText - src/core/transform.ts — splice、cache-mark 搬迁、收益门控
- src/core/baseline.ts —
CACHE_CREATE_RATE = 1.25、CACHE_READ_RATE = 0.1
0. 本文要回答的问题
如果我们把过去的轮次(turn)转成图片,前缀不就改变了吗——缓存不就失效了吗?而且既然新内容迟早会变成旧内容,边界岂不是每一轮都在移动,导致持续不断地重新成像、重新换缓存 key?
短答案:pxpipe 确实把历史成像,它是缓存安全的,"旧"由一个量化(quantized)边界定义,因此重新换 key 是罕见的一次性事件,而非每轮抖动。缓存断点(即"mark")正是让这份成本变成"一次性"的接缝。
1. 是的——我们始终开启历史成像
collapseHistory(src/core/history.ts)始终开启、无条件下发(transform.ts 中标注为 "Variant C history-image compression. ALWAYS-ON, unconditional")。它遍历messages[],找出最大的工具调用已闭合的前缀段(tool-closed prefix run),用blocksToText将其序列化为文本——该函数会包含 assistant 回复、tool_use参数与tool_result内容——然后把这段文本渲染成 PNG 图片块,放入一条前置的合成 user 消息中。
从 src/core/history.ts 源码看,blocksToText对块类型的处理如下:
| 块类型 | 序列化行为 |
|---|---|
text | 原文文本 |
tool_use | [tool_use <name>]\n+ 紧凑 JSON(JSON.stringify,不缩进——美化会放大文本约 5 倍,且渲染器是按行感知的) |
tool_result | [tool_result(错误时附 (error))]\n+ 内部文本;内部图片块折叠为[image]占位符,避免双重编码 |
image | [image]占位符 |
thinking及其它 | 静默丢弃——只有最近一条 assistant 轮次需要逐位(bit-perfect)往返,而它天然位于 live tail 中 |
保持为文本的是"活尾巴"(live tail):最后keepTail轮,加上任何处于未闭合工具序列内的内容,以及最近一条 assistant 的 thinking 签名(它必须逐位往返)。
所以过去的 assistant 回复绝对会变成图片。本文其余部分探讨的是:为什么这样做是安全的。
2. "老化"问题真实存在
每一轮,活尾巴增长,最旧的尾巴轮次变得有资格成为"旧"内容。因此文本↔图片边界倾向于随时间前移。危险在于:
如果"旧"意味着*"除了最后 keepTail 轮之外的一切"*——一个每轮移动的窗口——那么边界每轮都会前进一条消息。被折叠的集合每轮都会改变 → 渲染出的 PNG 字节每轮都会改变 →每轮都是新的缓存 key → 每轮都在整个历史上付一次
cache_create(1.25×)。
这并非假设。它就是2026-05-19 的回归(bug #28),定价文档引用它作为−250% "savings"的例子。移动边界是缓存粉碎机(cache shredder)。
3. 修复方案:"旧"是量化的,而非移动窗口
边界被吸附到以collapseChunk条消息(默认50)为单位的固定网格上。摘自 src/core/history.ts:
const rawCutoff = messages.length - o.keepTail; const cutoff = o.collapseChunk > 0 ? Math.min(rawCutoff, Math.max(minCollapsePrefix + protectedPrefix, Math.floor(rawCutoff / o.collapseChunk) * o.collapseChunk)) : rawCutoff; const boundary = findClosedPrefixBoundary(messages, cutoff);Math.floor(rawCutoff / collapseChunk) * collapseChunk是整个技巧所在:成像资格以离散跳跃前进,而非连续前进。随后findClosedPrefixBoundary将其拉回到最近的工具已闭合点,确保图片永远不会拆散一对开着的tool_use/tool_result。
从 src/core/history.ts 的实现细节看,findClosedPrefixBoundary通过openSet追踪跨消息的tool_useid 与tool_result配对,并特殊处理并行工具调用:相邻的 assistant-call/user-result 对被视作一轮工具调用(一些 Anthropic 客户端会以 A-call/A-result、B-call/B-result 的序列化方式发出并行批次),因此两个相邻 pair 之间的看似闭合的空隙不是安全的折叠边界。
它是一个阶梯,不是斜坡。设keepTail = 4、collapseChunk = 50:
messages.length | rawCutoff | 量化后的cutoff | 被成像的内容 |
|---|---|---|---|
| 54 | 50 | 50 | msgs[0..50) |
| 80 | 76 | 50 | msgs[0..50) ←相同 |
| 103 | 99 | 50 | msgs[0..50) ←相同 |
| 104 | 100 | 100 | msgs[0..100) ←跳跃 |
在大约 50 轮内,被折叠集合是一组固定的消息→ 字节完全一致的 PNG → 前缀全程读取为热缓存(cache_read,0.1×)。新内容堆积在文本尾巴里;只有当对话越过下一个 50 的倍数时,它才跨入"已成像"区域。
historyImageSha8(src/core/transform.ts)按请求记录图片哈希,正是为了让这一点可从events.jsonl验证:边界保持期间,相邻的折叠轮次必须报告相同的history_image_sha8。一个逐轮移动的哈希正是 bug 复发的信号。注意该函数通过合成消息的 banner 文本(HISTORY_SYNTHETIC_INTRO)定位历史图片消息,而不是假设它是messages[0]——当存在 slab 锚点(protectedPrefix = slabAnchorIdx + 1)时,messages[0]是受保护的 slab 消息,按位置硬编码哈希会错测成"slab 稳定性"(#11 归因问题)。相关测试见 tests/history.test.ts("quantizes the collapse boundary onto a stable grid — image bytes stay byte-identical within a chunk window":mk(20)与mk(22)两次折叠的图片字节完全一致,而mk(70)跨越网格窗口后允许改变)与 tests/cache-bust-attribution.test.ts。
4. 一次性燃尽,以及控制它的门控
一次块跨越(chunk crossing)会改变成像区域的字节一次 → 那一轮支付一次新的cache_create。这就是一次性燃尽(one-time burn)。它:
- 每个稳定窗口一次性,而非每轮——大约每
collapseChunk轮一次 create,分摊在其间的热读之上。 - 双向门控,只在能回本时才发生:
对称燃尽项(isCompressionProfitable,src/core/transform.ts):
burnImageSide = priorWarmTokens × (CACHE_CREATE_RATE − CACHE_READ_RATE) // ≈ 1.15× 被放弃的热前缀 compress iff imageTokens + burnImageSide < textTokens + burnTextSide该燃尽项是对称的(burnTextSide = priorWarmImageTokens × (1.25 − 0.10)):切换模式会使当时处于热缓存的一侧失效、支付一次 cache_create。燃尽项加在将会翻转的那一侧——把会话钉在当前模式,直到每轮节省超过燃尽成本(反抖动 / anti-flapping)。
历史摊销门控(isCompressionProfitableAmortized):
accept iff I × (CC + CR×(N−1)) < T × CR × N CC = 1.25, CR = 0.10其中N = historyAmortizationHorizon("假设此前缀还会被复用 N 次")。默认N = 1,这是刻意保守的——N = 1时折叠几乎从不获胜,因此宿主一旦观察到其缓存活得足够长、足以摊销 create,就提高N。N ≈ 10时,只要图片低于约 0.7× 文本,折叠就获胜。源码注释给出的参考阈值:N=5 → I < 0.30×T;N=10 → I < 0.47×T(src/core/transform.tsisCompressionProfitableAmortized)。当horizon <= 1时回退到逐轮冷门控。相关测试见 tests/history.test.ts 的 "isCompressionProfitableAmortized — multi-turn horizon gate"。
- 冷启动免费。在第 1 轮 / 全新对话中,
priorWarmTokens与priorWarmImageTokens默认为0,燃尽项完全归零——没有可失去的热缓存,因此从一开始就成像不会破坏任何东西。
5. 统一原则:cache mark 就是接缝
以上所有内容都归结为一条规则:
字节稳定的内容放在 cache mark 之前;每轮易变的内容放在它之后。"一次性"是相对该 mark 定义的。
pxpipe从不新增断点——Task #21:它把调用方已有的cache_control标记搬迁到由该标记覆盖的内容所产生的最后一张静态图片上(src/core/transform.ts 的relocateAnchorToHistoryImage;文档表述为"mark 骑在最后一张图片上")。于是断点恰好落在稳定↔易变接缝处:
[ intro / static slab image(s) ] ← 稳定 [ last static image ] ← cache_control ◄── mark(搬迁,而非新增) ─────────────── cache 断点 ─────────────── [ end-marker + dynamic <env> + billing line ] ← 每轮都变 [ history image, current user content ] ← 在 mark 之后从 src/core/transform.ts 的relocateAnchorToHistoryImage实现看,搬迁是纯搬迁:只有当 slab 图片已携带锚点时它才动作,因此总标记数永不增加。它通过 banner 文本识别合成历史消息,把 mark 钉在carryOverImageOrdinal(collapseHistory报告的、最后一个完全网格对齐的字节稳定历史图片)上,而非最新、仍在增长的最末块——后者正是 #11 的翻车点:钉在最后一帧会导致每次窗口前进都换 key。落点兜底为合成消息的最后一张图片;slab 锚点则通过'[End of rendered context.]'边界文本定位,找不到锚点时直接返回(绝不新增标记)。
两个条件使燃尽成为一次性,布局同时满足两者:
- 到 mark 为止的一切在轮次间字节完全一致——由量化边界(§3)保证。
- 所有易变内容都在 mark 之后——billing line、动态
<env>、当前 user 消息都被 splice 到断点之后,永不污染前缀缓存 key。
当两者都成立时,到 mark 为止的前缀每轮都读热缓存,前缀仅在 mark 处/之前的字节真正改变时(即初始的文本→图片翻转与每次块跨越)才重新换 key。这就是一次性 create;其间的每一轮都是热读。
为什么 slab 受到保护
承载 slab 的头部 user 消息通过protectedPrefix(src/core/transform.ts:slabAnchorIdx + 1)免于折叠。如果历史折叠将其卷入,blocksToText会把 system-prompt/tool-docs 图片降级为[image]占位符,slab 的cache_control锚点就会消失——于是每次网格跨越都会使整个前缀失效。把它排除在折叠范围之外,将其钉在前端作为稳定的缓存锚点,并把历史图片放在它之后。测试 tests/anthropic-cache-align.test.ts 与 tests/history.test.ts 均断言messages[0]是承载 slab 的 user 消息而非合成历史消息,且 slab 图片真实存在。
6. 这正是 Claude Code 自身的模型
Claude Code 原生就带一个大稳定前缀(system prompt、tool docs、<system-reminder>、较早的历史),以cache_control断点收尾,后面跟着一段薄薄的每轮尾部。pxpipe 保留了这一精确形态。它只是:
- 交换稳定前缀的表示——冗长文本 → 字节稳定的图片。
- 带着 mark 一起移动(搬迁而非新增),使接缝保持在相同的逻辑位置,且 pxpipe 不消耗 4 个断点预算中的任何一个(测试 tests/anthropic-cache-align.test.ts 断言输出标记数 ≤ 输入标记数,且搬迁后恰好为 1 个)。
- 量化历史边界,使成像区域的字节只在块跨越时改变,保持 Claude Code 赖以工作的原生字节稳定性。
一次性不是运气——它由把 mark 锚定到最后一张稳定图片、并把所有每轮抖动推到它之后来强制执行。
7. 默认参数(真源:HISTORY_DEFAULTS,src/core/history.ts)
| 选项 | 默认 | 含义 |
|---|---|---|
keepTail | 4 | 最近轮次总是保持为文本(活尾巴)。 |
minCollapsePrefix | 10 | 少于这么多轮就不值得折叠。 |
collapseChunk | 50 | 边界吸附的网格 → 阶梯之间图片字节稳定。 |
protectedPrefix | 0 | 开头的消息永不被折叠(运行时设为 slab 锚点 + 1)。 |
cols | 100 | 软换行列数提示(历史渲染为密集单列)。 |
相关(位于TransformOptions,src/core/transform.ts):
| 选项 | 默认 | 含义 |
|---|---|---|
HISTORY_CHARS_PER_TOKEN | 2.0 | 历史 cpt 拟合(Opus 4.7 / Fable 5 tokenizer)。 |
historyAmortizationHorizon | 1 | 摊销门控中的N;一旦证明缓存长寿就调高。 |
旋钮直觉
- 更大的
collapseChunk→ 更少的翻转/燃尽(缓存更便宜),但尾巴中未成像的文本更多(压缩更少)。 - 更小的
collapseChunk→ 更激进的压缩,但燃尽更频繁。 - 更大的
historyAmortizationHorizon→ 更愿意为未来的热读现在吃一次 create;仅当缓存确实能活那么久时才安全。
8. 一段话总结
我们始终开启地成像历史轮次,但"旧"是一个量化边界(floor((len − keepTail) / collapseChunk) × collapseChunk,并吸附到工具闭合点),因此成像区域的字节在整个collapseChunk窗口内保持一致,前缀每轮都读热缓存。新内容只在块跨越时老化进图片;每次跨越花费一次单独的cache_create,由对称燃尽项与摊销视野双重门控,只在热读能回本时触发。整件事之所以成立,是因为调用方的cache_control标记被搬迁(从不新增)到最后一张稳定图片上,把每个字节稳定的东西放在接缝之前、每个每轮易变的东西放在它之后——这正是 Claude Code 已经依赖的前缀缓存形态。
延伸阅读:定价数学与诚实核算见 docs/CACHING_AND_SAVINGS.md(含 warm/cold 工作示例与baseline_eff − actual_eff推导);缓存对齐契约的 TDD 测试见 tests/anthropic-cache-align.test.ts 与 tests/cache-bust-attribution.test.ts;缓存稳定性端到端验证见 tests/cache-stability-e2e.test.ts。
【免费下载链接】pxpipe
cut Claude Code token usage by rendering text context as images
相关推荐
Awesome-KV-Cache-Compression:优化大型语言模型KV缓存压缩的利器
Awesome KV Cache Compression:优化大型语言模型KV缓存压缩的利器 在大型语言模型(LLM)的推理过程中,如何有效管理内存和计算资源是
掌握OpenCode多项目并发处理:现代开发者的终极效率提升方案
掌握OpenCode多项目并发处理:现代开发者的终极效率提升方案 在当今快节奏的软件开发环境中,开发者往往需要同时处理多个项目:前端React应用、后端API服
人工智能AI 应用AI Agent代码智能体CLI开发者工具1688-customer-opportunity:5大核心功能彻底提升店铺客群运营效率
1688 customer opportunity:5大核心功能彻底提升店铺客群运营效率 1688 customer opportunity是一款专为1688平
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考