news 2026/11/2 12:39:48

显存显示有余量却报CUDA OOM?GPU服务器显存排查全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
显存显示有余量却报CUDA OOM?GPU服务器显存排查全指南

租GPU服务器跑模型,我隔三差五就会收到几乎一模一样的求助:“日志里报CUDA OOM,可nvidia-smi看显存明明还剩二十多G,这机器是不是被坑了?”

这句话本身不算错,但“显存有余量”和“这段显存能用”完全是两码事。nvidia-smi显示的是物理显存的当前占用情况,而CUDA分配器在申请内存时,看的是能不能拿到一段连续、可用、且符合当前上下文的显存块。这篇文章不写空理论,就按我在租用GPU服务器、低显存跑模型时踩过的真实场景来讲:那些“余量”到底被谁偷走了,为什么2MB都分配不出来,以及遇到OOM时应该按什么顺序排查。

1. nvidia-smi 显示“显存够用”,和 CUDA 能分到显存,是两码事

1.1 你在nvidia-smi里看到的“空闲”,不一定是你能用的空闲

先做一个最基础的区分。nvidia-smi里每一张卡的Memory-Usage,表示当前这张卡上所有CUDA上下文一共占了多少显存。这个数字是物理视角的全局数字。但如果这台GPU服务器本身是多人共享的,或者你跑在容器里,那么“能看到的显存”和“你能申请到的显存”会存在偏差。

最常见的情况是容器里看到宿主的整张卡:

  • 你启动了一个Docker容器,容器里执行nvidia-smi,它依然会报告这张物理GPU的总显存,比如80GB。
  • 但运行时真正允许你这个进程使用多少显存,可能被容器runtime、调度平台或者MIG实例限制在某个配额以内。
  • 当你的进程申请超过配额时,CUDA立刻返回out of memory,而nvidia-smi从容器内部看过去,可能还显示“空闲60GB”。

类似的还有MIG模式。一张物理GPU被切成分区,nvidia-smi会给你展示计算实例的名称和内存上限。此时“总显存”已经是切片后的数字,但如果你没注意上下文,依然会觉得“剩余量还很多,怎么会OOM”。

所以在服务器上排查这种问题,第一步永远是问自己一个问题:这个OOM是物理显存不足,还是配额不足?如果是配额不足,那么即使物理显存空出一半,你也一样拿不到。

1.2 一个“2MB都分配不出来”的真实案例

我在调试大模型推理时,见过一条特别有代表性的报错:

CUDA out of memory. Tried to allocate 2.00 MiB

当时的nvidia-smi显示GPU还有接近30GB空闲。很多人的第一反应是驱动坏了、显存虚标。但真正的原因不是“缺2MB”,而是CUDA分配器在虚拟地址空间里找不到一段连续的2MB区域。

这里有个容易混淆的概念:显存不只是物理容量,还有一个虚拟地址空间的维度。当程序反复分配、释放不同大小的显存块之后,剩下的物理显存总量可能很大,但这些物理页被映射到地址空间时,被大量活动块切碎了。申请2MB听着很小,可如果那30GB空闲都被打散成几百KB到1MB的小碎片,那2MB的连续请求一样会失败。

你可以把GPU显存想象成一个停车场。余量很大不等于有一个能停大巴车的空车位。如果整个停车场到处都是几米长的死角,一辆12米长的客车就是进不去,倒车也不行。显存OOM很多时候不是“地方不够”,而是“连续的地方不够”。

1.3 系统内存和GPU显存的OOM也不要混在一起

还有一坑。很多人说“OOM”,但日志里写的是“cannot allocate memory”,这其实可能是宿主机系统内存不足,而不是GPU显存不足。尤其是在低显存部署场景里,你需要把一部分层offload到CPU,或者使用pinned memory做异步拷贝。如果CPU内存不够,CUDA在准备H2D拷贝缓冲区时同样会报类似“out of memory”的错误。看到的词都一样,但解决方式完全不同。

