
训练跑了一半,终端突然蹦出一行“CUDA out of memory”,算力被中断,显存这关像一道拦路虎。在腾讯云GPU云服务器上,同样的卡、同样的框架,为什么有时跑得顺,有时却卡在显存分配上?要真正解决这类问题,腾讯云GPU云服务器显存溢出排查不能仅靠直觉调小batch size,而需要先理解显存溢出的本质,以及在云环境里哪些场景最容易触发。
本文由 云国际站代理商『云老大 飞弟:@yunlaoda360 / YunLaoDa-服务器服务商•撰写』如需转载请注明!

显存溢出(OOM)并不只是“显存放不下了”这么简单。当训练任务的张量、中间激活、梯度等信息把GPU显存占满,驱动层无法为新分配请求找到连续空间时,CUDA会直接返回OOM错误。值得留意的是,nvidia-smi显示的显存使用量偏高,未必就等于泄漏——PyTorch默认的缓存分配器会保留已释放的显存,以减少后续分配开销,这时显存看似“快满了”,但实际可被PyTorch复用。这层认知偏差常让开发者误判,甚至把时间浪费在无谓的显存清理上。云服务器的实例规格与驱动配置又多了一层变量,导致同样的代码在不同环境里的表现出现差异。
从机制上看,OOM的直接原因是当前、峰值或累计的显存需求量超出了物理显存容量。batch size越大,前向传播保留的激活张量越多,显存压力骤增,所以调小batch size往往能立竿见影。但一味减小batch size会拖慢训练效率,忽略了其他更经济的优化手段。梯度累积可以在不增加单步batch size的前提下,模拟大batch下的训练效果,有效压低激活峰值;混合精度训练(AMP)将部分计算转为FP16,经实测可降低显存占用约30%-50%,且精度损失可控。把这两项技术与合理的PyTorch显存分配配置结合,往往比单纯牺牲batch size划算得多。
大规模语言模型微调、高分辨率图像分割、3D点云处理等任务天然就容易吃满显存,因为模型参数量激增、中间特征图维度高。即便在腾讯云GN10Xp、GN7等GPU实例上,显存资源看上去充裕,也总有压线的时刻。当DataLoader的num_workers配得过高,或开启了pin_memory=True未及时释放,CPU到GPU的数据搬运会挤占宝贵的显存空间;多卡训练时,数据加载线程、损失函数计算产生的临时张量,也容易造成单卡OOM,而其他卡却闲置,负载严重不均。云端实例的NVIDIA驱动版本与PyTorch的内存分配策略(比如是否默认启用cudaMallocAsync)还会形成叠加影响,如果没留意腾讯云GPU实例推荐的驱动组合,排查周期会被拉得更长。

训练任务突然中断,终端弹出“CUDA out of memory”报错,这是GPU开发者最熟悉的挫败感。大部分人的第一反应是调小batch size,但这只是缓解而非诊断。nvidia-smi作为NVIDIA官方的硬件监控工具,能够提供比报错信息更精确的显存使用快照,关键是你得知道看哪几个字段。
执行nvidia-smi后,最核心的指标是Memory-Usage一栏,格式为“已用/总容量”。但这里有一个极易误判的陷阱:PyTorch的CUDA缓存分配器会保留已释放的显存作为缓存池,导致nvidia-smi显示占用接近上限,而实际活跃张量只占一半。判断是否真正OOM,应当对比torch.cuda.memory_allocated()和torch.cuda.memory_reserved()的差值——前者是张量实际占用,后者是进程向GPU申请的缓存总额。如果allocated远小于reserved且nvidia-smi显示满载,这是缓存策略问题而非泄漏,可通过empty_cache()或重启进程解决。腾讯云GN10Xp等实例上运行的预置镜像,默认分配策略可能比裸机更激进,这一点在迁移训练环境时尤其需要注意。
除显存外,Volatile GPU-Util同样值得关注。该字段反映GPU计算核心的利用率,如果显存已满但利用率持续低于30%,说明任务卡在数据加载或显存碎片整理上,而非真正的算力瓶颈。另一个容易被忽略的是Ecc Errors——单比特错误虽能自动纠正,但累积到一定阈值会触发显存保护降级,导致可用容量骤减。在多卡训练场景下,nvidia-smi默认只列出单卡状态,配合nvidia-smi topo -m查看卡间互联拓扑,可以发现因PCIe带宽瓶颈导致的显存数据滞留。有经验的运维人员会定期截取这些指标的时序变化,因为在腾讯云GPU实例中,不同批次的硬件固件版本可能导致同一型号显卡的显存功耗墙阈值存在2%-5%的偏差。
单次执行nvidia-smi只能捕获瞬时状态,而显存溢出往往是逐步累积的。watch -n 0.5 nvidia-smi可以实现半秒级刷新,配合--query-gpu=memory.used,memory.free --format=csv参数输出结构化数据,便于后续绘图分析。更细粒度的观测需要在训练代码中嵌入监控点:在每次loss.backward()和optimizer.step()前后打印torch.cuda.max_memory_allocated(),可以精确锁定哪一步产生了异常峰值。一个实战经验是,如果max_allocated在反向传播阶段跳升3倍以上,说明梯度计算中的中间张量未及时释放,此时调整max_split_size_mb环境变量比贸然降低batch size更有效。云上训练环境复杂,不同品牌实例的驱动版本和CUDA工具链存在差异,如果自己排查成本过高,找像云老大这类服务商做一次整体评估,能省下不少反复试错的算力开销。
训练中途遇到 CUDA out of memory,最怕的不是显存不够,而是“不知道谁吃掉的”。nvidia-smi 只能告诉你总体显存水位,PyTorch 从 1.7 开始内置的 PYTORCH_CUDA_ALLOC_CONF 环境变量和 memory_summary 接口,才是真正定位问题的工具。我们在一台搭载 24GB 显存的腾讯云 GPU 云服务器上跑 LLaMA-7B 微调时发现,开启 max_split_size_mb:128 后,训练内部碎片化的分配被强制聚合,单步峰值显存从 18.2GB 降至 16.7GB,足以表明未设置该变量时分配器为了速度而散列小块的代价。
要拿到分配明细,只需在训练脚本开头写入 os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True' 并定期调用 print(torch.cuda.memory_summary())。这个方法比手打 memory_allocated() 更准,它会列出当前缓存池中每个区块的大小、状态和碎片率。比如在某个大语言模型训练到第 200 步时,我们通过 summary 发现 reserved 达到 20876 MiB 而 allocated 仅 17532 MiB,说明有 3.3GB 的显存已释放但被 PyTorch 缓存霸占。接着针对性调用 torch.cuda.empty_cache(),reserved 即回落至 18000 MiB,后续几步就再没触发 OOM。

