想象你是一档选秀节目的总导演,十万名练习生报名参加《明日之星》。如果让每个人都进录音棚完整唱一首歌,预算会瞬间爆炸;但如果只凭走廊里三十秒的清唱就定冠军,又太草率。聪明的节目组会把比赛拆成三场:海选时副导演在走廊快速听音,只求“别把真正有潜力的人漏掉”;复赛时晋级者进棚完整演唱,导师按音准、情感、台风快速打分;决赛时最后几个人站上舞台,顶级制作人逐句打磨,排出最终名次。每一场的目标不同、成本不同、评判的精细度也不同——这就是“漏斗式晋级”的智慧。 RAG(检索增强生成)系统为用户的提问找答案文档,和这档选秀如出一辙。知识库里可能躺着上百万篇文档,而大模型每生成一次回答,只看得见送入上下文的那三五篇。为了把最相关的几篇送进去,我们需要“多阶段排序”:先用最轻量的方式快速捞回一大批候选,再逐步用更精细的模型缩小范围、排出名次。这是一场用“层级”换“性价比”的优雅设计,也是本文的主角。

岗位关联

掌握多阶段排序,你就握住了 RAG 系统的“成本-效果”天平:在有限算力预算下,把最相关的文档送进大模型的视野——这是每个检索系统工程师的必修课。

目录
  1. 可视化演示
  2. 漏斗排序原理
  3. 召回海选
  4. 粗排复赛
  5. 精排决赛
  6. 模型选型
  7. 阈值设定
  8. 性能指标
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示多阶段排序的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

多阶段排序:多阶段排 多阶段排序的漏斗架构与设计动机 召回阶段的模型选择与候选生成策 核心机制可视化 多阶段排序整体架构与关键组件

漏斗排序原理

假设你是《明日之星》的总导演,十万名练习生报名,预算却只够给一百个人完整的比赛体验。你会怎么选?聪明的做法绝不是让十万人同台竞技,而是把比赛拆成三场:走廊里三十秒清唱先海选一波,录音棚完整演唱再筛一轮,最后只让少数几个人站上决赛舞台。为什么?因为每一场的目标不同、成本不同、评判精细度也不同——海选看潜力,复赛看实力,决赛看综合表现。

RAG 系统面临的正是同样的困局:知识库里躺着上百万篇文档,而大模型每回答一个问题,只想看到最相关的三五篇。如果让重型排序模型扫描全库,算力成本是天文数字;如果只用轻量模型草草排序,答案质量又无法保证。多阶段排序把“百万里挑五”这个大任务,拆成“召回→粗排→精排”三个子任务,让每一层都只做自己最擅长的事。

召回层用最轻量的模型保证覆盖面,粗排层在几百个候选上做中等精度打分,精排层用最昂贵的交叉编码器在几十个候选里定名次。三层逐级收缩、层层递进,就像一个漏斗:上面口大,装得下所有可能性;下面口小,流出的都是精华。整个链路的成本可以写成:

就像节目组把 90% 的预算花在决赛十个人身上,而不是给十万人各发一个录音棚——多阶段排序的本质,是用“层级”换“性价比”。昂贵的算力,只留给最关键的那一小批。

Ctotal=cr×N+cc×Nc+cf×Nf\displaystyle C_{\text{total}} = c_r \times N + c_c \times N_c + c_f \times N_f
总成本 = 召回单价 × 全量文档数 + 粗排单价 × 粗排候选数 + 精排单价 × 精排候选数。其中规模逐级缩小,单价逐级上升。
  1. 全量规模$N$ 是所有待检索文档的总数,可能高达百万级,这是漏斗的第一道“宽口”。
    NN
  2. 召回单价$c_r$ 是召回阶段处理单篇文档的成本,BM25 或向量检索都非常便宜。
    crc_r
  3. 粗排与精排单价$c_c$、$c_f$ 分别代表粗排和精排处理单个候选的单价,后者可能是前者的几十倍。
    cc≪cfc_c \ll c_f
  4. 规模逐级收缩$N > N_c > N_f$,例如一百万 → 两百 → 三十,让昂贵模型面对的最小。
    N≫Nc≫NfN \gg N_c \gg N_f
  5. 对比反例若只用精排扫全库,成本是 $c_f \times N$,比漏斗高出几个数量级。
    C′=cf×NC' = c_f \times N

