3. 模型缓存策略:全量缓存 vs 增量缓存、缓存预热机制、缓存淘汰策略、缓存一致性保证

聊到 vLLM 的缓存,很多人第一反应就是「把模型塞进显存就完事了」。嗯,这话对了一半。真正在生产环境里跑过的人都知道,缓存策略选不对,轻则推理延迟飙升,重则直接 OOM 把节点搞崩。我在这块踩过的坑,够写一本小册子了。

今天咱们就把缓存策略拆开揉碎了讲。说白了,就是四个核心问题:存什么、怎么存、满了怎么办、多副本怎么保持一致

3.1 全量缓存 vs 增量缓存

先看第一个选择题。你部署一个 70B 的模型,显存 80GB,模型权重就占了 60GB。这时候你面临两个选择:

  • 全量缓存:把整个模型的所有层、所有参数都加载到显存里。好处是推理时不用从磁盘读,延迟最低。坏处是显存占用巨大,一个节点只能放一个模型副本。
  • 增量缓存:只加载模型的一部分,比如只加载前几层,或者只加载当前请求需要的部分。其他部分按需从磁盘或远端加载。

我个人习惯在 延迟敏感型场景 用全量缓存。比如在线对话服务,用户等不了 500ms 以上的延迟。这时候多花点显存是值得的。

但在 吞吐优先型场景,比如离线批量推理,我会倾向增量缓存。我记得有一次给客户做视频理解模型的批量推理,模型 40GB,显存只有 32GB。全量缓存根本放不下。后来改成增量缓存,每次只加载推理需要的 4 层,配合流水线并行,吞吐量反而比单节点全量缓存高了 3 倍。

核心结论:全量缓存保延迟,增量缓存保吞吐。没有绝对的好坏,看你的业务指标是什么。

3.2 缓存预热机制

缓存预热,说白了就是「别等用户来了才加载模型」。我见过太多团队,模型部署完直接对外提供服务,结果第一个请求等了 30 秒才返回。用户早就跑了。

预热的核心思路就两个:

  1. 预加载权重:在 Pod 启动时,把模型权重从对象存储(比如 S3、MinIO)拉到本地,再加载到显存。这一步可以用 Init Container 来做。
  2. 预填充 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
小技巧:预热请求的 prompt 长度最好覆盖你的业务分布。比如你的业务平均输入 512 tokens,那就用 512 tokens 的 prompt 做预热。这样 KV Cache 的分配更贴近真实情况。

3.3 缓存淘汰策略

显存就那么大,总有放不下的时候。这时候就得决定:把谁踢出去?

vLLM 默认用的是 LRU(最近最少使用) 策略。但我在项目中遇到过一个问题:LRU 对长序列很不友好。你想想看,一个 4096 tokens 的长请求,它的 KV Cache 占了大量显存。如果后面来了很多短请求,这个长请求的 Cache 很快就会被淘汰。但下次再遇到长请求,又得重新计算,延迟直接爆炸。

所以我一般会建议做 分层淘汰

层级 策略 适用场景
L1(热数据) LRU 短请求、高频访问的 Cache
L2(温数据) LFU(最不经常使用) 长请求、低频但重要的 Cache
L3(冷数据) FIFO(先进先出) 一次性请求、测试流量

具体实现上,可以在 vLLM 的 CacheEngine 里加一个 eviction_policy 参数。嗯,这里要注意,改源码前一定要做压力测试。我曾经改完淘汰策略,结果在高并发下出现了死锁,排查了一整天才找到问题。

避坑指南:千万不要用「随机淘汰」。我曾经在一个项目里图省事用了随机策略,结果每次推理的延迟抖动超过 200%。用户投诉电话直接打到了 CEO 那里。

3.4 缓存一致性保证

最后这个点,很多人会忽略。当你用多个 Pod 部署同一个模型时,每个 Pod 都有自己的本地缓存。问题来了:如果模型权重更新了,怎么保证所有 Pod 的缓存都同步更新?

我见过最粗暴的做法:直接重启所有 Pod。嗯,这确实能保证一致性,但代价是服务中断几十秒甚至几分钟。

更好的做法是 版本化缓存

  1. 每个模型权重都有一个版本号(比如用 Git commit hash 或时间戳)。
  2. Pod 启动时,从配置中心(比如 etcd 或 Consul)拉取当前最新的版本号。
  3. 如果本地缓存的版本号与最新版本号不一致,就触发缓存刷新。
  4. 刷新时,采用 蓝绿部署 的方式:先启动新版本的 Pod,等新 Pod 就绪后再下线旧 Pod。

这里有个关键点:缓存刷新期间,请求不能断。我建议用 preStop 钩子来做优雅退出:

lifecycle:
  preStop:
    exec:
      command:
      - /bin/sh
      - -c
      - |
        # 通知负载均衡器,本 Pod 不再接收新请求
        curl -X POST http://localhost:8080/drain
        # 等待正在处理的请求完成(最多等 30 秒)
        sleep 30

这样旧 Pod 会等所有正在推理的请求处理完再退出,新 Pod 已经预热好了,用户完全感知不到缓存切换。

一句话总结:缓存一致性不是靠「强同步」实现的,而是靠「优雅切换」和「版本管理」来保证的。

知识体系总览

下面这张图把缓存策略的核心逻辑串起来了。你可以把它当作一个决策树来用:

vLLM 模型缓存策略决策树 缓存策略选择 全量缓存 增量缓存 延迟敏感场景(在线服务) 需要预热:预加载 + 预填充 吞吐优先场景(批量推理) 按需加载,配合流水线并行 淘汰策略:分层淘汰(L1/L2/L3) 一致性保证:版本化 + 蓝绿部署

这张图从左到右展示了缓存策略的完整链路。你从根节点出发,根据业务场景选择全量还是增量,然后依次考虑预热、淘汰和一致性。每个环节都有对应的技术方案。

好了,关于缓存策略的核心内容就这些。记住一句话:没有银弹,只有 trade-off。你在实际项目中遇到的具体问题,欢迎随时交流。

专注资料整理