本节导读:在很多真实业务里,请求之间共享大量相同前缀——系统提示词、few-shot示例、RAG模板。Prefix Caching让这些重复计算一次、多次复用,是vLLM中"零成本换性能"的最大杠杆。本节讲透其原理、启用方法与命中率优化。
LLM生成第一个token前,必须对整个输入做一次prefill计算。如果1000个请求都带同一段2000 token的系统提示词, naive做法下这2000 token的计算要重复1000次。Prefix Caching把已计算的KV结果按块缓存,后续请求命中相同前缀就直接跳过计算——TTFT可从秒级降到毫秒级。
vLLM以block(默认16 token)为单位组织KV Cache。每个block的缓存键由前缀哈希链构成:hash(block N) = hash(hash(block N-1) + 本块token)。这意味着:
| 场景 | 共享前缀来源 | 典型命中效果 |
|---|---|---|
| 多轮对话 | 历史消息逐轮累积 | 极高,每轮只需算新增部分 |
| RAG问答 | 统一模板+few-shot | 高,仅检索内容不同 |
| 批量分类/抽取 | 指令+示例固定 | 高 |
| 通用聊天(开放话题) | 仅chat模板头 | 低,几十字命中 |
判断标准:把请求prompt的前缀两两对比,重合度越高收益越大。
vllm serve Qwen/Qwen2.5-7B-Instruct \ --enable-prefix-caching \ --max-model-len 8192
(vLLM 0.9+对部分配置默认开启;显式加上可确保行为一致。)注意:开启后 max_model_len 越大、KV池越大,可缓存的前缀块越多,命中率越高。
curl -s http://localhost:8000/metrics | grep prefix_cache # gpu_prefix_cache_queries_total / gpu_prefix_cache_hits_total
用两个总量计算命中率:
hit_rate = gpu_prefix_cache_hits_total / gpu_prefix_cache_queries_total
健康的多轮对话服务命中率通常在60%~95%;低于30%说明前缀共享结构有问题,进入步骤3。
反面示例(缓存杀手)——把变化内容放在前面:
# 每个请求的doc_id都不同 → 整个前缀永远无法命中 prompt = f"文档{doc_id}内容如下:{retrieved_text}\n请回答:{question}\n{SYSTEM_PROMPT}"
正面示例——固定部分前置、变量后置:
prompt = ( f"{SYSTEM_PROMPT}\n" # 固定:角色定义、输出格式 f"{FEW_SHOT_EXAMPLES}\n" # 固定:few-shot示例 f"参考资料:\n{retrieved_text}\n" # 变化:检索内容 f"问题:{question}" # 变化:用户输入 )
调整后,系统提示+few-shot这段最长的前缀在全量请求间共享,命中率立刻上来。
多副本部署时,同一会话的不同轮次要打到同一实例(实例间不共享缓存):
upstream vllm_pool { hash $arg_session_id consistent; server 10.0.0.1:8000; server 10.0.0.2:8000; }
客户端在请求中携带会话ID即可。
发布/重启后缓存为空,首波请求TTFT会偏高。上线后先主动灌一轮典型请求:
import requests for prompt in WARMUP_PROMPTS: # 覆盖各业务线的标准模板 requests.post("http://localhost:8000/v1/chat/completions", json={"model": "...", "messages": prompt, "max_tokens": 1})
max_tokens=1 只算prefill,恰好把KV Cache填好,成本极低。
改造前:模板内嵌时间戳与随机ID,命中率8%,P99 TTFT 2.1s。
改造动作:①剥离时间戳/ID到响应侧;②固定部分前置;③LB加会话哈希;④上线预热。
改造后:命中率79%,P99 TTFT降至0.4s,GPU计算量下降约35%——没有换任何硬件。
Q1:前缀缓存会带来精度问题吗?
不会。命中的KV与重新计算的结果在数值上等价(同一权重同一输入),只是省掉了重复计算,生成内容不受影响。
Q2:缓存占满显存会怎样?
缓存块与运行块共用KV池,LRU自动淘汰冷前缀,无需干预。真正的风险是 max_num_seqs 过大导致运行块挤占缓存块、命中率下滑,此时应参考3.1节收缩并发参数。
Q3:为什么我的多轮对话命中率不高?
逐轮打印prompt做diff排查。常见元凶:①每轮重写的系统提示词(如附带当前时间);②客户端把历史消息顺序打乱或增量拼接时引入空白差异;③会话被LB分发到不同副本。逐一消除后命中率会阶梯式上升。
Q4:KV Cache量化(fp8)和前缀缓存冲突吗?
不冲突,可以叠加:--kv-cache-dtype fp8 把缓存体积压半,等效扩大了可缓存前缀量。代价是长上下文任务可能有轻微精度损失,建议先用业务评估集验证输出质量再上线。
**Q5:CPU offloading值得开吗? --swap-space` 能把被抢占序列的KV换到CPU内存,仅在频繁preemption时有意义。它救不了吞吐(换页很慢),定位是"防雪崩保险丝",不能当作常规扩容手段。
Prefix Caching的收益来自负载中的前缀冗余,工程上要做对三件事:启用缓存、把prompt重组为"固定前缀+动态后缀"、多副本下做会话亲和。配合命中率监控,它通常是无损优化中ROI最高的一项。下一节进入精度与成本的平衡术——量化推理优化。