先定目标:高速 KV 缓存到底在解决什么
需要哪些核心组件
1. Token / Block 分配器
把连续的 KV 存储切成固定大小或半固定大小 block/page,避免每个请求拿一大段连续显存。
- 页式分配,降低显存碎片
- 按 block 建索引,支持重映射
- 配合批量回收和惰性回收
2. Prefix 索引与查找层
把前缀转成可匹配的结构,不管是 radix tree、trie、chunk index 还是 hash 索引,目的都是快速判断“哪些 token 之前已经算过”。
- 最长前缀匹配
- 增量插入
- 共享前缀引用计数
3. Scheduler / Admission 控制
缓存系统不只是存储层。调度器必须知道哪些请求能命中缓存、哪些值得等待合批、哪些需要抢占显存。
- prefill / decode 分阶段调度
- 命中优先级决策
- 容量水位保护
4. 多级存储层
真正的基础设施通常不是只有 HBM。一层是 GPU HBM,二层可能是 host DRAM,三层可能是本地 NVMe 或远端对象缓存。
- GPU 常驻热块
- CPU 存温数据
- 磁盘/远端存冷数据
5. 传输与预取链路
跨层缓存不是白送的。DMA、PCIe、NVLink、RDMA、异步预取、后台搬运,这些决定了命中是不是“真有收益”。
- 异步 load / prefetch
- 带宽预算与限流
- 搬运与计算重叠
6. 回收、压缩与观测
缓存系统最后死在这里的最多。没有 eviction、引用计数、碎片治理、命中率观测,缓存迟早从加速器变成负债。
- LRU/LFU/成本感知回收
- OOM 保护与降级
- hit rate / load latency / spill rate 指标
底层原理:为什么这些组件能提速
| 原理 | 解决的问题 | 收益 | 代价 |
|---|---|---|---|
| Prefix reuse | 重复请求每次都从头 prefill | 直接减少 attention FLOPs 和 TTFT | 需要复杂索引与失效管理 |
| Paged / blocked KV | 连续显存分配碎片严重 | 更稳定的内存管理和并发容量 | 需要额外 indptr / indices 映射 |
| Multi-tier cache | GPU HBM 太贵太小 | 扩大可复用上下文池 | 引入搬运延迟 |
| Asynchronous prefetch | 命中冷数据但取回太慢 | 减少 stall,让 GPU 不空等 | 需要预测和调度协同 |
| Reference counting / pinning | 共享前缀被误删 | 提升正确性和热数据稳定性 | 状态维护复杂 |
| Cost-aware eviction | 命中率高但搬入搬出抖动大 | 回收更接近真实收益最大化 | 策略和观测门槛更高 |
最常见的拓扑:单机热缓存、多机共享目录、分层冷存
┌──────────────────────────────────────┐
│ Router / Gateway │
│ prompt normalize + route by prefix │
└──────────────────────────────────────┘
│
┌─────────────────┴─────────────────┐
│ │
┌──────────────────────┐ ┌──────────────────────┐
│ Inference Node A │ │ Inference Node B │
│ ┌──────────────────┐ │ │ ┌──────────────────┐ │
│ │ GPU HBM hot KV │ │ │ │ GPU HBM hot KV │ │
│ ├──────────────────┤ │ │ ├──────────────────┤ │
│ │ CPU warm KV │ │ │ │ CPU warm KV │ │
│ ├──────────────────┤ │ │ ├──────────────────┤ │
│ │ local NVMe spill │ │ │ │ local NVMe spill │ │
│ └──────────────────┘ │ │ └──────────────────┘ │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
└──────────────┬───────────────────┘
│
┌──────────────────────────────────────┐
│ Shared metadata / prefix directory │
│ hash / trie / radix / ownership map │
└──────────────────────────────────────┘
拓扑 A:单机内闭环
适合单机多卡或小规模集群。调度、索引、HBM/DRAM 分层都在一个节点内完成,链路短,调优简单。
- 延迟最低
- 实现最稳
- 缓存复用范围仅限本机
拓扑 B:多机共享元数据
各推理节点本地存 KV,本地命中优先;只共享 prefix 元数据目录和 ownership 信息,避免把所有 KV 真正做成远程中心化存储。
- 扩展性更强
- 不会把所有流量压到中心缓存
- 需要更聪明的路由与失效同步
不要一上来就做“全局远程 KV”
更靠谱的演进顺序
- 先做本地 HBM + DRAM 两级缓存
- 再加 prefix 目录和路由亲和
- 最后再考虑跨机 materialized KV 迁移
原因
- 绝大部分收益先来自本地 prefix 命中
- 网络 hop 是硬成本,不会凭空消失
- 先把命中、回收、碎片治理做好再扩展
请求流转时序:一条命中的请求到底怎么跑
Client Request
│
▼
[Prompt normalize]
│ tokenize / trim / template canonicalization
▼
[Prefix lookup]
│ longest-prefix match -> locate cache entry
├──────── miss ────────► full prefill on GPU
│
└──────── hit ─────────► map token blocks / fetch warm blocks
│
▼
[Scheduler admission]
│ merge with running decode batch if possible
▼
[GPU execute remaining prefill]
│
▼
[Decode loop]
│
▼
[Update index + refcount + heat]
│
▼
[Evict / pin / spill if needed]
一个靠谱的组件边界长什么样
| 层 | 职责 | 不要做什么 |
|---|---|---|
| Router | 规范化请求、做亲和路由、尽量把相似 prefix 打到同节点 | 不要自己维护 KV 细粒度生命周期 |
| Prefix Index | 维护 prefix → block list / ownership / heat | 不要直接碰 GPU 执行细节 |
| Memory Manager | 管理 HBM/DRAM/NVMe block、引用计数、回收策略 | 不要决定业务优先级 |
| Scheduler | 决定 admission、batch 组成、prefetch 时机 | 不要把索引结构硬编码死 |
| Transfer Engine | 执行异步搬运和预取,重叠通信与计算 | 不要自己拍脑袋回收缓存 |
| Observability | 输出 hit rate、spill rate、fetch latency、fragmentation | 不要只看平均值 |
设计里的关键取舍
固定 block 大小 vs 可变 block
固定 block 更利于实现和管理;可变 block 在理论上能减少浪费,但 metadata 和重组复杂度更高。多数工程系统会先用固定 block,再局部加优化。
精确匹配 vs 模糊共享
严格 prefix 匹配最稳;做相似 prompt 的模糊共享往往收益不如风险大,因为位置、模板、special token 差一点都可能让缓存失效。
中心目录 vs 去中心目录
中心目录简单但容易成为瓶颈;完全去中心又难协调。现实里经常是分片目录 + 局部 ownership + 路由亲和。
回收看“最近”还是看“成本”
单纯 LRU 容易错杀刚好贵的 prefix。更好的策略是结合热度、重算成本、传输成本、命中概率做综合回收。
高速的前提:你的 prompt 体系得可规范化
该做的
- 统一模板和 separator
- 把高频 system prompt 固化
- 把无关动态字段后移
- 做 tokenize 之前的 canonicalization
别做的
- 每个请求塞唯一 trace id 到 prefix
- 让多条同构请求拥有不同模板空格/换行
- 把工具结果历史无限拼进同一前缀
- 指望缓存层替你纠正 prompt 设计问题
缓存基础设施最容易挂掉的地方
症状 根因 ──────────────────────── ───────────────────────────────────── 命中率看起来高 但搬运延迟太大,实际 TTFT 没降 显存经常爆 block 回收不及时 / pin 太多 / 批量回收缺失 吞吐抖动 prefetch 与 decode 抢带宽 / admission 不稳 跨节点命中率低 路由没做 prefix affinity 缓存越大越慢 metadata 过重 / 查找成本过高 / 冷数据太多 ��统很复杂但收益一般 上游 prompt 根本不规范、复用率不够
推荐的落地路线
阶段 1:单机高质量缓存
- paged KV allocator
- prefix index
- 引用计数 + eviction
- 基础 hit / miss 指标
阶段 2:调度协同
- 命中优先 admission
- prefetch 与 batch 构造结合
- 热数据 pinning
- 容量水位和降级路径
阶段 3:集群化
- prefix-aware routing
- 共享元数据目录
- 跨机 warmup / transfer
- 基于收益的全局回收策略
适合什么团队 / 不适合什么团队
适合
- 有明显重复前缀流量的 LLM 服务
- 长 system prompt、长上下文、RAG 模板固定的场景
- 在线推理成本高、TTFT 敏感的团队
- 已经有基本调度和显存管理能力的 infra 团队
不适合
- 请求高度随机、前缀几乎不复用
- 业务还在 prompt 形态剧烈变化期
- 团队还没有观测、回收、调度的工程基础
- 想一步到位做全球共享远程 KV,但没有高带宽低时延网络预算