1. 先搞清楚 Nvidia-smi 为什么对 KV Cache 泄漏“失明”
如果你正在用 vLLM 部署大模型,而且习惯用nvidia-smi看显存占用,那你很可能已经被“骗”过好几次了。最典型的现象是:模型跑着跑着,nvidia-smi显示显存占用很高,但 vLLM 的日志里显示每个请求的 KV cache 占用却不大;或者反过来,日志显示 KV cache 已经快满了,nvidia-smi看到的显存数字却波澜不惊。这个 GitHub 项目 Kvcachescope 解决的,正是这种“工具看不到真实情况”的问题。
先说结论:Nvidia-smi 不是不知道显存里有 KV cache,但它不会把 KV cache 单独列出来给你看。它只能报告整张显卡的总显存使用量,包括模型权重、激活值、CUDA context、通信缓冲区、临时张量,以及 vLLM 管理的 KV cache 池。这些内容全部混在一起,从 nvidia-smi 的视角看过去,你只能得到一个总数,完全不知道 KV cache 到底占了多少、有没有持续增长、是不是泄漏了。
适合看这篇文章的人有三类:
- 用 vLLM 做生产部署,需要判断显存是否够用、KV cache 是否泄漏的运维和算法工程师。
- 正在调
gpu_memory_utilization、max_num_seqs、max_model_len等参数,但搞不清 KV cache 真实占用的人。 - 想自建一个轻量观测工具,把 vLLM 的 KV cache 占用实时可视化的人。
最值得关注的点是:Kvcachescope 不是去“修复” KV cache 泄漏,而是让你能看见它。在排查泄漏问题之前,先把观测做起来,比盲目改配置有用得多。下面按实际落地顺序拆解。
2. vLLM 里的 KV Cache 到底是怎么分配和释放的
2.1 PagedAttention 机制决定了 KV Cache 不能直接用 nvidia-smi 看
要理解 Kvcachescope 的价值,先要理解 vLLM 的 KV cache 管理方式。
vLLM 的核心优化是 PagedAttention,思路类似操作系统的分页内存管理。KV cache 不是按完整序列分配的,而是按固定大小的 block 分配的。每个 block 可以存放若干个 token 的 Key 和 Value 张量。请求过来时,vLLM 从显存里预留好的 KV cache 池中取 block;请求结束或 token 被淘汰时,block 再释放回池中。
这个机制带来一个关键结果:KV cache 对 vLLM 来说是一整块预先分配好的显存池,而不是“用一点涨一点”的动态内存。你在启动 vLLM 时通过gpu_memory_utilization指定显存利用率,vLLM 会在 CUDA context、模型权重之后,把剩余显存尽量都预留给 KV cache 池。也就是说,即使你现在只处理一个请求,KV cache 池也可能已经占用了十几个 GB 的显存。
nvidia-smi 看到的高显存占用,很大概率是 vLLM 预先占用的 KV cache 池。这个数字高,不代表泄漏;这个数字稳定,也不代表一切正常。关键在于:池子里有多少 block 是空闲的,有多少 block 被占着,被占的 block 是否超出了正常请求的峰值。
2.2 泄漏的真实形态:不是显存总数一直涨,而是空闲池不断缩水
很多人把“显存泄漏”理解成nvidia-smi里的数字一直上涨,涨到某个临界点 OOM。但在 vLLM 场景下,更常见的泄漏形态是:
- KV cache 池的总大小不变,因为启动时就预分配好了。
- 池中的空闲 block 不断减少,因为某些请求结束后,block 没有被正确释放回池中。
- 表现为并发请求变多时,可用 KV cache 不够,触发 preemption 或者请求排队,但 nvidia-smi 的显存总数并没有大幅波动。
这种情况下,你盯着 nvidia-smi 看,只会觉得“显存一直很满,是不是模型权重太大?”或者“显存没有涨,应该没泄漏吧?” 两个判断都是错的。
Kvcachescope 做的事情,就是把 vLLM 内部的 KV cache block 分配状况暴露出来,让你能看到空闲 block、占用 block、每个序列的 block 分布,以及池的使用率变化曲线。有了这些数据,泄漏就不再是“感觉”,而是可量化的趋势。
3. 在本地环境把 Kvcachescope 跑起来
3.1 前置条件:需要 vLLM 的日志或监控接口先有数据
Kvcachescope 本身不是替代 vLLM 的独立系统,而是一个观测层。它的数据来源一般是 vLLM 在运行过程中输出的指标,或者 vLLM 提供的内部状态信息。因此,前置条件是你的 vLLM 服务已经能正常跑起来,并且能输出 KV cache 相关指标。
实际部署时,我建议先把以下环境信息确认一遍:
- vLLM 的版本。不同版本对 KV cache 指标的名称和输出位置可能有差异,建议先确认版本再对接。
- 启动参数里是否开启了相关的统计或日志输出。有些版本需要显式开启 metrics 或 profiling 开关。
- 监控环境是否有 Python 和常见数据处理库。Kvcachescope 这类工具通常以命令行或 Web 面板形式运行,Python 环境会比较省事。
原始材料里没有给出具体的安装命令,所以这里不写死。通用做法是先把项目代码克隆到本地,然后按照仓库 README 里的依赖列表安装。如果你拿到的版本已经打包成 pip 包,那直接安装等待时间会短很多。
3.2 先跑一个最小样例:单请求、单输出、连续观察
我的习惯是,任何观测工具第一轮都不上批量,先跑最小样例。
启动一个 vLLM 服务,用一个小模型,甚至可以直接用本地已有的量化模型。然后通过 API 发一个请求,同时让 Kvcachescope 开始记录。请求完成后,看 KV cache 池的占用率是否恢复到请求之前的状态。
这里有三个判断点:
- 请求开始时,空闲 block 是否减少。
- 请求结束时,空闲 block 是否恢复。
- 连续发多个相同请求,空闲 block 的恢复曲线是否平稳。
如果第一条和第二条符合预期,说明 vLLM 的基本分配释放逻辑正常。如果第三条出现“每次请求后空闲 block 都少一点点”的阶梯式下降,那就基本可以确认存在泄漏趋势。
不要一上来就压并发。并发压力大时,preemption 本身会触发 block 的多次分配和释放,干扰判断。先单请求抓基线,再逐步加压。
4. 怎么判断是“正常占满”还是“泄漏”
4.1 建立基线和阈值:不要凭感觉说泄漏
观测工具上线后,你面对的第一问题是:什么算正常,什么算泄漏?
我建议先用一个稳定负载压出基线。比如固定 10 个并发请求,连续跑 10 分钟,记录 KV cache 池的空闲 block 数量。如果空闲量在初始下降后保持稳定,波动在合理范围内,那说明没有泄漏。如果空闲量持续阶梯式下降,即使速度很慢,也值得继续观察。
判断泄漏的常见指标是:
- 空闲 block 占比持续下降,且不随请求结束而恢复。
- KV cache 池命中率或者重利用率长期偏低。
- 随着时间推移,相同请求的响应速度变慢,因为可用的 KV cache 空间变小,触发更多 preemption。
- 最终在显存没有占满的情况下,vLLM 报出类似 KV cache 空间不足的错误。
这些现象里,前两条是 Kvcachescope 能直接看到的,后两条是你需要在业务侧感知的。结合起来判断,准确率会高很多。
4.2 CPU 和 GPU 侧分开看:有些“泄漏”实际上是显存碎片
还有一个常见误区,就是把所有显存占用异常都归为 KV cache 泄漏。实际上,vLLM 运行过程中,显存数据会分成几类:
- 模型权重:固定占用,不会变化。
- CUDA context:固定占用,取决于 CUDA 版本和模型。
- 激活值:每个请求临时产生,请求结束即释放。
- KV cache 池:vLLM 预先分配,池内 block 动态分配和释放。
- 通信缓冲区:多卡环境明显,单卡环境占比低。
如果 Kvcachescope 显示 KV cache 池的空闲 block 很充足,但 nvidia-smi 显示显存已经快满了,那大概率不是 KV cache 泄漏,而是其他部分吃掉了显存。这时候要去看激活值峰值、CUDA context 大小,甚至要考虑显存碎片化问题。
反过来,如果 nvidia-smi 显示显存还有余量,但 vLLM 反复出现 KV cache 不足的报错,那才是 KV cache 池内部出现了管理问题,或者你配置的gpu_memory_utilization太低,导致 KV cache 池本身太小。
5. 让观测工具服务生产环境:批量、连续、告警
5.1 从单机观测到定时采集
本地跑通 Kvcachescope 后,如果只手动看几次,价值有限。真正有用的是让它持续运行,把 KV cache 池使用率输出成时间序列。
实际操作中,可以设置一个定时任务,每隔一段时间采集一次指标,比如每 30 秒或 1 分钟。采集内容包括:
- KV cache 池总 block 数。
- 当前空闲 block 数。
- 当前占用 block 数。
- 正在运行的序列数量。
- 发生 preemption 的次数。
有了这些连续数据,就可以绘制趋势线。当空闲 block 占比从稳定状态进入持续下降区间,或者 preemption 频率突然升高,就应该触发人工排查。
这里要提醒一点:不要把采集间隔设得太短。KV cache 池的状态变化非常快,如果每 100 毫秒采集一次,可能只是抓到瞬时抖动,看不出趋势。生产环境 30 秒到 1 分钟一次通常足够。
5.2 接入日志和告警,不要只靠肉眼看面板
Kvcachescope 这一类工具,如果只能看实时面板,那它更适合调试,不适合生产监控。更稳妥的做法是把指标输出到日志文件,再接入现有的监控系统。
我在生产环境里一般会做三件事:
- 把 KV cache 池的使用率写入结构化日志,比如 JSON 格式,方便后续检索。
- 在空闲 block 占比低于某个阈值时,产生一条警告日志。
- 把 preemption 计数和请求排队时间关联起来,判断是否需要扩容或调整并发参数。
阈值怎么定?不能只看绝对数字。要看你的负载模型。长期并发高、请求长,空闲 block 占比天然会低;空闲池从 30% 降到 5% 可能只是负载变化,不是泄漏。但如果负载没有变化,空闲池持续下降,那就是异常信号。阈值的意义不是“低于 10% 就是泄漏”,而是帮助你发现趋势异常。
5.3 多卡环境要注意:每张卡的 KV cache 池独立,但需要对照看
如果你的 vLLM 服务跑在多卡环境,比如两张或很多张 GPU,情况会更复杂。vLLM 的 KV cache 会根据并行策略分布到多张卡上,但每张卡的显存状态可能不完全一致。
这时建议为每张卡单独记录 KV cache 池使用率,同时对比各卡之间的差异。如果某张卡的空闲 block 明显低于其他卡,而请求分布又是均匀的,那可能是负载分配不均,也可能是那张卡的显存管理出了问题。
还有个细节:多卡环境下,nvidia-smi的输出包含每张卡的利用率、显存占用、温度、功耗。Kvcachescope 能补充的是“每张卡上 KV cache 池内部状态”。两者要对照起来看,而不是只盯其中一个。
6. 用参数调整配合观测结果,解决 KV Cache 相关问题
6.1 先看数据,再改gpu_memory_utilization
很多人一遇到显存问题,第一反应是把gpu_memory_utilization调高。但这个参数不是越高越好。它决定了 vLLM 愿意把多少比例的显存预留给 KV cache 池。调高后,KV cache 池变大,能容纳更多 token,但也意味着留给 CUDA context、激活值、其他缓冲区的空间更小。如果模型本身较大,或者并发请求触发大量激活值,调太高反而容易导致显存分配失败。
正确做法是先用 Kvcachescope 观察 KV cache 池的使用率。如果空闲 block 时长时短,说明池容量基本够用,不需要调高。如果空闲 block 长期处于低位,且 preemption 频率高,再考虑调高gpu_memory_utilization或减小max_num_seqs、max_model_len。
6.2 并发数、序列长度和 KV Cache 池容量的关系
KV cache 池能容纳多少 token,取决于三个因素:
- KV cache 池总大小。
- 每个 token 在模型中每层所需的 KV cache 大小。
- 每层使用的 group size 和 head size 等结构参数。
简单理解:模型越大、层数越多、注意力头越多,每个 token 占用的 KV cache 空间就越大。同样的 KV cache 池,能容纳的 token 数就越少。
如果你发现 KV cache 池频繁打满,优先考虑的是降低并发请求数,或者降低max_model_len,而不是直接加显存。因为 KV cache 的占用与“正在生成的 token 总数”正相关,而正在生成的 token 总数等于并发序列数与每个序列长度的乘积。想压住占用,砍这两项最直接。
6.3 调整参数后,重新观察基线是否变化
参数调整不能只看服务能不能启动。我建议每次调整后,都用固定负载重新测一轮基线:
- 相同并发数。
- 相同请求内容。
- 相同持续时间。
- 观察 KV cache 池的空闲率曲线、preemption 次数、平均响应时间。
如果调整后 KV cache 池空闲率上升,但响应时间明显变慢,说明你砍掉的容量超过了实际需求,属于过度调整。如果空闲率上升,preemption 下降,响应时间变化不大,那这次调整是有价值的。
7. 常见问题与排查链路
7.1 KV cache 池使用率很高,但服务没有报错
这种情况最常见的原因是负载本来就不低,比如并发请求多、序列长。先看正在运行的序列数。如果序列数高,KV cache 占用高就是正常的。再看 preemption 次数。如果 preemption 一直没有明显增长,说明池容量够用,只是“繁忙”。
注意:KV cache 池使用率高,不等同于泄漏。泄漏的定义是“请求结束后,应该释放的 block 没有释放”,而不是“池子被占满了”。
7.2 空闲 block 持续下降,但显存总量没有变化
这种情况很典型。vLLM 启动时已经预分配了 KV cache 池,所以 nvidia-smi 的显存总量不会因为池内分配变化而波动。你只能通过 vLLM 内部的指标看到空闲 block 在减少。
排查顺序是:
- 先看是否单请求连续运行后也下降。如果是,说明释放逻辑可能有问题。
- 再看并发场景下的下降幅度。如果并发越大下降越快,可能是 block 管理在高并发下出现竞争问题。
- 确认 vLLM 版本。已知的版本缺陷可能影响 block 释放。
- 把观测周期拉长,排除偶发抖动。
7.3 请求结束后,KV cache 占用没有恢复正常
如果单请求测试就出现这个问题,优先怀疑释放逻辑。检查你的请求是否真的正常结束,包括客户端是否断连、输出流是否完整关闭。有些情况下,请求尚未完全结束,vLLM 会保留对应的 KV cache block,这是正常行为,不是泄漏。
判断标准很简单:等客户端连接完全断开后再观察几秒,看 block 是否释放。如果恢复正常,说明只是时序问题。如果长时间不恢复,再考虑 KV cache 泄漏。
7.4 多卡环境下各卡 KV cache 占用差异很大
先检查请求负载是否均匀分布到各卡。如果负载不均,KV cache 占用不均很正常。其次检查模型并行配置。有些并行策略下,不同卡存放不同层的 KV cache,每层 block 大小不同,显存占用自然不同。最后才考虑某张卡存在异常释放问题。
7.5 监控工具本身看不到数据
这种问题往往不是 Kvcachescope 坏了,而是数据源没有配置对。按这个顺序排查:
- 先确认 vLLM 服务是否在运行,且监控指标确实有输出。
- 确认监控工具的采集路径、端口、权限是否正确。
- 确认采集频率是否过快,导致缓冲刷新不及。
- 确认 vLLM 版本和监控工具版本之间是否存在兼容问题。
8. 从“看见 KV Cache”到真正解决泄漏问题
8.1 观测只是第一步,定位才是关键
Kvcachescope 这类工具的最大价值,不是给你一个好看的仪表盘,而是把 vLLM 内部被隐藏的状态暴露出来。它解决的是“盲区”问题,而不是“修复”问题。
在实际排查中,我一般会按这个思路走:
- 先用观测工具确认 KV cache 池是否存在持续下降趋势。
- 如果存在,区分是负载变化还是释放异常。
- 如果是释放异常,去检查请求生命周期、输出流闭合、批量任务是否有异常分支。
- 如果找不到明显问题,检查 vLLM 版本和已知缺陷。
- 最后才考虑通过调整参数来缓解,而不是根治。
8.2 长期生产建议:把 KV cache 观测纳入常态化监控
如果你只是学习跑 demo,手动看一下 KV cache 占用就够了。但如果是生产环境,我强烈建议把 KV cache 的观测纳入常态化监控。
原因很简单:KV cache 泄漏往往不是一瞬间发生的,而是慢慢累积的。等到 nvidia-smi 的显存数字出现异常,或者服务开始频繁报错,影响面已经很大了。而 KV cache 池内部的空闲 block 变化,能更早反映问题。
具体落地上,至少做到三点:
- 定时采集 KV cache 池使用率和 preemption 次数。
- 设置趋势异常告警,而不是只盯固定阈值。
- 每次升级 vLLM 版本或调整部署参数后,重新跑一轮基线。
8.3 最后踩坑经验:不要只信一个工具
我的建议始终是:Nvidia-smi 要看,Kvcachescope 这类 KV cache 观测也要看,但都不要单独依赖。
nvidia-smi 适合看整卡资源状态,比如显存有没有被其他进程吃掉、GPU 利用率是不是健康、卡的功耗和温度是否正常。 KV cache 观测工具适合看 vLLM 内部的显存池管理情况。 两者结合,才能完整判断一个 vLLM 服务的显存健康状况。
如果只盯着 nvidia-smi,你会忽略 KV cache 池内部的分配和泄漏趋势。如果只盯着 KV cache 池,你会忽略 CUDA context、激活值和其他进程造成的显存占用。多视角交叉验证,才是生产环境该有的态度。