揭秘内存暴涨:解决大模型分布式训练OOM纪实

news2024/11/25 13:44:19

在现代深度学习开发中,我们通常依赖其他模块,像搭积木一样构建复杂的软件系统,这个过程往往快速且有效。然而,如何在遇到问题时迅速定位和解决问题,由于系统的复杂性和耦合性,一直困扰着深度学习系统设计和维护者。

作为爱奇艺后端技术团队的一员,我们详细记录了一次解决深度学习训练内存相关问题的过程,希望为正努力解决棘手问题的同行提供一些启示。

01

   背景

过去的一个季度中,我们一直在A100集群观察到随机的cpu内存oom现象。随着大模型训练的引入,oom更加的令人难以忍受,使得我们下定决心要解决这个问题。

回望来路,一时间觉得豁然开朗。事实上我们也曾经很接近问题的真相,不过当时缺乏足够的想象力而错过。

02

   过程

在最开始的阶段,我们对历史log进行了归纳分析。发现了若干规律,对最后的解决有很好的指导意义:

  • 这个是A100集群中遇到的新问题,其他集群没遇到过

  • 问题和pytorch的ddp分布式训练有关;使用pytorch的其他训练模式没有遇到

  • 这个oom问题相当随机,有的3个小时遇到,有的1个多星期才遇到

  • 内存在oom的时候以暴涨形式发生,基本上1分半内完成从10%到90%的涨幅,如下图所示:

    5f4dcb8f0ec0c2cc476a0a50b1b6fc9c.png

虽然有以上信息,但是由于问题基本不能可靠复现,开始阶段完全是靠发散的想象;猜了很多可能的原因,比如:

  • 会不会是代码问题,因为object没有回收,导致持续的内存泄露?

  • 会不会是底层的内存分配器问题,类似因为glibc的PTMALLOC分配器的碎片过多,所以在某个时刻,突发的内存请求导致了持续的内存分配?

  • 会不会是硬件的问题?

  • 会不会是软件特定版本的 bug?

下面我们对前两个假设进行具体的介绍。

  • 是代码的问题吗?

为了研判是否是代码的问题,我们在出现过问题的场景上加入调试代码,并周期性的进行调用。如下代码会打印出目前python gc模块所不能回收的所有object。

093e54293de22583a5461ee6ca955b75.png但追加了该代码之后,得到的log研判,oom的时候并没有占用大量内存的unreachable object存在,且持续的gc也不能缓解oom本身。所以至此我们的第一个猜想破产,问题不是代码(内存泄漏)引起的。

  • 是内存分配器引起的吗?

在这个阶段,我们引入了jemalloc内存分配器,它的优势和glibc默认的PTMALLOC相比,在于可以提供更有效率的内存分配,以及对于内存分配本身调试更好的支持,由此可以实现:

  • 会不会是默认内存分配器的问题

  • 更好的调试和分析手段

为了不修改torch本身的代码,以及在python中直接查看jemalloc目前的状态,我们使用了ctypes来将jemalloc的接口直接在python中进行暴露:

d4e67587a02459d734d03b9f185a76be.png

这样我们把这段代码放到一个函数内,就可以周期性的获知目前jemalloc接收来自于上层的请求【allocated】,以及它向系统请求的实际物理内存的大小【mapped】。

经过实际的复现过程中,最终发现allocated和mapped这两个数值在发生OOM时非常接近。所以我们对于内存碎片的假说也因此破产。

  • 究竟是什么问题引起的?

在山穷水尽疑无路的时候,再一次对已有OOM的log进行了梳理,发现有一个之前没有被重点分析的方向:即我们有若干次多台机器在相近的时间(相邻1-2分钟)发生OOM。

那么有什么合理的解释来说明这种神奇的同步性?普通的bug应该不会引起如此的同调性反复发生。所以他们之间可能存在某种必然关联。

那么这种关联性来源于何处?对于这个问题的探索,分析的视角移向分布式训练上的网络通信上。

最开始对于通信的怀疑还是针对出现OOM的机器,怀疑它们之间因为某种原因产生了通信,进而导致彼此出现问题,所以在日常训练中,加入了tcpdump,对网络流量进行监控。

终于在加入tcpdump之后一次OOM中,抓到了最值得怀疑的一次通信。即在发生问题若干分钟之前,OOM的机器接收到了安全扫描流量。

03

   最终定位

在抓到安全团队扫描这个怀疑对象之后,我们协同安全团队一起进行分析,最终发现,能够根据扫描稳定复现OOM问题,所以触发原因已经八九不离十了。但是,到了这里,我们只是能够复现以及变更安全扫描策略规避OOM问题,还需要进一步对代码进行分析并最终定位。

