Files
rag/docs/RAG性能分析报告.md
lacerate551 b966be5417 fix: 修正 RAG性能分析报告中的错误和遗漏
- INTENT_MAX_TOKENS 2048 不再标注为'浪费',推理模型思考链需 ~1000 token
- 删除 P2 建议'INTENT_MAX_TOKENS 降低 2048→512'(推理模型不可降)
- P0 优化建议标注 deepseek-v4-flash 额度已尽,需寻找替代轻量模型
- Rerank 配置补充云端 xop3qwen8breranker(讯飞云)信息
- §6.4 休眠模块:AgenticRAG 类已在 v4.0 删除,更新描述
- 阶段 7 补充两管线断裂问题:chart_contexts 降门槛、P0 安全网、答案后过滤鸡生蛋问题
- 阶段 9 补充推理模型 content 为空问题和 reasoning_content 回退机制
- 阶段 10 图片后过滤补充 _filter_images_by_answer 函数名
2026-06-22 13:08:14 +08:00

20 KiB
Raw Permalink Blame History

RAG 知识库问答系统 — 性能分析报告

基于 15 题基准测试 + 代码静态分析 | 2026-06-22

基准测试环境:本地 deepseek-v4-flash 模型,服务器使用 mimo-v2.5 时延迟特征可能不同


一、完整请求管线总览

一次 /rag/stream 请求经历 10 个串行阶段每个阶段必须等待前一阶段完成后才能开始。SSE 流式响应在 LLM 生成阶段才开始产出用户可见内容,前 8 个阶段用户感知为"等待中"。

用户提问
  │
  ├─ 0. 输入校验 & 安全过滤          ~1ms
  ├─ 1. 意图分析LLM 调用)          ~3500-5000ms  ◄── 瓶颈 #1
  ├─ 2. 语义缓存查找                  ~50-200ms
  ├─ 3. 混合检索(向量+BM25+图片)     ~200-600ms
  ├─ 4. RRF 融合 + Rerank             ~300-800ms
  ├─ 5. MMR 去重                      ~5-20ms
  ├─ 6. 救援管线3层               ~5-30ms
  ├─ 7. 图片选择 + VLM 增强           ~10-50ms
  ├─ 8. 上下文构建                    ~5-15ms
  ├─ 9. LLM 流式生成                  ~5000-15000ms ◄── 瓶颈 #2
  └─ 10. 后处理(对齐/引用/会话存储)   ~50-200ms

总耗时:典型请求 10-20 秒(含流式输出时间),其中 LLM 调用占 80%以上


二、各阶段详细分析

阶段 1意图分析 — 最大瓶颈之一

位置core/intent_analyzer.pyanalyze() 方法

功能:一次非流式 LLM 调用,同时完成问题改写、指代消解、意图分类、子查询生成。

执行流程

精确缓存查找Dict, O(1)
  ↓ 未命中
语义缓存查找FAISS, cosine ≥ 0.92
  ↓ 未命中
LLM 调用非流式JSON 输出)    ◄── 主要耗时
  ↓
写入双层缓存 + 返回 IntentAnalysis

关键参数

参数 服务器值 说明
INTENT_MODEL mimo-v2.5 与主模型共用,无法使用轻量模型
INTENT_TEMPERATURE 0.1 低温度保证确定性输出
INTENT_MAX_TOKENS 2048 推理模型思考链 ~1000 token + JSON 输出 ~200 token配置合理
INTENT_HISTORY_WINDOW 6 取最近 6 条历史消息
精确缓存上限 500 条 内存 Dict重启清零
语义缓存阈值 0.92 余弦相似度,过于严格则命中率低

实测耗时deepseek-v4-flash3500-5000ms。mimo-v2.5 因模型更大,预期 4000-7000ms

瓶颈分析

  1. 非流式调用:必须等待完整 JSON 响应才能解析,无法提前返回。这是所有阶段中唯一必须完整等待的 LLM 调用。
  2. 模型过重:意图分析本质是分类+改写任务,不需要 mimo-v2.5 这样的推理大模型。换用轻量非推理模型可减少 30-40% 延迟,但百炼 deepseek-v4-flash 额度已尽,需寻找其他可用轻量模型。
  3. MAX_TOKENS 配置合理mimo-v2.5 是推理模型,思考链消耗 ~1000 tokenJSON 输出 ~200 token2048 是必要预算(此前 1024 导致 content 为空、全部输出进入 reasoning_content 引发解析错误)。
  4. 缓存命中率低:精确缓存 key 包含历史上下文,同一用户连续问不同问题时不会命中;语义缓存阈值 0.92 过于严格。