召回海选

海选现场,走廊里挤满十万名练习生,副导演每人只给三十秒,问三个问题:“会唱吗?有辨识度吗?台风稳吗?”这轮筛选非常粗糙——紧张到破音的天才可能被放走,但当过主播的老油条也可能混进来。不过没关系,只要大方向没跑偏,复赛和决赛还能把真正的高手捞回来。

召回阶段在 RAG 里扮演的正是海选角色。它的 KPI 不是“排得准”,而是“别漏掉”。常见的召回器有三种:BM25 基于词频做倒排检索,快、可解释、不需要训练;稠密向量检索用双塔编码器把 query 和 doc 都编码成向量,再用内积或余弦找语义近邻,能理解“同义改写”这类语义匹配;混合检索把两者结果取并集,兼顾词汇匹配与语义理解。召回的目标是找出 100~200 个候选,让真正的相关文档几乎都躺在里面。

召回质量好不好,用覆盖率来衡量:

海选宁可多放一百个平庸者进复赛,也不能放走未来的冠军。召回正是这种“宁滥勿缺”的哲学——它决定了整个漏斗的天花板。

Recall@K=∣相关文档∩前K个结果∣∣全部相关文档∣\displaystyle \text{Recall@K} = \frac{|\text{相关文档} \cap \text{前K个结果}|}{|\text{全部相关文档}|}
前 K 个结果覆盖了多少比例的相关文档。它不关心排序先后,只关心“有没有漏”。
  1. 候选规模$K$ 是这一层保留的候选文档数,例如召回 Top200。
    K=200K = 200
  2. 命中交集分子统计“真正相关的文档”里,有多少篇出现在前 K 个结果中。
    ∣相关文档∩前K个结果∣|\text{相关文档} \cap \text{前}K\text{个结果}|
  3. 相关总数分母是知识库里全部相关文档的数量,通常由人工标注或权威答案确定。
    ∣全部相关文档∣|\text{全部相关文档}|
  4. 目标形态当分子完全覆盖分母时 Recall = 1,此时允许候选里夹带大量噪声也没关系。
    Recall@K→1\text{Recall@K} \to 1

粗排复赛

复赛在录音棚里进行,上千名晋级的练习生每人完整唱一首歌,三位导师按音准、情感、舞台感快速打分。这轮的成本比海选高出一截,但比决赛低得多;它的任务不是挑出冠军,而是把明显平庸的人筛掉,让决赛舞台只站实力派。

粗排夹在召回与精排之间,输入几百个候选,输出几十个。它最常用的模型是双塔结构(Dual Encoder):query 编码成一个向量,doc 编码成另一个向量,两者做内积得到相关分。关键技巧在于,doc 向量可以离线批量预计算并缓存,线上只需要编码一次 query,再对几百个向量做一次批量矩阵运算,几毫秒就能完成全部打分。

双塔打分本质上是在比较两个向量的方向一致性:

复赛评委能快速听出“音准跑偏”,但判断不了“这个选手三年后能不能成为巨星”——那是决赛制作人的活儿。粗排同样如此:它擅长快速淘汰明显不相关的候选,却难以捕捉一词多义、指代消解这类深层语义,这正是决赛要解决的问题。