经过对代码分析以及定位,最终确定问题点在于pytorch的DDP分布式训练协议,相关代码如下:

d0dfec39cbc7a8f2313535d5b8ff3a72.png

如上图所示,pytorch分布式训练在master端口持续监听消息。

52a23a9d1b20b202dbad11ec4ba75fda.png

Nmap扫描【nmap -sS -sV】正好触发到了QueryType::ADD这个消息类型,也就是上图tcpdump所示data部分的绿框数字【03】,进而导致pytorch尝试使用recvString这个函数预分配一段buffer,来接受它认为的后续消息。但这个buffer长度是使用【03】后面的一个uint64_t[little-endian]类型来进行解析的,也就是红框数字【e0060b0000】,即962174058496字节,这个数值被理解为将要接收1T数据,pytorch向内存分配器请求了相应内存之后,内存分配器向内核进一步请求相应的物理页。而由于我们的gpu训练集群没有配置巨页表,因此Linux只能按照4K粒度来逐渐在缺页中断中来满足内存分配器的1T内存请求,也就是大概需要1分钟左右来分配所有内存,和前面观察到的OOM大概发生在1分钟左右的快速内存增长对应。

04

   解决方案

知道前因后果之后,解决方案也变得自然而然:

1. 短期:变更安全扫描策略规避

2. 长期:和社区沟通加强 pytorch DDP 协议的健壮性1

05

   总结

在完成对于OOM问题的调查过程回溯之后,我们发现在这个过程中,我们实际上对于内存相关的工具和调试方法已经做了一轮有效的测试。

在这个过程中,我们发现有一些通用的点可以为后续的研发所借鉴:

  • Jemalloc对于内存问题能够起到很有效的定量分析,能够捕捉到对于python+C这种混合编程系统中底层内存的相关问题。

  • Memray。我们在调试过程中给予它很高的期望,但最终发现memray能够发挥最好的领域还是处在纯python侧,对于pytorch DDP这种混合编程系统力有不逮。

有的时候还是需要从更大的维度来思考问题。比方如果不把和外部无关服务通信过程拉进来考虑,就不会发现真正的根本原因。

【1】https://github.com/pytorch/pytorch/issues/106294

d423600fc7217073a57a546e18913611.jpeg

也许你还想看

Spring Cloud Gateway下的GC停顿排查之旅

爱奇艺海外运营系统的设计和实践

爱奇艺数据湖实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/868039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UTONMOS:元宇宙在网络游戏领域得到充分运用

元宇宙到底是个啥?这个词大概意思应该就是人类能从真实世界进入一个虚拟世界体验另一种生活,这个虚拟的世界就叫“元宇宙”。 从科幻走入现实,元宇宙究竟有什么用途?它离我们到底还有多远?又将给我们的生活带来哪些变…

htmlCSS-----高级选择器

