KV Cache 是因果语言模型为仍可能被后续 Token 访问的历史位置保留的逐层 Attention 状态。它减少自回归解码时重复计算 Key 和 Value 投影,却同时消耗显存容量、带宽和调度槽位。因此,生产设计必须回答四个问题:保留哪些 Token 状态、何时允许复用、状态放在哪里,以及如何验证正确性与租户隔离。

本文只负责 KV 状态生命周期。大模型推理指南解释完整的 Prefill/Decode 链路,分离式推理服务指南负责跨工作池传输 KV 状态。

直接回答

KV Cache 用持久状态换取更少的重复投影计算。Prefill 为 Prompt Token 创建 K/V;每个 Decode Step 读取保留的 K/V,为最新 Token 计算 Q/K/V,再把新 K/V 追加到缓存。

flowchart LR P["Prompt Token"] --> F["Prefill"] F --> C["逐层 K/V Cache"] C --> D["Decode Step"] N["最新 Token"] --> D D --> R["读取保留的 K/V"] D --> A["追加新 K/V"] A --> C D --> O["下一个 Token Logits"]
问题 生产环境中的正确结论
Cache 会让 Decode 变成常数时间吗? 不会。它消除历史投影的重复计算,但标准 Attention 仍需读取并关注保留的 K/V。
最大上下文是否等于已分配缓存? 不一定。Dynamic、Paged、Sliding Window、Static 和共享前缀布局的物理预留方式不同。
两个请求可以共享缓存吗? 只有 Token 前缀及所有影响缓存的输入兼容,并且安全策略允许复用时才可以。
Prefix Cache 会加速生成吗? 它可以减少重复 Prefill 和 TTFT,不会减少新输出 Token 的 Decode 工作量。
低精度缓存一定更快吗? 不一定。量化减少字节数,却会引入转换、Scale 元数据、Kernel 开销和质量风险。

KV Cache 如何工作

在因果自注意力中,历史 Token 不能依赖未来 Token。只要模型和输入历史不变,某个历史位置的 Key 与 Value 一旦计算完成,就可以在后续 Decode Step 中复用。

对于位置 t 的新 Token,每个 Attention Layer 都会:

  1. 计算新 Token 的 Query、Key 和 Value;
  2. 用新 Query 与保留的 Key 计算相关性;
  3. 按 Attention Weight 聚合保留的 Value;
  4. 把新 Key 和 Value 追加到该层缓存。

其逻辑操作是:

text
K_cache <- concat(K_cache, k_t)
V_cache <- concat(V_cache, v_t)

output_t = softmax(q_t @ K_cache.T / sqrt(head_dim)) @ V_cache

之所以只缓存 K 和 V,是因为未来 Query 会重复使用它们;当前 Query 只服务当前位置,后续位置不会再用。Attention Score 也无法复用,因为每个新 Query 都会产生不同的分数向量。

该解释只适用于因果 Transformer Attention。Encoder-Decoder 模型可能分别维护 Self-Attention 与 Cross-Attention Cache;Sliding Window、Chunked、Latent Attention、循环或混合架构保留的状态不同,需要使用模型专属公式。

Prefill 与 Decode 的缓存行为不同

Prefill 并行创建初始缓存,Decode 则逐步增长或更新缓存。只有先区分这两个阶段,才能判断某项优化会改善哪个延迟指标。

阶段 Cache 操作 主要压力 关键指标
Prefill 为 Prompt Token 写入 K/V 计算量、Prompt 长度、初始分配 TTFT、输入 Token 吞吐
Decode 读取保留 K/V 并追加新状态 显存带宽、驻留 Token、Batch 调度 TPOT、输出 Token 吞吐
跨请求复用 查找兼容前缀 Block,跳过重复 Prefill 命中率、Block 粒度、身份、淘汰 命中/未命中 TTFT、复用 Token 数
Offload 或传输 在内存层级或 Worker 之间移动 Block 链路带宽、排队、局部性 传输延迟、等待时间

