详情

首页手游攻略 KV Cache 不是魔法:长上下文的显存账如何算?

KV Cache 不是魔法:长上下文的显存账如何算?

佚名 2026-07-21 18:25:55

前言:问题不是“有没有缓存”,而是“缓存够不够用”

大模型生成文本时,不是一次性把完整答案写出来。

KV Cache 不是魔法:长上下文的显存账怎么算?

它是一个 token 一个 token 往前走:

根据已有上下文,预测下一个 token把新 token 接到上下文后面再预测下一个 token

这件事听起来简单,但一旦上下文变长,问题就来了。

比如一个请求里有:

system prompt工具说明用户问题几十页文档历史对话中间推理结果

模型每生成一个新 token,都要参考前面这些内容。

如果每一步都把前文完整重算一遍,推理会非常慢。

所以推理框架都会使用 KV Cache

把前文已经算过的 Key / Value 缓存下来后面生成新 token 时直接复用

这就是 KV Cache 的基本作用。

但工程上真正麻烦的地方不在这里。

真正麻烦的是:

KV Cache 省了计算,却把压力转移到了显存。

上下文越长、并发越高、输出越久,KV Cache 占用就越大。

部署模型时,KV Cache 最终会变成一道资源预算题:

显存有多少?上下文多长?并发多高?延迟和吞吐怎么取舍?

这篇文章按这个问题链展开:

为什么需要 KV Cache它到底缓存了什么显存占用怎么估算部署时怎么配置和排查

一、生成为什么会越来越慢?

先从最朴素的生成过程看起。

假设模型要生成这句话:

KV Cache 可以减少重复计算

它不是一次生成整句话,而是逐步生成:

KVKV CacheKV Cache 可以KV Cache 可以减少KV Cache 可以减少重复...

每走一步,前文就变长一点。

而 Transformer 的 attention 机制要求当前 token 去看前面的 token。

也就是说,当模型生成“重复”时,它要参考:

KV / Cache / 可以 / 减少

当它继续生成“计算”时,它又要参考:

KV / Cache / 可以 / 减少 / 重复

上下文越长,历史越多。

如果没有缓存,模型每一步都要重新处理完整前文。

这就像你写文章时,每写一个字都从标题开始重新读一遍全文。

能做,但很浪费。

KV Cache 的第一个动机就在这里:

前文已经算过了,就不要每一步都重新算。

二、Attention 里为什么只缓存 K/V?

要理解 KV Cache,绕不开 attention 里的三个量:

Q:QueryK:KeyV:Value

可以先用一个不严格但好理解的说法:

Q:当前 token 想找什么信息K:历史 token 可以被怎样匹配V:历史 token 真正提供什么内容

当前 token 会拿自己的 Q,去和前文每个 token 的 K 做匹配。

匹配分数越高,说明当前 token 越应该关注那个历史 token。

然后模型再根据这些分数,把对应的 V 加权汇总。

简化成一条链:

当前 token 的 Q↓匹配历史 token 的 K↓得到 attention 权重↓加权汇总历史 token 的 V

所以 attention 本质上是在每一层里做一次“查找和汇总”。

那为什么缓存的是 K/V,不是 Q/K/V

原因在自回归生成的方向。

大语言模型生成时,是从左到右的。

准备生成下一个 token 时,真正发起查询的是“当前位置”。

所以当前 token 需要新的 Q

但历史 token 的 K/V 已经算好,可以直接复用。

比如当前上下文是:

KV Cache 可以

模型已经为前面三个 token 算过:

KV -> K1, V1Cache-> K2, V2可以 -> K3, V3

下一步生成新 token 时,只需要:

算新 token 的 Q/K/V用新 token 的 Q 去看旧 K/V把新 token 的 K/V 追加进缓存

旧 token 的 Q 对后续生成没有太大价值。

因为旧 token 不再作为“当前位置”发起查询。

所以缓存的是:

Key / Value

这就是 KV Cache 这个名字的来源。

三、prefill 和 decode:KV Cache 快在哪里?

一次完整的大模型请求,可以拆成两个阶段:

先读入输入:prefill再生成输出:decode

这两个词听起来有点工程化,但其实很好理解。

prefill 就是模型正式回答前,先把你的输入全部读一遍。

这个输入包括:

system prompt用户问题历史对话工具说明RAG 检索出来的文档你塞进去的长文本

比如你把一段 128K tokens 的长文档发给模型。

模型不能没读文档就开始答。