s(q,d)=uqTvd\displaystyle s(q, d) = \mathbf{u}_q^{\mathsf{T}} \mathbf{v}_d
查询向量与文档向量的内积。内积越大,说明两者在语义空间中越“同向”,相关度越高。
  1. 查询向量$\mathbf{u}_q$ 是 query 经过编码器得到的向量表示,线上实时计算。
    uq\mathbf{u}_q
  2. 文档向量$\mathbf{v}_d$ 是每篇文档预计算好的向量,离线缓存,线上直接读取。
    vd\mathbf{v}_d
  3. 内积运算$\mathbf{u}_q^{\mathsf{T}} \mathbf{v}_d$ 把两个向量逐维相乘再求和,数学上等价于衡量“同向程度”。
    uqTvd\mathbf{u}_q^{\mathsf{T}} \mathbf{v}_d
  4. 高效原因没有 token 级交互,只有纯向量的批量矩阵乘法,所以吞吐极高。
    O(d) per pairO(d) \text{ per pair}

精排决赛

决赛舞台上只剩下三个人。顶级制作人终于可以逐句打磨:这一句的气息该不该断?尾音收在哪里最有感染力?哪个眼神能让观众记住她?一对一深度交流得出的结论,远比海选的三十秒清唱可靠——当然,成本也贵得多。

精排(Rerank)用的正是这种“深度交流”:交叉编码器(Cross-Encoder)不再把 query 和 doc 分开编码,而是把两者拼接成一句话 [CLS] query [SEP] doc [SEP] 一起送入 Transformer。由于注意力机制,query 的每个 token 都能“看见”文档的每个 token,语义交互发生在最细粒度,因此相关度判断最精准。它的代价是复杂度随输入长度呈平方级增长,所以只能用在几十个候选的小舞台上。

交叉编码器的打分可以表示为:

决赛制作人的时间只够给三个人,但正因为花在了刀刃上,排出名单才让人信服。精排输出最终 TopN(通常 3~10 个)直接送入大模型上下文——这就是用户真正看到的那份“冠军榜单”。

score(q,d)=W h[CLS]\displaystyle \text{score}(q, d) = \mathbf{W}\, \mathbf{h}_{[CLS]}
取拼接序列首位的 CLS 向量,经过一个线性层映射成标量相关分。这个分数就是决赛排名依据。
  1. CLS 向量$\mathbf{h}_{[CLS]}$ 是模型读完"query + doc"整句话后,首位令牌的上下文向量,浓缩了全部交互信息。
    h[CLS]\mathbf{h}_{[CLS]}
  2. 打分矩阵$\mathbf{W}$ 是一个可学习的线性层,把高维向量压缩成一个标量。
    W\mathbf{W}
  3. 相关分数score 越大代表越相关,精排层据此对全体候选排序。
    score(q,d)∈R\text{score}(q, d) \in \mathbb{R}
  4. 与粗排的本质区别粗排是两个向量“互不相见”,精排是每个 token 都深度互动。
    双塔:s=uqTvdvs交叉:s=W h[CLS]\text{双塔:} s = \mathbf{u}_q^{\mathsf{T}}\mathbf{v}_d \quad \text{vs} \quad \text{交叉:} s = \mathbf{W}\,\mathbf{h}_{[CLS]}

模型选型

海选、复赛、决赛的评委配置完全不同:海选是实习导演,便宜但眼光粗;复赛是声乐老师,专业且价格适中;决赛是顶级制作人,昂贵却判断精准。给每个赛段配错评委,要么浪费预算,要么埋没人才。

模型选型遵循同样的逻辑。召回层优先选便宜、快的模型:BM25、Sentence-BERT 双塔或它们的混合,目标是覆盖面,精度够用就行。粗排层选中等精度的双塔模型(如 MiniLM 双塔、GTE 系列),在百级候选上快速排序。精排层选高精度的交叉编码器(如 bge-reranker、Cohere Rerank 系列),在十级候选上精雕细琢。

选型时要同时看四个维度:单位成本(单文档打分价格)、单文档耗时、精度上限、可缓存性。一个典型的配置是“召回 100 + 粗排 30 + 精排 8”,端到端延迟可以压在 200 毫秒以内;而如果直接用精排去扫 100 个候选,延迟可能翻三五倍。实践中还有一种“两段式”漏斗:当召回后候选本身就小于 50 个时,直接砍掉粗排、召回后立即精排,省去一层开销,适合对延迟极其敏感的实时搜索场景。

