Embedding 三个后端怎么选,以及为什么必须统一一个入口
选 embedding 模型,最容易犯的错不是选错,而是选完之后把if backend == ...散落在业务代码的每个角落。这篇讲我在这周踩的三个坑,和一个我现在觉得是硬规矩的设计。
一、先把三个后端摆出来
我语料是三家 LLM 平台的官方 API 文档,embedding 后端也考察了三家:
| 后端 | 模型 | 维度 | 单批上限 | 额度 |
|---|---|---|---|---|
| 智谱 | embedding-3 | 2048 | 32 | 免费额度已耗尽 |
| 阿里百炼 | text-embedding-v4 | 1024 | 10 | 免费 100 万 token / 90 天 |
| 本地 | bge-small | — | — | 装不上(torch 拉不下来) |
先说一个容易被忽略的事实:DeepSeek 不提供 embedding 接口。所以"我 chat 用 DeepSeek、embedding 也顺手用 DeepSeek"这条路根本不存在,必须另找一家。
本地bge-small是最诱人的选项——离线、免费、可控。但本机没有显卡,torch从 download.pytorch.org 拉不下来(DNS 失败),装都装不上。"本地最省事"在没显卡的机器上是句空话。
二、统一入口是硬要求,不是洁癖
最后选了百炼(额度够、MRR 也最高,见第五节)。但真正重要的决定不是选谁,是怎么把它接进代码。
# 统一入口:业务代码只调 embed(),永远不知道后端是谁defembed(texts:list[str],backend:str="bailian")->list[list[float]]:return_BACKENDS[backend](texts)# 反面教材:把 if/else 散落在业务代码里ifbackend=="local":v=bge.encode(q)# 1024 维elifbackend=="bailian":v=dashscope_embed(q)# 1024 维else:v=glm_embed(q)# 2048 维 ← 维度都对不上为什么这是硬要求,不是"好看一点"?因为这里藏着一个最阴的 bug:
库里写入用百炼、查询时用智谱,代码照常跑、不报错、HTTP 200,但向量维度对不上、语义空间根本不是同一个,检索结果全错,而且极难排查。
这种 bug 不会在单元测试里冒出来,只会在"某个角落漏改了一处"之后悄悄上线。统一入口把"改后端"从"全局搜embed改八处"变成"改一处",从根上消灭这个错误类别。
三、换后端必须整库 + 查询一起重算
这是统一入口之外的第二个硬规矩:不同模型的向量不能混用。
- 维度不同:智谱 2048,百炼 1024;
- 即使维度相同,语义空间的几何也不一样——同一个词在两个模型里的向量,距离和方向对不上。
所以"我把写入换到百炼,查询还留着智谱"是自欺。换后端只有一条路:
1. 全库重新 embedding(写入端) 2. 查询端同步换成同一模型 3. 重新跑一遍评测,确认没退化实测一次全量重建:3700 块 / 3563 向量 / 407.6 秒 / ¥0.59。因为额度是按 token 算的,重建整库约 84 万 token,吃掉百炼免费额度(100 万/90 天)的八成多。所以"换后端"不是免费的,成本上要想清楚再动。
四、批量上限要按后端分别设
两个后端的单批上限不一样:百炼10,智谱 32。这是服务端硬校验,超了就 400。
写批量脚本时最容易犯的错是"统一设成 32"——因为智谱能到 32,顺手就全用 32。结果百炼那边第 10 条之后就报 400,你还以为是网络抖动。
批次大小是后端的属性,不是脚本的属性。我的做法是把batch_size挂在后端配置里,而不是写死在循环里。
五、选谁不靠感觉,靠冒烟 MRR
"哪个模型效果好"这种话,靠嘴说没用。我用同一批冒烟查询(8 条,含跨文档综合题)对两个后端做了检索质量对比:
| 后端 | MRR |
|---|---|
| 百炼 text-embedding-v4 | 0.729 |
| 智谱 embedding-3 | 0.646 |
百炼 MRR 更高,加上额度还够,就定了百炼。这个对比只有 8 条样本,只能用来选型,不能当最终指标——真正拍板检索质量的是后面 50 条 golden set 的 recall@5。
一个藏得最深的坑:额度耗尽不是 429
智谱额度耗尽时,返回的错误码是1113,不是大家条件反射去匹配的 429。我当时全量重建跑到第 59 批卡住,日志里既没有 429 也没有明确的"额度不足",查了半天才发现是 1113。
教训:别把可用性押在一份免费额度上。免费额度是让你做原型验证的,不是让生产服务依赖的。你代码里对 429 做了重试、降级、告警,但额度耗尽这条错误路径根本没覆盖——因为它的错误码不在你的想象里。
小结
| 结论 | 数字 / 事实 |
|---|---|
| DeepSeek 不提供 embedding | 必须另找后端 |
| 三个候选 | 智谱(2048/批32/额度耗尽) · 百炼(1024/批10/免费) · 本地(装不上) |
| 统一入口 embed() | 硬要求:消灭"库一个模型、查询另一个模型"这类不报错的全错 bug |
| 换后端 | 必须整库 + 查询一起重算,不能混用 |
| 全量重建成本 | 3700 块 / 407.6s / ¥0.59 / 84 万 token |
| 百炼单批上限 | 10,超了 400 |
| 冒烟选型 | 百炼 MRR 0.729 > 智谱 0.646 |
| 额度耗尽 | 错误码 1113,不是 429 |
上一篇:为什么我没用 LangChain。下一篇:为什么 3563 条向量我用暴力全量而不是 ANN 索引。