向量化与重排模型服务优化:Embedding 与 Reranker 的性能工程

RAG 的检索质量与延迟,一半取决于 Embedding 与 Reranker 的服务效率。这两类模型和生成模型截然不同:Embedding 是编码器(高吞吐、批处理友好),Reranker 是交叉编码器(算力密集、批内耦合)。本文拆解它们的服务特征、批处理与量化优化、GPU/CPU 选型、部署架构与评估指标,给出可落地的调优路径。

一个 RAG 系统的端到端延迟里,生成往往不是最慢的——Embedding 编码(文档入库、query 向量化)和 Reranker 重排(对 Top-K 候选打分)常占掉大头,尤其在检索候选多、文档库大的场景。这两类模型的服务优化和 LLM 生成完全不同:它们不逐 token 解码,而是「一次前向、一个向量/一个分数」。理解这个差异,才能对症下药。本文讲清 Embedding 与 Reranker 的服务特征与优化手段。

前置:ONNX Runtime 跨平台推理优化 、推理引擎终极对比 、模型量化技术详解 。

一、两类模型的服务特征

Embedding 与 Reranker 虽同属「检索栈」,但服务特征截然不同:

维度Embedding(双编码器)Reranker(交叉编码器)
输入单段文本query + 文档成对
输出一个向量(768/1024 维)一个相关性分数
计算量小(每段一次前向)大(每对一次全交叉注意力)
批处理极友好(各段独立)受限于对数量
典型模型BGE / GTE / E5 / text-embeddingbge-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 在线 queryCPU(ONNX)省 GPU,延迟可接受
高 QPS 在线 queryGPU 或专用加速器延迟敏感
边缘/私有化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 / INT81.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 优化 = 延迟工程(两阶段控候选 + 批处理 + 蒸馏)+ 独立部署 + 全程质量评测——「先分清是吞吐还是延迟问题」。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 排序学习与搜索召回排序系统
  2. 数据版本控制与血缘:DVC 与 LakeFS
  3. 模型可解释性:SHAP、LIME 与注意力归因