Hugging Face 的 Caching 文档解释了逐层追加行为,并明确缓存只应用于推理而非训练。训练会更新权重并使用不同执行语义,不能把推理缓存当作训练捷径。

按驻留 Token 计算 KV Cache 显存

对基础的未压缩 Decoder-only Cache,先计算每个保留 Token 的字节数:

text
bytes_per_token =
    2
    * n_layers
    * n_kv_heads
    * head_dim
    * bytes_per_element

logical_cache_bytes =
    bytes_per_token
    * resident_token_slots

因子 2 分别代表 Key 和 Value。n_kv_heads 是 KV Head 数量,不一定等于 Query Head。若活跃序列保留长度不同:

text
resident_token_slots = sum(每条活跃序列的保留 Token 数)

这只是逻辑容量,不是 GPU 上的物理分配。还需加入或实测:

  • Static Cache 预留或分页取整;
  • Block Table、Scale、元数据和分配器开销;
  • 重复前缀是物理共享还是各自保存;
  • 推测解码或 Beam Search 分支;
  • Tensor/Pipeline Parallel 的切分与复制;
  • 临时 Attention Workspace 和激活;
  • Cross-Attention、Sliding Window 或 Latent State 等模型专属状态。

模型标称上下文窗口只是上限。只有当前布局确实按上限预分配时,它才等于物理 Cache 预留;Dynamic 和 Paged Engine 可更接近实际保留 Token 分配,Static Cache 可能预留更多,Sliding Window Layer 则在训练窗口之外停止保留旧 Token。

运行容量估算

下面的无依赖脚本按活跃序列长度估算逻辑 Cache。它故意不隐藏量化元数据和 Page Overhead,避免给容量规划制造虚假精度。

python
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable


GIB = 1024 ** 3


@dataclass(frozen=True)
class KVCacheSpec:
    n_layers: int
    n_kv_heads: int
    head_dim: int
    bytes_per_element: int

    def bytes_per_token(self) -> int:
        values = (
            self.n_layers,
            self.n_kv_heads,
            self.head_dim,
            self.bytes_per_element,
        )
        if any(value <= 0 for value in values):
            raise ValueError("KV cache dimensions must be positive")
        return (
            2
            * self.n_layers
            * self.n_kv_heads
            * self.head_dim
            * self.bytes_per_element
        )


def logical_cache_gib(
    spec: KVCacheSpec,
    retained_sequence_lengths: Iterable[int],
) -> float:
    lengths = tuple(retained_sequence_lengths)
    if not lengths or any(length < 0 for length in lengths):
        raise ValueError("Provide one or more non-negative sequence lengths")
    resident_tokens = sum(lengths)
    return spec.bytes_per_token() * resident_tokens / GIB


active_sequences = [4096, 2048]
mha = KVCacheSpec(
    n_layers=32,
    n_kv_heads=32,
    head_dim=128,
    bytes_per_element=2,
)
gqa = KVCacheSpec(
    n_layers=32,
    n_kv_heads=8,
    head_dim=128,
    bytes_per_element=2,
)

try:
    print(f"MHA logical cache: {logical_cache_gib(mha, active_sequences):.3f} GiB")
    print(f"GQA logical cache: {logical_cache_gib(gqa, active_sequences):.3f} GiB")
except ValueError as error:
    raise SystemExit(f"Invalid capacity input: {error}") from error

# MHA logical cache: 3.000 GiB
# GQA logical cache: 0.750 GiB

四倍差异只来自给定参数下 KV Head 从 32 个变为 8 个。这不意味着任意 MHA Checkpoint 都能在运行时直接切换为 GQA。

把 Cache Identity 当作正确性契约

只有产生某个 Block 的计算完全兼容时,缓存才可复用。Token 相同只是必要条件,生产身份通常还包括文本之外的信息。