成熟的节目组手里有一张人员预算表:赛段越小、评委越贵,总成本反而越低。模型选型的精髓也一样——把最强的模型留给最小的候选集,让每一分算力都花在决定性环节上。

阈值设定

复赛要淘汰多少人?决赛留几个?导演组必须拍板“晋级线”。线划得太高,可能误杀潜力股,将来被观众骂“遗珠之憾”;线划得太低,决赛站满平庸者,制作人忙不过来,内容也注水。阈值就是漏斗各层的开口大小,直接决定整场秀的水位。

设置各层 TopK 时要平衡四个因素:相关文档密度——知识库里真正相关的文档通常只有寥寥几篇,TopK 设太大只会灌入噪声;模型能力上限——精排模型能同时“看清”的候选有限,候选太多时精度下降且成本飙升;下游容忍度——LLM 上下文窗口有限,送入的 TopN 通常只有 3~10 个;延迟预算——每一层耗时累加必须低于服务的 SLA 红线。

阈值不能拍脑袋,要在验证集上找“收益曲线拐点”:横轴是 TopK,纵轴是检索质量(如 nDCG)。随着 K 增大,质量提升会从陡峭变平缓,拐点之后每多塞一个文档,收益微乎其微、成本却线性上涨——这个拐点就是最优晋级线。更进阶的做法是动态调整:当粗排后分数出现断崖式下跌,说明高质量候选已经很集中,可以主动把精排输入从 30 缩到 15;当分数分布密集难分伯仲,则适当放宽,避免误杀。

晋级线定得聪明,节目既不水也不贵;定错了,要么决赛舞台人满为患,要么年年上演“遗珠之憾”。阈值设定的本质,是用数据找到“多花一分钱是否还值得”的分界点。

性能指标

选秀季结束,怎么评价这届办得好不好?观众看收视率,粉丝看冠军的含金量,行业看有没有被淘汰的选手后来爆红——“遗珠率”才是检验海选的试金石。排序系统同样需要多维度的尺子,不能只看一块表。

离线评估常用的三把尺子各有分工。Recall@K 衡量覆盖率,回答“相关文档有没有被捞进来”,主要盯召回层;MRR 衡量首个相关结果的位置,回答“用户是不是第一眼就看到答案”,适合只有一个标准答案的问答场景;nDCG@K 衡量整条列表的多级排序质量,回答“高度相关的文档是不是排在了最前面”,对相关度分级标注最敏感。在线侧还要盯端到端延迟、吞吐、单查询成本,以及大模型回答的忠实度。

MRR 的数学定义非常直观:

而 nDCG@K 则更细腻,它对多级相关度做了指数放大和位置折扣:

就像选秀节目要同时盯住“冠军含金量、遗珠率、制作成本”三本账,RAG 排序也要看齐“覆盖率、首位命中率、整体排序质量、延迟与成本”。只盯一个指标,就像只看收视率定节目好坏——迟早翻车。

MRR=1Q∑q=1Q1rankq\displaystyle \text{MRR} = \frac{1}{Q}\sum_{q=1}^{Q}\frac{1}{\text{rank}_q}
分子是当前排序的折损累计增益(DCG),分母是理想排序下的最大可能值(IDCG),两者相除得到 0~1 的归一化得分。
  1. 相关度等级$\text{rel}_i$ 是第 $i$ 位结果的人工标注相关度(如 0~3 分级)。
    reli\text{rel}_i
  2. 指数放大$2^{\text{rel}_i}-1$ 让“高度相关”的收益压倒“有点相关”,例如 rel=3 得 7 分,rel=1 只得 1 分。
    2reli−12^{\text{rel}_i}-1
  3. 位置折扣$\log_2(i+1)$ 对靠后的位置打折,第 4 位的贡献只有第 1 位的约三分之一。
    log⁡2(i+1)\log_2(i+1)
  4. 理想归一化分母 $\text{rel}_i^{*}$ 是理想排序下的相关度序列,DCG 除以 IDCG 后越接近 1 说明越接近完美排序。
    nDCG@K=DCGIDCG\text{nDCG@K} = \frac{\text{DCG}}{\text{IDCG}}

