第 3 节 · Token、上下文窗口与成本
为什么需要 Token
计算机只能处理数字,不能直接理解文本。所以在文本进入 LLM 之前,必须先把它转换成数字序列——这个过程叫 分词(Tokenization),切出来的每个最小单元叫 Token(词元)。
你可能会问:直接按字符(character)或按单词(word)切不就行了?两种朴素方案都有致命问题:
| 方案 | 做法 | 问题 |
|---|---|---|
| 按单词切 | 用空格/标点分隔 | 词表爆炸(英文有几十万单词),且遇到新词(如 "ChatGPT")直接抓瞎 |
| 按字符切 | 每个字母/汉字单独一个 | 单个字符没有语义("a" 本身什么意思?),模型学习效率极低 |
所以现代 LLM 采用了折中方案:子词分词(Subword Tokenization)。
Token 和向量的关系
分词只完成了第一步——把文本拆成 token 序列。但模型做的是数学运算,它需要的是数字向量,不是字符串碎片。
所以完整的流程是:
文本 → 分词 → 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 序列。
- 初始化:把所有文本拆成最小的字符
- 统计:哪两个相邻字符出现频率最高?
- 合并:把这一对合并成一个新 token
- 重复 2-3,直到词表大小达到预设值(如 100K / 200K)
训练语料: "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 不是字、不是词,是介于两者之间的"子词单位"(subword)。
LLM 不直接处理字符串——它先把文本切成 token 序列,再对每个 token 做概率预测。你可以把 token 理解为模型世界里的"原子"。
不同语言的 Token 差异
| 文本 | 字符数 | Token 数(o200k_base) | 规律 |
|---|---|---|---|
"Hello, world!" | 13 | 4 | 英文 1 词 ≈ 1 token |
"你好,世界!" | 6 | 4 | 中文常见词可能被合并 |
"def foo(): return 1" | 19 | 6 | 代码的标点缩进各算 token |
"🤖 AI" | 4 | 3 | emoji 可能拆成多个 token |
经验法则:
- 英文:1 token ≈ 4 个字符 ≈ 0.75 个单词
- 中文:1 个汉字 ≈ 0.5-1.5 token,常见词可能合并成一个 token
- 代码:标点、缩进和重复片段都会影响分词,所以代码不能只按行数估算
如何查看 Token 切分
可以用 OpenAI 的 tiktoken 库来查看任意文本被切成了什么 token:
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 | $15 | 1M 上下文,代码场景常用 |
| 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-4o | 128K | 多模态主力 |
| GPT-4.1 | 约 1M | 长上下文文本/视觉输入 |
| Claude Sonnet 4.6 | 1M | 长上下文已进入标准定价 |
| Gemini 2.5 Pro | 1M | 长上下文请求可能有阶梯价 |
| DeepSeek-V3 | 128K | 开源性价比 |
三个容易搞错的点
1. 输入 + 输出共享窗口
128K 上下文 ≠ 能输入 128K 再输出一堆。是 输入 + 输出 ≤ 128K,且输出还受 max_output_tokens 单独限制(常见范围是 8K-64K,推理模型可能更高)。
如果你给模型 100K 的输入,留给输出的空间只剩 28K——但实际上在此之前,大多数模型就会因为输出上限而截断了。
2. 超出窗口 = 直接报错
API 不会"自动截断",而是返回错误:
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)

早期研究(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
成本估算实例
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 会演示三件事:
- 不同语言的 tokenization 对比:英文、中文、日文、代码、emoji
- 成本估算:一份文档跑多少次要多少钱
- 故意撑爆上下文窗口:构造超长输入,看 API 怎么报错
小结
| 概念 | 一句话理解 |
|---|---|
| Token | LLM 的最小处理单位(子词) |
| 上下文窗口 | 一次推理能看多少 token,输入+输出共享 |
| Lost in the Middle | 长上下文的中间位置容易被忽略 |
| 计费单位 | 按 token 计费,不是按字符 |
| 推理 token | 推理模型会产生隐藏 thinking tokens,也会计费 |
| 经验法则 | 中文 1 字 ≈ 0.5-1.5 token,英文 1 词 ≈ 1 token |
下一节:模型挺能聊的,但它有时候会编——这就是幻觉。为什么会发生?怎么防?