json
{
  "modelId": "provider/model-name",
  "modelRevision": "immutable-revision",
  "tokenizerRevision": "immutable-revision",
  "adapterId": "none",
  "chatTemplateVersion": "chat-v3",
  "attentionConfigHash": "sha256:...",
  "kvDtype": "bf16",
  "multimodalInputHashes": [],
  "trustScope": "tenant-acme",
  "cacheSaltId": "salt-ref-42"
}

Cache Identity 至少覆盖所有可能改变 K/V 的输入:

  • 模型权重与不可变 Revision;
  • Tokenizer、特殊 Token 与 Chat Template;
  • LoRA 或其他 Adapter Identity;
  • 位置/Attention 配置与 Cache Dtype;
  • 多模态 Embedding 或输入 Hash;
  • 缓存布局不兼容时的 Engine Identity;
  • Tenant 或 Trust Group 隔离策略。

vLLM 的 Prefix Cache 设计会 Hash Block Token、父前缀身份,以及 LoRA ID、多模态输入 Hash 等额外值。V1 设计还支持 Per-request Cache Salt,用于隔离 Trust Group 并降低基于命中延迟的信息泄露。这是 vLLM 的具体能力,不能替代业务授权和数据留存策略。

按真实权衡比较 Cache 策略

不同 Cache Strategy 作用于不同维度,也可以组合。只说“开启 KV Cache”不足以决定内存布局。

策略 改变什么 优势 成本或边界
Dynamic Cache 随 Token 增长保留状态 避免直接预留完整最大长度 动态 Shape 可能限制编译,分配仍需 Headroom
Static Cache 预分配固定最大 Shape 支持的 Runtime 可获得稳定 Shape 与编译收益 Mask 空槽并浪费显存或 Attention 工作
Paged Cache 把逻辑 Token Block 映射到非连续物理 Block 减少预留与碎片,便于 Block 共享 引入 Block Table、调度策略、分页取整和专用 Kernel
Sliding/Chunked Cache 只保留模型定义的 Window 或 Chunk 为使用该 Attention Pattern 训练的 Layer 限制状态 不能无损改装到 Full-Attention 模型
Prefix Cache 跨请求复用兼容的完整前缀 Block 跳过重复 Prefill,并可能物理共享 Block 需要重复前缀、身份检查、淘汰和隔离
Offloaded Cache 把状态移动到 CPU 或其他内存层级 在 GPU 压力下保留更多状态 传输延迟与链路竞争可能抵消收益
Quantized Cache 用更低精度保存 K/V 减少字节并容纳更多 Token 转换开销、元数据、Kernel 支持和质量风险

Hugging Face 当前的 Cache Strategy 文档区分 DynamicCacheStaticCacheQuantizedCache 和 Offloaded Variant。文档明确指出:Static Cache 是编译与空槽浪费的权衡;Offload 是显存与传输工作的权衡;在短上下文且显存充足时,量化甚至可能降低速度。

PagedAttention 能证明什么

PagedAttention 改变的是 KV 物理内存管理,而不是模型 Attention 语义。它把一条序列的逻辑 Cache 划分为 Block,按需分配并映射到非连续物理内存。

这形成了类似内存管理器的生命周期:

  1. 为通过准入的请求预留 Block Capacity;
  2. Prefill 和 Decode 创建 Token State 时分配 Block;
  3. Identity 匹配时共享只读前缀 Block;
  4. 分支发生分歧时复制或新分配 Block;
  5. 请求结束后释放或标记 Block 可复用;
  6. 显存压力下淘汰无引用的可复用 Block。

原始 PagedAttention 论文在其模型与负载中,相对 FasterTransformer 和 Orca 报告了相近延迟下 2–4 倍吞吐。这能支持分页设计,却不是对当前引擎、硬件、序列分布和解码策略的通用倍率。