AI 应用场景

企业知识库智能问答

阶段:RAG 检索与精排

解决的问题:企业知识库动辄几十万篇制度文档,传统单模型方案要么慢得没法上线,要么漏掉真正相关的文档。多阶段排序把召回、粗排、精排串成漏斗,在 200 毫秒级延迟内锁定最相关文档,兼顾准确率与算力成本。

具体案例:某银行客服知识库有 50 万篇制度文件。用户问“年假没休完怎么办”,系统先用 BM25+向量混合召回 200 篇,粗排双塔筛到 30 篇,精排 reranker 排出 Top 5 交给大模型。上线后答案准确率提升 23%,单次检索成本下降 60%。

电商商品搜索排序

阶段:召回 + 精排

解决的问题:纯 BM25 搜“牛奶”会把标题含“牛奶饼干”的商品顶上来;纯语义模型全库打分又太慢。多阶段排序让召回层广撒网抓住语义相关的商品,精排层再综合销量、品牌、相关性排出商业价值最高的结果。

具体案例:某电商平台搜索“无糖酸奶”,召回层用双塔模型捞回 500 个语义相关商品,精排层用交叉编码器融合品牌力和销量信号排出 Top 20 展示,搜索转化率(GMV)提升 12%。

学术论文检索助手

阶段:召回 → 粗排 → 精排全链路

解决的问题:arXiv 上有 300 万篇论文,科研助手要从中找出与课题最相关的文献。单靠引文网络或关键词容易漏掉跨领域工作,全库跑交叉编码器又不现实。

具体案例:某科研助手把“多阶段排序在 RAG 中的应用”作为查询,先用 BM25+引文网络海选 200 篇,再用 SPECTER 向量粗排筛到 50 篇,最后用 cross-encoder 精排 Top 10 推荐给用户。相比单模型方案,推荐精确度显著更高,且日均支撑百万级查询。

Python 演示

