LLM Infer Infra 学习资料库

KV Cache 存储详解:数据模型、协议、容量、性能与故障

本篇定位:KV 存储的共同原理以本篇为准,不绑定具体框架。Mooncake、LMCache 页面讲各自的实现细节,两者结合的完整链路见联合链路。

存储主题 · 设计图册 · LMCache + Mooncake 实现 · 专家能力总纲

这一章面向需要设计和负责 KV 存储子系统的人。目标是把“把 KV 存起来”拆成可验证的协议和预算,再用具体实现检验。先掌握KV 的计算语义;这里重点讨论状态离开引擎之后如何存、如何取,以及失败时怎样保持正确。

1. 先写清系统契约

普通缓存允许 miss;KV 缓存也可以丢弃可重算数据,但不能把错误或不完整的命中交给模型消费。因此应分别定义:

契约 必须说明什么
内容身份 哪些模型、前缀、位置和配置可以共享?
表示格式 bytes 如何映射到 layer、head、rank、group、dtype 和页?
可见性 什么事件之后读者能发现并使用完整对象?
生命周期 谁持有源、目标和远端资源,何时允许回收?
失败语义 miss、超时、部分完成、校验失败分别返回什么?
性能与服务 允许等多久,何时放弃加载,怎样限制回源?
运维边界 故障域、隔离、配额、升级、恢复与审计如何实现?

“接口返回成功”必须落到其中某个具体事件,不能笼统表示从网络到 GPU 的所有步骤都完成了。

2. 逻辑身份和物理布局分开设计

2.1 key 标识什么

一个教学上的身份描述可以包含:

共享域/租户 + 模型与适配器版本 + 位置语义
+ token 前缀身份 + KV 表示格式 + group/rank

这不是要求把所有字段原样拼进字符串。可以使用稳定的 manifest ID、namespace 和内容 hash,但要能证明被省略的信息在该共享域内固定。tokenizer 和模板会影响最终 token 序列;LoRA、RoPE、模型 revision 等还可能改变相同 token 的状态。

内容寻址通常把父前缀身份纳入后续 chunk 的 hash。相同后缀 token 出现在不同前文之后,不能因此共享深层 KV。hash 一致也不能替代版本和布局契约;外部客户端还要有真正的授权,namespace 本身不等于访问控制。

2.2 四种大小不能混用

KV 数据模型

单位 职责 常见混淆
page/block 引擎 GPU 内存分配单位 不一定连续,也不一定是存储对象
chunk token 匹配与传输组织粒度之一 不保证等于一个 page 或完整对象
object 存储查找、发布、读取的单位 一个 chunk 可拆成多个 group/rank 对象
segment 提供存储空间或传输寻址的资源区域 不等于物理主机或故障域

读取对象之后可能还要做页收集/散射、层/页顺序转换和设备拷贝。存储层报告“这些字节读到了”,不自动说明 GPU kernel 能按当前布局解释。

2.3 一个可以手算的映射例子

假设普通 GQA:32 层、8 KV heads、head dimension 128、BF16,全部层为普通 full attention。

每 token = 2 × 32 × 8 × 128 × 2 = 128 KiB
8,192 token = 1 GiB(整个模型的 KV)
chunk = 256 token → 每个逻辑 chunk = 32 MiB
TP=4 且 KV 均匀切分、无复制 → 每 rank 的 chunk = 8 MiB
每请求 32 个 chunk × 4 个 rank = 128 个存储对象
128 × 8 MiB = 1 GiB

这里没有 allocator、元数据和副本开销。TP=4 的除法有明确前提;不能直接套到 KV head 复制、MLA、滑窗或混合状态模型。引擎最终跳过的 token 还受边界与首 token 处理影响。

3. 不可变对象与完整发布

对象协议

写入可分成保留空间、填充、验证完成、发布四步。重复写同一个内容 key 可以设计成幂等,但需要处理两个写者同时写、一个写者失败、已有 key 内容不一致等情况。

不可变 key 的价值是减少在途读者与覆盖写的冲突。若确实需要更新,必须明确版本号、并发条件和读者看到哪个版本;不能把任意 put 都当成覆盖更新。

多个对象组成一个模型前缀时,还要定义组合完整性。普通 full-attention 示例:

          chunk0  chunk1  chunk2
rank0       1       1       1
rank1       1       0       1

共同连续前缀只有 chunk0。批量 API 提高提交效率,不自动提供跨所有对象的事务或完整前缀保证。混合模型要按各 group 的窗口和状态快照语义计算,不能简单套用逐列 AND。

4. 读保护、租约和 buffer 所有权

保护机制 主要保护对象 需要确认的边界
引擎页引用 GPU 页不被提前复用 取消、抢占、异步保存、其他共享请求
本地读写保留 staging/resident 内存 发布、重复写者、owner、完成回调
adapter 本地锁计数 该 adapter 管理的使用/淘汰决策 不自动成为跨客户端分布式锁
Store 读租约 有时间窗口的远端读取保护 排队、时钟假设、到期、节点退出
设备事件/传输完成 在途读写已达到对应完成条件 不自动证明对象语义、布局和租约有效
pin/TTL 保留偏好或生命周期策略 不等于持久化,也不替代在途保护

