LLM Token 原理

理解 token 是理解一切 LLM 优化的起点。

什么是 Token

LLM 不直接处理文字,而是处理 token —— 一种介于字符和单词之间的文本单元。

Hello, world! 为例,GPT 系列模型的 tokenizer 会将其拆分为:

Token文本
15339Hello
11,
1917 world
0!

4 个 token,而不是 2 个"单词"或 13 个"字符"。

交互式 Tokenization 演示
Hello1,2world3!4567891011
11 个 token

Tokenization 过程

主流 LLM 使用 BPE(Byte Pair Encoding) 算法:

BPE 算法步骤演示
点击步骤卡片查看 BPE 合并过程

将语料 "low lower newest widest" 拆分为单个字符作为初始 token。

low
low
lower
lower
newest
newest
widest
widest
1 / 4

这意味着常见词(thefunction)是一个 token,而罕见词或中文会被拆成多个 token。英文平均约 4 字符/token,中文约 1.5 字符/token。

不同模型的差异

各模型家族使用不同的 tokenizer,同一段文字的 token 数可能差异 20-30%。这也是为什么 token 估算(如 RTK 的 len / 4)只能是近似值。

上下文窗口

上下文窗口(context window)是 LLM 单次对话能处理的 token 总量上限。

模型窗口大小
GPT-4o128K
Claude Opus 4200K
Claude Sonnet 4200K

输入 vs 输出 token:输入 token(prompt)和输出 token(completion)共享窗口,但定价不同。输入通常是输出价格的 1/3 到 1/5,但占用的窗口空间完全相同。

窗口溢出:当输入超过窗口限制时,模型(或客户端)会截断最早的消息。在 agentic 场景下(如 Claude Code),这意味着工具调用结果和早期上下文会丢失——模型可能"忘记"之前做过什么。

上下文窗口可视化
拖动滑块模拟对话历史增长,观察窗口占用变化
对话历史
当前输入
剩余
0已用 90K / 200K tokens200K
System Prompt
对话历史
当前输入
剩余空间

为什么要缩减 Token

成本

Token 直接对应 API 费用。假设一个开发者每天通过 Claude Code 产生 50 万输入 token:

对团队来说,节省按人头线性增长。

Token 成本计算器
100K2M
未优化
Token 量
500K
每日费用
$1.50
年化费用
$547.50
优化后 (假设缩减 60%)
Token 量
200K
每日费用
$0.60
年化费用
$219.00
年化节省 ~$328.50 / 人

速度

LLM 推理分两个阶段:

Prefill vs Decode 两阶段
调整输入 token 数量,观察两阶段延迟变化
Prefill~2s
并行处理全部输入 token
50K tokens
Decode~3.2s
逐个生成输出 token
...
20K100K
关键洞察:Prefill 延迟随输入 token 线性增长(20K→0.8s,100K→4s),Decode 延迟保持不变(~3.2s)。减少输入 = 更快响应。

质量

这是最容易被忽视、但可能最重要的原因:

"Lost in the Middle" —— 研究表明,LLM 对上下文中间部分的内容关注度显著低于开头和结尾。当上下文中充满无关信息(如 git log 的 merge commit、Signed-off-by trailer),模型更容易忽略关键信息。

“Lost in the Middle” 注意力衰减
拖动滑块将关键信息放在上下文的不同位置,观察检索准确率变化
!
开头中间结尾
检索准确率
将关键信息放在上下文第 1/20 位置
72%

颜色深浅表示模型对该位置的注意力强度。开头和结尾的信息更容易被模型关注,中间部分容易被“遗忘”。

减少噪声 = 提升信噪比 = 更准确的输出。

缩减策略分类

Token 缩减策略
预过滤

在输入到达 LLM 之前裁剪无关内容

RTK
压缩/去重

规则化压缩,去除重复信息

opencode DCP 插件
摘要

用小模型生成摘要,压缩后给大模型

LLM-based summarization
架构级

从系统设计层面减少单次 token 用量

RAG / 滑动窗口 / 分层记忆

预过滤

在系统命令输出到达 LLM 之前,根据预定义规则裁剪。不调用 LLM,零额外成本,延迟极低。

代表项目:RTK —— 拦截 git loglscargo build 等 40+ 命令的输出,去掉冗余信息。

压缩/去重

