利用英特爾技術加速 PyTorch 分散式微調

釋出於 2021 年 11 月 19 日
在 GitHub 上更新

儘管最先進的深度學習模型效能驚人,但其訓練時間通常很長。為了加快訓練作業,工程團隊依賴分散式訓練,這是一種分而治之的技術,其中叢集中的伺服器各保留一份模型副本,在訓練集的子集上進行訓練,並交換結果以收斂到最終模型。

圖形處理單元 (GPU) 長期以來一直是訓練深度學習模型的“事實標準”選擇。然而,遷移學習的興起正在改變這一局面。模型現在很少從零開始在龐大資料集上進行訓練。相反,它們經常在特定(和更小)的資料集上進行微調,以構建比基礎模型在特定任務上更準確的專用模型。由於這些訓練作業要短得多,使用基於 CPU 的叢集可能是一種有趣的選擇,可以同時控制訓練時間和成本。

本文內容

在本文中,您將學習如何透過在搭載 Ice Lake 架構和效能最佳化軟體庫的 Intel Xeon 可擴充套件 CPU 伺服器叢集上分發訓練作業來加速 PyTorch 訓練作業。我們將從零開始使用虛擬機器構建叢集,您應該能夠輕鬆地在自己的基礎設施上覆制此演示,無論是在雲端還是在本地。

透過執行文字分類作業,我們將在 MRPC 資料集(GLUE 基準測試中包含的任務之一)上微調 BERT 模型。MRPC 資料集包含從新聞來源提取的 5,800 對句子,並帶有一個標籤,告訴我們每對句子中的兩個句子是否語義等效。我們選擇這個資料集是因為它的訓練時間合理,而嘗試其他 GLUE 任務只需更改一個引數即可。

叢集啟動並執行後,我們將在單個伺服器上執行基線作業。然後,我們將將其擴充套件到 2 臺伺服器和 4 臺伺服器,並測量加速比。

在此過程中,我們將涵蓋以下主題:

  • 列出所需的基礎設施和軟體構建模組,
  • 設定我們的叢集,
  • 安裝依賴項,
  • 執行單節點作業,
  • 執行分散式作業。

我們開始工作吧!

使用英特爾伺服器

為了獲得最佳效能,我們將使用基於 Ice Lake 架構的英特爾伺服器,該架構支援 Intel AVX-512 和 Intel Vector Neural Network Instructions (VNNI) 等硬體功能。這些功能可加速深度學習訓練和推理中常見的操作。您可以在此簡報 (PDF) 中瞭解更多資訊。

所有三家主要雲提供商都提供由英特爾 Ice Lake CPU 驅動的虛擬機器:

當然,您也可以使用自己的伺服器。如果它們基於 Cascade Lake 架構(Ice Lake 的前身),那麼它們也可以使用,因為 Cascade Lake 也包含 AVX-512 和 VNNI。

使用英特爾效能庫

為了在 PyTorch 中利用 AVX-512 和 VNNI,英特爾設計了 PyTorch 英特爾擴充套件。該軟體庫為訓練和推理提供了開箱即用的加速,因此我們絕對應該安裝它。

在分散式訓練中,主要的效能瓶頸通常是網路。事實上,叢集中的不同節點需要定期交換模型狀態資訊以保持同步。由於轉換器是具有數十億引數(有時更多)的大型模型,因此資訊量很大,並且隨著節點數量的增加,情況只會變得更糟。因此,使用為深度學習最佳化的通訊庫很重要。

事實上,PyTorch 包含 torch.distributed 包,它支援不同的通訊後端。在這裡,我們將使用英特爾 oneAPI 集體通訊庫 (oneCCL),它是深度學習中使用的通訊模式(all-reduce 等)的高效實現。您可以在這篇 PyTorch 部落格文章中瞭解 oneCCL 與其他後端相比的效能。

現在我們已經清楚了構建模組,接下來我們談談訓練叢集的整體設定。

設定我們的叢集