目录 前言 伪类选择器 状态类 结构类 伪元素选择器 属性选择器 前言 前面我们学习了CSS中的相关选择器(链接html&CSS-----CSS选择器(上)_灰勒塔德的博客-CSDN博客 html&CSS-----CSS选择器(下)_灰勒塔…

【Linux】线程的互斥

目录 写在前面的话 相关背景概念 什么是互斥 互斥锁(互斥量) 互斥锁的使用 一些相关的问题 线程安全和可重入的区别 写在前面的话 本文章主要介绍了线程的互斥的相关内容,而且本文的概念也比较多,所以需要有一些前提知识作…

【ES6】深入理解ES6(1)

一、块级作用域绑定 var声明及变量提升机制 二、字符串和正则表达式 字符串(String)是JavaScript6大原始数据类型。其他几个分别是Boolean、Null、Undefined、Number、Symbol(es6新增)。 更好的Unicode支持 1. UTF-16码位 字…

23款奔驰AMG GT50更换原厂运动排气系统,战斗感立马提升了

改装运动排气,原车中控的按键组也是需要更换的。与原车按键相比,只是多了一个排气的控制按键,也正是这个按键,能让车辆可静可怒,安静与怒吼就在一键之间。

QT-图标绘画工具

QT-图标绘画工具 一、效果演示二、关键程序三、程序链接 一、效果演示 二、关键程序 代码如下: #include "DrawDialogFactory.hpp" #include "DrawDialog.hpp" #include "GlobalDrawProperties.hpp"#include "Shape.hpp"…

pnpm常用命令

pnpm常用命令 下载pnpm,但是出现了 npm WARN notsup Unsupported engine for pnpm8.6.12: wanted: {"node":">16.14"} (current: {"node":"14.15.0","npm":"6.14.8"}) npm WARN notsup Not compa…

ViewUI表格Table嵌套From表单-动态校验数据合法性的解决方法

项目场景: 项目需求:在表格中实现动态加减数据,并且每行表格内的输入框,都要动态校验数据,校验不通过,不让提交数据,并且由于表格内部空间较小,我仅保留红边框提示,文字…

【编程指南】ES2016到ES2023新特性解析一网打尽

ES2016 Array.prototype.includes() Array.prototype.includes 方法: 这个方法用于检查数组是否包含特定元素,如果包含则返回 true,否则返回 false // 我有一个水果篮子 const fruitBasket [apple, banana, orange, grape];// 我要检查篮…

关于pycharm安装出现的interpreter field is empty,无法创建项目存储位置

关于pycharm安装出现的interpreter field is empty(解释器为空) 关于pycharm安装出现的interpreter field is empty,无法创建项目存储的位置。如图: 我之前安装的时候一直老是有这个提示,后来才发现是因为没安装这个p…

腾讯云服务器轻量和CVM有什么区别?

腾讯云轻量服务器和云服务器有什么区别?为什么轻量应用服务器价格便宜?是因为轻量服务器CPU内存性能比云服务器CVM性能差吗?轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境,云服务器CV…

linux 安装go 1.18版本

首先去官网找到对应的版本 直接下载下来(如果服务器可以直接访问到go 官网也可以wget直接下载到服务器) 然后把该包上传到linux 的/usr/local 目录下 然后直接解压安装该包: sudo tar -C /usr/local -zxvf go1.18.10.linux-amd64.tar.gz 然…

通过Statement静态语句,实现CRUD操作

首先你需要创建 数据库 和 s_students学生表,再进行下一步的 增(add),删(del),改(update),查(query)。 查询所有学生姓名: Testvoid query(){try{Statement st conn.createStatement();ResultS…

利用Torchmetrics库快速进行Torch的评价指标计算(推荐)

目录 1、安装 2、基本流程介绍 3、MetricCollection 4、自定义指标 5、我们可以调用多个指标计算不同的任务 6、可以是标签,也可以是one_hot编码 7、常用分类指标计算 8、异常报错 1、安装 官网地址:Welcome to TorchMetrics — PyTorch-Metrics 1.0.1 documenta…

JUL 日志 - 最简单易用的Java日志框架

在正式的生产环境下是不能使用 System.out 进行日志记录的 因为 System.out 不能提供时间、线程、执行过程 等信息,如果要手动打印输出则会非常麻烦 而日志就帮我们把这些事给干了 接下来我们学一个最简单的日志框架 JUL JUL全称Java util Logging是java原生的日志框…

用户数据报协议UDP

UDP的格式 载荷存放的是:应用层完整的UDP数据报 报头结构: 源端口号:发出的信息的来源端口目的端口号:信息要到达的目的端口UDP长度:2个字节(16位),即UDP总长度为:2^16bit 2^10bit * 2^6bit 1KB * 64 64KB.所以一个UDP的最大长度为64KBUDP校验和:网络的传输并非稳定传输,…

资源限制类题目解法,看这一篇就够了!

算法拾遗三十七资源限制类题目 资源限制技巧汇总32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,可以使用最多1GB的内存,怎么找到出现次数最多的数32位无符号整数的范围是0~4294967295,现在又一…

【VBA入门】WorkBook 对象 Name操作 宏录制筛选删除代码

VBA 入门 问题记录1 了解Excel工作簿、表格关系1 默认新建WorkBook2 新建WorkBook并命名工作表添加数据3新建带有指定数量工作表的工作簿 ActiveWorkbook.Names用法(1) 创建名称 (全局名称和局部名称) 宏录制验证删除可行性大招!!&#xff01…

Linux基础与应用开发系列九:各类系统函数与标准IO函数

open_close函数 OPEN函数 头文件&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> 函数原型&#xff1a; 当文件存在时 int open(const char* pathname,int flags) 当文件不存在时 int open (const char* pathname,int f…

(黑客)自学误区

一、自学网络安全学习的误区和陷阱 1.不要试图先成为一名程序员&#xff08;以编程为基础的学习&#xff09;再开始学习 行为&#xff1a;从编程开始掌握&#xff0c;前端后端、通信协议、什么都学。 缺点&#xff1a;花费时间太长、实际向安全过渡后可用到的关键知识并不多…