☰
LLM应用性能优化:缓存、提示压缩与检索加速实战
2026/10/22 19:24:28 网站建设 项目流程

做AI应用最容易被忽视、又最让人头疼的事,就是性能。我做过几个智能问答和RAG检索类的项目,前期功能都跑通了,一上量就开始卡:请求排队、Token烧钱、首字返回慢到用户以为服务挂了。后来把缓存、提示压缩、检索加速这三板斧全用上,同样的服务,延迟直接砍掉一半多,成本也降了不少。

这篇就把我这套性能优化思路完整整理出来。文章会围绕三个核心手段展开:缓存机制解决重复计算,提示压缩解决Token浪费,检索加速解决向量召回慢。适合正在做LLM应用、RAG系统、智能客服、知识库问答的同学参考,如果你刚入门但已经能跑通一个基础Demo,这篇的实操方案也能直接照抄。

1. 性能检查:先搞清楚系统到底慢在哪

性能优化最忌讳一上来就动手,结果改了一堆配置,实际收益却很小。我习惯先做一轮瓶颈画像,把请求链路从头到尾拆开测量一遍,再决定动哪个环节。

1.1 完整的请求链路有哪些耗时环节

一个典型的RAG问答请求,从用户输入到返回答案,大体经过这几个环节:

  • 输入预处理:检查缓存、意图判断、问题改写,耗时大概几毫秒到几十毫秒。
  • 检索阶段:Embedding向量化、向量库查询、可选的关键词召回、排序融合,这里往往是最大的延迟来源。
  • 上下文组装:把检索到的文档拼进Prompt,做截断或压缩,耗时很少,但直接影响后面生成的质量。
  • LLM推理:调用大模型生成答案,依赖输入长度、输出长度和模型大小,通常要几百毫秒到数秒。

拿我之前的日志数据举例:一次请求总耗时2.8秒,其中向量检索占了1.2秒,LLM推理占1.1秒,剩下的是其他开销。所以优化重点就很明确:先把检索压下来,再压缩Prompt减少模型计算量。

1.2 量化指标:别凭感觉决策

性能优化之前,必须先建立基线。我一般用三个指标来衡量:

  • P95首字节延迟:用户感知最明显的是首Token时间,目标是大型对话场景控制在1.5秒以内。
  • 总请求耗时:从请求发出到完整流式返回结束,适合判断一次交互的整体体验。
  • 每请求Token消耗:包括输入Token和输出Token,直接和成本挂钩。

建议在日志里打上阶段耗时埋点,比如cache_hit_time、retrieve_time、llm_first_token_time,跑上一两天真实流量,拿数据说话。我见过不少项目优化了半天,回头看日志才发现瓶颈根本不在自己猜的地方。

1.3 缓存、压缩、检索各自解决的痛点

这三个技术手段不是孤立的,它们对应三个不同的瓶颈:

  • 缓存:解决“重复计算”问题,同样的诉求别算两遍。
  • 提示压缩:解决“Token浪费”问题,Prompt里塞满废话,每一句都在烧钱和增加首字延迟。
  • 检索加速:解决“召回慢”问题,让向量查询从秒级降到百毫秒级。

把它们组合起来的效果是:缓存兜底高频请求,压缩降低生成成本,检索加速降低召回时间。三者合在一起,收益往往是相乘而不是相加。

2. 缓存机制:从KV缓存到语义缓存,一层一层把重复计算干掉

缓存是性能优化里见效最快的手段,但很多人只盯着“KV Cache”,忽略了应用层的语义缓存,结果优化空间白白浪费了。

2.1 应用层缓存:命中一次省一次Embedding和生成

我在项目里最先实现的是一个简单的“精确查询缓存”:把用户输入做归一化(去空格、转小写、同义词归一),然后作为Key,把答案和对应的检索结果存进Redis。命中后直接返回,省去了检索、组装、生成整个链路。

