pxpipe:通过图像化压缩输入token降低Claude Code成本
通过将代码转换为图像并由模型进行OCR识别,将《Fable》的开发成本降低了60%
pxpipe是一个本地代理,将系统提示、工具文档和历史记录等密集文本渲染为PNG图像,利用图像token成本取决于像素尺寸的特性压缩输入token。在Fable 5模型上,约25k文本token压缩为约2.7k图像token,端到端账单降低59–70%。SWE-bench Lite 10个实例全部通过,成本从$54降至$27;SWE-bench Pro 19对测试中18对判定一致,单次请求成本降低约60%。该方法有损(精确ID等需保持文本),默认仅处理`claude-fable-5`请求,可通过`PXPIPE_MODELS`变量控制。
pxpipe 通过把大量上下文渲染成图像来降低 token 开销,实测能削减 60-70% 的账单,对重度使用 Claude Code 的开发者很诱人,但它有损,精确值可能读错,适合容错高的编码场景。
通过将庞大的上下文渲染为图像,把 Claude Code 的输入 token 削减——同样的系统提示词、工具文档和历史记录,token 用量却只有一小部分。
一张图像的 token 成本由其像素尺寸决定,而非其中包含多少文本。在真实的 Claude Code 流量中,密集内容(代码、JSON、工具输出)每个图像 token 约可容纳 3.1 个字符,而每个文本 token 仅约 1 个字符。pxpipe 是一个本地代理,正是利用了这一差距:它在请求离开你的机器之前,将请求中庞大的部分(系统提示词、工具文档、较早的历史记录)重写为紧凑的 PNG。
节省幅度取决于工作负载——pxpipe 在 token 密集的内容上占优,而对稀疏/小型请求则不作改动——因此这些是实测的快照,而非恒定值。首要且持久的结果是输入 token 的削减:密集的系统提示词、工具文档和历史记录以紧凑图像而非文本的形式输入(上例中约 25k 文本 token 被渲染为约 2.7k 图像 token),每个请求都与其自身的count_tokens反事实基准进行对比衡量。美元金额是这一结果的下游产物——按当前 Fable 的标价,token 的削减体现为端到端账单降低约 59–70%(压缩请求上约 72–74%;完整定价计算见 FAQ)。但标价明天就可能变化,而 token 数量不会,因此值得关注的是 token——而非美元。两者均可从~/.pxpipe/events.jsonl复现。
这就是模型所看到的、取代文本的内容:
约 48k 字符的系统提示词 + 工具文档(本仓库自己的 README、FINDINGS 和源代码),作为文本约 25k tokens,作为本页面约 2.7k 图像 tokens。由真实的 transformRequest 流水线生成:空白压缩、重排为完整行并用 ↵ 标记原始换行、OCR 指令横幅叠加渲染在顶部。模型读取这样的渲染结果,在干净评测中达到 100/100(见基准测试)。
演示
Fable 5 演示(默认,100/100 读取器):
Fable-AB-Demo.mp4
- 两个演示均在 Fable 5 上双栏对比(左侧为纯文本,右侧为 pxpipe)。
- Fable 能读取 Opus 读不了的内容。Opus 拒绝处理的图像化短语计数(见下方 Opus 演示):pxpipe 分支在 39 个图像化填充文件中精确计数出 token 10/10(与
grep基准真值逐行匹配),并正确完成多步骤账目运算(8037 → … → 15,021)。 - 答案相同,成本约低 7 倍。两个演示后的会话总计:纯文本 $42.21,上下文 已用 96%(964.5k/1M——再做一个任务就会触发强制压缩)对比 pxpipe $6.06,上下文仍有富余(73.5k/1M)。
- 诚实的提醒,在片段中可见: pxpipe 那一侧先回答了计数,还需要一次追加提示才按要求的一行格式打印出账本余额;普通那一侧第一次就遵循了格式。可读性在 Fable 上已经解决——单次回复的格式遵循是剩下的粗糙边缘。
Opus 4.8 演示(Opus 默认禁用):
Opus-AB-Demo.mp4
并排对比——普通 Claude(左)对比 pxpipe(右),两者都运行在 Opus 4.8 上(需主动启用;pxpipe 是针对 Fable 调优的——见上方的 Fable 片段)。点击图片观看(Google Drive)。
- 演示 1——修复一个失败的测试套件: 两者都通过;仪表盘显示 pxpipe 将请求削减到 token 的一小部分(真实的、服务器实测的 上下文/token 削减)。
- 演示 2——一个大文件上下文(40 个文件,约 382k tokens)加上一道数学题和一个"数这个短语"的任务: 数学答案(一个小的 文本 针)在两者上都能读出。短语计数需要读取 被图像化 的填充内容——所以 pxpipe-on-Opus 无法读取它,并 诚实地表明它不会编造一个数字(已记录的损失性限制:精确值保持为文本)。而普通那一侧则陷入逐文件计数的困境。
试一试(30 秒)
npx pxpipe-proxy # proxy on 127.0.0.1:47821 ANTHROPIC_BASE_URL=http://localhost:47821 claude # point Claude Code at it
打开 http://127.0.0.1:47821/ 查看实时仪表盘:节省的 token、按会话统计、每一次文本→图像转换的并排对比、一个全局终止开关,以及包括 GPT 5.6 和 GPT 5.5 在内的运行时模型标签。
其他一切都不变。响应照常流式返回;pxpipe 只压缩 请求(即你向上传递的上下文),从不压缩模型的输出。最近的轮次保持文本形式;系统提示词、工具文档以及更早的大批历史记录则被转为图像。
诚实说明部分,依赖它之前请先阅读
它是有损的。pxpipe 是要点层级,而非无损存储。在一项大海捞针评测中,密集图像化内容里精确的 12 字符十六进制字符串在 Opus 上返回0/15,在 Fable 5 上为 13/15,而其失败模式是静默虚构:一个看似合理的错误值,而非报错。任何你需要逐字节精确取回的内容(ID、哈希、密钥、精确数字)都必须保持文本形式。最近的轮次确实如此;专门的逐字风险防护尚未构建。
精确召回逃生通道。pxpipe 只对 Fable 请求进行图像化(PXPIPE_MODELS=claude-fable-5),因此任何运行在非 Fable 模型上的子智能体都以文本形式传递。将需要逐字节精确值的工作路由到这样的子智能体——可全局使用 CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-4-6,或在智能体 frontmatter 中按智能体使用 model: sonnet。它从源头(文件/JSONL)读取,而非图像化的历史记录。这覆盖了你刻意路由的精确召回;它不会捕捉你未曾预料的静默误读——那正是上文尚未构建的防护。
它会破坏实际工作吗?在我们所测量的范围内表现持平:一个 10 实例的 SWE-bench Lite 试点(简单子集)在两组上均解决了 10/10,pxpipe 开启时花费 $27,关闭时花费 $54(token 等价),而在 19 对 SWE-bench Pro(更难、长时程)上,开启时解决了 14/19,关闭时解决了 15/19,每请求降低 60%:判定在 18/19 上一致,而唯一的分歧(一次开启时的失败)在复现时 3/3 均重新解决,即运行间的智能体方差,而非压缩所致。样本量小,细节和注意事项见下文。
节省取决于工作负载。它在 token 密集的内容(约 1 字符/token:代码、JSON、哈希)上胜出,而在稀疏的英文散文(约 3.5 字符/token)上亏钱。内置门控只对数学上划算的内容进行图像化,并依据 N=391 条生产数据行进行了校准。
模型范围:一个 PXPIPE_MODELS CSV 控制两个模型系列中哪些模型基座会被图像化——默认 claude-fable-5,gpt-5.6(GPT 5.5 需主动开启;它在图像化上下文上表现会下降)。将 PXPIPE_MODELS=off 设为禁用图像化,或使用 ~/.config/pxpipe/config.json 搭配 { "models": "off" }(或一个列表)。对于 GPT,pxpipe 将工具定义保留为原生 JSON(只有冗长的 schema 描述文字会移入图像),因此工具调用保持可靠;与 Claude 路径不同,GPT 路径不会添加或依赖 Anthropic cache_control 提示词缓存标记。仪表盘上的开关可以实时切换任何模型,而无需更改客户端配置。Opus 4.7/4.8 原本是 Claude 的默认范围,但会误读约 7% 的渲染结果(10200→9400),因此在 Fable 5 以相同的图像计费达到 100/100 后,它被默认关闭——如需重新启用请自行承担风险,可通过 PXPIPE_MODELS 或仪表盘开关操作。其余一切均原样通过。
基准测试(可复现)
使用模型不可能记住的全新随机数字问题进行测量:
| 测试 | N | 文本 | pxpipe(图像) | tokens |
|---|---|---|---|---|
全新算术题,claude-fable-5 | 100 | 100% | 100% | −38% |
新颖的算术,claude-opus-4-8 | 100 | 100% | 93% | −38% |
| 要点回忆 A/B(决策、数值、路径、名称、否定;含干扰项;15k-45k 字符会话),Fable 5 | 98/组 | 98/98 | 98/98 | - |
| 状态跟踪(数值被修改 3 次,最终值/首次值/计数),Fable 5 | 18/组 | 18/18 | 18/18 | - |
| 对从未陈述过的事实的虚构(越低越好),Fable 5 | 16/arm | 0/16 | 0/16 | - |
| 逐字 12 字符十六进制召回,密集渲染,Opus | 15 | 15/15 | 0/15 | - |
| 逐字 12 字符十六进制召回,密集渲染,Fable 5 | 15 | - | 13/15 | - |
SWE-bench Lite 试点(端到端任务质量)
10 个 SWE-bench Lite 实例,Claude Code + Fable 5,通过 pxpipe 开启与关闭的配对运行,使用官方 swebench Docker 测试框架评分:
| pxpipe 开启 | 关闭 | |
|---|---|---|
| 已解决 | 10/10 | 10/10 |
| 请求大小对比自身未压缩正文 | −65% | ±0 |
−65% 是按请求计算的(count_tokens 在压缩前对每个正文进行探测),因此不存在轮次数混杂因素。n=10/组,Lite 偏向简单。运行总计、凭据、注意事项:eval/swe-bench/。
SWE-bench Pro 基准测试(更难、长周期)
两次运行中共完成 19 对(2 对被丢弃:两组均结账失败),相同设置,官方 SWE-bench_Pro-os Docker 测试框架:
| pxpipe 开启 | 关闭 | |
|---|---|---|
| 已解决 | 14/19 | 15/19 |
| 请求大小 vs 自身未压缩正文 | −60% | ±0 |
判定结果在 18/19 上一致(三个实例在两个分支上都失败,其中一个在两个分支上产生了字节级完全相同的补丁)。唯一的分歧(navidrome,ON 分支失败)在 ON 分支上重复了 3 次:三次运行都产生了完全相同的补丁并且成功解决,因此最初的失败是智能体运行间的随机波动,而非压缩所致。凭证:eval/swe-bench-pro/。
我们还跑了 GSM8K:96% 成像。但 GSM8K 在训练数据中,因此模型会通过自身的误读回忆起记忆中的答案,从而虚高分数,所以我们以干净的新数字评测为主。复现:eval/gsm8k/ · eval/needle-haystack/ · eval/gist-recall/ · 完整分析见 FINDINGS.md 。
常见问题
这个头条数字是端到端的,还是只针对你触及的那些请求? 端到端,整张账单。大多数压缩工具只报告它们所触及的那部分输入的节省量,这会美化数字。端到端的分母是每一个生产请求:那些小的、pxpipe 正确地未加触及的请求,所有的缓存写入和读取,以及所有的输出 token(代理从不压缩这些)。在一个 13,709 个请求的快照上,这一数字是 59%($100 → 约 $41);后来一个 8,904 个已压缩请求的追踪测得约 70%。仅压缩的运行会更高(约 72–74%),并单独列出,从不作为头条数字。确切数字取决于工作负载——请在你自己的日志上复现它。
这个数学是怎么测量的? 同一请求的两侧,在同一时刻。对于每一个 /v1/messages POST,代理会在原始未压缩的请求体(反事实)上发起一次免费的 count_tokens 探测,与真实转发并行,并从响应中读取 Anthropic 实际计费的 usage 块。两者都落在 ~/.pxpipe/events.jsonl 的同一行,因此不存在轮次计数或运行间的混淆因素。美元换算使用 Fable 5 的标价比例:输入 ×1.0,缓存写入 ×1.25,缓存读取 ×0.1,输出 ×5。缓存定价对两侧完全相同地应用,因此缓存折扣相互抵消,不能被重复计为"节省"。你可以自己从事件日志中重新推导:公式和字段名记录在 src/core/baseline.ts 中。
它实际压缩什么? 三种输入块,每一种都经过一道盈利性门槛:
- 大型
tool_result请求体(文件读取、命令输出、日志),token 密集内容超过约 6k 字符 - 较早的折叠历史:位于实时尾部之后的轮次会被重新渲染为图像页,而最近的轮次始终保持文本形式
- 静态系统提示词 + 工具文档板块
其他所有内容都按字节完全一致地透传:你的消息、最近的轮次、模型的输出(它就是响应本身,代理从不触碰它)、稀疏的散文文本,以及任何小到无法胜出的内容。非 Fable 模型则完全透传。
它在基准测试之外真的失败过吗? 是的,在数周的日常使用中失败过一次:模型从图像化的聊天历史中回忆起一个人的名字,并且自信地记错了。没有报错,只是一个看似合理的错误名字。这正是有据可查的失败模式:图像化内容中的精确字符串并非字节安全。编程会话能容忍这一点,因为智能体在编辑前会重新读取文件;而纯粹的聊天回忆没有这样的校验。
工作原理
tool_result string ──► wrap at 1928px-wide columns ──► pack ~92,000 chars/page ──► PNG[]
该代理拦截 /v1/messages,将符合条件的大量历史重写为图像块,以缓存友好的方式拼接回去(静态前缀得以保留,因此提示词缓存继续有效),然后转发。每个请求的事件记录到 ~/.pxpipe/events.jsonl。
经济账是这样的:一张 1928×1928 的图像约消耗 4,761 个视觉 token,最多可容纳约 92,000 个字符(按观察到的密度约合 48,000 个文本 token),因此纯文本更便宜只有在其密度超过约 19 字符/token 时才会如此。Claude Code 的对话记录远低于这一水平(观察值为 1.91 字符/token,N=391)。运行时估算器(estimateImageCount)加上字符/token 门控会针对每个请求做出判断;稀疏的散文则保留为文本。
库的使用(无代理)
同一个引擎,无需代理。将文本渲染为 PNG,或运行完整的缓存安全转换:
import { renderTextToPngs, transformAnthropicMessages } from "pxpipe"; const imgs = await renderTextToPngs(toolResultText); // RenderedImage[] const { body, applied, info } = await transformAnthropicMessages({ body: requestBytes, model: "claude-fable-5", });
options.keepSharp(block) 将块固定为文本(覆盖针对 ID、哈希、路径的启发式规则);options.emitRecoverable 返回被图像化块的原始内容,以便有状态的调用方能够恢复它们——这两者构成了针对下文有损限制的保真契约的两半。运行时为纯 JS(Node 和 edge/Workers);@napi-rs/canvas 仅在构建时使用。完整 API、类型和常量:src/core/index.ts。
开发
pnpm install && pnpm test # 376 tests pnpm run build # regenerates dist/
局限性
- 有损:参见上文"诚实部分"。从图像中进行逐字回忆并不可靠。
- 渲染延迟:编码 PNG 会为大型请求在发出前增加时间(部分被模型摄入更少 token 所抵消)。响应正常流式传输。
- ASCII/Latin-1 经过充分测试;CJK 可用但较为保守。
- 运行时是纯 JS——可在 Node 以及 edge/Workers 上运行。
@napi-rs/canvas是仅构建期使用的开发依赖(用于重新生成字形图集),而非运行时依赖。 - 仅限 Fable 5。
路线图
以上一切都是经过测量的。这里的一切都不是。这些是假设,而非断言;它们要么以带有 n 的数字形式交付,要么被砍掉。
- 更清晰的字形。 13/15 的逐字差距部分源于字体可读性,而不仅仅是模型。跨渲染风格的逐字符混淆矩阵运行到一半暂停了(
eval/glyph-matrix/);如果某种零成本风格能降低读取错误,那么门控就能在相同保真度下压缩得更狠。 - 有效上下文。 密集文本以图像形式承载时,token 数约少 3 倍。如果这在实时窗口中成立,而不仅仅是在账单上,那么 1M token 能容纳约 2 倍的真实内容。悬而未决的问题:一个需要约 2M 原始上下文的任务,在大部分内容被图像化后,能否在 Fable 的 1M 内运行?
- 更少的活跃文本,更敏锐的模型。 长上下文在填充过程中会削弱推理能力。将旧的庞杂内容图像化,能缩小模型主动读取的内容,同时保持其可访问。假设:相同的信息,更小的活跃上下文,更好的长任务准确率。
一个赌注:更长的有效上下文,以及在长任务上更敏锐的模型,来自同一个 Fable 5。要么给出数字,要么撤回,中间没有炒作。
许可证
来源:Hacker News 热门(buzzing.cc 中文翻译) · github.com