阶段 2语义缓存查找

位置core/semantic_cache.py

功能FAISS 向量索引,用 embedding 余弦相似度匹配历史问答,命中则跳过整个检索+生成流程。

查找逻辑

  1. "{query}|{collections}" 编码为向量
  2. FAISS IndexFlatIP 搜索 top-1向量已归一化内积=余弦)
  3. 阈值 ≥ 0.92 → 命中
  4. 验证 cache_type == "rag_answer"(区分意图缓存和问答缓存)

性能50-200ms主要是 embedding 编码耗时FAISS 搜索本身 <1ms

瓶颈分析:此阶段本身不慢,但受限于 0.92 的高阈值,实际命中率较低。同一个问题的不同表述方式(如"智启平台支持哪些数据源" vs "智启能对接什么数据库")余弦相似度可能在 0.85-0.91 之间,无法命中。降低阈值会引入误命中风险。

阶段 3混合检索

位置core/engine.pysearch_knowledge()

功能:三路并行召回 + RRF 融合

三路召回

召回路径 数量 后端 耗时
向量检索 recall_k = max(20, 30×3) = 90 ChromaDB + bge-base-zh-v1.5 (CPU) 100-300ms
BM25 关键词 top_k=30 rank_bm25 (jieba 分词) 10-30ms
图片独立召回 n_results=5 ChromaDB过滤 chunk_type 20-50ms

RRF 融合Reciprocal Rank Fusion

score = weight / (k + rank + 1)k = 60

动态权重(按查询长度自适应):
  alpha = clamp((len(query) - 15) / 35, 0, 1)
  vector_weight = 0.3 + 0.4 × alpha    # 短查询 0.3,长查询 0.7
  bm25_weight = 1.0 - vector_weight

性能:总计 200-600ms。Embedding 编码是主要耗时CPU 推理ChromaDB 查询本身很快。

瓶颈分析

  1. Embedding 在 CPU 上运行bge-base-zh-v1.5 编码 90 个候选文档需要 100-200ms。GPU 可降至 10-30ms但当前服务器未配置 GPU。
  2. 召回数量偏大recall_k=90 意味着 embedding 编码量大。实际 Rerank 只取 20 个,多召回的 70 个是浪费。可降低 RECALL_MULTIPLIER 从 3 到 2。
  3. BM25 无瓶颈纯内存计算jieba 分词+BM25 打分共 10-30ms。

阶段 4Rerank

位置core/engine.pyrerank_results()

功能CrossEncoder 精排,对融合后的候选重打分

服务器配置RERANK_BACKEND = "local",使用 ONNX 格式的 bge-reranker-base。云端备选为 xop3qwen8breranker(讯飞云 APIRERANK_BACKEND = "cloud""fallback" 时启用。

流程

  1. 检查 Rerank 缓存MD5 of query + sorted_doc_ids
  2. 缓存未命中 → 调用 ONNX 推理predict(query, doc) × N
  3. 按分数排序,截取 RERANK_TOP_K=15

性能

  • 缓存命中:<1ms
  • 缓存未命中20 个候选300-800msCPU ONNX 推理)

瓶颈分析

  1. CPU 推理CrossEncoder 是最重的 CPU 计算任务。每个 (query, doc) pair 需要一次 forward pass。20 个候选 × ~30ms/pair ≈ 600ms。
  2. max_length=512:长文档截断到 512 token避免 OOM 但可能丢失信息。
  3. 缓存效果好:同一查询+同一文档集合直接命中缓存。但首次查询必经此阶段。

阶段 5MMR 去重

位置core/mmr.py

当前配置MMR_USE_EMBEDDING = False(文本模式),使用 jieba 词级 Jaccard 相似度

算法:贪心选择,每次取与已选集合最不相似且与查询最相关的候选

性能5-20ms纯文本计算无 embedding 开销)

瓶颈分析无瓶颈。文本模式 Jaccard 非常轻量。

阶段 6救援管线3 层)

位置api/chat_routes.py 内部函数 + core/engine.py

第一层 — BM25 发散救援

  • 当 BM25 top-3 中的文档被 Rerank 打到极低分时,恢复其分数到 CLUSTER_RESCUE_FLOOR=0.06
  • 防止关键词完全匹配但语义分低的文档被丢弃

第二层 — 词法匹配救援

  • 提取查询 bigram对每个低分文档计算 bigram 匹配率
  • 匹配率 > 0.35 → 提升到 floor
  • 附带邻居救援:同文档 ±8 个 chunk_index 的邻居也被提升

