RAG 重排序指南:Cohere Rerank vs bge-reranker vs Cross-Encoder

引言

RAG 系统的标准流程是:向量检索 Top-K → 直接喂给 LLM。但向量检索(双塔模型)的弱点是精度有限:它擅长快速召回大量相关文档,但不擅长精细区分「真正相关」和「看起来相关」。

重排序(Reranking)是解决这一问题的关键环节:用一个更强大的模型对检索结果重新打分排序,将最相关的文档排到前面。

Query → 向量检索 Top-50 → 重排序 → Top-5 → LLM 生成

本文深入对比三种主流重排序方案。


1. 为什么需要重排序?

1.1 双塔 vs 交叉编码器

特性双塔模型(Bi-Encoder)交叉编码器(Cross-Encoder)
架构Query 和 Doc 独立编码Query 和 Doc 拼接后联合编码
交互无(仅在最后做余弦相似度)全程(Attention 层交互)
精度
速度极快(可预计算索引)慢(每对 Q-D 需独立前向)
用途初筛召回精排

1.2 RAG 中的两阶段检索

# 两阶段检索流程
def two_stage_retrieve(
    query: str,
    vector_index,          # 双塔向量索引
    reranker,              # 交叉编码器重排序
    first_stage_k: int = 50,   # 初筛数量
    final_k: int = 5,          # 最终数量
) -> list:
    # Stage 1: 向量检索(快,召回多)
    candidates = vector_index.search(query, top_k=first_stage_k)

    # Stage 2: 重排序(精,筛少)
    pairs = [(query, doc["content"]) for doc in candidates]
    scores = reranker.predict(pairs)

    # 排序并取 Top-K
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, score in ranked[:final_k]]

1.3 效果提升

指标无重排序有重排序提升
Top-1 准确率62.3%78.1%+15.8%
Top-5 召回率81.2%91.5%+10.3%
MRR0.6810.832+0.151

2. 方案一:Cohere Rerank API

2.1 概述

Cohere Rerank 是托管的商业重排序 API,基于自家训练的 rerank 模型,支持多语言。

模型语言上下文长度特点
rerank-multilingual-v3.0100+ 语言4096 tokens最新多语言版本
rerank-english-v3.0英语4096 tokens英文最优
rerank-v3.5多语言4096 tokens最新版,支持自定义

2.2 代码实现

import cohere

class CohereReranker:
    """Cohere Rerank 封装"""
    def __init__(self, api_key: str, model: str = "rerank-multilingual-v3.0"):
        self.client = cohere.Client(api_key)
        self.model = model

    def rerank(
        self,
        query: str,
        documents: list[str],
        top_n: int = 5,
        max_chunks_per_doc: int = None,
    ) -> list[dict]:
        """
        对文档列表重排序
        返回: [{"index": int, "relevance_score": float, "document": str}, ...]
        """
        response = self.client.rerank(
            model=self.model,
            query=query,
            documents=documents,
            top_n=top_n,
            max_chunks_per_doc=max_chunks_per_doc,
        )

        results = []
        for result in response.results:
            results.append({
                "index": result.index,
                "relevance_score": result.relevance_score,
                "document": documents[result.index],
            })
        return results


# 在 RAG 中使用
reranker = CohereReranker(api_key="your-api-key")

# 假设向量检索已返回 50 篇候选
candidates = vector_index.search(query, top_k=50)
doc_texts = [doc["content"] for doc in candidates]

# 重排序取 Top-5
reranked = reranker.rerank(query, doc_texts, top_n=5)
for r in reranked:
    print(f"Score: {r['relevance_score']:.4f} | {r['document'][:100]}...")

2.3 优缺点

优点缺点
零部署成本,API 即用按调用付费($2/1K searches)
多语言支持优秀数据需发送到云端(隐私)
4096 token 长文档支持依赖网络,延迟 ~200-500ms
持续更新模型不可定制微调

3. 方案二:BAAI bge-reranker

3.1 概述

