延迟要分段测
RAG 的延迟由多段串联构成:查询向量化、向量检索、重排、生成(首 token 与总时长),中间还有网络串行等待。先用分段计时测出各段 P95 占比,再集中优化占比最高的那一段,比全链路撒胡椒面有效得多。每项优化后都要跑评测回归,确认没有用召回质量换延迟。
基本信息
| 分段口径 | 向量化、检索、重排、生成、网络串行 |
|---|---|
| 判定依据 | 各段 P95 占比,平均值会掩盖长尾问题 |
| 常见瓶颈 | 生成首 token 时间与重排的候选规模 |
| 配套要求 | 每次优化后跑评测,确认召回未下降 |
典型使用场景
- 用户反馈慢:先量出各段耗时,避免在不是瓶颈的地方反复折腾
- 扩容决策:确认瓶颈在向量库还是生成侧,决定加哪种资源
- 参数调整:调 efSearch 或 nprobe 换取延迟下降,同时看召回损失
- 上下文瘦身:减少送入生成的片段数与长度,直接压缩生成耗时
- 体验优化:生成侧改流式输出,让用户更早看到内容
分段计时
分段计时
import time
t0 = time.perf_counter()
candidates = retrieve(q) # 检索段
t1 = time.perf_counter()
ranked = rerank(q, candidates) # 重排段
t2 = time.perf_counter()
answer = generate(q, ranked) # 生成段,可另记首 token 时间
t3 = time.perf_counter()
print(f"retrieve={t1-t0:.3f}s rerank={t2-t1:.3f}s gen={t3-t2:.3f}s")
# 统计多轮后取 P95,而不是只看平均值
常见优化手段
| 环节 | 手段 | 注意 |
|---|---|---|
| 检索 | 调低 efSearch 或 nprobe 到评测允许范围 | 用召回指标兜底,别只看延迟 |
| 重排 | 缩小候选池、换更小模型、批量推理 | 候选太少会直接掉召回 |
| 生成 | 流式输出、控制输出长度、命中缓存 | 流式改善感知延迟,不改善总时长 |
| 网络 | 连接池复用、并行取数 | 串行段合并常被忽略 |
优化每项都要跑评测回归:延迟降了但召回掉了,等于把问题转嫁给质量,短期看不出来,长期会体现在用户放弃率上。
先建立延迟看板再谈优化:把各段 P50 与 P95 持续记录下来,才能判断某次改动是真的有效还是当天流量波动的结果。
缓存与预热是小成本大收益的一环:常见问题直接命中缓存、模型连接与索引提前加载,都能省掉冷启动那一段耗时,且不牺牲检索质量。