第三层 — 章节聚类救援engine.py _section_cluster_boost

  • (source, normalized_section) 分组
  • 检测"全灭章节":所有成员分数 < min_score
  • 要求 ≥ 3 成员 + ≥ 2 种 chunk_type
  • 提升最强聚类的 top-3 章节,每节最多 8 个 chunk

性能5-30ms纯规则计算

瓶颈分析无瓶颈。但在复杂表格/对比查询中,救援管线的效果直接影响回答质量。

阶段 7图片选择

位置api/chat_routes.pyselect_images()

评分体系(多层叠加):

评分因子 分值 条件
图号精确匹配 +10.0 查询提到"图2.3"且匹配
表号精确匹配 +10.0 查询提到"表1"且匹配
关键词匹配 +2.0/词 jieba 分词后匹配,上限 +8.0
字符重叠 +0.2/字 上限 +3.0
章节匹配 +1.5/关键词 图片章节与查询关键词重叠
图表类型 +2.0 (chart) / +1.0 (image) chunk_type 加分
引用+章节匹配 +8.0 + 5.0 被文本引用且章节/来源匹配
VLM 相关性 < 0.3 -3.0 VLM 描述与查询不相关
VLM 相关性 ≥ 0.5 +2.0 VLM 描述与查询相关
章节距离过远 -5.0 section_similarity < 0.3 且非引用

动态预算

场景 MAX_IMAGES MIN_SCORE
精确图号查询 2 5.0
检索结果含图片数据 5 2.0
文本中有图号引用 3 2.0
默认 2 3.0

性能10-50ms规则计算 + VLM 相关性检查)

瓶颈分析:图片选择本身不慢。瓶颈在 VLM 描述生成knowledge/lazy_enhance.py),需要调用 VLM 模型为每张图片生成文字描述。当前设计为懒加载(首次查询时生成并缓存),首次查询可能有 60s 超时。

两管线断裂问题select_images() 独立于文本上下文管线选择图片,但图片描述可能因 min_score 过滤未进入 LLM 上下文。具体问题链:

  1. chart_contexts 降门槛CrossEncoder 对图片/图表打分系统性偏低0.002-0.08 vs 文本 0.3-0.9),原 min_score=0.05 会过滤掉几乎所有图表。已修复为 min_score * 0.5 = 0.025
  2. P0 安全网:即使降门槛后,图片描述仍可能被 _build_context_with_budget() 截断。安全网在 LLM 生成前检查:若 selected_images 的描述不在 context_text 中,强制追加缺失描述。日志标记为 [P0] 图片描述未完整注入 context
  3. 答案后过滤鸡生蛋问题_filter_images_by_answer() 根据 LLM 答案内容过滤图片。若 LLM 因上下文缺失而回答"未找到某图表"正确图片会被误过滤。P0 安全网可缓解此问题。

阶段 8上下文构建

位置api/chat_routes.py_build_context_with_budget() / _order_text_contexts_for_prompt()

预算控制

CONTEXT_MAX_CHARS = 8000    # 硬上限(约 4000 token
CONTEXT_SOFT_LIMIT = 6000   # 软限制(超过后只接受高分 chunk
MAX_CONTEXT_CHUNKS = 20     # 最大切片数

排序策略

  1. 枚举/对比查询:保持原文顺序,注入 ━ section ━ 分隔符
  2. 普通查询:按 (source, section) 分组 → 组内按 chunk_index 排序 → 组间按 max_rerank_score 排序
  3. 表格保护:即使分数低于 min_score只要同章节有高分文本 chunk表格 chunk 也保留floor = min_score × 0.3

性能5-15ms

瓶颈分析无瓶颈。但上下文质量直接影响 LLM 生成质量。8000 字符 ≈ 4000 token对于需要对比多个文档的查询可能不够。

阶段 9LLM 流式生成 — 最大瓶颈

位置core/engine.pygenerate_answer_stream()

功能:将上下文 + 历史 + 查询组装成 prompt调用 LLM 流式输出

Prompt 结构

[System] 你是一个专业的知识库问答助手...(规则指令)
[System] 参考资料context, 最多 8000 字符)
[User×N] 历史对话(最多 10 轮)
[User] 当前问题

关键参数

参数 服务器值 说明
MODEL mimo-v2.5 主生成模型
LLM_TEMPERATURE 0.7 较高温度,生成更发散
LLM_MAX_TOKENS 3000 最大输出 token
LLM_TOP_P (未设置) 默认 1.0

实测耗时deepseek-v4-flash5000-15000ms含流式输出。mimo-v2.5 推理模型预计 8000-20000ms

