什么是上下文?——AI的"对话笔记本"
第 1 章 什么是上下文?——AI 的"对话笔记本"
小熊的魔法书
森林深处有一棵老橡树,橡树的树洞里住着一只名叫"咔咔"的小熊。咔咔今年七岁,和你差不多大。它有一个神奇的伙伴——一本会说话的魔法书。
每天早晨,咔咔都会打开魔法书问问题。
"魔法书,魔法书,今天天气怎么样?" 魔法书翻动书页,亮起柔和的蓝色光芒:"今天是晴天,适合去河边玩耍。"
"魔法书,魔法书,我昨天答应妈妈要做什么来着?" 魔法书沉默了几秒钟,然后翻到昨天的页面:"你昨天答应妈妈下午三点去采蘑菇。"
小熊很奇怪:"魔法书,你怎么记得这么清楚?" 魔法书笑着回答:"因为我有一本'对话笔记本'。每次你跟我说的话,我都会一字不落地记下来。当你再次问我问题的时候,我会把笔记本里的内容和你最新的问题一起'看'一遍,这样我就能回答你了。"
你猜对了——这本"对话笔记本",就是我们要讲的上下文(Context)。
魔法书也有"忘记"的时候。如果对话太长,笔记本写满了,新进来的话就会把旧的话挤出去。魔法书忘记早上的事,不是因为它不聪明,而是因为笔记本的页数是有限的。
所以,上下文就是 AI 在回答你之前,能够"看到"的全部文字信息。它包括三部分:你之前说过的话、AI 之前回答过的话、以及你刚刚提出的最新问题。这三者拼在一起,组成了 AI 当前的"视野"。
理解了这一点,你就抓住了理解 AI 记忆系统的第一把钥匙。
---
什么是上下文?AI 的"工作记忆"
让我们把魔法书的故事翻译成计算机的语言。
当你打开一个聊天软件(比如 ChatGPT、豆包、Kimi、文心一言),在输入框里敲下一段文字,按下回车键的那一刻,你的文字会沿着网络"飞"到一台巨大的计算机里。这台计算机里运行着一个 AI 模型——一个由数百亿个数字参数组成的"大脑"。
AI 大脑在思考之前,必须先把你的问题"读进去"。但它读的方式和你不一样:你看到的是汉字和英文单词,它看到的却是一串串数字。
那么,从你按下回车到 AI 给出回答,中间到底发生了什么?大致是这样的:
``` 你的问题 → 切成小块 → 变成数字 → 喂给 AI 大脑 → AI 输出数字 → 翻译回文字 → 显示在屏幕上 ```
这五步里,最关键、也最神奇的一步,就是"切成小块"。这一步有一个专业的名字,叫做Token 化(Tokenization),也叫"分词"。
为什么要切成小块呢?因为 AI 大脑其实不懂中文,也不懂英文,它只懂数学。数学里只有数字,没有"你好"、"hello"这样的字。所以,我们必须先把人类的语言翻译成数字,才能让 AI 处理。
而 Token,就是人类语言和 AI 数字世界之间的"翻译官"。
每一个 Token 都对应一个独一无二的数字编号,这个编号叫做 Token ID。比如,"你"可能对应 53901,"好"可能对应 10812。当你说"你好"的时候,AI 看到的不是两个字,而是两个数字:53901 和 10812。
反过来也一样——当 AI 想回答"你好"的时候,它的大脑里先冒出两个数字 53901 和 10812,然后系统把这两个数字翻译回汉字,显示给你看。
这就是上下文最底层的秘密:上下文不是一串文字,而是一串 Token,更准确地说,是一串 Token ID(数字)。
让我们用一个简单的例子来验证。假设你问 AI:
> "请告诉我天空为什么是蓝色的。"
这句话里有多少个 Token 呢?答案是 9 个左右(具体数字取决于使用的分词器)。如果是英文 "Please tell me why the sky is blue",大概是 7 个 Token。中文和英文的 Token 数量差异,恰恰反映了不同语言的"信息密度"——中文一个字往往承载更多信息。
现在,请你记住一句话:Token 是 AI 的"积木块",所有上下文都是由 Token 搭建起来的。
---
Token 化原理:把文字切成最小处理单元
你已经知道 Token 是什么了。但 Token 到底是怎么"切"出来的?为什么"你好"是两个 Token,而"你好吗"是三个 Token?
这就要涉及到一种叫做 BPE(Byte Pair Encoding,字节对编码) 的算法了。BPE 是目前几乎所有主流 AI 模型都在使用的分词方法。它的工作原理有点像小朋友玩的"找重复"游戏。
让我用一个真实的中文句子来演示。假设我们要切分这句话:
> "我爱吃苹果"
第一步,BPE 会查一张巨大的"词表"(vocabulary)。这张词表里预先存储了几万个常见的字、词片段和符号。比如词表里有"我"、"爱"、"吃"、"苹果"、"苹果派"等等。
第二步,BPE 从左到右扫描这句话,尝试在词表里找最长的匹配。
- 看到"我"——词表里有,匹配成功,切出第一个 Token:"我"
- 看到"爱"——词表里有,匹配成功,切出第二个 Token:"爱"
- 看到"吃"——词表里有,匹配成功,切出第三个 Token:"吃"
- 看到"苹果"——词表里有"苹果"这个词,匹配成功,切出第四个 Token:"苹果"
于是,"我爱吃苹果"被切成 4 个 Token。如果每个 Token 对应一个数字,那么这句话就被翻译成了类似 [1234, 5678, 9012, 3456] 这样的数字串。
听起来很简单对吧?但实际的情况要复杂得多。中文有上万个常用字,加上词组合,理论上可以切出无数种方式。BPE 的精妙之处在于,它通过统计"哪些字经常一起出现",自动学会了最合理的切分方式。比如"苹果"经常作为一个整体出现,所以 BPE 会优先把它当作一个 Token,而不是切成"苹"和"果"两个字。
而且,不同 AI 模型使用的词表是不同的。GPT 系列有 GPT 的词表,LLaMA 有 LLaMA 的词表,文心一言有文心的词表。这意味着同一句话在不同模型里被切成的 Token 数量可能不一样。GPT-4 切成 9 个 Token 的句子,LLaMA 可能切成 11 个,Qwen 可能切成 10 个。
为什么词表不能统一呢?因为每家公司在训练自己的 AI 时,使用的数据、统计的频率、优化目标都不一样,所以学出来的"切分习惯"也不一样。就像不同地方的人切西瓜的方法不一样——有的横着切,有的竖着切,但都能吃到西瓜。
这里还有一个重要的概念需要理解:Token 不一定等于一个字,也不一定等于一个词。
它可以是半个字(比如英文里的"un"、"ing"这样的词根),也可以是好几个字组成的词(比如中文里的"人工智能"、"上下文工程"),还可以是一个标点符号、一个空格、一个特殊字符。
举个例子,"人工智能"这四个字,在 GPT 的词表里可能被切成 1 个 Token,也可能被切成 2 个 Token("人工"+"智能"),甚至 4 个 Token("人"+"工"+"智"+"能")。具体怎么切,取决于词表里有没有"人工智能"这个完整的词条。
这是一个非常重要的细节:Token 是模型的"母语",字和词是人类的"母语"。两者不是一一对应的关系。
理解了这一点,你就能解释为什么 AI 经常会"数错字数"。当你让它写一首五言绝句,每句五个字,它可能会写成每句五个 Token——但五个 Token 对应的汉字数量可能不是五个。这就是 AI 和人类在"语言感知"上的根本差异。
---
用 Python 切分 Token:动手实践
光说不练假把式。现在让我们真正动手,用 Python 把一段中文文本切成 Token,看到底长什么样。
在开始之前,你需要做两件事:
第一,在你的电脑上安装 Python。如果你是 Windows 用户,可以去 python.org 下载安装包;如果是 Mac 用户,系统自带 Python;如果是 Linux 用户,你肯定已经会装了。
第二,安装一个叫做 `tiktoken` 的库。这是 OpenAI 公司开源的分词工具,可以把文字切成 GPT 模型使用的 Token。在终端(或命令提示符)里输入:
```bash pip install tiktoken ```
安装完成后,打开你的代码编辑器(比如 VS Code、PyCharm,或者最简单的 IDLE),新建一个文件,命名为 `token_demo.py`,然后输入下面的代码:
```python import tiktoken
第一步:选择一个分词器
这里我们使用 GPT-4 使用的分词器 cl100k_base
encoding = tiktoken.get_encoding("cl100k_base")
第二步:准备一段中文文本
text = "人工智能正在改变世界,它让机器学会了思考。"
第三步:把文本编码成 Token
tokens = encoding.encode(text)
第四步:打印结果
print(f"原始文本:{text}") print(f"Token 数量:{len(tokens)}") print(f"Token 列表:{tokens}")
第五步:把 Token 列表里的每个 ID 单独打印出来
for i, token_id in enumerate(tokens):
我们还可以把每个 Token 翻译回文字,看看对应什么
decoded = encoding.decode([token_id]) print(f" 第 {i+1} 个 Token:ID = {token_id},对应文字 = '{decoded}'") ```
运行这段代码,你会看到类似这样的输出:
``` 原始文本:人工智能正在改变世界,它让机器学会了思考。 Token 数量:14 Token 列表:[45626, 20918, 36117, 32118, 1555, 22164, 11230, 11, 109, 24097, 16143, 84208, 24680, 23456] 第 1 个 Token:ID = 45626,对应文字 = '人工' 第 2 个 Token:ID = 20918,对应文字 = '智能' 第 3 个 Token:ID = 36117,对应文字 = '正' ... ```
(注意:实际的 Token ID 和切分结果会随着 tiktoken 版本更新而变化,这里只是为了演示。)
观察输出,你会发现几件有趣的事情:
第一,"人工智能"被切成了两个 Token:"人工"和"智能"。这说明 GPT-4 的词表里没有把"人工智能"当作一个完整的词条。
第二,标点符号","和"。"也被切成了独立的 Token,而且它们的 ID 通常比较小(比如这里的 11)。
第三,"它让机器学会了思考"这一长串字,可能被切成 8 个 Token,每个 Token 对应一到两个字。
现在让我们试试英文,看看有什么不同。把代码里的 text 改成:
```python text = "Artificial intelligence is changing the world." ```
运行后你会发现,英文的 Token 切分规律和中文完全不同。比如 "Artificial" 可能被切成 "Art"、"ific"、"ial" 三个 Token,"intelligence" 可能被切成 "int"、"elligence" 两个 Token。这是因为英文的词根词缀非常丰富,BPE 会把这些片段当作独立的 Token。
而中文因为每个字本身就是最小的语义单位,所以切分粒度通常更细——一个字往往就是一个 Token,或者几个字组成一个 Token。
这就是为什么我们说:"每个 Token 大约对应 0.75 个英文单词或 1 个中文字"。这不是精确的数学公式,而是大量统计后得出的经验值。具体到某句话,数字可能有出入,但作为估算 Token 数量的快速方法,这个比例非常实用。
如果你想知道一段文本到底有多少个 Token,但手边没有 Python 环境,还有一个更简单的办法:直接使用 OpenAI 官网提供的 Tokenizer 工具(网址是 platform.openai.com/tokenizer)。打开网页,把文字粘贴进去,它会实时显示切分结果和 Token 数量。这是很多 AI 工程师日常使用的工具。
---
Token ID:藏在数字里的秘密
现在你已经亲手把一段文字切成了 Token,看到了每个 Token 对应的 ID。但你有没有想过:这些数字 ID 到底是怎么来的?为什么"人工"是 45626 而不是 1234?
这就要提到 AI 模型的"词表(Vocabulary)"了。
词表,你可以理解为一本巨大的"字典"。这本字典里列出了 AI 能认识的所有 Token,每个 Token 都有一个独一无二的 ID。就像你的班级座号表:1 号是张三,2 号是李四,3 号是王五——每个人对应一个数字。
GPT-4 使用的 cl100k_base 词表里,大约有 10 万个 Token。这 10 万个 Token 涵盖了:
- 几乎所有的英文单词(各种时态、词形变化)
- 常见的中文单字和词组
- 各种数字、标点符号、特殊字符
- 一些常用的代码片段(比如 "def"、"return")
- 甚至还有表情符号(比如 😀、🎉)
词表的构建过程,本质上是一个统计学习的过程。AI 训练时会阅读海量的文本数据——网页、书籍、新闻、对话记录——然后统计哪些字或词片段出现频率最高。频率越高的内容,越可能被收入词表,而且往往被当作一个完整的 Token;频率较低的内容,则可能被切成更小的片段。
这就是为什么高频词(比如"the"、"是"、"我")通常是单个 Token,而低频词或生僻词(比如"蹀躞"、"饕餮"这样的字)往往需要切成多个 Token。
让我用一个实验来验证这一点。试着运行下面的代码:
```python import tiktoken
encoding = tiktoken.get_encoding("cl100k_base")
高频词 vs 低频词
common_word = "你好" rare_word = "饕餮"
common_tokens = encoding.encode(common_word) rare_tokens = encoding.encode(rare_word)
print(f"'{common_word}' 被切成 {len(common_tokens)} 个 Token:{common_tokens}") print(f"'{rare_word}' 被切成 {len(rare_tokens)} 个 Token:{rare_tokens}") ```
你很可能会看到这样的结果:
- "你好" → 2 个 Token(每个字一个)
- "饕餮" → 4 个或更多 Token(每个字可能还要进一步切分)
这说明"饕餮"这样的生僻字在 GPT 训练数据中出现频率太低,词表里没有完整收录,只能拆成更小的片段来凑。
Token ID 的数值大小本身没有特别含义,不代表哪个 Token 更"重要"或者更"高级"。它只是在词表里的索引位置。比如 cl100k_base 词表里,"!" 的 ID 是 0,"," 的 ID 是 11,"的" 的 ID 可能是 14858,"人工智能"(如果作为一个整体)的 ID 可能是 45626。数字就是 ID,就像你的学号不代表你比同学更聪明一样。
但有一个有趣的规律:词表里 ID 较小的 Token 通常是标点符号、特殊字符和最基础的英文片段。这是因为在 BPE 算法的合并顺序中,这些符号最先出现,最先被分配 ID。
---
上下文窗口:AI 的"视野边界"
现在让我们把镜头拉远,回到魔法书的故事。
魔法书的"对话笔记本"页数是有限的。如果对话太长,笔记本写满了,新进来的话就会把最早的话挤出去。AI 的上下文也是一样的——它有一个最大长度限制,叫做上下文窗口(Context Window)。
上下文窗口,就是 AI 一次能处理的最大 Token 数量。
不同 AI 模型的上下文窗口大小不一样:
| 模型 | 上下文窗口 | |------|-----------| | GPT-3.5(早期) | 4,096 Token | | GPT-4(早期) | 8,192 Token | | GPT-4 Turbo | 128,000 Token | | Claude 3.5 Sonnet | 200,000 Token | | Gemini 1.5 Pro | 1,000,000 Token | | 国产大模型(多数) | 8,000 - 200,000 Token |
(这些数字会随着版本更新而变化,请以官方最新数据为准。)
让我们把这个数字换算成更直观的单位:
- 8,192 Token 大约相当于 6,000 个英文单词,或 12,000 个中文字——差不多是一篇短篇小说。
- 128,000 Token 大约相当于 96,000 个英文单词,或 192,000 个中文字——差不多是一本中等厚度的书。
- 1,000,000 Token 大约相当于 750,000 个英文单词——差不多能装下一整套《哈利·波特》七本书。
上下文窗口为什么是有限的? 这是个好问题。原因主要有三个:
第一,计算成本。AI 模型处理 Token 的计算量,随着 Token 数量的增长呈平方级增长(我们用数学符号 $O(n^2)$ 来表示)。如果上下文窗口从 8,000 扩大到 128,000,Token 数量增加 16 倍,但计算量会增加 256 倍。这意味着处理长上下文的成本极其昂贵。
第二,显存限制。AI 模型运行时需要把所有 Token 的信息都装进 GPU 的显存里。显存是有限的硬件资源,就像你的书包容量是固定的一样。Token 越多,占用的显存越大。
第三,注意力分散。研究表明,当上下文特别长的时候,AI 模型对其中某些信息的"注意力"会下降。就像一个小孩被塞了太多玩具,反而不知道玩哪个了。AI 在超长上下文中也会出现"找不到重点"的现象。
所以,上下文窗口既是一个技术限制,也是一个工程设计上的权衡。太大了,成本高、性能差;太小了,又不够用。
当你和 AI 对话时,如果你的对话超过了上下文窗口,最早的内容就会被"挤掉"——AI 就像失忆了一样,忘记了对话最开始的部分。这就是为什么有时候你会发现,AI 突然"忘了"你十分钟前告诉它的事情。
---
一段对话是如何变成 Token 序列的
让我们用一个完整的例子,把今天学到的所有概念串起来。