Rerank 不是瓶颈,前面的脏数据才是
系统上线之后,团队做了件“标准动作“:向量检索后面接一个 reranker。接完一测,端到端延迟多了几百毫秒,答对率却几乎没动。第一反应很一致:要么换个更快的 reranker,要么干脆把它砍了。
先摆两个数字定调,都是经验口径,不是 benchmark 实测:rerank 这一步通常带来 200~500ms 的额外延迟;而初轮召回的 top-k 里,通常有 30%~50% 的候选本来就不相关——token 看起来像,跟用户的问题没有真正对应。这两个数字放在一起,判断其实已经出来了:瓶颈不在 reranker,在它面前的候选集。
reranker 值多少钱,成本从哪来
先把话说公道:这篇不是来否定 rerank 的。在 RAG 链路里,rerank 是回报最高的单步操作。原因在机制上:向量检索是双塔结构(bi-encoder),query 和 passage 各自独立编码、互不见面,打分天然粗糙;reranker 是 cross-encoder,把 query 和候选同时喂进模型,让两者在注意力里充分交互,判断精度高一截。所以“粗排之后加精排“这笔钱,值得花。
成本的来源同样清楚。cross-encoder 每判一对 (query, chunk) 就是一次完整推理,成本跟候选数基本线性相关:给它 50 个候选,它做 50 次推理;给它 100 个,就做 100 次。这就是为什么生产链路里几乎没人拿它当第一道筛子——一旦铺到大量候选上,延迟立刻失控。回报最高和最容易变慢,本质是同一件事:它干的是高语义计算密度的活儿,每多一个候选就多一次重型 forward。
财报场景把这个矛盾又放大了三层。
一是跨年份文本极度相似。同一家公司 2024 Q2 和 2025 Q2 的管理层讨论、风险因素、经营回顾,常常只换了几个数字,embedding 看谁都像,初轮召回把它们成批拉出来,reranker 面对的是一堆“看起来都对“的候选,工作量天然偏大。
二是 hybrid 的副作用。财报适合混合检索,但混合的直接后果是候选池更大;不收敛的话,rerank 的负担继续往上涨。
三是财报 chunk 天然密集。一页摘要里可能同时出现收入、毛利、经营利润、每股盈利、同比、环比——一个 chunk 混着 5 个指标、2 个时间口径是常态。这种 chunk 对初轮检索很“友好“,什么关键词都带一点;对 reranker 极不友好,它得替你判断用户真正关心的是哪个指标,判别难度显著上升。
又贵又值。这个张力先立在这儿。接下来的问题是:贵的真正原因,到底是模型,还是它面前的世界。
它救不了什么:三个候选集层面的病
reranker 的工作范围有一条硬边界:它只在候选集里挑。正确答案没进 top-N,精排再好也排不出来。我在财报链路里碰到的三种病,全都病在这条边界之外。
年份错配。 用户问 2025 Q4 营收,正确 chunk 排在第 137 位——这个数字是举例,意思是它根本没进 top-100。reranker 看不见它,自然也救不了它。解法不在精排,在检索前的 metadata 硬过滤:report_year != 2025 的 chunk 全部排除,候选集从 10,000 窄到 200(同样是示意量级),正确答案自然进候选。第 6 篇讲时间错配时把这条路展开过,这里只补一句:这是过滤的病,不是排序的病。
单位缺失。 “营收 184,504(百万元)“和“营收 1,845(亿元)“是同一个数字,差 100 倍。reranker 看到两个 chunk 都有“营收“和数字,但它不认识“百万元“和“亿元“之间的换算关系。这是 chunk 的 metadata 层标 unit、在检索或生成阶段做数值归化的事,精排插不上手。
口径不同。 “期內盈利 1,150 亿(IFRS)“和“1,320 亿(Non-GAAP)”,语义高度相似,口径完全不同。reranker 只看语义,不会按口径做区分——第 7 篇专讲口径陷阱,这里不展开。
三个病的共同点:都发生在候选集进入 reranker 之前。
它面前的世界:五层上游优化
核心思路一句话给掉:输入 200 条低质量候选,reranker 再快也是浪费——200 次推理全是白烧;把候选压到 20 条高质量的,就算每次推理慢一点,总延迟反而可控,精度还更高,因为它不再被低质量样本拖票。
我在白板上画的处理顺序是:先减少噪声 → 再缩小检索范围 → 再提高首轮候选质量 → 最后才交给 reranker 做精排。对应五层,逐层说。
第一层,ingest 清洗。 先看噪声从哪来。目录页如果不单独处理,会把“收入、毛利、现金流、风险因素“这些高频词完整抄一遍,任何营收类查询都先命中目录——第 8 篇讲相似度错觉时,拿目录页当过“满分相似、零分信息“的典型。页脚里的公司全名是另一种污染:它出现在每一页,让所有 chunk 被同一段噪声“拉近“,embedding 空间里区分度整体下降。这些结构噪声混进召回,reranker 就被迫处理一批本不该进来的候选。我的 process_pdf_for_rag.py 里已经做进去了三件事:过滤页脚和纯页码、跳过目录页、用清洗后的正文层数据构建 chunk。解析和 reading order 的细节第 2 篇写过,这里只强调一句:清洗本身不是 rerank,但它直接决定 rerank 的候选分布。
第二层,metadata 预过滤。 财报问题天然带强约束:公司、年份、报告类型、期别,常常直接写在用户的问题里。既然检索范围本来就不该是全库,最合理的做法是先过滤、再检索。chunk schema 里通常已经有这些字段:
company / report_year / report_type / language /
section / period_type / unit / page_start / page_end
它们的价值不只是生成答案时贴 citation,更重要的是让检索一开始就缩圈。“先 filter 再 retrieve“在财报场景又准又快,因为它直接砍掉了 Dense 检索和 rerank 需要面对的 chunk 总量。代价得认:这个策略依赖 query parsing 足够稳定,问题太模糊、约束抽不出来时,过早过滤可能把正确答案一起挡在门外。第 6 篇的四层解法里它排第一,理由是同一个——财报场景通常满足“问题自带约束“这个前提,所以它在这里特别有效。
第三层,structure-aware chunking。 很多“reranker 慢“的表象,根子是 chunk 设计把活儿留给了 reranker。一个 chunk 里同时有 2025H1 和 2024H1、混着表头、正文解释和几个不同指标,reranker 就得先替系统做一遍“语义拆包“,再判断相关性——候选一多,这笔额外认知开销非常贵。我的处理脚本不按固定 token 长度暴力切,而是:识别 heading,让 chunk 继承 section;抽取 period_type 和 unit;period 变化时主动 flush,不让两个时间粒度住进同一个 chunk;长段落再做二次切分,保留少量 overlap。实测数字:腾讯 2025 中报——就是第 1 篇开场进系统的那份——清洗后 119 页,最终产出 345 个 chunk。代价摆在明处:chunk 变多、首轮索引变大。但只要 pre-filter 和 hybrid 跟上,这笔代价通常比让 reranker 处理“大而脏“的候选划算。切分的完整改造记录在第 4 篇。
第四层,hybrid 提高首轮 precision。 先说一个我见过的错误操作:怕漏召回,就把 top-k 一路往上加。多出来的候选大多是低质量假阳性,rerank 不会因此更准,只会更慢。正确的方向是让首轮候选本身更像“值得精排的候选“。财报里有两类相关性:lexical 的——术语、指标名、缩写、数字口径,比如资本开支、股份回购、non-GAAP;semantic 的——管理层解释、风险描述、经营表述。只用 Dense,会把各年份“语气相似“的管理层讨论一起拉进来;只用 BM25,又扛不住表达变体。混合召回的目的不是让候选更多,而是让真正有希望进入最终答案的候选更早出现。代价也明码标价:两套索引,加一套融合逻辑。第 9 篇讲过 BM25 和 Dense 怎么分工,这里从 rerank 视角补一句:hybrid 减少的是精排阶段的无效工作量。
第五层,section-aware retrieval。 section 信息比多数人以为的更有用:问风险,答案更可能在风险因素和附注;问管理层判断,更可能在主席报告和 MD&A;问精确数字,更可能在财务摘要、主表和附注。系统如果能先判断用户在问“数字“、“原因“还是“风险”,就优先去对应 section 里检索,reranker 就不用在完全异质的 section 之间做全量比较,延迟和误判都会下降。这一层的关键句我直接给:不是让模型更聪明,是让模型面对的比较任务更单纯。
五层之外,还有一类查询要单拎出来:跨期对比。“对比腾讯 2024 和 2025 中期报告里的广告业务变化”——如果当成一个 query 走单跳检索,会把两个时期的高相似 chunk 一次性全塞进候选池,rerank 压力巨大,还很容易出现“语义对、年份错“。更稳的做法是 multi-retrieval + synthesis:2024 检一遍、2025 检一遍,各自精排,最后在生成阶段汇总比较。链路变长了,但每次面对的候选集更小、语义更单纯。对财报这种跨期文本高度相似的数据,分而治之比一次性大上下文硬拼更稳。
复杂度该花在哪一层
五层收拢成一本账,其实是同一个取舍。
我的优化顺序固定为四步:metadata 硬过滤 → 向量检索 + query 改写 → reranker 精排 → LLM prompt 约束。从第一步到第四步,边际收益递减;经验上第一步能解决六成上下的问题——这是我在自己项目里的估算口径,不是统计结论——但不做第一步,后面三步全做也救不回来。
两种方案并排看更直观:
方案 A:PDF 转文本 → 固定长度切块 → 全库 dense → 放大 top-K → rerank 兜底
方案 B:清洗结构噪声 → structure-aware 分片 → 挂 metadata
→ pre-filter / section-aware → hybrid → 小候选集 rerank
方案 A 实现快,demo 容易跑起来;但随着文档量增大,rerank 越来越慢,而且慢得不值——它精排的大量候选,本来就不该进入精排阶段。方案 B 工程复杂度更高、调试点更多,但 rerank 的成本被控制在真正接近答案的候选上。财报这种强约束、高相似、数字敏感的场景,我几乎只选 B。
边界也必须划:换成知识库 FAQ 这类场景,chunk 数量小、文档之间差异大,方案 A 完全够用——reranker 处理 200 个候选,按经验也就 200ms 上下。别把财报场景的取舍,直接套到所有 RAG 系统上。
什么时候才轮到 reranker:用武之地与选型
上游五层做完,reranker 不是没用了,是终于轮到它干正事了。三类场景它不可替代:
- 语义相近但细节不同的排序。问“腾讯的游戏业务策略“,候选里增值服务、国际游戏、国内游戏、电竞都在,谁排前面需要精细区分,向量检索的排序粒度不够。
- 跨语言匹配。中文提问、chunk 中英混合,“revenue“和“收入“在 embedding 空间里有距离,在 cross-encoder 的交互编码里会被连起来。
- 短 query 长 chunk。问“腾讯利润“,答案埋在 800 token 的管理层讨论里,短 query 对长 chunk 的 embedding 匹配天然吃亏。
三类的共同点:正确答案已经在候选集里,只是排名不够高。reranker 干的是把第 15 名提到第 1 名的活儿——这是它的用武之地,也是它的全部边界。
反过来,什么时候可以不开?我能给的判断只有一半:chunk 少、文档差异大的场景(比如上一节的 FAQ),候选集天然干净,精排的边际收益小。这个结论目前只有延迟一侧的估算撑着,我没做过开/不开 reranker 的答对率对照实测【待补:缺一组开/不开 reranker 的延迟与答对率实测数据,素材中没有,发布前如能补测可替换此句】,所以这条边界按保守口径写:候选集小且干净时可以不开,拿不准就开。
真要上 reranker,选型看三个指标,都是工程口径。一是推理延迟:cross-encoder 单次推理大约 10-100ms,逐对串行跑 top-100 就是 1-10 秒——但批处理(batching)能把 top-100 压进几百毫秒到 2 秒以内,交互式场景优先选支持 batch 的方案。二是语言支持:bge-reranker-v2-m3(基于 bge-m3 的轻量多语言 reranker)对中文支持好,Cohere 的 rerank 系列多语言好但 API 成本高。三是和 embedding 模型的互补性:向量检索已经用了 multilingual-e5-large,reranker 就不必配同系列,补短板比叠长板值。
放大器,不是净化器
收束本篇,两句话。reranker 最合理的位置,不是大规模粗筛,而是小候选集上的精排器;跳过上游直接加 reranker,就像给一座地基不稳的房子装更好的窗户——窗户是真的好,房子还是会歪。它是放大器,你给它什么它就放大什么,包括脏数据;它不是净化器。
篇章二到这里走完一圈:第 6 篇的时间、第 7 篇的口径、第 8 篇的相似度、第 9 篇的单路、本篇的精排。五个错觉摆在一起,共同点不是“最后的模型不行“,而是检索链路的每个环节都藏着没检验过的假设。
单市场的检索链路到此讲完了。把镜头拉远一步:A 股、港股、美股的财报一起进系统,格式、准则、货币全不一样,一套向量库还扛得住吗?下一篇《A 股、港股、美股,不能只用一套向量库》讲这个。