所以先别急着骂显卡。看到OOM,第一件事是确认报错来自GPU驱动/CUDA层,还是来自操作系统内存分配层。两者都在字面上叫“OOM”,但一个是显存,一个是内存。

2. PyTorch 缓存分配器:显存被“扣住”的常见原因

2.1 PyTorch 不是按需向驱动申请显存,而是提前“囤地”

在PyTorch里用GPU,绝大多数情况下你会用到它的缓存分配器。这个分配器的核心逻辑很务实:cudaMalloc是昂贵操作,不能每次创建张量都向CUDA申请一次。所以分配器会在第一次需要显存时,一次性向驱动申请一大块,然后在进程内部把这块内存切成小块给张量用。

这样做的好处是速度快,坏处是:当一个张量被释放时,它的显存并不是真正还给GPU,而是留在PyTorch的缓存池里备用。所以nvidia-smi看到的使用量,往往大于你当前“存活张量”的总大小。

比如推理时,前向传播过程里有一个巨大的临时张量,占用了10GB。计算完成后这个临时张量被销毁,但PyTorch没有把这10GB还给CUDA。此时nvidia-smi里依然显示这张卡用了10GB。如果之后你又要申请一个3GB的缓存块,而当前空闲只有2GB,CUDA就报OOM,尽管这10GB里明明有8GB是可以被PyTorch“还回去”的。

很多人在这里就懵了:明明才刚释放过显存,怎么还是OOM?

2.2 allocated和reserved是两个完全不同的数字

排查PyTorch显存问题时,我最先看的不是nvidia-smi,而是PyTorch自带的显存统计。在Python里执行:

import torch print(torch.cuda.memory_summary(device="cuda:0")) print(torch.cuda.mem_get_info(device="cuda:0"))

memory_summary里会明确分开两个关键数字:

  • Allocated memory:当前真正被张量占用的显存大小。
  • Reserved memory:PyTorch分配器从CUDA手里申请到的显存大小,其中一部分正躺缓存池里待复用。

如果Allocated只有2GB,Reserved却有10GB,那你的显存明显被缓存分配器“扣住”了8GB。nvidia-smi看到的占用大概率接近Reserved,而不是Allocated。此时调用一次:

torch.cuda.empty_cache()

能释放掉缓存池里所有“当前没在使用”的块,让Reserved回落到接近Allocated。要注意,这只对PyTorch内部缓存有效,不能解决碎片问题。如果那8GB被切成几百块散落在地址空间里,释放后能不能合成一个完整的连续区域,还要看驱动和分配器的处理。

2.3 峰值分配决定了PyTorch“敢不敢”预留显存

还有一种更隐蔽的情况:PyTorch分配器会根据历史峰值,保留一个“够用”的缓存池。它不会傻到把所有释放的块全部还给驱动,因为那样下次遇到同样峰值时会再次触发昂贵的cudaMalloc。所以哪怕你在两次推理之间手动做了empty_cache,只要某个峰值分配出现过,Reserved就不会一下归零。

这也是为什么我不建议用“nvidia-smi当前空闲”来判断模型能不能跑。要看的是负载周期内的峰值。你的模型可能在某个瞬间需要15GB,绝大部分时间只占5GB。如果只看平时快照,会以为5GB就够;但一到那个瞬间,15GB申请失败,OOM来了。

2.4 两个环境变量能救回不少“假OOM”

PyTorch从1.9左右开始提供了显存分配策略环境变量,对碎片化问题特别有用的有两个:

PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

这个配置会让分配器使用可扩展段,而不是一次性申请一个大块。它更倾向于动态扩展已有段,显著降低虚拟地址碎片。深度学习训练、长序列推理场景里,这个参数是我第一个尝试的,效果好过硬调batch size。

另一个参数是:

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128

