侧边栏壁纸
博主头像
程序员进阶之路

技术之道,不可浅尝辄止;架构之路,须当支持以恒!

  • 累计撰写 18 篇文章
  • 累计创建 3 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
LLM

Embedding 入门指南:Token Embedding、文本向量、RAG 语义检索和模型选择

云端行笔
2026-06-21 / 0 评论 / 0 点赞 / 16 阅读 / 0 字

摘要:Embedding 是 RAG、向量数据库、语义检索和推荐系统里的基础能力。上一篇《RAG 向量数据库入门:从 Embedding 到检索增强生成》已经讲过 RAG 为什么需要 Embedding,本文作为系列专项篇,不再重复 RAG 背景,而是深入解释 Embedding 本身:Token Embedding 为什么本质是查表、Sentence / Document Embedding 如何由模型生成、语义相似度如何计算、常见开源框架和模型如何选择,以及在 RAG 项目中如何评估和治理 Embedding 效果。

写在前面:只讲 Embedding 本身

在上一篇文章《RAG 向量数据库入门与实战:从 Embedding 到检索增强生成》中,我们讲了 RAG 为什么需要 Embedding、向量数据库在 RAG 中做什么、以及“离线建库”和“在线问答”两条链路。

因此,本文不再重复解释 RAG 的整体价值。而是要追问一个更底层的问题:Embedding 到底是怎么把文本变成向量的?

如果用一句话回答,可以总结为:Embedding 是把离散符号映射到连续向量空间,让模型和检索系统可以计算语义距离。

比如一句话:

订单进入结算流程后,不支持原路退款。

经过 Embedding 模型后,会变成一组数字:

  [0.012, -0.083, 0.145, ...]

这组数字不是给人看的,而是给机器计算的。在 RAG 里,用户问题和文档 chunk 都会被转换成向量。向量距离越近,系统越倾向认为它们语义相关。

本文会从两个层次展开:

  • 模型内部的 Token Embedding:本质是通过 token ID 查一张可训练矩阵。

  • RAG 检索里的 Sentence / Document Embedding:通常是文本经过 Transformer 编码和 pooling 后得到的整体向量。

这两个概念经常被混着说,但理解它们的区别很重要。

一、先明确边界:Embedding 不是 RAG 的全部

Embedding 在 RAG 里通常负责“把文本变成可检索的向量”。但 RAG 是否答得准,不只取决于 Embedding。

上一篇 RAG 文章里已经讲过,一条完整链路还包括:

  • 文档解析。

  • 文本切块。

  • 向量数据库。

  • metadata 过滤。

  • Hybrid Search。

  • Rerank。

  • Prompt 组装。

  • 引用来源。

  • 评估集。

所以这篇文章不会再大段解释“为什么需要 RAG”或者“为什么关键词搜索不够”。而是要把 Embedding 讲深一点:它如何产生、如何选择模型、如何评估,以及为什么它只能解决“召回候选”的一部分问题。

二、Embedding 到底是什么:一组有语义的数字

Embedding 的输出是向量。向量可以理解成一个数字数组:

  [0.012, -0.083, 0.145, 0.031, ...]

这个数组可能有 384 维、768 维、1024 维、1536 维,甚至更多。维度多少取决于模型。初学者可以先把它想象成一个坐标。在二维地图里,一个点可以用:(x, y)表示位置。

在 Embedding 空间里,一段文本可以用几百到几千个数字表示位置。语义相近的文本,位置更近;语义差异大的文本,位置更远。比如:

“请假需要谁审批?”

“员工休假审批流程是什么?”

这两句话应该距离较近。而:“Redis AOF 持久化怎么配置?”和请假审批的距离应该更远。

这里有一个初学者很容易混淆的地方:大家说 Embedding 时,可能指两件不同层次的东西。

第一种:Token Embedding。

模型内部使用的词向量。它更接近“查表”。

第二种:Sentence / Document Embedding。

RAG、向量数据库、语义搜索中使用的文本向量。它通常指一句话、一段文本、一个 chunk 经过模型编码后得到的整体向量。

这两者相关,但不是一回事。

2.1 Token Embedding:本质是一张可训练参数表

在 Transformer 模型内部,可以把 Embedding 看成一张巨大的参数表。

假设:

词表大小 Vocabulary = 50000

Embedding 维度 = 4

那么模型里会有一张矩阵:

Embedding Matrix = 50000 x 4

