系列目标 :30 天从 LangChain 入门到企业级部署
今日任务:识别性能瓶颈 → 实现四重加速策略 → 构建高并发低延迟 RAG 服务!
⚡ 一、为什么 RAG 需要性能优化?
一个典型 RAG 请求耗时分解:
- Embedding 生成:300~800ms(CPU)
- 向量检索:50~200ms(Milvus/PGVector)
- LLM 生成:500~2000ms(Qwen 7B 本地)
总耗时 ≈ 1~3 秒
→ 用户体验差,无法支撑高并发
后果:
- ❌ 客服机器人响应慢
- ❌ 内部知识库卡顿
- ❌ 服务器资源耗尽
💡 今天,我们就用四大优化策略,将 P95 延迟压到 500ms 以内!
📊 二、RAG 性能瓶颈分析
表格
| 阶段 | 耗时 | 优化方向 |
|---|---|---|
| 1. 输入 Embedding | 高(CPU 密集) | 缓存、模型量化、批处理 |
| 2. 向量检索 | 中(I/O + 计算) | 索引调优、过滤下推 |
| 3. LLM 生成 | 最高(自回归) | 缓存、流式输出、小模型兜底 |
| 4. 网络/序列化 | 低但可累积 | 异步、连接池 |
✅ 优化原则:缓存 > 并行 > 精简 > 升级硬件
🚀 三、优化策略 1:Embedding 缓存(Redis)
高频问题重复计算 Embedding 是巨大浪费!
ini
# day28_rag_optimization.py
import hashlib
import json
from redis import Redis
redis_client = Redis(host="localhost", port=6379, decode_responses=True)
def get_cached_embedding(text: str) -> list:
key = "emb:" + hashlib.md5(text.encode()).hexdigest()
cached = redis_client.get(key)
if cached:
return json.loads(cached)
return None
def cache_embedding(text: str, embedding: list):
key = "emb:" + hashlib.md5(text.encode()).hexdigest()
redis_client.setex(key, 3600, json.dumps(embedding)) # 缓存1小时
# 在检索前使用
query = "年假政策"
cached_emb = get_cached_embedding(query)
if cached_emb:
results = vectorstore.similarity_search_by_vector(cached_emb, k=3)
else:
emb = embeddings.embed_query(query)
cache_embedding(query, emb)
results = vectorstore.similarity_search_by_vector(emb, k=3)
✅ 效果 :相同 query 嵌入耗时从 600ms → 2ms
💡 对"FAQ 类问题"提升显著
🔄 四、优化策略 2:LLM 输出缓存(语义级)
即使 query 不同,语义相似也可复用答案!
ini
def get_semantic_cache(query: str, threshold: float = 0.95) -> str:
query_emb = embeddings.embed_query(query)
# 在缓存向量库中检索
cached_results = cache_vectorstore.similarity_search_by_vector(
query_emb, k=1, score_threshold=threshold
)
if cached_results:
return cached_results[0].metadata["answer"]
return None
def save_to_semantic_cache(query: str, answer: str):
doc = Document(page_content=query, metadata={"answer": answer})
cache_vectorstore.add_documents([doc])
# 使用
cached_ans = get_semantic_cache("员工有多少天年假?")
if cached_ans:
final_answer = cached_ans
else:
final_answer = rag_chain.run(query)
save_to_semantic_cache(query, final_answer)
🔧
cache_vectorstore可用轻量 Chroma 或专用缓存库✅ 覆盖 paraphrase 场景:"年假几天?" vs "一年有多少天带薪假期?"
⚙️ 五、优化策略 3:异步 & 批处理(提升吞吐)
场景:Web API 支持并发请求
python
# FastAPI 示例
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.post("/rag")
async def rag_api(query: str):
# 异步调用(避免阻塞)
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, run_rag_sync, query)
return {"answer": result}
def run_rag_sync(query: str):
# 同步 RAG 逻辑(含缓存)
return rag_chain.run(query)
✅ 利用 Python 异步 I/O 提升并发能力
进阶:Embedding 批处理(降低 CPU 峰值)
ini
# 若有多个 query 同时到达(如批量导入)
queries = ["Q1", "Q2", "Q3"]
embeddings_list = embeddings.embed_documents(queries) # 比逐个调用快 2~3 倍
💡 Ollama 的
embed_documents已支持批处理
📉 六、优化策略 4:流式输出(改善用户体验)
用户不必等 2 秒才看到第一个字!
ini
from langchain_core.callbacks import StreamingStdOutCallbackHandler
# 初始化流式 LLM
streaming_llm = ChatOllama(
model="qwen:7b",
temperature=0,
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()]
)
# 构建流式 Chain(需自定义)
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
prompt = PromptTemplate.from_template("根据以下内容回答:{context}\n\n问题:{question}")
streaming_chain = LLMChain(llm=streaming_llm, prompt=prompt)
# 在 Web 中返回 SSE
@app.get("/rag-stream")
async def rag_stream(query: str):
retriever = vectorstore.as_retriever()
docs = retriever.get_relevant_documents(query)
context = "\n".join([d.page_content for d in docs])
async def event_stream():
full_response = ""
for chunk in streaming_chain.stream({"context": context, "question": query}):
full_response += chunk
yield f"data: {chunk}\n\n"
# 可选:保存到缓存
save_to_semantic_cache(query, full_response)
return StreamingResponse(event_stream(), media_type="text/event-stream")
✅ 感知延迟降低 70% :用户 200ms 内看到首字
🧪 七、其他优化技巧
表格
| 技巧 | 说明 |
|---|---|
| 向量维度压缩 | 用 PCA 将 768 维 → 256 维(精度损失 <2%,速度↑) |
| 小模型兜底 | 简单问题用 Qwen 1.8B 快速回答 |
| 连接池 | Milvus/PGVector 客户端复用连接 |
| 预热 | 服务启动时加载模型到内存 |
| 监控 | Prometheus 记录各阶段耗时(Embedding/Retrieve/Generate) |
💡 推荐组合:
- 高频问题 → Embedding 缓存 + 答案缓存
- 长尾问题 → 流式输出 + 异步
- 批量任务 → Embedding 批处理
📈 八、性能对比(实测数据)
表格
| 优化前 | 优化后 | 提升 |
|---|---|---|
| 平均延迟:1800ms | 平均延迟:420ms | 4.3x |
| P95 延迟:2500ms | P95 延迟:680ms | 3.7x |
| QPS(4核):3 | QPS(4核):22 | 7.3x |
📌 测试环境:Intel i7, 32GB RAM, Ollama + Qwen 7B + Milvus Standalone
📦 九、配套代码结构
bash
langchain-30-days/
└── day28/
├── rag_with_cache.py # Embedding + 语义缓存
├── rag_streaming_api.py # FastAPI + SSE 流式输出
└── performance_bench.py # 压测脚本(locust)
📝 十、今日小结
- ✅ 识别了 RAG 三大性能瓶颈
- ✅ 实现了 Embedding 缓存 与 语义级答案缓存
- ✅ 掌握了 异步 API 与 流式输出 提升用户体验
- ✅ 学会了 批处理 降低 CPU 峰值
- ✅ 了解了维度压缩、小模型兜底等进阶技巧
🎯 明日预告:Day 29 ------ RAG 监控与告警!用 Prometheus + Grafana 打造运维看板!