深入篇 28 / vLLM V1 运行时细节 · 2026-11-08

vLLM KV 管理:hash 身份、free队列与淘汰

解释“已算hash”不等于“可共享KV”,并手算一次前缀命中与块池回收。

核心阅读 12–18 分钟练习另需 10–20 分钟前置:25、27;原课09

本课只拿下这一件事

解释“已算hash”不等于“可共享KV”,并手算一次前缀命中与块池回收。

缓存身份需要比token子串更多信息。不同身份的请求不能仅因token子串相同就共享缓存。
不同身份的请求不能仅因token子串相同就共享缓存。

计算hash和登记可用缓存是两件事

请求创建/输出追加可以预先形成完整块token hash,但设备可能尚未算出KV。只有满足计算完成、缓存组与状态约束的块才能进入可复用前缀。hash链包含前块hash、本块tokens及extra keys;此版本extra keys可涵盖LoRA、多模态标识、cache salt、prompt embeddings。block_pool再区分KV group。异步系统不能用“hash存在”代替“数据已就绪”。

全部prompt见过,也未必全跳过

设标准full-attention单group、B=16、prompt长度32,两块都能匹配历史缓存。常规生成还需要最后位置的hidden/logits,因此get_computed_blocks把最大命中长度限制到31。完整块对齐后只能命中16,剩下16要重算。它不是数学上必须重算整块,而是此路径的块对齐策略。要区分“块可命中”和“本轮实际命中多少”。

free queue 的手工演练

初始free=[U5,C3,C4],U无hash,C带hash,ref_cnt都为0。请求命中C3并touch后,队列=[U5,C4]、C3.ref_cnt=1;请求释放后C3回队尾,队列=[U5,C4,C3]。分配两个新块得到U5和C4,C4的旧hash被清理,而C3仍能命中。由此看到缓存内容与活跃引用是两个维度。

本版本默认将cached free块放队尾,无hash块放队首优先复用;evict_first等路径能改优先级。用“类似LRU”建立直觉可以,但不能把整个队列当成一条无例外的时间排序。touch、free_blocks与get_new_blocks是观察生命周期的三个小入口。

别把当前可容纳说成永不OOM

full_sequence_must_fit检查当前已知request.num_tokens对应的序列能否容纳,不是提前保证所有未来生成都能一直增长。lookahead、watermark、滑动窗口与多KV group还会影响分配;本课例子刻意限制为一个标准group。端到端系统的容量问题应同时记录token预算、块可用数、prefix hit与抢占次数。

最后一个token的logits边界。图的槽位采用B=4示意寻址;32-token例子的B=16见正文。
图的槽位采用B=4示意寻址;32-token例子的B=16见正文。

改变变量,验证直觉

启用 JavaScript 后可交互;上面的图和下面的手算练习始终可读。

数值练习:先算,再核对

B=16,prompt长度48,已有三块完整缓存,常规生成需重算最后位置:先算最大可复用token,再按整块对齐。另把free队列例子中释放顺序换掉,判断新分配会覆盖谁。

闭卷检索

不要回看正文。答错后先解释原因,再重新打开这一课。

把结论落到源码

  1. 找到get_computed_blocks里的last token注释,核对完整块对齐返回值。
  2. 追touch/free_blocks/get_new_blocks,标出hash索引与free链表分别在何时改变。

以下行号由本地 vendor 文件生成;上游链接固定完整 commit,离线摘录可直接打开。

优先阅读:固定源码 get_computed_blocks、BlockPool。

速查:形状与公式 · 三框架映射 · 术语表 · 复习与实战路线 · 4070 Ti本地实验

记录你此刻能做到什么

只在当前浏览器保存自评;不会自动写成已掌握的 learning record。

随时追问老师。把“第 28 课 + 哪个公式 / 哪个张量 / 哪一行 + 你的推导”发给我,我们可以继续拆到每一步。