首页 > 其他分享 >LLM大模型:deepspeed实战和原理解析

LLM大模型:deepspeed实战和原理解析

时间:2024-07-28 23:28:47浏览次数:17  
标签:显存 deepspeed torch 参数 LLM 显卡 model 解析

   多年前搞大数据,因为单节点无力存储和计算PB级别的数据,所以hadoop这种分布式存储和计算框架是标配!如今搞大模型,仍然需要对大量样本数据做计算,因为涉及矩阵运算,单机单卡运算效率太低,也涉及到分布式计算了,大模型时代的分布式pre-train和Inference框架就有现成的—deepspeed!

  1、老规矩,先直观体验一下deepspeed的使用:

   (1)自己定义一个简单的模型:model.py

import torch
import numpy as np

class FashionModel(torch.nn.Module): 
    def __init__(self):
        super().__init__()
        self.seq = torch.nn.Sequential(
            torch.nn.Linear(in_features=784, out_features=300),
            torch.nn.ReLU(),
            torch.nn.Linear(in_features=300, out_features=10)
        )

    def forward(self, batch_x):
        return self.seq(batch_x)

def img_transform(img): 
    img = np.asarray(img) / 255
    return torch.tensor(img, dtype=torch.float32).flatten()

  (2)训练的核心代码train.py:

import argparse
import torch
import torchvision
import deepspeed
from model import FashionModel, img_transform

# 命令行参数:deepspeed ds_train.py --epoch 5 --deepspeed --deepspeed_config ds_config.json
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int, default=1, help="local rank passed from distributed launcher")
parser.add_argument("--epoch", type=int, default=1, help="epoch")
parser = deepspeed.add_config_arguments(parser)
cmd_args = parser.parse_args()  # deepspeed命令行参数

dataset = torchvision.datasets.FashionMNIST(root='./dataset', download=True, transform=img_transform)  # 数据集
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, num_workers=4, shuffle=True)  # 数据加载器,batch_size应该等于train_batch_size/gpu数量

model = FashionModel()  # 自定义的模型
model, _, _, _ = deepspeed.initialize(args=cmd_args, model=model, model_parameters=model.parameters())  # deepspeed分布式模型
loss_fn = torch.nn.CrossEntropyLoss()

for epoch in range(cmd_args.epoch):
    for x, y in dataloader:
        x = x.cuda()
        y = y.cuda()
        output = model(x)
        loss = loss_fn(output, y)
        model.backward(loss)  # 走deepspeed风格的backward
        model.step()
    print("epoch {} done...".format(epoch))
    model.save_checkpoint('./checkpoints')

  配置文件ds_config.json

{
    "train_batch_size": 128,
    "gradient_accumulation_steps": 1,
    "optimizer": {
        "type": "Adam",
        "params": {
            "lr": 0.00015
        }
    },
    "zero_optimization": {
        "stage": 2
    }
}

  deepseed安装好后,直接一行命令就开始运行:deepspeed ds_train.py --epoch 2 --deepspeed --deepspeed_config ds_config.json ;从日志可以看出:有几块显卡就会生成几个进程并发训练;显卡之间使用nccl互相通信;

        

   主进程rank 0 打印日志:

        

   显存都用上了:

       

  训练完毕生成的模型:

        

   pre-train完成后的总要对LLM评估吧,代码ds_eval.py:

import argparse
from model import FashionModel, img_transform
import deepspeed
import torchvision
import torch

### deepspeed ds_eval.py --deepspeed --deepspeed_config ds_config.json
parser = argparse.ArgumentParser()
parser.add_argument('--local_rank', type=int, default=-1, help='local rank passed from distributed launcher')
parser = deepspeed.add_config_arguments(parser)
cmd_args = parser.parse_args()  # deepspeed命令行参数

model = FashionModel().cuda()  # 原始模型
model, optimizer, _, _ = deepspeed.initialize(args=cmd_args, model=model, model_parameters=model.parameters())  # deepspeed分布式棋型
model.load_checkpoint('./checkpoints')  # 加载参数