它要先把这 128K tokens 过一遍 Transformer,并在每一层里为这些 token 生成对应的 K/V。

这个过程就是 prefill。

所以 prefill 可以理解成:

处理 prompt理解输入建立初始 KV Cache准备生成第一个 token

这个阶段通常决定首 token 延迟,也就是:

Time To First Token,TTFT

如果 prompt 很长,prefill 就会很重。

用户感受到的就是:

我发出请求后,模型迟迟没有开始吐第一个字。

decode 则发生在模型开始输出之后。

从第一个输出 token 开始,模型进入这种循环:

生成一个新 token把新 token 追加到上下文为新 token 计算新的 K/V把新的 K/V 追加到 KV Cache继续生成下一个 token

每生成一个新 token,模型会复用前面已经缓存的 K/V,只为新 token 追加新的 K/V。

这个阶段决定生成过程中的速度,也就是:

Inter-Token Latency,ITL

如果输出很长,decode 就会跑很久。

用户感受到的就是:

模型已经开始输出了,但后面每个 token 出得慢。

所以 KV Cache 对推理速度的贡献可以这样理解:

prefill 阶段:为整段输入建立初始 KV Cache,影响 TTFTdecode 阶段:持续复用并追加 KV Cache,影响 ITL 和总生成时长

没有 KV Cache,decode 会反复重算历史。

有了 KV Cache,decode 仍然要看历史,但不用把历史 token 的 K/V 重新投影一遍。

这就是流式生成能跑起来的基础。

也因此,后面调 vLLM 时要先判断慢在哪里:

第一个 token 慢:优先看 prefill、长 prompt、prefix caching后续 token 慢:优先看 decode、KV Cache 压力、batching 和显存

四、速度不是白来的:KV Cache 吃的是显存

KV Cache 省的是计算,花的是显存。

它不是一个小缓存。

它要为每个请求保存:

batch sizesequence length会产生 KV Cache 的 attention 层数KV heads 数量每个 head 的维度K 和 V 两份数据精度

粗略公式是:

KV Cache 显存= batch_size× sequence_length× attention_layers× 2× num_kv_heads× head_dim× bytes_per_element

这几个变量不要一口气硬背,可以分成三组。

1. 请求侧变量:由你的服务流量决定

第一组是请求侧变量:

batch_sizesequence_length

batch_size 表示同一时刻有多少条序列在占用 KV Cache。

在离线推理里,它可能就是一次 batch 里有多少条样本。

在在线服务里,它更接近:

当前正在被 vLLM 调度、还没生成完的请求数量

所以这里的 batch_size 不只是代码里手写的 batch size。

只要线上同时有很多用户请求,每条请求都在生成,每条请求就都需要自己的 KV Cache。

并发越高,这一项越大。

sequence_length 表示每条序列当前占了多少 token。

它不只是 prompt 长度,而是:

sequence_length = prompt tokens + 已经生成的 output tokens

所以长 prompt 会吃 KV Cache,长输出也会吃 KV Cache。

比如用户发了 128K tokens 的文档,又让模型生成 8K tokens 的报告,那么这条序列最后接近:

128K + 8K = 136K tokens

这两个变量是部署时最容易被低估的。

因为很多人只看“模型能支持 262K 上下文”,却忘了:

262K 是单条序列的上限线上还要乘以同时存在的序列数

2. 模型侧变量:由模型结构决定

第二组是模型侧变量:

attention_layersnum_kv_headshead_dim

attention_layers 表示有多少层会产生标准 attention 的 K/V cache。

对传统 decoder-only Transformer 来说,它通常接近模型层数。

比如 32 层模型,可以先按 32 层估。

但如果模型是混合架构,就不能只看总层数。

有些层可能不是标准 attention,不会按同样方式产生 K/V cache。

后面讲 Qwen3.5-9B 时,这个点会直接影响估算结果。

num_kv_heads 表示 KV head 的数量。

注意它不一定等于 Q head 的数量。

很多模型会用 GQA,也就是 Grouped-Query Attention:

Q heads 可能很多KV heads 可能更少多个 Q heads 共享一组 K/V

比如:

Q heads = 16KV heads = 4

估 KV Cache 时,用的是 KV heads = 4,不是 Q heads = 16

这也是 GQA 能降低 KV Cache 显存占用的原因之一。

head_dim 表示每个 attention head 的维度。

如果 head_dim = 256,就表示每个 KV head 里,一个 token 的 K 向量有 256 个数,V 向量也有 256 个数。