资源释放必须在不会再被任务访问之后发生。尤其是超时:它可能只代表调用者没有拿到结果,底层任务仍可能写目标 buffer。安全做法需要隔离目标、排空或确认取消完成,再决定复用;不能仅因控制 RPC 超时就释放内存。

进程重启与地址复用还涉及代际问题:旧回调、旧 descriptor 和旧请求 ID 不能操作新一代资源。这是设计审查点,具体实现是否使用 epoch、session 或其他方式,要沿代码验证。

5. 存储控制面与数据面

目录维护对象位置、大小、状态、副本和保护信息;payload 由相应资源持有。它们的吞吐和故障模式不同:

专家设计不能只写一个“高可用 Master”。还要说明主从切换后的旧主隔离、恢复点、已发布与未发布对象的处理、payload 存活验证,以及客户端如何重新取得有效位置。具体 HA 机制在实现层核对。

6. 做容量、对象数和网络三本账

6.1 容量

沿用前面的模型,保留 32,768 个互不重叠的 8K 前缀:

逻辑 KV = 32,768 × 1 GiB = 32 TiB
两份 payload 副本 = 64 TiB
128 台 × 512 GiB/台 = 64 TiB 物理 DRAM
若仅 80% 可用于该池,实际预算 = 51.2 TiB → 装不下

即使增加到 160 台,80% 预算也只是刚好 64 TiB,还要检查这 20% 是否已足够覆盖元数据、注册/暂存、故障恢复和扩缩容余量,不能把所有余量重复计入或完全忽略。前缀共享会降低唯一状态量,需要按唯一 chunk 建模,不能按请求数反复相乘。

6.2 对象和元数据

按 8 MiB/rank-object,有 32 TiB / 8 MiB = 4,194,304 个逻辑对象。若假设每对象目录项 512 B,则仅这部分约 2 GiB;副本描述、key、索引、allocator 和快照还有额外占用。512 B 是预算假设,不是某个实现的测量结果。

把 chunk 减小 8 倍,理论对象数增加 8 倍。收益是否值得,要同时看部分命中精度、控制 QPS、注册与传输开销。

6.3 网络

假设到达 200 请求/s,75% 的请求需要从远端恢复整份 1 GiB,且无本地层命中:

读取 payload = 200 × 0.75 × 1 GiB = 150 GiB/s
400 Gbit/s ≈ 46.57 GiB/s(理想线速)
若规划按 65% 线速,约 30.27 GiB/s/链路
仅聚合读取就至少约需 5 条等效链路

这不是 5 张卡就能满足 SLO 的结论。还要看热点分布、机架上联、TP fan-out、源/目的内存、H2D 和故障降额。若另外每秒保存 50 份新 KV、每份两副本,写 payload 又有约 100 GiB/s;应建立有方向的流量矩阵,结合全双工链路和实际复制路径计算。

7. 判断复用是否值得

已确认候选命中时,比较:

省掉的关键路径计算时间
与
新增查询 + 等待 + 传输 + 转换 + 设备同步的关键路径

有重叠时按时间线计算,不能机械相加,也不能假定完全隐藏。做准入或预取决策时还不知道未来是否复用,需要再考虑复用概率、写入成本、对象寿命与占用空间的机会成本。

建议同时记录:请求/token 命中率、成功加载 token、实际少算的 token、读写字节、host/网络排队、TTFT/ITL、goodput 和每单位有效服务成本。热数据更集中可能提高命中率,同时恶化热点队列。

8. 策略与多租户

准入决定要不要存,淘汰决定删谁,预取决定何时搬,路由决定请求发往哪里。这四项需要共同评估,不能分别优化一个局部指标。

容量、写入带宽、预取在途量和回源计算都应有预算。至少考虑大租户挤占、小租户尾延迟、热点前缀复制、一次性长请求污染,以及某个租户升级后整体命中骤降。

跨租户共享必须有显式策略与授权边界;日志和指标避免泄漏原始 prompt。版本升级可以采用 namespace 隔离、双读新写、受控预热和回滚窗口,但这些是候选设计,不代表当前框架默认具备。

9. 故障矩阵与验收

故障 正确性目标 服务目标与证据
缓存 miss 不错误跳过计算 限制回源并观测 miss 的原因
部分对象丢失 重新计算有效范围 避免对无用后缀继续搬运
写者退出 不发布半成品 残留空间最终回收且不伤在途任务
读取超时 不消费未知完成的目标 重试有界、目标隔离、资源最终释放
节点/故障域退出 不消费失效地址 副本选择、重算预算、修复速率
主节点切换 不接受旧主错误状态 明确恢复点、隔离和重连
校验/布局不匹配 拒绝该缓存 可定位到版本、对象与数据路径
热点/容量打满 不破坏活跃读者 准入、背压、公平性、SLO 曲线

验收至少包含正常读写、并发重复写、读取期间淘汰、取消/超时、部分 rank 缺失和节点故障。协议推演不能替代真实 GPU 正确性与硬件性能验证。

接着读LMCache + Mooncake 联合链路,把这些问题逐项映射到实际接口、线程、资源和返回值。

输入主题、术语或问题