跳转到内容

第 3 节 · Token、上下文窗口与成本

为什么需要 Token

计算机只能处理数字,不能直接理解文本。所以在文本进入 LLM 之前,必须先把它转换成数字序列——这个过程叫 分词(Tokenization),切出来的每个最小单元叫 Token(词元)

你可能会问:直接按字符(character)或按单词(word)切不就行了?两种朴素方案都有致命问题:

方案做法问题
按单词切用空格/标点分隔词表爆炸(英文有几十万单词),且遇到新词(如 "ChatGPT")直接抓瞎
按字符切每个字母/汉字单独一个单个字符没有语义("a" 本身什么意思?),模型学习效率极低

所以现代 LLM 采用了折中方案:子词分词(Subword Tokenization)

Token 和向量的关系

分词只完成了第一步——把文本拆成 token 序列。但模型做的是数学运算,它需要的是数字向量,不是字符串碎片。

所以完整的流程是:

bash
文本 分词 Token 序列 查表 向量序列 模型计算
"Hello world"  ["Hello", " world"]  [token_id_1, token_id_2]  [[0.12, -0.03, ...], [0.87, 0.44, ...]]

每个 token 对应词表里的一个整数 ID,然后通过一张嵌入矩阵(Embedding Matrix) 把这个 ID 映射为一个高维向量(通常 1024-12288 维)。具体 ID 取决于模型使用的分词器,同一个词在不同模型里可能对应不同编号。

这个向量才是模型真正"看到"的东西——它编码了这个 token 的语义信息。语义相近的 token,向量也会相近(比如 "king""queen" 的向量距离就比 "king""banana" 的近得多)。

为什么不直接跳过 token,直接把文字变成向量? 因为你需要先确定"以什么为单位"来生成向量。按字符太细没语义,按单词太粗词表爆炸——Token 恰好是那个"正确的粒度"。分词决定了模型用什么粒度来理解和生成语言。

简单来说:Token 是文本和向量之间的桥梁。没有 token,模型无法知道该把哪些字符打包成一个语义单元去做向量化。

Token 的简单原理:子词分词

核心思想一句话:常见的词保持完整,罕见的词拆成有意义的碎片

比如 "Tokenization" 会被拆成 "Token" + "ization"——两个碎片各自有意义,合起来也能还原。而高频词 "the" 会作为一个整体保留。

最主流的算法之一叫 BPE(Byte-Pair Encoding),OpenAI 的 tiktoken 编码器就属于这一类。其他厂商也可能使用 SentencePiece、Unigram 或自研变体,但核心目标相同:把文本压缩成模型能处理的 token 序列。

  1. 初始化:把所有文本拆成最小的字符
  2. 统计:哪两个相邻字符出现频率最高?
  3. 合并:把这一对合并成一个新 token
  4. 重复 2-3,直到词表大小达到预设值(如 100K / 200K)
bash
训练语料: "low lower lowest"
 1 轮: 频率最高的相邻对 = ('l', 'o') → 合并为 'lo'
 2 轮: 频率最高的相邻对 = ('lo', 'w') → 合并为 'low'
 3 轮: 频率最高的相邻对 = ('e', 'r') → 合并为 'er'
...
最终词表: ['low', 'er', 'est', ...]

这样做的好处:

  • 词表可控:不管语言多复杂,词表大小固定(通常 32K-200K)
  • 不怕新词:没见过的词总能拆成已知的子词组合
  • 保留语义:高频词完整保留,低频词的碎片也是有意义的片段

你不需要自己实现分词器——模型提供商已经训练好了。我们只需要理解"Token 是怎么来的",就能更好地估算成本和管理上下文。

2026 年实践中要注意:不同模型的 token 数不一定相同。OpenAI 可以用 tiktoken 估算;Anthropic、Gemini 等模型最好以官方 API 返回的 usage 或官方 token counting 工具为准。

什么是 Token

Token 不是字也不是词,而是介于两者之间的子词单位

Token 不是字、不是词,是介于两者之间的"子词单位"(subword)。

