☰
vLLM显存管理实战:CUDA OOM排查与KV Cache调优
2026/10/3 11:21:14 网站建设 项目流程

用vLLM部署大模型遇到CUDA out of memory,基本是每个入坑的人都绕不过去的一道坎。我印象很深,第一次在A100上跑一个7B模型,启动参数照着网上的示例抄,结果请求一进来直接OOM,我当时第一反应是“这卡是不是假的”。后来认真看了显存日志、翻了一遍vLLM的KV Cache分配机制,才算彻底搞明白这个框架的“性格”:它跟普通PyTorch程序不一样,不是用到多少显存才占多少,而是启动时就把显存预算几乎全包圆了。如果你恰好也在这个问题上耗过半天,或者正在怀疑“为什么同样的模型别人能跑我不能”,这篇文章应该能直接帮到你。

这篇文章会从vLLM的显存管理原理、OOM报错怎么定位、参数怎么调、多卡与多模型部署怎么做几个角度,把问题讲透,并附上可以直接抄的配置方案。适合刚用vLLM上线推理服务、以及准备做单机多卡部署但还没完全理清资源规划的开发者。

1. 先搞清楚vLLM为什么一上来就“吃满”显存

1.1 它的默认行为:KV Cache 预分配

vLLM和普通推理脚本最大的区别在于,它默认会把显卡显存的90%都“圈”进自己的池子,然后在这个池子里做精细管理。这90%里不仅包括模型权重,还包括给KV Cache预留的大头空间。为什么要这么做?因为vLLM的核心卖点是PagedAttention,它借鉴了操作系统虚拟内存的分页思想,把KV Cache切成固定大小的物理块,按需分配、按页管理。这样多个请求并发时,显存不用为每一个请求单独预留完整空间,而是动态共享,大幅提高吞吐。

但代价就是:框架启动时会预先申请一大块显存作为可用池,如果模型权重较大、并发参数又拉得比较高,预留的这部分就可能直接撑爆物理显存,报出torch.OutOfMemoryError。很多人在这一步就开始慌,以为是模型太大,其实大多数情况只是“预算超支”。

1.2 显存预算到底花在哪

我们把一次vLLM推理过程中显存的去向拆开看,基本是这几块:

  • 模型权重:加载进来的参数,FP16格式下每10亿参数约占2GB。
  • KV Cache:推理过程中保存的历史token注意力缓存,跟层数、KV heads数量、序列长度、并发数直接相关。
  • 激活值:前向计算中产生的中间张量,大batch、长序列时会显著增加。
  • CUDA context与PyTorch预留缓存:框架自身占用的基础空间,一般百MB到几个GB不等。

vLLM启动时那行日志会打印类似GPU KV cache size: 55.61 GB这样的信息,它指的就是KV Cache部分。你把这个值和模型权重加起来,基本就是这张卡被框架“锁定”的总量。要是申请量已经大于显存总量,启动阶段就会OOM;要是启动阶段正常、请求时OOM,往往是运行期KV Cache扩展到了峰值,加上其他临时张量一起越过了物理上限。

理解了这套预算逻辑,后续优化方向就很明确了:要么缩模型权重,要么缩KV Cache这块大头,要么换更大显存或者多卡分摊。

2. 看到CUDA OOM,先定位是哪种“爆法”

2.1 怎么读vLLM和PyTorch的报错信息

CUDA OOM的报错信息分两种常见形态,处理思路完全不同。

第一种是纯vLLM层面的报错,常出现在启动或调用接口时,文本类似“not enough memory”或“Unable to allocate KV cache”,这种基本是启动参数给定的模型权重加KV Cache预算已经超过显存总量,需要直接改参数再重启。

第二种是PyTorch抛出的torch.OutOfMemoryError: CUDA out of memory. Tried to allocate ...。注意这串文本里有个关键点:它后面通常会跟“already allocated”和“reserved in total by PyTorch”。allocated是指当前真正被张量占用的量,reserved是PyTorch显存池里预留的量,free则是物理剩余但未被缓存池释放的量。vLLM在这种情况下的分配往往是一口气要几GB到几十GB,一旦free部分不够,就直接崩。

如果你把日志打出来,看到“Tried to allocate 30.00 GiB”这种,说明有一个巨大的连续显存请求无法满足。这种大请求多数出现在prefill阶段(处理用户输入长文本时),因为要一次性计算整段prompt的KV Cache并写入显存。遇到这个情况,降低max-model-len或者限制单请求长度是最直接的解法。

2.2 用nvidia-smi看现场

我每次排查OOM,第一步永远是开另一个终端跑watch -n 0.5 nvidia-smi,看显存变化曲线:

  • 如果启动阶段显存就顶着上限、进程直接被kill,说明权重加KV Cache预算超了,得调小gpu-memory-utilization。
  • 如果启动时显存平稳,一打并发请求显存唰一下上去然后崩掉,说明KV Cache在运行期扩张太猛,需要降低并发数或限制序列长度。
  • 如果显存没满但也报OOM,大概率是出现了少量“显存碎片”,有一块大的连续空间分配不出来。这个时候调小max-num-seqs、或者换成更新版本的vLLM(新版对碎片优化更积极),比单纯降预算更有效。

nvidia-smi只能看总量趋势,它不会告诉你是哪块占的,但结合请求压测的时机,能帮你快速缩小范围。

2.3 从启动日志里找KV Cache的实际分配量

还有一个很实用的小习惯:把vLLM启动时的完整日志保留下来。日志里会打印模型config、Maximum concurrency for ... tokens、以及KV Cache的分配大小。比如我用Qwen2.5-14B在A100上启动,日志里直接能看到类似“GPU KV cache size: 45.00 GiB”和“max_num_seqs”的相关计算值。

根据这个值,你可以倒推当前配置下KV Cache还能支持多大并发、多长序列。比如KV Cache是45GB,单序列8K长度大约占用几个GB,那么并发数能到多少一目了然。别等到OOM了才回头看日志,启动日志本身就是最好的“显存预算表”。

3. 调参解决OOM的完整实操

3.1 先说结论:常用的参数组合表

如果你现在急着上线,不想深究原理,下面这张表可以直接作为调整起点。注意,这只是“能跑”的起点,不是最优配置,实际要按模型规格和业务并发来微调。

参数作用推荐值(单卡80GB,14B模型)推荐值(单卡80GB,70B量化模型)
--gpu-memory-utilization框架可使用的显存比例0.85 ~ 0.900.88 ~ 0.92
--max-model-len最长序列长度4096 ~ 81922048 ~ 4096
--max-num-seqs最大并发序列数32 ~ 6416 ~ 32
--dtype权重精度bfloat16float16或量化权重
--quantization量化方式有需要才引awq / gptq
--swap-spaceCPU换页空间(GB)4 ~ 88 ~ 16
--cpu-offload-gb把部分层权重放内存0(默认)视内存余量调整

这张表的核心逻辑是:显存总量一定,模型权重大小一定,剩下的空间就用来养KV Cache。你要么降权重,要么降并发,要么降长度,总要有一边先妥协。

3.2 gpu-memory-utilization怎么算、怎么调

gpu-memory-utilization是vLLM最有存在感的参数,默认值是0.90。它表示vLLM最多可以占用显卡显存的90%。为什么不是100%?因为CUDA context本身要占空间,激活值和临时buffer也要空间,全占满会导致运行时崩溃。

合理范围一般在0.80到0.95之间,不建议低于0.75,否则KV Cache太小,并发吞吐损失非常明显。举个例子,8GB的消费级显卡跑7B模型,如果不量化,权重就要占据差不多14GB,直接放不下;就算用INT4量化权重压到4GB左右,留给KV Cache的预算也就1~2GB,这种情况下建议把gpu-memory-utilization设在0.90,同时把max-model-len压到1024~2048,才能勉强稳住。

反过来,在80GB的A100/H100上跑14B模型,FP16权重约28GB,设置0.90时有72GB可用,KV Cache能拿接近44GB,跑8K上下文、64并发都还很宽裕。这里给一个估算公式:可用KV Cache ≈ 显存总量 × 0.90 − 模型权重大小 − 预留buffer(约2~4GB)。如果算出来是负数,说明权重太大,要不换更大的卡,要不量化,要不走多卡。

3.3 max-model-len和max-num-seqs是OOM重灾区

这两个参数直接影响KV Cache的峰值占总空间,很多人容易忽视。

单序列KV Cache大小可以用这个公式粗算:

