LLM并行训练6-激活优化
前置知识
Activation
激活指的是一些在fp时计算得到的临时tensor, 会用于bp时的计算. 如果能在fp计算后把临时tensor缓存下来就可以加速bp, 缺点在于某些激活会占用大量显存. 以一层transformer结构为例分析下各层的存在的激活.

简单部分的分析这里忽略. 主要分析下几个不好理解的计算:
- \(QK^T\): 需要缓存Q输出和K输出 \(2sbh+2sbh\)
- \(softmax\): a个head, 每个head进行s次softmax概率分类, 一共有b*s个token, 输出为fp16, 所以是\(2 * a * s * (s * b) = 2as^2b\)
- \(dropout\): 需要缓存一个mask数组标记哪些位置的token需要被反向更新. 所以每个位置只需要bool, 所以是\(a * s * (s * b) = as^2b\)
如果激活的临时显存全部不释放, 每个transformerLayer需要占用的量级为\(sbh(34+5\frac{as}{h})\) (不包含任何并行加速的情况)

选择性激活重计算
重计算部分: 以GPT-3为例: a = 96, s = 2048, and h = 12288, 5as/h = 80. 占了总激活显存的70%左右的主要是attention里的部分比如: softmax的输出, dropout的mask, dropout的输出. 这些部分的激活都跟矩阵乘法没关系, 所需要的计算量很小. 把这些在前向计算完之后直接释放显存, 在bp用的时候再重计算的话就能以很小的计算代价换取70%的显存存储.
拆分存储部分: 而比如像线性层的中间输出, Q,K,V这些中间tensor. 因为前面有一个和W的矩阵乘法, 重计算代价巨大更适合临时缓存起来用于bp计算复用. 这些激活通过张量并行和序列并行把他们拆分存储, 在bp使用的时候通过集合通信的方式再拉取, 来减少显存消耗.
张量并行共计24sbh: 并行后拆分为t份的包括:
- attention部分: QKV的输入2sbh, \(QK^T\)结果4sbh, 和V相乘结果里的2sbh. 共计8sbh
- MLP部分: 两个8sbh, 因为TP的先列在行的计算方法全部平分成了t份, 共计16sbh

序列并行共计10sbh, 拆分成t份的包括:
- attention部分: layerNorm的输入输出4sbh, dropout的mask sbh
- MLP部分: layerNorm的输入输出4sbh, dropout的mask sbh
序列并行
序列并行指的是在 Transformer 层的非张量并行切分部分,计算在序列维度(s)是独立的. 所以在这个维度上以切分数量和张量并行数相同的方式进行切分.
为什么切分数要和TP相等呢? 回忆下TP后如何汇聚计算结果, 在行并行后allReduce, 把每张卡各自计算的结果纵向拼起来还原成完整的输入. 如果我们想把这部分完整的输入进行切分存储, 如果切分数量和TP不一致意味着在$\bar{g} $ 这个地方在allReduce之后还要再进行一次reduceScatter进行分割, 在\(g\)地方再allGather..
而如果切分数和TP相等, 在\(\bar{g}\)这里可以把allReduce直接省掉, 相当于把allReduce拆分的两个操作. 在通信量保持不变的情况下分离了layerNorm的激活

Zero-R
激活分区 & checkpointing
这里提到在模型并行的时候activation会存在冗余副本. 这里应该就是指的是TP输入的冗余副本. 论文里是说到会把激活给partition多份..其实我感觉实现方法和megatron里的序列并行就是一样的, 标记一下等细看deepspeed代码的时候再确认下.
另外还提到个新方法利用内存来存激活checkpoint, 想了下应该是类似下图的步骤
- 在最初始的几层fp和bp的时间间隔比较远, 适合在做完fp后memcpyAsync到内存.
- 靠近loss的后面几层激活还存在显存里, 在bp的时候直接用完就释放了. 快到cpu激活的部分通过memcpyAsync回来.
- 如果训练和copy激活能使用同一个stream, 那么这块就不需要同步, 按流的顺序实行即可