Paging 也不会消除全部浪费:最后一个 Block 可能只填一部分,元数据与 Indirection 仍存在,Block Size 会影响 Kernel 与碎片,Scheduler 也仍可能过度承诺 Token Capacity。

Prefix Cache 优化 Prefill,而不是 Decode

Prefix Cache 在请求之间复用兼容的 Token 前缀 K/V。收益由复用 Token 数、命中率、查找与传输成本,以及 Block 被淘汰前的存活时间共同决定。

vLLM 的 Automatic Prefix Caching 指南把重复查询长文档和多轮对话列为适合负载,同时明确指出核心边界:Prefix Cache 减少的是 Query Prefill,不会缩短新输出 Token 的 Decode 时间。

至少监控:

text
reused_token_ratio = reused_prefix_tokens / eligible_prefix_tokens
hit_rate           = requests_with_reuse / eligible_requests

单看 Request Hit Rate 会误导决策。复用 16 个 Token 和复用 16,000 个 Token 都算一次命中,但省下的 Prefill 工作完全不同。

云服务商的 Prompt Caching 应被视为产品契约。它可能公开 Token 阈值、TTL、折扣或自动匹配,却未承诺某种内部 KV 实现。不能仅凭功能名称推断 Engine Layout、跨请求留存时长或安全边界。

量化与 Offload 必须分别评测

KV Quantization 和 Cache Offload 都是在不同瓶颈之间交换成本,不是自动生效的低显存模式。

KIVI 论文观察到 Key 与 Value 的分布不同,并提出非对称 2-bit 量化:Key 按 Channel,Value 按 Token。论文中的显存、Batch、吞吐和质量结果只属于其模型与实现;更换 Dtype、Kernel、Residual Cache Length 或模型后都要重新测量。

量化需要同时比较:

  • 逻辑与物理 Cache Byte;
  • Quantize/Dequantize Kernel 时间;
  • 不同上下文长度下的 TTFT、TPOT 和吞吐;
  • 任务质量与确定性回归切片;
  • Scale、Residual Cache、Padding 和元数据开销。

Offload 需要比较:

  • 每请求和每输出 Token 的传输字节;
  • Host-to-device 与 Device-to-host Queue Time;
  • 不同内存层级的命中延迟;
  • 并发 Prefill/Decode 下的链路利用率;
  • 传输慢于重算时的 Recompute Cost。

TensorRT-LLM 的 KV Cache Reuse 文档把 Host Offload 用于延长可复用 Block 的保留时间,同时明确传输有成本,较旧互连未必获益。该结论依赖硬件和负载。

GQA 与 MQA 是模型架构选择

Grouped-Query Attention 与 Multi-Query Attention 从源头减少 n_kv_heads,从而降低 Cache Byte 和带宽。它们通常是训练后 Checkpoint 的属性,不是无需修改模型就能打开的 Serving Flag。

  • MHA 为每个 Query Head 使用一个 KV Head;
  • MQA 让所有 Query Head 共享一个 KV Head;
  • GQA 在两者之间,让多组 Query 共享较少的 KV Head。

MQA 论文通过共享 Key 与 Value 降低增量 Decode 的带宽需求;GQA 论文把 GQA 定义为质量与效率之间的中间方案,并评测了 Uptraining 方法。容量估算必须读取 Checkpoint 的真实配置,不能从参数量或模型家族名猜 Head 数量。

容量规划必须使用负载分布

Admission Control 应按驻留 Token Demand 预留,而不是按一个醒目的请求数。10 个短请求与 10 个最大上下文请求并不等价,连续批处理还会在每个调度迭代改变活跃集合。

可执行的容量流程是:

  1. 收集 Prompt、Output、Retained Context 和并发分布;
  2. 从准确 Checkpoint 与 Cache Dtype 计算 Bytes per Token;
  3. 建模 Page Rounding、共享前缀、分支与安全余量;
  4. 从 GPU 容量扣除权重、激活、Workspace 和非 Cache 运行时内存;
  5. 推导以 Token Capacity 为核心的准入限制;
  6. 用真实到达突发回放请求分布;
  7. 在 SLO 边界验证 OOM、Preemption 和延迟。

