测试方法:围绕「大模型网关接入」构造 20 篇中文文档 + 一一对应的 20 篇英文文档,
20 条中文 query + 20 条英文 query 人工标注答案,同一批语料在同一时间窗内跑三个模型。
语料规模有限,小于 5 个百分点的差距不构成结论,请按自己的语料复现。
一、先选对模型
选 bge-m3 的场景
- 语料以中文为主:检索质量与 3-small 同档,单价是它的一半, 同一批中文文本的 token 数又只有它的 42% —— 实际花费约 1/5
- 想省存储:1024 维比 1536 省 33%、比 3072 省 66%
- 需要覆盖冷门语种(100+ 语言)
- 希望本地能跑同一个开源模型,保证线上线下向量一致
选 OpenAI 的场景
- 语料以英文或代码为主:检索质量高一档。这类内容上 bge-m3 的 token 数反而多 15%–50%, 但单价减半之后总花费仍然更低,所以这里该按质量选,不是按价格选
- 知识库里中英文混排、又只要返回一条答案
- 需要
dimensions降维来压缩存储 - 已有大量按 OpenAI 分数带标定的阈值,不想重标
二、长文档一定要切块
bge-m3 的窗口是 8192 token,一篇上万字的手册整篇塞得进去。但不该这么做。
实测:把 20 个小节串成一篇长手册,候选集里另放 20 篇「每篇正好对应其中一节」的强干扰短文,
再用 20 条问题去检索——
同一个问题,对「整篇」和「正确那一小节」的相似度平均相差 +0.10:
一篇长文的单一向量是全文语义的平均值,会被任何一段精准的短文本压过去。
三、相似度阈值必须按模型重标
这是从 OpenAI 迁到bge-m3 时最容易翻车的地方。两者的分数带完全不同。
同一批人工标注的语义对,三个模型给出的分:
实测出的最佳单一阈值(
bge-m3):
作为对照,
text-embedding-3-small 中文场景的最佳阈值是 0.45,3-large 是 0.33。
四、相似度不能用来判断「说得对不对」
这一条对所有 embedding 模型都成立,不是某个模型的缺陷,但必须提前知道:
三个模型全部失守。余弦相似度衡量的是「在不在谈同一件事」,不是「说法是否一致」。
所以否定、价格数字、版本号、实体名这类关键差异,检索阶段一定分不开。正确的兜底是:
1
向量召回 Top 50–100
用
bge-m3 把候选范围快速缩小,阈值只用来挡掉明显无关的。2
重排序精排 Top 3–5
用
bge-reranker-v2-m3 对候选逐条打分。
它是 query 和文档拼在一起过一遍模型的 Cross-Encoder,恰好擅长区分这类细微差异,
而且和 bge-m3 出自同一个模型家族。3
生成阶段让大模型自己判断
把 Top 3–5 连同原始问题一起交给大模型,在提示词里明确要求「若检索内容与问题不符,直接说没有找到」。
五、批量与并发怎么开
批量:64–128 是拐点
128 条以后每条摊薄成本几乎不再下降(58ms → 53ms),单次耗时却涨了 7 倍。
单次耗时直接决定客户端超时风险,以及一次失败要重做多少工作。
并发:线上 8,灌库 32–48
- 线上实时检索走并发 8:实测 200 次零失败
- 离线灌库可以开到 32–48:吞吐最高,但已经开始出现 429,必须带指数退避
- 不要超过 64:96 并发失败率 11.8%,且出现 59 秒级的挂起请求
六、成本怎么算
成本由两件事相乘决定:单价和同一段文本被切成多少 token。两者在这里都不一样。bge-m3 是 $0.01 / 1M tokens,text-embedding-3-small 是 $0.02,单价先差一半。
再叠上分词效率——bge-m3 用 XLM-R 的 SentencePiece,中文约 2.1 字/token,
OpenAI 的 cl100k 中文只有约 0.9 字/token:
中文语料的实际花费约为
text-embedding-3-small 的 1/5。
英文和代码上 bge-m3 消耗的 token 更多,但单价减半之后总花费仍然更低——
所以这两类语料该不该用它,看的是检索质量(英文 Recall@1 70% vs 80%),不是价格。
存储侧同样有差距。向量已 L2 归一化、返回值本身就是 fp16 精度,
所以用 float16 存 bge-m3 的向量是零精度损失的:
七、几个会静默出错的坑
7.1 LangChain 的默认配置会让召回率从 80% 掉到 15%
实测代价:
修复方式:
7.2 空字符串会被当成有效输入
input: "" 在 bge-m3 上返回 200(OpenAI 官方此处是 400),会得到一条 1024 维向量并计费 2 token。
切块脚本如果没过滤空块,知识库里就会混进一批无意义向量,还会在检索时随机冒出来。
入库前自己过滤空白文本。
7.3 dimensions 参数不能用
Model "bge-m3" does not support matryoshka representation, changing output dimensions will lead to poor results.
bge-m3 没有做 Matryoshka 训练,截断向量会显著掉点。要压缩存储请用 float16,不要自己截断维度。
7.4 模型名大小写敏感、没有别名
只有bge-m3 可用。BAAI/bge-m3、BGE-M3 都会返回 503「无可用渠道」。
7.5 8192 是「每条输入」的上限
单条超过 8192 token 直接返回 400,不会静默截断(这是好事:不会拿到一个丢了后半段却看着正常的向量)。 但这个上限是逐条判定的,不是整次请求:实测单次请求塞进 1024 条 / 102560 token 仍然正常返回。 批量里只要有一条超限,整个请求就 400,报错里的 token 数是那一条的,不是总和。八、一份可以直接抄的最小实现
相关文档
文本向量化 API
接口参数、返回格式、快速上手
重排序模型
bge-reranker-v2-m3,精排环节的正确解法RAG 实战调优
两段式检索架构、召回条数怎么定
模型价格
全部 embedding 模型的实时价格