RAG低延迟架构设计:从5秒到500ms的优化全链路
导读:你的 RAG 系统本地测试很快,一上线用户就抱怨"转圈圈";Embedding 调用远程 API 耗时 800ms,LLM 生成又要 2 秒,重排序模型再卡 500ms------用户等 5 秒才看到第一个字。延迟不是"某个环节慢",而是整条链路的累积。本文从离线在线分层架构出发,拆解 Embedding、向量检索、重排序、LLM 生成、架构设计五个环节的延迟优化策略,附项目对照和可直接落地的配置建议。
适合读者:
- RAG 系统延迟高、需要系统优化响应速度的开发者
- 需要设计低延迟 RAG 架构的技术负责人
- 准备 RAG 面试、需要回答"怎么降低RAG延迟"的同学
- 在用 FastAPI + 远程 API 搭建 RAG 后端的工程师
阅读收益:
- 理解离线/在线分层架构的核心思想
- 掌握五个优化方向的具体策略和代码实现
- 学会用 SSE 流式输出大幅降低用户体感延迟
- 理解为什么"LLM 生成是最大的延迟来源"
- 获得一套可直接落地的低延迟 RAG 优化检查清单
目录
- 问题背景:RAG延迟从哪来
- [架构分层:离线预处理 + 在线查询](#架构分层:离线预处理 + 在线查询)
- 优化方向一:Embedding向量化
- 优化方向二:向量数据库检索
- 优化方向三:重排序精筛
- 优化方向四:LLM生成(最大延迟来源)
- 优化方向五:架构层面
- 完整延迟预算表
- 踩坑清单:低延迟设计的8个关键问题
- 面试速答版
- 总结与延伸
- 文末互动
1. 问题背景:RAG延迟从哪来
1.1 一个真实场景
用户问"怎么申请公积金提取",系统内部经历了什么:
时间轴:
0ms 用户提问
50ms Embedding API 调用(远程硅基流动)
850ms 等待 Embedding 返回 1024 维向量 ❌
900ms 向量检索(Milvus,本地)
950ms BM25 检索(本地)
1000ms RRF 融合
1100ms 重排序 API 调用(远程硅基流动)
1600ms 等待重排序返回 ❌
1650ms 证据过滤(本地)
1700ms 构造 Prompt(本地)
1750ms LLM API 调用(远程 DeepSeek)
3750ms 等待 LLM 首 token ❌❌❌
5000ms 完整回答返回
三个最大延迟来源:Embedding 远程调用(800ms)、重排序远程调用(500ms)、LLM 首 token(2000ms)。
1.2 延迟拆解
| 环节 | 耗时 | 占比 | 优化空间 |
|---|---|---|---|
| Embedding | 800ms | 16% | 本地化部署、缓存 |
| 向量检索 | 50ms | 1% | 已很快,HNSW 优化 |
| BM25 检索 | 50ms | 1% | 已很快 |
| RRF 融合 | 50ms | 1% | 已很快 |
| 重排序 | 500ms | 10% | 轻量化模型、减少候选 |
| LLM 首 token | 2000ms | 40% | 流式输出、精简上下文 |
| LLM 完整输出 | 1600ms | 32% | 流式输出降低体感 |
| 合计 | ~5000ms | 100% |
结论:LLM 生成占 72%(首 token + 完整输出),是最大延迟来源。
2. 架构分层:离线预处理 + 在线查询
2.1 核心思想
离线层(预处理好,不阻塞在线):
文档解析 → 文本分块 → Embedding 向量化 → 建索引 → 存向量库
在线层(只处理用户查询):
接收问题 → Embedding → 检索 → 重排序 → 构造 Prompt → LLM 生成 → 返回
原则:离线层做的事情越多,在线层做的事情越少,延迟越低。
2.2 离线层设计
python
"""离线预处理流水线"""
class OfflinePipeline:
def process_document(self, file_path: str):
# 1. 解析文档
doc = parse_pdf(file_path)
# 2. 文本分块
chunks = split_text(doc.content, chunk_size=420)
# 3. 批量 Embedding(离线批处理,不阻塞)
embeddings = self.embedding_model.embed_documents(
[c.content for c in chunks],
batch_size=32, # 批量处理,提升吞吐
)
# 4. 入库
self.vectorstore.upsert(chunks, embeddings)
# 5. 更新 BM25 索引
self.bm25_index.update(chunks)
2.3 在线层设计
python
"""在线查询服务(FastAPI)"""
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.post("/api/chat")
async def chat(request: ChatRequest):
# 在线层只做:检索 + 生成
# Embedding、重排序、LLM 都在这里调用
result = await rag_service.ask(request.question)
return {"answer": result}
@app.post("/api/chat/stream")
async def chat_stream(request: ChatRequest):
# SSE 流式输出:首 token 立刻返回
async def generate():
async for token in rag_service.ask_stream(request.question):
yield f"data: {token}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
3. 优化方向一:Embedding向量化
3.1 方案:本地部署 Embedding 模型
远程 API 调用最大的问题是网络延迟和并发限制。
python
# 当前:远程 API(硅基流动)
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="BAAI/bge-m3",
api_key=os.getenv("SILICONFLOW_KEY"),
base_url="https://api.siliconflow.cn/v1",
)
# 每次调用 = 网络往返 + 排队等待
# 优化:本地部署(Ollama / vLLM / Xinference)
from langchain_community.embeddings import OllamaEmbeddings
embeddings = OllamaEmbeddings(
model="bge-m3",
base_url="http://localhost:11434",
)
# 本地推理 = 无网络延迟 + 无并发限制
延迟对比:
| 方案 | 延迟 | 成本 | 适用场景 |
|---|---|---|---|
| 远程 API | 200-1000ms | 按量计费 | 小规模、快速验证 |
| 本地 GPU | 10-50ms | 机器成本 | 生产环境、高并发 |
| 本地 CPU | 50-200ms | 机器成本 | 中等规模、预算有限 |
3.2 缓存高频查询
python
from functools import lru_cache
import hashlib
class EmbeddingCache:
def __init__(self, maxsize=10000):
self.cache = {}
def get_key(self, text: str) -> str:
return hashlib.md5(text.encode()).hexdigest()
def get(self, text: str):
return self.cache.get(self.get_key(text))
def set(self, text: str, embedding: list):
self.cache[self.get_key(text)] = embedding
# 使用
cache = EmbeddingCache()
async def embed_with_cache(text: str):
cached = cache.get(text)
if cached:
return cached
embedding = await embeddings.aembed_query(text)
cache.set(text, embedding)
return embedding
效果:高频相同问题(如"怎么申请公积金")直接命中缓存,Embedding 延迟从 800ms → 0ms。
4. 优化方向二:向量数据库检索
4.1 索引优化
详见上一篇《RAG向量数据库优化实战》,核心要点:
- 大数据量用 HNSW 索引
- M = 16-32,ef_construction >= 128
- ef >= top_k * 2
- 上线必做 Recall@K 压测
4.2 并行检索
向量检索和 BM25 检索可以并行执行,不是串行:
python
import asyncio
async def hybrid_retrieve(question: str) -> list[Document]:
# 并行执行两路检索
vector_task = vector_search(question, k=10)
bm25_task = bm25_search(question, k=10)
vector_results, bm25_results = await asyncio.gather(
vector_task, bm25_task
)
# RRF 融合
return rrf_fusion(vector_results, bm25_results)
效果:串行 = 50ms + 50ms = 100ms,并行 = max(50ms, 50ms) = 50ms。
4.3 连接复用
python
# 错误:每次查询新建连接
async def search(query: str):
client = httpx.AsyncClient() # 每次都新建
return await client.post(...)
# 正确:全局复用连接池
http_client = httpx.AsyncClient(
limits=httpx.Limits(max_connections=100),
timeout=httpx.Timeout(30.0),
)
async def search(query: str):
return await http_client.post(...) # 复用 TCP 连接
5. 优化方向三:重排序精筛
5.1 减少重排序候选数量
python
# 当前:重排序处理 20 条
reranked = await rerank(question, fused_results[:20])
evidence = filter_by_score(reranked, threshold=0.3)[:5]
# 优化:只重排序前 10 条(足够选出 top-5)
reranked = await rerank(question, fused_results[:10])
evidence = filter_by_score(reranked, threshold=0.3)[:5]
效果:重排序延迟从 500ms → 250ms(候选减半,延迟近似减半)。
5.2 轻量重排序模型
python
# 当前:bge-reranker-v2-m3(效果强但慢)
# 优化:交叉编码器换为轻量模型,或蒸馏版
# 极端低延迟场景:关闭重排序
if latency_critical:
evidence = fused_results[:5] # 跳过重排序
else:
evidence = await rerank(question, fused_results[:10])
权衡:关闭重排序牺牲 5-10% 的精度,但节省 500ms 延迟。适合对延迟极度敏感的场景。
6. 优化方向四:LLM生成(最大延迟来源)
6.1 精简上下文
传给 LLM 的 token 越多,生成越慢。控制送入 LLM 的 chunk 数量:
python
# 当前:传入 10 条 chunk,每条 500 字 = 5000 字 ≈ 1500 tokens
context = "\n\n".join(doc.content for doc in evidence[:10])
# 优化:只传入 top-3 条最相关的 chunk
context = "\n\n".join(doc.content for doc in evidence[:3])
# 1500 tokens → 450 tokens,输入减少 70%
效果:输入 token 减少,首 token 延迟降低,总生成时间减少。
6.2 SSE 流式输出(最重要)
python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from langchain_core.messages import HumanMessage
app = FastAPI()
@app.post("/api/chat/stream")
async def chat_stream(request: ChatRequest):
"""SSE 流式输出:首 token 立刻返回,用户体感延迟大幅降低"""
# 1. 先做检索(不可流式)
evidence = await retrieve(request.question)
prompt = build_prompt(request.question, evidence)
# 2. 流式调用 LLM
async def generate():
async for chunk in model.astream(prompt):
yield f"data: {chunk.content}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
)
流式 vs 非流式的用户体验对比:
| 模式 | 用户看到第一个字 | 完整回答 | 体感延迟 |
|---|---|---|---|
| 非流式 | 5000ms | 5000ms | 极慢(干等) |
| 流式 | 2000ms | 5000ms | 快(立刻有反馈) |
SSE 流式输出把"首 token 延迟"从 5000ms 降到 2000ms,体感提升 60%。
6.3 选择推理速度快的模型
| 模型 | 首 token 延迟 | 质量 | 适用场景 |
|---|---|---|---|
| GPT-4 | 2-3s | 最高 | 高质量要求 |
| DeepSeek-V3 | 1-2s | 高 | 生产平衡 |
| DeepSeek-V2.5 | 0.5-1s | 中高 | 低延迟场景 |
| Qwen-Turbo | 0.3-0.5s | 中 | 极致延迟 |
建议:默认用 DeepSeek-V3,延迟敏感场景切换到轻量模型。
6.4 Prompt 精简
python
# 冗长 Prompt(慢)
system_prompt = """你是一个企业政策问答助手。你的职责是根据用户的问题,
从提供的参考资料中找出最相关的信息,并用简洁友好的语言回答。
你需要注意以下几点:
1. 只能基于参考资料回答...
2. 如果资料不足请说明...
3. 需要标注引用来源...
...(500字)
"""
# 精简 Prompt(快)
system_prompt = "基于参考资料回答,资料不足时说明,标注引用来源。"
# Token 从 200 → 20,输入减少 90%
7. 优化方向五:架构层面
7.1 多级缓存
python
class MultiLevelCache:
def __init__(self):
self.l1_cache = {} # L1:内存缓存(最近1000条)
self.l2_cache = None # L2:Redis(最近10万条)
async def get(self, question: str) -> str | None:
# L1 检查
if question in self.l1_cache:
return self.l1_cache[question]
# L2 检查
if self.l2_cache:
result = await self.l2_cache.get(question)
if result:
self.l1_cache[question] = result # 回填 L1
return result
return None
async def set(self, question: str, answer: str):
self.l1_cache[question] = answer
if self.l2_cache:
await self.l2_cache.set(question, answer, ttl=3600)
效果:高频问题命中缓存,跳过完整 RAG 链路,延迟从 5000ms → 10ms。
7.2 服务拆分
单体架构(所有东西跑在一起):
┌─────────────────────────────────────┐
│ FastAPI 服务 │
│ ├── 文档解析 │
│ ├── Embedding │
│ ├── 向量检索 │
│ ├── 重排序 │
│ └── LLM 生成 │
└─────────────────────────────────────┘
拆分架构(离线在线分离):
┌─────────────┐ ┌─────────────────┐
│ 离线服务 │ │ 在线 RAG 服务 │
│ ├── 文档解析 │ │ ├── 检索 │
│ ├── 分块 │ │ ├── 重排序 │
│ ├── Embedding│ │ └── LLM 生成 │
│ └── 建索引 │ └─────────────────┘
└─────────────┘
7.3 异步处理
python
# 文档上传不阻塞接口
@app.post("/api/upload")
async def upload_document(file: UploadFile):
# 保存文件后立即返回
file_path = await save_file(file)
# 异步处理:解析 → 分块 → Embedding → 入库
asyncio.create_task(process_document_async(file_path))
return {"message": "文件已上传,正在处理中"}
async def process_document_async(file_path: str):
"""后台异步处理文档"""
doc = parse_document(file_path)
chunks = split_text(doc)
embeddings = await embed_documents(chunks)
await vectorstore.upsert(chunks, embeddings)
7.4 资源隔离
部署架构:
┌─────────────┐
│ 负载均衡 │
└──────┬──────┘
│
┌──────┴──────┐
│ │
▼ ▼
┌──────┐ ┌──────┐
│RAG API│ │RAG API│ (FastAPI 服务,2-4核CPU)
└──┬───┘ └──┬───┘
│ │
└─────┬─────┘
│
┌──────┴──────┐
│ 内网专线 │
└──────┬──────┘
│
┌──────┴──────┐
│ │
▼ ▼
┌──────┐ ┌──────┐
│Milvus│ │Redis │ (向量库+缓存)
└──────┘ └──────┘
独立部署:
┌──────────┐
│Embedding │ (GPU 机器,推理 Embedding)
│ 服务 │
└──────────┘
┌──────────┐
│Rerank │ (GPU 机器,推理重排序)
│ 服务 │
└──────────┘
┌──────────┐
│LLM │ (GPU 机器,推理大模型)
│ 服务 │
└──────────┘
原则:向量库、Embedding、Rerank、LLM 分开部署,避免互相抢占 GPU。
8. 完整延迟预算表
8.1 优化前 vs 优化后
| 环节 | 优化前 | 优化后 | 优化手段 |
|---|---|---|---|
| Embedding | 800ms | 50ms | 本地部署 + 缓存 |
| 向量检索 | 50ms | 30ms | HNSW 调参 |
| BM25 检索 | 50ms | 30ms | 并行执行 |
| RRF 融合 | 50ms | 20ms | 优化算法 |
| 重排序 | 500ms | 200ms | 减少候选到10条 |
| LLM 首 token | 2000ms | 800ms | 精简上下文 + 快模型 |
| LLM 完整输出 | 1600ms | 1600ms | 流式输出降低体感 |
| 体感延迟 | 5000ms | ~1000ms |
注意:流式输出不改变总生成时间,但把"首 token 延迟"从 5000ms 降到 1000ms,用户体验大幅提升。
9. 踩坑清单:低延迟设计的8个关键问题
| 序号 | 问题 | 现象 | 原因 | 解决方案 |
|---|---|---|---|---|
| 1 | Embedding 远程调用 | 每次 500ms+ | 网络延迟 | 本地部署或加缓存 |
| 2 | 串行检索 | 向量+BM25 串行 = 100ms | 没并行 | asyncio.gather |
| 3 | 重排序候选太多 | 重排序 500ms+ | top-k 太大 | 只重排前 10 条 |
| 4 | 传入 LLM 的 chunk 太多 | LLM 生成慢 | 上下文太长 | 只传 top-3 条 |
| 5 | 不用流式输出 | 用户干等 5 秒 | 非流式必须等完整输出 | SSE 流式输出 |
| 6 | 每次新建 HTTP 连接 | API 调用慢 | TCP 握手开销 | 连接池复用 |
| 7 | 没有查询缓存 | 相同问题重复算 | 没缓存高频问题 | L1/L2 多级缓存 |
| 8 | 文档上传阻塞接口 | 上传 10MB PDF 卡住 | 同步处理 | 异步后台处理 |
10. 面试速答版
低延迟 RAG 架构分两层:离线层做文档解析/分块/向量化/建索引(预处理好),在线层只跑检索+生成。五个优化方向:
- Embedding:本地部署或用缓存,把 800ms 降到 50ms
- 向量检索:HNSW 索引 + 并行检索 + 连接复用
- 重排序:减少候选数量(10条),极端场景可关闭
- LLM 生成:精简上下文(传3条而非10条)、用 SSE 流式输出降低体感、选快模型
- 架构层面 :多级缓存(相同问题直接返)、服务拆分、异步处理、资源隔离
一句话:最大延迟在 LLM 生成------控 chunk 数量、开 SSE 流式、选快模型;重排很耗时,极端场景可关掉牺牲精度换速度。
11. 总结与延伸
11.1 核心知识点回顾
低延迟 RAG = 离线预处理 + 在线优化
五个优化方向:
Embedding:本地化 + 缓存
向量检索:HNSW + 并行 + 连接复用
重排序:减少候选 + 轻量模型
LLM 生成:精简上下文 + SSE 流式 + 快模型
架构:多级缓存 + 服务拆分 + 异步 + 资源隔离
延迟预算(优化后目标):
体感延迟 < 1000ms(流式首 token)
完整回答 < 3000ms
11.2 延伸方向
- 预计算热门查询:对高频问题预先生成回答,命中时直接返回
- 边缘计算:把 Embedding 和向量检索放到 CDN 边缘节点
- 模型量化:用 INT8/INT4 量化模型,推理速度提升 2-4 倍
- 推测解码(Speculative Decoding):用小模型生成草稿,大模型验证,加速生成
12. 文末互动
你的 RAG 系统现在的端到端延迟是多少?哪个环节最慢------Embedding、检索、重排序还是 LLM 生成?评论区分享你的延迟数据和优化经验。
思考题:如果一个高频问题"怎么申请公积金"每天有 1000 次查询,但政策每个月更新一次,你的缓存策略应该怎么设计才能既省延迟又避免返回过期回答?欢迎在评论区讨论。
本文聚焦 RAG 系统的低延迟架构设计。如果觉得有帮助,欢迎点赞收藏,后续会更新预计算和边缘计算的进阶内容。