在此演示中,我使用執行 Amazon Linux 2 的 Amazon EC2 例項(c6i.16xlarge,64 vCPU,128GB RAM,25Gbit/s 網路)。在其他環境中設定會有所不同,但步驟應該非常相似。

請記住,您將需要 4 個相同的例項,因此您可能需要規劃某種自動化以避免重複執行相同的設定 4 次。在這裡,我將手動設定一個例項,從該例項建立新的 Amazon Machine Image (AMI),然後使用此 AMI 啟動三個相同的例項。

從網路角度來看,我們需要以下設定:

  • 在所有例項上開啟埠 22 以進行 ssh 訪問,用於設定和除錯。
  • 配置主例項(您將從該例項啟動訓練)與所有其他例項(**包括主例項**)之間的無密碼 ssh
  • 在所有例項上開啟所有 TCP 埠,用於叢集內部的 oneCCL 通訊。**請確保不要將這些埠向外部世界開放**。AWS 提供了一種方便的方法,即只允許來自執行特定安全組的例項的連線。這是我的設定:

現在,讓我們手動配置第一個例項。我首先建立例項本身,附加上面的安全組,並新增 128GB 儲存。為了最佳化成本,我將其作為競價型例項啟動。

例項啟動後,我透過 ssh 連線到它以安裝依賴項。

安裝依賴項

我們將遵循以下步驟:

  • 安裝 Intel 工具包,
  • 安裝 Anaconda 分發版,
  • 建立新的 conda 環境,
  • 安裝 PyTorch 和 PyTorch 的 Intel 擴充套件,
  • 編譯並安裝 oneCCL,
  • 安裝 transformers 庫。

看起來很多,但一點也不復雜。我們開始吧!

安裝英特爾工具包

首先,我們下載並安裝英特爾 OneAPI 基礎工具包AI 工具包。您可以在英特爾網站上了解更多資訊。

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 的英特爾擴充套件

接下來,我們安裝 PyTorch 1.9 和英特爾擴充套件工具包。**版本必須匹配**。

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


 # Will error if the minimal version of Transformers is not installed. Remove at your own risks.
-check_min_version("4.13.0.dev0")
+# check_min_version("4.13.0.dev0")

 require_version("datasets>=1.8.0", "To fix: pip install -r examples/pytorch/text-classification/requirements.txt")

@@ -191,6 +193,17 @@ def main():
     # or by passing the --help flag to this script.
     # We now keep distinct sets of args, for a cleaner separation of concerns.

+    # add local rank for cpu-dist
+    sys.argv.append("--local_rank")
+    sys.argv.append(str(os.environ.get("PMI_RANK", -1)))
+
+    # ccl specific environment variables
+    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.7 倍**。

-np 設定為 4 並啟動新作業後,我現在看到叢集中的每個節點上都執行著一個程序。

訓練在**2 分 36 秒**內完成,加速比為**3 倍**。

最後一件事。將 --task_name 更改為 qqp,我還運行了 Quora Question Pairs GLUE 任務,該任務基於更大的資料集(超過 400,000 個訓練樣本)。微調時間如下:

  • 單節點:11 小時 22 分鐘,
  • 2 個節點:6 小時 38 分鐘 (1.71x),
  • 4 個節點:3 小時 51 分鐘 (2.95x)。

看來加速效果相當一致。您可以隨意繼續嘗試不同的學習率、批次大小和 oneCCL 設定。我相信您能更快!

結論

在本文中,您學習瞭如何構建基於英特爾 CPU 和效能庫的分散式訓練叢集,以及如何使用該叢集來加速微調作業。事實上,遷移學習正在使 CPU 訓練重新回到人們的視野中,在設計和構建下一個深度學習工作流時,您絕對應該考慮它。

感謝您閱讀這篇長文。希望您覺得它資訊豐富。歡迎將反饋和問題傳送至 julsimon@huggingface.co。下次再見,繼續學習!

朱利安

社群

註冊登入以評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.