model.eval()  # 分布式推理模式

# 只有主控进程带头做这些动作
if torch.distributed.get_rank() == 0:
    dataset = torchvision.datasets.FashionMNIST(root='./dataset', download=True, transform=img_transform)  # 训练数据集
    batch_X = torch.stack([dataset[0][0], dataset[1][0]]).cuda()
    outputs = model(batch_X)  # 分布式推理
    print('分布式推理:', outputs.cpu().argmax(dim=1), [dataset[0][1], dataset[1][1]])

### 模型转成torch单体
torch.save(model.module.state_dict(), 'model.pt')  # 保存为普通torch模型参数
model = FashionModel().cuda()  # 加载torch模型
model.load_state_dict(torch.load('model.pt'))
model.eval()  # 单体推理
outputs = model(batch_X)
print('单体推理:', outputs.cpu().argmax(dim=1), [dataset[0][1], dataset[1][1]])

  评估代码也简单,也是直接一行命令:deepspeed ds_eval.py --deepspeed --deepspeed_config ds_config.json

import argparse
from model import FashionModel, img_transform
import deepspeed
import torchvision
import torch

### deepspeed ds_eval.py --deepspeed --deepspeed_config ds_config.json
parser = argparse.ArgumentParser()
parser.add_argument('--local_rank', type=int, default=-1, help='local rank passed from distributed launcher')
parser = deepspeed.add_config_arguments(parser)
cmd_args = parser.parse_args()  # deepspeed命令行参数

model = FashionModel().cuda()  # 原始模型
model, optimizer, _, _ = deepspeed.initialize(args=cmd_args, model=model, model_parameters=model.parameters())  # deepspeed分布式棋型
model.load_checkpoint('./checkpoints')  # 加载参数

model.eval()  # 分布式推理模式

# 只有主控进程带头做这些动作
if torch.distributed.get_rank() == 0:
    dataset = torchvision.datasets.FashionMNIST(root='./dataset', download=True, transform=img_transform)  # 评估数据集
    batch_X = torch.stack([dataset[0][0], dataset[1][0]]).cuda()
    outputs = model(batch_X)  # 分布式推理
    print('分布式推理:', outputs.cpu().argmax(dim=1), [dataset[0][1], dataset[1][1]])

