Jev Search 换成自建索引后回答变慢 / 先分段量一遍耗时

文章导读
换成自建索引后 Jev Search 回答变慢,先别急着调 top_k 或换模型。把一次查询拆成召回、结果整理、模型调用三段,分别打时间戳,看耗时落在哪一段,再决定动哪里。以下做法可按你实际使用的检索库和模型客户端替换字段名。
📋 目录
  1. Ⅰ 在查询入口和出口各打一个时间戳
  2. Ⅱ 把召回段单独计时
  3. Ⅲ 把模型调用段单独计时
  4. Ⅳ 同一问题重复跑几次看波动
  5. Ⅴ 按耗时占比决定先动哪一段
A A

换成自建索引后 Jev Search 回答变慢,先别急着调 top_k 或换模型。把一次查询拆成召回、结果整理、模型调用三段,分别打时间戳,看耗时落在哪一段,再决定动哪里。以下做法可按你实际使用的检索库和模型客户端替换字段名。

接自建索引后变慢,通常不是单点问题,先别改配置。建议在查询入口和出口各打一个时间戳拿到总耗时,再把召回段和模型调用段的起止时间分别打印,用同一个 trace 标识串起来。同一个问题重复跑几次,区分偶发与稳定变慢。哪一段耗时占比明显偏高,就先查那一段的可调项;调整后仍用同一套计时复跑,才能判断有没有效果,环境差异需要结合实际日志确认。

在查询入口和出口各打一个时间戳

入口和出口的时间差就是这次查询的总耗时,它是后面所有分段拆解的参照。在请求处理函数的开头生成一个能区分单次查询的标识,例如 trace_id 或 request_id,并让该次查询的所有日志都带上它,否则并发下几行日志混在一起就没法归因到同一次请求。

import time, uuid, logging

def handle_query(question: str):
    trace_id = uuid.uuid4().hex[:8]        # 单次查询标识
    t0 = time.perf_counter()
    logging.info("query_start trace=%s question_len=%d", trace_id, len(question))
    try:
        return run_pipeline(question, trace_id)
    finally:
        total_ms = (time.perf_counter() - t0) * 1000
        logging.info("query_end trace=%s total_ms=%.1f", trace_id, total_ms)

日志里同时带上 trace_id、问题长度或摘要、总耗时。只看一条 query_end 只能知道整体慢,要配合后面的分段日志才知道慢在哪。检索和模型如果跑在独立进程或独立服务里,时间戳仍以调用方为准,这样各段相加才方便和总耗时对齐。

把召回段单独计时

召回开始的时间戳放在进入检索调用之前,结束时间戳放在拿到候选文档之后,中间不要夹结果整理和提示词拼接,否则整理耗时会被算进召回。打印时带上返回条数,便于判断是不是一次拉了过多候选。

t1 = time.perf_counter()
chunks = retriever.search(question, top_k=8)   # 替换成你的检索调用
t2 = time.perf_counter()
logging.info("recall_done trace=%s recall_ms=%.1f hits=%d",
             trace_id, (t2 - t1) * 1000, len(chunks))

结果整理段一般不用单独插桩,用总耗时减去召回耗时和模型耗时,能得到一个粗略值。需要更细时,再在排序、去重、截断之后各加一个时间戳,分别打印。

把模型调用段单独计时

模型调用前后各打一个时间戳,圈出来的是调用方看到的等待时间,包含网络往返、排队和重试。模型服务端自己统计的推理耗时往往小于这个值,两者不是一回事,以你实际观察到的为准。如果用流式输出,首 token 时间和完整返回时间分开记,回答变慢有时只是首字延迟变高。

Jev Search 换成自建索引后回答变慢 / 先分段量一遍耗时
t3 = time.perf_counter()
resp = llm.chat(prompt)          # 替换成你的模型调用
t4 = time.perf_counter()
llm_ms = (t4 - t3) * 1000
logging.info("llm_done trace=%s llm_ms=%.1f", trace_id, llm_ms)

同一问题重复跑几次看波动

只跑一次分不清偶发和稳定变慢。选一个固定问题,连续跑五次以上,把每轮的召回耗时、模型耗时、总耗时填进同一张表,看是每轮都慢,还是某一两轮出现尖峰。

轮次召回耗时 (ms)模型耗时 (ms)总耗时 (ms)备注
1是否首次 / 是否命中缓存
2
3
4
5当时的并发数量

只有个别轮次偏高,优先怀疑缓存未命中、索引冷加载、模型服务排队或超时重试;每一轮都慢,问题更可能出在稳定的配置或数据规模上。备注里记下当时是否首次查询、并发多少、是否命中缓存,这些信息决定了复跑结果能不能互相比较。

按耗时占比决定先动哪一段

拿到几轮数据后先算占比,不要凭感觉改配置。可以先用这套规则判断优先看哪一段:

  • 召回段占比明显偏高:先看 top_k 是否过大、过滤条件是否导致大范围扫描、索引是否常驻内存、是否每次查询都重新加载索引文件、分片与副本设置是否合适。
  • 模型调用段占比明显偏高:先看提示词和上下文拼了多少字符、max_tokens 是否过大、是否在排队、有没有超时重试、能否改成流式先返回首段内容。
  • 整理段占比偏高:先看重排、去重、排序是否对大量候选重复计算,能否在召回阶段就截断候选数量。

每次只改一项,改完用同一个问题、同一套计时再跑一遍,对比三段耗时和总耗时的变化。总耗时没有下降,说明改的这项不是主要瓶颈,回退后再试下一项。本地、容器和同机其他负载都会影响结果,比较时尽量让运行条件接近。