每一行对应一个 token ID,每一列是这个 token 的一个向量维度。

假设我们有这样一个矩阵:

  ID      Vector
  0       [0.11,  0.23, -0.45,  0.67]
  1       [-0.12, 0.89,  0.34, -0.22]
  ...
  1024    [0.42, -0.15,  0.88,  0.36]
  3521    [0.71,  0.53, -0.22,  0.14]
  19876   [-0.31, 0.67,  0.51, -0.48]

如果输入一句话:

我喜欢AI

先经过 tokenizer:

["我", "喜欢", "AI"]

再转换成 token ID:

[1024, 3521, 19876]

然后就去 Embedding Matrix 里取对应行:

  1024  -> [0.42, -0.15,  0.88,  0.36]
  3521  -> [0.71,  0.53, -0.22,  0.14]
  19876 -> [-0.31, 0.67,  0.51, -0.48]

最终得到:

  [
    [0.42, -0.15,  0.88,  0.36],
    [0.71,  0.53, -0.22,  0.14],
    [-0.31, 0.67,  0.51, -0.48]
  ]

所以从 Token Embedding 这一层看,它确实不是通过某个复杂公式实时算出来的,而是:

公式可以写成:

Embedding(token_id) = Embedding_Matrix[token_id]

GPT-4,业内推测,词表大小约200万,维度8k~16k

为什么这张表会有语义?

这里很多人会误解。虽然计算过程只是查表,但这张表并不是人工写进去的。训练开始时:

AI -> [-0.08, 0.13, 0.04, -0.11]

这些数字几乎是随机的,没有任何语义。随后模型会不断执行:

训练数据足够多、训练目标足够有效时,经常出现在相似上下文里的 token,向量会逐渐靠近。例如:

北京、上海、广州、深圳

可能会在空间里形成一团。

香蕉、橘子、苹果

也可能形成另一团。

而“苹果”这个词如果出现在 iPhone、MacBook、公司财报这些上下文里,它在上下文模型中还会通过 Transformer 后续层被赋予不同语境下的表达。

这也是为什么现代语言模型不能只看第一层 token embedding。真正的上下文理解,是 token embedding 进入 Transformer 后,由多层 self-attention 和前馈网络共同完成的。

2.2 RAG 里的 Embedding:通常不是简单查表结果

RAG 和向量数据库里说的 Embedding,通常不是某个 token 的查表向量,而是一段文本的整体向量。

例如:

订单进入结算流程后,不支持原路退款。

这个 chunk 可能有十几个 token。模型会先做 token embedding lookup,再经过 Transformer 编码,最后通过 pooling、特殊 token 输出、归一化等方式,把整段文本变成一个固定维度的向量。

简化流程是:

要准确地区分:

Token Embedding 是模型内部给每个 Token 的初始表示;而 RAG 中使用的 Embedding,是整段文本经过 Transformer 编码后得到的语义表示。

  • 前者解决“如何把文字变成数字”。

  • 后者解决“如何让数字真正表达语义”。

而向量检索、Milvus、pgvector、Chroma、LangChain、Spring AI 等 RAG 体系所使用的,几乎都是后者。

2.3 为什么要从离散 ID 变成连续向量

Token ID 本身只是编号。比如:

我 = 1024

喜欢 = 3521

AI = 19876

这些数字没有语义上的大小关系。不能因为:

19876 > 1024

就说:

AI > 我

这没有任何意义。Token ID 是离散符号,只适合做索引,不适合直接做语义计算。Embedding 把离散 ID 映射到连续向量空间:

AI -> [-0.31, 0.67, 0.51, -0.48]

进入连续空间后,模型和检索系统就可以计算:

  • 距离。

  • 相似度。

  • 方向。

  • 聚类关系。

比如余弦相似度越接近 1,通常表示两个向量方向越接近,语义也可能越相似。

2.4 为什么 Transformer 必须先做 Embedding

Transformer 不能直接处理字符串。它内部做的是大量矩阵运算:

Q = XWq

K = XWk

V = XWv

这里的 X 必须是数字矩阵。原始文本:

"AI"

"退款"

"订单"

不能直接参与矩阵乘法。所以必须先做:

文字 -> token -> token id -> embedding lookup -> 数字矩阵 X -> Transformer

可以把整体流程记成:

2.5 相似度怎么计算

Embedding 生成之后,需要用相似度函数判断两个向量是否接近。常见方法有三种:

Cosine Similarity

余弦相似度看两个向量方向是否接近。文本语义检索中非常常见。

Dot Product

点积会计算两个向量的乘积和。有些模型训练时就针对点积相似度做了优化。

Euclidean Distance

欧氏距离就是空间里的直线距离,距离越小越接近。

不要随便混用相似度算法。最好看模型文档推荐什么距离函数。模型训练目标和检索时使用的距离函数不匹配,效果可能会变差。

三、Embedding 是怎么生成的

Embedding 不是普通哈希。

  • 哈希强调唯一性:两个文本只要有一点不同,哈希值就可能完全不同。

  • Embedding 强调语义相似:两句话字面不同,但意思相近,向量距离应该接近。

生成 Embedding 的典型流程是:文本 -> tokenizer 分词 -> embedding 模型编码 -> pooling / 向量输出 -> 可选归一化 -> 存储或检索

3.1 文本预处理

在 RAG 场景里,原始文档通常不能直接送进 Embedding 模型。你需要先做:

  • 去掉页眉、页脚、广告、导航。

  • 保留标题、章节、表格说明。

  • 把长文档切成 chunk。

  • 给 chunk 加 metadata。

  • 控制 chunk 长度,避免超过模型输入上限。

Embedding 模型再强,也救不了脏数据。如果 PDF 解析错乱、表格列名和值分离、标题丢失,生成出来的向量也会跟着混乱。

3.2 文本切块

Embedding 通常不是对整本书、整份合同、整套文档一次性生成向量,而是对 chunk 生成。比如:

文档 -> 章节 -> 段落 -> chunk -> embedding

  • 切块太小,语义不完整。

  • 切块太大,噪声太多,召回精度下降,也会增加 token 成本。

初学者可以先用一个稳妥策略:

  • 按标题和段落切。

  • 保留列表和表格的完整性。

  • chunk 控制在几百到一千多 token。

  • 给 chunk 带上标题路径。

  • 不要一开始就只按固定字符数硬切。

3.3 调用模型生成向量

以 Sentence Transformers 为例:

  from sentence_transformers import SentenceTransformer
  
  model = SentenceTransformer("BAAI/bge-m3")
  
  texts = [
      "订单进入结算流程后,不支持原路退款。",
      "人工退款需要运营提交申请,财务审批通过后处理。"
  ]
  
  embeddings = model.encode(texts, normalize_embeddings=True)
  
  print(embeddings.shape)

输出结果就是一个二维数组:

文本数量 x 向量维度

如果有 2 段文本,每段向量是 1024 维,那么输出形状就是:

(2, 1024)

3.4 存入向量数据库

向量一般不会单独存。通常会和原文、ID、metadata 一起存入向量数据库:

  {
    "id": "refund-policy#chunk-001",
    "text": "订单进入结算流程后,不支持原路退款。",
    "embedding": [0.012, -0.083, 0.145],
    "metadata": {
      "doc_id": "refund-policy",
      "title": "退款规则",
      "version": "2026",
      "permission": "support"
    }
  }

用户提问时,再把问题也转换成向量:

用户问题 -> query embedding -> 向量数据库相似度检索 -> 返回最相关 chunk

四、Embedding 在 RAG 中怎么用

RAG 完整链路在前文已经讲过,这里只看 Embedding 负责的那一小段。它做两件事。

  • 离线阶段把文档 chunk 转成 document embedding。

  • 在线阶段把用户问题转成 query embedding。

然后向量数据库计算 query embedding 和 document embedding 的相似度,找出候选 chunk。简化后是:

  document chunk -> document embedding
  user query     -> query embedding
  
  query embedding vs document embedding -> 相似度检索

这里要注意一个关键点:document embedding 和 query embedding 必须可比较。

通常来说,文档和问题要使用同一个 Embedding 模型,或者使用同一个模型体系里专门训练好的 query encoder / document encoder。

如果文档用模型 A 生成向量,问题用模型 B 生成向量,而且两个模型不是同一向量空间,检索结果就会变得不可控。

4.1 query 和 document 要不要加前缀

有些 Embedding 模型建议加前缀,例如:

query: 订单结算后还能退款吗?

passage: 订单进入结算流程后,不支持原路退款。

这样模型能区分“用户问题”和“候选资料”的角色。但不是所有模型都需要前缀。是否加前缀,要看模型说明。最怕的是同一个系统里一部分数据加了前缀,一部分没加;或者入库时没加,查询时乱加。这样会让召回效果不稳定。