### 模型转成torch单体
torch.save(model.module.state_dict(), 'model.pt')  # 保存为普通torch模型参数
model = FashionModel().cuda()  # 加载torch模型
model.load_state_dict(torch.load('model.pt'))
model.eval()  # 单体推理
outputs = model(batch_X)
print('单体推理:', outputs.cpu().argmax(dim=1), [dataset[0][1], dataset[1][1]])

  运行效果如下:

  直观感受完了deepspeed的使用,感觉比较简单,底层分布式的训练方案已经由框架都封装好了,开发人员直接调用即可! 接下来最重要的就是了解分布式训练方案的原理了!

  2、做分布式训练,要么是单节点显存不够,要么是算力不够。算力主要是各种矩阵运算啦,GPU硬件本身就是为这种计算定制的,软件层面无法明显优化,所以分布式系统主要优化的就是显存的占用啦

  (1)先看看单机单卡训练时的显存占用,假设模型的参数量是m:

  • 参数本身存储需要显存,用FP32和FP16混合精度存放,需要6m显存
  • 梯度保存:用FP16存放,需要2m显存
  • optimizer优化器:以adam为例,梯度下降的时候要存梯度和梯度平方,每个参数要存2个状态,需要8m显存

       

  在不考虑存放训练数据的前提下,pre-train至少需要6m+2m+8m=16m的显存,所以后续的重点就是怎么优化这三部分显存占用啦!

  (2)先来看看最简单的一种情况:data parallel,简称DP。假设有N个显卡:

  • 就是把训练数据均分成N份,然后N个显卡同时做forward和backward;N快显卡网络的初始参数都是一样的
  • 产生的gradient都发送给某个特定的显卡(这里用0号显卡表示)
  • 0号显卡根据gradient更新自己网络的参数,然后把新的参数广播发送给其他所有显卡,让所有显卡的网络参数保持一致
  • 除了0号显卡,其他显卡的作用就是计算loss和梯度

      

   

  这种DP方式的缺陷很明显:0号显卡要收集其他所有显卡梯度,更新参数后要把新的参数广播给所有显卡,显卡之间的通信量很大,具体同步的数据量和显卡数据是线性正比的关系

  (3)DP的改进版:distributed data parallel,简称DDP;和DP比,每块卡单独生成一个进程;

  • 数据照样均分成,N个显卡同时做forward和backward;N快显卡网络的初始参数都是一样的
  • 因为每张卡的数据不同,所以loss和梯度肯定不同,此时通过Ring-allReduce同步梯度,让每张卡的梯度保持一致
  • 每张卡根据梯度更新自己的网络参数;因为每张卡的loss和梯度是要通过Ring-allReduce互相同步的,并且网络的初始状态也是一样的,所以每张卡的optomizer和网络状态始终是一样的!

        

   显卡集群总的数据传送量:因为使用了Ring-allReduce传输数据(每个结点只给下一个结点传输数据,并不是整个集群广播),所以总的传入传出总量是固定的,不会因为显卡集群扩大导致数据传输大增

       

  3、上述的DP和DDP,通过分布式增加了算力,但缺陷还是很明显的:并未节约显存!所以由此产生了ZeRO技术!

  (1)预训练时,optimizer占用8倍参数量的显存空间,是最耗费显存的,所以肯定先从这种“大户”下手啦!前面的DP和DDP,每块显卡都保存了完整的optimizer,互相都有冗余,能不能消除这个冗余了?比如集群有3块显卡,每块显卡只存1/3的optimizer状态?这就是ZeRO-1的思路!举个栗子:transformer不论decoer还是encoder,不是由一个个block上下叠加组成的么?比如有12个block、3块显卡,那么每块显卡存储4个block的optimizer,不就ok啦?

  • 数据照样均分成,N个显卡同时做forward和backward;N快显卡网络的初始参数都是一样的
  • foward时所有显卡可以并行(因为都存储和FP16的网络参数),然后各自计算loss和梯度
  • 最关键的就是BP了:现在每块显卡只存了部分optimizer,怎么做BP更新参数了?
    • 因为每块显卡都有完整的FP16网络参数,所以每块显卡都可以并且需要根据loss计算梯度
    • 最后4个block的optimizer是GPU2负责,所以GPU0和1并不更新这4个block的参数。但是更新参数涉及梯度啊,GPU2的loss和梯度信息不完整,这时就需要GPU0和1把自己计算的梯度信息发送给GPU2,整合后计算mean,用于更新最后4个block的参数!
    • 同理,中间4个block的梯度由GPU0和2发送给CPU1,GPU1整合后计算mean,用于更新中间4个block的参数!最前面4个block的梯度由GPU1和1发给GPU0,GPU0整合后计算mean再更新网络参数!
    • 3块显卡更新了各自负责block的网络参数,然后互相广播,至此:每块GPU的网络参数都是最新的了!

             

   通信量分析:和DDP是一样的,但是每块显卡节约了显存!最核心的就是每块显卡都把不属于自己负责那段网络的梯度发送给指定负责的网卡,并未盲目全体广播,此处节约了带宽!但因为每块显卡要广播更新后的网络参数,所以网络通信相比DDP并未减少!

        

   这个思路有点像国内的铁路局:国家在不同的区域分别设置了铁路局,每个局负责自己片区铁路线路的建设和运维;等建设完毕就把这个消息发送给其他铁路局,然后开通相应的运输路线!

  (2)既然每块GPU只负责更新部分参数,那是不是只保存对应的梯度也行了?这就是ZeRO-2的思路!

  • 数据照样均分成,N个显卡同时做forward和backward;N快显卡网络的初始参数都是一样的
  • foward时所有显卡可以并行(因为都存储和FP16的网络参数),然后各自计算loss和梯度
  • 做BP时:
    • GPU0和GPU1计算出最后4个block的梯度后发给GPU2,让GPU2更新optimizer和网络参数,这部分的梯度自己都丢弃,完全不存;
    • 其他两个block的参数做法类似,不再赘述
    • 最后3块显卡再互相广播更新后的网络参数

        

  (3)既然optimizer和梯度都可以只存部分,那参数是不是也可以了?这就是ZeRO-3的思路了!但这次的情况又有点不同:网络参数都不完整,怎么forward?这就只能依靠广播了,需要用到的时候让其他GPU发过来!

  • 数据均分成N份,同时做forward;但因为每块显卡的参数都不全,所以涉及到自己的时候要让其他显卡发过来;比如最前面4个block做forward,GPU0有,但是GPU1和2没有,就让GPU0广播;其他block同理,用的时候广播,用完就丢弃不存储
  • BP计算loss和梯度也要网络参数啊,咋办?同样还是广播的方式补全!

        

   这种思路本质是需要用到的时候让其他GPU配合发送过来,用完就删除不存储!用显卡之间的带宽换显存的空间!通行量如下:

      

   通行量是DDP的1.5倍,但是显存占用比DDP小了接近60倍!最后,来自ZeRO官方论文的总结对比:分别是DDP、ZeRO1/2/3阶段的显存消耗:

       

   实战中,一般采用ZeRO-2: 没有增加通行量,但是极大减少了显存的占用!

 

