4. vLLM内存模型:显存管理的艺术

聊到vLLM的显存管理,我得先说说自己的经历。记得第一次部署大模型时,我天真地以为只要GPU显存够大就行。结果呢?一个70B的模型,明明显存还有剩余,却总是OOM。后来我才明白——问题不在模型本身,而在KV Cache这个"隐形杀手"。

说白了,大模型推理时,每生成一个token,都要把之前所有token的Key和Value缓存下来。你想想看,生成长度为2048的序列,KV Cache占用的显存可能比模型权重还大。这就是为什么我们需要vLLM这套精妙的显存管理机制。

4.1 KV Cache:显存消耗的"大头"

先看个具体数字。假设我们用FP16精度部署一个70B模型,batch size=1,序列长度=2048:

模型权重:70B × 2 bytes = 140 GB
KV Cache:2(K和V)× 80层 × 4096(hidden_dim)× 2048(seq_len)× 2 bytes ≈ 2.68 GB

嗯,这里要注意——KV Cache是跟序列长度成正比的。如果序列长度翻倍到4096,KV Cache也跟着翻倍。我在项目中遇到过最极端的情况:一个长文档摘要任务,序列长度到了8192,KV Cache直接吃掉了20多GB显存。

核心要点:KV Cache的显存占用 = 2 × num_layers × hidden_dim × seq_len × precision_bytes

这个公式我建议你记下来,做容量规划时特别有用。

4.2 PagedAttention:操作系统的智慧

vLLM最让我佩服的设计,就是PagedAttention。说白了,它借鉴了操作系统的虚拟内存思想——把KV Cache切成固定大小的"页"(page)。

为什么会想到这个?你想想看,传统做法是给每个请求预分配一整块连续显存。但问题是,我们事先不知道请求会生成多长的序列。预分配少了会OOM,预分配多了又浪费。

PagedAttention的做法是:

  • 把KV Cache分成16KB大小的页
  • 每个页可以存放固定数量token的K和V
  • 页与页之间不需要物理连续
  • 通过页表来管理逻辑地址到物理地址的映射

我在调优时发现一个有意思的现象:当并发请求数超过32时,PagedAttention的内存利用率能到95%以上。而传统预分配方案,利用率往往不到60%。

个人经验:PagedAttention的页大小默认是16KB,但你可以根据模型调整。我习惯把页大小设为hidden_dim × 2 × 2(K和V各一个页),这样每个页刚好存一个token的KV Cache。

4.3 显存与内存的"暧昧关系"

很多人以为vLLM只用显存,其实不是。它有一套完整的显存-内存分层机制:

层级 存储内容 访问速度 容量
GPU显存 模型权重 + 活跃KV Cache ~1 TB/s 有限(80GB/卡)
CPU内存 不活跃KV Cache + 调度队列 ~50 GB/s 较大(512GB+)
磁盘(可选) 冷数据KV Cache ~500 MB/s 巨大

vLLM的调度器会动态判断:哪些KV Cache是"热的"(正在被当前请求使用),哪些是"温的"(可能被复用),哪些是"冷的"(短期内用不到)。热的留在显存,温的换到内存,冷的甚至写到磁盘。

我曾经踩过一个坑:把swap空间设得太小,结果大量请求同时进来时,vLLM在显存和内存之间频繁换入换出,性能直接掉到原来的1/3。后来我学乖了,把swap空间设为显存大小的2倍。

4.4 OOM场景:那些让我熬夜的教训

说到OOM,我得好好聊聊。vLLM虽然做了很多优化,但该崩的时候还是会崩。我总结了几种典型场景:

场景一:突发长序列

大部分请求都是短文本,突然来了个超长文档。KV Cache瞬间暴涨,显存被打满。vLLM虽然有swap机制,但swap本身也需要时间。

场景二:并发数估算失误

你以为能跑32并发,结果每个请求都生成长文本。32 × 4096长度的KV Cache,显存直接爆掉。

场景三:显存碎片化

频繁的分配和释放,导致显存碎片。虽然PagedAttention缓解了这个问题,但极端情况下还是会出现"有空间但分配不出来"的情况。

避坑指南:我曾经在生产环境遇到过最诡异的问题——显存明明还剩10GB,但vLLM就是报OOM。排查了半天,发现是显存碎片化导致的。解决方案是设置--gpu-memory-utilization 0.9,给显存留点余量。

最后,我画了张图帮你理解vLLM的显存管理全貌:

vLLM显存管理架构图 请求队列 PagedAttention调度器 页表管理 + 换入换出决策 GPU显存(热数据) 模型权重 + 活跃KV Cache 访问速度:~1 TB/s CPU内存(温数据) 不活跃KV Cache 访问速度:~50 GB/s 磁盘(冷数据) 冷KV Cache 访问速度:~500 MB/s 换入/换出 换入/换出 OOM检测与处理 处理策略 请求排队 → 显存压缩 → 换出到内存 → 拒绝请求

这张图展示了vLLM的三级存储架构。调度器是核心,它根据KV Cache的"热度"决定数据放在哪一层。OOM检测模块会持续监控显存使用率,一旦超过阈值就触发处理策略。

嗯,说到这我想起一个细节:vLLM的OOM处理策略是可以配置的。我建议你把--max-num-batched-tokens设得保守一点,比如显存能支持的理论最大值的80%。这样即使有突发流量,系统也有缓冲余地。

实用建议:部署前先用vLLM的dry_run模式跑一遍,看看不同并发数下的显存消耗。我每次上线新模型都会这么做,省得半夜被报警吵醒。


公众号:蓝海资料掘金营,微信deep3321