首页 / 资讯中心 / 文章详情

RAG又慢又飘?从检索到生成的全链路性能调优指南

RAG又慢又飘?从检索到生成的全链路性能调优指南 ★ FEATURED ARTICLE
很多团队第一次把 RAG 做出来用时都会经历一段“能用但很难用”的尴尬期。接口响应慢吞吞答案时好时坏问深一点就开始胡编。如果最近你也在折腾检索增强生成系统被延迟高、回答飘这两个问题搞得头疼这篇文章应该能帮你少走不少弯路。我会从一次真实的性能调优过程出发把 RAG 从检索到生成的完整链路拆开来讲细说每一步为什么会慢、为什么会答不准以及我实际验证过有效的优化手段。这个内容适合刚把 RAG 跑通、正准备做性能优化的开发者也适合已经上线但被吐槽体验太差的团队参考。我不会堆概念所有方案都会给出参数、步骤和避坑经验你可以直接照着动手调。1. 先说清楚RAG 的延迟和飘忽来自哪里1.1 一次请求背后到底发生了什么很多人的第一反应是“换个更快的模型”。但真实情况里大模型推理往往只占整个响应时间的一部分。一次完整的 RAG 链路大致是收到用户问题 → 处理问题文本 → 向量化编码 → 到向量库做相似度检索 → 拼接检索结果与原始问题 → 发给大模型生成回答 → 流式返回。每一步都有延迟而且像网络传输、文本预处理这些容易被忽略的环节在极端情况下也会吃掉几百毫秒。回答飘就更复杂了。它往往是三个因素叠加的结果检索到的内容本身不对检索内容对但被截断或切碎模型被无关片段干扰。很多人只盯着大模型换参数实际上答案不靠谱时80% 的时间都应该去查检索环节。我建议先把自己系统的全链路耗时打点打出来让数据说话。不要凭感觉判断慢在哪里。我曾经接手的某内部文档问答项目表面看是整体响应 6 秒太慢打点之后发现检索阶段占了 2.8 秒文本后处理加拼接占了 1.2 秒真正送给模型的只有 1.8 秒的推理时间。如果当时盲目换更大的模型方向就完全走错了。1.2 延迟高的三个大头第一是向量检索延迟。向量库的索引类型、数据量大小、查询并发数直接决定这一步耗时。数据量十万级和千万级量级不同需要选择的索引完全不一样。第二是 Embedding 环节。不管是用开源模型还是调用在线接口把一段文本转成向量都要花时间尤其当输入文本很长、或者单次请求需要批量编码时这个耗时会被放大。第三是 LLM 推理本身的延迟。生成 token 是逐个出的输出越长耗时越线性增长。除此之外还有两个容易被忽略的隐藏延迟源一个是网络往返尤其是调用外部模型接口时请求体较大、连接未复用都会增加额外延迟另一个是向量库连接池配置不对高并发时线程排队导致检索耗时倍增。1.3 回答飘的核心原因回答飘我自己的排查经验是优先看召回。召回不准通常有几种来源一是嵌入模型能力不足语义相近但表述不同的内容召回效果差二是 TopK 设置不合理要么召回太少导致信息不够要么召回太多把噪声也带进去了三是相似度阈值没有控制低质量片段也被拼进上下文四是文本被切得太碎把一个完整知识点横切成了好几段导致每段信息都不完整。另外还要看提示词的结构。如果提示词里只说“根据资料回答”没有明确“如果资料中没有答案请直接说不知道”模型就会倾向于强行编造。把这块规则写清楚回答飘的问题能改善不少。2. 检索侧调优把速度和回头率一起抓2.1 向量检索参数怎么调才合理TopK 是我建议第一个动手调的参数。很多人习惯设一个固定值就不动了但实际项目里不同场景对 TopK 的需求差异很大。我做过的某电商客服知识库FAQ 型问答 TopK 取 3 效果就很好但换到文档分析场景同一个问题往往横跨多个章节TopK 设成 8 甚至 10召回才够完整。这里有一个实用技巧不要把 TopK 当成一个参数而是配合相似度分数阈值一起看。先取一个较大的 TopK比如 20然后根据分数分布动态截断。例如只保留相似度超过 0.72 的片段如果不足 3 段就再放宽阈值。这种动态策略比固定值靠谱能显著减少噪声片段进入上下文。向量库索引类型的选择也很关键。数据量小于 50 万时暴力检索Flat性能足够毫秒级响应且召回最准没必要上复杂索引。数据量上百万后再考虑 HNSW 这类 ANN 索引。HNSW 有两个关键参数需要调M控制每个节点的最大连接数和 efSearch控制查询时的探索范围。简单来说M 越大召回越准但内存占用越高efSearch 越大查询越慢但准确率越高。我自己的经验是 M 取 16 到 32efSearch 先设 64如果延迟达标但召回不足再往上加到 128、256一个一个试。2.2 混合检索是“回答飘”的一剂猛药向量检索擅长语义匹配但对精确关键词非常不敏感。比如用户搜“RPA 机器人流程自动化”向量可能把它理解成“机器人技术”召回结果完全跑偏。解决这个问题的方法就是混合检索把 BM25 稀疏检索和向量稠密检索的结果做融合。我在某内部知识库项目中实测过只做向量检索时命中率大概在 71%加上 BM25 后命中率提升到 84% 左右。融合方式我推荐 RRFReciprocal Rank Fusion原理很简单对多路检索结果按排名取倒数分再求和最后按总分排序。它不需要调权重鲁棒性比加权求和好很多。需要留意的是BM25 对中文场景要做好的分词。直接用空格分词效果很差建议接入 jieba 或按字粒度索引否则关键词检索的收益会大打折扣。某些向量库虽然内置了 BM25但分词器对中文支持一般采样测试几轮就能看出差距。2.3 分块策略直接影响检索质量这块是很多人容易忽略的重灾区。分块太大单段包含多个主题向量化后语义不够聚焦检索命中率下降分块太小语义被切碎信息不完整就算检索到了给到模型也没办法生成完整答案。我的经验是分块策略和文本本身的结构强相关。具体来说对 Markdown 或标题层级文本优先按标题语义分段而不是机械按字符数切。先按章节切开再对过长的章节按段落二次切分最后如果单段还是太长设定一个最大长度做重叠切割重叠量控制在块长度的 10% 到 20%。重叠切割有一个反直觉的地方重叠太多会导致检索结果冗余拼接后上下文里大量相同信息反而挤压了其他有效信息的位置。我曾经把重叠设成 30%结果回答质量反而下降。后来改为按句子边界加少量重叠效果比较理想。3. 生成侧调优让大模型把答案“答稳”3.1 提示词模板里的约束条件同样的检索结果给到大模型后能不能生成靠谱回答提示词的约束强度差别很大。我见过太多提示词只写“根据以下资料回答问题”其余全靠模型自由发挥。这种做法一旦检索结果里混入无关内容模型很容易被带偏。我实践下来比较稳定的模板包含这几个部分角色定义、任务说明、引用资料区、约束条件、输出格式。其中约束条件要明确写只能基于资料内容回答、资料不足时直接回答“未找到相关信息”、不要推断或编造。这些约束放在提示词后部、靠近资料区的位置模型执行得更好。另外如果检索到的片段有来源和标题信息拼进提示词时保留它们能让模型在处理多段内容时更好地区分信息来源。一个小细节是片段之间用明确的 XML 标签或 Markdown 引用块分隔比连续堆文本的效果好得多。3.2 生成参数怎么配合调整Temperature、TopP 这类参数不是只影响创造性它们直接影响回答的“飘”的程度。对知识问答类场景Temperature 我建议压在 0.1 到 0.3 之间TopP 控制在 0.8 左右。需要明确的是Temperature 调低只是减小随机采样概率并不能彻底消除幻觉。真正要消除幻觉还得靠检索质量和提示词约束。max_tokens 也要合理设置。设得太短答案容易被截断设得太长用户等得越久。比较好的做法是结合问题类型动态判断但实现复杂度高。折中方案是设一个偏大的值配合流式输出用户感知不到生成时间和完整生成的差别。还有一个容易被忽略的参数是频率惩罚和存在惩罚。当检索片段较多、上下文较长时适当调高存在惩罚可以减少模型重复说过的内容但调过头会导致回答生硬、信息密度降低。我的建议是这两个参数默认不动只有在输出明显重复时才微调。3.3 Rerank 重排序用一点延迟换大幅准确率很多人没有在 RAG 链路里加重排序其实这一步对回答飘的改善非常明显。向量召回的时候头部结果可能还行但第 5 到第 20 位的结果相关性会明显变差。如果直接把 TopK8 的结果全送给模型很有可能一半都是噪声。Rerank 模型会把召回的候选重新打一遍分把最相关的排到最前面。我实测的某项目加入 Rerank 后精确匹配率提升明显代价是每次查询增加了 50 到 120 毫秒的延迟。这个延迟换来的准确率提升非常值得。需要注意的是Rerank 的输入长度有限制不要直接把超长文本整个丢进去。配合前面“先取较大 TopK再 Rerank 后取前 N”的策略效果最好。选 Rerank 模型时优先考虑专用模型而非通用向量模型。因为 Rerank 任务和语义匹配任务不完全一样专用模型对相关性的判断更准确。如果条件有限至少也要选择在配对任务上训练过的模型不要直接拿向量模型充当 Rerank。4. 工程链路优化从 6 秒压到 2 秒的真实手段4.1 缓存带来的收益超出想象RAG 场景中用户问题往往有大量重复或近似。同一个问题反复查库、反复调用模型纯属浪费。语义缓存可以把常见问题直接截住大幅降低平均响应延迟。我做的某知识问答系统上线语义缓存后整体响应时间平均值从 5.8 秒降到了 3.2 秒。缓存命中时直接返回生成好的答案响应时间能压到 300 毫秒以内。实现思路是对用户问题做向量化在缓存中做相似度检索如果相似度超过预设阈值比如 0.92就直接返回缓存中的答案。这里有一个细节要提醒缓存必须有失效策略否则资料库更新后旧答案还会继续被重复返回。我在项目里是结合文档版本的变更来清缓存简单地说每当知识库文档重新导入或更新就对受影响的缓存做失效处理。还有一种做法是给缓存结果打上文档版本号查询时对比版本号版本不一致就直接穿透到完整链路。4.2 连接池与并发配置看不见的延迟杀手很多团队在压测时发现单请求延迟已经优化得不错但并发一上来就崩。问题往往出在连接池配置上。向量库客户端、模型服务 SDK 默认连接池非常保守不调配置就等于把并发请求排队处理。以向量库客户端为例默认连接数可能只有 10 到 20当查询并发到达 50 时大部分请求都在等待连接释放。我把连接池最大连接数调到和线上服务实例副本数匹配同时设置合理的超时时间单查询耗时和 P95 都大幅改善。模型推理服务也是一样如果用的是自建的推理网关务必检查它的最大并发数和队列长度不要让请求在队列里空等。另一个细节是连接复用。调用外部模型 API 时如果不开启 HTTP Keep-Alive每次请求都要重新建立 TCP 连接TLS 握手开销会占掉不少时间。开启连接复用之后延迟能降低 10% 到 15%。这个收益在长文本、高并发场景特别明显。4.3 模型量化和服务部署层面的取舍模型推理是 RAG 链路里耗时的大头之一。把 LLM 做量化是最直接的加速手段。从实际效果看4-bit 量化一般能带来 30% 到 50% 的推理加速模型精度损失在可接受范围对知识问答场景影响很小。如果你用的是 7B 到 13B 级别的模型量化前后的差异用户基本感知不到。但量化也有代价。显存占用降低后你可以把更大的模型塞进同一张卡但吞吐量不一定线性提升。这里建议做一次完整的压测对比量化前后在不同并发下的吞吐表现。我自己的经验是量化后单卡吞吐确实提升明显但并发到一定程度后瓶颈会转移到显存带宽上继续提并发的收益开始递减。服务部署层面一个容易被忽视的点是动态批处理。推理服务端开启动态批处理可以把多个请求合并成一个大 batch提升 GPU 利用率。这需要推理框架支持并配合合理的等待时间设置。等待时间设 20 到 50 毫秒比较合适太长会牺牲单请求延迟太短又来不及攒批。这个参数需要根据实际流量节奏反复试。5. 一个完整调优案例的复盘与避坑记录5.1 项目初始状态与无法忍受的问题这里分享一个我用上述思路完整调优的模拟项目。某团队做了一个内部文档智能问答系统知识库大概 30 万段文档切片部署在单台 GPU 服务器上模型是 7B 量化版本。上线后的表现是平均响应时间 8 秒遇到长文档问题甚至会到 15 秒答案质量也不稳定用户经常反馈答非所问有些问题来回问两次给出矛盾答案。这个项目最让人抓狂的不是慢而是无法定位瓶颈。于是第一步就是做链路打点把从请求进入网关到响应返回的每一步耗时都记录下来。打点结果非常直观文本预处理和 Embedding 耗时占 25%向量检索占 35%Rerank 占 5%LLM 推理占 30%其他开销占 5%。检索相关的时间加起来超过了一半。5.2 逐步优化的完整过程第一步优化的是向量检索。原来用的索引参数是无脑默认efSearch 只有 40数据量上来后召回质量明显下降。我把 efSearch 调到 128同时把 TopK 从 5 调整到动态策略先召回 20 条再用 Rerank 精排取前 5。这一步之后回答飘的问题明显好转但查询延迟涨了一些还在可接受范围。第二步是引入 BM25 混合检索。原来只做向量检索用户输入含专业缩写或精确型号时经常召回失败。接入 BM25 并用 RRF 做融合后召回率明显提升。这一步查询耗时增加了大约 30 毫秒但答案准确率的提升非常可观。同时重新设计了分块策略从原来固定 300 字切块改为按标题语义切分重叠切片进一步改善了长文档的召回连贯性。第三步是处理 Embedding 瓶颈。原来每次请求都现算用户问题的向量虽然单次只有几十毫秒但并发一高就很伤。我先缓存了知识库侧的所有向量用户侧的查询向量做了语义缓存。结果就是高频重复问题的部分请求可以直接命中缓存不需要走完整链路。这一步让平均响应延迟又降了一截。第四步才是优化模型推理。把生成参数统一调整为低 Temperature配合 Prompt 强调“资料不足时直接拒绝回答”明显降低了幻觉率。然后把模型从通用对话向调优为更偏知识问答的方向同参数量下回答稳定性提升了一个台阶。5.3 优化效果与关键数字变化优化完成后的结果平均响应时间从 8 秒降到 2.1 秒P95 从 12 秒降到 3.8 秒。回答质量方面人工评测的相关性准确率从 62% 提升到 84%幻觉比例从明显偏高降到可接受区间。这里有一个很有参考价值的对比单看 LLM 推理耗时我们其实只优化了大约 20% 的量。更多的耗时节省来自缓存命中、检索优化、并发配置这些外围环节。这说明 RAG 性能调优绝对不能只盯着模型本身整个链路上的每个环节都值得打磨。5.4 常见问题速查表现象可能原因排查方向与解决建议TopK 增大后延迟飙升向量索引 efSearch 过低或未用 ANN 索引调整 efSearch检查索引类型是否适应当前数据量答案经常带着无关内容相似度阈值过低或 Rerank 缺失调高阈值加入 Rerank 并设置合理截断同一问题过段时间答案变差资料更新后缓存未失效给缓存打版本号文档更新后主动失效对应缓存并发一高就大量超时连接池配置过小或 HTTP 未复用调整连接池上限开启 Keep-Alive 和动态批处理专业术语、型号检索不到只有向量检索、分词不合适引入 BM25 混合检索优化中文分词回答空洞但检索看起来有结果分块过大导致语义不聚焦按标题语义切分控制单块长度加少量重叠输出总是重复几句话频率惩罚或存在惩罚未设置适当调高存在惩罚观察输出变化6. 调优过程中的几条实用经验与心得最后再分享几个我在多次调优中沉淀下来的经验。第一链路打点必须放在第一位。没有完整耗时分布之前任何盲目优化都可能白费功夫。建议从最开始就把每一步的耗时、输入输出大小、调用成功失败状态记录清楚。而且要量化到每一个环节包括文本预处理、Embedding 调用、向量库查询、Rerank、Prompt 拼接、LLM 流式首个 token 延迟和总生成耗时。有了这些数据你才知道该往哪个方向使劲。第二不要一上来就追求最新的模型或最贵的方案。很多性能问题通过调整现有组件的配置就能解决大半。先把手上的每个环节的参数吃透再考虑上重武器。比如 efSearch、TopK、连接池、缓存这几个点调好之后往往能有质的飞跃。第三任何优化都要以线上真实流量来验证。离线测试时我用的是理想环境数据分布和线上差异很大。只有在灰度环境压测并对比真实用户反馈之后才能确定优化没有引入新的问题。第四流式输出是一种性价比极高的体验优化手段。即使做完所有优化后总耗时有 3 秒只要用户第一 token 在 500 毫秒内出现主观体验就完全不像 3 秒。这个技巧在很多场景下比硬调性能更有效果而且实现成本很低。RAG 性能调优不是一锤子买卖它是一个不断测量、调整、验证的循环过程。希望这篇内容能帮你在自己的系统上把延迟降下来、答案质量提上去少踩几个我踩过的坑。
阅读完成 · 觉得有帮助?
咨询建站