这三个变量基本由模型配置决定。

部署时你一般不会改它们。

你要做的是从模型 config 或 model card 里把它们找出来,别填错。

3. 存储侧变量:由缓存精度决定

第三组是存储侧变量:

2bytes_per_element

公式里的 2 表示同时存两份东西:

一份 K一份 V

所以 KV Cache 是 Key + Value 的缓存,不是只存 Key。

bytes_per_element 表示每个数占多少字节。

常见情况是:

FP16 / BF16:2 bytesFP8:1 byte

所以 FP8 KV Cache 的直觉很简单:

同样的上下文和并发,KV Cache 显存理论上接近减半

但这是用精度换容量,后面要配合评估验证。

把这三组放在一起看,KV Cache 优化就清楚多了:

请求侧:并发多少、prompt 多长、输出多长模型侧:attention 层数、KV heads、head_dim存储侧:BF16 / FP16 / FP8

其中你部署时最能直接控制的是请求侧和存储侧。

模型侧变量通常是选模型时就决定了。

但先别急着调参数。

公式真正想告诉你的,是 KV Cache 的增长方式:

上下文长度翻倍,KV Cache 近似翻倍并发数翻倍,KV Cache 近似翻倍attention 层数越多,KV Cache 越大KV heads 越多,KV Cache 越大

这就是长上下文推理贵的原因。

不是模型权重放进 GPU 就万事大吉。

你还要给每个正在服务的请求,留出它自己的 KV Cache 空间。

模型权重像固定成本。

KV Cache 像运行时成本。

用户越多、上下文越长、生成越久,运行时成本越高。

4. 例子:Qwen3.5-9B 的 KV Cache 大概吃多少显存?

我们用 Qwen/Qwen3.5-9B 这个 dense / 非 MoE 专家路由模型来算一遍。[1][2]

4.1 先看模型结构

Qwen3.5-9B 不是传统的“32 层全都是 full attention”的 Transformer。

它的文本模型一共有 32 层,attention 类型按 4 层一组循环:

连续 3 个 linear attention block再接 1 个 full attention block

这组结构重复 8 次,所以可以写成:

8 × (3 × linear attention + 1 × full attention)

这里的 3 个 linear attention block 不是三种不同功能的层,而是同一类层连续出现三次。

先用一张表把 linear attentionfull attention 的差别说清楚:

如果只看上下文长度 n 这一维,可以粗略对比成:

关注点full attentionlinear attention
基本做法当前 token 和所有历史 token 逐个匹配把历史信息压到可递推更新的紧凑状态
prefill 复杂度O(n²)通常接近 O(n)
decode 单 token 复杂度每生成 1 个 token,要看 n 个历史 token,约 O(n)更多依赖递推状态,通常接近 O(1)
缓存形态按历史 token 保存标准 K/V cache,约 O(n)不按每个历史 token 保存完整标准 K/V,更多保存紧凑状态
长上下文压力上下文越长,计算和 KV Cache 压力越明显更适合长上下文,显存和计算增长更温和

表里的 O(n) / O(n²) 不是精确性能公式,真实速度还会受 kernel、batching、GPU 利用率、head_dim、实现细节影响。

但它足够说明这里最重要的区别:

full attention 的成本会明显随上下文长度增长linear attention 试图把长上下文成本压得更接近线性

这里先不展开所有 attention 变体,只关注一个和 KV Cache 估算直接相关的问题:

哪些层需要按历史 token 保存标准 K/V cache?

后面可以单独写一篇,系统讲 full attention、linear attention、sliding window attention、GQA / MQA 分别解决什么问题。

放回 Qwen3.5-9B 这个例子,它的设计是在两件事之间折中:

大多数层用 linear attention 降低长上下文成本;少数层用 full attention 保留更强的精细注意力能力。

这也是它支持长上下文时 KV Cache 压力相对小的原因之一:

不是每一层都要按 full attention 保存标准 K/V cache。

4.2 再看哪些配置会进入公式

接下来只取和标准 attention KV Cache 直接相关的字段:

总层数:32layer_types:8 × (3 × linear attention + 1 × full attention)num_attention_heads:16num_key_value_heads:4head_dim:256max_position_embeddings:262,144

它们分别对应:

总层数:模型一共有多少层,但不能直接等同于 attention_layerslayer_types:哪些层是 linear attention,哪些层是 full attentionnum_attention_heads:Q heads 数量,用来理解 attention 结构num_key_value_heads:KV heads 数量,真正进入 KV Cache 公式head_dim:每个 head 的维度,真正进入 KV Cache 公式max_position_embeddings:模型默认最大上下文长度

最容易看错的是 总层数layer_types 的关系。只看总层数 32,很容易以为:

attention_layers = 32

但对 Qwen3.5-9B 来说,标准 full attention 层只有 8 个,所以估标准 attention KV Cache 时:

attention_layers = 8

4.3 代入公式算一遍

再把变量代入公式:

attention_layers = 8num_kv_heads = 4head_dim = 256bytes_per_element = 2# BF16 / FP16

单个 token 的 KV Cache 大约是:

8 × 2 × 4 × 256 × 2 bytes= 32,768 bytes= 32 KB

这 32 KB 的含义是:

每多一个 token这条序列大约多占 32 KB KV Cache

4.4 换成上下文长度和并发

所以单条序列在不同上下文长度下,大概是:

上下文长度单条序列 KV Cache 粗算
32K tokens1 GB
128K tokens4 GB
262K tokens8 GB

如果线上同时有 8 条 128K 上下文请求在生成,就要再乘以并发:

4 GB × 8 = 32 GB

如果把 KV Cache 从 BF16 / FP16 换成 FP8,bytes_per_element 从 2 变成 1,理论上接近减半:

上下文长度BF16 / FP16FP8
128K tokens4 GB2 GB
262K tokens8 GB4 GB

这就是这节最重要的结论:

Qwen3.5-9B 在 262K 上下文下,单条序列的标准 attention KV Cache 粗算约 8 GB。

4.5 估算结果怎么用?

但这个估算值只能当量级参考。

真实部署还会有:

模型权重临时 tensorCUDA / NCCL / runtime 开销调度器开销多模态模块开销vLLM 预留和碎片

所以不要把它当成精确显存账本。

它更适合用来判断:

当前上下文长度和并发目标,大概是不是离谱?

4.6 一个常见误算

最后提醒一个常见误算。

如果你把 Qwen3.5-9B 当成 32 层全 attention 模型来算:

32 × 2 × 4 × 256 × 2 bytes= 128 KB / token262K tokens -> 约 32 GB

这个结果会明显偏大。

原因不是公式错了,而是 attention_layers 填错了。

估 KV Cache 时,不要只看总层数。

要看有多少层真的会产生标准 attention 的 K/V cache。

五、从理论到实践:KV Cache 在部署里怎么落地?

理解显存账以后,下一步是看它在部署里怎么体现出来。

KV Cache 通常不需要你手动实现,现代推理框架一般已经内置。

你真正要关心的是三件事:

怎么给 KV Cache 分配显存怎么观察 KV Cache 是否不够用怎么根据 workload 调整上下文、并发和缓存策略

下面用 Qwen3.5-9B + vLLM 作为例子,把实践拆成三步:

启动时:给模型和 KV Cache 一个初始预算运行时:观察是不是出现 KV Cache 压力调参时:根据现象调整显存、并发、前缀复用和精度

vLLM 只是一个落地框架,不是唯一答案。其他推理框架也会面对类似问题:

KV Cache 放不放得下?一次调度多少请求合适?重复 prompt 能不能复用?KV Cache 能不能低精度保存?无效上下文能不能少塞一点?

vLLM 是一个大模型推理服务框架。你可以把它理解成:

模型权重已经有了GPU 也已经有了vLLM 负责把很多用户请求调度到 GPU 上,尽量让吞吐更高、延迟更稳、显存浪费更少。

KV Cache 是 vLLM 必须重点管理的资源,因为:

模型权重通常是固定占用KV Cache 会随着请求、上下文长度、输出长度不断变化

很多请求同时进来时,prompt 长度、输出长度和生命周期都不同,KV Cache 会变成一堆大小不同、释放时间不同的显存块。

vLLM 的 PagedAttention / block-based KV cache 管理,可以先粗略理解成:

不要给每条请求一次性分配一整块连续显存而是把 KV Cache 拆成很多小 block请求需要多少,就分配多少 block请求结束后,再把 block 回收给别的请求

这样可以减少显存碎片和浪费,让同一张 GPU 更稳定地服务更多请求。

这里不展开 vLLM 内部实现,只把它当成一个实践窗口:

我们前面算出来的 KV Cache 显存账,在真实部署里会变成哪些启动参数、日志信号和调整动作?

1. 启动时:先给上下文和显存一个预算