python85 行
import mathimport random  class MultiStageRanker:    """三段式漏斗排序:召回 -> 粗排 -> 精排"""     def __init__(self, total=1000, recall_k=100, coarse_k=30, final_k=5,                 num_queries=20, seed=42):        self.total = total        self.recall_k = recall_k        self.coarse_k = coarse_k        self.final_k = final_k        rng = random.Random(seed)        # 为每个查询生成真实相关度(ground truth),并标出相关文档        self.true_map = {}        self.gold_map = {}        for q in range(num_queries):            rel = {d: rng.random() for d in range(total)}            gold = sorted(rel, key=rel.get, reverse=True)[:5]            self.true_map[q] = rel            self.gold_map[q] = set(gold)        # 不同阶段引入不同程度噪声,模拟"轻量模型更不准"        self.noise = {stage: {d: rng.gauss(0, sigma) for d in range(total)}                      for stage, sigma in [("recall", 0.35), ("coarse", 0.12), ("fine", 0.03)]}     def recall(self, q):        """海选:轻量打分,快速捞回 recall_k 个候选"""        scored = [(d, self.true_map[q][d] + self.noise["recall"][d])                  for d in range(self.total)]        scored.sort(key=lambda x: x[1], reverse=True)        return [d for d, _ in scored[:self.recall_k]]     def coarse_rank(self, q, cands):        """复赛:中等精度打分,筛出 coarse_k 个"""        scored = [(d, self.true_map[q][d] + self.noise["coarse"][d])                  for d in cands]        scored.sort(key=lambda x: x[1], reverse=True)        return [d for d, _ in scored[:self.coarse_k]]     def fine_rank(self, q, cands):        """决赛:高精度精排,返回最终有序列表"""        scored = [(d, self.true_map[q][d] + self.noise["fine"][d])                  for d in cands]        scored.sort(key=lambda x: x[1], reverse=True)        return scored[:self.final_k]     def search(self, q):        """完整漏斗:召回 -> 粗排 -> 精排"""        cands = self.recall(q)        cands = self.coarse_rank(q, cands)        return [d for d, _ in self.fine_rank(q, cands)]     def evaluate(self, k=5):        """用 Recall@k、MRR@k、nDCG@k 评估漏斗整体质量"""        recalls, mrr_list, ndcg_list = [], [], []        for q, gold in self.gold_map.items():            ranked = self.search(q)[:k]            rel = self.true_map[q]            hit = [d for d in ranked if d in gold]            recalls.append(len(hit) / len(gold))            for pos, d in enumerate(ranked):                if d in gold:                    mrr_list.append(1.0 / (pos + 1))                    break            else:                mrr_list.append(0.0)            dcg = sum((2 ** rel[d] - 1) / math.log2(pos + 2)                      for pos, d in enumerate(ranked))            best = sorted(gold, key=rel.get, reverse=True)            idcg = sum((2 ** rel[d] - 1) / math.log2(pos + 2)                       for pos, d in enumerate(best))            ndcg_list.append(dcg / idcg if idcg else 0.0)        return {"Recall@5": sum(recalls) / len(recalls),                "MRR@5": sum(mrr_list) / len(mrr_list),                "nDCG@5": sum(ndcg_list) / len(ndcg_list)}  if __name__ == "__main__":    ranker = MultiStageRanker()    top = ranker.search(0)    metrics = ranker.evaluate()    print("最终 Top 文档: ", top)    for name, value in metrics.items():        print("{}: {:.3f}".format(name, value))
目的:用合成数据模拟一个 1000 篇文档的知识库,完整演示“召回→粗排→精排”三级漏斗,并输出 Recall@5、MRR@5、nDCG@5 三个核心指标,验证漏斗策略的排序质量。
  1. self.noise = {stage: {d: rng.gauss(0, sigma) ...}}为每层注入不同强度的随机噪声,模拟“召回模型最糙、精排模型最准”的现实差异。
  2. def recall(self, q)第一阶段海选:对全量 1000 篇文档做轻量打分,快速捞回 100 个候选。
  3. scored.sort(key=lambda x: x[1], reverse=True)按预估分数降序排列,是整个排序链路最核心的操作。
  4. return [d for d, _ in scored[:self.recall_k]]只保留前 K 个候选,把规模从全量压缩到百级。
  5. gold = sorted(rel, key=rel.get, reverse=True)[:5]取真实相关度最高的 5 篇作为“标准答案”,用于后续评估。
  6. mrr_list.append(1.0 / (pos + 1))第一个相关文档排得越靠前,MRR 贡献越大,第 1 名得满分。
  7. dcg = sum((2 ** rel[d] - 1) / math.log2(pos + 2) ...)nDCG 的分子:相关度指数放大、位置对数打折。
  8. if __name__ == "__main__":可执行入口,直接运行即可看到三个指标的具体数值。

常见误区

误区召回嘛,就是快速捞一批候选的预处理环节,选个最快的模型对付一下,后面精排能力强,肯定能把好文档排到前面。

正解召回决定了整个漏斗的天花板——如果相关文档压根没进候选集,后面粗排精排再强也无能为力。就像海选虽然粗,但若导演在海选就把未来的冠军淘汰了,决赛再专业也救不回来。召回看的是“不可漏”,粗排精排看的是“排得准”,两者职责不同,同样关键。

为何常见:因为召回单点看误差很大、效果不明显,人们容易把它当成“丢垃圾”的预处理,而忽略了候选集的完整度才是整个检索系统的上限。

