首页优化地图方法索引基础概念
返回技术汇总表 ↗

M014 · 独立详解

历史 KV 复用:为什么生成时不用重算整段文字

从三次前向的对照例子出发,理解每层缓存了什么、下一轮读什么,以及省计算为何又带来内存压力。

模型与算法内存与数据搬运详解已编写
视频讲解待制作

这篇文章的小视频尚未制作;可以先阅读下方文字详解。

本页目录

01 · 没有缓存,每生成一个 token 都会发生什么

先假设模型已经加载,输入是四个 token:[A,B,C,D]。模型处理这四个位置,利用 D 位置的最终输出预测下一个 token E。为了再预测 F,一个最直接的实现会把 [A,B,C,D,E] 全部重新输入模型;再预测 G 时,又处理 [A,B,C,D,E,F]。

这里重复的不只是读了一遍 token id。每个位置的向量都要经过多层 Attention、线性变换和逐位置的其他运算。历史位置的中间结果一次次重新生成,最后却只用最后一个位置的输出预测下一步。

要预测谁不使用缓存:本轮处理位置使用缓存:本轮新增位置
EA B C D,共4个A B C D,共4个,建立缓存
FA B C D E,共5个E,共1个,读取历史缓存
GA B C D E F,共6个F,共1个,读取历史缓存

02 · 为什么历史结果可以复用

标准因果 Attention 有一条约束:某个位置只能看自己和之前的位置,不能看未来。因此,当 E 被追加到末尾,A 到 D 在同一模型、同一位置和同一执行配置下的历史表示不需要随未来输入改变。每一层都可以保留这些位置已经算出的 K 和 V。

Q 是当前查询,K 用来与查询算相似度,V 是按注意力权重汇总的内容。下一轮的新位置需要用自己的 Q 去读取历史 K/V,通常不需要历史位置的 Q。因此常规生成缓存保存 K/V,而不保存整段历史 Q。

“每一层”很关键:第1层的 K/V 来自第1层输入,第2层的 K/V 来自经过第1层后的表示,不能拿一份缓存用于所有层。

依据:Transformers · How Caching Works ↗

03 · Prefill 建缓存,Decode 读缓存并追加

  • Prefill:整段提示词经过所有模型层;每层把提示词各位置的 K/V 写入本层缓存。最后一个位置的输出产生 logits,采样出首个新 token。
  • Decode:把刚采样出的 token 输入模型,使用正确的绝对位置。每层为这个新位置计算 Q/K/V,Attention 读取历史 K/V,也包括当前位置的 K/V。
  • 提交新状态:各层缓存长度增加1。最终输出再产生下一个 token;新采样 token 尚未经过前向。
教学伪代码 · 展示数据依赖,不是引擎 API
cache = empty_cache_for_each_layer()
logits, cache = forward(prompt_ids, positions=0..N-1, cache=cache)
next_id = sample(logits[-1])
while need_more_tokens:
    # next_id 是已经采样、但还没有算过自身 K/V 的输入
    logits, cache = forward([next_id], positions=[N], cache=cache)
    N += 1
    next_id = sample(logits[-1])

真实引擎可能按页写入、预分配槽位或采用不同函数签名。上面的循环只表达时间顺序;位置、注意力掩码、有效长度都必须与缓存中已有内容对应。

04 · 省了哪些计算,还有哪些计算

对第1节的例子,不缓存时三轮共处理 4+5+6=15 个位置;缓存时处理 4+1+1=6 个新增位置。这可以说明逐位置投影和其他历史位置计算的重复被消除,不能据此宣称整个推理提速 15/6=2.5 倍。

操作有缓存的 Decode 是否还做原因
当前 token 的投影、归一化、MLP仍然做当前位置要经过全部模型层。
历史位置的 K/V 投影通常不重做读取本层已经计算好的缓存。
当前 Q 与历史 K 的打分、Softmax、加权 V仍然做生成仍依赖历史内容。
历史位置查询的 Attention 输出通常不重做只需当前查询的输出。
采样与服务调度仍然做KV 缓存没有替代输出选择和请求执行。