启动服务时,先不要急着追求“上下文越大越好”。

你至少要先定两个预算:

上下文预算:单条请求最长允许多少 token显存预算:这张 GPU 最多给 vLLM 用多少显存

一个纯文本服务的基础启动命令可以写成:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --gpu-memory-utilization 0.92

这里几个参数的含义是:[3]

参数作用
--max-model-len 262144允许单条请求最长到 262K tokens
--reasoning-parser qwen3按 Qwen3 系列格式解析 reasoning 内容
--language-model-only纯文本服务时不加载多模态能力
--gpu-memory-utilization 0.92vLLM 最多使用这张 GPU 92% 的显存

如果你要用 Qwen3.5 的视觉能力,就不要加 --language-model-only

这里和 KV Cache 最直接相关的是 --max-model-len--gpu-memory-utilization

前者决定单条请求最多能长到多少 token;后者决定 vLLM 最多使用多少比例的 GPU 显存。

0.92 不是“需要 0.92GB 显存”,而是:

vLLM 最多可以使用这张卡 92% 的显存

比如一张 40GB 显卡,可用预算大约是:

40GB × 0.92 = 36.8GB

那剩下的 8% 显存去哪了?

它不是浪费,而是安全垫,用来留给 CUDA、临时 tensor、通信 buffer、显存碎片和运行时峰值波动。

如果把 gpu_memory_utilization 拉得太满,短时间看起来能多放一点 KV Cache,但高峰请求、长输出或并发波动时更容易 OOM。

这 36.8GB 里面要同时放:

模型权重KV Cache运行时临时开销CUDA / kernel / 调度开销

那这条命令大概要多大的显卡?

以 Qwen3.5-9B 的纯文本 BF16 / FP16 推理粗算:

模型权重:约 18GB262K 单条序列 KV Cache:约 8GB运行时和框架开销:预留几 GB 到十几 GB

如果你真的想跑 --max-model-len 262144,并且希望单条请求能接近 262K 上下文,比较稳的判断是:

显卡显存粗略判断
24GB通常不适合 BF16 / FP16 跑满 262K 上下文
40GB单条长上下文有机会,但要控制并发并预留运行时开销
80GB更适合长上下文和一定并发

如果显存不够,可以先从两个方向缩小预算。

第一,降低上下文长度:

max_model_len:262K -> 128K单条序列 KV Cache:约 8GB -> 约 4GB

第二,使用 FP8 KV Cache:

BF16 / FP16 KV Cache:262K 约 8GBFP8 KV Cache:262K 约 4GB

所以显卡大小不是只由模型参数量决定,而是由这几项一起决定:

模型权重最大上下文长度并发序列数KV Cache 精度运行时预留空间

常见部署参数建议

如果没有历史压测数据,可以先按下面这些思路起步,再用真实 workload 压测修正。

这里的“独占 GPU”指这张 GPU 上基本只跑这个 vLLM 实例,没有其他训练任务、推理服务、桌面进程或重型监控进程长期占用显存。

参数先怎么设什么时候调主要代价
--gpu-memory-utilization0.85~0.90 稳妥起步;独占 GPU 时可试 0.90~0.95KV Cache 空间不够、频繁 preemption太高更容易 OOM,也会挤压运行时余量
--max-model-len按真实需求设,不要默认拉满;可先从 64K/128K 起步需要支持更长 prompt 或更长输出越大,最坏情况下 KV Cache 预算越高
--kv-cache-memory-bytes默认不设;需要精确控预算时再设,如 20G想直接指定每张 GPU 上 KV Cache 占多少显存设置后会覆盖 gpu_memory_utilization 推断出的 KV Cache 大小
--max-num-seqs根据并发目标起步,显存紧就降低同时运行请求太多、延迟抖动明显调低会限制并发能力
--max-num-batched-tokens根据 prompt / output 长度和吞吐目标调单轮调度 token 太多、KV Cache 压力大调低可能降低吞吐
--enable-prefix-caching前缀重复多时建议开启固定 system prompt、工具 schema、长文档多轮问答需要前缀真的相同,prompt 组织要稳定
--kv-cache-dtype默认先不改;显存瓶颈明显时评估 fp8想用更低精度 KV Cache 换容量可能有质量影响,需要 eval 验证
--tensor-parallel-size / --pipeline-parallel-size单卡不够时再考虑模型权重太大或希望释放单卡 KV Cache 空间多卡通信和部署复杂度上升