BAAI(智源研究院)推出的 bge-reranker 是开源重排序模型中最受欢迎的系列:

模型参数量语言特点
bge-reranker-base278M中英文轻量,CPU 可用
bge-reranker-large560M中英文效果好,需 GPU
bge-reranker-v2-m3568M100+ 语言多语言最新版
bge-reranker-v2-gemma2B多语言基于 Gemma,效果最强
bge-reranker-v2-minicpm-layerwise2.7B多语言支持层级剪枝

3.2 代码实现

import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer

class BGEReranker:
    """bge-reranker 本地部署"""
    def __init__(
        self,
        model_name: str = "BAAI/bge-reranker-v2-m3",
        device: str = "cuda",
        max_length: int = 512,
        batch_size: int = 32,
    ):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSequenceClassification.from_pretrained(model_name)
        self.model.to(device)
        self.model.eval()
        self.device = device
        self.max_length = max_length
        self.batch_size = batch_size

    @torch.no_grad()
    def compute_scores(
        self,
        pairs: list[tuple[str, str]],  # [(query, doc), ...]
    ) -> list[float]:
        """计算每对 (query, doc) 的相关性分数"""
        all_scores = []

        for i in range(0, len(pairs), self.batch_size):
            batch = pairs[i:i + self.batch_size]
            texts = [(q, d) for q, d in batch]

            inputs = self.tokenizer(
                texts,
                padding=True,
                truncation=True,
                max_length=self.max_length,
                return_tensors="pt",
            ).to(self.device)

            scores = self.model(**inputs).logits.view(-1).float()
            all_scores.extend(scores.cpu().tolist())

        return all_scores

    def rerank(
        self,
        query: str,
        documents: list[str],
        top_n: int = 5,
    ) -> list[dict]:
        """重排序主入口"""
        pairs = [(query, doc) for doc in documents]
        scores = self.compute_scores(pairs)

        ranked = sorted(
            enumerate(scores),
            key=lambda x: x[1],
            reverse=True,
        )

        results = []
        for idx, score in ranked[:top_n]:
            results.append({
                "index": idx,
                "relevance_score": score,
                "document": documents[idx],
            })
        return results


# 使用
reranker = BGEReranker("BAAI/bge-reranker-v2-m3", device="cuda")
results = reranker.rerank(query, candidates, top_n=5)

3.3 bge-reranker-v2-gemma(大模型版)

# 基于 Gemma-2B 的重排序器,效果接近 Cohere
class BGEGemmaReranker:
    def __init__(self, model_name="BAAI/bge-reranker-v2-gemma"):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSequenceClassification.from_pretrained(
            model_name,
            torch_dtype=torch.bfloat16,
            device_map="auto",
        )
        self.model.eval()

    @torch.no_grad()
    def rerank(self, query, documents, top_n=5, max_length=8192):
        pairs = [(query, doc) for doc in documents]
        inputs = self.tokenizer(
            pairs,
            padding=True,
            truncation=True,
            max_length=max_length,
            return_tensors="pt",
        ).to(self.model.device)

        scores = self.model(**inputs).logits.view(-1).float()
        ranked = sorted(enumerate(scores.tolist()), key=lambda x: x[1], reverse=True)
        return [{"index": i, "score": s, "document": documents[i]} for i, s in ranked[:top_n]]

4. 方案三:自训练 Cross-Encoder

4.1 何时需要自训练

场景是否自训练
通用文档检索❌ 用预训练模型即可
领域特化(医疗/法律/金融)✅ 领域词汇差异大
多语言混合⚠️ 优先用 bge-multilingual
特殊排序逻辑(如时效性)✅ 需要自定义特征

4.2 训练数据构造

import json
import random
from typing import List, Tuple

