↓ 跳过正文
上下文压缩的物理代价:KV Cache 压力、注意力衰减与有损摘要
  1. Posts/

上下文压缩的物理代价:KV Cache 压力、注意力衰减与有损摘要

Claude Code 源码拆解 - 这篇文章属于一个选集。
§ : 本文

大模型在物理底层不存在真正意义上的生物记忆,只有在显卡高带宽内存(HBM)中随序列长度线性堆叠的 Key-Value Cache,以及在数学上随序列拉长而被急剧稀释的注意力权重分布。Claude Code 呈现给开发者的所谓长程对话能力,本质上是一套在硬件显存与精度边界夹缝中运行的缓存逐出流水线,伴随着不可逆的信息失真与前缀失效代价。

在长周期编程任务中,开发者常常对 AI 助手的健忘感到困惑:明明第 5 轮确认过的接口规范,到了第 25 轮模型却视若无睹;明明前文明确修复过的类型定义,在一次 /compact 之后又被重新写回旧的缺陷模式。这是物理约束与信息论共同收紧的客观结果。

物理底座的硬约束:KV Cache 与显存爆炸
#

在基于 Transformer 架构的自注意力机制中,推理阶段(Decode Phase)为了避免对历史 Token 进行重复投影计算,系统会将每一层自注意力计算生成的 Key 与 Value 矩阵固化在显存中,这即是 KV Cache。

我们可以推导单个会话在自回归生成时的物理显存占用。设模型层数为 $L$,模型隐藏层维度为 $D$,注意力头数为 $H$。在现代生产级模型普遍采用的分组查询注意力(GQA,Grouped Query Attention)配置下,设 Key/Value 头的数量为 $H_{kv}$,每个注意力头的维度为 $D_h = D / H$。当上下文序列长度达到 $S$ 时,使用 FP16 精度(每个参数 2 字节),单请求的显存占用为:

$$\text{KV Cache Size} = 2 \times 2 \times L \times H_{kv} \times D_h \times S \text{ bytes} = 4 \times L \times H_{kv} \times D_h \times S \text{ bytes}$$

以典型的 70B 级别模型(如 $L=80$, $H_{kv}=8$, $D_h=128$)为例,每增加一个 Token,KV Cache 就需要吞掉约 $320 \text{ KB}$ 显存。当上下文滑向 200,000 Token 时:

$$\text{Memory} \approx 4 \times 80 \times 8 \times 128 \times 200,000 \approx 65.5 \text{ GB}$$

这仅是单个并发请求在纯 KV Cache 上的显存开销。在多并发集群中,服务商不可能让用户的历史会话毫无节制地霸占高昂的 HBM。

更致命的是首字生成延迟(TTFT,Time to First Token)的物理墙。在没有命中前缀缓存的情况下,对 150k Token 历史上下文执行 Prefill 计算的时间复杂度是 $O(S^2)$。没有工程手段干预的长上下文调用,交互延迟会迅速突破数十秒,彻底摧毁 CLI 工具的实时反馈闭环。

数学与认知的双重衰减:注意力稀释与 Lost-in-the-Middle
#

除了硬件显存,长上下文在算法层面面临更严峻的注意力稀释(Attention Dilution)。自注意力机制的核心公式为:

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V$$

在 Softmax 计算中,第 $i$ 个查询向量对所有历史 Token 的注意力权重总和必须归一化为 1:

$$\alpha_{i,j} = \frac{\exp\left(\frac{q_i k_j^T}{\sqrt{d_k}}\right)}{\sum_{m=1}^S \exp\left(\frac{q_i k_m^T}{\sqrt{d_k}}\right)}$$

当序列长度 $S$ 从 2,000 膨胀到 150,000 时,分母项的累加项增加了近百倍。即使模型拥有基于 RoPE(旋转位置编码)的外推能力,背景噪声 token 依然会均摊有限的概率质量。

经典论文 Liu et al. (2023) Lost in the Middle: How Language Models Use Long Contexts 揭示了长序列注意力的物理衰退规律:

1
2
3
4
5
6
7
8
9
检索与推理准确率
100% |  \                                            /
     |   \                                          /
 75% |    \                                        /
     |     \                                      /
 50% |      \           U 型注意力深谷            /
     |       \__________________________________/
  0% +------------------------------------------------->
     输入开头 (System Prompt)    中间历史 (工具输出/对话)   结尾 (最新观察)

模型对位于上下文起始区域的系统提示(定义身份与工具契约)和位于末端的最新错误输出具备最高的召回敏感度。而深陷在序列中段的编译日志、前期探索的局部代码片段以及过往的架构决议,其注意力权重不可避免地跌入谷底。在长程推理中指望大模型精确记住第 15 轮某工具返回的变量名,违背了当前的数学物理现实。

Claude Code 的四级压缩管线与失真传导
#

面对显存压力与注意力衰减,Claude Code 构筑了一套四级梯级压缩管线。表面上它维系了长对话的连贯性,但在工程底层,每一级都在向信息熵妥协。

1. 观察结果裁剪(Observation Trimming)
#

在 Think-Act-Observe 循环 中,最凶猛的上下文消耗源来自工具调用的回显——一次 npm test 或 cargo check 很容易吐出上万行的栈追踪日志。

Claude Code 的第一道防线是对已完成工具结果实施定长截断。由于源代码与测试文件实体持久化在磁盘上,运行结果属于即时消耗品,系统会剥离中间的冗长输出,仅保留退出状态码与前后关键几行。这是整个管线中信息损失比最小的压缩操作。

2. 对话重写压缩(Conversation Compaction / /compact)
#

当序列总长度触碰安全阈值(如额定窗口的 75%)时,系统启动对话压缩。其机制是启动一个独立的子调用,让模型对前半段已沉淀的历史消息生成结构化摘要:

  • 梳理已完成的代码修改与已验证的事实;
  • 罗列未完成的待办事项与已知技术障碍;
  • 用数百 Token 的抽象描述彻底覆盖成千上万 Token 的原始多轮交互。
信息熵增与复印机效应(Semantic Drift)
香农信息熵公式 $H(X) = -\sum p(x) \log p(x)$ 决定了自然语言摘要必然是一次不可逆的高熵到低熵的有损投影。大模型在摘要自身对话时,会自动抹杀代码中的极端边界条件与幽灵缺陷。当一次长达 50 轮的重构历经两次 /compact,后半段的模型实际上是在阅读一份「对前序观察的概括的概括」。信息在递归压缩中发生语义漂移,模型会逐渐自信地根据自身摘要的语法歧义产生全新的认知偏差。

3. 上下文滑动窗口规划(Window Management)
#

压缩管道需要精确的动态 Token 记账。在向 Anthropic API 发起每次请求前,系统必须动态预留至少 4k 到 8k Token 作为模型的输出配额。

如果在当前上下文长度加上系统指令后,剩余空间无法满足模型的输出阈值,滑动窗口控制器会强制剔除更早的消息体。这种裁决属于纯粹的机械内存页面淘汰。

4. 分层提示词动态注入(Selective Injection)
#

在 System Prompt 工程 中拆解过的洋葱分层架构,在此处发挥限流作用。核心身份(Core Identity)常驻,而次要的 Skill 定义、非核心工具 Schema 以及跨文件规范(CLAUDE.md),仅在检测到相关触发词时按需注入,从而将基础占位空间压缩到极限。

商业落地的算力博弈:Prompt Caching 与前缀破坏的代价
#

如果上下文管理仅仅是删减文本,系统架构尚可算作单纯。但在现代大模型商业化计费体系下,压缩行为直接撞上了 Prompt Caching(前缀提示缓存) 的经济与延迟杠杆。

Anthropic 的 API 计费规则提供了一项关键特性:对完全命中前缀缓存的上下文,输入 Token 价格直降 90%,且 Prefill 计算由服务端直接复用物理显存中的 KV Cache 镜像。这不仅将计费成本压缩至十分之一,更将首字延迟压低至百毫秒级。

1
2
3
4
5
6
7
常规请求 (缓存命中):
[System Prompt] -> [Tool Defs] -> [Turn 1..N-1] | -> [New User Input]
<---------------- Warm Prefix (90% Off, Fast) ->|  (Cold Prefill)

