利用英特爾技術加速 PyTorch 分散式微調
儘管最先進的深度學習模型效能驚人,但其訓練時間通常很長。為了加快訓練作業,工程團隊依賴分散式訓練,這是一種分而治之的技術,其中叢集中的伺服器各保留一份模型副本,在訓練集的子集上進行訓練,並交換結果以收斂到最終模型。
圖形處理單元 (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 驅動的虛擬機器:
- 亞馬遜網路服務:Amazon EC2 M6i 和 C6i 例項。
- Azure:Dv5/Dsv5 系列、Ddv5/Ddsv5 系列 和 Edv5/Edsv5 系列 虛擬機器。
- Google Cloud Platform:N2 Compute Engine 虛擬機器。
當然,您也可以使用自己的伺服器。如果它們基於 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。下次再見,繼續學習!
朱利安