06 KV Cache、Prefix Cache、PagedAttention 与 vLLM
课程锚点:范围板书;Lecture06 Pages3–9;Lecture07 Slides4–6;Tutorial05;week6/Chapter_4_vllm_lab.md。Lab中的实测结果要按实际配置解释,不能反过来当成普适定律。
6.1 为什么旧 K/V 可以缓存
固定参数、确定性推理、相同历史token/位置/有效上下文下,因果网络中位置j的hidden state只依赖≤j的输入。追加后续token不会改变旧hidden state,所以每一层旧K_j/V_j可复用。双向encoder若修改上下文则不满足这个条件。
新token到达一层:计算q_new/k_new/v_new→对q/k做正确位置旋转→追加新k/v→q_new与全部允许的历史K做点积→softmax→乘历史V→输出到下一层。每层有自己的cache,不只保存embedding层的一份。
为何不通常缓存Q?未来query需要和旧K匹配,再读旧V;生成新位置时不需要重算旧位置输出,也不需要旧Q。Q缓存不能替代K/V。cache也不是把attention权重A存起来:未来Q变了,A必须重新计算。
6.2 Prefill 与 decode
prefill一次处理已知prompt的T_p个token并建立各层KV;通常只用最后位置logits生成第一个新token。后续decode通常每次输入刚生成的新token,加历史cache长度,得到下一个token的logits。
因此“prefill阶段不产生任何新token”并非所有实现下正确:prefill输出能直接用于采首token。TTFT包括排队、prefill、采样和网络输出等;不能把TTFT当纯prefill kernel时间。
无cache、每步重跑完整前缀,会重复做旧token的投影、FFN和attention。仅考察生成阶段、忽略prompt且长度到T时,重复全序列attention成本可达Σt²=O(T³D);cache后每步只新增一行attention,累计Σt=O(T²D)。dense投影/FFN从重复所有位置变成主要每步一个新位置。缓存没有使softmax attention总工作量变成O(T)。
6.3 KV 的字节数
2表示K与V;N层数;B同时驻留的序列数;T每条缓存长度;Hkv个KV头;d每头宽;b每元素bytes。不同序列长度用ΣT_i代替BT。这里不计block舍入、元数据、临时工作区、滑窗/混合层等特殊结构。
例N=32、B=1、T=8192、Hkv=8、d=128、BF16 b=2:KV=1,073,741,824 bytes=1 GiB。若MHA的Hkv=32,4 GiB;MQA的Hkv=1,128 MiB。batch=8的同长GQA需要8 GiB。cache通常随长度和并发线性增长。
这说明GQA节省KV存储和带宽;但是每个Q头仍算匹配分数,不能说总attention FLOPs直接按Hq/Hkv倍减少。
6.4 Prefix cache 的匹配条件比“文字像”更严格
跨请求复用相同前缀KV,例如固定system prompt、多轮对话未变的历史、同一长文档。通常需要token ID序列及其全部先行上下文一致,并具有兼容的模型权重、adapter、位置与其他影响状态的配置。
为什么不是任意相同substring都能复用?同样一句话若前面历史不同,其hidden states已含不同信息,K/V通常不同。语义相似、标点差异、chat template差异也不能自动当精确命中。
完整缓存块常用链式hash,将父块hash、本块token等纳入键。分叉后第一个不同块影响后续匹配;未满块是否可复用是实现细节。命中后主要节省相应prefill,不意味着decode免费,也不意味着所有后缀计算都免除。最终位置logits如何取得也可能要求额外处理边界token。
Token命中率=命中token数/可比较prompt token数;request命中率=有命中的请求数/请求数;block命中率按块统计。若一次请求只命中1/1000 token,它可以计为一次request hit,却token命中率极低。读取累计metrics要作差,并确认当前版本指标究竟统计token还是block查询。
6.5 PagedAttention 解决什么问题
如果每个请求预分配连续最大长度KV空间,会有内部浪费;请求不断开始/结束、长度不同,也会导致外部碎片和扩容困难。PagedAttention借用虚拟内存分页思想:逻辑token块映射到可不连续的物理KV块,通过block table访问。
流程:请求来→按需分配物理block→token位置映射到逻辑block和offset→attention kernel根据表读取实际KV→生成增长时追加block→请求结束回收或保留可复用块。共享prefix/beam分支可以共享只读块,写入时适当copy-on-write及引用计数。
例block size16、请求长35,需3块48个槽位,末尾浪费13;不是无浪费,但比预留很大的max length好。分页并不自动压缩每个KV元素,也不把GPU显存变无限;它减少管理浪费、改善共享和可用并发。PagedAttention原论文。
6.6 Continuous batching 与框架分工
静态batch等待一批请求全结束;continuous batching在迭代边界让已完成请求退出、新请求加入,减少空等,改善吞吐。调度需要平衡prefill与decode,长prefill可能影响其他请求的首token/后续token延迟,因此可能采用chunked prefill等策略。
| 方法 | 主要优化对象 | 是否改模型注意力定义 |
|---|---|---|
| KV cache | 同一因果序列的重复历史计算 | 否 |
| Prefix cache | 不同请求的共同历史prefill | 否 |
| PagedAttention | KV块分配、共享和读取 | 否 |
| FlashAttention | attention的HBM/SRAM数据搬运 | 否,标准精确版本 |
| GQA | KV头数与模型结构 | 是架构取舍 |
| Speculative decoding | 逐token目标模型调用效率 | 正确随机版本保持目标采样分布 |
| Continuous batching | 多请求调度与硬件利用 | 否 |
vLLM是服务/推理框架,不等于单一attention算法;OpenAI-compatible表示接口协议兼容,不表示模型来自某家公司。框架能否加载某架构、支持某dtype/MTP/TP取决于版本和后端,应查实际安装配置。
6.7 把Lab实验设计正确
缓存对照:固定模型、精度、硬件、prompt长度、输出长度上限与采样设置;先预热kernel;构造A共享长前缀+B各异短后缀;与等长但不共享的组比较;记录冷/热缓存、block/token命中数、TTFT、输出token数、端到端延迟。
不要只比较两个字数不同的prompt;不要把全局cache hit counter累计值当本次命中;不要只测一次就下结论。若公共API无法禁用/清空缓存,报告这个控制限制,不能宣称已经隔离了prefix cache的因果效应。
吞吐实验:并发1/2/4/8…;在固定测量窗口统计真实新增token总数/秒,另报请求/秒、p50/p95延迟、TTFT、TPOT。TPOT常按首token之后生成间隔估计;输出只有1个token时分母为0需单独处理。吞吐提高可以同时伴随单请求延迟变差。
本地已有remote_27b_lab/results/*.csv可供分析实验设计。它们是特定远端配置的观测,不应拿“27B”模型名代替实测总参数、激活参数或精度说明。