执行 /compact 后的请求 (缓存击穿):
[System Prompt] -> [Tool Defs] -> [Compacted Summary] | -> [New Input]
<--- Warm --->  (Prefix Invalidated) <----- Cold Prefill (Full Cost) ->

这里构成了残酷的工程悖论:

  • 不压缩:上下文线性增长,每次交互虽然命中前缀缓存,但总 Token 基数持续攀升,显存膨胀逼近硬上限,且模型在 Lost-in-the-Middle 效应下智商线性下滑;
  • 执行压缩:/compact 抹掉了历史序列的中间部分,替换为精简的摘要。但这彻底破坏了前缀的字节级一致性。服务端原先缓存的数万 Token KV Cache 瞬间全部失效。系统为了省下几千 Token 的窗口空间,下一轮请求却必须按 100% 的全价重新对长达数万 Token 的上下文执行 Cold Prefill,造成显著的计费跳涨与交互卡顿。

在节省显存窗口与保护缓存前缀之间,没有任何免费的午餐。

工程实践的硬核解法:BYF 的 Observation Masking 与 CacheStaking
#

针对 Claude Code 全量压缩导致的缓存失效与语义丢失,开源项目 BYF 在 ADR 0011 中提出了一套精细化的确定性治理方案:

1. 阈值带观察掩码(Threshold Bands)
#

BYF 摒弃了「平时不管、满了全局压一次」的粗放策略,引入了基于上下文容量的梯级水位线:

  • 低水位(60% 上下文压力):仅掩码易过期的感知类工具输出,如 glob、grep 的长文件列表,将其压缩为单行元数据 [Glob: 42 files matched];
  • 高水位(80% 上下文压力):掩码非关键的 bash 执行日志,保留最近 5 行与退出码;
  • 核心契约免死:write_file 与 edit 的实际代码 diff 受到最高保留优先级保护,直到达到 85% 极限水位前绝不丢弃。

2. 输出卸载到物理磁盘(Output Offloading)
#

当某个工具输出超过 8,000 Token(例如超大测试报告或代码反编译结果)时,BYF 拒绝将其直接塞入上下文。系统将全量文本写入本地沙箱的临时文件(如 /tmp/byf-output/turn-12.log),在上下文中仅暴露:

1
2
[Output truncated. Full content (145KB) written to /tmp/byf-output/turn-12.log.
Showing first 500 chars: ... ]

若模型后续在推理时确实需要特定细节,它必须主动调用 read_file 配合行号定位读取。这一设计强制把上下文内存压力转化为文件系统的磁盘 I/O。

3. Turn 边界缓存桩(CacheStakingStrategy)
#

为了最大化利用 Anthropic 的前缀缓存折扣,BYF 建立了严格的「3+1」缓存断点协议:

  • 静态桩:系统指令、核心工具元数据;
  • 动态桩:固定在上一轮最后一条 Assistant 消息的结尾。

在会话生命周期中,只要未发生致命的上下文溢出,系统绝不重写任何已经固化的历史轮次。所有观察掩码的操作全部严格在历史生成后延期执行,确保前缀缓存断点在物理内存中保持最长存活时间。

结语:戒断“无限上下文”的技术执念
#

工业界对“百万 Token 窗口”与“终身记忆 Agent”的狂热,很大程度上是一种掩盖系统工程无能的营销修辞。

一个成熟的系统架构师应当清醒地认识到:Transformer 的注意力机制是一块昂贵、易挥发且对距离极度敏感的物理缓存。依托有损摘要强行堆砌的长程会话,终究会在信息熵增的重力下蜕变为漏洞百出的虚幻沙盒。

在真实的软件工程实践中,当一次交互超过 30 个轮次、执行过多次重写之后,最具专业素养的做法是敲下 git commit,将确定的代码状态沉淀在版本控制中,然后果断输入 /clear。带着干净利落的架构契约开启全新的空白会话,彻底摆脱泥潭般的历史缓存干扰。

 Author
作者
ByF (BaiFan)
ByF,即 BaiFan(白帆)。系统后端工程师,专注 AI 底层原理与智能体架构。主站 baifan.site。
Claude Code 源码拆解 - 这篇文章属于一个选集。
§ : 本文