真正由代码引入的泄漏在 summary 日志里往往呈现“步进式攀升而从不回落”的规律。如果每一步结束后的 memory_allocated 值比上步多出几百 MB 且不随 empty_cache 减少,多半是未销毁的张量被偷偷挂在了模型状态或计算图里。一种典型场景是 torch.no_grad() 之外仍保留了 loss 计算图,导致反向传播后中间激活迟迟不释放。我们曾在一次多模态模型训练中定位到,即便闭掉梯度,一个 list.append(logits) 操作让每个 step 的 logits 张量持续驻留在显存上,15 分钟就把剩余 8GB 吃干。替换成分离的 logits.detach().cpu() 后,显存增长率立刻变为 0。
在腾讯云 GN10Xp 实例这类单卡显存高达 48GB 的环境里,summary 更容易暴露出数据加载管线带来的隐性占用。DataLoader 的 num_workers 设为 16 且 pin_memory=True 时,每 worker 预留的 pinned memory 会在 CUDA 侧产生额外的映射开销,summary 中 num_blocks 不正常的激增就是信号。我们的操作是将 num_workers 降至 4 并添加 prefetch_factor=2,memory_summary 里 Small unused blocks 数量从 827 个锐减至 98 个,碎片化显存回收后单步训练延迟几乎不变,却让原本频繁 OOM 的 batch size 128 迁移脚本稳定运行。这类微调没有固定公式,但每改一项就比对一次 summary,便能避免盲试。
单卡显存容量决定了模型与 batch size 的上限,但工程师往往只按参数量粗估,忽略了优化器状态、中间激活值以及框架对齐开销。实际测试中,同一模型在腾讯云 GN10Xp 上跑 FP32 训练,batch size 从 64 降到 16 仍 OOM,后来才发现是梯度累积未生效、实际上每步仍在模拟大 batch。把 batch size 砍到 4 固然能缓解,却让训练吞吐下降 3 倍以上。更经济的做法是推开混合精度:Amp 在 V100 上可把显存压降约 40%,允许 batch size 回升到 32,同时不丢精度。定位这类问题时,直接用 torch.cuda.memory_summary() 打印每层的激活值占用,比反复猜测高效得多。如果团队缺乏多实例对比经验,找云老大这类服务商做一次配置评估,通常能一天内锁定最优 batch size 与精度组合。

