Skip to content
Go back

Prefix Caching

KV Cache aware routing

KV Cache routing 和 load balance 有本质冲突:

Table of contents

Open Table of contents

Prefix caching

llm.inference(
	input_tokens: list[int], # N tokens
	previous_kv_cache: list[Tensor], # M kv cache for prefix tokens and M < N
) -> output_tokens, new_kv_cache

output_tokens: # generated tokens, length N'
new_kv_cache: # kv cache for N + N' tokens

Why M < N ?

tokens 是 key,KV cache tensors 是 value。因此我们可以先想象一个 KVCacheStore,它只暴露两个接口:store(tokens, kv_cache) 和 retrieve(tokens)。

class KVCacheStore:
	def store(self, tokens, kv_cache_tensors):
		pass
	def retrieve(self, tokens):
		return kv_cache_tokens

普通 key-value store 的语义是:key 完全相等才返回 value。但 LLM 前缀复用需要更细的语义。

假设系统已经缓存了 prompt ABCDE 对应的 KV Cache,当新请求是 ABCDF 时,前四个 token ABCD 的 KV Cache 仍然可复用。

Prefix Caching 的前缀复用示意

从算法角度看,最长前缀匹配可以用 Trie 做。但讲者强调,工程实现不一定要直接维护复杂 Trie。vLLM serving 通常会把 token 序列切成固定大小的 block,再对每个 block 计算 prefix-aware hash

Chunk Hash

用块级哈希实现前缀索引。

如果逐 token 建索引,元数据数量和查找开销会变大;如果把整个 prompt 建成一个 key,又无法做前缀复用。vLLM 采用折中方式:把 token 序列切成固定大小 block,然后按 block 计算前缀相关的 hash。

Prefix caching 的目标是:当多个请求共享相同 prompt prefix 时,复用已经计算好的 KV cache,减少 prefill 开销。

在 serving engine 内部,prefix cache 通常和 KV cache 的 block/page 管理绑定。例如 vLLM 的 PagedAttention 会把 KV cache 拆成固定大小的 block;block size 可以理解为一种 chunk size。SGLang 则使用 RadixAttention,通过 radix tree 管理 token prefix 和 KV cache 的复用。

如果把 KV cache 放到 Redis / CPU / Disk 这种外部存储中,一般不需要每个路由节点都维护完整 trie。更常见的方式是把 prompt tokens 按 chunk size 切分,对每个 chunk 计算 hash,然后用 hash 映射到对应的 KV cache object。新请求到来时,系统按 chunk 顺序查询 Redis,找到最长连续命中的 prefix,然后只对 miss 的后缀重新 prefill。

chunk size 是一个 tradeoff:

chunk size 小:

chunk size 大:

因此 chunk size 的优化不能只看 hit rate,而要综合看。本质目标是最大化:节省的 prefill compute - 额外的 cache lookup / transfer / management overhead


Share this post:

Previous Post
如何写论文