使用英特尔技术加速PyTorch分布式微调
Use Intel technology to accelerate PyTorch distributed fine-tuning.
尽管它们表现出色,但现代深度学习模型通常需要很长时间来训练。为了加快训练作业的速度,工程团队依赖于分布式训练,这是一种分而治之的技术,其中集群服务器每个都保留模型的副本,在训练集的子集上训练它,并交换结果以收敛到最终模型。
图形处理单元(GPU)长期以来一直是训练深度学习模型的事实选择。然而,迁移学习的兴起正在改变游戏规则。现在,模型很少从头开始在庞大的数据集上训练。相反,它们经常在特定(较小)的数据集上进行微调,以构建更适用于特定任务的专门模型,这些模型比基础模型更准确。由于这些训练作业时间较短,使用基于CPU的集群可能是一个既能控制训练时间又能控制成本的有趣选择。
这篇文章是关于什么的
在本文中,您将学习如何通过在由冰湖架构驱动的Intel Xeon可扩展CPU服务器集群上进行分布式训练来加速PyTorch训练作业,这些服务器运行性能优化的软件库。我们将使用虚拟机从头开始构建集群,您应该能够轻松地在自己的基础设施上复制演示,无论是在云端还是在本地。
我们将在MRPC数据集(GLUE基准测试中包含的任务之一)上微调BERT模型来运行文本分类作业。MRPC数据集包含从新闻来源中提取的5800个句子对,带有一个标签,告诉我们每对句子是否在语义上等价。我们选择这个数据集是因为它的训练时间合理,并且尝试其他GLUE任务只需要调整一个参数。
一旦集群运行起来,我们将在单个服务器上运行一个基准作业。然后,我们将扩展到2台服务器和4台服务器,并测量加速比。
在此过程中,我们将涵盖以下主题:
- 列出所需的基础设施和软件构建模块
- 设置我们的集群
- 安装依赖项
- 运行单节点作业
- 运行分布式作业
让我们开始工作吧!
使用Intel服务器
为了获得最佳性能,我们将使用基于冰湖架构的Intel服务器,该架构支持硬件功能,如Intel AVX-512和Intel向量神经网络指令(VNNI)。这些功能加速了深度学习训练和推理中通常出现的操作。您可以在此演示文稿(PDF)中了解更多信息。
所有三个主要的云提供商都提供基于Intel冰湖CPU的虚拟机:
- 亚马逊网络服务:Amazon EC2 M6i和C6i实例
- Azure:Dv5/Dsv5系列,Ddv5/Ddsv5系列和Edv5/Edsv5系列虚拟机
- 谷歌云平台:N2计算引擎虚拟机
当然,您也可以使用自己的服务器。如果它们基于冰湖架构(冰湖的前身),那么它们也可以使用,因为冰湖还包括AVX-512和VNNI。
使用Intel性能库
为了利用PyTorch中的AVX-512和VNNI,Intel设计了适用于PyTorch的Intel扩展。该软件库提供了训练和推理的即插即用加速,因此我们应该安装它。
在分布式训练方面,主要的性能瓶颈通常是网络。实际上,集群中的不同节点需要定期交换模型状态信息以保持同步。由于transformer是具有数十亿参数的大型模型(有时甚至更多),信息量是巨大的,随着节点数量的增加,情况只会变得更糟。因此,使用为深度学习优化的通信库非常重要。
事实上,PyTorch包括torch.distributed包,支持不同的通信后端。在这里,我们将使用Intel oneAPI集体通信库(oneCCL),它是一种高效的深度学习通信模式(全约简等)的实现。您可以在这篇PyTorch博文中了解oneCCL与其他后端的性能比较。
既然我们对构建模块有了清晰的认识,让我们谈谈训练集群的整体设置。
设置我们的集群
在此演示中,我使用运行Amazon Linux 2的Amazon EC2实例(c6i.16xlarge,64个虚拟CPU,128GB内存,25Gbit/s网络)。在其他环境中设置将有所不同,但步骤应该非常相似。
请记住,您将需要4个相同的实例,因此您可能希望计划某种自动化措施,以避免重复运行相同的设置4次。在这里,我将手动设置一个实例,从该实例创建一个新的Amazon Machine Image(AMI),并使用此AMI启动三个相同的实例。
从网络的角度来看,我们需要进行以下设置:
- 在所有实例上打开端口22以进行
ssh
访问进行设置和调试。 - 在主实例(您将从中启动训练的实例)和所有其他实例之间配置无密码
ssh
。 - 在所有实例上打开所有TCP端口以在集群内进行oneCCL通信。请确保不要向外部世界开放这些端口。AWS提供了一种方便的方法,只允许来自运行特定安全组的实例的连接。这是我的设置方式。
现在,让我们手动准备第一个实例。我首先创建实例本身,附加上述安全组,并添加128GB的存储。为了优化成本,我将其作为spot实例启动。
实例启动后,我使用ssh
连接到它以安装依赖项。
安装依赖项
以下是我们将遵循的步骤:
- 安装Intel工具包,
- 安装Anaconda发行版,
- 创建一个新的
conda
环境, - 安装PyTorch和用于PyTorch的Intel扩展,
- 编译和安装oneCCL,
- 安装
transformers
库。
看起来很多,但没有什么复杂的。我们开始吧!
安装Intel工具包
首先,我们下载并安装Intel OneAPI基础工具包以及AI工具包。您可以在Intel网站上了解它们。
wget https://registrationcenter-download.intel.com/akdlm/irc_nas/18236/l_BaseKit_p_2021.4.0.3422_offline.sh
sudo bash l_BaseKit_p_2021.4.0.3422_offline.sh
wget https://registrationcenter-download.intel.com/akdlm/irc_nas/18235/l_AIKit_p_2021.4.0.1460_offline.sh
sudo bash l_AIKit_p_2021.4.0.1460_offline.sh
安装Anaconda
然后,我们下载并安装Anaconda发行版。
wget https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh
sh Anaconda3-2021.05-Linux-x86_64.sh
创建一个新的conda环境
我们退出并重新登录以刷新路径。然后,我们创建一个新的conda
环境以保持整洁。
yes | conda create -n transformer python=3.7.9 -c anaconda
eval "$(conda shell.bash hook)"
conda activate transformer
yes | conda install pip cmake
安装PyTorch和用于PyTorch的Intel扩展
接下来,我们安装PyTorch 1.9和Intel扩展工具包。版本必须匹配。
yes | conda install pytorch==1.9.0 cpuonly -c pytorch
pip install torch_ipex==1.9.0 -f https://software.intel.com/ipex-whl-stable
编译和安装oneCCL
然后,我们安装一些编译oneCCL所需的本机依赖项。
sudo yum -y update
sudo yum install -y git cmake3 gcc gcc-c++
首先,我们克隆oneCCL存储库,构建库并安装它。 再次强调,版本必须匹配。
source /opt/intel/oneapi/mkl/latest/env/vars.sh
git clone https://github.com/intel/torch-ccl.git
cd torch-ccl
git checkout ccl_torch1.9
git submodule sync
git submodule update --init --recursive
python setup.py install
cd ..
安装transformers库
接下来,我们安装transformers
库和运行GLUE任务所需的依赖项。
pip install transformers datasets
yes | conda install scipy scikit-learn
最后,我们克隆transformers
存储库的一个分支,其中包含我们将要运行的示例。
git clone https://github.com/kding1/transformers.git
cd transformers
git checkout dist-sigopt
我们完成了!让我们运行一个单节点作业。
启动单节点作业
为了获得基准线,让我们在transformers/examples/pytorch/text-classification
中运行run_glue.py
脚本,启动一个单节点作业。这应该适用于任何实例,并且在进行分布式训练之前,它是一个很好的健全性检查。
python run_glue.py \
--model_name_or_path bert-base-cased --task_name mrpc \
--do_train --do_eval --max_seq_length 128 \
--per_device_train_batch_size 32 --learning_rate 2e-5 --num_train_epochs 3 \
--output_dir /tmp/mrpc/ --overwrite_output_dir True
这个作业需要7分钟46秒。现在,让我们使用oneCCL设置分布式作业并加快速度!
使用oneCCL设置分布式作业
运行分布式训练作业需要三个步骤:
- 列出训练集群的节点,
- 定义环境变量,
- 修改训练脚本。
列出训练集群的节点
在主节点的transformers/examples/pytorch/text-classification
中,我们创建一个名为hostfile
的文本文件。这个文件存储了集群中各个节点的名称(IP地址也可以)。第一行应该指向主节点。
这是我的文件内容:
ip-172-31-28-17.ec2.internal
ip-172-31-30-87.ec2.internal
ip-172-31-29-11.ec2.internal
ip-172-31-20-77.ec2.internal
定义环境变量
接下来,我们需要在主节点上设置一些环境变量,特别是它的IP地址。您可以在文档中找到有关oneCCL变量的更多信息。
for nic in eth0 eib0 hib0 enp94s0f0; do
master_addr=$(ifconfig $nic 2>/dev/null | grep netmask | awk '{print $2}'| cut -f2 -d:)
if [ "$master_addr" ]; then
break
fi
done
export MASTER_ADDR=$master_addr
source /home/ec2-user/anaconda3/envs/transformer/lib/python3.7/site-packages/torch_ccl-1.3.0+43f48a1-py3.7-linux-x86_64.egg/torch_ccl/env/setvars.sh
export LD_LIBRARY_PATH=/home/ec2-user/anaconda3/envs/transformer/lib/python3.7/site-packages/torch_ccl-1.3.0+43f48a1-py3.7-linux-x86_64.egg/:$LD_LIBRARY_PATH
export LD_PRELOAD="${CONDA_PREFIX}/lib/libtcmalloc.so:${CONDA_PREFIX}/lib/libiomp5.so"
export CCL_WORKER_COUNT=4
export CCL_WORKER_AFFINITY="0,1,2,3,32,33,34,35"
export CCL_ATL_TRANSPORT=ofi
export ATL_PROGRESS_MODE=0
修改训练脚本
我们已经对训练脚本(run_glue.py
)进行了以下更改,以启用分布式训练。当您使用自己的训练代码时,您需要进行类似的更改。
- 导入
torch_ccl
包。 - 接收主节点的地址和集群中节点的本地等级。
+import torch_ccl
+
import datasets
import numpy as np
from datasets import load_dataset, load_metric
@@ -47,7 +49,7 @@ from transformers.utils.versions import require_version
# 如果没有安装 Transformers 的最低版本,将会出错。自行决定是否删除。
-check_min_version("4.13.0.dev0")
+# check_min_version("4.13.0.dev0")
require_version("datasets>=1.8.0", "修复方法:pip install -r examples/pytorch/text-classification/requirements.txt")
@@ -191,6 +193,17 @@ def main():
# 或通过在此脚本中传递 --help 标志。
# 现在我们保留不同的参数集,以更清晰地分离功能。
+ # 为 CPU 分布式添加本地等级
+ sys.argv.append("--local_rank")
+ sys.argv.append(str(os.environ.get("PMI_RANK", -1)))
+
+ # CCL 特定的环境变量
+ if "ccl" in sys.argv:
+ os.environ["MASTER_ADDR"] = os.environ.get("MASTER_ADDR", "127.0.0.1")
+ os.environ["MASTER_PORT"] = "29500"
+ os.environ["RANK"] = str(os.environ.get("PMI_RANK", -1))
+ os.environ["WORLD_SIZE"] = str(os.environ.get("PMI_SIZE", -1))
+
parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments))
if len(sys.argv) == 2 and sys.argv[1].endswith(".json"):
设置已经完成。现在让我们将训练作业扩展到2个节点和4个节点。
使用 oneCCL 运行分布式作业
在主节点上,我使用mpirun
启动一个2节点作业:-np
(进程数)设置为2,-ppn
(每个节点的进程数)设置为1。因此,将选择hostfile
中的前两个节点。
mpirun -f hostfile -np 2 -ppn 1 -genv I_MPI_PIN_DOMAIN=[0xfffffff0] \
-genv OMP_NUM_THREADS=28 python run_glue.py \
--model_name_or_path distilbert-base-uncased --task_name mrpc \
--do_train --do_eval --max_seq_length 128 --per_device_train_batch_size 32 \
--learning_rate 2e-5 --num_train_epochs 3 --output_dir /tmp/mrpc/ \
--overwrite_output_dir True --xpu_backend ccl --no_cuda True
几秒钟后,作业在前两个节点上开始运行。作业完成所需时间为4分钟39秒,加速比为1.7x。
将-np
设置为4并启动一个新作业,现在我可以看到集群中每个节点上都有一个进程运行。
训练完成所需时间为2分钟36秒,加速比为3x。
最后一件事。将--task_name
更改为qqp
,我还运行了基于更大数据集(超过400,000个训练样本)的 Quora Question Pairs GLUE 任务。微调的时间如下:
- 单节点:11小时22分钟,
- 2个节点:6小时38分钟(1.71倍),
- 4个节点:3小时51分钟(2.95倍)。
看起来加速比相当稳定。随意尝试不同的学习率、批量大小和 oneCCL 设置。相信您可以做得更快!
结论
在本篇文章中,您学习了如何基于英特尔CPU和性能库构建分布式训练集群,以及如何使用该集群加速微调任务。确实,迁移学习将CPU训练重新引入游戏中,当您设计和构建下一个深度学习工作流程时,应该考虑使用它。
感谢阅读这篇长篇文章。希望您觉得有益。欢迎在[email protected]提供反馈和问题。下次再见,继续学习吧!
Julien