其他

1、以前做大数据,hadoop是标配,会安装、运维、调优甚至修改hadoop框架内部各种组件的研发很吃香,进大厂后工资都不低;同理,在以后AI时代,会安装、运维、调优甚至更改分布式训练/微调/推理框架的研发肯定更吃香,这绝对是个很不错的方向!

 2、显卡之间通信,涉及到参数传递的,会让显卡组成虚拟环,环内每个显卡的每个维度都依次给下一个显卡发送数据,直到每个显卡的参数都一样位置,这期间的经历称为scatter-reduce和all-gather!

    scatter-reduce:单个维度向下扩散依次累加;这里一看到reduce,就想起了10多年前因大数据爆火的map-reduce框架;这里的reduce功能和map-reduce的功能原理上一模一样,没本质区别!

        

   all-gather:单个完成的维度往下扩散,确保其他显卡该维度的数据是正确的!

       

   在Ring-allReduce中,每块显卡都在发送和接受数据,可以最大程度利用每块显卡的上下行带宽

 

参考:

1、https://www.bilibili.com/video/BV1LC4y1Y7tE/?spm_id_from=333.337.search-card.all.click&vd_source=241a5bcb1c13e6828e519dd1f78f35b2  deepspeed训练和推理

2、https://www.bilibili.com/video/BV1fb421t7KN/?spm_id_from=333.337.search-card.all.click&vd_source=241a5bcb1c13e6828e519dd1f78f35b2  分布式大模型训练

3、https://www.deepspeed.ai/     https://github.com/microsoft/DeepSpeed    https://www.deepspeed.ai/getting-started/   官网

4、https://www.bilibili.com/video/BV1ks4y1u7qr/?vd_source=241a5bcb1c13e6828e519dd1f78f35b2   DeepSpeed-Chat 模型训练实战

5、https://arxiv.org/pdf/1910.02054   ZeRO: Memory Optimizations Toward Training Trillion Parameter Models

6、https://www.bilibili.com/video/BV1mm42137X8/?spm_id_from=333.788.recommend_more_video.0&vd_source=241a5bcb1c13e6828e519dd1f78f35b2   DeepSpeed ZeRO技术

标签:显存,deepspeed,torch,参数,LLM,显卡,model,解析
From: https://www.cnblogs.com/theseventhson/p/18326382