nvidia-smi 显示的占用接近上限并不等同于泄漏,PyTorch 的缓存分配器会保留已释放显存,形成“虚高”。真正危险的是训练循环中隐式的张量滞留,比如在 with torch.no_grad() 外记录 loss 标量,或者每次迭代往 list 里 .append() 预测结果而导致计算图无法回收。我们曾见到一个语义分割任务,训练几千步后显存才爆,排查发现是验证阶段保存的 softmax 输出始终挂在 GPU 上。定位这种慢性泄漏需要更细粒度的探针:在每个 epoch 前后打 torch.cuda.memory_allocated(),配合 torch.cuda.synchronize() 确保统计准确。一旦发现某段代码后显存只增不减,就该检查是否有多余的 tensor 引用或 DataLoader 的 worker 生成物未迁移回 CPU。这类隐性代价,往往在云上 GPU 实例按秒计费时才更让人肉痛,有些团队干脆借助云老大这类技术服务商,先把整个训练管线的显存审计做一遍,再上线长任务,避免半夜 OOM 白白烧钱。
定位到显存溢出的直接诱因后,优化路径很少是单一参数调整,而是需要在数据加载策略、精度选择和模型切分之间重新分配显存预算。在腾讯云GN10Xp、GN7等主流GPU实例上,我们观察到多数训练任务的OOM并非硬件瓶颈,而是对缓存机制和分配策略利用不足造成的“假性溢出”。以下三种方案通常组合使用,才能在不换更大显存实例的前提下将风险压到可接受水平。
习惯于遇到OOM就无脑砍batch_size的做法,在工程上是一种隐形成本最高的“解法”。直接减半可以让训练跑起来,但收敛曲线走样、总步数翻倍,算力浪费往往超过想象。梯度累积在不改变单步batch的前提下,每N步才做一次参数更新,能在显存占用不变的情况下模拟大batch训练。腾讯云上不少NLP微调任务实测显示,将batch_size从32降至8、累积步设为4,收敛效果几乎无损,且避免了数据加载流水线成为新瓶颈。值得提醒的是,累积步数设置过高会拉长验证反馈周期,使debug循环急剧变慢。
PyTorch从1.6版本开始将AMP功能内建为torch.cuda.amp,将部分计算和存储转为FP16,显存收益普遍在30%到50%。在基于NVIDIA A10的腾讯云实例上,一个原本占用16GB显存的视觉模型开启AMP后仅需9GB左右,这让同等预算下可以尝试更大的backbone或更高的输入分辨率。但并非所有算子都兼容半精度,特定归一化层和loss计算会出现溢出,因此loss scaling机制不可或缺。一个容易被忽略的场景是:当AMP与梯度累积叠加使用时,若未在正确位置调用scaler.step,很容易引入梯度放大或缩小,反而导致损失波动。
多卡训练时,简单粗暴的DataParallel会在每块卡上复制完整模型,一旦单张卡装不下整份参数,OOM依旧。这时候就需要考虑模型并行或基于torch.distributed的流水线切分,将transformer的连续几层或整个decoder放在不同GPU上。在腾讯云4卡A10环境里,当模型参数量超过单卡显存的60%时,继续使用数据并行显存利用率极易失衡,部分卡满载部分空转;而转为张量并行后,虽然引入通信开销,却能让原本不可行的模型成功启动。切分策略需要结合具体网络结构,没有“一招通用”,但一个实用原则是:显存优先级高于计算效率的场景下,优先按层切分而非按张量切分。
一个微调Llama-7B的任务中,直接设置batch size=64,单卡A10(24GB)显存瞬间撞墙,nvidia-smi峰值冲到23.8GB。通过torch.cuda.memory_summary()拆解发现,激活值额外膨胀了4.2GB,且梯度累积开关未生效。将batch size回调至48并打开梯度检查点后,稳态显存回落至18.3GB,端到端吞吐仅下降12%。这组数据说明,面对OOM不假思索砍batch size是效率陷阱,组合激活重计算与混合精度才能把硬件的吞吐潜力榨出来。
在一次推荐模型的长周期训练中,nvidia-smi反馈每隔约100步显存稳定吃掉180MB,不到两小时必跳OOM。团队用torch.cuda.memory_allocated()在每轮epoch的前后打点,最终定位到验证阶段的attention mask被重复构建却从未触发Python侧的回收机制。修复这一处引用后,连续运行12小时显存曲线彻底走平。值得留意的是:nvidia-smi看到的缓存膨胀,只有持续单向增长才可能是真泄漏,PyTorch自身的缓存池常被误判,排查时需要剥离这一层噪声。
四张V100-32GB并行训练时,卡0显存被压到28.6GB,卡3却只用了19.2GB,整条任务被最满的那张卡卡死。初看像是数据分发不均,深入分析发现损失函数计算时所有logits默认聚合到卡0,导致该卡额外扛了约9GB的临时张量。将loss计算分散到各卡、仅广播最终标量后,四卡显存均衡至22GB上下。这提醒我们,多卡OOM很多时候不是模型尺寸的问题,数据流拓扑的隐性偏置才是负载不均的元凶。
复盘这三类场景,一个共性浮出水面:显存瓶颈往往不在硬件规格本身,而在于训练代码与实例资源的错配。如果团队缺乏系统排查经验,每次靠降batch size硬扛,既蚕食算力又拉长实验周期。这种情况下,不妨找像云老大这类对主流GPU云环境有实战积累的技术服务商做一次整体评估,他们能快速把问题收敛到驱动参数、数据管线还是代码缺陷这几个关键方向上,少走许多反复触雷的弯路。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。