误区知识库也就几十万篇,精排模型再慢也能扫完,搞三层漏斗不是多此一举吗?

正解交叉编码器对每个候选都要把 query 和 doc 拼起来做完整的 Transformer 前向,扫描 10 万篇文档就是 10 万次深度计算,延迟和算力成本直接爆炸。漏斗的价值恰恰是把昂贵的计算压缩到最小的候选集上。就像不能让制作人给十万选手逐句打磨——那不是专业,是破产。

为何常见:只看到了精排的精度优势,没有算“延迟×数量×单次成本”的总账,忽略了算力预算这个硬约束。

误区精排完多返回几个文档,反正大模型上下文窗口挺大,塞得下,多给点资料不会错吧?

正解大模型上下文虽然能容纳长文本,但塞入大量低相关文档会稀释注意力、引入噪声,token 变多还意味着费用上涨、回答变慢变啰嗦。正确做法是在验证集上画“TopN vs 质量”曲线,找到收益变平缓的拐点,通常 3~10 个就够用。

为何常见:人们对“多”有一种朴素的安全感,忽略了信息噪声和 token 成本的双重损耗。

误区只要返回的结果是相关的,排序模型就算合格,准确率高就是好系统。

正解RAG 场景里用户只看到 Top 5,“第一个相关结果排在第几位”(MRR)、“多级相关度下整体排序是否合理”(nDCG)、“相关文档有没有被覆盖”(Recall)比单纯的准确率更贴近真实体验;此外还要看时延、吞吐和单查询成本,否则模型再准,上线也扛不住流量。

为何常见:初学者习惯用分类任务的准确率思维惯性去理解排序任务,没有意识到“排序质量”是多维的、与用户感知强相关。

面试题

Q1. 多阶段排序最核心的设计思想是什么?

单选题 · easy

  • A.用超强模型一次扫全库提精度
  • B.用漏斗逐级缩小候选求平衡← 正确答案
  • C.把所有文档分桶并行来处理
  • D.让大模型直接逐篇打分选优

答案:B。多阶段排序的核心是“先广后精”的漏斗策略:召回层用轻量模型保证覆盖面,粗排/精排层在缩小的候选集上用更精细的模型排序,从而在效果与算力成本之间取得平衡。其余选项要么成本失控,要么只是并行优化,都没有体现“逐级收缩”的思想。

Q2. 召回阶段最应该关注哪个指标?

单选题 · mid

  • A.MRR:首个相关结果排序位置
  • B.nDCG:整体排序质量指标
  • C.Recall@K:相关文档覆盖比例← 正确答案
  • D.点击率:用户行为指标

答案:C。召回的目标是“别漏掉”,所以最该盯 Recall@K。MRR 和 nDCG 衡量的是排序精度,那是粗排和精排的任务;点击率是在线业务指标,与召回覆盖率不在一个层面。选错指标就像海选时非要考核“台风细节”,方向就错了。

Q3. 下列哪些是粗排阶段的典型配置?(多选)

多选题 · easy

  • A.BM25 词频倒排检索
  • B.双塔编码向量内积打分← 正确答案
  • C.交叉编码逐词精细打分
  • D.批量矩阵运算快速排序← 正确答案

答案:BD。粗排要“中等精度×高吞吐”,双塔向量内积配合批量矩阵运算是标配;BM25 属于召回层的轻量工具;交叉编码器属于精排层,精度虽高但太慢,不适合在百级候选上大量使用。就像复赛评委可以快速打分,但不会给每个选手做三小时深度访谈。

Q4. 精排阶段通常使用交叉编码器,主要原因是?

单选题 · mid

  • A.能做词级双向交互判断最准← 正确答案
  • B.推理速度最快适合全库扫
  • C.无需训练可直接拿来就用
  • D.不限制输入输出长度大小