它的作用是控制大块缓存被二次切分的阈值。主要场景是:一个大块被反复切分成各种尺寸的小块,导致很多块既不能完整满足新请求,又因为太小无法归还给驱动。把它调大,能减少这种“碎块踩踏”,但不同模型表现不一样,建议作为第二个试验项。

如果你用的是TensorFlow,也有对等选项:

import tensorflow as tf gpus = tf.config.experimental.list_physical_devices("GPU") if gpus: tf.config.experimental.set_memory_growth(gpus[0], True)

开启后TF不再一把梭哈把整张卡预占,而是按需增长。副作用是运行中可能遇到更多碎片,对常规推理没有大问题。

3. 模型显存占用:权重只是冰山一角,KV Cache和激活值才是凶手

3.1 从参数到权重的第一层显存计算

很多人会拿“模型参数量 × 精度字节数”来估算显存,这个思路没错,但不完整。先看最简单的推理场景:

精度每参数字节数7B模型权重大致占显存
FP32428GB
FP16/BF16214GB
INT817GB
INT4 / NVFP40.53.5GB

这张表只能告诉你“权重裸大小”。真正的显存需求还得加上KV Cache、激活值、框架预分配的计算缓冲区和CUDA上下文。把这四类一起算进去,才是一个能落地的数字。

训练场景更夸张。除了权重,还要存梯度和优化器状态。Adam优化器在混合精度训练下,每个参数至少要额外吃掉8到12字节,最终总占用往往是“权重占显存”的三到四倍。如果你拿着推理的估算去开训练任务,不OOM才怪。

3.2 KV Cache为什么可以瞬间吃光“剩余显存”

大语言模型推理时,随着生成token数量的增长,KV Cache是线性增长的。它的计算公式大致是:

KV Cache大小 ≈ 2 × 层数 × KV头数 × 头维度 × 精度字节数 × 当前序列长度 × batch

这里面的“2”对应K和V两份缓存。举个例子:一个32层、每个KV头128维、8个KV头的模型,用FP16存KV Cache,每个token大约需要:

2 × 32 × 8 × 128 × 2 / 1024 / 1024 ≈ 0.125MB

看着不大,但换算到不同上下文长度:

上下文长度KV Cache占用
4K512MB
32K4GB
128K16GB

很多低显存运行模型的任务,权重通过INT4量化能塞进8G卡,但一旦你想把上下文从8K拉到32K,KV Cache直接多出几GB。这个多出来的部分就是运行中申请失败的常见火药桶。显存快照在生成前看还有4GB空闲,跑到第40轮对话忽然OOM,大多数时候就是KV Cache在作祟。

3.3 临时计算缓冲区和“预占”的坑

除了KV Cache,推理框架一般还会为算子分配临时计算缓冲区。比如某些注意力算子和归一化步骤,需要在运行时申请一块大小和batch、序列长度、head维度挂钩的scratch memory。这些缓冲区不一定全程存在,但会在计算的那一瞬间申请最大块。

更常见的是框架“预占”。像llama.cpp这类推理引擎,启动时就会根据--ctx和--batch提前把KV Cache和compute buffer一次性分配好。你以为模型很小所以不会OOM,但框架为了最大化吞吐,直接把未来可能用到的显存全锁定了。此时nvidia-smi显示“还剩一点”,可一旦有新的连续大块申请需求,就立刻失败。

所以模型显存规划不能只算权重。至少要按下面这个公式来预估:

总显存需求 = 权重占用 + KV Cache上限 + 激活峰值 + 框架固定开销

算完还要再留10%到20%余量,因为CUDA上下文、驱动状态、多进程共享都会额外吃掉一点。

4. MoE、量化与低显存部署:为什么省了计算却没省显存

4.1 MoE的“稀疏”省的是计算,不是显存

最近关于MoE架构的低显存讨论特别多,有一个误区反复出现:既然模型每次只激活Top-K个专家,那是不是可以把其他专家不加载进显存?

