部署过 vLLM 的人应该都有过这种经历:GPU 显存看着还剩十几个 G,但服务突然 OOM;或者长对话到某一轮之后响应变得极慢,重启之后又恢复如初。如果只用nvidia-smi观察,你会觉得一切都很正常——显存没满、温度正常、进程还在,但线上问题就是反复出现。
这篇文章要讲的核心判断是:nvidia-smi对 vLLM 的 KV cache 泄漏基本是失明的。它能看到进程占用了多少显存,却看不到 KV cache 的 block 是否在合理分配、释放、复用。Kvcachescope 这类工具的出现,正是为了补上这一块视野。
读完这篇文章,你会理解 KV cache 为什么是大模型推理时的显存大头,"泄漏"到底指什么,如何在线上复现并定位问题,以及怎么把监控维度从"显存总量"升级到"KV cache block 状态"。
1. 为什么说 nvidia-smi 对 KV cache 泄漏是"失明"的
先做一个类比。nvidia-smi看到的显存占用,相当于整栋楼门口的总水表:它只知道楼里一共用了多少水,但不知道水是流进了正常的房间,还是在某条管道里漏掉了,又或者热水器和冷水管之间出现了内循环。
vLLM 的 KV cache 管理,恰好就是这栋楼里的管道系统。它的内部结构非常复杂,包括按 block 分配的空闲池、正在被请求占用的活跃 block、被抢占后暂时保留的 block、前缀缓存占用的共享 block,以及因为分配策略和碎片化而无法使用的空洞。这些细节全部发生在 vLLM 进程内部,对外部监控工具来说是不可见的。
所以你会看到一种奇怪的现象:从nvidia-smi看,进程的显存占用曲线非常平稳;但从业务层面看,服务的吞吐在下滑,延迟在上升,甚至开始报 "out of memory"。
真正的问题不是 GPU 显存总量不够,而是 vLLM 内部的 KV cache 空间出现了"逻辑泄漏"——可用 block 越来越少,但进程级显存占用没有明显变化。
我们可以从监控维度上做一个对比:
| 观察维度 | nvidia-smi | vLLM 日志与 metrics | Kvcachescope 这类 KV cache 专用工具 |
|---|---|---|---|
| 进程级显存总量 | 能看到 | 不一定直接暴露 | 不作为主要目标 |
| GPU 利用率 | 能看到 | 不一定直接暴露 | 不作为主要目标 |
| KV cache 总 block 数 | 看不到 | 部分能看到 | 能看到 |
| 空闲 block 数量 | 看不到 | 部分能看到 | 能看到 |
| 每个请求占用的 block | 看不到 | 难直接对应 | 能看到 |
| 前缀缓存命中情况 | 看不到 | 部分能看到 | 能看到 |
| 碎片化程度 | 看不到 | 难判断 | 能看到 |
| 泄漏定位 | 完全失明 | 难以量化 | 核心能力 |
这就是标题里说的 "blind":不是nvidia-smi坏了,而是它的视角停留在 GPU 硬件层面,离 vLLM 的 KV cache 管理层隔了好几层。
2. 先把 KV cache 说清楚:它为什么又大又难管
KV cache 的英文全称是 Key-Value Cache。Transformer 模型在解码时,每生成一个新 token,都需要重新计算当前序列里所有历史 token 的注意力分数。为了不把历史 token 的 Key 和 Value 全部重算一遍,推理框架会把它们缓存下来,这就是 KV cache。
用一句话概括:KV cache 是牺牲显存、换推理速度的空间换时间设计。
KV cache 的大小与模型结构直接相关,我们可以按下面的简化估算公式来理解:
KV cache 大小 ≈ 层数 × KV 头数 × 头维度 × 序列长度 × 请求数 × 每个 KV 值占用的字节数这里的"每个 KV 值占用的字节数"取决于精度。例如 FP16 是 2 字节,INT8 是 1 字节。不同模型使用的注意力机制(MHA、GQA、MQA)会影响 KV 头数,GQA 的 KV 头数通常远小于 Q 头数,所以显存占用会在这一步出现明显差异。
下面给一个 Python 计算脚本,把公式变成可以直接运行的代码:
# 文件路径:estimate_kv_cache.py # 功能:估算一个请求在给定序列长度下占用的 KV cache 显存 # 注意:这是简化估算,实际 vLLM 中还有 block 对齐、额外显存开销,结果需按比例修正 def estimate_kv_cache_memory( num_layers: int, num_kv_heads: int, head_dim: int, seq_len: int, num_requests: int, bytes_per_item: int = 2, # FP16 为 2,INT8 为 1 ) -> int: """ 返回估算的 KV cache 显存,单位:字节 """ kv_per_token = num_layers * num_kv_heads * head_dim * 2 # Key 和 Value 各一份 kv_per_sequence = kv_per_token * seq_len total = kv_per_sequence * num_requests * bytes_per_item return total # 以某个 7B/8B 级别模型为例,参数来自模型配置,可自行替换 num_layers = 28 num_kv_heads = 4 head_dim = 128 seq_len = 8192 num_requests = 16 total_bytes = estimate_kv_cache_memory( num_layers=num_layers, num_kv_heads=num_kv_heads, head_dim=head_dim, seq_len=seq_len, num_requests=num_requests, ) print(f"估算 KV cache 显存: {total_bytes / 1024**3:.2f} GB") print(f"单个请求 8K token 上下文占用: {total_bytes / num_requests / 1024**3:.2f} GB")如果你把上面的num_requests从 16 调到 64,结果会非常惊人:请求越多、上下文越长,KV cache 的显存增长越快,而且不是线性增长,是"请求数 × 序列长度"两维同时放大的增长。
这带出了一个核心矛盾:KV cache 不像静态模型权重那样一次分配就固定不变,它是在线动态变化的。
vLLM 为了管理这种动态变化,引入了基于 block 的分配机制。KV cache 不再是连续的一大块显存,而是被切成固定大小的 block,一个 block 通常可以容纳固定数量的 token。当请求不断生成新 token 时,vLLM 从空闲 block 池里取 block;请求结束时,block 应该回到空闲池等待复用。
这套机制的难点在于:
- block 的分配和释放是高频操作,一个活跃请求的每一步生成都可能触发;
- 并发请求之间会竞争空闲 block;
- 出现抢占(preemption)时,被踢出的请求 block 不会立刻释放,需要重新调度;
- 前缀缓存(prefix caching)可能让多个请求共享一部分 block,统计口径更复杂。
从运维角度看,我们无法通过 GPU 显存总量去反推这些内部状态。这就是为什么需要一个 KV cache 专用的观察工具。
3. Kvcachescope 到底补上了哪块视野
Kvcachescope 从名字上就能看出定位:Scope 是"观察范围",KV Cache 是对象,合起来就是"观察 KV cache 的显微镜"。
这类工具的价值不在于替代nvidia-smi,而在于把 vLLM 内部的 KV cache 状态暴露出来。它可以回答三个以往很难回答的问题:
- 当前到底有多少 KV cache block 可用?
- 是哪个请求、哪类会话结构占用了大量 block?
- 当服务性能下降时,是因为显存总量不够,还是因为 block 被低效占用、碎片化、命中率下降?
如果只看表面,很容易误以为"显存还够,说明 KV cache 没问题"。但 KV cache 的问题和显存总量不是一回事。
实际项目里,一个典型的故障轨迹是这样的:
- 服务刚启动时,一切正常;
- 业务方开始压测,发送大量长上下文请求;
- 某个请求因异常中断,连接断开,但在 vLLM 内部,对应的 seq 状态没有及时清理;
- 或者是前缀缓存不断累积,共享 block 越来越多,但被其他不相关请求命中不了;
- 服务开始频繁抢占(preemption),每次抢占都要重新计算,吞吐骤降;
- 最终新请求无法申请到足够 block,返回 OOM。
在这个过程中,nvidia-smi显示的显存占用可能一直平稳,因为 vLLM 在启动时通常会按照配置预留一块较大的显存区域,内部 block 池的收缩扩张不会立刻在进程级显存上反映出来。
Kvcachescope 这类工具会从 vLLM 内部读取 BlockManager 的状态,得到每个 block 的归属、每个序列的 block 列表、空闲 block 数量、前缀缓存命中情况、碎片率等指标。这些信息才是 KV cache 调优和故障定位真正需要的数据。
由于该项目是近期以 Show HN 形式发布的开源项目,具体安装方式和 API 可能快速变化,我这里不写死命令。更合适的做法是:在下面的章节中,先讲清楚不依赖任何特定工具的通用排查方案,再说明接入这类工具的通用思路。这样即使项目版本迭代,方法论依然成立。
4. 环境准备与通用手术刀:nvidia-smi 之外的三个视角
在真正定位 KV cache 泄漏之前,先要明确一件事:我们需要从三个层面观察一个正在运行的 vLLM 服务。
第一个层面是硬件层,也就是nvidia-smi能看到的内容:进程显存占用、GPU 利用率、温度、功耗。第二个层面是推理框架层,也就是 vLLM 日志和 metrics 暴露出来的内容:请求数、preemption 计数、cache 命中率等。第三个层面是资源分配器层,也就是 PyTorch caching allocator 或 CUDA 图分配器内部的状态。
下面我们先搭建一个最小实验环境。
4.1 环境要求
- 一台装有 NVIDIA GPU 的 Linux 服务器,驱动正常;
- Docker 或 Python 虚拟环境,用于运行 vLLM;
- vLLM 以及一个开源模型,本文以 Qwen 系列为例,但代码思路适用于其他模型;
- Python 3.8+,安装
requests、pynvml等辅助库。
启动一个 vLLM 服务时,建议把关键参数显式写出来,而不是全部用默认值:
docker run --rm --gpus all \ -p 8000:8000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --ipc=host \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-8B \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 8参数说明:
--gpu-memory-utilization 0.85表示 vLLM 最多可以使用 85% 的显存,这是保护 GPU 驱动、显示输出和其他进程的常见做法;--max-model-len 8192限制单请求最大上下文长度,这里只是一个示例值,需要以模型实际支持范围为准;--max-num-seqs 8限制最大并发序列数,避免请求全部打进来瞬间压垮 KV cache 池;--ipc=host在一些容器环境中用于增加共享内存,防止 tokenizer 或 DataLoader 工作进程出现共享内存不足问题。
需要特别说明的是,vLLM 版本迭代很快,部分参数在新版本中可能改名或默认行为变化。建议以你实际使用的 vLLM 版本--help输出为准。
4.2 视角一:硬件层监控命令
启动服务后,开一个终端窗口,用下面的脚本每 2 秒采样一次显存:
# 文件路径:watch_gpu.sh # 功能:每隔 2 秒打印 GPU 显存和利用率摘要 while true; do nvidia-smi --query-gpu=timestamp,memory.used,memory.total,utilization.gpu \ --format=csv,noheader,nounits sleep 2 done这个脚本会持续输出 GPU 显存总量和使用量。它能帮我们确认"进程是否在涨显存",但对 KV cache 内部的 block 状态无能为力。
4.3 视角二:vLLM 日志与 metrics
vLLM 启动时会打印模型配置、显存使用计划、KV cache block 数量等信息。要重点关注以下内容:
- 日志开头的 KV cache 显存预留信息;
- block size 和总 block 数量;
- 每次请求结束后的统计信息;
- preemption 相关警告。
同时,vLLM 兼容 OpenAI API 的服务通常会在/metrics路径暴露 Prometheus 格式的指标。生产环境中,可以用 Prometheus 采集,再用 Grafana 展示。这里做一个轻量的手工检查:
curl -s http://localhost:8000/metrics | grep -E "^vllm" | head -50如果只输出少量指标,说明你的 vLLM 版本把部分指标放到了其他命名空间下;重点观察包含cache、block、preemption、running这几个关键词的指标。
4.4 视角三:PyTorch caching allocator
如果 KV cache 分配走的是 PyTorch 的 caching allocator,我们可以在 Python 进程内调用内存摘要。实际操作上,可以在 vLLM 服务启动前注入一段探针,或者用调试模式单独跑推理脚本。
下面是一个独立脚本,展示如何在一个使用 GPU 的 Python 进程里获取 allocator 视角的内存摘要:
# 文件路径:show_memory_summary.py # 功能:展示 PyTorch caching allocator 视角的 GPU 内存状态 # 使用方式:在业务进程内或调试进程中按需打印,不要直接附加到生产进程 import torch # 触发 CUDA 初始化,让 allocator 完成初始化 torch.cuda.init() current_allocated = torch.cuda.memory_allocated() reserved = torch.cuda.memory_reserved() max_allocated = torch.cuda.max_memory_allocated() print(f"当前分配: {current_allocated / 1024**3:.2f} GB") print(f"预留显存: {reserved / 1024**3:.2f} GB") print(f"历史最大分配: {max_allocated / 1024**3:.2f} GB") # 打印完整摘要,包含 cached_blocks、reserved_segments 等内部状态 print(torch.cuda.memory_summary(abbreviated=False))这里有一个关键点:nvidia-smi看到的显存占用,往往更接近torch.cuda.memory_reserved()的范围,而不是torch.cuda.memory_allocated()。这也就是为什么服务内部实际只用了很少的 KV block,但nvidia-smi的显存数字依旧很大。
5. 用一次完整实验演示"显存没满,但 KV cache 不再工作"
下面我们通过一次可重复的实验,理解 KV cache 逻辑泄漏的场景。实验不做任何危险操作,只观察服务行为。
5.1 实验目标
让一个 vLLM 服务连续处理多轮长对话请求,观察三个现象:
nvidia-smi显示的进程显存是否变化;- vLLM 内部 KV cache 状态如何恶化;
- 请求最终是否出现 OOM 或性能骤降。
5.2 压测脚本
下面脚本会模拟多轮对话:每一轮都在原始输入后面追加新的用户内容,让序列长度不断增加。
# 文件路径:simulate_long_chat.py # 功能:模拟多轮长对话请求,持续增加输入的 token 长度 # 使用方式:python simulate_long_chat.py import time import requests BASE_URL = "http://localhost:8000/v1/chat/completions" conversation = [ {"role": "system", "content": "你是一个耐心的助手,请详细回答每个问题。"}, ] # 每轮追加一段文本,让序列长度显著增长 for round_idx in range(10): user_text = f"这是第 {round_idx} 轮的补充信息,请结合上面的对话继续分析:" user_text += "我们需要非常仔细地讨论一个技术问题," * 50 conversation.append({"role": "user", "content": user_text}) payload = { "model": "Qwen/Qwen3-8B", "messages": conversation, "max_tokens": 256, "temperature": 0.7, } try: start_t = time.time() resp = requests.post(BASE_URL, json=payload, timeout=120) cost = time.time() - start_t if resp.status_code != 200: print(f"第 {round_idx} 轮失败,HTTP {resp.status_code}: {resp.text[:200]}") break data = resp.json() content = data["choices"][0]["message"]["content"] usage = data.get("usage", {}) print(f"第 {round_idx} 轮成功 | 耗时 {cost:.2f}s | " f"prompt_tokens={usage.get('prompt_tokens')} | " f"completion_tokens={usage.get('completion_tokens')}") except Exception as exc: print(f"第 {round_idx} 轮异常: {exc}") break time.sleep(1)运行方式:
python simulate_long_chat.py此时另开一个终端运行显存采样:
bash watch_gpu.sh5.3 通过 vLLM 日志观察 preemption
在多轮请求打到一定程度后,vLLM 日志中很可能出现 preemption 相关记录。这表示新的 token 申请不到足够的 KV cache block,系统必须把一部分已缓存的历史 block 丢弃或重算。
preemption 次数上升时,即使nvidia-smi显示显存没有变化,服务的实际可用 KV cache 空间也已经缩水。这是 KV cache 逻辑泄漏最典型的表现之一。
5.4 用 pynvml 从进程外读取显存
上面实验更多是服务端视角。我们再写一个独立的 Python 脚本,用pynvml从外部分钟级采样显存,并记录时间点,方便和业务日志对齐:
# 文件路径:sample_gpu_with_pynvml.py # 功能:从进程外读取 GPU 显存并记录到 CSV # 使用方式:python sample_gpu_with_pynvml.py 2 100 import csv import sys import time from datetime import datetime from pynvml import ( nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo, nvmlDeviceGetUtilizationRates, ) def main(): interval = int(sys.argv[1]) if len(sys.argv) > 1 else 2 count = int(sys.argv[2]) if len(sys.argv) > 2 else 100 output = "gpu_samples.csv" nvmlInit() handle = nvmlDeviceGetHandleByIndex(0) with open(output, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["timestamp", "used_mb", "free_mb", "total_mb", "gpu_util"]) for _ in range(count): mem = nvmlDeviceGetMemoryInfo(handle) util = nvmlDeviceGetUtilizationRates(handle) now = datetime.now().isoformat() writer.writerow([ now, mem.used // 1024**2, mem.free // 1024**2, mem.total // 1024**2, util.gpu, ]) f.flush() time.sleep(interval) print(f"采样完成,结果写入 {output}") if __name__ == "__main__": main()运行方式:
python sample_gpu_with_pynvml.py 2 100这个脚本的价值在于把显存数据落到文件里,你可以用 Excel、pandas 或任何可视化工具画曲线,和时间轴对齐。
5.5 实验结论
如果实验顺利,你大概率会看到以下结果:
nvidia-smi或pynvml的显存占用曲线没有明显上升,甚至是一条平台线;- 服务在第 N 轮请求后开始变慢,preemption 增多;
- 某些请求开始报错,提示无法分配足够的 KV cache block;
- 重启服务后一切恢复。
这不是模型的 bug,也不是 vLLM 简单的显存泄漏,而是 KV cache 空间在高并发、长上下文场景下的正常但难观测的消耗过程。Kvcachescope 这类工具的价值,就是把这个过程可视化,让"难观测"变成"可观测"。
6. 接入 Kvcachescope 的通用思路
Kvcachescope 因为刚发布不久,安装方式可能有变化。我不写死命令,而是给出一个"面对这类工具时通用的接入思路",你可以拿着这个思路去对照官方仓库的 README。
6.1 确认工具的采集模式
KV cache 监控工具一般有两种模式:
- 旁路采集:进程外拉取 vLLM 的 metrics 或日志,做二次聚合;
- 应用内探针:在 vLLM 进程内注册回调,直接读取 BlockManager 状态。
你先确认 Kvcachescope 是哪种模式。如果是旁路采集,不需要改动 vLLM 启动命令;如果是应用内探针,通常需要在启动 vLLM 的 Python 入口中增加几行初始化代码。
6.2 通用接入步骤
无论工具具体怎么实现,接入步骤大概率是下面几步:
- 安装工具,建议在独立虚拟环境中安装;
- 确认 vLLM 服务 metrics 端口或日志路径;
- 运行工具,观察能否读到 KV cache block 数据;
- 用压测脚本制造长上下文请求,观察面板数据变化;
- 将历史数据保存,和
nvidia-smi曲线对比。
6.3 一个示意性的探针写法
下面代码是"示意性质",不代表 Kvcachescope 的真实 API。目的是让你理解探针模式大概长什么样:
# 文件路径:kv_cache_probe_demo.py # 功能:探针模式接入的通用示意代码 # 注意:这段代码不是 Kvcachescope 的真实 API,只用于理解接入思路 import time # 假设工具提供了这样的接口,具体名称以仓库文档为准 # from kvcachescope import attach_vllm_probe # 初始化探针,传入 vLLM 服务进程或 engine 实例 # probe = attach_vllm_probe(engine_instance) # 每隔 5 秒打印一次 KV cache block 状态 for _ in range(12): # state = probe.get_kv_cache_state() # print(state.summary()) time.sleep(5)如果你的目标是在生产环境中使用,务必先看官方文档确认 API,再到测试环境验证,不要直接把示意代码复制到生产。
6.4 给团队的落地建议
Kvcachescope 这类工具更适合放在压测和问题定位阶段使用,不适合作为唯一的生产告警源。生产环境仍然建议以 vLLM 自带 metrics、请求延迟、错误率和 preemption 计数为主,KV cache 专用工具作为深入排查的辅助手段。
团队接入时建议分三步走:
- 测试环境跑通:在压测环境部署工具,确认它能看到 KV cache block 变化;
- 建立基线:记录不同并发、不同上下文长度下的 block 占用基线;
- 关联告警:当 preemption 抬升或 cache 命中率下降时,用工具回放现场。
7. 常见问题与排查思路
下面整理几个与 KV cache、vLLM 部署、nvidia-smi相关的高频问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
nvidia-smi报错 "has failed because it couldn't communicate with the nvidia driver" | NVIDIA 驱动模块未加载、内核模块版本不匹配 | 执行 `lsmod | grep nvidia`,看驱动模块是否存在 |
容器内nvidia-smi正常但 vLLM 识别不到 GPU | 容器缺少 GPU 运行时参数 | 检查 docker run 命令是否带--gpus all | 增加--gpus all,并确认 NVIDIA Container Toolkit 已安装 |
nvidia-smi显存没满,但新请求报 OOM | KV cache block 耗尽或碎片化 | 查看 vLLM 日志中的 preemption 记录,拉取 metrics | 调低--max-num-seqs,适当调低--max-model-len,观察 KV cache 命中率 |
| 服务运行几小时后吞吐明显下降 | 长上下文请求累积,preemption 增加;前缀缓存占用增长 | 对比nvidia-smi曲线和 vLLM 日志 | 通过工具查看哪些请求占用了无法复用的 block,必要时滚动重启 |
--enable-prefix-caching开启后感觉没有效果 | 请求前缀不相同,或版本默认行为不同 | 检查 metrics 中 cache 相关计数 | 确认请求的 system prompt 是否一致,按版本文档确认参数是否默认开启 |
修改--gpu-memory-utilization后启动失败 | 预留显存过低或过高,模型加载空间不足 | 查看 vLLM 启动日志中的内存估算日志 | 从 0.8 开始逐步调整,避免一次顶满 |
某些版本遇到和chunk_size相关的异常行为 | Prompt 分块、编码或缓存粒度相关的参数变化 | 查看该版本 release notes 和 issue 区 | 优先回退到稳定版本,或在官方 issue 中搜索相同报错 |
这里要特别提醒一点:对生产环境做任何显存相关配置变更前,先备份当前启动命令和配置,在测试环境验证,并准备好快速回滚手段。显存参数一旦设置错误,最容易出现的不是服务不可用,而是"服务能启动,但压测几分钟后才暴露问题"。
8. 最佳实践:让 KV cache 可观测、可预期、可回收
结合上面的实验和排查思路,下面给出几条在工程项目中能直接落地的建议。
8.1 不要顶满显存
gpu-memory-utilization不要设置成 0.98 或 0.99。vLLM 确实可以在高百分比下运行,但一旦出现 KV cache 碎片化或突发长请求,你就没有缓冲空间。更稳妥的做法是:
- 先按 0.85 起步;
- 观察 preemption 指标;
- 如果长期无 preemption,再小幅上调。
8.2 把 KV cache 监控接入 Prometheus 体系
生产环境不要靠"肉眼盯 nvidia-smi"。至少要做到:
- 采集 vLLM 的
/metrics; - 配置 preemption、cache 命中率、运行中请求数等关键告警;
- 把
nvidia-smi的显存数据也采集进来,方便故障时对齐时间线。
8.3 区分"显存不够"和"KV cache 被低效占用"
当服务 OOM 或性能下降时,先判断属于哪一类:
- 显存总量确实不够:压低并发、减少上下文长度、升级硬件;
- KV cache 被低效占用:检查长连接请求、未释放的会话状态、前缀缓存命中率、碎片率。
Kvcachescope 这类工具的核心应用场景是第二种判断。
8.4 建立请求级观测能力
比"进程级显存监控"更贴近问题的,是请求级观测:
- 每个请求的 prompt token 数和 completion token 数;
- 每个请求实际分配的 block 数;
- 请求结束后 block 是否回到空闲池;
- 达到一定序列长度后,preemption 是否开始出现。
如果能把这些数据和业务日志关联,定位泄漏的速度会快很多。
8.5 安全与权限提醒
如果 KV cache 专用工具提供"强制释放 block"或"手动清理缓存"的能力,要格外谨慎。这类操作会影响在线推理服务,甚至导致正在处理的请求直接失败。建议只在测试环境尝试,生产环境需要走变更评审流程。
9. 总结与后续学习方向
这篇文章真正想传达的点是:不要把nvidia-smi的显存数字当成 KV cache 健康状态的指标。它适合观察硬件资源水位,但看不见 vLLM 内部的 block 分配、空闲、碎片化和 preemption 状态。Kvcachescope 这类工具的出现,意味着社区开始把 KV cache 当作一等公民来观测,这对部署 vLLM 的团队来说是一个很及时的补充。
下一步你可以这样做:
- 用文中的压测脚本在你自己的 vLLM 服务上复现一次 KV cache 空间耗尽过程;
- 用
watch_gpu.sh和sample_gpu_with_pynvml.py采集显存数据,观察它和业务表现的脱节; - 查看 Kvcachescope 官方仓库的 README,确认它当前支持哪种接入方式;
- 把 vLLM 的 metrics 接入 Prometheus,建立 preemption 和 cache 命中率的告警。
KV cache 是一个越深入越有意思的领域。继续研究的方向可以是 vLLM 的 BlockManager 源码、PagedAttention 的实现原理、prefix caching 的命中边界,以及在不同并发模型下 KV cache 碎片率的差异。先让问题可观测,才能让问题可解决。