换成自建索引后 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 时间和完整返回时间分开记,回答变慢有时只是首字延迟变高。
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 是否过大、是否在排队、有没有超时重试、能否改成流式先返回首段内容。
- 整理段占比偏高:先看重排、去重、排序是否对大量候选重复计算,能否在召回阶段就截断候选数量。
每次只改一项,改完用同一个问题、同一套计时再跑一遍,对比三段耗时和总耗时的变化。总耗时没有下降,说明改的这项不是主要瓶颈,回退后再试下一项。本地、容器和同机其他负载都会影响结果,比较时尽量让运行条件接近。