相关文章

  • 麦克斯韦方程组解析——电磁理论的基石与奥秘
    麦克斯韦方程组解析——电磁理论的基石与奥秘麦克斯韦方程组的核心作用组件/步骤描述麦克斯韦方程组描述电磁场的基本方程组,由四个主要方程构成功能揭示电场、磁场与电荷、电流之间的关系,是电磁理论的基础应用领域广泛应用于电子学、光学、通信等领域其基本公式如下:高......
  • 大模型训练为何离不开GPU?深度解析与显卡推荐
    在人工智能的蓬勃发展中,大模型的训练成为了热门话题。然而,许多人还不清楚为什么训练这些庞大的模型需要GPU(图形处理单元)。本文将深入探讨GPU在大模型训练中的重要性,并推荐几款适合的显卡。一、GPU与CPU的区别在讨论大模型训练时,理解GPU(图形处理单元)与CPU(中央处理单元)之间的区......
  • Flutter网络错误全解析:当“A network error occurred“遇上“https://maven.google.co
    摘要:在Flutter开发过程中,我们经常需要从远程仓库获取依赖包,而https://maven.google.com/是Flutter依赖的主要来源之一。然而,开发者可能会遇到"Anetworkerroroccurredwhilechecking‘https://maven.google.com/’"的错误提示。本文将从资深Flutter开发专家的角度出发,......
  • 鸣潮PC端启动报错全面解析:卡顿、下载卡99%、黑屏、崩溃闪退的解决之道
    《鸣潮》作为一款备受期待的游戏,其独特的玩法和精美的画面吸引了大量玩家。然而,不少玩家在尝试体验这款游戏时,遇到了一系列令人头疼的技术问题,包括启动报错、游戏卡顿、下载进度卡在99%、黑屏、崩溃闪退等。这些问题不仅影响了游戏体验,也让玩家感到沮丧。本文将深入分析这些问......
  • 鸣潮游戏错误126:加载x3daudio1_7.dll失败的全面解析与修复指南
    在畅玩鸣潮游戏时,不少玩家可能会遭遇错误代码「126」,提示“加载x3daudio1_7.dll失败,该文件缺失或损坏”。这个问题看似棘手,实则有迹可循,通过本文,我们将深入探讨其成因,并提供详细的解决步骤,帮助你重拾游戏乐趣。x3daudio1_7.dll是什么?x3daudio1_7.dll是一个与DirectX音频组件......
  • 会员购项目面试题解析:高效数据抓取与异常处理
    会员购项目亮点日志记录信息协程异步抓取数据,大大提高抓取速度捕获异常,并添加重试机制源码importloggingimporttimeimportrequestsimportasyncioimportaiohttpfromaiohttpimportContentTypeErrorimportcsv#配置日志logging.basicConfig(level=logging......
  • C++ 数据结构体解析
    文章目录1.定义结构体2. 访问结构成员3. 结构作为函数参数4. 指向结构的指针5. typedef关键字1.定义结构体C/C++数组允许定义可存储相同类型数据项的变量,但是结构是C++中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。结构用于表示一条记......
  • 加州大学伯克利分校等发表的RouteLLM:利用偏好数据学习路由大语言模型
    加州大学伯克利分校等发表的RouteLLM:利用偏好数据学习路由大语言模型原创 无影寺 AI帝国 2024年07月18日08:03 广东一、结论写在前面论文标题:RouteLLM:LearningtoRouteLLMswithPreferenceData论文链接:https://arxiv.org/pdf/2406.18665v2LLM在广泛的任务中......
  • 如何使用 Pandas 解析函数处理 Excel 中的合并单元格?
    我有一个包含合并的列和行的Excel文件,我想读取该Excel文件并解析它以将其转换为DataFrame。这只是所发生情况的一个小示例,因为我拥有的真实数据非常多很大,有很多桌子。这就是Excel文件的样子:当我尝试时xl=pd.read_excel('file')我得到了这个:......
  • 如何在Python脚本中解析和打印来自pymeter的API响应和错误代码?
    如何在python脚本中解析和打印来自pymeter的API响应和错误代码?PyMeter是jmeter的python版本。(pymeter帮助文档-https://pymeter.readthedocs.io/en/latest/api.html)我正在尝试获取API的性能统计数据-https://reqres.in/api/users?page=2......