输入与输出 Token 都要限制。输出上限只约束未来增长;超长 Prompt 在准入时已经消耗 Prefill 计算和 Cache 容量。多轮会话还要明确旧轮次是继续驻留、重新计算、摘要还是淘汰。

用质量与 SLO 门禁评测 Cache 变更

Cache 实验必须固定 Checkpoint、Engine Version、硬件拓扑、请求 Trace 与解码配置,否则吞吐差异无法归因于 Cache Strategy。

维度 指标 必须切片
容量 物理 Cache Byte、驻留 Token、Block 利用率、Headroom 上下文长度、并发、Cache Dtype
Prefill TTFT、输入 Token 吞吐、复用 Token 比例 命中/未命中、前缀长度、Tenant
Decode TPOT、输出 Token 吞吐、显存带宽 保留长度、Batch Occupancy、输出长度
Scheduler Queue Time、准入拒绝、Preemption、Recompute 突发强度、优先级
生命周期 Allocation、Free、Eviction、Offload Transfer Cache Tier、Block Age、模型 Revision
正确性 Output/Logit 回归、任务质量、Mismatch Error Dtype、模型、语言、长上下文

应报告 Percentile,而不是只看平均值。Prefix Cache Hit Rate 很高时,若长前缀持续被淘汰或远端 Block 到达过慢,P95 TTFT 仍可能很差。

失败模式与安全边界

KV State 由用户输入派生,可能持续保留敏感信息。Cache Memory、Hash、Metadata、传输路径和 Timing Behavior 都应按受保护数据处理。

失败 原因 控制
复用后输出错误 模型、Tokenizer、Adapter、Template、模态、位置或 Dtype 不兼容 版本化 Cache Identity,拒绝不兼容 Block
跨租户泄露 Prefix Block 跨 Trust Boundary 复用 Tenant-scoped Key/Salt、授权、内存清理和留存上限
Timing Side Channel 命中/未命中延迟暴露某个前缀是否存在 隔离策略、引擎支持时使用 Cache Salt、限制明细指标暴露
OOM 或反复 Preemption 突发长度下 Token Capacity 过度承诺 Token-based Admission、Headroom、输入输出上限
命中率高但 SLO 无收益 前缀很短或 Decode 占主导 按复用 Token 加权,并拆分命中/未命中延迟
Offload 卡顿 CPU/GPU 或网络链路饱和 Tier Budget、传输指标、局部性感知调度
量化质量回退 Dtype 不支持、Scale 不合适或 Layer 敏感 准确 Engine 支持检查与质量门禁
陈旧状态 部署或 Adapter 更新后未失效 按不可变 Revision 划分 Namespace,并 Drain 旧代次

不要为了调试命中率就记录原始 Prompt、Token ID、Salt 或 Cache 内容。应使用不透明 ID、聚合指标、访问控制和有界留存。

生产决策清单

启用或修改 KV Cache Strategy 前:

  • 确认准确的模型、Tokenizer、Adapter、Engine 与 Cache Dtype;
  • 按 KV Head 和 Retained Token 分布计算 Bytes per Token;
  • 为非 Cache VRAM 与碎片预留 Headroom;
  • 分开测量 Prefill、Decode、Reuse 与 Transfer;
  • 定义 Allocate、Fork、Free、Evict、Invalidate 和 Rollout 行为;
  • 按 Tenant 或明确 Trust Group 隔离 Cache Reuse;
  • 对量化、淘汰或压缩执行 Output Quality Regression;
  • Canary 新配置,并可回滚到上一代 Cache。

来源与相关内容

一手技术来源:

本站主题边界: