1. 为什么企业知识库绕不开 Rerank 这道坎
做过 RAG 知识库的人大概都有过这种体验:向量检索明明召回了十几条内容,看着相似度分数都挺高,可真正喂给大模型之后,回答还是答非所问。用户问"年假怎么算",检索出来的却是"请假流程""考勤制度""调休规定"混在一起,模型只能硬着头皮拼凑,最后给出一段似是而非的答案。这个问题在企业内部知识库场景里尤其致命,因为企业文档往往术语密集、章节交叉、同一件事分散在多个文件里,纯靠向量相似度那一层排序,很难把真正对的那一条顶到最前面。
Rerank(重排序)就是来解决这个问题的。简单说,它是在向量召回之后、喂给大模型之前,插入的一道精排工序。向量检索负责"广撒网",把可能相关的几十条候选捞出来;Rerank 负责"精挑细选",用更强的语义模型对这几十条逐一打分,重新排出一个更靠谱的顺序,只把 Top-K 条真正相关的塞进上下文。我所在团队过去大半年一直在做企业智能知识库的落地,Rerank 这块从选型、接入、调参到线上踩坑,几乎把能踩的雷都踩了一遍。这篇就把整个实践过程摊开讲,包括我们为什么最终选了现在这套方案、参数怎么定、Provider 配置有哪些坑、效果怎么评估,以及那些文档里不会写但线上一定会遇到的事。
不管你是刚接触 RAG 知识库的新手,还是已经在调优检索效果的老手,只要你的场景涉及"文档多、问题杂、答案要求准",Rerank 这一环基本躲不掉。下面我按实际落地的顺序,从整体设计讲到具体实现,再到问题排查,尽量把每一步的"为什么"都说清楚。
2. 整体方案设计与 Rerank 的定位拆解
2.1 一条完整的检索链路长什么样
先把整条链路摆出来,不然后面讲 Rerank 容易悬空。我们企业知识库的检索流程大致分四段:
第一段是文档预处理。原始文档(Word、PDF、Confluence 导出、飞书文档)先做解析,切成 chunk。这里 chunk 大小我们定在 400 到 600 字之间,重叠 80 字左右。切太碎语义不完整,切太大噪声多,这个区间是反复试出来的。
第二段是向量召回。用 embedding 模型把 query 和所有 chunk 都转成向量,走向量库做近似最近邻搜索,召回 Top-N 条。N 我们一般设 30 到 50,看文档总量和延迟预算。
第三段就是Rerank 重排序。把 query 和召回的 N 条候选一起送进 Rerank 模型,模型输出每条的相关性分数,按分数重排,取 Top-K(K 通常 3 到 8)。
第四段是生成。把 Top-K 的 chunk 拼进 prompt,交给大模型生成答案,同时附上引用来源。
很多人会问:既然 Rerank 模型这么强,为什么不直接用它检索全库?答案是性能。Rerank 是交叉编码器(cross-encoder),它要把 query 和每一条文档拼在一起过一遍模型,计算量跟候选数量成正比。全库几万条文档逐条打分,延迟根本扛不住。所以工程上的标准做法就是"向量召回粗筛 + Rerank 精排"这个两段式结构,前者负责把候选压到几十条,后者负责在这几十条里做高质量排序。这个分工是整个方案设计的核心逻辑,理解了它,后面所有参数选择就都有依据了。
2.2 为什么向量召回不够,非得加一层 Rerank
这里得把原理讲透,不然容易觉得 Rerank 是"锦上添花"。向量检索用的是双编码器(bi-encoder):query 和文档分别独立编码成向量,然后算余弦相似度。好处是文档向量可以离线算好、建索引,检索时只算 query 向量,快。但代价是 query 和文档在编码阶段"没见过面",模型没法捕捉两者之间的细粒度交互。比如 query 是"试用期员工有没有年假",文档 A 讲"试用期员工的假期规定",文档 B 讲"正式员工年假天数"。双编码器可能因为都含"年假""员工"这些词,给两条差不多的分数,但真正对的只有 A。
Rerank 用的交叉编码器就不一样,它把 query 和文档拼成一个序列[CLS] query [SEP] document [SEP]一起送进模型,注意力机制能在两者之间充分交互,判断"这条文档到底能不能回答这个问题"。精度上通常比双编码器高一截,代价就是慢、不能预计算。所以它天然适合做精排,不适合做召回。这个"粗排快、精排准"的组合,是检索系统里非常经典的架构,搜索引擎里早就在用,RAG 只是把它搬到了语义检索场景。
2.3 方案选型:自建、API 还是用平台内置
落地时第一个决策是 Rerank 能力从哪来。我们当时评估了三条路:
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 开源自建(如 BGE-Reranker 系列) | 数据不出内网、可控、无调用成本 | 要 GPU、要运维、效果调优靠自己 | 数据敏感、量大、有算力 |
| 第三方 API | 接入快、效果好、免运维 | 按量计费、数据出境顾虑、依赖网络 | 中小量、快速验证 |
| 平台内置 Rerank | 零配置、和流水线打通 | 可调空间小、绑定平台 | 用现成平台搭知识库 |
我们最终是混合策略:核心敏感知识库走开源自建,部署在内网 GPU 机器上;一些非敏感的、量不大的场景走 API。这么选的原因是数据合规和成本两头都要顾。开源自建这块我们用的是 BGE 系列的 reranker 模型,具体版本这里不展开,重点是它支持中英文、社区活跃、有现成的推理封装。
这里有个经验:别一上来就纠结模型选哪个,先把链路跑通。我们最开始花了快两周对比各种 reranker 的离线指标,结果发现真正影响线上效果的,是 chunk 切分和 Top-N 的设置,模型之间的差距反而没那么大。选型阶段用默认推荐模型快速验证,等链路稳了再针对性换模型调优,效率高得多。
3. Rerank 核心细节与实操要点
3.1 候选数量 N 和保留数量 K 怎么定
这是 Rerank 落地里最需要拍脑袋、也最影响效果的两个参数。先说 N(召回候选数)。N 太小,真正相关的文档可能压根没被召回,Rerank 再强也救不回来;N 太大,Rerank 计算量上去了,延迟涨,而且噪声变多反而可能干扰排序。我们的经验值是N 取 30 到 50,具体看两点:一是文档库规模,库越大、主题越杂,N 要适当放大;二是延迟预算,Rerank 的耗时基本和 N 线性相关。
再说 K(最终喂给模型的条数)。K 太大,上下文里塞进不相关内容,模型容易被带偏,还浪费 token;K 太小,可能漏掉关键信息。我们一般K 取 3 到 8,问答类场景取小一点(3 到 5),需要综合多份文档做总结的场景取大一点(6 到 8)。
这里给一个我们实测的延迟参考(内网单卡,候选平均长度 500 字):
| N(候选数) | Rerank 平均耗时 | 备注 |
|---|---|---|
| 20 | 约 120ms | 延迟敏感场景可用 |
| 30 | 约 180ms | 我们最常用的档位 |
| 50 | 约 300ms | 效果略好,延迟可接受 |
| 100 | 约 600ms | 除非必要,不推荐 |
提示:N 和 K 不要拍死,做成可配置项。不同知识库、不同业务线的最优值不一样,线上能调比什么都强。
3.2 分数阈值:什么时候该"宁缺毋滥"
Rerank 输出的是相关性分数,这个分数可以直接用来做过滤。我们线上加了一条规则:如果 Top-1 的分数低于某个阈值,就判定为"知识库没有相关内容",直接回复"未找到相关答案",而不是硬塞给大模型。这条规则救了我们很多次,因为企业知识库里问题覆盖不全太常见了,硬答出来的错误信息比不答危害大得多。
阈值怎么定?没有万能值,得看你的 reranker 输出分布。做法是拿一批标注好的 query,看"相关问题"和"不相关问题"的分数分布,取一个能较好分开两者的点。我们最后定在 0.3 左右(归一化后的分数),但这个值因模型而异,一定要自己测。
3.3 批处理与并发:别让 Rerank 成为瓶颈
Rerank 是逐条打分的,N=30 就是 30 次前向计算。如果串行跑,延迟会很难看。我们的做法是批处理:把 N 条候选拼成一个 batch 一次性送进模型,GPU 利用率高,延迟能压下来一大截。同时在线服务层做并发控制,避免高峰期把 GPU 打满。
还有个细节:候选要按长度排序后再组 batch,把长度接近的放一起,减少 padding 浪费。这个优化看着小,实测能省 15% 到 20% 的推理时间。
3.4 和向量召回的分工边界
有个坑我踩过:一开始我们指望 Rerank 能"纠正"向量召回的失误,比如 query 里有关键词但向量没召回对。实际上 Rerank 只能在召回集合内排序,召回没进来的,它无能为力。所以如果发现某类问题总是答不好,先别急着调 Rerank,回去看召回——大概率是 embedding 模型或者 chunk 切分的问题。Rerank 是精排,不是召回补救。这个边界一定要清楚,否则会在一堆无效调参上浪费时间。
4. 实操落地:从接入到上线的完整过程
4.1 环境与依赖准备
我们自建这套跑在内网一台带 GPU 的服务器上,基础环境是 Python 3.10 + PyTorch + transformers。Rerank 模型通过 transformers 加载,封装成一个 HTTP 服务对外提供打分接口。核心依赖大致是这些:
pip install torch transformers fastapi uvicorn模型文件提前下载好放本地,避免运行时联网。服务启动脚本大概长这样:
from fastapi import FastAPI from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch app = FastAPI() model_path = "/models/reranker" tokenizer = AutoTokenizer.from_pretrained(model_path) model = AutoModelForSequenceClassification.from_pretrained(model_path) model.eval() device = "cuda" if torch.cuda.is_available() else "cpu" model.to(device) @app.post("/rerank") def rerank(query: str, docs: list[str]): pairs = [[query, d] for d in docs] with torch.no_grad(): inputs = tokenizer(pairs, padding=True, truncation=True, max_length=512, return_tensors="pt").to(device) scores = model(**inputs).logits.view(-1).float().cpu().tolist() ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True) return {"results": [{"doc": d, "score": s} for d, s in ranked]}这段代码的关键点:padding=True配合批处理,truncation=True防止超长文档报错,max_length=512是模型上限。上线前一定要压测,看单请求延迟和并发能力。
4.2 接入检索流水线
Rerank 服务跑起来后,接到主检索流程里。伪代码逻辑:
def retrieve(query, top_n=30, top_k=5, score_threshold=0.3): # 1. 向量召回 candidates = vector_store.search(query, top_n=top_n) # 2. Rerank 精排 docs = [c.text for c in candidates] reranked = rerank_service(query, docs) # 3. 阈值过滤 + 取 Top-K filtered = [r for r in reranked if r["score"] >= score_threshold] final = filtered[:top_k] return final这里有个顺序问题要注意:先过滤阈值再取 Top-K,还是先取 Top-K 再过滤?我们选前者。因为如果先取 Top-K,可能 K 条里有一半是低分的,过滤完只剩一两条,反而漏了。先过滤能保证只要分数够就保留,数量灵活。
4.3 Provider 配置的那些坑
现在很多团队用平台化工具搭知识库流水线,Rerank 往往作为一个 Provider 节点接入。这块的配置错误是高频问题,我见过太多类似的报错,比如"provider 缺少 base_url 配置""provider rejected the request schema"这类。归纳下来,Provider 配置要盯紧这几项:
- base_url:自建服务的地址,必须带协议和端口,路径要对。少一个斜杠都可能 404。
- api_key:即使是内网服务,很多框架也要求填一个占位 key,空着会直接报配置错误。
- model 名称:要和 Provider 端注册的名字完全一致,大小写敏感。
- 请求 schema:不同框架对 Rerank 接口的入参出参格式要求不同,有的要
query+documents,有的要query+passages,对不上就报 schema 错误。 - 超时设置:Rerank 比普通接口慢,默认超时经常不够,要单独调大。
注意:配置类报错九成是"名字对不上"或"地址写错",排查时先把这几项逐字核对一遍,比看日志快。
4.4 效果评估怎么做
上线前必须有一套评估。我们建了一个小测试集,大概 200 条 query,每条人工标注了"应该命中的文档"。评估指标主要看两个:Recall@K(Top-K 里有没有包含正确文档)和MRR(正确文档排在第几位的倒数均值)。加 Rerank 前后对比,我们这套的 Recall@5 从 0.71 提到了 0.89,MRR 从 0.52 提到了 0.78,提升非常明显。
评估集不用很大,但一定要覆盖真实业务问题,别拿通用问答凑数。企业知识库的问题往往很"土",比如"报销单丢了怎么补""出差补贴标准是多少",这些才是要重点测的。
5. 常见问题与排查技巧实录
5.1 效果类问题速查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 加了 Rerank 效果没提升 | 召回本身太差,候选里就没有对的 | 先看 Recall@N,N 条里有没有正确答案 |
| 相关问题被排到后面 | 阈值/模型不匹配、query 太短 | 换模型、补全 query、检查 chunk 质量 |
| 不相关问题也被答 | 阈值太低 | 提高 score_threshold |
| 延迟突然变高 | N 太大、batch 没生效、GPU 被占 | 看 N 配置、检查批处理、看 GPU 利用率 |
5.2 那些文档不会写的坑
坑一:query 太短导致 Rerank 失效。用户输入"年假"两个字,Rerank 拿这么短的 query 去和文档比对,信息量太少,排序容易乱。我们的做法是在检索前做一层query 改写/扩展,把"年假"补成"员工年假天数怎么计算",再送进检索。这一步对 Rerank 效果提升很明显。
坑二:chunk 里混入大量页眉页脚。PDF 解析出来的 chunk 经常带着"第 X 页""公司内部资料"这类噪声,Rerank 会被这些无关文字干扰。预处理阶段一定要做清洗,把重复的页眉页脚、水印文字去掉。
坑三:多语言混排。我们知识库里有中英文文档,早期用单语言 reranker,英文文档排序明显差。后来换成多语言模型才解决。如果你的库是多语言的,选型时务必确认模型支持。
坑四:Rerank 服务和向量库版本不一致。有次更新了 chunk 切分逻辑,但向量库没重建索引,导致召回和 Rerank 看到的文档对不上,排查了大半天。改切分逻辑一定要重建索引,这是铁律。
坑五:把 Rerank 当万能药。前面说过,Rerank 只能排序召回集合内的内容。如果某类问题一直答不好,八成是召回或切分的问题,别在 Rerank 上死磕。
5.3 性能优化的小技巧
除了批处理和按长度排序,还有几个实测有效的点:开启 FP16 推理,显存占用减半、速度提升明显;模型常驻显存,别每次请求都加载;对高频 query 做结果缓存,企业知识库里重复问题其实不少,缓存命中率能到 20% 以上,直接省掉一次 Rerank。
6. 一些个人体会
Rerank 这东西,说复杂也复杂,说简单也简单。核心就一句话:它是精排,不是召回,别指望它解决召回的问题。把这句话刻在脑子里,能省掉一半的无效调参。
我最大的体会是,RAG 知识库的效果是个系统工程,Rerank 只是其中一环。chunk 切分、embedding 选型、query 改写、阈值策略,每一环都影响最终效果,而且它们之间是乘法关系,不是加法。任何一环拉胯,整体就上不去。所以别迷信某个"最强 reranker",把整条链路的数据质量做扎实,比换模型管用得多。
最后分享一个我们线上一直在用的小习惯:每次调整检索链路(换模型、改参数、改切分),都跑一遍评估集,记录指标变化。时间长了你会有一份自己的"调参日志",知道什么改动有效、什么改动是白费劲。这份日志比任何教程都值钱,因为它是针对你自己业务场景的。