瓶颈分析

  1. 模型延迟不可控LLM API 的 TTFT首 token 时间)和 TPStoken/秒)取决于服务端负载和网络。这是整个管线中唯一无法通过代码优化的瓶颈。
  2. 推理模型 content 为空问题mimo-v2.5 是推理模型,max_tokens 不足时思考链消耗全部预算,content 为空需从 reasoning_content 提取内容。当前 LLM_MAX_TOKENS=3000 足够,但若降低此值需注意思考链预算。
  3. Prompt 长度影响 TTFT8000 字符上下文 + 10 轮历史 ≈ 6000-8000 input token。输入越长TTFT 越高。
  4. 流式缓解:用户看到第一个 token 的等待时间 = 意图分析 + 检索 + TTFT ≈ 6-12 秒。流式输出减少了感知等待,但总耗时不变。
  5. MAX_TOKENS=3000 配置合理:推理模型思考链占用部分预算,实际回答通常 500-1500 token3000 平衡速度与质量。

阶段 10后处理

功能:答案对齐、图片过滤、引用附加、会话存储、语义缓存写入

子步骤

步骤 耗时 说明
图号引用提取 ~5ms 正则匹配答案中的图/表引用
图片后过滤 ~5ms _filter_images_by_answer(),根据 LLM 答案内容裁剪不相关图片
引用清理 ~2ms 去除 LLM 添加的 [N] 标记
引用附加 ~10ms 添加结构化 [ref:chunk_id]
敏感内容过滤 ~5ms prompt_guard 检查
会话存储 ~20-100ms SQLite 写入
语义缓存写入 ~10-50ms FAISS 索引添加

总计50-200ms。无瓶颈


三、缓存体系分析

五层缓存架构

精确缓存层
  ├─ Query Cache      LRU 500, TTL 1h    最终结果缓存(含 rerank 分数)
  ├─ Embedding Cache  LRU 2000, TTL 24h  文档向量缓存
  ├─ Rerank Cache     LRU 1000, TTL 1h   CrossEncoder 分数缓存
  └─ Intent Cache     Dict 500, 无 TTL   意图分析结果缓存

语义缓存层
  └─ FAISS Cache      max 10000, 阈值 0.92  向量化相似问答缓存

缓存命中场景

