Snapcompact: 通过图像节省Token
摘要
Snapcompact 是一种技术,它将文本渲染为密集的像素字体图像,用更便宜的图像Token替换文本Token,以极低的输入成本实现近乎逐字逐句的召回。
暂无内容
查看缓存全文
缓存时间: 2026/06/13 22:28
# Snapcompact:最先进的压缩技术——即时、本地、免费。三项全占
来源:https://blog.can.ac/2026/06/10/snapcompact/
一个1568×1568的PNG图片,使用6×10像素字体,大约能容纳4万个字符的文本。这相当于约1万个token的文本量,而按Anthropic的像素计费公式,这仅需3,279个图像token。你明白我的意思了吗?
**Snapcompact**:当上下文窗口填满时,将其渲染成密集的像素字体位图,然后作为图像返回。“一图胜千言”字面上成真了——模型几乎逐字地读取回来,而输入价格只需三分之一。请看基准测试:
这最初只是一个玩笑(“免费token漏洞,哈哈”)。然后我对其进行了基准测试,找出了问题所在,拆解了Qwen的注意力层,修复了问题,再次进行基准测试——现在我把这一切写下来,因为它在前沿模型上也表现出色。
---
## ¶ (https://blog.can.ac/2026/06/10/snapcompact/#0x0-the-case-against-compaction)0x0:反对压缩的理由
我并不是压缩的忠实粉丝。在每个测试框架中,包括我自己的,我一直觉得它会“削弱”模型,以至于还不如开启一个全新的会话。
省略工具结果是一个不错的替代方案——即时、确定——但有时并不足够。它偶尔还会让模型对工具调用感到困惑。LLM会完成故事;如果你的一半故事是`[省略...]`,你觉得它们对使用这些工具的信心会有多大?
交接(Handoffs)是目前最好的方法——但与计划不同,你通常不会主动引导交接,当你不引导时,代理会浪费宝贵的上下文来写一篇不必要的详细日记,接着是一份待办事项列表,这几乎是在乞求下一个代理宣布目标不可能实现,然后发布一个“MVP”版本。
我的想法基本上是,如果你经常需要压缩,那你肯定做错了什么:要么计划的范围出现了蔓延,要么应该通过子代理明确协调,这样主代理才能对整个范围负责。
然而,被1M上下文窗口宠坏了,现在我在会话结束时经常会达到50万token的标记——这在几个月前对我来说是不可饶恕的罪过。但对于长期任务,由一个一致的代理不间断地驱动计划会更好,即使有积极的委派,这也很容易达到那个级别。
所以,当我看到5小时使用限制条变红,而这个东西对我咧嘴笑时,我在想:也许我应该定期压缩……
好吧。但如果我必须压缩,那它不能丢失任何东西。
---
## ¶ (https://blog.can.ac/2026/06/10/snapcompact/#0x1-a-stupid-experiment)0x1:一个愚蠢的实验
一切始于一个328KB的会话日志和一个简单的问题:如果我把这个东西打印出来,然后用它开始会话会怎样?
第一次尝试最大程度地贪婪:使用了Tom Thumb (https://rob.lag.net/2010/01/23/tiny-monospace-font.html),一种3×5像素的字体,单个图像中包含122,696个字符。
我将其发送给一个全新的代理会话,没有做任何解释,然后收到了回复:
> 图像似乎是纯噪声,带有随机像素,这表明它可能已损坏或是一个被错误命名为PNG的文件。
好吧。第二次尝试使用了X11的`6x10`字体(字形实际设计用于该单元格大小),共40,716个字符,每行文本循环使用六种颜色。相同的模型,结果如下:
- 它识别出会话的主题,并**逐字向我引用**。
- 它100%准确地命中了日志中的18个标识符。
- 当被问及图像最底行(日志截断处)的一个赋值时,它有所保留(“我猜可能是`0`”)——但猜对了状态。
1万个token的文本,由3,279个图像token承载,以近乎完美的精度回忆起来。好吧,现在我投入了。
---
## ¶ (https://blog.can.ac/2026/06/10/snapcompact/#0x2-optimizing-the-fonts)0x2:优化字体
字体能小到什么程度?我扫描了一些字体配置,并要求模型转录固定区域,根据真实文本计算编辑相似度:
| 字体 (px²/字符) | 字符数 | 图像 token (估计) | 转录准确率 | 标识符读取 |
|--------|--------|-------------------|------------|------------|
| 8×13 | 1,042 | 23,520 | 1.00 | 20/20 |
| 6×10 | 60 | 40,716 | 0.79 | 20/20 |
| 5×8 | 40 | 61,348 | 0.37 | 17/19 |
| 5×7 | 35 | 70,112 | 0.30 | 10/20 |
| 4×6 | 24 | 102,312 | 0.02 | 9/20 |
悬崖很陡,大约在**每个字符35-40 px²**左右。在此之上,精确转录会退化,但*标识符级别*的回忆却出奇地强:模型无法重现每个字节,但它能读出名称。在此之下,则一无所获。
有趣的是,这一节比无用还糟糕——这个精确的优化稍后会反过来咬我们一口。
---
## ¶ (https://blog.can.ac/2026/06/10/snapcompact/#0x3-thinking)0x3:思考……
关于我自己日志的轶事并不具有普遍性,所以让我们做一个合适的基准测试:SQuAD v1.1,包含标准答案的抽取式问题。测试框架将文本段落按每种技术的承载能力分块,从每个块中均匀抽取30个问题(因此答案分布在图像的每一行,从上到下),并在同一个语料库上运行每种技术:
- **text**——直接传递语料库;上限
- **handoff**——一个简单的交接提示
- **compact**——提供商端压缩(如果可用),否则使用摘要调用
- **img-{font}-{variant}**——snapcompact,其中变体为**bw**(纯黑底白字)或**sent**(字形墨水按句子循环颜色)
分数为SQuAD F1;模型被告知在无法提取事实时回答UNREADABLE。
| 技术 | fable-5 | opus-4.8 | gpt-5.5 | gemini-3.5-flash |
|---------------------|-------------|-------------|-------------|------------------|
| text (上限) | 0.904 $0.498| 0.911 $0.636| 0.861 $0.084| 0.898 $0.057 |
| handoff | 0.540 $1.224| 0.248 $1.006| 0.368 $0.238| **0.889** $0.175 |
| compact | 0.406 $0.942| 0.000 $0.757| **0.896** $0.339| 0.000 $0.042|
| img-6×10-sent | **0.882** $0.640| 0.601 $0.243| 0.822 $0.245| 0.805 $0.097 |
| img-6×10-bw | 0.856 $0.756| **0.652** $0.236| 0.792 $0.302| 0.767 $0.113 |
| img-5×8-sent | 0.773 $0.453| 0.409 $0.162| 0.751 $0.181| 0.738 $0.100 |
| img-5×8-bw | 0.830 $0.686| 0.425 $0.161| 0.778 $0.235| 0.674 $0.094 |
第一次尝试还不错……等等,我的token节省呢?怎么会更贵?让我们看看另一个表。
### ¶ (https://blog.can.ac/2026/06/10/snapcompact/#tokens-inputoutputthinking)Token:输入/输出/思考
| 技术 | fable-5 | opus-4.8 | gpt-5.5 | gemini-3.5-flash |
|---------------------|---------------------------|---------------------------|---------------------------|---------------------------|
| text (上限) | 37,793 / 2,410 / 1,435 | 37,793 / 931 / 0 | 17,761 / 2,998 / 2,298 | 24,535 / 10,745 / 9,983 |
| handoff | 49,363 / 14,609 / 3,717 | 43,237 / 4,773 / 0 | 25,032 / 11,710 / 4,835 | 49,387 / 36,577 / 11,959 |
| compact | 45,130 / 9,828 / 3,248 | 40,436 / 2,014 / 0 | 45,562 / 15,329 / 1,032 | 26,151 / 6,582 / 5,422 |
| img-6×10-sent | 11,816 / 10,437 / 9,483 | 11,816 / 877 / 0 | 10,188 / 14,049 / 13,368 | 4,991 / 23,491 / 22,764 |
| img-6×10-bw | 11,816 / 12,772 / 11,782 | 11,816 / 796 / 0 | 10,188 / 17,639 / 16,958 | 4,991 / 27,616 / 26,879 |
| img-5×8-sent | 7,955 / 7,474 / 6,897 | 7,955 / 577 / 0 | 4,519 / 10,773 / 10,336 | 3,355 / 24,659 / 24,196 |
| img-5×8-bw | 7,955 / 12,141 / 11,559 | 7,955 / 568 / 0 | 6,823 / 13,892 / 13,463 | 3,355 / 23,014 / 22,542 |
几个结论:
1. **它确实有效**:在fable上,我测试的每个语料库长度上,F1得分在0.86-0.96之间,以三分之一的输入价格承载相同的信息。太棒了。
2. **输入节省并非免费**:模型通过*推理*来解码密集图像,而思考成本约为文本条件下输出token的5倍(在此示例中)。
按Anthropic的输出定价,解码税一次就可能吃掉输入节省。这在4万个token范围内是一个吹毛求疵的小问题——(a)没人会在这个范围内进行压缩,(b)解码只发生一次,而不是每轮——但仍然不够理想。
基线结果大多证实了为什么这值得做。散文式压缩是事实粉碎机:在压缩后的上下文中,Gemini在**240次中**回答UNREADABLE**240次**,Opus回答209次——摘要保留了你*在做什么*,而不是你*知道什么*。有两个例外:OpenAI的不透明服务器端压缩几乎保留了所有内容(但可能他们只是跳过了压缩,谁知道呢?),而Gemini的交接文档违反了提示的精神,写下了琐事,哈哈。
无论如何——该技术是否有效是每个模型视觉栈的经验属性,你必须进行测试。所以现在我们要学习视觉栈实际上是如何工作的。
---
## ¶ (https://blog.can.ac/2026/06/10/snapcompact/#0x4-two-carriers-one-state)0x4:两种载体,一种状态
更强的说法——使snapcompact成为一种内存格式而不是一个花招的说法——是模型对两种载体的*思考*方式相同。我们知道它能读取图像;问题是内部结果是否具有文本的形状。
设置,在本地Qwen2.5-VL-7B-Instruct上:取一个SQuAD块和关于它的十二个问题。每个问题运行两次:一次将块作为纯文本放在提示中,一次将块作为1568²位图放在提示中——并捕获**最后一个提示token**处的隐藏状态,即模型“即将回答”的摘要,在每个解码器层。
原始状态看起来相似,原因无聊(相同的模板,相同的模型),所以比较减去每个载体的逐层均值——任何在中心化后存活下来的都是内容,而不是载体。然后进行三次测量:
- **匹配对**(相同问题,文本 ↔ 图像):在第19层余弦相似度为**0.66**。**不匹配对**(不同问题):**−0.06**。状态编码的是*哪个问题对应哪个内容*,而不是哪种输入格式。
- **跨载体检索**:对于每个文本运行,找到最接近的图像运行。从第2层开始,相同的匹配**12次中12次**。
- **表征几何**:在文本载体内部计算的12×12问题相似性矩阵,与图像载体的矩阵在**r = 0.94**(第1层)时相关,在最终层稳定在**0.85**。两种载体几乎立即打印出相同的结构关系;随着深度增加的是每个问题的状态融合。
在行为上,两种载体产生相同的答案。这就是定价数学所利用的属性:PNG不是你上下文的*图像*——它收敛于*成为*你的上下文。
如果像素在模型内部变成文本,你可以问是在*哪里*。工具是logit透镜:在每个层,取覆盖答案词的视觉token的隐藏状态,通过最终的RMSNorm和LM头,检查top-1词汇条目。**锁定**是第一个其top-1是答案的BPE片段的层。
对于基线8×13渲染,包含“spectacular”尾部的补丁在十七层中解码为CJK噪声,在L18附近经过*字母形状*的噪声(`ALLERY`, `IGHL`——笔画组合成正字法),然后在**L24**翻转为`acular`,到最后一层达到p=0.39。
这很重要,因为锁定之前的层用于将像素转化为单词,而锁定之后的层可以自由地处理它们。所以我决定当一个头脑简单的人。注意力会积累证据,对吗?重复行,读取应该会更强?
另一件一旦你了解视觉token如何工作就非常明显的事情——Qwen将图像切分成28×28像素的窗口,每个窗口对应一个视觉token——是我们在文章开头纯粹凭感觉选择的字体大小不行。在6×10的情况下,每个token窗口包含大约13个字形,分布在三行文本中。
一个token窗口中的重叠垃圾更少:需要的思考更少。简单。
| 条件 | 锁定层 | 峰值p(答案) | 字符/视觉token |
|------------------------|--------|------------|--------------|
| 基线 8×13 | L24 | 0.39 | 7.5 |
| 重复行 ×2,彩色 | L23 | 0.94 | 3.75 |
| 对齐,4×2 字符/token | L24 | 0.74 | 8.0 |
| 对齐,2×1 字符/token | L23 | 0.99 | 2.0 |
| 对齐,1 字符/token | L22 | 0.99 | 1.0 |
| 对齐 + 重复 | L23 | **1.00** | 1.0 |
深度拒绝移动(显著):最多L24到L22。这与OCR路由文献一致,其中视觉何时成为文本是一个架构属性 (https://arxiv.org/abs/2602.22918)。但*置信度*是完全可控的:仅行重复就将解码从0.39提升到0.94,同时仍承载每视觉token 3.75个字符。格式不能让模型更早读取;但它能使读取无歧义。笨模型不用想太多。
现在,在写文献综述时,我注意到这并非新想法:DeepSeek-OCR (https://arxiv.org/abs/2510.18234) 训练了一个自定义编码器用于光学上下文压缩,Karpathy 也 riffed (https://x.com/karpathy/status/1980397031542989305) 关于像素可能击败token作为输入媒介。
然而,在代理测试框架的上下文中,似乎人们看到浪费的思考量后就放弃了。但你看,我们为Qwen做的笨优化在前沿模型上通用得非常好!
1. **经过仔细调整的密集文本位图作为上下文载体,表现非常出色。** 在可读性下限处的合成像素字体渲染,经过基准测试——包括计费公式、静默降采样和自适应思考成本——与代理实际使用的压缩策略进行比较。PNG队加油!
2. **你可以在7B开放模型上将锁定驱动到确定性。** 从p=0.39到p=1.00,这么小的改变就达到了显著效果。是的,重复使像素面积翻倍——我们本可以获得比*仅仅*~3倍更显著的节省——但这仍然比文本便宜,现在具有近乎完美的回忆。如果你愿意,甚至可以将工具结果作为PNG返回。
这是它实时运行在测试框架中的样子:
---
测试框架再次获胜:模型本身没有变化;我们改变了它们周围的上下文。
*评估框架、字体渲染器、每个问题记录、白盒探针:omp (https://github.com/can1357/oh-my-pi/tree/master/packages/snapcompact/research)——`uv run final.py` 复现API网格(冷启动约$35,缓存后免费);表征运行需要本地GPU和Qwen2.5-VL-7B。*
即将在 oh-my-pi (https://omp.sh/) 上线!
相似文章
代码审查变得昂贵,重写变得廉价
LLMs 通过生成过度设计的代码,使代码审查变得更加昂贵,但重写现在变得廉价,从而将开发者的工作转向更多的前期规划和迭代简化。
文件系统是AI代理的新原语
本文认为,文件系统因其悠久历史和在LLM训练数据中的广泛包含,为AI代理记忆提供了一种自然直观的原语,在探索性推理和持久化上下文方面优于传统数据库和API。
可能是个愚蠢的问题,但如何为多个用户提供完整的上下文长度?
用户询问llama.cpp如何为每个用户提供完整的上下文长度,并指出它似乎只是共享上下文池,而不是为每个用户提供专用上下文。
我们在HuggingFace上训练了一个专注于网络安全的类似Mythos的开源权重LLM
一个名为OpenMythos的开源LLM通过SFT和RLVR进行训练,专攻网络安全任务,相关数据集已在HuggingFace上提供。该模型旨在减少幻觉并提高安全相关查询的精确度。
我的代理悄悄地损坏了它自己的记忆图谱,而我正在尝试一些方法。
作者描述了一个问题:LLM 代理的记忆图谱因错误的边而损坏,并提议使用声明的本体来验证写入和遍历。对 120 条故意破坏的遍历进行的测试捕获了所有错误。