比较常见的起步组合是:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 131072 --reasoning-parser qwen3 --language-model-only --gpu-memory-utilization 0.90 --enable-prefix-caching

如果确认显存是主要瓶颈,再逐步尝试:

--gpu-memory-utilization 0.92~0.95--kv-cache-dtype fp8--max-num-seqs 适当降低--max-num-batched-tokens 适当降低

没有一个“业界万能参数”。更接近生产实践的做法是:

先保守启动看 preemption / TTFT / ITL / 吞吐再按瓶颈逐项调整每次只改一两个参数用真实请求压测

2. 运行时:看 KV Cache 是否真的有压力

运行起来以后,不要只看“显存占用高不高”。

判断 KV Cache 有没有压力,可以按三步走。

2.1 第一步:看有没有直接证据

最直接的证据是 vLLM 日志里出现:

preempted ... not enough KV cache space

这基本可以说明:

当前这批请求需要的 KV Cache已经超过了当前可用空间

如果这个日志频繁出现,就可以优先按 KV Cache 空间不足处理。

2.2 第二步:看 metrics 是否接近打满

如果没有明显日志,继续看 vLLM metrics。

重点看:

vllm:kv_cache_usage_percrunning requestswaiting requestsTTFTITLpreemption counter

判断逻辑是:

kv_cache_usage_perc 长时间很高waiting requests 开始增加TTFT / ITL 同时变差preemption counter 也在涨

这几个信号一起出现,基本就能判断 KV Cache 有压力。[7]

2.3 第三步:用 GPU 看板排除其他瓶颈

GPU 看板可以辅助判断,但不能单独下结论。

原因是 vLLM 可能会按 gpu_memory_utilization 预分配 GPU cache。

所以你看到显存占用很高,不一定说明 KV Cache 已经打爆。

可以按下面几种组合判断:

观察到的现象更可能是什么问题
KV cache usage 高 + waiting 增加 + TTFT/ITL 变差 + preemption 增加KV Cache 压力大
GPU utilization 高,但 KV cache usage 不高,waiting 没明显增加更像计算瓶颈
waiting 很多,但 GPU utilization 不高可能是调度、限流、上游请求或客户端消费问题
显存占用高,但 KV cache usage 不高可能只是预分配或其他显存占用

2.4 preemption 到底是什么意思?

preemption 可以理解成“抢占”:KV Cache 空间不够时,vLLM 临时暂停一部分请求,释放它们占用的 KV Cache,把显存让给其他请求。[4]

preemption 能让服务继续跑,但被暂停的请求后续可能需要重算一部分上下文。

表现出来通常是:

现象可能感受
TTFT 变高第一个 token 更久才出来
ITL 变差已经开始输出,但 token 流得更慢
吞吐下降同一时间处理的请求或 token 变少
延迟抖动有些请求突然很慢

所以,判断 KV Cache 是否真的有压力,不是看单个指标,而是看组合信号。

3. 调整时:按现象选择对应动作

确认 KV Cache 有压力以后,再进入调整。

不要把下面这些参数当成孤立开关。

先按现象选方向:

现象优先动作对应小节
频繁 preemption,KV cache usage 接近打满增加 KV Cache 可用空间3.1
高峰期抖动,请求同时涌入控制并发和 batch token3.2
TTFT 高,且 prompt 前缀大量重复开启 Prefix Caching3.3
显存仍然紧,但想保留长上下文或并发评估 FP8 KV Cache3.4
prompt 里有大量旧日志、重复文档、无关历史做上下文治理3.5

3.1 容量不够:给 KV Cache 留更多显存

如果日志里频繁出现 preemption,最直接的方向是给 KV Cache 更多空间。

vLLM 里有两个常见旋钮:[3][4]

参数适合什么时候用注意点
--gpu-memory-utilization想提高 vLLM 整体可用显存比例太高可能挤压运行时余量
--kv-cache-memory-bytes想直接指定 KV Cache 显存预算设置后会覆盖 gpu_memory_utilization 推断出的 KV Cache 大小

最常见的方式是提高 gpu_memory_utilization,比如从 0.92 提到 0.95:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --gpu-memory-utilization 0.95

如果你想更直接地控制 KV Cache 预算,可以用:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --kv-cache-memory-bytes 20G

这两个参数不要当成两个同时生效的旋钮。

一个是整体显存比例,一个是更直接的 KV Cache 显存预算。

3.2 瞬时压力太高:控制并发和 batch token