答案是否定的。MoE模型的“稀疏激活”指的是推理计算量只作用于少数专家,但权重加载量依然是全部参数。模型的总显存占用不会因为路由只挑了2个专家而变小。你可以把MoE想象成一家大食堂:100个厨师都站在备菜区,一桌客人最多同时让3位厨师做菜,但你不能确定下一个客人会点哪个厨师的拿手菜,所以这100个人都必须在岗位上。除非你做专家级offload,把一部分专家放在CPU内存里,需要时再流式换入,否则显存占用和同参数量的Dense模型在一个量级。

这也是为什么有些宣传“8G显存就能跑”的MoE模型,实际用起来还是会OOM。它们可能压低了权重精度、缩短了上下文、控制了batch,但只要你把其中一项放宽,显存就会立刻炸掉。

4.2 量化降的是权重,不是所有中间产物

量化模型能大幅降低权重占用,这是好事。比如一个70B模型,FP16权重要140GB,INT4后大约35GB,这差距非常可观。但要注意,量化只压缩静态参数。KV Cache、中间激活值、算子临时缓冲区,绝大多数情况下还是以FP16或BF16的精度计算,不会因为权重变成INT4就自动减半。

“NVFP4量化显存要求”这类说法,如果只标了权重占用,那是不完整的。你看到的“需要6G/8G显存”,很可能只算了量化权重,没算KV Cache和推理框架的compute buffer。实际部署时,8G卡跑一个权重只占4G的模型,依然可能因为上下文太长、batch太大而OOM。

低显存部署第一步不是“调节多少层量化”,而是先明确自己的上下文长度和batch。把KV Cache上限确定下来,再反推能放得下多少权重。否则很容易陷入“量化好了,但一加长上下文就崩”的循环。

4.3 低显存跑模型的正确姿势:算峰值,然后给峰值留15%以上

我自己在6G和8G卡上跑模型,习惯按下面顺序估算:

  1. 确定目标上下文长度,算出KV Cache上限。
  2. 估算推理框架的临时缓冲区,通常按1GB到2GB预留。
  3. 用量化后权重大小往显存里套。
  4. 三者相加后,再除以0.85,相当于至少留15%缓冲。

如果算出来超过显卡容量,优先砍上下文、砍batch,而不是继续强行把权重压得更狠。权重压到4bit之后,再往2bit压,精度和稳定性都会明显受影响,反而可能触发更多推理bug。在低显存卡上,“够用但紧张”从来不是很好的状态。Model只要能跑,步调还稳,比硬塞进去天天OOM强得多。

5. 一份可以直接抄的 OOM 排查链路

5.1 先确认报错来自哪一层

遇到OOM时,立刻区分三种情况:

  • CUDA层报错:CUDA out of memory,但进程还在,只是无法继续申请。
  • 系统内存层报错:RuntimeError: Cannot allocate memory,可能是宿主机CPU内存不够。
  • 进程被直接杀死:Killed,这种情况多半是系统OOM Killer动手了,不一定是显存。

如果是第三种,去看看系统内存和swap。特别是在offload场景下,几十层模型被塞进CPU内存,CPU内存本身不够时,GPU任务会直接连同进程一起被干掉。表现形式很像是“显存莫名其妙OOM”,实际根因完全不在GPU上。

5.2 查进程,别放过“看不见的占用者”

在共享租用的GPU服务器上,OOM可能是别人的进程造成的。先看全局:

nvidia-smi

如果有权限,再查具体计算进程:

nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv

这里有个细节:容器内PID命名空间隔离,你在容器里看到的PID未必是宿主机的真实PID。但至少能判断出“这张卡上有多少个计算进程”。如果发现一个不属于你的进程占着一半显存,那OOM的原因就找着了。这时候不是优化代码的问题,而是需要跟服务商确认这张卡是不是真的独占。

5.3 用PyTorch内存报告,区分“真不够”和“假不够”

跑代码前,先执行:

import torch print(torch.cuda.memory_summary(device="cuda:0"))

