【RAG 系列】Embedding 模型怎么选、怎么用——向量嵌入从原理到选型实战
RAG 系列第 3 篇 | 阅读时间约 18 分钟上一篇[Chunking 实战——分块策略才是 RAG 效果的核心变量]先讲一个故事图书馆里有一百万本书。你问管理员“有没有讲’怎么让程序跑快’的书”如果管理员是个索引机器人它会逐字扫描每本书的标题找到含有程序和跑快这两个词的书——找不到因为没有书名叫这个。但如果管理员是个真人他会理解你的意图去找《高性能计算》《系统性能优化》《算法与数据结构》——这些书里没有跑快这两个字但它们就是你要的东西。Embedding向量嵌入做的就是把理解意图这件事变成计算机可以执行的数学操作。它把文字压缩成一串数字向量让语义相近的内容在数学空间里靠得更近。从此程序跑快和性能优化不再是毫无关联的字符串而是坐标系里距离只有 0.12 的两个点。一、Embedding 模型的原理演进理解现代 Embedding 模型需要先看它是怎么一步步进化来的。第一代词向量Word2Vec / GloVe2013-2015核心思路一个词一个向量向量由词的上下文共现关系决定。训练数据中国王经常和王后、权力、统治同时出现 苹果经常和水果、甜、吃同时出现 → 国王 的向量和 王后 的向量相近 → 国王 的向量和 苹果 的向量很远经典的类比推理就是这么来的向量(国王) - 向量(男) 向量(女) ≈ 向量(王后)致命缺陷一词一义。苹果这个词不管是水果还是手机品牌都只有一个向量。上下文无法影响词义。第二代上下文嵌入ELMo / BERT2018核心突破同一个词在不同句子里向量不同。我吃了一个苹果 → 苹果 的向量偏向水果 苹果发布了新款手机 → 苹果 的向量偏向科技公司BERT 用 Transformer 的双向注意力机制让每个词的向量都能感知整个句子的语境。新的问题BERT 生成的是词级别的向量而 RAG 需要的是句子/段落级别的向量——这整段话的意思应该用一个向量表示而不是每个词各一个。第三代句子嵌入Sentence-BERT / 双编码器2019 至今核心思路在 BERT 的基础上用孪生网络Siamese Network训练让模型学会把整个句子压缩成一个固定维度的向量。训练目标 相似句子对 → 向量余弦相似度Cosine Similarity接近 1 不相似句子对 → 向量余弦相似度接近 0 训练数据 NLI自然语言推理数据集、语义相似度标注数据集……这就是现代 Embedding 模型的基本架构。RAG 里用的所有主流模型都是这套范式的升级版。下面用一句话「向量检索比关键词更智能」逐步走一遍完整流水线Step 1Tokenizer分词转 Token IDTokenizer 不是按中文字或英文单词切割而是用BPE字节对编码算法把文本切成子词Subword单元再映射到词表里的整数 ID。输入文本 向量检索比关键词更智能 分词结果 [[CLS], 向量, 检索, 比, 关键, ##词, 更, 智能, [SEP]] ↑ ↑ ↑ ↑ 特殊起始符 独立词根 子词后缀 特殊结束符 Token ID [ 101, 20288, 26267, 3683, 20851, 35462, 3940, 22253, 102 ] 输出一个整数序列长度 Token 数这里是 9这里有两个细节值得注意##词是什么词表里没有关键词这个三字整体Tokenizer 就把它拆开关键→ 词表里有独立成一个 Token##词→##前缀表示我是上一个 Token 的后缀不能单独存在这是 WordPieceBERT 系模型使用的分词算法处理词表外生僻词的方式——拆成更小的已知单元而不是直接报错。[CLS]和[SEP]是什么BERT 系模型训练时规定的特殊符号Tokenizer 会自动加在句子的头尾。[CLS]Classification Token后面 Pooling 步骤会用到[SEP]Separator Token用来标记句子结束。Step 2Transformer Encoder上下文建模每个 Token ID 先查 Embedding 表得到一个初始向量768 或 1024 维。然后经过多层自注意力机制Self-Attention让每个 Token 的向量都能看到整句话里的其他 Token从而融入上下文信息。经过 12 层 Self-Attention 后每个 Token 的向量都发生了变化“检索的向量不再是通用的搜索语义而是知道自己在讲向量检索”比的向量感知到了整个比较句式比…更…的结构每个词都读过了整句话向量里融入了完整的上下文信息这一步的输出是9 个向量每个 Token 一个还不是我们想要的整句话的向量。Step 3Pooling把 N 个 Token 向量压成 1 个句子向量Step 2 结束后手里有 9 个向量每个 Token 一个。但 RAG 需要的是整句话的一个向量。Pooling 就是把 9 变成 1 的步骤。Step 2 输出 [CLS] 向量 检索 比 关键 ##词 更 智能 [SEP] ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │v0 │ │v1 │ │v2 │ │v3 │ │v4 │ │v5 │ │v6 │ │v7 │ │v8 │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ 1024维 1024维 1024维 1024维 1024维 1024维 1024维 1024维 1024维 ↓ Pooling ┌──────────┐ │ 句子向量 │ 1024 维代表整句话 └──────────┘方式 ACLS Pooling——直接取 v0[CLS] 向量 检索 比 关键 ##词 更 智能 [SEP] ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │v0 │ │v1 │ │v2 │ │v3 │ │v4 │ │v5 │ │v6 │ │v7 │ │v8 │ └─┬─┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ └──► 句子向量 v0 直接拿走其他 8 个丢掉BERT 训练时用[CLS]的向量来做句子级别的分类任务所以它被迫学会了汇总全句信息。但这个能力是针对分类任务练出来的用来算语义相似度效果一般。方式 BMean Pooling——去掉首尾对中间 7 个取平均[CLS] 向量 检索 比 关键 ##词 更 智能 [SEP] ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │v1 │ │v2 │ │v3 │ │v4 │ │v5 │ │v6 │ │v7 │ │ │ └───┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └───┘ 丢弃 │ │ │ │ │ │ │ 丢弃 └──────┴──────┴──────┴──────┴──────┴──────┘ 逐维取平均 │ ▼ 句子向量[第0维] (v1[0]v2[0]v3[0]v4[0]v5[0]v6[0]v7[0]) / 7 句子向量[第1维] (v1[1]v2[1]v3[1]v4[1]v5[1]v6[1]v7[1]) / 7 ...共 1024 维每一维都这样算用一个 3 维的简化例子感受一下真实是 1024 维道理完全一样第0维 第1维 第2维 向量 0.8 0.3 0.5 检索 0.9 0.4 0.2 比 0.1 0.9 0.3 关键 0.7 0.2 0.6 ##词 0.6 0.3 0.4 更 0.2 0.8 0.1 智能 0.9 0.1 0.7 ────────────────────────── 平均 0.6 0.43 0.4 ← 这就是整句话的向量3维[CLS] 和 [SEP] 为什么要去掉它们是人为加的特殊符号不代表任何真实词语平均进去会引入噪音。什么是 padding mask实际跑模型时同一个 batch 里不同句子长度不同短句会被填充到一样长句子A9个Token: [CLS] 向量 检索 比 关键 词 更 智能 [SEP] 句子B5个Token: [CLS] 向量 检索 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] ↑ 这些是填充符没有语义Mean Pooling 时必须跳过[PAD]否则平均结果会被拉偏。代码里的attention_mask就是一个 0/1 数组标记哪些位置是真实内容1、哪些是填充0乘进去就能自动排除填充。实际代码对比fromtransformersimportAutoTokenizer,AutoModelimporttorch tokenizerAutoTokenizer.from_pretrained(BAAI/bge-m3)modelAutoModel.from_pretrained(BAAI/bge-m3)text向量检索比关键词更智能inputstokenizer(text,return_tensorspt,paddingTrue,truncationTrue)withtorch.no_grad():outputsmodel(**inputs)# 方式 A取 [CLS] 向量cls_embeddingoutputs.last_hidden_state[:,0,:]# shape: (1, 1024)# 方式 BMean Pooling需要 attention_mask 排除 paddingtoken_embeddingsoutputs.last_hidden_state# shape: (1, seq_len, 1024)attention_maskinputs[attention_mask]mask_expandedattention_mask.unsqueeze(-1).float()mean_embedding(token_embeddings*mask_expanded).sum(1)/mask_expanded.sum(1)# shape: (1, 1024)BGE-M3 和大多数现代模型默认用Mean Pooling效果更稳定。Step 4L2 归一化让向量落在单位球面上归一化前的向量举个极简 3 维例子 v [3.0, 4.0, 0.0] 向量的模长度 √(3² 4² 0²) √25 5.0 归一化后 v_normalized v / ||v|| [3/5, 4/5, 0/5] [0.6, 0.8, 0.0] 新的模 √(0.6² 0.8² 0²) √1.0 1.0 ✅ 落在单位球上为什么要归一化归一化之后两个向量的余弦相似度Cosine Similarity 点积Dot Productcos_sim(a, b) (a · b) / (||a|| × ||b||) 归一化后 ||a|| ||b|| 1所以 cos_sim(a, b) a · b ← 只需要一次点积运算速度更快 向量数据库如 Faiss、Milvus的最近邻搜索可以直接用点积 比计算完整余弦相似度快 3-5 倍。最终输出的 1024 个数字就是这句话在语义空间里的坐标。另一句话Semantic search outperforms keyword matching它的坐标和这句话的点积会接近 1——两句意思相同在空间里紧挨着。二、2026 年主流 Embedding 模型横评模型选型是工程决策不是学术比赛。下面的对比以RAG 工程场景下的实际表现为核心不追求榜单排名。模型速览模型维度最大 Token开源/商业擅长BGE-M310248192开源MIT多语言、长文档、中文text-embedding-3-large30728191商业OpenAI英文综合最强text-embedding-3-small15368191商业OpenAI英文性价比高Jina Embeddings v310248192开源商业多语言、长文档、任务感知Gemini Embedding30722048商业Google多语言配合 Gemini 生态Voyage AI (voyage-3)102432000商业超长文档代码BGE-M3BAAI2024目前开源阵营综合表现最强的选择中文场景首选。三大核心能力M3 Multi-Functionality Multi-Linguality Multi-GranularityMulti-Functionality多功能 同一个模型同时支持 ① 稠密检索Dense Retrieval生成语义向量用于向量数据库 ② 稀疏检索Sparse Retrieval生成 BM25 风格的关键词权重 ③ 多向量检索ColBERT-style每个 Token 一个向量精度最高但存储大 Multi-Linguality多语言 100 种语言中英文效果均优异 Multi-Granularity多粒度 支持从短句几十字到长文档8192 Token的嵌入代码示例fromFlagEmbeddingimportBGEM3FlagModel modelBGEM3FlagModel(BAAI/bge-m3,use_fp16True)sentences[向量检索是 RAG 系统的核心组件,如何提升知识库的检索效果,]# 稠密向量最常用embeddingsmodel.encode(sentences,batch_size12,max_length8192)dense_vecsembeddings[dense_vecs]# shape: (2, 1024)# 同时获取稀疏向量用于混合检索outputmodel.encode(sentences,return_denseTrue,return_sparseTrue)sparse_weightsoutput[lexical_weights]# dict: {token_id: weight}适合你如果需要本地部署、中文场景、混合检索、预算有限。text-embedding-3-large / smallOpenAI2024商业模型里性价比和效果的黄金标准英文场景下几乎是默认选择。独特优势维度截断Matryoshka Representation LearningMRLfromopenaiimportOpenAI clientOpenAI()responseclient.embeddings.create(modeltext-embedding-3-large,input[向量嵌入的工作原理是什么],dimensions256# 可以把 3072 维截断到 256 维精度略降但存储/速度大幅改善)embeddingresponse.data[0].embedding# 256 维向量维度 vs 效果权衡text-embedding-3-largedimensionsMTEB 分数存储占比3072全量64.6100%153663.750%25661.08.3%对大多数 RAG 场景1536 维已经足够存储成本直接减半。适合你如果英文为主、不介意 API 依赖、希望快速接入且效果稳定。Jina Embeddings v3Jina AI2024最大亮点任务感知嵌入Task-Specific Embeddings。同一个模型传入不同的task参数向量空间的优化目标会切换importrequests urlhttps://api.jina.ai/v1/embeddingsheaders{Authorization:Bearer YOUR_KEY}# 检索任务——query 侧payload{model:jina-embeddings-v3,task:retrieval.query,# 专为查询向量优化input:[RAG 系统怎么提升效果]}# 检索任务——document 侧建库时用payload_doc{model:jina-embeddings-v3,task:retrieval.passage,# 专为文档向量优化input:[RAG检索增强生成通过……]}支持的任务类型retrieval.query/retrieval.passage/separation聚类/classification/text-matching适合你如果多语言场景、需要查询侧和文档侧向量分开优化、长文档8192 Token。Gemini EmbeddingGoogle2025集成在 Google AI 生态里多语言表现突出同样支持任务感知task_type参数。注意最大 Token 只有 2048长文档场景受限。除非已经在 Google Cloud / Vertex AI 生态里否则不作为首选。三、选型决策树你的主要语言是什么 │ ├── 中文为主 / 中英混合 │ │ │ ├── 需要本地部署数据不出内网 │ │ └──► BGE-M3开源效果最佳 │ │ │ └── 可以用 API │ └──► BGE-M3API 版或 Jina v3 │ └── 英文为主 │ ├── 追求最高精度不在意成本 │ └──► text-embedding-3-large3072 维 │ ├── 平衡精度和成本 │ └──► text-embedding-3-small 或 text-embedding-3-large1536 维截断 │ └── 超长文档 8192 Token └──► Voyage AI voyage-3支持 32000 Token 特殊需求 图文混排知识库 → 见第 4 篇《多模态嵌入》 代码库检索 → Voyage AI voyage-code-3 / text-embedding-3 系列 已在 Google 生态 → Gemini Embedding四、代码实战批量 Embedding 成本估算批量 Embedding以 BGE-M3 本地为例fromFlagEmbeddingimportBGEM3FlagModelfromtypingimportListimportnumpyasnp modelBGEM3FlagModel(BAAI/bge-m3,use_fp16True)defbatch_embed(texts:List[str],batch_size:int32)-np.ndarray: 批量嵌入自动分批处理避免 OOM 返回 shape: (len(texts), 1024) all_embeddings[]foriinrange(0,len(texts),batch_size):batchtexts[i:ibatch_size]outputmodel.encode(batch,batch_sizebatch_size,max_length8192,show_progress_barFalse)all_embeddings.append(output[dense_vecs])returnnp.vstack(all_embeddings)# 用法chunks[chunk 1 的内容...,chunk 2 的内容...,...]# 你的分块结果embeddingsbatch_embed(chunks,batch_size32)print(f生成了{len(embeddings)}个向量每个维度{embeddings.shape[1]})批量 Embedding以 OpenAI API 为例OpenAI API 可以一次请求传入多个文本但有单次 Token 上限200K超出需要分批发送并加小延迟避免限流。核心逻辑和上面的 BGE-M3 版本一致区别只是把model.encode()换成client.embeddings.create(model..., inputbatch)返回结果从resp.data里取.embedding。成本估算建库前先估算 API 成本避免意外账单importtiktokendefestimate_embedding_cost(texts:List[str],model:strtext-embedding-3-small)-dict: 粗略估算嵌入成本以 OpenAI 价格为基准 价格参考https://openai.com/pricing需实时确认 PRICE_PER_1M_TOKENS{text-embedding-3-small:0.02,# $0.020 / 1M tokenstext-embedding-3-large:0.13,# $0.130 / 1M tokens}encodertiktoken.get_encoding(cl100k_base)total_tokenssum(len(encoder.encode(t))fortintexts)price_per_millionPRICE_PER_1M_TOKENS.get(model,0.02)estimated_costtotal_tokens/1_000_000*price_per_millionreturn{chunk_count:len(texts),total_tokens:total_tokens,avg_tokens:total_tokens//len(texts),estimated_cost:f${estimated_cost:.4f},}# 示例chunks[...]*10_000# 1 万个分块resultestimate_embedding_cost(chunks,modeltext-embedding-3-small)print(result)# {chunk_count: 10000, total_tokens: 1500000,# avg_tokens: 150, estimated_cost: $0.0300}经验数字一个 10 万字的企业知识库约 300 个 chunk每 chunk 平均 150 Token用text-embedding-3-small建库成本不到$0.001几乎可以忽略。查询侧每次检索也只需嵌入用户问题约 20-50 Token成本极低。五、工程细节容易踩的坑坑 1查询向量和文档向量用了不同模型# ❌ 错误建库时用 BGE-M3查询时换成了 OpenAIdoc_embeddingbge_model.encode(chunk_text)# 1024 维BGE 空间query_embeddingopenai_client.embed(user_query)# 1536 维OpenAI 空间# 两个向量根本不在同一个空间计算相似度毫无意义# ✅ 正确建库和查询必须用同一个模型query_embeddingbge_model.encode(user_query)坑 2忘了非对称检索Asymmetric Retrieval用户的问题Query和文档Passage在文本结构上天然不对称问题短、口语化、缺乏背景文档长、信息密集、有完整上下文部分模型对这种不对称做了专门优化查询侧和文档侧要传不同参数# Jina v3 的正确用法query_embmodel.encode(taskretrieval.query,inputuser_question)doc_embmodel.encode(taskretrieval.passage,inputchunk_text)# BGE 系列也有专门的 query instructionquery_with_instructionf为这个句子生成表示以用于检索相关文章{user_question}query_embbge_model.encode(query_with_instruction)坑 3向量没有归一化就计算余弦相似度BGE-M3 和 OpenAI 的输出已经默认归一化可以直接点积。如果用自己训练的模型记得手动归一化v / np.linalg.norm(v)。未归一化的向量计算出来的点积不等于余弦相似度结果会偏。总结BGE-M3text-embedding-3-largeJina v3Gemini Embedding开源✅❌部分❌中文效果⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐英文效果⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐最大 Token8192819181922048本地部署✅❌❌❌混合检索支持✅原生❌❌❌任务感知❌❌✅✅一句话选型原则中文 / 本地部署 → BGE-M3英文 / 快速接入 → text-embedding-3-small 起步效果不够再升 large多语言 / 任务感知需求 → Jina v3。参考资料评测基准MTEB: Massive Text Embedding BenchmarkarXiv 原文| Hugging Face 实时榜单全球公认的文本嵌入评测标准涵盖检索、重排、聚类等 8 类任务。RAG 选型时重点看 Retrieval 子项得分不要只看总分。榜单支持按语言中文/英文和任务类型筛选。MMTEB: Massive Multilingual Text Embedding BenchmarkMTEB 的多语言升级版覆盖中文及长文档检索、代码检索等复杂场景更贴近工业实战。选型方法论Mastering RAG: How to Select an Embedding Model — Galileo AI把选型拆成四个维度向量维度带来的存储与延迟、上下文窗口限制、私有化部署的显存开销、数据隐私安全。适合在拍板前做系统性评估。Compare Embeddings for Retriever — Ragas 官方教程用你自己的业务文档自动合成测试集端到端跑出不同 Embedding 模型的 Context Relevance 得分——用数据而不是凭感觉做选型决策。下一篇[多模态嵌入——图文混排知识库怎么建]