LLM 不直接处理字符串——它先把文本切成 token 序列,再对每个 token 做概率预测。你可以把 token 理解为模型世界里的"原子"。

不同语言的 Token 差异

文本字符数Token 数(o200k_base规律
"Hello, world!"134英文 1 词 ≈ 1 token
"你好,世界!"64中文常见词可能被合并
"def foo(): return 1"196代码的标点缩进各算 token
"🤖 AI"43emoji 可能拆成多个 token

经验法则

  • 英文:1 token ≈ 4 个字符 ≈ 0.75 个单词
  • 中文:1 个汉字 ≈ 0.5-1.5 token,常见词可能合并成一个 token
  • 代码:标点、缩进和重复片段都会影响分词,所以代码不能只按行数估算

如何查看 Token 切分

可以用 OpenAI 的 tiktoken 库来查看任意文本被切成了什么 token:

python
import tiktoken

enc = tiktoken.get_encoding("o200k_base")  # GPT-4o / GPT-4.1 系列常用编码
text = "你好,世界!"

tokens = enc.encode(text)
pieces = [enc.decode([t]) for t in tokens]

print(f"Token 数: {len(tokens)}")
print(f"切分结果: {pieces}")
# → ['你好', ',', '世界', '!']

如果你知道具体模型,也可以优先使用 tiktoken.encoding_for_model("gpt-4o")。如果本地 tiktoken 版本还不认识新模型,再退回到对应的基础编码(如 o200k_base)。

NOTE

遇到 emoji 这类多字节字符时,单个 token 不一定能独立解码成完整字符,逐个 decode 可能出现 。这不是乱码错误,而是该 token 只对应原始字节的一部分。

运行 demo_03_tokens.py 可以看到更多语言的切分对比。

为什么要在乎 Token

三个原因:

1. 计费按 Token

LLM API 的定价单位是 token,不是字符。下面是典型价格(2025-2026 水平,价格持续下降中,仅用于教学估算):

模型输入 $/1M token输出 $/1M token备注
GPT-4.1$2$8性价比高,1M 上下文
GPT-4o$2.5$10多模态主力
Claude Sonnet 4.6$3$151M 上下文,代码场景常用
Gemini 2.5 Pro$1.25 / $2.5$10 / $15长上下文请求可能加价
DeepSeek-V3$0.27$1.10开源,极低价
混元 Turbo S免费/极低免费/极低以官方活动为准

算一笔账:一次典型的 Coding Agent 调用(输入 5K + 输出 1K token),用 GPT-4.1 大约 $0.018(约 ¥0.13)。一天跑 100 次 ≈ ¥13,一个月 ≈ ¥400。

WARNING

推理模型(o1/o3/DeepSeek-R1/Gemini Thinking 等)有隐藏成本:它们会在内部生成 reasoning / thinking tokens。这些 token 用户通常看不到,但会占用上下文窗口,并按输出 token 计费。复杂任务里,实际成本可能比普通聊天模型高数倍。

TIP

真实账单还会受到 prompt caching、Batch API、长上下文阶梯价、工具调用、图片/音频 token、云厂商汇率和地区价格影响。生产环境不要只看模型单价,要看完整调用链。

这就是为什么做 Agent 必须关注上下文管理——不是什么都往里塞。

2. 上下文窗口按 Token 算

模型能"看到"多少内容,不是按字数算的,而是按 token 数。一个 128K 上下文的模型,粗略能装进去几万到十几万中文字——听起来很多,但如果你把一整个代码仓库塞进去,很容易就爆了。

所以在构建 Agent 时,你需要精打细算:哪些信息必须放进上下文,哪些可以省略或摘要。

3. 生成延迟跟 Token 数线性相关

LLM 是逐个 token 生成的,每输出一个 token 就要做一次前向传播。输出 100 token 和输出 1000 token,延迟大约差 10 倍。

这意味着:如果你让模型"详细解释",它会输出更多 token,响应时间也会明显变长。在 Agent 场景中,控制输出长度(比如要求模型只返回 JSON、只输出结论)是优化响应速度的关键手段。

上下文窗口:LLM 的"工作内存"

输入和输出共享同一个上下文窗口,加起来不能超出容量

模型一次推理能"看"到的最大 token 数,叫上下文窗口(context window)

模型上下文窗口备注
GPT-4o128K多模态主力
GPT-4.1约 1M长上下文文本/视觉输入
Claude Sonnet 4.61M长上下文已进入标准定价
Gemini 2.5 Pro1M长上下文请求可能有阶梯价
DeepSeek-V3128K开源性价比

三个容易搞错的点

1. 输入 + 输出共享窗口

128K 上下文 ≠ 能输入 128K 再输出一堆。是 输入 + 输出 ≤ 128K,且输出还受 max_output_tokens 单独限制(常见范围是 8K-64K,推理模型可能更高)。

如果你给模型 100K 的输入,留给输出的空间只剩 28K——但实际上在此之前,大多数模型就会因为输出上限而截断了。

2. 超出窗口 = 直接报错

API 不会"自动截断",而是返回错误:

bash
context_length_exceeded: This model's maximum context length is 128000 tokens.
However, your messages resulted in 134567 tokens.

ChatGPT 之所以聊久了会"忘事",是因为前端主动裁掉了早期消息来避免超限——模型本身不会自动处理。

3. 越长越笨(Lost in the Middle)

长上下文里中间最容易被忽略,注意力呈 U 形曲线

早期研究(2023)表明,大部分模型在上下文超过 50% 容量后,对中间位置的内容关注度显著下降。把关键信息放在一长段文本的正中间,模型很可能会忽略它。这被称为 "Lost in the Middle" 现象。

NOTE

2025-2026 年的新一代模型(Claude Sonnet 4.6、Gemini 2.5 Pro、GPT-4.1 等)在长上下文检索测试(如 Needle-in-a-Haystack)上已经有显著改善。但这些测试往往更像"找关键词",不能完全代表真实 Agent 场景。到了极长上下文 + 多步推理 + 多处证据整合时,问题仍然存在。

实践中仍建议:

  • 关键信息放开头或结尾,不要藏在超长材料正中间
  • 先检索再塞上下文,不要把整个仓库、整本文档都丢进去
  • 用摘要压缩历史,保留结论、决策和未完成任务
  • 把输出格式写清楚,减少无效输出 token

成本估算实例

python
import tiktoken

enc = tiktoken.get_encoding("o200k_base")  # GPT-4o / GPT-4.1 系列常用编码

# 假设你要让 LLM 审查一个 500 行的 Python 文件
with open("my_code.py") as f:
    code = f.read()

n_tokens = len(enc.encode(code))
input_price_per_1m = 2.0  # GPT-4.1 输入价格 $2/1M,仅作估算
cost_per_call = n_tokens / 1_000_000 * input_price_per_1m

print(f"文件: {len(code)} 字符 → {n_tokens} token")
print(f"单次审查输入成本: ${cost_per_call:.4f}")
print(f"每天审查 50 个文件: ${cost_per_call * 50:.2f}")

动手试试

运行 demo_03_tokens.py 会演示三件事:

  1. 不同语言的 tokenization 对比:英文、中文、日文、代码、emoji
  2. 成本估算:一份文档跑多少次要多少钱
  3. 故意撑爆上下文窗口:构造超长输入,看 API 怎么报错

小结

概念一句话理解
TokenLLM 的最小处理单位(子词)
上下文窗口一次推理能看多少 token,输入+输出共享
Lost in the Middle长上下文的中间位置容易被忽略
计费单位按 token 计费,不是按字符
推理 token推理模型会产生隐藏 thinking tokens,也会计费
经验法则中文 1 字 ≈ 0.5-1.5 token,英文 1 词 ≈ 1 token

下一节:模型挺能聊的,但它有时候会编——这就是幻觉。为什么会发生?怎么防?

Released under the MIT License.