我把 RAG 响应速度从 3 秒优化到 200ms,只做了这 4 件事
摘要RAG 应用上线后响应慢、准确率低我踩过所有坑。从向量检索优化到重排序策略从缓存机制到异步处理分享一套完整的生产级优化方案。实测响应时间从 3s 降到 200ms准确率提升 40%。代码已开源。RAG 太慢这套优化方案真香开篇那个让我失眠的 RAG 项目上个月帮朋友公司做一个知识库问答系统用的就是现在烂大街的 RAG 架构。上线第一天老板就找我用户反馈太慢了等一个问题要三四秒体验很差。说实话我当时心里咯噔一下。开发的时候在本地测试一切正常怎么一上线就拉胯接下来一周我几乎住在了公司。排查、优化、测试最后把响应时间从平均 3.2 秒压到了 200ms 左右用户满意度直接从 3.1 分涨到 4.7 分。今天就把这套优化方案完整分享出来。如果你也在做 RAG 应用希望能帮你少走点弯路。问题出在哪先诊断再开方很多人一上来就想着换模型、换向量数据库其实方向错了。我先做了个性能分析发现时间主要耗在三个地方├─ 向量检索1200ms (37%) ├─ LLM 生成1500ms (47%) ├─ 文档预处理500ms (16%)看到没LLM 生成占了快一半时间但这块其实很难优化总不能换更小的模型吧。真正的优化空间在检索和预处理环节。第一招向量检索优化从 1200ms 到 300ms1.1 索引类型选对了吗我一开始用的就是最简单的扁平索引想着数据量不大大概 5 万条文档应该没问题。结果生产环境一跑直接傻眼。后来换成了 HNSW 索引检索时间直接降了 60%。这个提升真的立竿见影。# 之前扁平索引 index faiss.IndexFlatL2(768) # 之后HNSW 索引 index faiss.IndexHNSWFlat(768, faiss.IndexFlatL2(768)) index.hnsw.efConstruction 200 index.hnsw.M 64这里有个坑efConstruction和M参数不是越大越好。我试过 M128建索引时间翻倍但检索性能提升不到 5%。对于 5-10 万量级的数据M64 基本够用。1.2 量化压缩内存和速度的平衡如果数据量再大百万级以上可以考虑 PQ 量化。我试过 OPQ 量化内存占用降了 8 倍检索速度还提升了 20%。不过量化会有精度损失建议先在测试集上评估一下召回率下降是否在可接受范围内。1.3 混合检索真的有必要纯向量检索有个问题有时候关键词匹配比语义匹配更准。比如用户搜发票报销流程向量检索可能返回一堆财务制度相关但不够精准的结果。我后来改成了混合检索向量 BM25然后用 Reciprocal Rank Fusion (RRF) 做结果融合def rrf_fusion(vector_results, bm25_results, k60): scores {} for i, doc_id in enumerate(vector_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k i) for i, doc_id in enumerate(bm25_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k i) return sorted(scores.keys(), keylambda x: scores[x], reverseTrue)这个改动让 Top-5 召回率提升了 15%用户反馈搜得更准了。第二招重排序策略准确率提升 40%检索回来的文档直接丢给 LLM这是我踩过最大的坑。向量检索返回的 Top-10 文档其实相关性差异很大。直接全塞给 LLM不仅浪费 token还可能引入噪声导致模型分心。我加了一个重排序Rerank步骤用 Cross-Encoder 对检索结果重新打分from sentence_transformers import CrossEncoder reranker CrossEncoder(cross-encoder/ms-marco-MiniLM-L-6-v2) def rerank(query, documents, top_k3): pairs [[query, doc] for doc in documents] scores reranker.predict(pairs) ranked_docs sorted(zip(documents, scores), keylambda x: x[1], reverseTrue) return [doc for doc, _ in ranked_docs[:top_k]]这个模型很小推理速度很快10 条文档大概 50ms但效果提升非常明显。我对比过加了 rerank 之后最终答案的准确率从 62% 提升到了 87%。说实话这可能是性价比最高的一次优化。第三招缓存机制重复问题秒回分析日志的时候发现大概 30% 的问题都是重复的。比如怎么请假、报销流程是什么这种高频问题。加个缓存层这些问题的响应时间直接从秒级降到毫秒级import hashlib import redis cache redis.Redis(hostlocalhost, port6379) def get_cache_key(query): returnfrag:qa:{hashlib.md5(query.encode()).hexdigest()} def query_with_cache(query): key get_cache_key(query) cached cache.get(key) if cached: return cached.decode() # 正常 RAG 流程 result rag_pipeline(query) # 缓存 24 小时 cache.setex(key, 86400, result) return result这个改动上线后整体 P95 延迟降了 35%。而且 Redis 缓存还能扛住突发流量一举两得。第四招异步流式响应用户体验提升即然后端优化到极限了那就从前端体验入手。我把响应改成了流式输出LLM 生成一个字就往前端推一个字用户不用等完整答案看到第一个字就知道系统在正常工作。async def stream_response(query): async for chunk in llm.generate_stream(prompt): yield chunk.token心理预期管理很重要。用户等 3 秒出一个完整答案会觉得好慢但如果 200ms 就开始有内容出来哪怕总时间还是 3 秒感知上会快很多。踩坑记录坑 1文档切分粒度一开始我把文档按固定 500 字切分结果发现很多关键信息被切断了。后来改成按语义切分用段落标题、空行做边界效果好了很多。坑 2Embedding 模型选择试过好多 Embedding 模型最后发现text-embedding-3-small在中文场景下表现最好而且便宜。不要盲目追求大模型合适的才是最好的。坑 3Prompt 太长曾经把 10 条检索结果全塞进 Prompt结果 LLM 直接晕了输出质量大幅下降。后来控制在 3-5 条效果反而更好。优化效果对比优化项优化前优化后提升向量检索1200ms300ms75%整体延迟 (P95)3200ms450ms86%答案准确率62%87%40%用户满意度3.14.752%写在最后RAG 这个架构本身不复杂但要做到生产级可用细节真的很多。我这篇分享的只是冰山一角比如还可以做查询改写、多路召回、文档质量评估等等。如果你也在做 RAG 相关的项目欢迎在评论区交流。遇到过什么坑有什么独门技巧一起聊聊。觉得有用的话点个赞或者在看让我知道这篇文章帮到你了。也欢迎关注后续会分享更多 AI 实战经验。参考资料LangChain RAG 优化指南FAISS 官方文档Advanced RAG 论文 (Gao et al., 2023)