KV Cache 不够,不一定只能加显存。如果问题来自“同一瞬间进来的请求太多”,另一条路是减少同一轮调度的压力。

常见参数是:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --max-num-seqs 64 --max-num-batched-tokens 8192

可以粗略理解为:

参数控制什么调低后的效果代价
max_num_seqs一次最多同时调度多少条序列减少同时占用 KV Cache 的请求数并发能力可能下降
max_num_batched_tokens一次调度最多处理多少 token降低单轮调度的 token 压力吞吐可能下降

这是一组典型取舍:

想要更高吞吐:通常希望 batch 更充分想要更低延迟:不能让请求排队太久显存紧张:要限制并发序列和批内 token

所以不要只问“这个参数应该设多少”,应该先问:

我的目标是吞吐优先,还是延迟优先?我的典型 prompt 多长?我的典型输出多长?峰值并发是多少?

参数是被 workload 推出来的。

3.3 长 prompt 重复:用 Prefix Caching 复用前缀

如果慢主要发生在 prefill,且很多请求前缀高度相同,可以考虑 prefix caching。

常见场景是:

相同 system prompt相同工具 schema相同安全策略相同长文档不同用户问题

这时可以启用 Automatic Prefix Caching:[5]

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --enable-prefix-caching

它的作用是复用相同前缀对应的 KV Cache block,主要优化 prefill。

那它会不会增加显存?

更准确的说法是:

它会占用 KV Cache 空间来保留可复用的前缀 block,但这些 block 可以被后续请求共享,避免重复 prefill。

所以它不是“免费不占显存”,也不是简单“显存翻倍”。

如果前缀命中率高,它通常很划算:多保留一些可复用 block,换来更低的 TTFT 和更少的重复计算。

如果前缀几乎不重复,它的收益就有限,还可能占用一部分 cache 空间。

适合这些场景:

长文档多轮问答固定 system prompt 的聊天服务相同工具 schema 的 Agent 请求批量处理同一背景材料、不同问题的任务

注意,前缀必须真的相同才容易命中。如果你每次都把时间戳、随机 request id、动态用户状态放在最前面,prefix cache 命中率会变差。

更好的组织方式是:

稳定内容放前面:system prompt、工具说明、固定文档动态内容放后面:用户问题、临时变量、当前轮状态

3.4 显存还是紧:用 FP8 KV Cache 换容量

如果显存还是紧,但你又想支持更长上下文或更高并发,可以考虑 KV Cache 量化。[6]

比如:

vllm serve Qwen/Qwen3.5-9B --port 8000 --max-model-len 262144 --reasoning-parser qwen3 --language-model-only --kv-cache-dtype fp8

它的收益和代价可以放在一起看:

方面影响
收益KV Cache 占用变小,同样显存能容纳更多 token
收益频繁 preemption 的概率可能下降
代价可能有精度损失
代价需要硬件和 vLLM 版本支持
代价不同模型受影响程度不同,必须用自己的 eval 验证

FP8 KV Cache 不是“免费提速按钮”,它更像是:

用一点潜在质量风险,换更大的上下文容量和并发空间。

所以更稳的流程是:

先跑业务 eval比较 auto / fp8 的回答质量再压测 TTFT、ITL、吞吐和 preemption最后灰度上线

3.5 上下文太脏:少把无效内容塞进模型

还有一种 KV Cache 优化,不发生在推理框架里,而发生在应用层。

因为 KV Cache 只会缓存已经进入上下文的 token。

如果 prompt 里塞了很多过时日志、重复工具结果、无关历史对话,推理框架只能老老实实为它们分配 KV Cache。

所以应用层也要做:

方法作用
Context Compact把旧工具输出压缩成摘要
RAG只召回当前问题需要的文档片段
Memory把长期信息放到外部状态里,需要时再取
Prompt 组织稳定前缀放前面,动态内容放后面

这类优化不会改变 KV Cache 的单 token 成本,但会减少进入模型的 token 数。从公式上看,就是直接降低:

sequence_length

很多线上系统里,这比单纯调 gpu_memory_utilization 更有效。

4. 最后给一个排查 checklist

把上面这些放到一起,如果你用 vLLM 部署模型,遇到长上下文慢、显存紧、吞吐上不去,可以按这个顺序排查:

