想象你是一档选秀节目的总导演,十万名练习生报名参加《明日之星》。如果让每个人都进录音棚完整唱一首歌,预算会瞬间爆炸;但如果只凭走廊里三十秒的清唱就定冠军,又太草率。聪明的节目组会把比赛拆成三场:海选时副导演在走廊快速听音,只求“别把真正有潜力的人漏掉”;复赛时晋级者进棚完整演唱,导师按音准、情感、台风快速打分;决赛时最后几个人站上舞台,顶级制作人逐句打磨,排出最终名次。每一场的目标不同、成本不同、评判的精细度也不同——这就是“漏斗式晋级”的智慧。 RAG(检索增强生成)系统为用户的提问找答案文档,和这档选秀如出一辙。知识库里可能躺着上百万篇文档,而大模型每生成一次回答,只看得见送入上下文的那三五篇。为了把最相关的几篇送进去,我们需要“多阶段排序”:先用最轻量的方式快速捞回一大批候选,再逐步用更精细的模型缩小范围、排出名次。这是一场用“层级”换“性价比”的优雅设计,也是本文的主角。
掌握多阶段排序,你就握住了 RAG 系统的“成本-效果”天平:在有限算力预算下,把最相关的文档送进大模型的视野——这是每个检索系统工程师的必修课。
可视化演示
动态演示多阶段排序的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
多阶段排序:多阶段排 多阶段排序的漏斗架构与设计动机 召回阶段的模型选择与候选生成策 核心机制可视化 多阶段排序整体架构与关键组件
漏斗排序原理
假设你是《明日之星》的总导演,十万名练习生报名,预算却只够给一百个人完整的比赛体验。你会怎么选?聪明的做法绝不是让十万人同台竞技,而是把比赛拆成三场:走廊里三十秒清唱先海选一波,录音棚完整演唱再筛一轮,最后只让少数几个人站上决赛舞台。为什么?因为每一场的目标不同、成本不同、评判精细度也不同——海选看潜力,复赛看实力,决赛看综合表现。
RAG 系统面临的正是同样的困局:知识库里躺着上百万篇文档,而大模型每回答一个问题,只想看到最相关的三五篇。如果让重型排序模型扫描全库,算力成本是天文数字;如果只用轻量模型草草排序,答案质量又无法保证。多阶段排序把“百万里挑五”这个大任务,拆成“召回→粗排→精排”三个子任务,让每一层都只做自己最擅长的事。
召回层用最轻量的模型保证覆盖面,粗排层在几百个候选上做中等精度打分,精排层用最昂贵的交叉编码器在几十个候选里定名次。三层逐级收缩、层层递进,就像一个漏斗:上面口大,装得下所有可能性;下面口小,流出的都是精华。整个链路的成本可以写成:
就像节目组把 90% 的预算花在决赛十个人身上,而不是给十万人各发一个录音棚——多阶段排序的本质,是用“层级”换“性价比”。昂贵的算力,只留给最关键的那一小批。
- 全量规模$N$ 是所有待检索文档的总数,可能高达百万级,这是漏斗的第一道“宽口”。
- 召回单价$c_r$ 是召回阶段处理单篇文档的成本,BM25 或向量检索都非常便宜。
- 粗排与精排单价$c_c$、$c_f$ 分别代表粗排和精排处理单个候选的单价,后者可能是前者的几十倍。
- 规模逐级收缩$N > N_c > N_f$,例如一百万 → 两百 → 三十,让昂贵模型面对的最小。
- 对比反例若只用精排扫全库,成本是 $c_f \times N$,比漏斗高出几个数量级。
召回海选
海选现场,走廊里挤满十万名练习生,副导演每人只给三十秒,问三个问题:“会唱吗?有辨识度吗?台风稳吗?”这轮筛选非常粗糙——紧张到破音的天才可能被放走,但当过主播的老油条也可能混进来。不过没关系,只要大方向没跑偏,复赛和决赛还能把真正的高手捞回来。
召回阶段在 RAG 里扮演的正是海选角色。它的 KPI 不是“排得准”,而是“别漏掉”。常见的召回器有三种:BM25 基于词频做倒排检索,快、可解释、不需要训练;稠密向量检索用双塔编码器把 query 和 doc 都编码成向量,再用内积或余弦找语义近邻,能理解“同义改写”这类语义匹配;混合检索把两者结果取并集,兼顾词汇匹配与语义理解。召回的目标是找出 100~200 个候选,让真正的相关文档几乎都躺在里面。
召回质量好不好,用覆盖率来衡量:
海选宁可多放一百个平庸者进复赛,也不能放走未来的冠军。召回正是这种“宁滥勿缺”的哲学——它决定了整个漏斗的天花板。
- 候选规模$K$ 是这一层保留的候选文档数,例如召回 Top200。
- 命中交集分子统计“真正相关的文档”里,有多少篇出现在前 K 个结果中。
- 相关总数分母是知识库里全部相关文档的数量,通常由人工标注或权威答案确定。
- 目标形态当分子完全覆盖分母时 Recall = 1,此时允许候选里夹带大量噪声也没关系。
粗排复赛
复赛在录音棚里进行,上千名晋级的练习生每人完整唱一首歌,三位导师按音准、情感、舞台感快速打分。这轮的成本比海选高出一截,但比决赛低得多;它的任务不是挑出冠军,而是把明显平庸的人筛掉,让决赛舞台只站实力派。
粗排夹在召回与精排之间,输入几百个候选,输出几十个。它最常用的模型是双塔结构(Dual Encoder):query 编码成一个向量,doc 编码成另一个向量,两者做内积得到相关分。关键技巧在于,doc 向量可以离线批量预计算并缓存,线上只需要编码一次 query,再对几百个向量做一次批量矩阵运算,几毫秒就能完成全部打分。
双塔打分本质上是在比较两个向量的方向一致性:
复赛评委能快速听出“音准跑偏”,但判断不了“这个选手三年后能不能成为巨星”——那是决赛制作人的活儿。粗排同样如此:它擅长快速淘汰明显不相关的候选,却难以捕捉一词多义、指代消解这类深层语义,这正是决赛要解决的问题。
- 查询向量$\mathbf{u}_q$ 是 query 经过编码器得到的向量表示,线上实时计算。
- 文档向量$\mathbf{v}_d$ 是每篇文档预计算好的向量,离线缓存,线上直接读取。
- 内积运算$\mathbf{u}_q^{\mathsf{T}} \mathbf{v}_d$ 把两个向量逐维相乘再求和,数学上等价于衡量“同向程度”。
- 高效原因没有 token 级交互,只有纯向量的批量矩阵乘法,所以吞吐极高。
精排决赛
决赛舞台上只剩下三个人。顶级制作人终于可以逐句打磨:这一句的气息该不该断?尾音收在哪里最有感染力?哪个眼神能让观众记住她?一对一深度交流得出的结论,远比海选的三十秒清唱可靠——当然,成本也贵得多。
精排(Rerank)用的正是这种“深度交流”:交叉编码器(Cross-Encoder)不再把 query 和 doc 分开编码,而是把两者拼接成一句话 [CLS] query [SEP] doc [SEP] 一起送入 Transformer。由于注意力机制,query 的每个 token 都能“看见”文档的每个 token,语义交互发生在最细粒度,因此相关度判断最精准。它的代价是复杂度随输入长度呈平方级增长,所以只能用在几十个候选的小舞台上。
交叉编码器的打分可以表示为:
决赛制作人的时间只够给三个人,但正因为花在了刀刃上,排出名单才让人信服。精排输出最终 TopN(通常 3~10 个)直接送入大模型上下文——这就是用户真正看到的那份“冠军榜单”。
- CLS 向量$\mathbf{h}_{[CLS]}$ 是模型读完"query + doc"整句话后,首位令牌的上下文向量,浓缩了全部交互信息。
- 打分矩阵$\mathbf{W}$ 是一个可学习的线性层,把高维向量压缩成一个标量。
- 相关分数score 越大代表越相关,精排层据此对全体候选排序。
- 与粗排的本质区别粗排是两个向量“互不相见”,精排是每个 token 都深度互动。
模型选型
海选、复赛、决赛的评委配置完全不同:海选是实习导演,便宜但眼光粗;复赛是声乐老师,专业且价格适中;决赛是顶级制作人,昂贵却判断精准。给每个赛段配错评委,要么浪费预算,要么埋没人才。
模型选型遵循同样的逻辑。召回层优先选便宜、快的模型: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 排序也要看齐“覆盖率、首位命中率、整体排序质量、延迟与成本”。只盯一个指标,就像只看收视率定节目好坏——迟早翻车。
- 相关度等级$\text{rel}_i$ 是第 $i$ 位结果的人工标注相关度(如 0~3 分级)。
- 指数放大$2^{\text{rel}_i}-1$ 让“高度相关”的收益压倒“有点相关”,例如 rel=3 得 7 分,rel=1 只得 1 分。
- 位置折扣$\log_2(i+1)$ 对靠后的位置打折,第 4 位的贡献只有第 1 位的约三分之一。
- 理想归一化分母 $\text{rel}_i^{*}$ 是理想排序下的相关度序列,DCG 除以 IDCG 后越接近 1 说明越接近完美排序。
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 演示
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))
self.noise = {stage: {d: rng.gauss(0, sigma) ...}}为每层注入不同强度的随机噪声,模拟“召回模型最糙、精排模型最准”的现实差异。def recall(self, q)第一阶段海选:对全量 1000 篇文档做轻量打分,快速捞回 100 个候选。scored.sort(key=lambda x: x[1], reverse=True)按预估分数降序排列,是整个排序链路最核心的操作。return [d for d, _ in scored[:self.recall_k]]只保留前 K 个候选,把规模从全量压缩到百级。gold = sorted(rel, key=rel.get, reverse=True)[:5]取真实相关度最高的 5 篇作为“标准答案”,用于后续评估。mrr_list.append(1.0 / (pos + 1))第一个相关文档排得越靠前,MRR 贡献越大,第 1 名得满分。dcg = sum((2 ** rel[d] - 1) / math.log2(pos + 2) ...)nDCG 的分子:相关度指数放大、位置对数打折。if __name__ == "__main__":可执行入口,直接运行即可看到三个指标的具体数值。
常见误区
误区召回嘛,就是快速捞一批候选的预处理环节,选个最快的模型对付一下,后面精排能力强,肯定能把好文档排到前面。
正解召回决定了整个漏斗的天花板——如果相关文档压根没进候选集,后面粗排精排再强也无能为力。就像海选虽然粗,但若导演在海选就把未来的冠军淘汰了,决赛再专业也救不回来。召回看的是“不可漏”,粗排精排看的是“排得准”,两者职责不同,同样关键。
为何常见:因为召回单点看误差很大、效果不明显,人们容易把它当成“丢垃圾”的预处理,而忽略了候选集的完整度才是整个检索系统的上限。
误区知识库也就几十万篇,精排模型再慢也能扫完,搞三层漏斗不是多此一举吗?
正解交叉编码器对每个候选都要把 query 和 doc 拼起来做完整的 Transformer 前向,扫描 10 万篇文档就是 10 万次深度计算,延迟和算力成本直接爆炸。漏斗的价值恰恰是把昂贵的计算压缩到最小的候选集上。就像不能让制作人给十万选手逐句打磨——那不是专业,是破产。
为何常见:只看到了精排的精度优势,没有算“延迟×数量×单次成本”的总账,忽略了算力预算这个硬约束。
误区精排完多返回几个文档,反正大模型上下文窗口挺大,塞得下,多给点资料不会错吧?
正解大模型上下文虽然能容纳长文本,但塞入大量低相关文档会稀释注意力、引入噪声,token 变多还意味着费用上涨、回答变慢变啰嗦。正确做法是在验证集上画“TopN vs 质量”曲线,找到收益变平缓的拐点,通常 3~10 个就够用。
为何常见:人们对“多”有一种朴素的安全感,忽略了信息噪声和 token 成本的双重损耗。
误区只要返回的结果是相关的,排序模型就算合格,准确率高就是好系统。
正解RAG 场景里用户只看到 Top 5,“第一个相关结果排在第几位”(MRR)、“多级相关度下整体排序是否合理”(nDCG)、“相关文档有没有被覆盖”(Recall)比单纯的准确率更贴近真实体验;此外还要看时延、吞吐和单查询成本,否则模型再准,上线也扛不住流量。
为何常见:初学者习惯用分类任务的准确率思维惯性去理解排序任务,没有意识到“排序质量”是多维的、与用户感知强相关。
面试题
Q1. 多阶段排序最核心的设计思想是什么?
- A.用超强模型一次扫全库提精度
- B.用漏斗逐级缩小候选求平衡← 正确答案
- C.把所有文档分桶并行来处理
- D.让大模型直接逐篇打分选优
答案:B。多阶段排序的核心是“先广后精”的漏斗策略:召回层用轻量模型保证覆盖面,粗排/精排层在缩小的候选集上用更精细的模型排序,从而在效果与算力成本之间取得平衡。其余选项要么成本失控,要么只是并行优化,都没有体现“逐级收缩”的思想。
Q2. 召回阶段最应该关注哪个指标?
- A.MRR:首个相关结果排序位置
- B.nDCG:整体排序质量指标
- C.Recall@K:相关文档覆盖比例← 正确答案
- D.点击率:用户行为指标
答案:C。召回的目标是“别漏掉”,所以最该盯 Recall@K。MRR 和 nDCG 衡量的是排序精度,那是粗排和精排的任务;点击率是在线业务指标,与召回覆盖率不在一个层面。选错指标就像海选时非要考核“台风细节”,方向就错了。
Q3. 下列哪些是粗排阶段的典型配置?(多选)
- A.BM25 词频倒排检索
- B.双塔编码向量内积打分← 正确答案
- C.交叉编码逐词精细打分
- D.批量矩阵运算快速排序← 正确答案
答案:BD。粗排要“中等精度×高吞吐”,双塔向量内积配合批量矩阵运算是标配;BM25 属于召回层的轻量工具;交叉编码器属于精排层,精度虽高但太慢,不适合在百级候选上大量使用。就像复赛评委可以快速打分,但不会给每个选手做三小时深度访谈。
Q4. 精排阶段通常使用交叉编码器,主要原因是?
- A.能做词级双向交互判断最准← 正确答案
- B.推理速度最快适合全库扫
- C.无需训练可直接拿来就用
- D.不限制输入输出长度大小
答案:A。交叉编码器把 query 和 doc 拼接成一句,让每个 token 通过注意力机制两两交互,能捕捉一词多义、隐式指代等深层语义,精度最高。代价是速度慢,所以只能在精排的小候选集上用。选项 B、C、D 都与交叉编码器的实际特性相反。
Q5. 在漏斗成本公式 C = cr×N + cc×Nc + cf×Nf 中,总成本可控的关键是什么?
- A.召回单价比精排更贵更耗钱
- B.规模逐级缩至昂贵模型小集← 正确答案
- C.三个阶段成本相近相互抵消
- D.大模型不参与排序没有成本
答案:B。漏斗的精髓是让单价最低的召回模型面对最大的 N,让单价最高的精排模型只面对最小的 Nf。若用精排直接扫全库,成本是 cf×N,比漏斗高出几个数量级。就像节目组把最贵的制作人时间只留给决赛三个人。
Q6. 多阶段排序相比单模型直接排序的优点有哪些?(多选)
- A.降低端到端延迟与算力开销← 正确答案
- B.各阶段独立优化灵活替换← 正确答案
- C.保证所有相关文档排首位
- D.精度成本搭配更均衡可控← 正确答案
答案:ABD。漏斗结构把高成本计算压缩到最后一层,端到端延迟与算力开销显著下降;各阶段解耦,可以独立换模型、调参数;不同阶段配置不同精度模型,效果与成本更均衡。但它并不保证所有相关文档排首位——召回一旦漏掉,精排也无能为力。
Q7. 某系统召回后候选只有约 30 个,部署多阶段排序最合理的是?
- A.保留完整两端缺一不可
- B.去掉粗排直接精排处理← 正确答案
- C.去掉精排用召回直接出
- D.扩大召回量完善漏斗
答案:B。漏斗的层数是按候选规模设计的。当候选只有 30 个时,粗排省下的计算已经微不足道,直接让精排处理更利落,这就是“两段式”漏斗。如果候选只有 30 个还硬凹三层,等于复赛只有三个人,纯属浪费。层数服务于规模与预算,而非越多越好。
Q8. 关于 nDCG@K 的说法,正确的是?
- A.只关心首个相关结果位置
- B.指数放大相关度并位置折扣← 正确答案
- C.数值越大召回覆盖越完整
- D.归一化无需理想排序参考
答案:B。nDCG 用 2^rel - 1 指数放大高度相关文档的收益,用 log2(i+1) 对靠后位置打折,再除以 IDCG 归一化,衡量整条列表的多级排序质量。只关心首个相关结果位置是 MRR 的职责;不归一化则无法跨查询比较。
Q9. 设置精排 TopN 时应综合考虑哪些因素?(多选)
- A.大模型上下文窗口长度上限← 正确答案
- B.验证集收益曲线拐点位置← 正确答案
- C.单查询延迟与令牌成本预算← 正确答案
- D.文档库存量决定输出级数
答案:ABC。TopN 决定多少文档进入大模型上下文,窗口有限所以有硬上限;收益曲线拐点告诉我们加文档是否还有边际收益;token 成本随 TopN 线性上涨,延迟也会变长。文档库存量影响的是召回层规模,而不是精排输出数量——这两个概念常被混淆。
Q10. 在“召回-粗排-精排”链路中发现答案质量差,但延迟预算仍有富余,最应优先排查哪里?
- A.精排返回数量过大噪声多
- B.召回阶段漏掉真正的相关文档← 正确答案
- C.粗排模型向量维度不够
- D.大模型推理层数需要增加
答案:B。漏斗的天花板在召回——如果相关文档连候选集都没进,粗排精排再准也无力回天。排查顺序应是:先验证召回覆盖率,再调粗排阈值,再调精排 TopN 与模型,最后才怀疑大模型本身。就像选秀出了问题,先查海选有没有漏掉好苗子,而不是急着换决赛评委。