把输出里的Reserved和Allocated对比一下。如果Reserved明显大于Allocated,优先试试empty_cache,再配合PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True跑一次。

如果是“真不够”,memory_summary里通常会显示当前申请的最大块大小,然后告诉你分配失败。这个失败信息特别有用——注意它给出的数字,比如“Tried to allocate 4.00 GiB”。如果你看到申请的是4GB而显存只满到80%,那大概率不是总容量问题,而是碎片或Reserved占用问题;如果你申请的是一个接近剩余总量的数字,那才是显存不足。

5.4 用“减半法”锁定谁是凶手

当模型各种参数混在一起时,最快定位峰值来源的办法是逐个减半:

  1. 把batch size减半,如果OOM消失,说明激活值或batch相关缓冲区是主因。
  2. 把上下文长度减半,如果OOM消失,KV Cache是主因。
  3. 把并行度或者显存里的层数减半,如果OOM消失,权重加载是主因。
  4. 把推理框架的n_batch调小,如果OOM消失,compute buffer是主因。

每一步变化对应不同的显存消耗项。这个方法看起来笨,但比盯着nvidia-smi猜要快得多。一次只改一个变量,问题边界很快就清楚了。

5.5 给PyTorch设一个显存上限“护栏”

如果你在租用的共享卡上跑服务,不想因为自己的进程吃满显存而影响别人,也不想被别人影响,可以用PyTorch的进程级显存比例:

import torch torch.cuda.set_per_process_memory_fraction(0.9, device=0)

意思是当前进程最多使用该GPU显存的90%。好处是让分配器更谨慎,不会过度预留;坏处是如果你原本正好需要95%,那会有更多OOM。这个值更适合服务型任务,而不是训练调试。

6. 租用GPU服务器,怎么提前把显存余量算明白

6.1 租卡之前先按峰值算一次“最少显存”

我不建议只看“这模型要20G显存,所以我租24G卡”。正确的做法是把所有运行时峰值都算出来。下面这个简化估算可以当作模板:

params_b = 7 # 参数量,单位:十亿 weight_gb = params_b * 2 # FP16权重 kv_gb = 4 # 目标上下文对应的KV Cache,按你的场景估 act_gb = 1.5 # 激活和临时Buffer,推理一般留1~2GB peak = weight_gb + kv_gb + act_gb safe = peak / 0.8 # 留20%余量 print(f"峰值约 {peak:.1f} GB,建议租用显存 >= {safe:.1f} GB")

如果是训练,把优化器和梯度也加进peak。训练时的显存峰值往往比推理高好几倍,千万不要拿推理的估算去卡训练。

6.2 共享型租卡的三个提前问句

租GPU服务器时,我建议在下单前问服务商三个问题:

  • 这张卡是独享还是共享?nvidia-smi里能不能看到其他人的进程?
  • 容器内能申请到的显存上限是多少?有没有MIG/vGPU切片限制?
  • CPU内存给多少?系统盘和swap是否够用于模型offload?

这三个问题能避免绝大多数“明明有显存却OOM”的诡异场景。很多人把锅甩给服务商,最后发现是容器默认限制了显存,或者卡本身被别人占了。

6.3 多卡服务器上,用CUDA_VISIBLE_DEVICES隔离你自己的进程

如果你租到的是8卡机,优先用环境变量把进程绑定到指定卡,避免多个任务因为默认分配逻辑互相挤占:

export CUDA_VISIBLE_DEVICES=0,1

如果服务商支持计算模式设置,也可以把卡设为独占模式,防止其他租户强行分食:

nvidia-smi -c EXCLUSIVE_PROCESS

需要root权限,所以只有租用独立服务器时才用得着。容器的场景里,容器runtime一般已经做了隔离,不需要也不希望你直接改宿主机的GPU设置。

6.4 监控显存时,别信“瞬时空闲”,要看时间曲线

运行时可以用:

watch -n 1 nvidia-smi