KV Cache(bytes) ≈ 2 × num_layers × num_kv_heads × head_dim × seq_len × dtype_bytes

拿Qwen2.5-14B为例,config里num_hidden_layers=48,num_key_value_heads=8,head_dim=128,FP16下dtype_bytes=2,那么在seq_len=8192时:

2 × 48 × 8 × 128 × 8192 × 2 ≈ 1.61 GB

看起来一个序列8K才1.6GB,不算大。但如果max-num-seqs设为256,那就是256个请求同时跑,KV Cache峰值直接到400多GB,一张80GB的卡根本不可能扛住。所以OOM很多时候不是模型放不下,是并发请求把KV Cache撑爆了。

实际操作时,建议先把max-num-seqs降下来看效果。比如从默认256降到64,KV Cache峰值就压掉四分之三。如果业务确实需要高并发,优先考虑多卡部署,不要硬塞单卡。

3.4 量化与dtype:换显存的另一条路

当模型权重占用太高、把KV Cache挤得没地方时,量化是最有效的“拆东墙补西墙”手段。

vLLM对AWQ、GPTQ、FP8等量化格式支持比较成熟,用INT4量化后,7B模型权重大概从14GB降到4GB左右,省下来的10GB可以全部投给KV Cache,等价于把并发能力翻倍。还有一个容易被忽略的选项是FP8 KV Cache,也就是把KV Cache本身压缩成8位浮点,显存占用直接减半。vLLM里加一个--kv-cache-dtype fp8就能启用,效果比较明显,但要留意模型和显卡的兼容性,H100、L40S这类卡支持较好。

如果什么都不想动,只想稳妥,那把--dtype从float16换成bfloat16也能稍微缓解一点。大部分新卡对BF16支持更好,但显存占用跟FP16基本没差别,别指望这一项能救OOM。

3.5 swap-space、cpu-offload等兜底手段

当所有参数都压到极限还是不够,可以试试vLLM的“换页”机制。

--swap-space参数指定了多少GB的CPU内存可以拿来作为KV Cache的溢出缓冲。也就是说,当显存里的KV Cache满了,paged到内存,推理依然可以继续,只是变慢。这个方案适合显存差一点、但机器内存很充裕的场景。个人经验是:swap-space给4GB到16GB可以救急,但不要指望它扛高并发,一旦大量page fault,token生成速度会是断崖式下跌。

另一个选项是--cpu-offload-gb,把模型的部分层权重放到CPU内存,前向计算时再搬到GPU。这个方案能跑更大模型,但速度损失同样很明显。我一般只在需要验证一个模型是否能配合vLLM跑通时用,线上基本不会开。

4. 单机多卡、多模型部署的显存规划

4.1 tensor-parallel-size的作用机制

单卡显存实在不够时,正确的方向是上多卡,而不是继续压缩参数。vLLM里对应的参数是--tensor-parallel-size,它可以按张量并行方式把模型切到多张显卡上。

举个例子,70B模型FP16权重约140GB,单张80GB肯定放不下,但只要--tensor-parallel-size 2,权重会被切到两张80GB卡上,每卡70GB;再开--tensor-parallel-size 4,每卡35GB,这时候KV Cache的空间就很宽裕了。需要注意,多卡部署后,gpu-memory-utilization建议留一点余量,不要设到0.95,因为张量并行在每一层前向后向计算之间都要做allreduce通信,通信buffer和中间激活都会占显存。我实测下来,TP=2时设在0.88到0.92之间比较稳妥,TP=4时甚至可以试试0.93,但必须观察启动日志确认没有隐性OOM风险。

多卡部署还有一个好处:KV Cache也会跟着权重一起切分到多张卡上。也就是说,哪怕每张卡上KV Cache预算不变,总量是N倍,并发能力同步放大。这也是为什么对大模型推理来说,“单机多卡部署”几乎成了生产环境标配。

4.2 多模型同卡、多实例部署怎么分配