4.2 Embedding 只负责召回,不负责最终判断

Embedding 在 RAG 里通常负责“宽召回”。它把可能相关的资料找出来,但不保证排第一的就是最能回答问题的资料。所以很多生产 RAG 会加一层 Rerank:

向量召回 top50 -> Rerank -> 选择 top5 -> 放进 Prompt

Embedding 负责把候选证据找出来,Rerank 负责把更适合回答问题的证据排到前面。

五、常见 Embedding 框架和模型推荐

Embedding 可以用商业 API,也可以用开源模型。初学阶段,建议先用成熟工具跑通流程,再逐步比较不同模型效果。

1. OpenAI Embeddings

OpenAI 提供专门的 Embeddings API,可以把输入文本转换成向量,常用于搜索、聚类、推荐、异常检测、分类等场景。

优点:

  • API 简单。

  • 稳定性好。

  • 不需要自己部署模型。

  • 适合快速构建 RAG 或语义搜索原型。

缺点:

  • 需要外部 API。

  • 有调用成本。

  • 数据出境和合规要评估。

适合:

  • 快速产品验证。

  • 不想维护模型服务。

  • 对稳定性和开发效率要求高的团队。

2. Sentence Transformers

Sentence Transformers 是非常常用的开源文本向量框架,适合生成句向量、做语义相似度、语义搜索和聚类。一个最小示例:

  from sentence_transformers import SentenceTransformer
  
  model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
  
  sentences = [
      "How do I reset my password?",
      "What should I do if I forgot my password?"
  ]
  
  embeddings = model.encode(sentences)

优点:

  • 上手简单。

  • 模型丰富。

  • 本地可运行。

  • 适合教学、Demo 和中小规模实验。

适合:

  • 初学者。

  • 本地 RAG 实验。

  • 英文语义搜索。

  • 快速比较不同模型。

3. FlagEmbedding / BGE 系列

FlagEmbedding 是 BAAI BGE 系列模型常用工具,覆盖 embedding、reranker 等检索增强模型。

在中文和中英混合 RAG 场景里,BGE 系列很常见,尤其是 BGE-M3 这类多语言、多功能检索模型,经常被用作开源 RAG 的候选方案。

优点:

  • 对中文和多语言场景友好。

  • 检索和 rerank 生态比较完整。

  • 适合 RAG 项目做本地化和私有化部署。

适合:

  • 中文知识库。

  • 中英混合文档。

  • 企业私有化部署。

  • 希望 embedding 和 reranker 一起评估的团队。

4. Hugging Face Transformers

Transformers 更底层,也更灵活。

如果你需要自定义 pooling、微调模型、导出 ONNX、做推理优化,或者想把 embedding 模型放进自己的模型服务里,Transformers 会比简单封装更可控。适合:

  • 有模型工程经验的团队。

  • 需要微调或自定义推理流程。

  • 需要统一管理多种开源模型。

5. Hugging Face Text Embeddings Inference

当 embedding 请求量变大后,单机脚本 model.encode() 不一定够用。

Hugging Face Text Embeddings Inference 是一个面向 embedding 和 reranking 模型的高性能服务化方案,适合把模型部署成可被多个应用调用的服务。适合:

  • 大批量文档入库。

  • 多应用共享 embedding 服务。

  • GPU 推理。

  • 需要监控、批处理和线上服务能力。

6. Ollama Embeddings

Ollama 常用于本地运行模型,也提供 embeddings API。

它适合在开发机或内网环境快速启动一个本地 embedding 服务。适合:

  • 本地开发。

  • 数据不方便发到外部服务。

  • 轻量 Demo。

  • 内网验证。

怎么推荐

  • 如果只是学习:

Sentence Transformers + all-MiniLM-L6-v2

简单、轻量、资料多。

  • 如果做中文 RAG:

BAAI/bge-m3

BGE 中文/多语言系列

更值得优先测试。

  • 如果追求省心和稳定:

OpenAI Embeddings API

适合快速上线,但要考虑成本和合规。

  • 如果要内网私有化:

FlagEmbedding / Transformers / TEI / Ollama

根据并发、GPU、运维能力选择。

真正做生产项目时,不要只看别人推荐。最可靠的方法是拿自己的数据做评估。

六、怎么选择 Embedding 模型