场景 命中层 节省时间
完全相同的问题 + 相同知识库 Query Cache 跳过整个检索(节省 ~1s
相似问题(余弦 ≥ 0.92 Semantic Cache 跳过检索 + 生成(节省 ~10s
相同文档集合被 rerank Rerank Cache 跳过 CrossEncoder节省 ~600ms
相同文档需要 embedding Embedding Cache 跳过编码(节省 ~100ms
相同问题(意图层面) Intent Cache 跳过意图分析 LLM节省 ~4s

缓存问题

  1. 内存存储,重启清零:所有缓存都在进程内存中,服务重启后第一次请求全部冷启动。
  2. 语义缓存阈值过高0.92 的余弦阈值导致换一种说法就命中不了。
  3. Rerank 缓存不参与 kb_version 失效:文档更新后 rerank 缓存可能返回旧分数。
  4. 语义缓存淘汰策略粗暴:达到 10000 上限时全量清空(clear()),不是 LRU。

四、瓶颈总结与优化优先级

耗时分布(典型请求,基于 deepseek-v4-flash 实测)

意图分析 ████████████████████ 25-35%    ~4000ms
LLM 生成 ████████████████████████████████ 45-60%  ~8000ms
检索+Rerank ████████ 10-15%   ~1200ms
其他阶段  ██ 3-5%    ~400ms

优化机会排序

优先级 方向 预期收益 难度 风险
P0 意图分析换轻量模型 节省 2-3s/请求 低(需寻找可用轻量模型,百炼 deepseek-v4-flash 额度已尽)
P0 主模型 API 优化(换供应商/批处理) 节省 3-5s/请求 中(需要评估质量)
P1 意图分析结果缓存优化 重复问题节省 4s
P1 语义缓存阈值调优0.92→0.88 提高命中率,节省 10s 中(可能误命中)
P2 RECALL_MULTIPLIER 降低3→2 减少 embedding 编码量 ~30ms
P2 LLM_TEMPERATURE 降低0.7→0.3 减少发散,回答更精准 中(可能影响创造性)
P3 Rerank 换 GPU 推理 节省 ~500ms 高(需硬件)
P3 Embedding 换 GPU 推理 节省 ~150ms 高(需硬件)
P3 语义缓存改 LRU 淘汰 避免全量清空抖动

五、配置参数速查表

模型与服务

参数 当前值 说明
DASHSCOPE_MODEL mimo-v2.5 主 LLM
INTENT_MODEL mimo-v2.5 意图分析模型
VLM_MODEL mimo-v2.5 图片描述模型
EMBEDDING_MODEL_PATH bge-base-zh-v1.5 本地 embedding
RERANK_BACKEND local 本地 ONNX rerank可选 cloud/fallback
RERANK_CLOUD_MODEL xop3qwen8breranker 云端 rerank讯飞云 API

检索参数

参数 当前值 说明
RAG_SEARCH_TOP_K 30 最终返回数
RECALL_MULTIPLIER 3 向量召回倍数
RERANK_CANDIDATES 20 送入 Rerank 的候选数
RERANK_TOP_K 15 Rerank 后保留数
RERANK_CONTEXT_MIN_SCORE 0.05 最低 rerank 分数阈值
MMR_TOP_K 30 MMR 处理后保留数
MMR_LAMBDA 0.5 相关性/多样性平衡
MMR_USE_EMBEDDING False 使用 jieba Jaccard
RRF_K 60 RRF 平滑参数
DYNAMIC_RRF_ENABLED True 按查询长度自适应权重

上下文构建

参数 当前值 说明
CONTEXT_MAX_CHARS 8000 上下文硬上限(字符)
CONTEXT_SOFT_LIMIT 6000 软限制(超此只接受高分)
MAX_CONTEXT_CHUNKS 20 最大切片数
MAX_HISTORY_ROUNDS 10 对话历史上限

LLM 生成

参数 当前值 说明
LLM_TEMPERATURE 0.7 生成温度
LLM_MAX_TOKENS 3000 最大输出 token
LLM_TOP_P 未设置 默认 1.0(不限制)

缓存

参数 当前值 说明
QUERY_CACHE_SIZE 500 查询缓存容量
QUERY_CACHE_TTL 3600s 查询缓存有效期
EMBEDDING_CACHE_SIZE 2000 向量缓存容量
EMBEDDING_CACHE_TTL 86400s 向量缓存有效期
RERANK_CACHE_SIZE 1000 Rerank 缓存容量
RERANK_CACHE_TTL 3600s Rerank 缓存有效期
SEMANTIC_CACHE_THRESHOLD 0.92 语义缓存余弦阈值

救援管线

参数 当前值 说明
CLUSTER_MIN_MEMBERS 3 聚类最少成员数
CLUSTER_MIN_TYPES 2 聚类最少类型数
CLUSTER_SEED_FLOOR 0.35 种子分数下限
CLUSTER_RESCUE_FLOOR 0.06 救援后分数
CLUSTER_MAX_SECTIONS 3 最多救援章节数
BM25_DIVERGENCE_RESCUE_ENABLED True BM25 发散救援开关
BM25_DIVERGENCE_MAX_RANK 3 BM25 top-N 参与救援

置信度

参数 当前值 说明
CONFIDENCE_WARN_THRESHOLD 0.15 低于此值:谨慎回答
CONFIDENCE_CAUTION_THRESHOLD 0.30 低于此值:引用原文

六、架构层面的潜在风险

6.1 单点故障

  • LLM API 单点:所有 LLM 调用(意图分析 + 生成)都走同一个 DASHSCOPE_BASE_URL。API 限流或故障时整个服务不可用。意图分析有降级兜底(默认 need_retrieval=True但生成阶段无降级。
  • ChromaDB 单点:向量库文件损坏时所有检索失败。已有 ChromaDB corruption 风险分析文档。

6.2 内存压力

  • FAISS 语义缓存10000 条 × 768 维 float32 = ~30MB 向量数据 + 等量元数据。达到上限全量清空时可能有瞬间抖动。
  • BM25 全量加载:所有文档的全文 + jieba 分词结果都在内存中。知识库增长时内存线性增长。
  • 三层 LRU 缓存Query(500) + Embedding(2000) + Rerank(1000) = 最多 3500 条缓存。Embedding 缓存存 768 维向量2000 条 ≈ 6MB。

6.3 并发安全

  • gunicorn gthread 模式 2 线程 + ChromaDB 文件锁。并发写入 ChromaDB 时可能冲突。
  • FAISS 语义缓存使用 RLock线程安全。
  • 精确缓存使用 RLock线程安全。
  • IntentAnalyzer._exact_cache 是普通 DictPython GIL 保护下基本安全。

6.4 休眠模块

以下模块当前未接入生产流程,属于独立休眠模块(原属 AgenticRAG 类,该类已在 v4.0 删除):

  • confidence_gate.py — 置信度门控
  • quality_assessor.py — 质量评估器
  • reasoning_reflector.py — 推理反思器
  • loop_guard.py — 循环守卫

这些模块增加了代码库的维护负担但不影响运行时性能。可按需启用。