本地 Embedding 模型选型——国产 Embedding 够用吗
本地 Embedding 模型选型——国产 Embedding 够用吗
适读人群:在做 RAG 系统、需要本地部署 Embedding 的工程师 | 阅读时长:约 15 分钟 | 核心价值:BGE/M3E/OpenAI Embedding 的实测对比,给出明确的选型建议
做知识库项目的时候,Embedding 模型选型是一个看起来不起眼、但实际上直接影响 RAG 系统质量的决策。
我之前犯过一个错误:项目早期为了快速验证,用了 OpenAI 的 text-embedding-3-small,效果不错,上线了。后来客户说数据不能出境,要求本地部署,我才开始认真研究本地 Embedding。
折腾了一圈,做了一套系统测试。这篇文章把测试结果和最终的判断写出来,直接给结论,不绕弯。
先讲为什么要关心 Embedding 选型
RAG 系统里,Embedding 模型决定了两件事:
- 召回率:用户的问题能不能找到知识库里正确的内容
- 相关性排序:找到的内容是不是真正相关的,还是只是包含了相同的词
用一个差的 Embedding 模型,你会看到这样的现象:
- 用户问"如何退款",召回了"关于购物满减活动的说明"(都有"购物"相关词)
- 用户问"发票抬头怎么改",没有召回知识库里确实有的"发票信息修改教程"(因为模型没理解这两个表述是同一个意思)
Embedding 的本质是语义理解,差的模型只能做"关键词相似度",好的模型能做真正的"语义相似度"。
测试方案
我的测试基于两个真实的企业知识库项目:
测试集 A:电商客服知识库,约 3000 条 QA 对,全中文,包含大量口语化表述和缩写("顺丰"、"快递单号"、"七天无理由"等)
测试集 B:技术文档知识库,约 1500 个文档片段,中英混合,包含代码、技术术语、API 文档
评测指标:
- Top-1 准确率:最相关的那个文档是否正确
- Top-5 召回率:正确文档是否在前5个结果里
- 处理速度:每秒能处理多少条文本
- 本地部署成本:模型大小、内存/显存需求
参赛选手
OpenAI text-embedding-3-large / text-embedding-3-small
云端 API,不能本地部署,但作为基线对比。
BGE 系列(BAAI/bge-m3, bge-large-zh, bge-base-zh)
北京智源研究院(BAAI)出品,专门针对中文做了大量优化。
BGE-M3 是目前最强的开源多语言 Embedding 模型之一,支持中、英及100多种语言,且在同一向量空间里,可以做中英跨语言检索。
M3E(Moka Massive Mixed Embedding)
MokaAI 开源,专为中文场景设计,小模型体积友好。
text2vec-large-chinese
传统方案,用 BERT 架构做中文语义相似度,历史较老但社区资料多。
测试结果
中文客服知识库(测试集 A)
模型 | Top-1准确率 | Top-5召回率 | 速度(条/秒) | 模型大小
---------------------------|------------|------------|------------|--------
OpenAI text-embedding-3-large | 89.2% | 96.5% | 受网络限制 | 云端API
OpenAI text-embedding-3-small | 86.1% | 94.8% | 受网络限制 | 云端API
BGE-M3 | 87.5% | 95.9% | 45 条/秒* | 570MB
bge-large-zh-v1.5 | 86.8% | 95.2% | 68 条/秒* | 330MB
bge-base-zh-v1.5 | 84.3% | 93.7% | 210 条/秒* | 95MB
M3E-large | 82.1% | 91.8% | 85 条/秒* | 560MB
M3E-base | 79.6% | 90.3% | 280 条/秒* | 150MB
text2vec-large-chinese | 77.2% | 88.5% | 95 条/秒* | 670MB*速度测试环境:RTX 3090 24G,batch_size=32
技术文档(中英混合,测试集 B)
模型 | Top-1准确率 | Top-5召回率
---------------------------|------------|------------
OpenAI text-embedding-3-large | 91.3% | 97.1%
BGE-M3 | 88.7% | 95.8%
bge-large-zh-v1.5 | 79.4% | 89.2%
M3E-large | 74.6% | 86.1%
text2vec-large-chinese | 68.3% | 82.4%中英混合场景差异很明显:BGE-M3 是唯一一个在中英混合场景接近 OpenAI 水准的本地模型,其他中文专用模型在英文内容上掉了很多分。
我的明确推荐
场景一:纯中文,数据可以出境,追求最优效果
用 OpenAI text-embedding-3-small。
性价比最高的选择。small 版本比 large 便宜很多,在纯中文场景的效果差距不大(86% vs 89%),成本低一个数量级。
每百万 tokens 的费用:text-embedding-3-small 约 $0.02,text-embedding-3-large 约 $0.13。对于一个中等规模知识库,small 的成本完全可以接受。
场景二:纯中文,需要本地部署
用 bge-large-zh-v1.5。
原因:
- 和 BGE-M3 的中文效果差距很小(86.8% vs 87.5%),不超过 1 个百分点
- 模型体积更小(330MB vs 570MB),部署更轻量
- 推理速度更快(68 条/秒 vs 45 条/秒)
- 内存占用更小
除非你的场景对这 1% 的差距特别敏感,否则 bge-large-zh 是更实用的选择。
场景三:中英混合,需要本地部署
必须用 BGE-M3,没有其他选择。
其他中文 Embedding 模型在英文上的表现让人失望,技术文档里一旦出现大量英文术语,检索效果会严重下降。BGE-M3 是目前唯一在中英双语场景都能表现优秀的开源本地模型。
场景四:资源非常有限(内存 < 1GB,没有 GPU)
用 bge-base-zh-v1.5(有 GPU)或 M3E-base(CPU 环境)。
这两个模型体积小,CPU 上也能跑,适合资源受限的部署场景。效果会损失一些,但比用 text2vec 老一代方案好得多。
实际使用的代码
部署 BGE-M3
from FlagEmbedding import BGEM3FlagModel
import numpy as np
from typing import Union
class BGEEmbedder:
"""
BGE-M3 本地 Embedding
支持中英双语,适合混合语言知识库
"""
def __init__(self, model_name: str = 'BAAI/bge-m3', use_gpu: bool = True):
self.model = BGEM3FlagModel(
model_name,
use_fp16=use_gpu # GPU 上用 fp16 加速
)
def embed(
self,
texts: Union[str, list[str]],
batch_size: int = 32,
max_length: int = 8192 # BGE-M3 支持超长文本
) -> np.ndarray:
"""
生成文本的 Embedding 向量
texts: 单个字符串或字符串列表
返回: (n, 1024) 的 numpy 数组
"""
if isinstance(texts, str):
texts = [texts]
embeddings = self.model.encode(
texts,
batch_size=batch_size,
max_length=max_length,
return_dense=True,
return_sparse=False, # BGE-M3 支持稀疏向量,这里只用稠密向量
return_colbert_vecs=False
)['dense_vecs']
return embeddings
def similarity(self, text1: str, text2: str) -> float:
"""计算两个文本的语义相似度"""
emb1 = self.embed(text1)
emb2 = self.embed(text2)
# 余弦相似度
dot_product = np.dot(emb1[0], emb2[0])
norm1 = np.linalg.norm(emb1[0])
norm2 = np.linalg.norm(emb2[0])
return float(dot_product / (norm1 * norm2))
# 使用示例
embedder = BGEEmbedder()
# 批量生成 Embedding(建索引时用)
documents = ["什么是RAG系统", "如何处理PDF文档", "Python异步编程教程"]
embeddings = embedder.embed(documents)
print(f"向量维度: {embeddings.shape}") # (3, 1024)
# 查询相似度
query = "检索增强生成"
score = embedder.similarity(query, "什么是RAG系统")
print(f"相似度: {score:.4f}") # 应该比较高和向量数据库集成
import chromadb
from chromadb import EmbeddingFunction, Documents, Embeddings
class BGEEmbeddingFunction(EmbeddingFunction):
"""
ChromaDB 的自定义 Embedding 函数(BGE-M3)
"""
def __init__(self, model_name: str = 'BAAI/bge-m3'):
self.embedder = BGEEmbedder(model_name)
def __call__(self, input: Documents) -> Embeddings:
embeddings = self.embedder.embed(list(input))
return embeddings.tolist()
def build_knowledge_base(
documents: list[dict], # [{'content': '...', 'metadata': {...}}]
collection_name: str = 'my_kb',
model_name: str = 'BAAI/bge-m3'
) -> chromadb.Collection:
"""
构建向量知识库
"""
client = chromadb.PersistentClient(path='./chroma_db')
# 删除已有的同名集合(重建时)
try:
client.delete_collection(collection_name)
except Exception:
pass
collection = client.create_collection(
name=collection_name,
embedding_function=BGEEmbeddingFunction(model_name)
)
# 批量添加文档
batch_size = 100
for i in range(0, len(documents), batch_size):
batch = documents[i:i+batch_size]
collection.add(
ids=[f"doc_{i+j}" for j in range(len(batch))],
documents=[d['content'] for d in batch],
metadatas=[d.get('metadata', {}) for d in batch]
)
print(f"已处理 {min(i+batch_size, len(documents))}/{len(documents)} 条")
return collection
def search_knowledge_base(
collection: chromadb.Collection,
query: str,
n_results: int = 5,
where: dict = None
) -> list[dict]:
"""
查询知识库
"""
kwargs = {'query_texts': [query], 'n_results': n_results}
if where:
kwargs['where'] = where
results = collection.query(**kwargs)
output = []
for i in range(len(results['documents'][0])):
output.append({
'content': results['documents'][0][i],
'metadata': results['metadatas'][0][i],
'distance': results['distances'][0][i],
'relevance': 1 - results['distances'][0][i] # 转换为相关度分数
})
return output量化加速(显存不够时)
如果显存有限(低于 8G),可以用量化版本:
from transformers import AutoTokenizer, AutoModel
import torch
def load_quantized_bge(model_name: str = 'BAAI/bge-m3'):
"""
加载 4-bit 量化的 BGE 模型,大幅降低显存占用
需要 bitsandbytes 库:pip install bitsandbytes
"""
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(
model_name,
quantization_config=quantization_config,
device_map='auto'
)
return tokenizer, model关于"国产够不够用"这个问题
我的答案是:在中文场景够用,而且够用得很好。
BGE-M3 和 bge-large-zh 在纯中文的 Top-5 召回率上和 OpenAI 的差距在 1% 以内,对实际业务影响基本感知不到。
差距在哪里?在长文本理解和复杂语义推理上,OpenAI 的模型仍然有优势。但 RAG 的 Embedding 不需要做推理,只需要做语义相似度匹配,这个差距在实际场景里不明显。
真正的选型决策很简单:
- 数据能出境 + 预算够 → OpenAI text-embedding-3-small,省心省事
- 数据不能出境,或者要控制成本 → BGE-M3(中英混合)或 bge-large-zh(纯中文),够用
别在 Embedding 选型上过度纠结,这不是 RAG 系统质量的最大决定因素。数据质量、切片策略、Rerank 机制,这些的影响比 Embedding 选型更大。
Embedding 选一个"够好的",然后把精力放到更关键的地方去。
