多卡训练调研
随着模型尺寸和训练数据的大幅增加,只用一个GPU跑模型,速度会极慢,分布式训练在多个机器上一起训练模型,能极大的提高训练效率。
分布式训练有两种思路:
- 一是模型并行,将一个模型分拆成多个小模型,分别放在不同的设备上,每个设备只跑模型的一部分。由于模型的各个部分计算存在前后依赖,需要频繁通信,同步比较耗时,因此效率很低。
- 二是数据并行,完整的模型在每个机器上都有,把数据分成多份给到每个模型,每个模型在不同的数据上进行训练。数据并行目前是应用的较多的一种并行方法。
pytorch分布式训练
Pytorch中的多GPU并行计算是数据级并行,相当于开了多个进程,每个进程自己独立运行,然后再整合在一起。
DataParallel【只支持单机多卡】
简介
首先在前向过程中,你的输入数据会被划分成多个子部分(以下称为副本)送到不同的device
中进行计算,而你的模型module
是在每个device
上进行复制一份,也就是说,输入的batch
是会被平均分到每个device
中去,但是你的模型module
是要拷贝到每个devide
中去的,每个模型module
只需要处理每个副本即可,当然你要保证你的batch size
大于你的gpu
个数。然后在反向传播过程中,每个副本的梯度被累加到原始模块中。概括来说就是:DataParallel
会自动帮我们将数据切分 load
到相应 GPU
,将模型复制到相应 GPU
,进行正向传播计算梯度并汇总。
基本原理及固有缺陷:在 Data Parallel 模式下,数据会被自动切分,加载到 GPU。同时,模型也将拷贝至各个 GPU 进行正向传播。在多个进程之间,会有一个进程充当 master 节点,负责收集各张显卡积累的梯度,并据此更新参数,再统一发送至其他显卡。因此整体而言,master 节点承担了更多的计算与通信任务,且随 GPU 数目增多,负担还会进一步加重。这就容易造成网络堵塞,影响训练速度。
代码示例
# 函数定义
torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
'''
module : 模型
device_ids : 参与训练的 GPU 列表
output_device : 指定输出的 GPU, 通常省略, 即默认使用索引为 0 的显卡
'''
# 程序模板
device_ids = [0, 1]
net = torch.nn.DataParallel(net, device_ids=device_ids)
Distributed Data Parallel【支持单机多卡和多级多卡】
DDP
基本原理:倘若我们拥有 N 张显卡,则在 Distributed Data Parallel 模式下,就会启动 N 个进程。每个进程在各自的卡上加载模型,且模型的参数完全相同。训练过程中,各个进程通过一种名为 Ring-Reduce 的方式与其他进程通信,交换彼此的梯度,从而获得所有的梯度信息。随后,各个进程利用梯度的平均值更新参数。由于初始值和更新量完全相同,所以各个进程更新后的参数仍保持一致。
DDP与DP对比
-
DDP
可用于多机多卡训练,DP
却不行; -
DDP
效率更高,可以达到略低于显卡数目的加速比; -
DDP
能够实现负载的均匀分配,克服了DP
需要一个进程充当 master 节点的固有缺陷; -
采用
DDP
通常可以支持更大的 batch size,不会像DP
那样出现其他显卡尚有余力,而卡 0 直接 out of memory 的情况; -
另外,在
DDP
模式下,输入到 data loader 的 bacth size 不再代表总数,而是每块 GPU 各自负责的 sample 数量。比方说,batch size = 30,有两块 GPU。在DP
模式下,每块 GPU 会负责 15 个样本。而在DDP
模式下,每块 GPU 会各自负责 30 个样本;
DeepSpeed ZeRO【支持单机多卡和多机多卡】
DeepSpeed的核心是ZeRO(Zero Redundancy Optimizer),简单来说,它是一种显存优化的数据并行(data parallelism, DP)方案。而“优化“这个话题又永无止境,在过去两年DeepSpeed团队发表了三篇ZeRO相关的论文,提出了去除冗余参数、引入CPU和内存、引入NVMe等方法,从始至终都围绕着一个目标:将显存优化进行到底。
背景
Adam在SGD基础上,为每个参数梯度增加了一阶动量(momentum)和二阶动量(variance)。
混合精度训练,字如其名,同时存在fp16和fp32两种格式的数值,其中模型参数、模型梯度都是fp16,此外还有fp32的模型参数,如果优化器是Adam,则还有fp32的momentum和variance。
ZeRO将模型训练阶段,每张卡中显存内容分为两类:
- 模型状态(model states): 模型参数(fp16)、模型梯度(fp16)和Adam状态(fp32的模型参数备份,fp32的momentum和fp32的variance)。假设模型参数量 Φ ,则共需要 2Φ+2Φ+(4Φ+4Φ+4Φ)=4Φ+12Φ=16Φ 字节存储,可以看到,Adam状态占比 75% 。
- 剩余状态(residual states): 除了模型状态之外的显存占用,包括激活值(activation)、各种临时缓冲区(buffer)以及无法使用的显存碎片(fragmentation)。
来看一个例子,GPT-2含有1.5B个参数,如果用fp16格式,只需要3GB显存,但是模型状态实际上需要耗费24GB!相比之下,激活值可以用activation checkpointing来大大减少,所以模型状态就成了头号显存杀手,它也是ZeRO的重点优化对象。而其中Adam状态又是第一个要被优化的。
- ZeRo-1,对Adam优化器状态进行分区,模型参数和梯度依然是每个设备保存一份
- ZeRo-2, 对优化器状态和模型梯度进行分区,模型参数是每个设备保存一份
- ZeRo-3, 对优化器状态,模型梯度和模型参数进行分区
DeepSpeed单机多卡训练和多级多卡训练代码见官方文档
参考资料
PyTorch : Distributed Data Parallel 详解
基于triton 推理框架分布式推理
简介
Triton 是 Nvidia 发布的一个高性能推理服务框架,可以帮助开发人员高效轻松地在云端、数据中心或者边缘设备部署高性能推理服务。
Triton Server 可以提供 HTTP/gRPC 等多种服务协议。同时支持多种推理引擎后端,如:TensorFlow, TensorRT, PyTorch, ONNXRuntime 等。Server 采用 C++ 实现,并采用 C++ API调用推理计算引擎,保障了请求处理的性能表现。
在推理计算方面,Triton 支持多模型并发,动态batch等功能,能够提高GPU的使用率,改善推理服务的性能。Triton不仅支持单模型部署,也支持多模型集成(ensemble),可以很好的支持多模型联合推理的场景,构建起视频、图片、语音、文本整个推理服务过程,大大降低多个模型服务的开发和维护成本。
Triton的优势
与其他一些模型服务化工具相比,Triton具备如下的优势:
- 支持多种框架:Triton 支持几乎所有主流的训练和推理框架,例如: TensorFlow、NVIDIA TensorRT、PyTorch、Python、ONNX、XGBoost、scikit-learn RandomForest、OpenVINO、自定义 C++ 等。
- 高性能模型推理:Triton 支持所有基于 NVIDIA GPU、x86、Arm CPU 和 AWS Inferentia 的推理。 它提供动态batching、并发执行、最佳模型配置、模型集成(ensemble)和流式音频/视频输入,以最大限度地提高吞吐量和利用率。
- 专为 DevOps 和 MLOps 而设计:Triton 可以与 Kubernetes 集成以进行模型服务编排和扩展,支持导出用于监控的 Prometheus 指标,支持实时模型更新,并可用于所有主流的公有云 AI 和 Kubernetes 平台。 它还被集成到了许多 MLOps 软件解决方案中。
- 支持模型集成:由于大多数模型推理需要为单个查询执行具有预处理和后处理的多个模型;因此, Triton 支持模型集成和流水线。 Triton 可以在 CPU 或 GPU 上执行集成(ensemble)的各个部分,并可以在集成(ensemble)中使用多个框架。
- 具备企业级的安全性及 API 稳定性:用于生产环境推理的 NVIDIA Triton,通过企业级的安全性和 API 稳定性加速企业走向 AI 的前沿,同时降低开源软件的潜在风险。
Triton架构
Triton推理服务的大体流程为推理请求通过 HTTP/REST、GRPC 或 C API 到达推理服务,然后路由到每个模型相关的调度队列中。 Triton 实现了多种调度和批处理算法,可以在每个模型上面进行配置。 每个模型的调度器可选择执行推理请求的批处理,然后将请求传递给与模型类型对应的后端。后端使用批处理请求中提供的输入执行推理以生成请求的输出。 然后返回输出。
下面是Triton生态的重要组成组件和重要特性:
第一,Triton需要一个模型仓库,一个基于文件系统的模型存储库,Triton 将使其可用于推理。
第二,Triton 支持 C API 后端,允许 Triton 扩展新功能,例如:自定义预处理和后处理操作,甚至是新的深度学习框架。
第三,Triton 提供的模型可以通过专门的模型管理 API 进行查询和控制,该 API 可通过 HTTP/REST 或 GRPC 协议或 C API 获得。
第四,Triton 服务中的准备就绪(readiness)和存活(liveness)健康接口、利用率、吞吐量和延迟指标简化了 Triton 与部署框架(如:Kubernetes)的集成。
另外,Triton 服务架构允许多个模型或同一模型的多个实例在同一系统上并行执行,具体如下图所示, 系统可能有零个、一个或多个 GPU 卡。
模型类型及调度器
Triton定义了三种模型类型:无状态(stateless)、有状态(stateful)和集成(ensemble)模型,同时,Triton 提供了调度器来支持这些模型类型。
模型类型
Triton中三种模型类型如下:
- 无状态模型(Stateless models):简单来说就是应对不同推理请求没有相互依赖的情况。平常遇到的大部分模型都属于这一类模型,比如:文本分类、实体抽取、目标检测等。
- 有状态模型(Stateful Models):当前的模型输出依赖上一刻的模型的状态(比如:中间状态或输出)。对于推理服务来说,就是不同推理请求之间有状态依赖,比如:顺序依赖。
- 集成模型(Ensemble model):表示一个或多个模型组成的工作流(有向无环图)。最常见的使用场景就是:数据预处理->模型推理->数据后处理。通过集成模型可以避免传输中间tensor的开销,并且可以最小化请求次数。比如:
bert
实现的文本分类任务,需要在前置处理中对输入文本做Tokenizer,Tokenizer输出结果作为模型属性输入。如下所示,preprocess表示前置处理模型;bert_onnx表示文本分类模型;ensemble_bert_classification表示集成模型。
调度器
Triton提供了如下几种调度策略:
- Default Scheduler:默认调度策略,可以理解为推理框架内部的模型实例负载,将推理请求按比例分配到不同的模型实例执行推理。
- Dynamic Batcher:在应对高吞吐的场景,将一定时间内的请求聚合成一个批次(batch)。该调度器用于调度无状态的模型。
- Sequence Batcher:类似Dynamic batcher,Sequence batcher也是动态聚合推理请求。区别是Sequence batcher应用于有状态模型,并且一个序列的请求只能路由到同一个模型实例。
- Ensemble Scheduler:只能调度集成模型,不能用于其他类型的模型。