← 返回资讯
苏晴
资深编辑
已审核

查询从70秒压到20秒,换掉LLM砍掉93%耗时

给一家公司搭 RAG 系统,12 家子公司,5000 个文档块,要能回答财务分析类问题。听着挺简单对吧?我也这么想的。

查询从70秒压到20秒,换掉LLM砍掉93%耗时

查询从70秒压到20秒,换掉LLM砍掉93%耗时


去年接了个活儿。

给一家公司搭 RAG 系统,12 家子公司,5000 个文档块,要能回答财务分析类问题。听着挺简单对吧?我也这么想的。

结果翻车翻到怀疑人生。

端到端查询 70 秒,重排 54 秒。用户点个查询,能去泡杯咖啡回来还在转圈。绝了。

最后压到 20 秒,重排砍到 2 秒。这篇不是教程——是我踩坑的记录。倒着讲,先说结果,再说怎么把自己坑进去的。

先看数据

优化前后的对比,你们感受一下:

| 指标 | 优化前 | 优化后 | 提升 |

|------|--------|--------|------|

| 端到端查询 | ~70s | ~20s | 71% |

| 重排耗时 | 54s | 2s | 93% |

| API 调用次数 | 10-20 次 | 3-5 次 | 70% |

| 多公司覆盖率 | 60% | 95% | +35 p.p. |

20 秒对实时对话来说还是有点慢。但作为内部分析工具,能用了。讲真,用户从 70 秒等到 20 秒,已经感动得快哭了——我猜的。

重排,最大的坑

刚开始做 profiling,我盯着火焰图看了半天。

54 秒。

重排占了 54 秒。

我当时脑子里就一个想法:完犊子了。

原因蠢得一批——我用 LLM 做重排。每次把候选文档扔给 GPT,让它打分排序。调用一次 2-3 秒,候选池 20 条,串行调用。20 × 2.5,50 秒起步。这玩意儿快得像我奶奶过马路。

解决方案?换专用模型。gte-rerank-v2,本地部署,批量推理。2 秒搞定。

教训就一条:专用模型 > 通用模型。别用 LLM 硬上排序任务,拿大炮打蚊子,又贵又慢。这事儿我现在想起来还觉得当时的自己天真得可爱。

向量数据库选型,另一个早期翻车现场

项目只有 5000 个向量。

5000 个。不是 5000 万。

我上来就上了 Milvus Standalone。启动 10-30 秒,内存吃了 500MB(含 Docker),查询 20ms。当时还觉得自己挺专业,分布式向量数据库,听着就高级。

后来换成 FAISS:内存 50MB,即时启动,查询 15ms。

你品,你细品。

说实话,数据量小于 10 万,FAISS 完全够用。Milvus 是好东西,但小数据量上就是杀鸡用牛刀——不对,是用屠龙刀切葱花。

不过 FAISS 有个小坑,折腾了我一个下午。妈的。

C++ 底层对中文路径支持不好,Windows 下直接报错。报错信息还贼抽象,根本看不出是路径问题。我 debug 了两小时,最后灵光一闪试了英文路径,好了。

解决方案是用临时文件中转:

PYTHON
with tempfile.NamedTemporaryFile(delete=False, suffix=".index") as tmp:
 faiss.write_index(index, tmp.name)
 shutil.move(tmp.name, target_path)

就这几行代码,花了我一个下午。那种感觉,懂的都懂。

查询扩展别用 LLM

刚开始做查询扩展,我想着"反正都接 LLM 了,让它顺便扩展一下查询词呗"。

结果每次查询多了 3 秒。

而且 LLM 偶尔会扩出些莫名其妙的东西——把"营收"扩成"营收增长率",完全跑偏。你让它扩展个同义词,它给你整成概念延伸。服了。

后来改成预定义术语词典:

PYTHON
TERM_EXPANSIONS = {
 "营收": ["营业收入", "主营业务收入", "营业额"],
 "利润": ["净利润", "归母净利润", "利润总额"],
 # ... 共 18 组
}

直接替换,零延迟。比 LLM 扩展更稳定,而且——这个很重要——可控。你知道它一定会扩成什么,不会突然冒出个"营收环比增长速率"这种鬼东西。

原则就一条:能离线算的,不要在线算。

扯远了,说回正题。

合并去重的细节,小改动大效果

向量检索和 BM25 检索可能召回同一个文档块。刚开始我的去重逻辑是"先出现的保留",简单粗暴。

结果高分块经常被低分块盖掉。比如向量检索给了 0.95 的相似度,BM25 给了 0.6,但因为 BM25 先返回,0.6 的那个被保留了。你说气不气。

