RAG 系统性能瓶颈在哪里?一篇讲清楚如何优化
很多人第一次做 RAG(Retrieval-Augmented Generation,检索增强生成)时,会觉得它的流程很简单:
用户提问 → 检索资料 → 把资料交给大模型 → 生成答案。
但真正上线之后,经常会遇到几个问题:
- 响应越来越慢
- 并发一高就扛不住
- 检索结果很多,但答案反而变差
- Token 消耗越来越大
- 向量数据库明明很快,系统整体却还是很慢
原因很简单:
RAG 的性能问题通常不是出在某一个点,而是整条链路共同造成的。
一个典型的 RAG 请求大致可以拆成:
用户请求 → Query 处理 → Embedding → 向量检索 → Rerank → Prompt 拼接 → LLM 推理 → 返回结果
真正需要优化的,也是这几步。
一、最大的瓶颈:LLM 推理
大多数 RAG 系统里,真正最耗时间的通常不是向量检索,而是 大模型生成答案。
例如:
向量数据库可能几十毫秒就能返回结果,但大模型生成一段几百字的回答,可能需要几秒。
尤其是当你把大量检索结果全部塞进 Prompt:
用户问题
+
10 段文档
+
历史聊天记录
+
系统提示词
Prompt 可能瞬间膨胀到几千甚至几万个 Token。
Prompt 越长:
推理越慢、成本越高、模型越容易被无关信息干扰。
优化方法
第一件事不是换 GPU,而是:
减少送给 LLM 的 Token。
比如原来:
TopK = 20
一次检索 20 个 Chunk 全塞进去。
可以调整成:
Retrieve Top 20
↓
Rerank
↓
只保留 Top 3~5
↓
交给 LLM
本质上就是一句话:
多检索,少输入。
检索阶段可以稍微宽松,但真正送进大模型的内容必须精简。
二、第二个瓶颈:检索质量,而不是检索速度
很多人优化 RAG 时会盯着:
Vector Search = 100ms
然后花大量时间把它优化到:
30ms
但实际上,如果整个请求要 4 秒,这 70ms 通常不是最重要的问题。
真正影响体验的是:
检索出来的东西到底对不对。
例如用户问:
退款期限是多少?
结果检索出来:
Chunk 1:退款规则
Chunk 2:公司简介
Chunk 3:支付方式
Chunk 4:会员协议
Chunk 5:售后地址
虽然搜索很快,但只有第一条真正有用。
如果这些内容全部塞给模型,就会造成:
无关上下文
↓
Prompt 变长
↓
LLM 推理变慢
↓
答案准确率下降
因此 RAG 优化里一个非常重要的原则是:
检索准确率比单纯追求检索速度更重要。
三、Chunk 切分不合理
RAG 系统里一个非常容易被低估的问题,就是:
文档怎么切。
Chunk 太大,例如:
每块 3000 Token
优点是上下文完整。
但缺点也很明显:
- 检索粒度太粗
- 无关信息多
- Prompt 很长
- Token 成本高
Chunk 太小,例如:
每块 50 Token
又可能导致语义被切碎。
例如原文:
退款申请需要在购买后 30 天内提交。
超过 30 天将不支持退款。
如果刚好被切成两个 Chunk,就可能导致检索结果缺少完整语义。
比较常见的做法是:
Chunk Size:300~800 Token
Overlap:10%~20%
但这里没有万能数字。
技术文档、合同、代码、客服知识库,适合的 Chunk 大小都不同。
所以真正正确的思路是:
按照语义结构切,而不是机械地按照字符数切。
例如优先按照:
标题
段落
章节
函数
FAQ
表格
进行切分。
四、TopK 过大
这是很多 RAG 系统最常见的问题。
为了防止“漏掉答案”,直接:
TopK = 20
甚至:
TopK = 50
结果就是:
检索更多内容
↓
Prompt 更长
↓
LLM 更慢
↓
Token 成本更高
↓
噪声更多
实际上很多场景:
最终 TopK = 3~5
已经足够。
一个比较好的架构是:
Vector Search Top 20
↓
Rerank
↓
Top 5
↓
LLM
这样既保证召回率,又不会让 Prompt 过度膨胀。
五、Rerank 可能成为隐藏瓶颈
为了提升检索质量,现在很多 RAG 会使用:
向量召回
+
Reranker
例如:
Query
↓
向量搜索 Top 50
↓
Cross Encoder Rerank
↓
Top 5
准确率通常会明显提高。
但是问题也来了:
Rerank 本身也是模型推理。
如果每次都对几十甚至上百个文档进行重排序,并发一高,Reranker 就可能成为新的性能瓶颈。
解决方法一般是控制候选集,例如:
错误:
Top 100
↓
全部 Rerank
更合理:
Top 20
↓
Rerank
↓
Top 5
不要让 Reranker 干向量数据库应该干的活。
六、Embedding 重复计算
另一个很常见的问题是:
相同内容被重复 Embedding。
比如用户反复查询类似问题:
退款时间多久?
退款期限是多少?
多久可以申请退款?
如果每次都重新计算 Embedding,就会产生重复开销。
优化方法主要有两个:
文档 Embedding 离线计算
文档上传之后:
文档
↓
Chunk
↓
Embedding
↓
写入 Vector DB
这一步应该提前完成。
不要用户查询时才临时计算文档向量。
Query Embedding 加缓存
对于高频问题,可以缓存:
Query → Embedding
如果 Query 高度重复,可以直接复用。
七、数据库不是越高级越好
很多团队一遇到 RAG 性能问题,就开始换:
FAISS
↓
Milvus
↓
Pinecone
↓
Weaviate
↓
Elasticsearch
但实际上,大部分早期 RAG 系统的问题并不在数据库。
如果只有:
10 万
100 万
甚至几百万 Chunk
很多成熟的向量数据库都能轻松处理。
真正应该关注的是:
索引类型
Embedding 维度
TopK
过滤条件
数据量
并发量
比如:
HNSW
IVF
PQ
不同索引在:
速度
内存
召回率
之间存在不同取舍。
核心不是“哪个数据库最强”,而是:
你的规模到底需不需要这么复杂。
八、缓存通常是最便宜的性能优化
RAG 系统里有很多东西都可以缓存。
例如:
Query Embedding
检索结果
Rerank 结果
最终回答
假设大量用户都在问:
如何退款?
完全没必要:
Embedding
↓
Search
↓
Rerank
↓
LLM
每次全部重新执行。
可以直接:
Query
↓
Cache
↓
Answer
从几秒下降到几十毫秒都有可能。
因此很多高并发 RAG 系统最终都会大量使用:
Redis
+
Semantic Cache
普通缓存针对完全相同的问题。
Semantic Cache 则可以处理:
怎么退款?
如何申请退款?
退款流程是什么?
这种语义相似的问题。
九、并发问题往往比单次延迟更重要
开发阶段可能觉得系统很快:
一个用户:2 秒
但上线以后:
100 个用户同时请求
情况就完全不同。
瓶颈可能出现在:
Embedding API
Vector DB
Reranker
LLM API
数据库连接池
网络 IO
因此生产级 RAG 一般会使用:
异步调用
连接池
批量 Embedding
Batch Rerank
请求队列
限流
缓存
目标不是只优化:
单请求延迟
还要优化:
吞吐量
QPS
P95
P99
因为平均 2 秒没有意义。
如果:
P50 = 2 秒
P99 = 20 秒
用户依然会觉得系统很慢。
十、RAG 性能优化的正确顺序
很多人一开始就优化数据库,实际上顺序往往反了。
更推荐按照下面的顺序排查:
1. 看总延迟
↓
2. 拆分每个阶段耗时
↓
3. 看 Prompt Token
↓
4. 看 TopK
↓
5. 看 Chunk
↓
6. 看 Rerank
↓
7. 加缓存
↓
8. 最后优化基础设施
例如记录:
Embedding 80ms
Vector Search 60ms
Rerank 300ms
LLM 2800ms
这时候非常明显:
LLM = 2800ms
才是真正的大头。
如果你花两天把:
Vector Search
60ms → 30ms
用户基本感觉不到。
但如果通过减少 Prompt:
LLM
2800ms → 1500ms
体验就会明显提升。
总结
RAG 系统的性能瓶颈,通常集中在几个地方:
LLM 推理、Prompt 太长、检索噪声、Chunk 不合理、TopK 太大、Rerank 太重、重复计算以及高并发。
真正有效的优化思路可以概括成一句话:
尽可能早地过滤无关信息,尽可能少地把内容送给大模型。
所以一个比较合理的生产级 RAG 流程通常是:
User Query
↓
Query Rewrite
↓
Embedding
↓
Vector Search
Top 20
↓
Metadata Filter
↓
Rerank
Top 5
↓
Context Compression
↓
LLM
↓
Answer
再配合:
Cache
Async
Batch
Monitoring
基本就是一套比较完整的 RAG 性能优化思路。
最终真正要关注的,不是:
“我的向量数据库够不够快?”
而是:
“一次请求里,到底哪一步最慢、哪一步最贵、哪一步产生了最多无用信息?”
先把链路测清楚,再针对最大瓶颈优化,往往比盲目换模型、换数据库有效得多。