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的三级存储架构。调度器是核心,它根据KV Cache的"热度"决定数据放在哪一层。OOM检测模块会持续监控显存使用率,一旦超过阈值就触发处理策略。
嗯,说到这我想起一个细节:vLLM的OOM处理策略是可以配置的。我建议你把--max-num-batched-tokens设得保守一点,比如显存能支持的理论最大值的80%。这样即使有突发流量,系统也有缓冲余地。
实用建议:部署前先用vLLM的dry_run模式跑一遍,看看不同并发数下的显存消耗。我每次上线新模型都会这么做,省得半夜被报警吵醒。
公众号:蓝海资料掘金营,微信deep3321