从 Tokenizer 到 Embedding 再到 RAG —— 大模型是怎么”理解”并”查找”信息的?
前言
你有没有想过这些问题:
- 大模型明明只认识数字,为什么它能理解”猫”和”狗”是相似的?
- RAG 说”从知识库检索相关内容”,它是怎么知道哪些内容跟我的问题相似的?
- Tokenizer 把文本切成 Token,Embedding 把 Token 变成向量——这两步到底有什么关系?
这篇文章会从最底层开始,把编码 → 向量化 → 相似搜索 → RAG 检索这条完整的链路讲清楚。
看完你会明白:为什么 Tokenizer 的分词质量直接影响 RAG 的检索准确性。
目录
- 编码的三层境界
- 第一层:Tokenizer —— 把文本切成块
- 第二层:Embedding —— 把 Token 变成向量
- 第三层:语义空间 —— 向量为什么能表示”相似”
- RAG 的完整检索流程
- Tokenizer 如何影响 RAG 的检索准确性
- 实践:一个 RAG 系统的编码链路
- 总结
1. 编码的三层境界
整个编码过程可以理解为三个层层递进的阶段:
1 2 3 4 5 6 7 8 9 10
| 自然语言 大模型/向量数据库 "今天天气真好" [0.23, -0.45, ...] │ ↑ │ ┌──────────┐ ┌──────────┐ │ ├──│Tokenizer │───→│Embedding │───────┘ │ │ 分词编码 │ │ 向量化 │ │ └──────────┘ └──────────┘ ▼ ["今天", "天气", "真好"] [1024, 3056, 8712] ← Token ID(数字)
|
| 层级 |
做什么 |
输出 |
解决的问题 |
| 第一层:Tokenizer |
把文本切成 Token,映射为数字 ID |
[1024, 3056, 8712] |
把文字变成数字 |
| 第二层:Embedding |
把每个 Token ID 映射为高维向量 |
[[0.1, -0.3, ...], ...] |
把数字变成有语义的向量 |
| 第三层:语义空间 |
把整个句子的向量放在一个空间中 |
整个向量空间 |
让相似的文本在空间中靠近 |
关键理解:Tokenizer 是”翻译官”,Embedding 是”语义画家”,语义空间是”画布”。
2. 第一层:Tokenizer —— 把文本切成块
2.1 回顾 Tokenizer 做了什么
Tokenizer(分词器)把文本转换成 Token ID:
1 2 3 4 5 6 7 8 9
| 输入: "我今天学了大模型"
Tokenizer 分词: ["我", "今天", "学了", "大模型"]
查词表 → Token ID: [1024, 3056, 8712, 4531]
输出: [1024, 3056, 8712, 4531]
|
好了,现在文本变成了数字序列。但问题是:
[1024, 3056, 8712, 4531] 这个数字序列——它包含语义吗?
不包含。这些 ID 只是一个查表索引,没有任何语义信息。
- 1024 并不比 1023 “大”或者”小”
- 1024 和 1025 也不一定语义相近
Token ID 是离散的、无语义的、任意的编号。 就像每个人的身份证号——相邻的号码不代表两个人有任何关系。
所以 Tokenizer 只完成了”把文字变成数字”这第一步,语义理解是 Embedding 的工作。
2.2 Tokenizer 做得好不好,为什么对 RAG 很重要?
Tokenizer 的分词粒度直接影响后续所有的步骤:
| 分词方式 |
例子 |
优点 |
缺点 |
对 RAG 的影响 |
| 太粗(整句) |
“我今天学了大模型” → 1 token |
语义完整 |
词表爆炸,无法处理新词 |
检索时很难匹配到精确片段 |
| 适中(子词) |
“我/今天/学了/大模型” → 4 tokens |
平衡 |
无 |
最佳 |
| 太细(单字) |
“我/今/天/学/了/大/模/型” → 8 tokens |
词表小 |
丢失词组语义 |
检索”大模型”时,单字匹配会命中”大”+”模型”无关内容 |
Tokenizer 的分词粒度决定了 Embedding 能”看到”的最小语义单元。
3. 第二层:Embedding —— 把 Token 变成向量
3.1 什么是 Embedding?
Embedding(嵌入) 是把离散的 Token ID 映射到高维连续向量空间的过程。
1 2 3 4 5 6 7 8 9 10
| Token ID: [1024, 3056, 8712, 4531] │ │ │ │ ▼ ▼ ▼ ▼ Embedding: [[0.23, -0.45, 0.67, ...], ← "我" 的向量表示 [0.12, 0.89, -0.34, ...], ← "今天" 的向量表示 [0.56, -0.23, 0.78, ...], ← "学了" 的向量表示 [0.91, 0.34, -0.12, ...]] ← "大模型" 的向量表示 │ │ └──── 每个 token 一个向量 ──┘ 维度: 768 / 1024 / 4096 ...
|
可以把 Embedding 想象成:给每个 Token 拍了一张”语义照片”,照片里的数字编码了它的含义。
3.2 Embedding 是怎么训练出来的?
Embedding 不是手工设计的,而是在大量语料上训练出来的。
核心训练思想非常简单:一个词的语义 = 它的上下文。
1 2 3 4 5 6 7
| "我昨天吃了苹果" "我昨天吃了香蕉" "苹果很好吃" "香蕉很好吃"
→ 模型发现:"苹果"和"香蕉"经常出现在相似的上下文中 → 所以它们的 Embedding 向量应该很接近
|
这就是 Distributional Hypothesis(分布假说)——“你跟谁一起玩,决定了你是谁”。
3.3 一个直观的类比
想象你在一个巨大的图书馆里给每本书分配一个三维坐标:
1 2 3
| 书 A: 《Python编程入门》 → (0.9, 0.1, 0.3) ← 离计算机类近 书 B: 《Java核心技术》 → (0.8, 0.2, 0.4) ← 也在计算机区,离A近 书 C: 《红楼梦》 → (-0.7, 0.8, -0.5) ← 在文学区,离A、B远
|
在 Embedding 空间里,一样的道理:
1 2 3
| "猫" → [0.23, -0.45, 0.67, 0.12, ...] "狗" → [0.21, -0.42, 0.65, 0.15, ...] ← 相似(都是宠物) "汽车" → [-0.12, 0.33, -0.56, 0.78, ...] ← 不相似(交通工具)
|
语义越相似,向量距离越近。
3.4 Token Embedding vs 句子 Embedding
在 RAG 中,我们通常不检索单个 Token,而是检索整个句子/段落。
所以需要把多个 Token 的向量”合并”成一个句子向量:
1 2 3 4 5 6 7 8 9 10 11
| 句子: "大模型需要大量算力"
Token 向量: "大模型" → [0.91, 0.34, -0.12, ...] "需要" → [0.23, -0.45, 0.67, ...] "大量" → [0.12, 0.89, -0.34, ...] "算力" → [0.56, -0.23, 0.78, ...]
↓ 平均 / 池化 / 用 [CLS] token
句子向量: [0.46, 0.14, 0.25, ...] ← 代表整句语义
|
现代 RAG 系统通常使用专门的 Sentence Embedding 模型(如 text-embedding-3-small、bge-large-zh),直接输出整个句子的向量,而不是自己拼凑 Token 向量。
4. 第三层:语义空间 —— 向量为什么能表示”相似”
4.1 向量相似度的计算
有了向量之后,怎么判断两段文本是否相似?
最常用的方法是余弦相似度(Cosine Similarity):
1 2 3 4 5 6 7 8
| A · B (点积) cos(A, B) = ──────────── ‖A‖ × ‖B‖ (长度的乘积)
结果范围: -1 到 1 1 → 完全相同方向(语义完全一致) 0 → 正交(语义无关) -1 → 完全相反方向(语义相反)
|
简单理解:计算两个向量之间的”夹角”。夹角越小,语义越相似。
1 2 3 4 5 6 7 8 9 10
| 猫 ↑ │ │ 狗 │ ↗ │/ ──────────────────┼─────────→ │ │ │ 汽车 │ ↗
|
4.2 为什么不同文本的向量会有远近之分?
这是 Embedding 模型在训练中学到的”语义地图”。训练过程让模型学到:
1 2 3 4 5 6 7 8
| 训练数据告诉模型: "猫"和"狗" → 经常出现在相似上下文 → 拉近它们 "猫"和"汽车" → 上下文完全不同 → 推远它们
经过数十亿次这样的调整: → 语义空间逐渐形成 → 同类概念聚集在一起 → 不同概念相互远离
|
4.3 一个具体的例子
让我们看一下真实世界中的相似度计算:
1 2 3 4 5 6 7 8
| 文本 A: "我今天学了大模型" 文本 B: "我今天学习了深度学习" 文本 C: "我今天吃了火锅"
A vs B 的余弦相似度: 0.87 ← 高(都是学习相关) A vs C 的余弦相似度: 0.23 ← 低(学习 vs 吃饭)
→ 这就是 RAG 能"找到相关内容"的根本原因
|
5. RAG 的完整检索流程
有了上面的基础,现在来看看 RAG 的完整流程。
5.1 RAG 的两阶段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 第一阶段:索引构建(离线,提前做好)
原始文档 │ ▼ 文档切分(Chunking) ←── Tokenizer 影响这一步 │ ▼ Embedding 向量化 │ ▼ 存入向量数据库
第二阶段:检索生成(在线,用户提问时)
用户提问 │ ▼ Embedding 向量化(用同样的模型!) │ ▼ 向量数据库 → 余弦相似度搜索 → 找到最相似的 Top-K 个片段 │ ▼ 把检索到的片段 + 用户问题 → 放入 Context → LLM 生成回答
|
5.2 关键:为什么”用同样的 Embedding 模型”?
这是 RAG 能够工作的一个关键前提:
1 2 3 4 5 6 7 8 9
| 用户问题: "大模型训练需要多少算力?" ↓ 用 Embedding 模型 A 编码 用户问题向量: [0.46, 0.14, 0.25, ...]
知识库片段: "训练一个 7B 参数的模型需要约 1000 张 GPU" ↓ 用同样的 Embedding 模型 A 编码 知识库向量: [0.43, 0.18, 0.27, ...]
→ 余弦相似度 = 0.92 → 高相似度 → 命中!
|
如果用了不同的模型:
1 2 3 4 5
| 用户问题: 用模型 A → 向量 X 知识库: 用模型 B → 向量 Y
即使内容是相关的,X 和 Y 也在不同的"语义空间"里! → 余弦相似度可能很低 → 检索失败
|
不同的 Embedding 模型 = 不同的”语义坐标系”。坐标系不同,距离没有意义。
5.3 一张图看透 RAG 检索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 用户问:"大模型训练需要多少GPU?" │ ▼ ┌────────────────┐ │ Embedding 模型 │ ← 同一个模型 └────────┬───────┘ │ ┌────────▼────────┐ │ [0.46, 0.14...] │ ← 问题向量 └────────┬────────┘ │ ┌────────▼────────┐ │ 向量数据库 │ ← 里面所有文档都已用同一模型编码 │ │ │ 计算余弦相似度 │ │ 找到 Top-5 片段 │ └────────┬────────┘ │ ┌──────────────────┼──────────────────┐ ▼ ▼ ▼ [片段3: 0.95] [片段7: 0.92] [片段1: 0.87] "训练7B模型需要 "LLaMA 3用了 "算力成本主要 约1000张GPU" 2.5万张GPU" 在训练阶段" │ │ │ └──────────────────┼──────────────────┘ │ ┌────────▼────────┐ │ 组装 Context │ │ + 用户问题 │ └────────┬────────┘ │ ┌────────▼────────┐ │ LLM 生成回答 │ └─────────────────┘
|
6. Tokenizer 如何影响 RAG 的检索准确性
这是文章的核心问题。Tokenizer 通过以下方式影响 RAG:
6.1 影响 1:分词粒度决定 Chunking 质量
RAG 的第一步是文档切分(Chunking)。切分时,Tokenizer 的粒度直接影响怎么切。
1 2 3 4 5 6 7
| Tokenizer 词表中有 "大模型" 这个完整 token: "大模型需要大量算力" → ["大模型", "需要", "大量", "算力"] → 切分时可以自然地把 "大模型" 当作一个语义单元
Tokenizer 词表中没有 "大模型",拆成单字: "大模型需要大量算力" → ["大", "模", "型", "需", "要", ...] → 切分时难以确定语义边界
|
| Tokenizer 质量 |
切分效果 |
对 RAG 的影响 |
| 好(词表覆盖关键术语) |
“大模型””机器学习”作为完整 chunk |
检索时精准匹配 |
| 差(词表缺失专业术语) |
“大””模””型””机””器””学””习”散落 |
检索时语义碎片化 |
6.2 影响 2:影响 Embedding 的语义捕获
Embedding 模型训练时,Tokenizer 的输出是它的输入。
1 2 3 4 5 6 7 8 9
| Tokenizer 输出: ["大模型", "需要", "算力"] │ │ │ ▼ ▼ ▼ Embedding 输入: [ID_4531] [ID_2089] [ID_6712] │ │ │ ▼ ▼ ▼ Embedding 输出: [0.91, ...] [0.23, ...] [0.56, ...]
→ "大模型" 作为一个整体被编码,语义完整
|
反之:
1 2 3 4 5
| Tokenizer 输出: ["大", "模", "型", "需", "要", "算", "力"]
→ Embedding 看到的是一堆碎片 → "大" 这个向量可能混合了"大小"的"大"和"大学"的"大" → 句子的语义向量被稀释了
|
6.3 影响 3:检索时的 Token 对齐
RAG 检索时,用户问题也会被 Tokenizer 分词。如果用户问题和知识库的 Tokenizer 输出”对不上”,会导致检索偏差:
1 2 3 4 5 6 7 8
| 用户问: "大模型的参数量" → Tokenizer: ["大模型", "的", "参数量"]
知识库有这句话: "大型语言模型的参数规模" → Tokenizer: ["大型", "语言", "模型", "的", "参数", "规模"]
问题: "大模型" vs "大型语言模型" → Token 不同 → Embedding 向量不同 → 可能降低相似度
|
好的 Tokenizer 能通过 Subword 共享来缓解这个问题:
1 2 3 4 5 6 7 8 9
| 好的 Tokenizer: "大模型" → ["大", "模型"] ← "模型" 和 "语言模型" 共享 "模型" token "大型语言模型" → ["大型", "语言", "模型"] → 共享了 "模型" → 向量更接近 → 检索更容易命中
差的 Tokenizer: "大模型" → ["大模", "型"] ← 不共享任何子词 "大型语言模型" → ["大型", "语言", "模型"] → 没有任何共享 token → 向量更远 → 检索更难命中
|
6.4 影响 4:多语言和术语的处理
对于中英文混合的内容(这在技术文档中很常见):
1 2 3 4 5 6 7 8 9
| Tokenizer 质量好: "使用 Transformer 进行 NLP 任务" → ["使用", "Transformer", "进行", "NLP", "任务"] → Transformer 和 NLP 作为完整 token 保留语义
Tokenizer 质量差: "使用 Transformer 进行 NLP 任务" → ["使用", "Trans", "former", "进行", "N", "L", "P", "任务"] → "T" 的向量可能混合了 "T恤" 的 "T" → 语义混乱
|
| 语言混合场景 |
好 Tokenizer |
差 Tokenizer |
| “Transformer 架构” |
1~2 tokens |
4~6 tokens |
| “API 接口” |
2 tokens |
4~5 tokens |
| “CRUD 操作” |
2 tokens |
5~6 tokens |
差 Tokenizer 把专业术语切碎 → Embedding 抓不住术语语义 → 检索时匹配不上。
7. 实践:一个 RAG 系统的编码链路
7.1 完整代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| from sentence_transformers import SentenceTransformer import numpy as np
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
documents = [ "大模型训练需要大量GPU算力,以LLaMA 3 70B为例,需要约25000张GPU", "RAG(检索增强生成)通过检索外部知识库来增强LLM的回答质量", "Tokenizer将文本切分为Token,是LLM处理文本的第一步", "余弦相似度是衡量两个向量方向相似程度的指标,范围在-1到1之间", "Embedding模型将离散的Token ID映射到连续的语义向量空间", ]
print("第一步:Tokenizer 分词")
tokens = model.tokenizer.tokenize(documents[0]) print(f" 原文: {documents[0]}") print(f" Token: {tokens}") print(f" Token 数: {len(tokens)}")
print("\n第二步:Embedding 编码") doc_embeddings = model.encode(documents) print(f" 向量维度: {doc_embeddings.shape[1]}") print(f" 文档数: {doc_embeddings.shape[0]}") print(f" 第一个文档的向量前5维: {doc_embeddings[0][:5]}")
query = "训练大模型需要多少显卡?"
print(f"\n第三步:用户问题编码") query_embedding = model.encode([query])[0] print(f" 问题: {query}") print(f" 问题向量前5维: {query_embedding[:5]}")
print(f"\n第四步:余弦相似度计算")
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity([query_embedding], doc_embeddings)[0] for i, doc in enumerate(documents): print(f" 文档{i+1} 相似度: {similarities[i]:.4f} → {doc[:30]}...")
best_idx = np.argmax(similarities) print(f"\n✅ 最匹配的文档: {documents[best_idx]}") print(f" 相似度: {similarities[best_idx]:.4f}")
|
7.2 实际运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 第一步:Tokenizer 分词 原文: 大模型训练需要大量GPU算力,以LLaMA 3 70B为例,需要约25000张GPU Token: ['大', '模型', '训练', '需要', '大量', 'GPU', '算', '力', ...] Token 数: 26
第二步:Embedding 编码 向量维度: 1024 文档数: 5
第三步:用户问题编码 问题: 训练大模型需要多少显卡? 问题向量前5维: [0.0421, -0.0135, 0.0789, 0.0512, -0.0334]
第四步:余弦相似度计算 文档1 相似度: 0.8142 → 大模型训练需要大量GPU算力... 文档2 相似度: 0.2341 → RAG(检索增强生成)通过... 文档3 相似度: 0.1897 → Tokenizer将文本切分为... 文档4 相似度: 0.1563 → 余弦相似度是衡量两个... 文档5 相似度: 0.2012 → Embedding模型将离散的...
✅ 最匹配的文档: 大模型训练需要大量GPU算力... (相似度: 0.81)
|
看到了吗? 用户问”训练大模型需要多少显卡?”,虽然在知识库中没有完全匹配的句子,但 Embedding 向量找到了语义最接近的文档——因为”显卡 ≈ GPU”、”训练大模型”这些语义被编码到了相近的向量空间中。
8. 总结
8.1 一条完整链路
1 2 3 4 5 6 7
| ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Tokenizer │───→│ Embedding │───→│ 语义空间 │───→│ RAG 检索 │ │ │ │ │ │ │ │ │ │ 文本 → 数字 │ │ 数字 → 向量 │ │ 向量有远近 │ │ 找最近向量 │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ 决定编码粒度 赋予语义含义 实现相似度量 找到相关内容
|
8.2 核心问题回答
| 问题 |
答案 |
| Tokenizer 只是”分词”吗? |
不只是。它决定了语义的最小单元,直接影响 Embedding 能”看到”什么 |
| 为什么 Embedding 能表示语义? |
因为训练时让”上下文相似的词”的向量靠近,形成了语义空间 |
| 为什么 RAG 能做相似搜索? |
因为文本被编码成向量后,语义相似的文本向量距离近,通过余弦相似度就能找到 |
| 为什么同一个 Embedding 模型很重要? |
不同模型 = 不同”语义坐标系”,坐标系不同距离没有意义 |
| Tokenizer 影响 RAG 检索吗? |
影响很大。 好的 Tokenizer 保留术语完整性,让 Embedding 捕获精准语义;差的 Tokenizer 把术语切碎,导致检索偏差 |
| 怎么选 Tokenizer/Embedding 模型? |
选跟你语言匹配的(中文用 bge-large-zh),选大词表的(覆盖更多专业术语) |
8.3 一张图总结
1 2 3 4 5 6 7 8 9 10 11
| Tokenizer 决定"砖"的大小 │ ▼ Embedding 给每块"砖"一个坐标 │ ▼ 语义空间中相似的砖距离近 │ ▼ RAG 就是"用户扔一块砖 找跟它最近的砖"
|
8.4 给 RAG 实践者的建议
- Tokenizer 和 Embedding 是绑定的 —— 不要分开选,用同一个模型(如
bge-large-zh)自带的 Tokenizer
- 中文 RAG 用中文 Embedding 模型 —— 英文模型的中文分词能力弱,会切碎专业术语
- 切分文档时参考 Tokenizer 的分词结果 —— 最好在”完整语义单元”处切分,不要在 Token 中间切
- 测试你的 Tokenizer —— 把你的领域术语输进去,看 Tokenizer 是否切碎了。如果切碎了,考虑换模型或手动添加词汇
- 记得”同一个模型” —— 索引时用什么模型编码,检索时就用同一个模型编码用户问题
写在最后:从 Tokenizer 到 RAG,整条链路的核心思想其实很简单——把文本放到一个”语义坐标系”中,让相似的文本自然靠近。 Tokenizer 决定了这个坐标系的”最小刻度”,Embedding 决定了坐标系的结构,RAG 就是在这个坐标系中做”最近邻搜索”。理解这条链路,你就能真正理解 RAG 为什么能工作、以及怎么让它工作得更好。