选 Embedding 模型时,不要只看排行榜。排行榜能提供参考,但真实业务数据往往更复杂。可以按下面几个维度判断。

1. 语言

中文文档、英文文档、中英混合文档,对模型要求不同。

如果你的知识库主要是中文,不要只用英文效果好的模型;如果文档里有大量英文 API、代码类名、错误码,也要单独测试。

2. 领域

客服、法律、金融、医疗、代码、运维、教育,每个领域的表达都不一样。

通用模型可能知道“退款”和“退钱”相近,但未必理解你公司内部的产品名、流程名、配置项和缩写。

3. 输入长度

有些 embedding 模型适合短句,有些支持更长上下文。

如果你的 chunk 很长,要关注模型最大输入长度。超长文本被截断后,向量可能丢失关键信息。

4. 向量维度

向量维度越高,不一定效果越好,但通常会带来更多存储和计算成本。

比如同样是一百万个 chunk:

  • 384 维向量更省存储。

  • 1024 维向量可能表达能力更强,但成本更高。

维度选择要结合效果和成本一起看。

5. 延迟和吞吐

Embedding 不只在线查询时用,也会在文档入库时大量调用。

如果每天要处理几十万、几百万个 chunk,就要考虑批处理、GPU、限流、重试和成本。

6. 是否需要 reranker

有些团队希望 embedding 一步到位,但实际检索常常需要两阶段:

  • Embedding 召回候选

  • Reranker 精排结果

尤其是知识库规模变大、问题更复杂、候选 chunk 相似度接近时,reranker 的收益会很明显。

七、Embedding 落地时最容易踩的坑

1. 不同模型的向量混在一起

这是很常见的坑。

不同 embedding 模型生成的向量不在同一个空间里。把模型 A 和模型 B 的向量混在同一个 collection 里,距离计算就会变得没有意义。正确做法:

  • 模型升级时新建 collection 或 index version。

  • metadata 里记录 embedding 模型和版本。

  • 回滚策略提前设计。

2. 文档和问题用的模型不一致

入库时用一个模型,查询时换另一个模型,会导致召回不稳定。除非你使用的是同一模型体系里明确支持的 query encoder / document encoder,否则不要混用。

3. chunk 质量太差

Embedding 不是清洗工具。

如果 chunk 里混了页码、版权声明、导航菜单、错位表格,模型只能把这些噪声也编码进去。好的向量检索,先从好的文档解析和切块开始。

4. 只用向量检索

向量检索适合语义相似,不擅长所有问题。这些场景关键词搜索更强:

  • 错误码。

  • 订单号。

  • 字段名。

  • API 名称。

  • 配置项。

  • 法条编号。

生产 RAG 通常要做 Hybrid Search。

5. topK 越大越好

topK 变大,召回率可能提高,但噪声也会增加。

如果把太多不相关 chunk 放进 Prompt,大模型反而更容易答偏。更稳的做法是:适当扩大召回 topK -> rerank -> 去重和压缩 -> 只把高质量证据放进 Prompt

6. 没有记录日志

Embedding 检索问题很难凭感觉排查。至少要记录:

  • query

  • embedding model

  • topK

  • metadata filter

  • 召回 chunk id

  • 相似度分数

  • rerank 分数

  • 最终进入 Prompt 的 chunk

  • 用户反馈

没有日志,就没有可复盘的优化。

结语:Embedding 是语义检索的起点,不是终点

Embedding 很重要,但不要把它神化。它解决的是“把语义变成可计算的向量”。

在 RAG 系统里,它通常负责把可能相关的资料召回出来。至于一个完整 RAG 系统还需要哪些组件,前面的 RAG 总篇已经展开过:文档解析、切块、向量数据库、metadata、Hybrid Search、Rerank、Prompt、引用和评估,都在那条链路里。

这篇文章只补上其中最容易被一句话带过、但又最影响召回质量的一层:Embedding。

对于初学者来说,学习顺序可以是:

  • 先读 RAG 总篇,理解完整链路

  • 再读 Embedding 专项篇,理解向量是怎么来的

  • 最后用 bad case 反推模型、切块和检索策略

然后再问:

  • 为什么这个问题没召回?

  • 为什么正确 chunk 排在后面?

  • 为什么同义问题效果不同?

  • 为什么换了模型反而变差?

能回答这些问题,才算真正理解 Embedding。

一句话总结:

Embedding 不是让机器读懂文本的终点,而是让机器开始计算语义的入口。

0

评论区