2、显存溢出(OOM)原理:GPU显存管理机制、vLLM的显存分配策略、OOM发生的根本原因
聊到显存溢出,也就是OOM,很多同学第一反应就是“显存不够了呗”。嗯,这话没错,但不够深。我见过不少团队,明明显存够大,模型也能装下,可一跑推理就崩。为什么?
说白了,OOM不只是容量问题,更是管理问题。你想想看,GPU显存就像一间仓库,vLLM是仓库管理员。仓库够大,但管理员乱堆乱放,照样会爆仓。今天我就带你从底层拆解,看看OOM到底是怎么发生的。
核心观点:OOM的本质是“分配请求超过了可用显存”,但触发它的往往是碎片化、预分配策略不当、或者缓存管理失效。
2.1 GPU显存管理机制:硬件层面的“内存条”
先看硬件。GPU显存,本质上就是一块高带宽的DRAM。NVIDIA的卡,从T4到A100再到H100,显存大小从16GB到80GB不等。但管理方式,和CPU内存不太一样。
GPU显存管理有几个关键点:
- 线性分配 vs 池化分配:早期CUDA程序喜欢一次性申请一大块连续显存。但vLLM这类推理框架,用的是池化分配——提前划好一块“显存池”,按需从池子里拿。
- 显存碎片化:这是个大坑。频繁申请和释放不同大小的张量,会导致显存出现“蜂窝煤”一样的空洞。我遇到过一台8卡A100机器,明明每卡还有10GB空闲,但一申请个8GB的KV Cache就报OOM。查了半天,就是碎片化搞的鬼。
- CUDA上下文开销:每个进程加载CUDA时,会占用几百MB显存。多进程部署时,这个开销会叠加。
我的经验:我曾经在调优一个70B模型时,发现显存占用比理论值高了15%。排查后发现是PyTorch的缓存分配器没释放干净。加一行 torch.cuda.empty_cache() 就解决了。但注意,这招不能频繁用,否则会降低性能。
2.2 vLLM的显存分配策略:KV Cache是主角
vLLM之所以快,核心在于它把KV Cache管理做到了极致。但这也成了OOM的重灾区。
vLLM的显存分配,大致分三块:
- 模型权重:固定大小,加载后就占住了。
- KV Cache:动态分配,按请求的序列长度和batch大小变化。这是OOM的“头号嫌疑人”。
- 临时张量:前向传播过程中的中间结果,比如attention score。
vLLM用了一个叫 PagedAttention 的技术。它把KV Cache切成固定大小的“页”(page),按需分配。这有点像操作系统的虚拟内存——你不需要一次性把所有页都加载到物理显存里。
但问题来了:
- 预分配上限:vLLM启动时会通过
--max-model-len和--gpu-memory-utilization参数,算出一个“最大KV Cache容量”。如果这个上限设得太高,模型权重+KV Cache总和超过显存,直接启动失败。 - 动态增长:推理过程中,如果请求的序列特别长(比如生成2048个token),KV Cache会持续增长。一旦超过预分配上限,就OOM了。
避坑指南:我曾经遇到一个案例,用户把 --gpu-memory-utilization 设成了0.95,以为能榨干显存。结果模型加载完还剩5%,但KV Cache一增长就崩。后来我建议降到0.85,留出余量,问题解决。记住,显存利用率不是越高越好。
2.3 OOM发生的根本原因:三个“杀手”
结合上面的分析,我把OOM的根本原因归纳为三类。你可以对照排查:
| 杀手类型 | 触发场景 | 典型表现 |
|---|---|---|
| 容量超限 | 模型太大,或batch size/序列长度设置过高 | 启动时就报OOM,或跑几个请求后崩 |
| 碎片化 | 频繁变长请求,显存被切成小块 | 显存总量够,但申请连续块失败 |
| 缓存泄漏 | vLLM的KV Cache页回收不及时 | 长时间运行后,显存占用缓慢增长直到OOM |
举个例子。你部署了一个13B模型,显存24GB。模型权重占13GB,你设了 --max-model-len 4096,vLLM算出来KV Cache需要8GB。加起来21GB,看起来够。但实际跑起来,如果并发请求多,每个请求的序列长度不一样,KV Cache的页分配会产生碎片。跑着跑着,突然有个请求需要连续4GB的页块,但显存里全是1GB的小碎片——OOM就来了。
根本原因一句话:OOM不是显存“绝对不够”,而是“分配策略”和“实际负载”不匹配导致的。
2.4 一张图看懂显存分配与OOM
下面我用一张SVG图,把整个流程串起来。从模型加载到KV Cache分配,再到OOM触发点,一目了然。
从图里能看出来,OOM有两个触发点:一是启动时总和超限,直接报错;二是运行时KV Cache动态增长或碎片化,导致分配失败。后者更难排查,因为它是“跑着跑着突然崩”。
我的建议:遇到OOM,先看启动日志。vLLM会打印显存分配明细,比如“模型权重占用X GB,KV Cache预分配Y GB”。如果X+Y接近显存上限,那就调低 --gpu-memory-utilization。如果启动正常但运行中崩,重点检查碎片化——可以试试 --max-num-seqs 限制并发数,减少变长请求的冲击。
好了,这一章我们拆了显存管理、vLLM策略和OOM根因。下一章,我会带你实战排查一个典型的K8s环境下的OOM报错,看看日志里到底藏着什么线索。