恒定大小显存缓冲区
像all-reduce这些集合通信操作, 在一次通信一批很大的数据效率很高. 但缺点是会分配大量的临时显存, 这样会导致显存出现较大波动, 在大模型场景会出现问题.
所以zero在这块设定了一个固定的buffer_size, 超过buffer_size的时候分批次通信. cpu激活checkpointing的copy应该也需要相应的方式.
其实在写flux-gpu的sparse拷贝的时候也用了类似的方法..分批次拷贝来避免单次的超大通信和小数据的碎片通信.
显存碎片解决方法
显存碎片产生的原因: 在fp的时候只有一部分激活储存下来用于bp, 另外一些需要在bp重算的激活被释放了.就会导致一部分显存的使用周期很长, 另一部分很短, 从而产生显存碎片. 会导致两个问题: 1. 显存allocator查找满足大小的显存块效率很低 2. 可能会出现大块连续显存分配不出来.
论文里说会给activation和grad预分配好连续显存块..emm, 这个做法看着和llm.c里的实现是一样的, 其实在大模型里大部分的w/grad/activation在运行的时候都是定长的, 我们完全可以在第一次运行的时候全部分配好. 在网络计算的时候避免显存分配. 如果不使用allocator就不会有碎片问题.
Zero-Offload
主要用来解决模型规模远大于显存规模的问题, 看着灰常似曾相识, 和部署在本地内存的参数服务器很像. 感觉区别在于2点: 1. 训练是同步的, gpu在cpu更新optimizer_state的时候只能处于等待状态 2.内存里保存全量的参数, 不进行多机通信(多机通信应该会让本来就慢的cpu更加雪上加霜吧haha).
计算策略
- 保证 CPU 的计算负担远远小于 GPU,从而防止 CPU 成为计算瓶颈;保证 GPU 的内存节省最大;(optimizer_state是最占显存的同时, 也是不需要反复计算的, 一个batch里只需要存取一次, 不像fp16的w一样还会参与反向的梯度计算)
- 保证 CPU 和 GPU 之间的通信量最小;(在通信的时候进行量化和反量化)

调度策略
offload采用的时候zero2方案, 也就是fp16 w是分卡存储的. 考虑使用zero2的很重要的一个原因我猜测是在于多卡可以同时copy w, 而且没有冗余数据通信. 避免pciE带宽拖后腿.
下图是单卡的数据流, swap的部分论文里画错了应该是CPU->GPU, 通信和计算异步的地方主要有2处:
- g offload, 是在gpu bp的时候每计算完一层的g就async copy到内存
- p swap, cpu更新完一批w, 就分块进行量化和async copy到显存.

Fp16 w到了显存里后就和不同的zero2计算流程完全一样了.
后面还有一个和推荐模型cpu异步训练类似的cpu操作全隐藏训练模式, 只不过区别是把异步训练的n个batch对齐dense改成了固定1个batch.

Zero-Offload++
在第一版offload的时候, 所有的参数都是在cpu计算的, 上面也说到了. 在cpu计算的时候gpu只能空等, 如何在空等的时间窗口把gpu利用起来是一个很大的问题. offload++给了一个很棒的思路. 设置了一个os_w的存储比例, 以图示为例, 有40%的os_w存在内存里由cpu更新, 剩下的60%由gpu更新. 步骤如下:
- 在bp完靠上层40%的网络后, 把g往内存copy
- CPU开始逐步计算已经拉下来的g, 更新os_w. 把属于自己更新的那部分算完
- 到达属于GPU更新的部分后, GPU allScatter 剩下的60% grad到os_w存储的对应卡上, 更新显存里的os_w
- 等cpu算完后量化的fp16_w copy回显存和显存里的fp16_w合并, 进行下一轮计算

