2025 年每篇指南都告诉过你、但现在已经错了的三件事。以及取而代之的六个选择。
每月第一天,你打开云账单,发现 text-embedding-3-large 那一项仍然是每百万 tokens 13 美分。去年这个价格看起来还算合理,它成为默认推荐是有原因的,你付了钱然后继续往前走。但今天,你支付的是实际所需成本的两倍。
Voyage 在 1 月发布了 voyage-4,基础层价格为每百万 tokens 6 美分,lite 层为 2 美分。他们声称 large 层比 text-embedding-3-large 多 14% 的概率找到正确答案。单是价格计算本身,就足以迫使这场讨论发生。
“该选哪个 embedding model” 的答案不再是一个单一的 API 调用。Voyage-4 适合通用型 hosted 场景。Qwen3-Embedding-8B 是目前用于代码检索最强的 open-weight model。PDF 上的视觉检索已经分化成了自己的架构路线。Hybrid retrieval 改变了你是否真的需要一个新 embedding model 的成本与收益计算。
六类 workload,六个选择,以及去年三个已经过时的默认答案。
通用型商业 Hosted RAG
假设你有一个包含 1000 万个 chunk 的语料库,内容是工程文档、工单和规格说明,并且运行在 hosted API 上。选择是 voyage-4,价格为每百万 tokens $0.06。它支持 32K token context,默认输出 1,024 维,维度足够小,使得 pgvector index 仍然可以放进一台中等规格机器的 RAM 中;并且 2 亿免费 tokens 足以在你花一分钱之前完成类似 Django codebase 规模的索引。
Voyage-4 允许你用昂贵模型索引一次,再用便宜模型查询,因为它们共享同一个坐标系。
# Install: pip install voyageai numpy
# Env: export VOYAGE_API_KEY=...
import os
import numpy as np
import voyageai
client = voyageai.Client(api_key=os.environ["VOYAGE_API_KEY"])
corpus = [
"Postgres migrations use Alembic. Run alembic upgrade head after schema changes.",
"Docker Compose brings up the local stack. See docker-compose.dev.yml for services.",
"Deployments use Helm charts in the /deploy directory. Argo CD syncs from main.",
]
# Index the corpus with voyage-4-large at $0.12/M. One-time cost.
# The input_type="document" flag is required for asymmetric embedding.
doc_embeddings = client.embed(
corpus,
model="voyage-4-large",
input_type="document",
).embeddings
# Query with voyage-4-lite at $0.02/M. Every session.
# Same vector space. No re-embedding of the corpus needed.
query_embedding = client.embed(
["How do I run database migrations?"],
model="voyage-4-lite",
input_type="query",
).embeddings[0]
# Cosine similarity, top match.
scores = [
np.dot(query_embedding, doc) / (np.linalg.norm(query_embedding) * np.linalg.norm(doc))
for doc in doc_embeddings
]
top_idx = int(np.argmax(scores))
print(f"Top match (score={scores[top_idx]:.3f}): {corpus[top_idx]}")
# Top match (score=0.687): Postgres migrations use Alembic. Run alembic upgrade head after schema changes.
如果你已经在运行 text-embedding-3-large,并且相对于你的整体基础设施账单而言成本差异可以忽略不计,那么继续使用它也是说得通的。你错过的是共享向量空间带来的灵活性。
Self-Hosted Open-Weight RAG
如果这 1000 万个 chunk 的语料库是合同、患者记录,或者任何处于合规制度约束下、无法使用外部 API 的内容,那么模型必须运行在你自己的硬件上。选择 Qwen3-Embedding-8B。Apache-2.0 license,32,768 token context,可与 vLLM 和 SGLang 顺畅配合;在租用的 A100 上,只要吞吐量足够高、能够让 GPU 保持饱和,批量 ingestion pipeline 的有效每百万 token 成本会低于商业 API。
Qwen3 允许你在事后截断向量,这才是关键特性。这被称为 Matryoshka representation learning。
Qwen3 原生输出 4,096 维,对于 1000 万 chunk 的语料库来说,index 大约需要 160 GB RAM。你可以截断到 1,024 或 512 维,并保留几乎全部语义信号,因为训练过程会把最强的信号压入最前面的维度。截断本身只是一个 Python slice,但之后必须重新 normalize,否则 cosine similarity 会漂移。
# Install: pip install sentence-transformers torch numpy
import numpy as np
from sentence_transformers import SentenceTransformer
# Qwen3-Embedding-8B: Apache-2.0, 32k context, Matryoshka dims 32-4096.
# Load once at startup. On a single H100 or A100, batch throughput is high.
model = SentenceTransformer("Qwen/Qwen3-Embedding-8B")
query = "How do I run database migrations?"
doc = "Postgres migrations use Alembic. Run alembic upgrade head after schema changes."
# Embed at native 4096 dims. prompt_name="query" is required for the query side.
query_full = model.encode(query, prompt_name="query")
doc_full = model.encode(doc)
# Truncate to 512 dims.
# Storage: 4096 dims * 4 bytes = 16 KB per vector.
# Truncated: 512 dims * 4 bytes = 2 KB per vector. 8x reduction.
query_short = query_full[:512]
doc_short = doc_full[:512]
# Matryoshka embeddings are pre-normalized at full dim.
# Slicing breaks the norm, so you must re-normalize.
query_short = query_short / np.linalg.norm(query_short)
doc_short = doc_short / np.linalg.norm(doc_short)
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"4096-dim similarity: {cosine(query_full, doc_full):.3f}")
print(f" 512-dim similarity: {cosine(query_short, doc_short):.3f}")
# 4096-dim similarity: 0.712
# 512-dim similarity: 0.694
# Signal mostly preserved; storage is 8x smaller.
如果你没有能够高效运行 80 亿参数模型的 GPU 硬件,就跳过这个选择。对于低吞吐量 workload,hosted API 的成本通常低于配置专用实例。
代码检索 RAG
标准 tokenizer 看到 PaymentRetryPolicy 时,会把它粗暴地拆成 Payment、Retry、Policy。三个互不相关的英文词。代码模型不会这样做。
hosted 选择是 voyage-code-3,价格为每百万 tokens $0.18。self-hosted 选择是 Qwen3-Embedding-8B。
代码模型是在 abstract syntax trees 和开发者文档上训练的。模型会学到 PaymentRetryPolicy 是一个操作单元。它理解一个文件中的函数定义与另一个文件中的函数调用之间的关系。
与 Voyage 的通用模型相比,Voyage 对 voyage-code-3 收取 3 倍溢价。如果你的 codebase 很小,或者你的查询主要是关于架构的自然语言问题,那么通用型 voyage-4 模型很可能已经足够好。在支付溢价之前,先在开发者查询上衡量你的 retrieval hit rate。
Long-Context RAG
第 42 页的一个条款修改了第 3 页的一个定义。标准的 500-token chunking 会把它们分到不同的向量里。
长文档的选择是 voyage-context-3,价格为每百万 tokens $0.18。
Long-context model 可以在一次 embedding 调用中处理最多 32,000 tokens。模型一次性看到整篇文档,并把跨章节关系编码进最终向量。
如果你的平均文档长度低于 8,000 tokens,就跳过这个选择。使用更便宜模型的标准 chunking 更具成本效益,并且通常会在短文档上带来更好的 precision。
Multimodal 和 Visual-Document RAG
当这 1000 万个 chunk 是页面图像,而不是解析后的文本;当 PDF 里充满图表、表格和 layout,并且 pdfminer 会把它们扁平化成噪声时,检索问题的形态就变了。self-hosted 选择是 Nemotron ColEmbed V2 8B,hosted 选择是 voyage-multimodal-3.5,价格为每百万 text tokens $0.12、每十亿 pixels $0.60。截至 2026 年 2 月,Nemotron 使用 late-interaction 在 ViDoRe V3 上领先,因此它不是把整个页面 pooling 成一个向量,而是为每个 token 保留一个向量,并询问每个 query token 是否在页面某处有一个接近匹配,然后通过 MaxSim 求和。
存储惩罚非常严重。Single-vector pooling 存储一百万个文档页面大约需要 3.8 GB,而 Nemotron ColEmbed 8B 在 fp16 下存储同样的一百万页需要 5,897 GB。在 managed Milvus cluster 上,pooled 方案大约每月 $700,因此 1,500 倍的存储膨胀在实践中会变成 175 倍的月度账单,而且这还不包括搜索它所需的计算成本。
你可以通过应用 learned projection dimension reduction 来缓解这个问题,把向量从 4,096 维降到 128 维。这能保留约 95% 的准确率,同时把存储占用降到原来的 3%,让月度账单重新降下来。
如果你的 workload 只有文本,请忽略这些模型。不要为了“面向未来”而把 multimodal embedder 引入文本 pipeline。
预算或延迟受限的 RAG
预算紧张时的选择是 voyage-4-lite,价格为每百万 tokens $0.02。如果你把那 1000 万语料库服务给数千个并发查询,API 成本就不再是可以忽略的尾数,而 lite 层仍然与你的 base 和 large 层处在同一个坐标系中,因此你以后可以升级 query routing,而不必重新 embedding。
如果 latency 是你的硬约束,并且你无法承受访问 hosted API 的一次网络跳转,那么你需要运行一个小型 self-hosted model。截断到较小维度的 Qwen3-Embedding 表现不错,或者你也可以考虑能在 CPU 上运行的旧版 distilled models。
决策表
这个框架将六种 workload 形态映射到当前的最佳答案。
被推翻的 2025 年说法
去年很多所谓的常识现在都已经过时了。2025 年指南中的两个具体说法,在 2026 年的证据面前站不住脚。
更换模型时重新 embedding 整个语料库不再是一条硬性规则。voyage-4 家族的共享向量空间证明了,不同尺寸的模型可以读写同一个 latent space。如果你跨 vendor 迁移,比如从 OpenAI 到 Voyage,或者从 Voyage 到 Qwen3,你仍然需要重新 embedding。但在现代模型家族内部,这个摩擦已经消失。
甚至连 vector database vendor Vespa 都发布过数据显示,在所有测试模型上,hybrid retrieval 都优于 semantic-only。平均来看,最佳 hybrid 方法比 semantic-only 高 3 到 5 个百分点。
Dense embeddings 捕获语义含义。 它们知道 “server crash” 与 “hardware failure” 相关。BM25 捕获精确的词法匹配。它知道搜索 PAYMENTS_API_TIMEOUT_504 的用户想要的是包含这个精确字符串的文档,而不是一篇泛泛讨论网络延迟的文档。
当你使用 Reciprocal Rank Fusion 将它们结合起来时,你同时抓住了语义意图和精确标识符。Reciprocal Rank Fusion 是一个评分公式。它查看某个文档在 dense list 中的排名,以及同一个文档在 BM25 list 中的排名。它对两个排名都应用一个 smoothing constant,然后取倒数并相加。这可以防止在 vector search 中排名第一的文档压倒在两个列表中都排名第二的文档。
然后你把合并后的 top-K 列表交给 reranker,进行最后一轮 cross-encoder 打分。Cross-encoder 是一种把 query 和 document 放在一起读取以评估相关性的模型,而不是比较两个预先计算好的向量。
# Install: pip install voyageai rank-bm25 numpy
import os
import numpy as np
import voyageai
from rank_bm25 import BM25Okapi
client = voyageai.Client(api_key=os.environ["VOYAGE_API_KEY"])
corpus = [
"Postgres migrations use Alembic. Run alembic upgrade head after schema changes.",
"Docker Compose brings up the local stack. See docker-compose.dev.yml for services.",
"Deployments use Helm charts in the /deploy directory. Argo CD syncs from main.",
"SQLAlchemy handles ORM. Session lifecycle is managed by contextlib.contextmanager.",
"Migrations rollback is destructive. Always backup before alembic downgrade.",
]
query = "how do I revert a database migration safely?"
# Stage 1: dense embeddings via voyage-4.
doc_embeddings = client.embed(corpus, model="voyage-4", input_type="document").embeddings
q_embedding = client.embed([query], model="voyage-4", input_type="query").embeddings[0]
dense_scores = np.array([
np.dot(q_embedding, d) / (np.linalg.norm(q_embedding) * np.linalg.norm(d))
for d in doc_embeddings
])
# Stage 2: BM25. Catches lexical matches dense misses (e.g., "rollback", "downgrade").
tokenized = [doc.lower().split() for doc in corpus]
bm25 = BM25Okapi(tokenized)
# rank-bm25 uses get_scores() to return the array of scores
bm25_scores = bm25.get_scores(query.lower().split())
# Stage 3: Reciprocal Rank Fusion (RRF). Combine the two rankings.
# k is a smoothing constant, usually set to 60 in production systems.
def rrf(scores, k=60):
order = np.argsort(-scores)
ranks = np.empty_like(order)
ranks[order] = np.arange(len(order))
return 1.0 / (k + ranks + 1)
fused = rrf(dense_scores) + rrf(bm25_scores)
top_k_idx = np.argsort(-fused)[:3]
# Stage 4: Voyage rerank-2.5 on the top-K. Cross-encoder is more expensive
# per query but only runs on 3 candidates, not the whole corpus.
top_k_docs = [corpus[i] for i in top_k_idx]
reranked = client.rerank(
query=query,
documents=top_k_docs,
model="rerank-2.5",
top_k=3,
).results
print("Top result:", reranked[0].document)
# Top result: Migrations rollback is destructive. Always backup before alembic downgrade.
reranker 充当裁判。Cross-encoder 太慢,无法在你的 1000 万 chunk 数据库上运行,但它足够快,可以为 hybrid fusion 步骤召回的 30 个候选结果打分。
在你发起一次完整的 embedder 替换之前,先把 BM25 fusion 和 reranker 加入现有 pipeline。与单独升级 embedding model 相比,这种架构调整能以更低成本带来更多 retrieval quality 提升。
结语
如果你在那 1000 万 chunk 语料库上运行混合 workload,你不再需要在一个模型上妥协。你可以用 voyage-4-large 索引文本,用 voyage-4-lite 查询它,并把代码相关查询路由到 voyage-code-3。如果你更愿意掌控自己的基础设施,Qwen3-Embedding-8B 提供了一个 open-weight foundation,并且可以通过 Matryoshka truncation 适配你的存储预算。
你的 chunking strategy 要放在第一位。Hybrid retrieval 会捕获 dense vectors 漏掉的精确词法匹配。reranker 会对最终候选列表排序。当这三个杠杆都调优之后,你仍然缺少 semantic recall 时,更换 embedding model 才有意义。