有时候一张卡或一台机器要同时服务多个模型,最常见的是两个不同尺寸的模型同时在线。这里有几种做法:

  • 多实例按卡隔离:假设有2张80GB卡,一个14B模型用卡0,另一个7B模型用卡1,互不干扰。这是最省心、最推荐的做法。
  • 单卡多实例分配:只有一张卡但想同时跑两个小模型,可以分别启动两个vLLM实例,设置不同的CUDA_VISIBLE_DEVICES和gpu-memory-utilization。比如实例A给0.55,实例B给0.33,加起来必须小于1,且要留出少量余量给CUDA context。这种做法有个坑:两个实例各自独立申请显存,如果A先启动并且申请多了,B可能直接起不来。所以分配比例要保守,千万别卡死在临界值。
  • 单实例多模型:新版vLLM支持一次启动服务多个模型,底层的显存池由引擎统一调度。这种方式对显存的利用率更高,但多模型之间的KV Cache、调度策略会更复杂,如果版本较旧或模型差异大,建议先跑通再上生产。

我在多实例方案上踩过一个坑:给实例A设了gpu-memory-utilization=0.5,实例B设了0.5,结果B启动时报OOM,因为CUDA context和框架自身占的空间没留够。后来调整为A=0.48、B=0.40,总共只用了0.88左右,才稳定运行。这就是典型的“账面预算”和“实际预算”之间的误差,多实例部署时一定要留至少10%的缓冲。

4.3 纯CPU模式和LM Studio有什么区别

很多人看到vLLM的热搜词里有“纯CPU模式”和“LM Studio bionic”,会以为它们是可以互相替代的部署方式。实际上两者定位完全不同。

vLLM CPU模式是通过--device cpu启动的,主要解决的是“没有GPU但还想验证vLLM特性”的场景,比如本地测试API接口、CI流水线跑集成测试。性能上和GPU没法比,实测Qwen2.5-7B在纯CPU上生成速度可能只有每秒几个token,也就是勉强能用的水平。而且CPU模式也有内存压力,只不过报错从CUDA OOM变成了普通的内存不足,本质逻辑一样。

LM Studio则是一个本地GUI推理工具,它会自动做CPU与GPU的混合调度,显存不够时会把层offload到内存,所以用LM Studio跑同样的模型可能会“不崩”,但速度同样很慢。vLLM的思路相反:它更激进地占用GPU显存来保吞吐,一旦预算超了就明确报错,而不是悄悄降到内存去慢慢跑。所以“LM Studio能跑但vLLM却OOM”这件事,不代表vLLM更差,而是它把资源规划的责任交到了部署者手上。你在用vLLM时,心里要有个预期:它更像一台精心调过转速的发动机,参数给对了才能发挥最大功率。

5. 高频问题排查速查表

故障现象可能原因解决思路
启动即报CUDA OOM模型权重 + KV Cache预算超过显存总量调小gpu-memory-utilization,降低max-model-len,或量化权重
启动正常,首个请求OOMPrefill阶段一次性分配KV Cache过大调低max-model-len,限制单请求最大输入长度
并发一高就OOMmax-num-seqs过大导致KV Cache峰值超限降低max-num-seqs,或换多卡部署
显存没满但OOM显存碎片化,大块连续空间不足调低max-num-seqs,升级vLLM版本,或减少实例数
多实例同时跑,第二个起不来两个实例显存预算相加挤爆给两个实例留出10%以上的缓冲空间
70B以上大模型单卡放不下权重太大使用AWQ/GPTQ量化,或开启--tensor-parallel-size多卡部署
压测时生成速度骤降但没崩触发了CPU swap,KV Cache被paged到内存增加swap-space只会更慢,建议扩容显存或降并发
日志提示CPU OOM而不是GPU OOMCPU模式或offload模式下内存不足调低max-model-len和max-num-seqs,或加大系统内存

这张表覆盖了我自己遇到过的绝大多数情况。如果你按顺序排查完仍然OOM,不妨把vLLM的启动日志和错误堆栈完整贴给模型仓库的Issue区,社区回复速度一般还行。记得贴日志时带上vLLM版本号、模型路径、显卡型号和全部启动参数,缺少这些信息,别人很难帮到你。

最后再说一个容易被忽略的细节:每次调参后不要只看进程是否起来,一定要压一压真实流量。有些配置启动时看着稳,一旦来几个长上下文请求就崩,这往往是因为KV Cache的峰值预算没算清楚。可以根据实际业务里最长的那条prompt和最大并发量,用上面的公式先粗算一遍,再决定参数怎么配。我个人的习惯是先在日志或/metrics接口确认KV Cache利用率长期在70%到90%之间,才算把显存用到位——低了说明浪费,高了说明随时有OOM风险。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询