改成保留最高分:

PYTHON
if new_score > old_score:
 merged[pk]["scores"]["vector"] = new_score

检索精度提升了大概 15%。就改了一行判断条件。

有时候就是这样,不是算法不对,是逻辑太糙。

多公司查询的公平分配,这个场景挺有意思

用户问"三家运营商营收对比"。如果一家公司的高分块太多,其他公司的结果会被挤掉——比如移动的文档块占了前 15 个里的 12 个,联通和电信直接被挤出视线。

我加了个公平分配逻辑:

PYTHON
per_company = max(5, HYBRID_TOP_K // len(companies))

配合三层保底机制——覆盖率检查、候选召回、保底重检索——多公司覆盖率从 60% 提到 95%。

这东西不是算法创新,是工程洁癖。但好用。

工程化兜底,不设超时的后果

任何外部调用都要有超时和回退。不设超时的后果就是偶发卡死,用户等半天没响应。

真的,设超时这件事,属于那种"不做也不会死,但做了能救你一命"的操作。

PYTHON
LLM_TIMEOUT = 60
EMBEDDING_TIMEOUT = 30
RERANK_TIMEOUT = 15

try:
 reranked = gte_reranker.rerank(candidates)
except TimeoutError:
 logger.warning("gte-rerank 超时,回退到混合分数排序")
 reranked = sorted(candidates, key=lambda x: x["scores"]["hybrid"], reverse=True)

回退策略不一定完美,但比卡死强一万倍。用户宁可看到"还行"的结果,也不想盯着加载动画发呆。

分块策略,最被低估的环节

分块是 RAG 里最被忽视的环节。或者说,最容易被轻视的。

直接按段落切,表格很容易被切碎。表头在一个 chunk,数据在另一个 chunk,LLM 拿到的是残废信息——就像给人看财务报表但只给半张表。

我用的策略:子块 150 tokens(精确检索粒度)+ 父块 500 tokens(保留完整上下文)。检索时命中子块,生成时用对应父块做上下文。

Markdown 表格、代码块、列表这些"原子语义块"尤其要保护。一旦切断,语义就残了。这个方案——不对,应该叫策略——效果不错。

评估不能只看一个指标

粗召回阶段看 MAP@k、Recall@k,核心问题是"有没有把相关文档全找出来"。

精排阶段看 NDCG@k、Precision@k,核心问题是"高价值内容有没有排在最前面"。

评估框架用的 RAGAS,GPT-4 当裁判,自动算 Faithfulness、Answer Relevance、Context Relevance 这些指标。测试数据集里混了 20% 的 Unanswerable 问题,用来测拒绝机制——知识库里没答案的时候,系统应该说"我不知道",而不是瞎编。

这个机制上线前跑了一轮回归测试,确认不会把能回答的问题也拒掉。大概跑了 200 多条 case,手动抽查了 50 条。累,但值。

如果继续优化

当前 20 秒还有空间:

复杂度都不低,暂时没动。也可能下个月就动手了,谁知道呢。做独立开发就是这样,优化清单永远比开发清单长。

踩完坑才明白的几件事

先 profiling 再优化。别猜瓶颈在哪,用火焰图说话。我要是早点 profiling,也不至于在 Milvus 上浪费那么多时间。

优先解决耗时占比超过 50% 的问题。54 秒的重排不解决,优化别的都是扯淡。这个顺序不能乱。

专用模型干专一的活。重排用 gte-rerank-v2,别用 LLM 硬上。这个前面说过了,不重复。

批量永远比逐条快。API 调用次数比单次耗时更重要。20 次串行调用改成 1 次批量推理,质的飞跃。这个道理我懂,但真正体会到是这次。

超时必设,回退必有。任何外部调用都要有兜底。这是工程底线,不设就是给自己埋雷。

大概就这些。

RAG 优化这事儿,说到底不是调某个魔法参数,是把每个环节都掰开揉碎了看,找到那个真正拖后腿的东西,然后换掉它。54 秒的重排,500MB 的 Milvus,3 秒的 LLM 查询扩展——每一个坑都是自己踩出来的。

你们呢?有没有踩过类似的坑?

382
9570 阅读
4 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月27日 13:14

苏晴

资深编辑

科技媒体从业 8 年,曾就职于多家科技媒体。关注 AI 创业和投资赛道,采访过 50+ 位行业从业者。

读者评论 4

张工 3天前
写得很实在,特别是实测对比那部分,跟我自己的使用感受一致。
回复 点赞 (12)
前端工程师 6天前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)
技术小白 1周前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 1周前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)