答案:A。交叉编码器把 query 和 doc 拼接成一句,让每个 token 通过注意力机制两两交互,能捕捉一词多义、隐式指代等深层语义,精度最高。代价是速度慢,所以只能在精排的小候选集上用。选项 B、C、D 都与交叉编码器的实际特性相反。

Q5. 在漏斗成本公式 C = cr×N + cc×Nc + cf×Nf 中,总成本可控的关键是什么?

单选题 · mid

  • A.召回单价比精排更贵更耗钱
  • B.规模逐级缩至昂贵模型小集← 正确答案
  • C.三个阶段成本相近相互抵消
  • D.大模型不参与排序没有成本

答案:B。漏斗的精髓是让单价最低的召回模型面对最大的 N,让单价最高的精排模型只面对最小的 Nf。若用精排直接扫全库,成本是 cf×N,比漏斗高出几个数量级。就像节目组把最贵的制作人时间只留给决赛三个人。

Q6. 多阶段排序相比单模型直接排序的优点有哪些?(多选)

多选题 · easy

  • A.降低端到端延迟与算力开销← 正确答案
  • B.各阶段独立优化灵活替换← 正确答案
  • C.保证所有相关文档排首位
  • D.精度成本搭配更均衡可控← 正确答案

答案:ABD。漏斗结构把高成本计算压缩到最后一层,端到端延迟与算力开销显著下降;各阶段解耦,可以独立换模型、调参数;不同阶段配置不同精度模型,效果与成本更均衡。但它并不保证所有相关文档排首位——召回一旦漏掉,精排也无能为力。

Q7. 某系统召回后候选只有约 30 个,部署多阶段排序最合理的是?

单选题 · hard

  • A.保留完整两端缺一不可
  • B.去掉粗排直接精排处理← 正确答案
  • C.去掉精排用召回直接出
  • D.扩大召回量完善漏斗

答案:B。漏斗的层数是按候选规模设计的。当候选只有 30 个时,粗排省下的计算已经微不足道,直接让精排处理更利落,这就是“两段式”漏斗。如果候选只有 30 个还硬凹三层,等于复赛只有三个人,纯属浪费。层数服务于规模与预算,而非越多越好。

Q8. 关于 nDCG@K 的说法,正确的是?

单选题 · mid

  • A.只关心首个相关结果位置
  • B.指数放大相关度并位置折扣← 正确答案
  • C.数值越大召回覆盖越完整
  • D.归一化无需理想排序参考

答案:B。nDCG 用 2^rel - 1 指数放大高度相关文档的收益,用 log2(i+1) 对靠后位置打折,再除以 IDCG 归一化,衡量整条列表的多级排序质量。只关心首个相关结果位置是 MRR 的职责;不归一化则无法跨查询比较。

Q9. 设置精排 TopN 时应综合考虑哪些因素?(多选)

多选题 · hard

  • A.大模型上下文窗口长度上限← 正确答案
  • B.验证集收益曲线拐点位置← 正确答案
  • C.单查询延迟与令牌成本预算← 正确答案
  • D.文档库存量决定输出级数

答案:ABC。TopN 决定多少文档进入大模型上下文,窗口有限所以有硬上限;收益曲线拐点告诉我们加文档是否还有边际收益;token 成本随 TopN 线性上涨,延迟也会变长。文档库存量影响的是召回层规模,而不是精排输出数量——这两个概念常被混淆。

Q10. 在“召回-粗排-精排”链路中发现答案质量差,但延迟预算仍有富余,最应优先排查哪里?

单选题 · hard

  • A.精排返回数量过大噪声多
  • B.召回阶段漏掉真正的相关文档← 正确答案
  • C.粗排模型向量维度不够
  • D.大模型推理层数需要增加

答案:B。漏斗的天花板在召回——如果相关文档连候选集都没进,粗排精排再准也无力回天。排查顺序应是:先验证召回覆盖率,再调粗排阈值,再调精排 TopN 与模型,最后才怀疑大模型本身。就像选秀出了问题,先查海选有没有漏掉好苗子,而不是急着换决赛评委。