def build_training_data(
    queries: List[str],
    corpus: List[str],
    positive_pairs: List[Tuple[int, int]],  # (query_idx, doc_idx) 正样本
    negative_ratio: int = 4,
) -> List[dict]:
    """
    构造重排序训练数据
    """
    data = []
    for q_idx, d_idx in positive_pairs:
        # 正样本
        data.append({
            "query": queries[q_idx],
            "document": corpus[d_idx],
            "label": 1,
        })
        # 负采样
        neg_indices = random.sample(
            [i for i in range(len(corpus)) if i != d_idx],
            negative_ratio,
        )
        for neg_idx in neg_indices:
            data.append({
                "query": queries[q_idx],
                "document": corpus[neg_idx],
                "label": 0,
            })
    return data

4.3 微调 Cross-Encoder

from transformers import (
    AutoModelForSequenceClassification,
    AutoTokenizer,
    Trainer,
    TrainingArguments,
)
from datasets import Dataset
import torch

def train_custom_reranker(
    train_data: List[dict],
    base_model: str = "BAAI/bge-reranker-base",
    output_dir: str = "./custom-reranker",
    epochs: int = 3,
    lr: float = 2e-5,
    batch_size: int = 16,
):
    """微调自定义重排序模型"""
    tokenizer = AutoTokenizer.from_pretrained(base_model)
    model = AutoModelForSequenceClassification.from_pretrained(
        base_model,
        num_labels=2,
    )

    # 编码数据
    def encode(examples):
        features = tokenizer(
            list(zip(examples["query"], examples["document"])),
            padding="max_length",
            truncation=True,
            max_length=512,
            return_tensors="pt",
        )
        features["labels"] = examples["label"]
        return features

    dataset = Dataset.from_list(train_data)
    dataset = dataset.map(encode, batched=True, remove_columns=dataset.column_names)

    training_args = TrainingArguments(
        output_dir=output_dir,
        num_train_epochs=epochs,
        per_device_train_batch_size=batch_size,
        learning_rate=lr,
        warmup_ratio=0.1,
        weight_decay=0.01,
        save_strategy="epoch",
        evaluation_strategy="no",
        fp16=True,
    )

    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=dataset,
        tokenizer=tokenizer,
    )

    trainer.train()
    model.save_pretrained(output_dir)
    tokenizer.save_pretrained(output_dir)
    return model

5. 三方案对比

5.1 效果对比(MS MARCO Passage Ranking)

方案MRR@10nDCG@10Recall@10Top-1 准确率
无重排序(双塔)0.3410.4120.68342.1%
Cohere Rerank v3.00.4210.4870.79158.3%
bge-reranker-base0.3980.4610.75253.1%
bge-reranker-large0.4150.4780.77856.8%
bge-reranker-v2-m30.4230.4910.79559.2%
bge-reranker-v2-gemma0.4310.4980.80361.5%
自训练 (bge-base + 领域数据)0.4450.5120.81263.8%

5.2 延迟对比(50 篇候选文档)

方案硬件延迟(p50)延迟(p99)吞吐量
Cohere API云端280ms650ms~3.5 QPS
bge-reranker-baseCPU (8核)420ms850ms~2.4 QPS
bge-reranker-baseGPU (T4)45ms85ms~22 QPS
bge-reranker-largeGPU (T4)78ms140ms~13 QPS
bge-reranker-v2-m3GPU (T4)82ms150ms~12 QPS
bge-reranker-v2-gemmaGPU (A10)180ms320ms~5.5 QPS
自训练 (bge-base)GPU (T4)48ms90ms~21 QPS

5.3 成本对比

方案初始成本运行成本/月 (10K QPS)适合规模
Cohere API$0~$4,300小-中规模
bge-reranker-base (GPU)~$1,500 (T4)~$200 (电费+机器)中-大规模
bge-reranker-v2-gemma (GPU)~$5,000 (A10)~$500大规模
自训练模型~$2,000 (训练+GPU)~$200大规模(有领域数据)

6. 进阶:层级重排序