普通稠密 Attention 的单个 Decode 查询仍要访问长度为 N 的历史 K/V。查询分数形状通常是 1×N;工作量和读缓存的流量仍随上下文长度增加。所以“用了 KV Cache”不代表每个新 token 的成本恒定,也不代表模型一次前向只做 Attention。

05 · 省下重算,付出多少缓存空间

可把缓存想成每层各有两本随上下文增长的笔记。只估计普通 K/V 元素,单请求容量为:2 × 层数 L × KV 头数 Hkv × 头维度 d × 已缓存位置 N × 每元素字节数 b。批次内长度不同的请求要分别求和;分页、对齐、元数据与空闲池另计。

容量手算 · 不含模型权重、工作区、块表或预留池
L=32, Hkv=8, d=128, N=4096, b=2 字节
KV = 2 × 32 × 8 × 128 × 4096 × 2
   = 536,870,912 字节 = 512 MiB
8个同样长度的请求:仅有效 K/V 元素约 4 GiB

假如查询有32个头,KV 只有8个头,这是 GQA 的一种配置;容量公式使用8,不能把查询头数直接代入。若同层数与维度改为32个 KV 头,单请求容量变为2 GiB。MLA、混合层、滑动窗口等缓存结构需要自己的容量公式。

这也解释了三个关联方法:分页 KV 改善存放与分配;KV 量化减少每元素字节数;前缀缓存让兼容请求共享已算内容。它们可以与历史 KV 复用叠加,但分别有新的条件与代价。

06 · 从教学模型验证等价性

下方 CPU 例子实现一个两层、单头的因果 Attention 教学网络,用固定位置向量、线性投影和逐位置变换模拟缓存依赖。它分别运行完整前向、逐 token 缓存前向以及分块前向,检查各位置输出是否一致。它没有完整 LLM 的归一化、RoPE、训练或高性能内核。

实际接入应先检查框架或引擎已有缓存接口,确认 use_cache、各层缓存布局、attention mask 和位置参数。支持某个模型的缓存结构,比设置一个布尔开关更重要。

依据:Transformers · How Caching Works ↗

07 · 什么时候不能直接复用

  • 模型权重或适配器改变:原 K/V 属于原计算配置。
  • 同一 token 的先前上下文或位置改变:它的隐藏表示可能不同。
  • 非因果全序列 Attention:未来输入可能改变历史表示。
  • 训练更新参数:跨优化器步骤沿用生成缓存不能替代正确的训练前向与梯度图。
  • 滑动窗口截断、KV 量化或压缩:是否改变数学语义或数值误差,需要单独评估。

检查输出等价时,先固定 token 输入,在相同位置比较 logits 或隐藏向量并设置合理误差。随后才测试完整生成。采样和浮点舍入可能放大小误差,不能只凭两段文字是否一样判断缓存正确。

08 · NVIDIA 与昇腾:同一逻辑,不同实现

两类设备都能执行“缓存历史 K/V,计算当前位置”的生成逻辑。差异主要要在引擎里核对:缓存布局与精度、分页支持、实际 Attention 后端、搬运路径、并行分片以及支持的模型。不能仅凭 GPU 或 NPU 名称判断缓存收益。

可从 Transformers 的语义说明和 TensorRT-LLM 的缓存实现文档建立检查清单,再对照选定昇腾推理引擎的具体版本。本文没有把 NVIDIA 的接口当作昇腾现成可用的接口。

依据:TensorRT-LLM · KV Cache System ↗

09 · 检查自己是否真的理解

  • Prefill 后刚采样出 E,缓存里有 E 的 K/V 吗?
  • 为什么新 Q 仍要读取历史 K/V?
  • 生成三个新 token,为什么不是三次 Decode 前向再加一次 Prefill?
  • 32个查询头、8个 KV 头时,容量公式应该用哪个数?

流程位置与关联方法

这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。

资料与核查范围

原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-08。引用 main / latest 文档时,实际接口以安装版本为准。

代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。

← 返回总表中的 M014继续阅读:前缀缓存与缓存感知路由 →
AI Infra 知识库 · 总览 → 独立讲解网站更新 2026-10-08
搜索知识库

搜索方法与基础概念 · Esc 关闭