假設兩條 requests 是:
Request A = [system prompt][shared document][question A]
Request B = [system prompt][shared document][question B]
它們前面的 token IDs 完全相同。A 做完 Prefill 後,Attention 各層已經為這段 prefix 產生 K、V tensors。
B 不必再次對 shared prefix 執行 model forward:
載入 shared prefix 的 KV blocks
→ 只計算 question B
→ 開始產生第一個 output token
所以 Prefix Caching 省的是 Prefill compute,不是 tokenization,也不會讓後面的 autoregressive Decode 消失。
它也不是 semantic cache。「幫我摘要」和「請做摘要」意思接近,但 token IDs 不同,不能直接共用 KV。
假設 block size 是 4 tokens。四條 requests 都有:
16-token 相同 prefix + 4-token 不同問題 = 20-token prompt
| 情境 | 第 1 條 | 後 3 條 | 總 Prefill token work |
|---|---|---|---|
| 無 reuse | 20 | 3 × 20 | 80 |
| 有 Prefix Cache | 20 | 3 × 4 | 32 |
這個例子省下 80 - 32 = 48 次 prompt-token computation,也就是 60%。
這只是 token work 手算,不代表 TTFT 一定降低 60%。真實時間還包含 queueing、Scheduler、hash lookup、剩餘 suffix、GPU kernel 與 sampling。
vLLM 把 KV Cache 分成固定大小的 blocks。每個可快取 block 的 hash 由三類資料構成:
parent block hash
+ 這個 block 的 token IDs
+ LoRA / multimodal input / cache salt 等額外資訊
parent hash 很重要。即使某個 block 裡的 tokens 相同,只要前面的 context 不同,它的 KV 也不同,不能誤用。
查詢時會從第一個 block 開始,找出最長的連續命中 prefix;第一個 miss 之後就停止。
而且目前只快取 完整 blocks。如果 block size 是 4,兩條 prompts 前 10 個 tokens 相同:
可命中:前 8 tokens
仍要計算:第 9、10 token 與後續 suffix
因此「共同 prefix 有多長」和「實際 cache hit tokens」不一定相同。
一條新 request 進入 Scheduler 後,主要路徑可以簡化成:
1. 對 prompt blocks 建立 hash chain
2. KVCacheManager.get_computed_blocks()
3. 找出最長的 full-block cache hit
4. 將命中的 tokens 記為已計算
5. allocate_slots() 引用 cached blocks,並替 suffix 配置新 blocks
6. Model Runner 只計算尚未完成的 tokens
Request 完成後,block 的 reference count 會下降。沒有 request 正在使用的 block 仍可留在 cache 中等待下一次 reuse;當 KV 空間不足,它才依 LRU free queue 被逐出。
還有一個細節:如果整條 prompt 都命中,模型仍需要最後一個位置的 logits 才能開始 sampling。因此目前實作至少會重算最後一個 token;受 block alignment 限制時,可能重算最後一個完整 block。
| Workload | Prefix reuse |
|---|---|
| 固定 system prompt 的聊天服務 | 高 |
| 同一份長文件的多輪問答 | 高 |
| 固定 few-shot examples、只更換問題 | 高 |
| 每次 prompt 都完全不同 | 低 |
| 相同文字但 chat template、tokenizer 或 LoRA 不同 | 不應直接共用 |
即使 workload 很適合,也可能因 KV Cache 壓力而 miss:cached blocks 不是永久保存,新的 requests 仍會把舊 blocks 淘汰。
另外,要求 prompt logprobs 的 request 在目前 V1 實作中會跳過本地 Prefix Cache lookup,因為它需要重新計算 prompt 才能取得那些 logprobs。
Cache hit 可能反映在 latency 上。如果不同使用者能任意共享同一個 prefix cache,攻擊者可能用時間差推測某段內容是否曾被處理。
vLLM 支援把 cache_salt 放進第一個 block 的 hash。只有使用相同 salt 的 requests 才能共享 cached blocks,用來隔離不同 trust groups。