3. 模型缓存策略:全量缓存 vs 增量缓存、缓存预热机制、缓存淘汰策略、缓存一致性保证
聊到 vLLM 的缓存,很多人第一反应就是「把模型塞进显存就完事了」。嗯,这话对了一半。真正在生产环境里跑过的人都知道,缓存策略选不对,轻则推理延迟飙升,重则直接 OOM 把节点搞崩。我在这块踩过的坑,够写一本小册子了。
今天咱们就把缓存策略拆开揉碎了讲。说白了,就是四个核心问题:存什么、怎么存、满了怎么办、多副本怎么保持一致。
3.1 全量缓存 vs 增量缓存
先看第一个选择题。你部署一个 70B 的模型,显存 80GB,模型权重就占了 60GB。这时候你面临两个选择:
- 全量缓存:把整个模型的所有层、所有参数都加载到显存里。好处是推理时不用从磁盘读,延迟最低。坏处是显存占用巨大,一个节点只能放一个模型副本。
- 增量缓存:只加载模型的一部分,比如只加载前几层,或者只加载当前请求需要的部分。其他部分按需从磁盘或远端加载。
我个人习惯在 延迟敏感型场景 用全量缓存。比如在线对话服务,用户等不了 500ms 以上的延迟。这时候多花点显存是值得的。
但在 吞吐优先型场景,比如离线批量推理,我会倾向增量缓存。我记得有一次给客户做视频理解模型的批量推理,模型 40GB,显存只有 32GB。全量缓存根本放不下。后来改成增量缓存,每次只加载推理需要的 4 层,配合流水线并行,吞吐量反而比单节点全量缓存高了 3 倍。
3.2 缓存预热机制
缓存预热,说白了就是「别等用户来了才加载模型」。我见过太多团队,模型部署完直接对外提供服务,结果第一个请求等了 30 秒才返回。用户早就跑了。
预热的核心思路就两个:
- 预加载权重:在 Pod 启动时,把模型权重从对象存储(比如 S3、MinIO)拉到本地,再加载到显存。这一步可以用 Init Container 来做。
- 预填充 KV Cache:对于 vLLM 这种基于 PagedAttention 的框架,KV Cache 的预热也很关键。我建议在服务真正上线前,先发一批「虚拟请求」,把 KV Cache 填到一定水位。
这里给个我常用的预热脚本片段:
# 预热脚本示例
apiVersion: batch/v1
kind: Job
metadata:
name: vllm-warmup
spec:
template:
spec:
containers:
- name: warmup
image: your-registry/vllm-warmup:latest
env:
- name: MODEL_PATH
value: "s3://models/llama-70b/"
- name: WARMUP_REQUESTS
value: "100"
command:
- /bin/sh
- -c
- |
# 1. 下载模型权重
aws s3 cp $MODEL_PATH /models/ --recursive
# 2. 启动 vLLM 并发送预热请求
python warmup.py --model /models --num-requests $WARMUP_REQUESTS
3.3 缓存淘汰策略
显存就那么大,总有放不下的时候。这时候就得决定:把谁踢出去?
vLLM 默认用的是 LRU(最近最少使用) 策略。但我在项目中遇到过一个问题:LRU 对长序列很不友好。你想想看,一个 4096 tokens 的长请求,它的 KV Cache 占了大量显存。如果后面来了很多短请求,这个长请求的 Cache 很快就会被淘汰。但下次再遇到长请求,又得重新计算,延迟直接爆炸。
所以我一般会建议做 分层淘汰:
| 层级 | 策略 | 适用场景 |
|---|---|---|
| L1(热数据) | LRU | 短请求、高频访问的 Cache |
| L2(温数据) | LFU(最不经常使用) | 长请求、低频但重要的 Cache |
| L3(冷数据) | FIFO(先进先出) | 一次性请求、测试流量 |
具体实现上,可以在 vLLM 的 CacheEngine 里加一个 eviction_policy 参数。嗯,这里要注意,改源码前一定要做压力测试。我曾经改完淘汰策略,结果在高并发下出现了死锁,排查了一整天才找到问题。
3.4 缓存一致性保证
最后这个点,很多人会忽略。当你用多个 Pod 部署同一个模型时,每个 Pod 都有自己的本地缓存。问题来了:如果模型权重更新了,怎么保证所有 Pod 的缓存都同步更新?
我见过最粗暴的做法:直接重启所有 Pod。嗯,这确实能保证一致性,但代价是服务中断几十秒甚至几分钟。
更好的做法是 版本化缓存:
- 每个模型权重都有一个版本号(比如用 Git commit hash 或时间戳)。
- Pod 启动时,从配置中心(比如 etcd 或 Consul)拉取当前最新的版本号。
- 如果本地缓存的版本号与最新版本号不一致,就触发缓存刷新。
- 刷新时,采用 蓝绿部署 的方式:先启动新版本的 Pod,等新 Pod 就绪后再下线旧 Pod。
这里有个关键点:缓存刷新期间,请求不能断。我建议用 preStop 钩子来做优雅退出:
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 通知负载均衡器,本 Pod 不再接收新请求
curl -X POST http://localhost:8080/drain
# 等待正在处理的请求完成(最多等 30 秒)
sleep 30
这样旧 Pod 会等所有正在推理的请求处理完再退出,新 Pod 已经预热好了,用户完全感知不到缓存切换。
知识体系总览
下面这张图把缓存策略的核心逻辑串起来了。你可以把它当作一个决策树来用:
这张图从左到右展示了缓存策略的完整链路。你从根节点出发,根据业务场景选择全量还是增量,然后依次考虑预热、淘汰和一致性。每个环节都有对应的技术方案。
好了,关于缓存策略的核心内容就这些。记住一句话:没有银弹,只有 trade-off。你在实际项目中遇到的具体问题,欢迎随时交流。