X:000 · Y:000SECTOR / AIv0.9.SIGNALNO_ADS // NO_TRACK
CILLIAN.AI
← MEMORY

KV Cache:推理显存为什么会随上下文暴涨

权重只占一块固定显存。真正随并发和上下文变长的是 KV cache:每一层都要记下已经看过的 Key 和 Value。

2 MIN READ~1300 CHARS

大模型推理时,显存里不只有权重。还有一块会随着「正在处理的文本有多长、同时有几路请求」膨胀的缓存,叫 KV cache。很多「权重明明放得下,一并发就 OOM」的情况,先查的就是它。

它在记什么

Transformer 的自注意力要拿当前 token,去和前面所有 token 的 Key、Value 做运算。如果每生成一个新 token 都把历史重新算一遍,解码会慢到不能用。

所以实现上把已经算过的 Key 和 Value 留下来,下一步只算新 token,再和缓存拼起来。这块留下的张量就是 KV cache。

粗略体积可以记成:

KV cache ≈ 2 × 层数 × 批大小 × 序列长度 × 每层 KV 的隐藏维度 × 每元素字节数

前面的 2 是 Key 和 Value 各一份。序列长度和批大小是乘法关系:上下文从 2K 拉到 32K,或者并发从 1 提到 16,缓存都会按比例变大。权重则约等于「参数量 × 每参数字节数」,FP16 大约是 2 字节,不随这次请求的长度变化。

Prefill 和 Decode

生成通常分成两段:

阶段 在做什么 显存和延迟上的特点
Prefill 把整段提示词一次吃进去,算出第一个输出 token 算力重,决定首 token 时间(TTFT)
Decode 每次只生成一个新 token,并把它的 KV 追加进缓存 更吃缓存带宽;上下文越长,每步读的缓存越大

所以两种「慢」不是同一件事。首字很久才出来,优先看提示词是不是太长、系统提示有没有被重复计算。字已经在往外蹦但很慢,优先看并发、序列长度和 KV 有没有把显存或带宽打满。

为什么短句压测会骗人

用 "hello" 测出来的剩余显存,不能代表线上。线上提示词常常是系统提示加几轮对话再加检索来的材料。KV cache 按真实长度分配。服务要为每个模型准备一张表:在某个 max_len 下,最多允许多少并发。超过就排队或拒绝,而不是让分配失败把进程打死。

另外留一块给激活和显存碎片。理论值刚好卡满时,加载、卸载几次之后,空闲显存加起来够,却划不出连续的一块,分配仍然会失败。

和别的优化怎么配合

  • 前缀缓存(prefix cache):多条请求共享同一段系统提示的 KV,避免每条都重算这段。
  • 分页 KV(PagedAttention 一类):把缓存切成块来分配,减轻碎片,让不同长度的请求更好共卡。
  • 限制 max_tokens 和上下文:硬顶比事后 OOM 便宜。
  • 量化:主要缩小权重,有的实现也会压缩 KV。上下文特别长时,缓存仍然要单独限额,不能假设量化之后并发可以随意加。

小结

权重是固定账单,KV cache 是随请求变的账单。讲「这张卡能跑某个模型」时,必须同时说最大上下文和最大并发。压测用真实长度,并给并发一个硬上限。调度层面怎么分池,见 GPU 调度实战;服务本身的超时和排队,见 推理服务入门。

RELATED

NEXT STEP

本篇已归档到 MEMORY。继续解码相邻信号,或回到档案首页。