1. 看日志:有没有频繁 preemption?2. 看 workload:慢在长 prompt,还是长输出?3. 长 prompt 重复多:打开 enable_prefix_caching4. KV Cache 不够:提高 gpu_memory_utilization5. 想精确控预算:设置 kv_cache_memory_bytes6. 显存仍然紧:降低 max_num_seqs / max_num_batched_tokens7. 还要更长上下文或更高并发:评估 kv_cache_dtype=fp88. 单卡放不下:考虑 tensor parallel / pipeline parallel9. 每次改完:压测 TTFT、ITL、吞吐、preemption 和质量

这里几个指标很重要:

TTFT:Time To First Token,首 token 延迟ITL:Inter-Token Latency,生成过程中 token 间延迟吞吐:单位时间处理多少 token 或 requestpreemption:KV Cache 不够导致的抢占和重算

不要只盯平均吞吐。

线上用户往往更敏感的是:

第一下多久出来?出来以后是不是稳定地往外流?长请求会不会把短请求拖死?高峰期会不会频繁抖动?

这些问题背后,经常都有 KV Cache 预算的影子。

六、KV Cache、上下文窗口、Memory 不是一回事

最后再清一下几个容易混淆的概念。

概念解决什么问题存在哪里和 KV Cache 的关系
上下文窗口模型最多能看多少 token模型输入序列窗口越大,可能进入 KV Cache 的 token 越多
KV Cache已经进入上下文的 token 如何少重复计算推理引擎的 GPU / CPU cache只缓存推理中间状态,不决定内容该不该进上下文
Memory / RAG哪些长期信息应该放在模型外部,需要时再取数据库、向量库、文件、外部状态减少无关内容进入上下文,从源头降低 KV Cache 压力
Context Compact当前上下文太长时如何压缩当前 prompt / message 历史减少低价值 token,降低后续 KV Cache 占用

这也是为什么真实系统不能只靠 KV Cache。

如果你把所有历史对话、工具结果、日志和文档都塞进 prompt,KV Cache 会帮你少算,但不会帮你判断:

这些内容该不该进上下文?这些内容是不是已经过时?这些内容是不是可以压缩?这些内容是不是应该放到外部存储?

所以长上下文系统通常要一起做:

Context Compact:压缩当前上下文Memory / RAG:管理长期信息KV Cache:优化推理复用推理调度:平衡显存、吞吐和延迟

七、总结:从原理到部署,KV Cache 要讲清楚这五点

最后把全文收成一条清晰的讲法。

第一,先定义它。

KV Cache 是大模型自回归生成时,用来缓存历史 token 的 Key / Value 的机制。

第二,讲清楚它为什么快。

模型生成文本是一个 token 一个 token 往外生成。如果每生成一个新 token 都重新计算整段上下文,decode 会非常慢。有了 KV Cache,prefill 阶段先处理 prompt 并建立缓存;decode 阶段复用历史 K/V,只追加新 token 的 K/V。

第三,讲清楚它为什么贵。

KV Cache 不是免费的。它本质上是用显存换推理速度。上下文越长、并发越高、输出越长,需要保存的 KV Cache 就越大。

第四,讲清楚怎么估算。

KV Cache ≈ 并发序列数 × 序列长度 × attention 结构 × 精度

估算时不能只看模型总层数。比如 Qwen3.5-9B 是混合 attention 架构,不是每一层都会产生标准 full attention 的 KV Cache,所以要看真正产生标准 KV Cache 的 attention 层数、KV heads 和 head_dim。

第五,讲清楚部署时怎么看。

落到 vLLM 这类推理框架里,KV Cache 实践主要看几个信号和参数:

preemption 多,往往说明 KV Cache 空间不够;TTFT 高,要看 prefill、长 prompt 和 prefix caching;ITL 差,要看 decode、batching、并发和显存压力;显存紧,可以考虑降低 max_model_len、控制 max_num_seqs / max_num_batched_tokens,或者评估 FP8 KV Cache。

再压缩一下,就是三句话:

KV Cache 解决重复计算。KV Cache 的代价是显存。KV Cache 优化,本质是在上下文长度、并发、延迟、吞吐和质量之间做预算。

参考资料

[1] Qwen/Qwen3.5-9B - Hugging Face

[2] Qwen3.5 model documentation - Hugging Face Transformers

[3] Engine Arguments - vLLM Documentation

[4] Optimization and Tuning - vLLM Documentation

[5] Automatic Prefix Caching - vLLM Documentation

[6] Quantized KV Cache - vLLM Documentation

[7] Metrics - vLLM Documentation

点击查看更多
推荐专题
热门阅读