重点关注显存使用量的趋势。如果它在一个推理循环里反复上升到同一个峰值后回落,那是正常波动;如果它一步步爬升但不回落,那就是显存泄漏。泄漏的典型来源是循环里随手创建的张量没释放、缓存列表越攒越多、或者某个框架的静态缓存没有及时清理。

曾经排查过一个服务,用户抱怨“跑久了必OOM”,问题是每次推理逻辑里,中途生成的中间结果被追加到了一个全局list里。看起来每个item很小,但几千轮之后,所有激活值全留在显存里。nvidia-smi每次空闲时看着都正常,只有把时间线拉长才看出问题。这个教训总结成一句话:显存余量是快照,不是承诺。

6.5 让我留在最后的一条个人经验

我现在判断一台租用GPU服务器能不能跑某个任务,早就不看“当前剩余多少显存”了。我会把任务分成三个数字:权重需要多少、峰值需要多少、运行环境需要多少。前两个是模型决定的,第三个是框架和容器决定的。只有三者都满足,我才敢说“这台机器稳”。如果只是勉强塞进去,那说明你离下一次OOM只差一个更长的上下文或者更大的batch。

这也是为什么我强烈建议在低显存卡上跑模型时,把余量当成“保险”,而不是可压榨的空间。真等到只剩一两G才想起来优化上下文,往往已经来不及了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 12:39:03

硬件设计基础入门:元器件选型、电源保护与信号完整性要点

简介:面向硬件工程师与电子类专业学习者的基础知识点问答合集,以“100例”形式梳理元器件选型、电路保护与常见器件应用中的高频问题,适合入职准备、面试复习与日常设计速查。内容从电阻、电容、电感的品牌清单、0402/0603/0805封装含义、J/K…

作者头像 李华
网站建设 2026/11/2 12:38:29

基于8254级联分频与8255的车辆测速实验设计与误差优化

简介:这份面向微机原理与接口技术课程学习者的实验报告文档,围绕智慧交通车辆测速场景,完整记录从需求分析到结果验证的实现过程,适合正在做接口芯片综合实验、课程设计或物联网交通应用入门的学生参考。压缩包内共1个docx文件&am…

作者头像 李华
网站建设 2026/11/2 12:37:16

芯片CAD图纸高保真集成TinyMCE的技术方案

1. 芯片制造企业CAD图纸处理的核心挑战 在芯片设计制造领域,CAD图纸承载着晶圆厂最核心的工艺数据。当设计部门需要将这些专业图纸整合到技术文档系统时,传统截图粘贴方式会导致三大致命问题: 精度丢失 :截图后的位图无法缩放查…

作者头像 李华
网站建设 2026/11/2 12:36:21

无文件攻击技术解析与Web渗透防御策略

1. 无文件攻击的本质与Web渗透场景适配性 无文件攻击(Fileless Attack)本质上是一种不依赖传统磁盘文件写入的恶意代码执行技术。与传统恶意软件不同,它利用操作系统内置工具和合法进程作为载体,直接在内存中完成攻击链。在Web渗透…

作者头像 李华
网站建设 2026/11/2 12:36:18

手搓大模型:滑动窗口数字采样如何为BPE文本编码提速

这集我们继续手搓大模型。S07 文本编码模块已经写到了第三集,前两集我们把字节对编码(BPE)的基础流程跑通了,词表构建、子词合并、分词回退这些环节都有了可用的原型代码。但有一个性能瓶颈我在前两集是刻意绕过去的——每一轮子词…

作者头像 李华
网站建设 2026/11/2 12:35:58

推荐系统规模化落地的四维工程实践

1. 这不是“调参指南”,而是一份推荐系统规模化落地的实战地图 如果你最近在团队里听到“我们模型上线后QPS掉了一半”“特征实时更新延迟从200ms涨到3秒”“离线AUC涨了0.5%,线上CTR反而跌了0.3%”这类对话,那你大概率已经站在了推荐系统Sca…

作者头像 李华