这里的比值是人工设置的, 设置原理就是在尽量把显存用满的前提下尽可能的往GPU塞os_w, 塞不下的再放内存里. 这个思路感觉超棒, 待细看代码
参考
Megatron-LM论文: https://arxiv.org/pdf/2205.05198
zero-R论文: https://arxiv.org/abs/1910.02054
zero-offload: https://www.usenix.org/system/files/atc21-ren-jie.pdf
zero-offload++博客: https://github.com/microsoft/DeepSpeed/tree/master/blogs/deepspeed-offloadpp
LLM并行训练6-激活优化的更多相关文章
- ML2021 | (腾讯)PatrickStar:通过基于块的内存管理实现预训练模型的并行训练
前言 目前比较常见的并行训练是数据并行,这是基于模型能够在一个GPU上存储的前提,而当这个前提无法满足时,则需要将模型放在多个GPU上.现有的一些模型并行方案仍存在许多问题,本文提出了一种名为 ...
- PyTorch如何加速数据并行训练?分布式秘籍大揭秘
PyTorch 在学术圈里已经成为最为流行的深度学习框架,如何在使用 PyTorch 时实现高效的并行化? 在芯片性能提升有限的今天,分布式训练成为了应对超大规模数据集和模型的主要方法.本文将向你介绍 ...
- [Pytorch框架] 4.5 多GPU并行训练
文章目录 4.5 多GPU并行训练 4.5.1 torch.nn.DataParalle 4.5.2 torch.distributed 4.5.3 torch.utils.checkpoint im ...
- Pytorch:单卡多进程并行训练
1 导引 我们在博客<Python:多进程并行编程与进程池>中介绍了如何使用Python的multiprocessing模块进行并行编程.不过在深度学习的项目中,我们进行单机多进程编程时一 ...
- tensorflow 13:多gpu 并行训练
多卡训练模式: 进行深度学习模型训练的时候,一般使用GPU来进行加速,当训练样本只有百万级别的时候,单卡GPU通常就能满足我们的需求,但是当训练样本量达到上千万,上亿级别之后,单卡训练耗时很长,这个时 ...
- .Net中的并行编程-6.常用优化策略
本文是.Net中的并行编程第六篇,今天就介绍一些我在实际项目中的一些常用优化策略. 一.避免线程之间共享数据 避免线程之间共享数据主要是因为锁的问题,无论什么粒度的锁 ...
- [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush
[源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 目录 [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 0x0 ...
- [源码解析] PyTorch分布式优化器(3)---- 模型并行
[源码解析] PyTorch分布式优化器(3)---- 模型并行 目录 [源码解析] PyTorch分布式优化器(3)---- 模型并行 0x00 摘要 0x01 前文回顾 0x02 单机模型 2.1 ...
- MySQL 并行复制演进及 MySQL 8.0 中基于 WriteSet 的优化
MySQL 8.0 可以说是MySQL发展历史上里程碑式的一个版本,包括了多个重大更新,目前 Generally Available 版本已经已经发布,正式版本即将发布,在此将介绍8.0版本中引入的一 ...
- Java 进阶7 并发优化 1 并行程序的设计模式
本章重点介绍的是基于 Java并行程序开发以及优化的方法,对于多核的 CPU,传统的串行程序已经很好的发回了 CPU性能,此时如果想进一步提高程序的性能,就应该使用多线程并行的方式挖掘 CPU的 ...
随机推荐
- Vue 3 组件基础与模板语法详解
title: Vue 3 组件基础与模板语法详解 date: 2024/5/24 16:31:13 updated: 2024/5/24 16:31:13 categories: 前端开发 tags: ...
- WPF+Emgucv实现在图像上画出感兴趣的区域 并进行掩膜获取 得到图像均值 和简单的 漫水填充
<Grid.RowDefinitions></Grid.RowDefinitions> <Grid> <UniformGrid Columns="2 ...
- WPF基础之样式设置和模板化(三)
IsItemsHost 属性在此示例中,一个必需的重要属性是 IsItemsHost 属性.IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模 ...
- Redis单线程
Redis是基于Reactor模式开发的网络事件处理器,这个处理器是单线程的,所 以redis是单线程的. 为什么它是单线程还那么快呢? 主要有以下几个原因: 一.纯内存操作 由于Redis是纯内存操 ...
- ftp和tftp有什么区别
TFTP和FTP都是文件传输协议,但它们在很多方面存在明显的区别. 安全性:FTP协议使用的是明文传输,而TFTP协议使用的是UDP协议,没有使用TCP,所以不提供验证. 传输方式:FTP协议使用的是 ...
- Qt--ui的简单使用(1)
1 说明 本文主要介绍Qt ui界面的简单使用. 参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=22 2 工程创建 创建工程时,把创建界面给 ...
- C# 利用Autofac批量接口注入依赖【学习记录】
背景: 本人在一位大佬的Colder框架中看到了这个接口注入,然后呢就想学习一下ioc思想与di设计模式.此写法给我的感觉就是 非常的 优雅 ,优雅永不过时.关于接口注入的概念和ioc和di具体是什么 ...
- Nodejs fs模块 全局变量
fs模块提供了用于与文件进行交互相关方法 const fs = require('fs') # 写入数据 fs.writeFile(文件,数据,err=>{}) # 读取文件中数据 fs.rea ...
- INFINI Gateway 如何防止大跨度查询
背景 业务每天生成一个日期后缀的索引,写入当日数据. 业务查询有时会查询好多天的数据,导致负载告警. 现在想对查询进行限制--只允许查询一天的数据(不限定是哪天),如果想查询多天的数据就走申请. 技术 ...
- js中字符串的方法,17种方法
字符串的17种方法...... 1.length:返回字符串的长度. const str = "Hello, World!"; console.log(str.length); / ...