但精确匹配的覆盖率很低,因为用户问同一件事,经常会换不同的说法。比如“怎么设置支付密码”和“支付密码在哪里改”本质上是一回事,但字符串完全不一样。所以我又加了语义缓存:

  • 把用户查询向量化。
  • 在向量库里查找历史问题的相似度,取Top1。
  • 如果相似度超过阈值(我常用0.92),直接复用缓存答案。

语义缓存的核心是把Map从“字符串等于”升级成“语义相似”,命中率能提升一大截。不过要小心一个问题:相似并不等于相同,阈值设太高会漏掉真正的重复,设太低会把不同意图的错误答案返回给用户。我的经验是,知识问答类场景0.90-0.94比较稳,意图越刚性(比如查询订单状态)越可以往下调到0.88。

2.2 大模型推理层的Cache,不只是用现成的

模型侧其实也有两层缓存值得关注。

第一层是HuggingFace生态里的KV Cache,这是Transformer推理内置的缓存,解码时把前面Token的Key和Value缓存下来,避免重复计算。这个一般框架默认就开着,不需要手动动它,但要注意显存占用:长上下文场景下,KV Cache会吃掉大量显存,如果爆显存反而更慢。此时可以考虑对整条链路做流式输出,降低峰值压力。

第二层是“Prompt Cache”。很多推理框架(比如常见的推理服务)会缓存前缀的KV状态,也就是把系统提示词和固定的工具说明部分缓存起来,只要输入前缀一致,就能复用计算结果。这就要求在工程上把Prompt模板固定下来,不要在系统提示词里动态插入用户信息、时间戳之类的内容,否则缓存永远打不中。

2.3 缓存淘汰策略与一致性,很容易踩坑

缓存写起来简单,淘汰才考验功力。我之前犯过的错包括:缓存永不过期导致数据陈旧;缓存全部设置了5分钟过期导致雪崩。后来统一用以下的策略组合:

  • 热点数据设置TTL,知识库更新时主动删除对应Key。
  • 使用LRU淘汰策略,保证内存里只留热数据。
  • 大促或运营活动后,手动清理历史语义缓存的向量索引,防止垃圾相似结果污染。

另外强烈建议做“缓存填充”而不是“缓存穿透”。流量一来,如果缓存没命中,所有人都去打后端的向量库和模型接口,系统照样崩溃。我一般会在缓存未命中时加一把分布式锁,让同一个Key只有一个请求去查询后端,其余请求等结果直接复用。

3. 提示压缩:把Prompt里的每一克Token都榨干

提示压缩这门技术,本质是在保证生成质量的前提下,把输入给大模型的Token数量减下来。输入Token少了,首字延迟下降、计费下降、服务端的资源占用也下降。听起来很直接,但具体怎么做,大多数人只想到了“让模型自己概括”,这块坑很多。

3.1 为什么提示压缩能带来实打实的加速

大模型生成时的计算量和输入长度正相关,尤其处理长上下文时,KV Cache和注意力计算都随序列长度上升。把5000字压缩到2000字,等于直接让这部分的计算量和内存占用减半以上。

但有个原则务必记住:压缩不能破坏关键信息。做检索场景时,如果压缩太狠,把答案所在的原文细节弄丢了,生成的回答就是一本正经的胡说八道。所以我在做提示压缩时,追求的是“减掉无关,保住核心”。

3.2 我实测有效的三档压缩策略

第一档:结构化截断。这是成本最低、风险也最低的方法。先按文档标题和段落结构拆分内容,只保留与用户问题语义相关的段落,把无关背景、版权信息、落款直接去掉。用这种方式,检索返回的Top5文档经常能压缩掉50%-60%的Token,而且几乎不损失答案质量。

第二档:摘要压缩。让模型把较长段落提炼成要点,适合知识库中背景介绍类的内容。但实际操作里,我不建议每次请求都用大模型做摘要,因为那也是一次模型调用,成本反而拉高了。更靠谱的做法是离线预处理好:在文档入库时就把长文切成结构化的“摘要块”和“关键数据块”,在线推理时直接取现成的。

第三档:插入压缩标记。由于现在很多大模型对中英文内容都比较敏感,我会尝试“只提取与问题直接相关的句子”,相当于用规则先筛一遍,再拼进Prompt。如果检索到的段落是“产品的保修政策是……具体条款包括……”,我会只提取“保修政策为一年免费保换,非人为损坏不修”这类关键句。

3.3 压多少合适?一个可量化的经验区间

我一般按输入Token预算来控制压缩强度。比如设定“最终Prompt输入不超过2500 Token”,如果检索回来的原始材料有7000 Token,就按递归压缩法逐层压缩,直到到达预算。

压得越狠,回复越容易偏。我自己测试下来,压缩率控制在60%-70%之间,质量下降很小;超过80%就要小心了,特别是指令中包含复杂推理步骤的场景。比如“先判断提问意图,再调用工具,最后总结”这类少样本逻辑,压缩掉示例会导致模型行为漂移。

还有一个小技巧:如果系统提示词和示例占了大量Token,优先压缩示例。把多轮示例从“三个完整对话”剪成“一段最短可行示例”,既保住了格式约束,又大幅缩减输入长度。少样本示例的收益是边际递减的,塞得再多,效果提升也很有限,Token却照常烧。

4. 检索加速:向量索引调优、量化、混合检索策略

检索慢,主要是向量库的ANN检索慢,以及海量文档下候选集太宽。这里我从三个层面说明我自己的提速方案。

4.1 向量索引类型与参数:HNSW和IVF怎么选

当数据量在百万级以下,我默认用HNSW索引,搜得准,速度也能到百毫秒级。关键在于参数调整:

  • M(每个节点的最大连接数):我设32,连接数大一点,召回率高,但内存占用上升。
  • efConstruction:建索引时的搜索范围,设200左右,建索引慢一点,但质量好。
  • efSearch:每次查询时扩展的候选集,设64,延迟一般在40-80ms。如果追求更快,可以降到32,但召回率会轻微下降。

如果数据量到了千万级以上,内存快撑不住,就切到IVF索引。IVF的核心是“聚类后再搜索”,先通过聚类把向量空间划成N个桶,查询时只搜邻近的桶。关键参数是nlist和nprobe:

  • nprobe表示查询时搜索多少个桶。nprobe越少,速度越快,但精度越低。
  • 我的调参路径是:先固定nlist=4096,然后从nprobe=8往上加,直到召回率达到需要的95%,同时保证延迟不超过150ms。

4.2 向量量化:内存降一半,延迟也能降

向量维度越高,检索耗时越长。有两个方案:

方案一是标量量化(SQ):把浮点型向量压成int8。精度损失很小,内存直接砍到四分之一。我实测下来,在百万级数据集上,SQ8压完,检索延迟大概能降20%-30%,召回率下降不到两个点,对绝大多数问答场景完全够用。

方案二是乘积量化(PQ):把向量切分成子空间,每个子空间单独聚类并编码,适合超大规模数据。但PQ的精度损失比SQ大一点,如果检索结果频繁不理想,我会退回到SQ+HNSW的组合。

4.3 混合检索:关键词兜底,语义召回加速

单用向量检索有一个经典毛病:遇到专有名词、缩写、产品型号,向量召回容易失灵。比如搜“iPhone 15 Pro 掉电快”,向量可能找到一堆“手机电池健康度”的泛泛文章,却漏掉精确配型的内容。

我的解决方案是“向量检索 + BM25关键词检索 混合”,召回后做RRF(Reciprocal Rank Fusion)融合排序。具体做法是:

  • 向量检索返回Top20。
  • BM25返回Top20,权重略偏向精确关键词匹配。
  • 用RRF公式合并排序,公式简化后就是“排名倒数的加权和”,让两个来源的结果排队不再打架。

混合检索看似增加了检索环节,实际上反而降低了无效Token进入Prompt的概率,因为召回更精准,后续提示压缩的压力也小很多。

4.4 检索侧缓存:还记得前面的语义缓存吗

把语义缓存放在检索链路之前,是最划算的一笔投资。用户问“打印机连接不上怎么办”,第一次可能跑完整个检索流程,第二次来一个相似问法,直接命中缓存答案返回,检索环节一次都不用走。

我在实际项目里测过,启用语义缓存之后,整体平均延迟从1.8秒降到0.7秒,主要收益就来自免检索和免生成。注意,语义缓存一定要绑定“知识库版本号”,知识库内容变更后,历史缓存要清理掉,否则答案会越来越陈旧。

5. 常见问题与排查技巧

这部分是纯踩坑经验。很多性能问题,不是优化方案不对,而是细节没处理到位。

5.1 缓存总是不命中怎么办

先看键设计。如果缓存Key包含时间戳、用户ID这些一秒钟变好几次的内容,那命中率必然很低。我的做法是把Key拆成“意图ID+参数摘要+知识库版本号”,固定部分和变化部分分离。

再看阈值。语义缓存的相似度阈值调太高,比如0.99,那几乎只能命中完全没有改写的句子;调太低又容易误命中。建议先用一批真实历史查询统计相似度分布,再把阈值打到分布图的拐点位置。

5.2 压缩提示后模型输出变差怎么办

记住一句话:压缩的本质是信息筛选,不是泛泛地“让模型浓缩”。如果压缩后答案变差,我建议拆开检查:

  • 是不是连关键数据(数字、型号、专有名词)都被压掉了?解决办法是加一条规则,压缩时保留所有数字后跟单位的内容。
  • 是不是上下文逻辑断了?比如把“如果A,那么B”的转折条件压没了。解决办法是把每段保留其首句和最末句,保住局部逻辑结构。
  • 是不是角色指令被压掉了一条?系统提示词里的“仅基于资料回答”这种护栏语句,永远不要出现在可压缩的文本里。

5.3 向量检索延迟突然飙升

我遇到过一种情况:数据集不变,延迟却从80ms涨到300ms。排查后才发现是批量写入把索引的实时性拖住了。

如果你把新文档实时入库并且要求立即能被检索到,那每次插入都会触发索引更新。数据写入频繁时,HNSW索引会一直处于“重建状态”,查询排队就很厉害。解决办法是把写入做成批量模式,每5秒或每100条批量刷一次索引,同时让查询走旧索引,写完再切换。

另外一个常见点是网络带宽和磁盘IO:向量文件往往几十GB,如果存储盘是慢速机械盘,加载和查询都会受影响,最好把向量库建立在SSD实例或纯内存模式下。

5.4 压测时最容易忽视的隐性问题

  • Embedding接口也会成为瓶颈,尤其向量化查询并发高的时候,建议给Embedding模型也加缓存。
  • LLM流式输出时机要提前:首字返回速度直接影响用户体验,建议在模型输出第一个字节后就启动流式返回,而不是等完整生成完再回。
  • 批处理大小:如果你针对离线批量任务优化,Batch Size适当调大能明显提高吞吐;但在线请求千万别用大Batch,尤其不要用“边生成边等待所有请求完成”的逻辑,那样单请求延迟会急剧上升。

6. 经验总结和个人会坚持的做法

性能优化不是一个一劳永逸的改动,而是一套持续按数据调整的方法论。我自己现在做新项目,第一件事就是先把缓存和日志埋点搭好。哪怕最初缓存逻辑很简单,只有精确匹配和短TTL,也能在早期挡住大部分重复流量,留出充裕时间来优化检索和压缩。

提示压缩这步,我强烈建议做成“压缩规则可配置”,不要硬编码在代码里。每次模型版本升级后,同样的压缩策略效果可能不一样,留一个配置开关,方便线上对比和回滚。

检索加速的收益上限取决于数据质量,索引参数调了半天也救不了脏数据。所以项目早期宁可多花时间在切分和清洗,也别急着上HNSW调参。

最后分享一个小技巧:性能优化的优先级,永远跟着真实流量走。先看日志统计哪个环节耗时最长、哪个环节重复率最高,再动手改。如果需要,准备一个1000条左右的历史问答数据集做回归测试,每次调完缓存或压缩策略,跑一遍看答案相似度和延迟变化。有了这套基线,后面再怎么改都不怕翻车。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询