KV cache占用90%显存却空闲?LLM推理服务容量排查实战

发布时间:2026/11/3 16:41:22
KV cache占用90%显存却空闲?LLM推理服务容量排查实战 上周排查推理服务容量告警时我在监控面板上看到KV cache 用量一路冲到 90%按这个趋势不扩容就等着OOM。可我按权重、按请求数一算显存明明还空着一大半怎么KV cache就快满了后来扒开指标逐个看才发现这个“90%”里有一半是虚的——或者说系统确实只塞满了一半的容量统计口径却把另一半也记上了。这类现象在LLM推理部署里非常常见尤其当你用vLLM这类带PagedAttention的框架或者用自研框架做连续批处理时很容易被一个孤立的百分比带偏。如果你也被“90%告警”吓到过或者想搞清楚KV cache到底怎么算、怎么调这篇记录应该能帮你省下不少弯路。定位问题只是一半另一半是理解容量模型。在展开排查过程之前我得先把KV cache的“显存账本”讲清楚因为很多误判都是因为脑子里对KV cache的成本模型是空的。1. KV cache的显存账本先算清“大头”到底从哪里来1.1 KV cache是什么为什么自回归推理离不开它KV cache全称是 Key-Value Cache也就是Transformer在自回归解码过程中缓存下来的Key矩阵和Value矩阵。理解它不需要背公式你只需要记住一个事实大模型生成token是一个字一个字往外蹦的生成第N1个token时注意力机制要重新看一遍前面N个token的Key和Value。如果不做缓存每生成一个token都得把前面所有token的Key和Value从头算一遍计算量随序列长度平方级增长没人受得了。所以KV cache就是一张“草稿纸”第一次看到前面的token时顺手把算好的Key和Value抄在草稿纸上后面每次生成新token直接翻草稿纸查不再重复计算。草稿纸上的内容越多计算越省但显存也占得越多。这里的矛盾就是整篇排查的核心——KV cache是用显存换算力的典型产物它在工程上既是性能加速器又是容量黑洞。很多刚接触部署的朋友会本能的把KV cache当成一个“还挺重要的小组件”觉得模型权重动辄几十GB缓存能占多少这个直觉恰恰是错的。权重确实大但它是固定的KV cache却是随着“并发数 × 序列长度”线性增长的而且每条请求都有自己独立的一份。并发一多、上下文一长KV cache的体量会迅速超过权重成为真正的显存大头。1.2 用公式估算一个请求到底吃多少显存KV cache的占用有一套非常机械的计算公式我建议每个做推理部署的人都把它刻在脑子里KV cache 大小 2 × 层数 × KV头数 × 单头维度 × 序列长度 × 每元素字节数 × 批大小公式里第一个常量“2”对应Key和Value两份层数就是Transformer层数KV头数和单头维度看模型结构每元素字节数由数据类型决定FP16是2字节FP8是1字节INT8也是1字节。算下来7B模型单条4096 token的请求KV cache大概是2GB左右。我带大家手算一遍Llama-2-7B32层、32个KV头、head_dim128FP16下每token每层要缓存K cache32 × 128 × 2字节 8KBV cache同样8KB合计16KB/token/层32层全部算上每个token要512KB。一个4096 token的请求就是512KB × 4096 ≈ 2GB。也就是说光一条长请求的KV cache就已经吃掉了2GB显存。如果是32条请求并发哪怕平均长度只有2048KV cache总量也有32GB比权重本身的14GB还高一截。这还没算GQA/MQA这类优化如果模型用了分组查询注意力KV头数变少占用能再低好几倍。所以你会看到很多80GB显卡部署7B模型看似“很有富余”但并发稍微上去KV cache立刻顶到天花板。那个90%告警往往就是这种动态增长带来的。1.3 权重只是“定值”KV cache才是“活水”我习惯把显存占用分为“定值”和“活水”。模型权重、CUDA context、框架内部buffer是定值加载完就固定不变KV cache是活水池子有多大就能涨多高而且它跟着流量波动走。运维LLM服务时真正需要盯的不是权重大小而是这池活水的进出速度。这个认知会改变你排查问题的思路。看到“KV cache用量90%”第一反应不该是“显存不够要加卡”而该是“池子到底多大有效载荷到底有多少这90%是按什么口径算出来的”下面进入正题——90%但只装了一半在真实环境里通常由三个原因造成。2. 用量虚高的三个典型原因为什么90%可能只装了一半2.1 batch padding载荷被“平均长度”稀释最容易被忽略、也最贴合“只装了一半”字面意思的原因是batch内padding。想象一个朴素的服务一个batch同时处理16条请求其中最长的序列是2048个token其余15条平均只有256个token。很多简化实现为了保证batch维度对齐会让每条请求都按2048预分配整条KV cache空间——哪怕实际只有256个token在跑。来算这笔账16条请求每条都占2048 token的缓存容量总分配就是32768 token的容量但有效token只有2048 15×256 5888有效载荷只有18%。就算KV cache用量显示100%真正“干活”的缓存也只有不到两成。显示90%但实际只有一半在这个场景下一点也不夸张因为预分配机制本身就是按最长序列“虚”满的。即便是vLLM这类按块分配缓存、不用整条预占的框架也很难完全避免另一种形式的“一半”。PagedAttention把KV cache切成固定大小的块比如每块16个token一个只有1个token的请求也会占满一个16 token的块剩下15个token的位置闲置。当请求长度参差不齐、块又偏大时内部碎片照样会吞掉不少容量。看到90%后别急着加内存先看看平均序列长度和最大序列长度的比例往往真相就在这道除法里。2.2 gpu_memory_utilization池子本来就小百分比自然容易飘第二个常见原因藏在框架配置里典型的就是vLLM的--gpu-memory-utilization参数。这个参数默认值是0.9含义是“模型权重 KV cache 框架临时内存最多占用总显存的90%”。听起来很合理但你真去线上看会发现情况没那么理想。显卡上不是只有模型和KV cache还有CUDA context、cudnn/cublas的工作区、分布式通信库的buffer、KV cache池申请时的对齐碎片。这些零零碎碎先吃掉一块显存后gpu_memory_utilization0.9留给KV cache池的空间会被压缩得很小。池子总容量小了池内使用率自然容易飘。原本可能应该分到20GB的KV cache池实际只圈出来12GB池子里稍微跑几个长请求百分比就站上90%可整卡显存其实还有8GB、10GB空着只是这些空余由于池子边界固定根本拿不到KV cache头上。所以看到“KV cache 90%”时不要以为整卡快满了先看一眼nvidia-smi的实际显存占用量再对照池子的总容量。如果两个数字差距很大说明问题不在物理显存不够而在分配模型把池子圈小了或者临时内存吃掉了太多份额。2.3 prefill与decode的统计口径峰值被当成常态第三个原因和KV cache的生命周期有关。一条请求在prefill阶段会一次性把整个prompt的Key和Value算完并写进缓存这个阶段KV cache会瞬间冲高到了decode阶段每次只新增一个token的缓存增长很缓慢。如果监控面板长期显示高水位很可能就是一批带有超长系统提示词的请求常驻在那儿把水位顶住了。拿我自己线上一个业务举例系统提示固定有5000个token每轮用户对话只有几百token。模型同时服务20个用户时prefill阶段要为每个用户申请5000 token的KV cache块这一下就把池子填掉大半。但系统实际每秒生成的token数很低真正的计算压力远没有90%看起来那么可怕。gpu_cache_usage_perc这个指标衡量的只是“KV cache块池已经用掉几成”并不等于“显存快满了”或者“服务马上不可用”。这一点非常关键。如果你只盯着一个百分比做告警很容易在流量正常时收到一堆假警报然后花半天加卡、迁移最后发现根因只是统计口径没对齐。看KV cache相关指标时至少要同时看三个数池子利用率、剩余空闲块数、当前运行中的请求数和平均序列长度。只有把这三个数放在一起才能还原真实压力。2.4 不是所有“一半”都是虚的多卡不均衡讲完三个主要原因补一个容易被忽略的物理场景。标题里那句“可能只装了一半”有时候是字面意思多卡部署下KV cache只装满了其中一张卡另一张卡闲置着。有次我在TP2的双卡环境排查性能nvidia-smi显示GPU0已经用了72GBGPU1只用了41GB。模型权重按理说两份卡对称KV cache也应该是两份对称分片结果差距这么大明显是KV cache的分配没有跟上张量并行切分大量注意力计算和缓存堆到了GPU0身上。这种情况如果单看聚合监控里的“KV cache用量”你会误判成整体快满但实际是负载不均衡单卡早满了另一张卡还有一半闲在那边。排查方式很简单打开每卡视角看显存占用和利用率分布是否一致不一致就优先怀疑框架的并行切分配置或attention后端实现。3. 线上排查实录从指标到配置的定位流程3.1 第一步用metrics确认“90%”是指哪一层我自己习惯的排查路径第一件事永远是确认告警里的90%到底取自哪个指标。拿vLLM举例最常被接进监控的是vllm:gpu_cache_usage_perc它的定义是gpu_cache_usage_perc num_used_gpu_blocks / (num_used_gpu_blocks num_free_gpu_blocks)分子是已分配的KV cache块数分母是KV cache池总块数。这个指标描述的是池内使用率不是整卡显存使用率。启动服务时vLLM会先预估KV cache池能分配多少块这个池是固定的。池子小、请求长指标自然居高不下。在线上可以直接抓指标验证curl -s localhost:8000/metrics | grep -E vllm:(num_used_gpu_blocks|num_free_gpu_blocks|gpu_cache_usage_perc)同时执行nvidia-smi看整卡显存剩余。两者一对比立刻能区分“池子真的被填满了”和“池子太小导致百分比虚高”。3.2 第二步评估有效载荷别只看百分比确认口径后要评估有效载荷。我会做三件事看启动日志里的KV cache池总大小、看当前所有运行请求的平均序列长度、看batch内的长度分布。vLLM启动日志里通常会打印类似“GPU KV cache size: 12.00 GB”或“# GPU blocks: 800”的信息。用这个数对比你根据模型权重和总显存推算的应有池大小能直接发现池子是否被压缩。比如A100-80G上部署7B模型权重约14GB0.9占比下理论上KV cache池应该能分出50GB以上但日志只给了12GB说明池子被临时内存、context和通信buffer挤占得很严重这就是“虚高”的温床。再看请求长度分布。我习惯把Prometheus里的“平均生成token数与最大序列长度”做成时间序列如果连续几小时平均长度只有最大长度的三分之一那大概率存在padding浪费。在自研框架里我会额外打个日志统计每个batch内有效token和分配token的比例低于50%就该改代码或改分桶策略了。3.3 第三步调整参数把容量拉回正轨排查完再动手方向就清晰很多。不同根因对应不同动作切忌一上来就疯狂调gpu-memory-utilization。如果是池子被压缩优先考虑把显存让回KV cache关闭不必要的Profiler、精简通信buffer、评估关掉框架里用不到的额外组件必要时再把--gpu-memory-utilization从0.9调到0.85或者0.88。很多人以为调高这个参数能扩大池子但实际上调得太高反而会让KV cache池申请失败或因为没给context留空间而OOM调低一点往往更稳。同时用--max-num-seqs限制最大并发请求数让每个请求都能分到足够的池内块数避免请求太多导致排队甚至拒绝。如果是batch padding导致有效载荷过低我会在自研框架里引入序列长度分桶bucketizing把请求按长度归入256、512、1024、2048、4096这样的桶每个batch只装同一桶内的请求并把batch内的KV cache预分配调整到桶上限而不是全局最大长度。这个方法简单有效能把有效载荷从20%拉到70%以上。如果是vLLM则确认启用了PagedAttention的连续批处理不要再按整条序列预分配。如果只是prefill峰值抬高水位优化重点转向降低峰值开启prefix caching让相同前缀的系统提示词在不同请求间复用KV cache而不是每个请求都重新写一遍5000 token的缓存。启用的方式很直接启动参数加--enable-prefix-caching即可收益在系统提示词很长的业务里非常可观。这三个动作做完后一定要重启服务再压一轮流量看相同负载下KV cache利用率是否回落而不是凭感觉收工。我见过太多人只改了参数不看效果最后发现新配置把正确率或者吞吐拖垮了。3.4 修改前后的效果观测有一次典型的案例服务跑在A100-80G上gpu_cache_usage_perc常年在92%告警频发。第一反应是加卡但nvidia-smi显示显存还有22GB空闲明显是分配模型的问题。我按上面三步排查后确认三件事叠加池子被临时内存压缩到了18GB、请求长度分布悬殊导致padding浪费、长系统提示词反复prefill。随后把gpu-memory-utilization从0.9调到0.88限制max-num-seqs24开启enable-prefix-caching顺便把框架版本升级到修复KV cache分片问题的版本。改动之后相同流量下指标稳定在60%上下最高峰也没再超过75%告警彻底消失。这个结果说明多数“90%告警”根本不是容量不够而是指标和分配模型错位。你看到的百分比往往只是问题表面的一个切片。4. 从“虚高”到“真满”容量规划与进阶优化4.1 长序列请求是真正的“显存刺客”排掉虚高部分后也要警惕真实容量风险。KV cache增长是动态的一条长序列请求就可能吃掉GB级缓存。前面算过70B模型在GQA结构下8K token的单条请求KV cache也要2.5GB左右100条并发请求同时跑到8K就是250GB得三四张80GB显卡才扛得住。长上下文业务的扩容压力很多时候根本不是权重而是KV cache。所以容量规划要懂得“算峰值”而不是“算平均”。我会用一个简单的公式提前估算单请求峰值KV占用 2 × 层数 × KV头数 × head_dim × max_seq_len × 2字节把并发数乘上去就得到KV cache的峰值需求。再用gpu_memory_utilization和权重推算出池上限。两者一对比能提前发现“池上限远小于并发峰值”的隐患而不是等到告警出现。4.2 压KV cache占用的几个可靠手段如果真实负载确实压得很满降KV cache占用就是刚需。我按效果和性价比排序推荐几个方向GQA/MQA模型结构层面的优化多个query头共享KV头KV cache直接除以分组因子。选型时优先选GQA类模型能从根上减少缓存。KV cache量化在不明显掉点的情况下把缓存从FP16压到FP8或INT8占用直接减半。推理框架如果支持--kv-cache-dtype fp8_e4m3这类选项优先用硬件支持的格式。Prefix caching让相同前缀复用缓存适合有公共system prompt的场景实测能砍掉一大半重复prefill的高峰。Sliding Window Attention / StreamingLLM限定注意力窗口把窗口外的KV cache丢掉适合只关心近期上下文的业务能显著压制长序列增长。序列分桶和长度限制从业务侧限制单条请求的最大长度把最坏情况钉死避免被极端长尾请求打爆。这些手段不冲突可以组合用。我线上一般第一选择是GQA模型 KV量化 prefix caching三件套基本能把KV cache压力降到原来的三分之一到四分之一。4.3 多副本与多卡场景的负载均衡最后回到多卡不均衡那个场景。多副本服务遇到“一个实例KV cache 90%另一个只有50%”时别只盯着显存先查负载均衡策略。粘滞连接、会话保持、重试风暴都会导致流量集中在单个副本上让KV cache分布像扁担一样一头沉。真正的修复是把负载均衡策略切成按请求数或按实时连接数分发并关掉不必要的会话粘滞。这一点在自建网关和K8s Service负载均衡里都能配置成本低、见效快。5. 常见问题速查与实操心得5.1 常见问题速查表我把排查中积累的典型现象整理成一张表方便大家直接对照排查异常现象可能原因快速定位方法解决动作KV cache用量90%但nvidia-smi还有大量空闲池子被临时内存/context压缩看启动日志中KV cache size对比总显存调低gpu-memory-utilization精简临时buffer短请求多但用量虚高batch padding或块内碎片统计平均长度与最大长度比例序列分桶、换用PagedAttention、缩小块大小长系统提示词请求占满水位prefix频繁重复写入看运行请求数是否远小于用量开启prefix caching每卡显存占用差异极大TP分片/KV分配不均逐卡看nvidia-smi和利用率升级框架检查并行配置与attention后端并发一高就OOM或拒绝请求池上限小于并发峰值算单请求KV峰值×并发数限制max-num-seqs再加KV量化或GQA选型5.2 几条值得记住的经验我在这种排查上踩过不少坑有几条经验特别想分享。第一条监控指标永远不要孤立看KV cache 90%和整卡显存90%是两码事必须同时看池大小和剩余空闲块数。第二条调整参数之前先想清楚“为什么调整”很多人把gpu-memory-utilization从0.9改成0.95以为能扩大KV cache池结果服务OOM问题更严重。第三条修完一定要压测验证线上流量环境复杂改一个参数可能同时影响吞吐和延迟别省这一步。还有一条容易被忽略block size不是越小越好。块越小碎片越少但块索引和调度的开销越大CPU占用会上去。线上我通常先试默认值再根据平均序列长做一次敏感性测试选一个吞吐和显存利用率的平衡点。5.3 我的几个踩坑记录讲一个真实教训。有一阵子我的服务稳定运行KV cache利用率只有50%我以为很安全结果某天运营做活动用户上传了一大段长文本全部变成长上下文请求池子瞬间从50%冲到95%然后开始拒绝服务。那次的根因就是我在容量规划时只按平均长度算没按最大长度算峰值差点造成线上事故。后来我养成了习惯每次上线前把所有业务允许的极端输入长度全部枚举一遍逐个估算KV cache峰值写进容量清单。另一次我为了压缩显存给KV cache开了量化结果某天模型输出质量崩了排查半天才发现是新版框架的量化kernel对某个特殊长度序列处理有问题回退后恢复正常。从那之后凡是涉及KV cache的优化我都会先跑一轮标准评测集确认效果没有崩坏再上线。优化不是越激进越好先保住质量再谈显存。结尾最后一次检查KV cache水位前先问自己三个问题我个人在实际操作里最后给服务下结论之前都会做一次“三层对账”。第一层看池子总量和整卡显存是否匹配排除池子被压缩的虚高第二层看有效载荷和百分比是否匹配排除padding和碎片浪费第三层看峰值流量下的长序列是否超出池上限排除真实容量风险。这三层对完基本就不会被一个孤立的90%吓到也不会明明危险了还自我安慰“只是虚高”。最后再分享一个小技巧上线前故意用一个超长请求去压一次KV cache比如直接跑一个接近max-model-len的长序列记录KV cache用量和剩余块数。这一步能逼出很多隐藏问题。等你哪天在监控面板上再看到“用量90%”你会很自然地想这个90%的池子有多大里面真正装着的有效token又是多少想明白这两个问题告警就不再是恐慌的来源而是数据决策的起点。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询