为什么大语言模型处理 Token,而不是文字:Tokenizer 与 Embedding 一次讲清
为什么大语言模型处理 Token,而不是文字:Tokenizer 与 Embedding 一次讲清
摘要(先看结论)
- 模型不“读文字”,只“吃数字”。文字先经
tokenizer变成 token id 序列,再送进模型。 - token 不是“字”也不是“词”,更常见是“子词/片段”。同一句话在不同 tokenizer 下 token 数可能不同。
- token id 只是“词表里的索引”,数字大小本身没有语义远近;语义关系来自后续的向量表示(embedding)与模型训练。
- RAG 的 embedding 是“把一段文本变成一个向量用于检索”,和大模型内部的 token embedding 不是一回事,但它们都属于“用向量表示语义”的思路。
1. Tokenizer 在做什么:把文字变成模型能处理的输入
把一次对话输入到大模型,可以粗略理解成这条流水线:
- 文本 →
tokenizer.encode(...)→ token id 序列(整数) - token id 序列 →
embedding→ 向量序列(浮点数向量) - 向量序列 → 模型多层计算 → 预测下一个 token 的概率分布
- 采样/解码 → 输出 token id →
tokenizer.decode(...)→ 文本
这里最关键的一点是:模型的输入不是字符,而是序列化的整数(token id)。
2. Token 到底是什么:不是字,也不一定是词
token 更像“拼积木的单位”:
- 有时一个 token 就是一个完整词(英文里更常见)
- 有时一个词会被拆成多个 token(例如词根 + 词缀、或罕见拼写)
- 中文里常见情况是:一个 token 可能对应一个字、一个常见词、或带标点/空格的片段(取决于 tokenizer)
很多人卡住的点在这里:你看到的 token 像“字/词/片段”,但模型真正吃的是数字。
更精确地说有三层东西:
- token(文本片段):tokenizer 切出来的片段(“看起来像字/词/词组的一部分”)。
- token id(整数):每个 token 在词表里的编号;模型输入的是这一串编号。
- embedding(向量):模型把 token id 映射成向量后再进行计算。
那这个“编号(token id)”是怎么来的?
- 先用 BPE/WordPiece/Unigram 这类算法在海量语料上训练出一份词表(vocab):里面列出了“允许出现的 token 片段集合”。
- 再把词表里的每个 token 按某种固定顺序排好并依次编号(同时会给
<eos>/<bos>/<pad>/<unk>等特殊 token 预留固定编号)。 - 所以 token id 更像“字典索引号”,它的大小通常不代表语义远近;语义远近靠 embedding 与后续训练来形成。
因此,“上下文窗口有多少 token”并不能直接换算成“多少字”,只能做经验估算。
3. 为什么不能让模型直接处理文字
并不是“直接处理字符不行”,而是工程上通常不划算:
- 字符级别序列更长,计算更重(Transformer 的计算量与序列长度强相关)
- 子词 token 能在“词表大小”和“序列长度”之间做一个更好的折中
- token 化还能让模型更容易复用常见片段,减少对完全字符级建模的依赖
这里说的不是“汉字 vs 数字”。因为无论中文还是英文,最终都会变成 token id(数字)进模型;区别在于切分粒度会让“序列长度”差很多,从而影响计算量。
举个中文例子(示意,同一句话不同 tokenizer 可能略有差异):
- 字符级:
人 / 工 / 智 / 能 / 很 / 有 / 趣→ 7 个 token - 子词级:
人工智能 / 很 / 有趣→ 3 个 token
注意力的计算大致会随着 token 数 T 增长得很快(常见近似是 T²)。所以把 7 个 token 压到 3 个 token,工程上就能省下不少计算。
4. Embedding 是什么:把 token id 变成向量
token id 只是一个整数索引,本身没有“距离=语义距离”的含义。
embedding 的作用是:把离散的索引映射为连续向量,让模型可以用连续空间表示“相似/相关”的关系。
最常见的 token embedding 本质就是一张可训练的“查表矩阵”:
- 形状:
[vocab_size, d_model] - 输入:token id(例如 12345)
- 输出:长度为
d_model的向量(例如 4096 维)
工程实现经常是“embedding lookup”(按行取向量),而不是显式做 one-hot 再乘矩阵;但数学本质是一样的。
把上面几个词翻译成人话:
vocab_size:词表大小,也就是“token 总共有多少个编号”。例如 50,000 表示词表里一共有 50,000 个 token。d_model:向量维度,也就是“每个 token 用多少个数字来表示”。例如 4096 表示每个 token 都会被映射成一个长度为 4096 的向量。one-hot:把“一个编号”变成“一个很长的 0/1 向量”。例如词表大小是 5,token id=2 的 one-hot 就是[0, 0, 1, 0, 0](只有第 2 个位置是 1)。embedding lookup:直接“按编号查表取一行”。例如 token id=2,就去 embedding 矩阵里取第 2 行向量作为输出。
为什么说 one-hot 乘矩阵和 lookup 是一回事(用一个极小例子说明):
- 词表大小
vocab_size=5,向量维度d_model=3,embedding 矩阵就是一个5×3的表。 - token id=2:
- lookup:取表的第 2 行,得到一个长度为 3 的向量。
- one-hot:先得到
[0,0,1,0,0],再乘5×3的矩阵,结果也只会“选中第 2 行”,得到同一个向量。
你不需要“在工程里手写 one-hot”。了解它的价值主要是两点:
- 帮助你理解 embedding 这张表为什么是
[vocab_size, d_model]:每个 token id 对应表里的一行。 - 帮助你理解 lookup 为什么成立:lookup 只是把“one-hot × embedding 矩阵”这个运算优化成了“直接取一行”,更省内存和算力。
5. 一个常见误区:token id 的大小不代表语义远近
很多直觉会误以为:
- “苹果的 token id 和 apple 的 token id 如果很接近,那语义应该也接近”
这是不成立的,因为:
- token id 的分配方式本质是“词表里的编号”,通常只保证“可逆映射”,不保证“编号连续性有语义”
- 语义关系来自 embedding(以及后续多层计算)把不同 token 拉到“更相近/更可用”的向量空间里
所以你应该记住一句话:
token id 是索引;embedding 才是语义坐标。
6. RAG 里的 Embedding 是什么:把一段文本变成一个向量
RAG(检索增强生成)常见做法是:
- 把文档切成若干段(chunk)
- 用“文本 embedding 模型”把每段 chunk 变成一个向量(例如 768/1024/1536 维)
- 用户提问也变成一个向量
- 在向量库里做相似度检索(常见是余弦相似度或内积),拿到最相近的几段
- 把这些段落拼进大模型上下文,让大模型回答
这个 embedding 的目标是:让语义相近的文本在向量空间里更接近,从而能“用检索找相似内容”。
7. LLM Embedding vs RAG Embedding:哪里一样,哪里不一样
它们都叫 embedding,都在做“用向量表示语义”,但使用方式与产物不同:
| 对比项 | 大模型内部(token embedding / hidden states) | RAG(文本 embedding) |
|---|---|---|
| 输入对象 | 单个 token(序列中的每个位置) | 一段文本(一个 chunk / query) |
| 输出形态 | 一串向量(长度=token 数) | 一个向量(固定维度) |
| 是否上下文相关 | 是(同一个词在不同上下文向量会变) | 通常弱一些(一个文本得到一个向量) |
| 训练目标 | 主要服务“预测下一个 token” | 主要服务“语义相似度检索” |
| 工程用途 | 让模型能推理与生成 | 让系统能召回相关资料 |
一句话区分:
- 大模型的 embedding 更像“生成任务的内部表示”
- RAG 的 embedding 更像“检索任务的索引表示”
更多推荐


所有评论(0)