分析对话历史,识别重复的工具调用结果并去重或压缩。同样基于规则,零 LLM 成本。

代表项目:opencode DCP 插件 —— 作为 coding agent 的中间件,分析对话历史中重复的工具调用结果并自动压缩。

摘要

用一个小型/快速的模型对长文本生成摘要,再把摘要喂给主模型。有额外 LLM 调用成本,但主模型的 token 用量大幅减少。

常见于:长文档问答、多轮对话记忆压缩。

架构级

从系统设计层面避免一次性加载大量 token:

  • RAG:只检索相关片段而非全文
  • 滑动窗口:只保留最近 N 轮对话
  • 分层记忆:短期记忆(完整)+ 长期记忆(摘要)

Prompt Cache:复用而非缩减

除了减少 token 数量,还有一种互补策略:让重复的 token 更便宜

什么是 Prompt Cache

大模型推理分为两个阶段:

  1. Prefill —— 处理输入的全部 token,生成每一层的 Key-Value(KV)状态。这是推理中最耗计算的部分,延迟与输入 token 数线性相关。
  2. Decode —— 基于 KV 状态逐 token 生成输出。

Prompt cache 的核心思想:缓存 prefill 阶段产生的 KV 状态。后续请求如果拥有相同的 prompt 前缀,可以直接复用已有的 KV cache,跳过 prefill 计算。

这些 KV 状态存储在 GPU 显存(或少数厂商的磁盘)中。GPU 显存是昂贵的稀缺资源,因此缓存不能无限保留——每条缓存都有一个 TTL(Time To Live),过期后被驱逐。主流厂商的默认 TTL 从 5 分钟(Anthropic)到数小时(DeepSeek)不等。

直觉上的矛盾

Token 缩减工具会修改发送给 LLM 的内容——压缩、去重、裁剪。而 prompt cache 要求前缀 100% 一致才能命中。

这看起来是矛盾的:每次修改上下文,都会导致 cache miss,似乎让 cache 失去意义。

为什么长期来看仍然划算

实际情况中,一次典型的 agentic 会话(如 Claude Code 处理一个开发任务)可能包含 50-200 轮交互。分析 cache 在整个会话生命周期中的行为:

1. 静态前缀占比高

一次会话中,system prompt + CLAUDE.md + 工具定义等不变的前缀通常占 10K-50K token。这部分每一轮都完全相同,是 cache 的稳定命中区。即使后续上下文被修改,前缀的 cache 命中仍然有效。

2. 缩减后的 cache 更高效

假设原始上下文 100K token,RTK 压缩到 40K:

  • 未压缩:100K token 全价,即使 cache 命中 50%,仍为 50K 全价 + 50K 半价 = 75K 等价
  • 压缩后:40K token,cache 命中 50%,为 20K 全价 + 20K 半价 = 30K 等价

缩减让 cache miss 的代价也变小了

3. Cache miss 不是全有全无

Prompt Cache 前缀匹配
拖动滑块移动前缀匹配边界,观察 cache 命中率和节省比例
前缀匹配边界
Cache 命中 (复用 KV)
Cache 未命中 (重新计算)
Cache 命中率
67%
计算量节省
60%

即使后半段上下文发生变化,前缀部分的 KV 状态仍被复用。修改通常发生在上下文末尾(最新的工具调用结果),大部分 cache 仍然有效。

4. 成本算术

以 Anthropic Claude 为例(90% cache 读取折扣):

场景每轮 tokenCache 命中率*等价成本
无优化100K70%37K 等价
预过滤压缩 60%40K70%14.8K 等价
仅预过滤无 cache40K0%40K 等价

*表中 70% 命中率为假设值,实际取决于会话模式和 TTL 配置。90% 折扣基于 Anthropic 2026 年定价。

预过滤 + cache 叠加(14.8K)比单独使用任一方案都便宜。缩减降低基数,cache 降低单价,两者相乘。

与 Token 缩减的关系

总结:Token 缩减和 Prompt Cache 解决的是不同维度的问题:

  • 缩减解决"发送了不需要的 token"——信噪比问题
  • Cache解决"重复计算相同的 token"——冗余计算问题

两者不冲突,而是相乘关系。最优策略:先缩减(降低基数),再让 cache 发挥作用(降低单价)。

下一步

理解了原理,可以深入具体的实现探索: