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

news/2024/11/15 11:14:23/文章来源:https://www.cnblogs.com/theseventhson/p/18326382

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

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

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

import torch
import numpy as npclass 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) / 255return 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技术

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Iterator与Iterable(迭代器与可迭代)

一 前言 环境: python 3.10 win10 二 Iterator(迭代器) Iterator 是python的内置类型之一,看下其定义该类型的实例对象称之为iterator(迭代器对象) 要得到一个iterator(迭代器对象),可用内置函数iter()将 list tuple等转成迭代器对象 也可以自定义一个迭代器类型的class,…

java基础 手写回忆篇

java 特性:分布行,跨平台性,安全行,多线程,面向对象编程,简单性 高级语言分为编译型和解释型: 编译型:整个程序写完一起编译速度快效率高 解释性:需要一句解释一句编译速度慢效率低 java是两者综合:编译器(javac)先把你写好的代码编译成class文件(字节码文件)再用j…

洛谷 Markdown - 从入门到精通

洛谷 Markdown - 从入门到精通 编写——Jerrycyx(CSDN,洛谷) 洛谷博客查看因为洛谷博客的渲染机制和其它地方不一样,可能导致渲染错误,所以你可以到这里食用:https://www.luogu.com.cn/paste/wu019n2x绪论希望更丰富的展现?使用 Markdown。这是洛谷文字编辑时会出现的一行…

Dev-C++ 的功能与外观优化

预备 安装 安装 Dev-C++ 5.11:官方下载:https://sourceforge.net/projects/orwelldevcpp/(若下载缓慢可选择 Problem Downloading->Auto-select) 蓝奏云下载:https://wwu.lanzouq.com/iTwwW07r28ni运行安装包即可。 更改语言 如果界面语言为英文,选择 Tools -> Env…

OI 中各种输入方式的速度比较(C++,大量实测数据,附图表)

测试信息 本次共测试了以下几种输入方式的速度:scanf cin 快读 位运算快读 fread() + 位运算快读 关闭同步流的 cin 开启 tie 绑定并关闭同步流的 cin每组测试各输入方式均使用相同数据,为随机生成的 \(1000000\)(1E6) 个整数,范围在 \([-(2^{31}-1),2^{31}-1]\)(即 int …

乒乓球比赛计分系统需求流程——最小可用产品

计应222_杜晓瑾_2210502012 乒乓球比赛计分系统需求流程——最小可用产品 作为一名裁判,我希望可以在系统上进行网络计时、记成绩,以便大家可以监督和观看任务 Sprint 1 2 3 4 5 6 7 8 9 10 开通网络计时(1h) 1 0 网络计时(4h) 4 2 1 0 进行网络记成绩(3h…

Easysearch、Elasticsearch、Amazon OpenSearch 快照兼容对比

在当今的数据驱动时代,搜索引擎的快照功能在数据保护和灾难恢复中至关重要。本文将对 Easysearch、Elasticsearch 和 Amazon OpenSearch 的快照兼容性进行比较,分析它们在快照创建、恢复、存储格式和跨平台兼容性等方面的特点,帮助大家更好地理解这些搜索引擎的差异,从而选…

管理项目更高效:顶级进度管理系统大比拼

国内外主流的10款项目进度管理网站对比:PingCode、Worktile、滴答清单(TickTick)、Todoist、NarTick、Teambition、Monday.com、Asana、ClickUp、Trello。在选择合适的项目进度管理工具时,许多项目经理面临着如何找到既能满足团队需求又易于操作的系统的难题。市场上众多的…

05 详细的中断讲解

目录前言一、什么是中断二、如何使用中断1.stm32中断结构1.1 AFIO中断引脚选择1.2 EXTI边缘检测1.3 NVIC优先级配置2.配置stm32的中断1.打开时钟2.配置GPIO口3.配置AFIO控制4.配置EXTI功能5.配置NVIC6.配置完整代码3.书写中断服务函数总结 前言 又鸽了几天的文章,最近在做一个…

VS2022创建C C++ GTEST工程

原因 需要对带代码进行单元测试,选择在Visual studio 中使用GTEST 框架。 实施 创建一个常规的控制台可执行程序。然后使用NUGET安装包 安装GTEST 头文件和动态库,同时安装GTEST ADAPTER。安装可能提示找不到包源,此时需要根据提示配置一下,注意通配符很关键,不要问为甚吗…

攻防世界-简单的图片

攻防世界-简单的图片 1 首先拿到图片是一张非常朴实的图片用010打开发现没什么特别之处因为是png图片猜测是lsb隐写,这里一开始我用的zsteg发现提取不全用Stegsolve打开看看,尝试了半天发现什么特殊信息,最后发现位平面顺序即bit plane order这一选项要选择BGR,得到下图发现…

Logisim-006-2路选择器16位

仓库地址 https://gitee.com/gitliang/logisim-to-cpu