def hierarchical_rerank(
    query: str,
    candidates: list[dict],
    fast_reranker,          # bge-reranker-base(快速粗排)
    strong_reranker,        # bge-reranker-v2-gemma(精排)
    fast_top_n: int = 20,   # 粗排保留数量
    final_top_n: int = 5,   # 精排保留数量
) -> list[dict]:
    """
    两级重排序:先快排筛减,再强排精选
    """
    # Stage 1: 快速重排序(粗排)
    docs = [c["content"] for c in candidates]
    fast_results = fast_reranker.rerank(query, docs, top_n=fast_top_n)

    # Stage 2: 强力重排序(精排)
    refined_docs = [r["document"] for r in fast_results]
    final_results = strong_reranker.rerank(query, refined_docs, top_n=final_top_n)

    # 映射回原始索引
    return final_results

层级重排序可在保持精度的同时将总延迟降低 40-60%。


7. 部署最佳实践

7.1 批处理优化

# 动态批处理:将多个 query 的候选合并处理
class BatchRerankServer:
    def __init__(self, reranker, max_batch_size=64, max_wait_ms=50):
        self.reranker = reranker
        self.max_batch_size = max_batch_size
        self.max_wait_ms = max_wait_ms
        self.queue = []

    async def rerank(self, query, documents, top_n=5):
        # 入队等待批处理
        self.queue.append((query, documents, top_n))
        # ... 批处理逻辑

7.2 缓存

from functools import lru_cache
import hashlib

@lru_cache(maxsize=10000)
def cached_rerank(query: str, doc_hash: str, docs_tuple: tuple) -> tuple:
    """LRU 缓存重排序结果"""
    return tuple(reranker.rerank(query, list(docs_tuple)))

# 使用时计算文档 hash 作为缓存 key
def rerank_with_cache(query, documents):
    doc_hash = hashlib.md5(
        "".join(documents).encode()
    ).hexdigest()
    return cached_rerank(query, doc_hash, tuple(documents))

7.3 ONNX 加速

# 将 bge-reranker 导出为 ONNX 格式,推理速度提升 1.5-3x
def export_to_onnx(model, tokenizer, output_path):
    model.eval()
    dummy_input = tokenizer(
        ["hello"], ["world"],
        padding=True, truncation=True,
        return_tensors="pt"
    )
    torch.onnx.export(
        model,
        (dummy_input["input_ids"], dummy_input["attention_mask"]),
        output_path,
        opset_version=14,
        input_names=["input_ids", "attention_mask"],
        output_names=["logits"],
        dynamic_axes={
            "input_ids": {0: "batch", 1: "seq"},
            "attention_mask": {0: "batch", 1: "seq"},
        },
    )

8. 选型决策树

数据隐私要求高?
├── 是 → 不能用 Cohere API
│   ├── 有 GPU → bge-reranker-v2-m3
│   └── 仅 CPU → bge-reranker-base
└── 否 → 可以用 Cohere
    ├── 预算充足 + 追求最高质量 → Cohere Rerank v3.5
    └── 预算有限 → bge-reranker(本地部署)

领域差异大?
├── 是 → bge-reranker-base 微调(自训练)
└── 否 → 直接用预训练模型

延迟要求?
├── <100ms → bge-reranker-base (GPU) 或层级重排序
├── 100-500ms → bge-reranker-large / Cohere API
└── >500ms 可接受 → bge-reranker-v2-gemma

总结

方案推荐场景一句话总结
Cohere Rerank快速验证、小规模、多语言最省心的方案,API 一行搞定
bge-reranker-v2-m3中大规模、中英文、本地部署开源最佳平衡点
bge-reranker-v2-gemma追求最高质量、有 A10+ GPU开源效果最强
自训练 Cross-Encoder领域特化、有标注数据领域数据 + 预训练 = 最优效果

核心建议:RAG 系统中必须引入重排序环节。即使是最轻量的 bge-reranker-base,也能带来 15%+ 的准确率提升。先用 bge-reranker-base 起步,根据效果和延迟逐步升级。


相关阅读:RAG 分块策略、高级 RAG 模式、GraphRAG 解析

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。