一个 RAG 系统的端到端延迟里,生成往往不是最慢的——Embedding 编码(文档入库、query 向量化)和 Reranker 重排(对 Top-K 候选打分)常占掉大头,尤其在检索候选多、文档库大的场景。这两类模型的服务优化和 LLM 生成完全不同:它们不逐 token 解码,而是「一次前向、一个向量/一个分数」。理解这个差异,才能对症下药。本文讲清 Embedding 与 Reranker 的服务特征与优化手段。
前置:ONNX Runtime 跨平台推理优化 、推理引擎终极对比 、模型量化技术详解 。
一、两类模型的服务特征
Embedding 与 Reranker 虽同属「检索栈」,但服务特征截然不同:
| 维度 | Embedding(双编码器) | Reranker(交叉编码器) |
|---|---|---|
| 输入 | 单段文本 | query + 文档成对 |
| 输出 | 一个向量(768/1024 维) | 一个相关性分数 |
| 计算量 | 小(每段一次前向) | 大(每对一次全交叉注意力) |
| 批处理 | 极友好(各段独立) | 受限于对数量 |
| 典型模型 | BGE / GTE / E5 / text-embedding | bge-reranker / Cohere Rerank |
| 延迟敏感度 | 入库可离线,query 需在线 | 在线(在检索后、生成前) |
| 优化重点 | 吞吐、批处理、量化 | 批处理、蒸馏、候选裁剪 |
关键差异:
Embedding:N 段文本 → N 个向量(可并行、可缓存、可离线批量)
Reranker:query × M 个候选 → M 个分数(在线、算力密集)
→ Embedding 优化 = 吞吐工程(批大、量化、多流)
→ Reranker 优化 = 延迟工程(裁剪候选、批内并行、蒸馏)
工程要点:先分清「你在优化哪一个」。Embedding 是吞吐问题——文档入库动辄百万级,追求每秒编码多少段;Reranker 是延迟问题——它卡在在线链路里,query 来了必须快速出分。把 Reranker 当 Embedding 优化(一味加大 batch)会推高在线延迟;把 Embedding 当 Reranker 优化(一味追低延迟)会拖垮入库吞吐。
二、Embedding 服务优化
2.1 批处理:吞吐的第一杠杆
Embedding 模型是编码器,各段输入相互独立,天然适合大批处理:
# 批处理编码(GPU 利用率随 batch 提升)
import torch
from transformers import AutoTokenizer, AutoModel
tok = AutoTokenizer.from_pretrained("BAAI/bge-large-zh-v1.5")
model = AutoModel.from_pretrained("BAAI/bge-large-zh-v1.5").cuda().half()
@torch.inference_mode()
def encode(texts, batch_size=256, max_len=512):
out = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
enc = tok(batch, padding=True, truncation=True,
max_length=max_len, return_tensors="pt").to("cuda")
hidden = model(**enc).last_hidden_state
# BGE 用 CLS token,并做 L2 归一化
vec = torch.nn.functional.normalize(hidden[:, 0], dim=-1)
out.append(vec.cpu())
return torch.cat(out)
批处理调优要点:
□ batch_size 越大吞吐越高,直到显存或延迟拐点
□ 按长度分桶(length bucketing)→ 减少 padding 浪费
□ 排序后分批(相似长度聚在一起)→ 提升有效算力
□ padding 到批内最长即可,别固定到 max_len
长度分桶对吞吐影响巨大:一批里若混入一个 512 token 的长文本,其余短文本全被 padding 到 512,算力大量浪费。
2.2 量化:INT8 / FP16
量化收益:
□ FP16:显存减半,编码提速 ~1.5-2x,精度几乎无损
□ INT8(动态量化):再提速,精度损失小
□ 对 Embedding 这类「输出是连续向量」的模型,
INT8 量化对检索质量影响通常可接受(需评测验证)
注意:
□ 量化后必须重新评测检索指标(Recall@K、MRR)
□ 不同模型对量化的敏感度差异大,别一刀切
2.3 GPU 还是 CPU
| 场景 | 建议硬件 | 理由 |
|---|---|---|
| 大规模离线入库 | GPU 批量 | 吞吐高,摊薄成本 |
| 低 QPS 在线 query | CPU(ONNX) | 省 GPU,延迟可接受 |
| 高 QPS 在线 query | GPU 或专用加速器 | 延迟敏感 |
| 边缘/私有化 | CPU + INT8 | 无 GPU 环境 |
# CPU 上用 ONNX Runtime + INT8 动态量化
python -m onnxruntime.quantization.preprocess \
--input model.onnx --output model_pre.onnx
python -m onnxruntime.quantization.quantize_dynamic \
--input model_pre.onnx --output model_int8.onnx \
--weight_type QInt8
工程要点:Embedding 优化的核心是**「把 GPU 喂饱」**——大批处理 + 长度分桶 + FP16/INT8,三者叠加能把吞吐提升数倍。别用「一条一条编码」的朴素写法,那是把 GPU 当 CPU 用。低 QPS 场景果断上 CPU + ONNX,省下的 GPU 额度留给生成模型。
三、Reranker 服务优化
3.1 交叉编码器为何贵
Reranker 把 query 与文档拼在一起送入模型,让注意力在两者间充分交互——精度高,但计算量随「对数量」线性增长:
成本模型:
Reranker 延迟 ≈ 候选数 M × 单对计算时间
单对计算时间 ∝ 序列长度(query + 文档)
→ 降延迟的两条路:
① 减少 M(候选裁剪):先用便宜手段把 M 从 100 降到 20
② 降低单对成本(量化、蒸馏、短序列)
3.2 候选裁剪:两阶段检索
标准做法是「粗排 + 精排」:
两阶段检索:
Stage 1 向量检索(Embedding)→ Top-100 候选(便宜、快)
Stage 2 Reranker 精排 → Top-10(贵、准)
Stage 3 送入 LLM 生成
好处:
□ Reranker 只需处理 100 个候选,而非全库
□ 用 Embedding 的召回能力 + Reranker 的精度
□ 延迟可控(精排规模固定)
# 两阶段检索示意
candidates = vector_search(query_emb, top_k=100) # 粗排
pairs = [(query, c.text) for c in candidates]
scores = reranker.predict(pairs, batch_size=32) # 精排
top = [c for c, s in sorted(zip(candidates, scores),
key=lambda x: -x[1])[:10]]
3.3 批处理与量化
Reranker 的批处理受「对」的序列长度影响,同样要按长度分桶:
Reranker 批处理要点:
□ batch_size 受「对长度 × 批大小」的显存限制
□ 按 query+doc 总长度分桶
□ 长文档先截断(通常取前 512 token 足够)
□ FP16 加速;INT8 需评测精度
□ 蒸馏:用大 Reranker 蒸馏小模型(如 6 层),延迟减半
# Reranker 批量打分(长度分桶)
def rerank(query, docs, batch_size=32, max_len=512):
pairs = [(query, d) for d in docs]
scores = []
for i in range(0, len(pairs), batch_size):
batch = pairs[i:i+batch_size]
enc = tok(batch, padding=True, truncation=True,
max_length=max_len, return_tensors="pt").to("cuda")
with torch.inference_mode():
logits = model(**enc).logits.view(-1)
scores.extend(logits.float().cpu().tolist())
return scores
工程要点:Reranker 优化的核心是**「控制精排规模」**——用两阶段检索把候选数压到可控范围,再对这批候选做批处理 + 量化。别把整个文档库喂给 Reranker(成本爆炸);也别为省事把 Top-K 设得太小(损失召回)。「粗排召回 100 → 精排取 10」是久经考验的甜点区。
四、部署架构
4.1 服务形态
两种部署形态:
□ 独立服务:Embedding 与 Reranker 各自成服务(推荐)
→ 独立扩缩容、独立选型、故障隔离
□ 合体服务:一个进程同时提供两者
→ 省资源,但耦合(一个慢拖累另一个)
接口设计:
POST /embed {"texts": [...]} → {"vectors": [...]}
POST /rerank {"query": "...", "docs": [...]} → {"scores": [...]}
□ 都支持批处理(一次传多段/多对)
□ 都返回 usage(用于计费与监控)
4.2 缓存策略
Embedding 缓存:
□ 文档向量:入库时算一次,永久缓存(内容哈希为 key)
□ query 向量:相同 query 命中缓存(精确 + 语义)
□ 命中率通常很高(重复查询、重复文档)
Reranker 缓存:
□ (query, doc) 对分数:可缓存,但组合爆炸 → 收益有限
□ 更适合缓存「最终 Top-K 结果」而非中间分数
文档向量的缓存收益最大:一份文档只需编码一次,重复入库直接命中,避免重复计算。
# 缓存 key 设计:内容哈希(而非文档 ID)
import hashlib
def cache_key(text, model_name):
h = hashlib.sha256(text.encode()).hexdigest()[:16]
return f"emb:{model_name}:{h}" # 换模型即失效,避免串味
# 命中则跳过编码;未命中则批量编码后回填
def embed_cached(texts, store, model_name):
keys = [cache_key(t, model_name) for t in texts]
hits = store.mget(keys)
miss_idx = [i for i, v in enumerate(hits) if v is None]
if miss_idx:
vecs = encode([texts[i] for i in miss_idx])
for i, v in zip(miss_idx, vecs):
store.set(keys[i], v, ex=30*24*3600) # 30 天过期
hits[i] = v
return hits
缓存 key 必须包含模型名——换 Embedding 模型后,旧向量与新模型不在同一空间,混用会导致检索彻底失效。
4.3 与生成服务的协同
检索栈与生成栈的协同:
□ 检索服务独立扩缩容(检索 QPS 与生成 QPS 不同)
□ 检索延迟纳入端到端 SLO(TTFT 的一部分)
□ 检索服务故障时的降级(返回缓存/跳过精排)
□ 共享 GPU 池时做优先级隔离(见 GPU 共享与调度)
工程要点:独立部署 + 强缓存是检索栈的最佳实践。Embedding 与 Reranker 的计算特征不同(吞吐 vs 延迟),独立服务才能各自优化。文档向量缓存是「一次计算、长期复用」的典型,投入产出比极高,务必做好。整条检索链路的延迟应计入 RAG 的端到端 SLO。
五、性能调优与评估
5.1 调优检查清单
| 优化项 | 手段 | 预期收益 |
|---|---|---|
| Embedding 吞吐 | 大批处理 + 长度分桶 | 2-5x |
| Embedding 精度 | FP16 / INT8 | 1.5-3x |
| Reranker 延迟 | 候选裁剪(粗排 100→精排 10) | 数倍 |
| Reranker 成本 | 蒸馏小模型 | 2x |
| 缓存 | 文档向量 + query 向量 | 命中即零成本 |
| 硬件 | 低 QPS 走 CPU/ONNX | 省 GPU |
5.2 评估指标
优化不能牺牲检索质量,必须评测:
检索质量指标:
□ Recall@K:Top-K 里包含相关文档的比例(召回)
□ MRR:首个相关文档的排名倒数均值
□ NDCG@K:考虑排序位置的相关性
□ 端到端:RAG 答案的忠实度/正确率(最终标准)
性能指标:
□ Embedding:texts/s(吞吐)、P99 单批延迟
□ Reranker:pairs/s、P99 精排延迟
□ 缓存命中率
评测纪律:
□ 量化/蒸馏前后必须跑同一评测集对比
□ 关注「质量下降是否可接受」,而非「是否有下降」
□ 质量-延迟曲线:找 SLO 内的最优工作点
工程要点:检索栈的优化必须以质量指标为准绳。量化、蒸馏、候选裁剪都会影响召回与排序,必须用固定评测集验证「质量损失在可接受范围」。别只看延迟数字漂亮,检索质量悄悄下降会让整个 RAG 系统「答非所问」。质量与延迟的权衡数据,参照 推理基准与压测 的方法论固定下来。
六、速查表与一句话记忆
| 问题 | 一句话答案 |
|---|---|
| Embedding 本质 | 双编码器,吞吐问题,可批量可缓存可离线 |
| Reranker 本质 | 交叉编码器,延迟问题,算力密集 |
| Embedding 优化 | 大批处理 + 长度分桶 + FP16/INT8 |
| Reranker 优化 | 两阶段检索控候选 + 批处理 + 蒸馏 |
| 甜点区 | 粗排召回 100 → 精排取 10 |
| 缓存 | 文档向量永久缓存,收益最大 |
| 部署 | 独立服务、独立扩缩容、独立选型 |
| 硬件 | 低 QPS 走 CPU+ONNX,省 GPU |
| 评估 | Recall@K / MRR / NDCG + 端到端质量 |
| 纪律 | 量化/裁剪前后必评测,质量优先 |
一句话记忆:Embedding 优化 = 吞吐工程(大批 + 分桶 + 量化 + 缓存)+ Reranker 优化 = 延迟工程(两阶段控候选 + 批处理 + 蒸馏)+ 独立部署 + 全程质量评测——「先分清是吞吐还是延迟问题」。
延伸阅读
- ONNX Runtime 跨平台推理优化
- 推理引擎终极对比
- 模型量化技术详解
- 推理基准与压测
- Embedding 与 Reranker 模型选型 — 模型对比
- 向量数据库与索引 — 索引结构与检索
- 向量索引原理 — HNSW/IVF 等索引
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。