本测试基于 NVIDIA/DeepLearningExamples/PyTorch/LanguageModeling/BERT/ 仓库中提供的 PyTorch 框架的 BERT 实现,在 NVIDIA 官方提供的 20.03 NGC 镜像及其衍生容器中进行单机单卡、单机多卡、多机多卡的结果复现及速度评测,同时增加分布式实现,测试 1 机、2 机、4 机的吞吐率及加速比,评判框架在分布式多机训练情况下的横向拓展能力。
目前,该测试覆盖 FP32 及混合精度,后续将持续维护,增加使用其他优化方式的测评。
- NVIDIA/DeepLearningExamples PyTorch BERT 测评
-
- GPU:Tesla V100-SXM2-16GB x 8
-
-
驱动:NVIDIA 440.33.01
-
系统: Ubuntu 16.04
-
CUDA:10.2
-
cuDNN:7.6.5
-
-
系统: Ubuntu 18.04
-
CUDA 10.2.89
-
cuDNN 7.6.5
-
NCCL:2.5.6
-
PyTorch:1.5.0a0+8f84ded
-
OpenMPI 3.1.4
-
DALI 0.19.0
-
Python:3.6.9
更多容器细节请参考 NVIDIA Container Support Matrix。
Feature BERT PyTorch Multi-gpu training Yes Multi-node Yes Automatic mixed precision (AMP) Yes NVIDIA NCCL Yes
根据 BERT For PyTorch 中的 Getting the data 小节准备 PyTorch 使用的 hdf5
格式的 BERT 数据集,主要有 SQuAD (fine-tuning for question answering) 、Wikipedia (pre-training)、BookCorpus (pre-training)。
考虑到性能测试无需花费大量时间(网络良好,梯子健全情况下大约一天)制备完整数据集,简易 Wikipedia 数据集制作可参考以下步骤:
-
下载 Wikipedia 数据集并解压,取其 /AA、/AB 等节选数据路径数据作为使用的 data sample,放至 /workspace/examples/bert/data/extracted 下。
-
修改 /workspace/examples/bert/data/WikicorpusTextFormatting.py,如下
import glob import os import argparse class WikicorpusTextFormatting: def __init__(self, args, recursive = False): self.wiki_path = args.wiki_path self.recursive = recursive self.output_filename = args.output_filename # This puts one article per line def merge(self): print("wiki_path: ", self.wiki_path) print("output_filename: ", self.output_filename) with open(self.output_filename, mode='w', newline='\n') as ofile: for dirname in glob.glob(self.wiki_path + '/*/', recursive=False): for filename in glob.glob(dirname + 'wiki_*', recursive=self.recursive): print(filename) article_lines = [] article_open = False with open(filename, mode='r', newline='\n') as file: for line in file: if '<doc id=' in line: article_open = True elif '</doc>' in line: article_open = False for oline in article_lines[1:]: if oline != '\n': ofile.write(oline.rstrip() + " ") ofile.write("\n\n") article_lines = [] else: if article_open: article_lines.append(line) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Preprocessing Wiki Corpus...') parser.add_argument("--wiki_path", type=str, default="/workspace/bert/data/extracted", help="input wiki path") parser.add_argument("--output_filename", type=str, default="/workspace/bert/data/formatted_one_article_per_line/wikicorpus_en_one_book_per_line.txt", help="output file name") args = parser.parse_args() wiki_corpus = WikicorpusTextFormatting(args, recursive=True) wiki_corpus.merge() print("merge done.")
执行成功,会生成 data/formatted_one_article_per_line/wikicorpus_en_one_book_per_line.txt 文件。
然后,注释掉 /workspace/examples/bert/data/create_datasets_from_start.sh 中的 40 行之前
Download
和Properly format the text files
相关代码,直接进行Shard the text files
和create HDF5 files PHase 1
和Create HDF5 files Phase 2
即可。可以先制作数据集,运行容器时绑定数据集路径(
-v ./data:/data/
),也可以先起容器,制作完数据集,使用 scp 传输数据集至容器内的路径,并修改脚本中的数据集路径。
拉取 NGC 20.03 的镜像、搭建容器,进入容器环境。
# 下载镜像
docker pull nvcr.io/nvidia/pytorch:20.03-py3
# 启动容器
docker run -it --shm-size=16g --ulimit memlock=-1 --privileged \
--name pt_bert --net host \
-v ./data:/data/ \
-d pytorch:20.03-py3
单机测试下无需配置,但测试 2 机、4 机等多机情况下,则需要配置 docker 容器间的 ssh 免密登录,保证 PyTorch 官方的 mpi/nccl 分布式脚本运行时可以在单机上与其他节点互联。
安装ssh服务端
# 在容器内执行
apt-get update
apt-get install openssh-server
设置免密登录
-
节点间的 /root/.ssh/id_rsa.pub 互相授权,添加到 /root/.ssh/authorized_keys 中;
-
修改 sshd 中用于 docker 通信的端口号
vim /etc/ssh/sshd_config
,修改Port
为空闲端口号; -
重启 ssh 服务,
service ssh restart
。
测试机器上的容器环境内未查找到 IB 驱动,会导致测试时 NCCL 库只能使用 Socket 通信,无法达到最佳测试效果,因此需要额外安装,具体可参考安装 IB 驱动。
为了保持算法一致性,脚本 /workspace/examples/bert/run_pretraining.py 中的 FusedLAMB
被替换为 FusedAdam
,该脚本需做如下修改:
43 from apex.optimizers import `FusedLAMB, FusedAdam # add FusedAdam
.....
# modify FusedLAMB with FusedAdam
322 optimizer = FusedAdam(optimizer_grouped_parameters,
323 lr=args.learning_rate,
324 bias_correction=False)
如此即可。
本次测试集群中有 4 台节点:
- NODE1=10.11.0.2
- NODE2=10.11.0.3
- NODE3=10.11.0.4
- NODE4=10.11.0.5
每个节点有 8 张 V100 显卡, 每张显卡显存 16 G。
在容器内下载本仓库源码:
git clone https://github.com/Oneflow-Inc/DLPerf.git
将本仓库 /DLPerf/NVIDIADeepLearningExamples/PyTorch/BERT/scripts 目录源码移至 /workspace/examples/bert/test_scripts(需新建) 下,执行脚本
bash run_single_node.sh
即可执行针对单机单卡、单机 2 卡、4 卡、 8 卡, batch_size 分别取 32、48 等情况的集成测试,并将 log 信息保存在当前目录的 /ngc/pytorch/ 对应分布式配置路径中,如单机单卡为 /1n1g,意为 1 node 1 gpu;单机 8卡为 /1n8g,意为 1 node 8 gpus,以此类推。
如需测试 amp
,修改脚本中的 PREC
为 amp
即可。
若想测试 fp16
,直接使用 --fp16
选项,会提示
Warning: FP16_Optimizer is deprecated and dangerous, and will be deleted soon.
If it still works, you're probably getting lucky.
For mixed precision, use the documented API https://nvidia.github.io/apex/amp.html,
with opt_level=O1.
因此建议使用 amp
参数或者在验证过正确性情况下修改源码中的 opt_level
。
多机测试,一定要确保数据集存在各节点测试机器的相同路径下,各脚本的行为要一致,尤其是修改要保持同步。
如需测试 amp
,直接修改脚本中的 PREC
为 amp
即可。
- 两机测试
以 NODE1 和 NODE2 为例,run_two_nodes.sh 脚本已填入 2 台机器对应的 IP 及端口号,NODE1 上的脚本 single_node_train.sh 中 --node_rank
默认为 0,还需自行将 NODE2 机器上相同路径下的脚本 108 行 --node_rank
改为 1,在 2 台机器上同时运行脚本,
bash run_two_node.sh
- 多机测试
以本集群为例,最多支持 4 机 32 卡,run_multi_nodes.sh 脚本已设置 NODE1 为 master node,设置好其 IP 及端口号,还需自行将 NODE3 机器上相同路径下的脚本 108 行 --node_rank
中的改为 2, NODE4 的 --node_rank
改为 3,在 4 台机器上同时运行脚本,
bash run_multi_nodes.sh
即可执行多节点 batch_size 分别取 32、48 等情况的集成测试,并将 log 信息保存在当前目录的对应分布式配置路径中。
测试进行了多组训练(本测试中取 5 次),每次训练过程只取第 1 个 epoch 的前 150 iter,计算训练速度时取后 100 iter 的数据,以降低抖动。最后将 5 次训练的结果取中位数得到最终速度,并以此数据计算加速比。
运行 /DLPerf/NVIDIADeepLearningExamples/PyTorch/BERT/extract_pytorch_logs_time.py,即可得到针对不同配置测试结果 log 数据处理的结果:
python extract_pytorch_logs_time.py --log_dir /workspace/examples/bert/test_scripts/ngc_bert_b48/pytorch --warmup_batches 20 --train_batches 120 --batch_size_per_device 32
结果打印如下
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/4n8g/bert-base-adam-training_b96_fp16_5.log {5: 10273.14}
end_time: 2020-09-24 02:12:08.999291
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/4n8g/bert-base-adam-training_b96_fp16_1.log {5: 10273.14, 1: 10552.87}
end_time: 2020-09-24 02:15:18.098056
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/4n8g/bert-base-adam-training_b96_fp16_3.log {5: 10273.14, 1: 10552.87, 3: 10324.68}
end_time: 2020-09-24 02:16:52.945844
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/4n8g/bert-base-adam-training_b96_fp16_4.log {5: 10273.14, 1: 10552.87, 3: 10324.68, 4: 10349.12}
end_time: 2020-09-24 02:13:43.531300
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/4n8g/bert-base-adam-training_b96_fp16_2.log {5: 10273.14, 1: 10552.87, 3: 10324.68, 4: 10349.12, 2: 10414.77}
end_time: 2020-09-24 03:20:44.972941
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n1g/bert-base-adam-training_b96_fp16_5.log {5: 463.85}
end_time: 2020-09-24 03:15:44.213131
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n1g/bert-base-adam-training_b96_fp16_1.log {5: 463.85, 1: 462.35}
end_time: 2020-09-24 03:18:14.318222
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n1g/bert-base-adam-training_b96_fp16_3.log {5: 463.85, 1: 462.35, 3: 466.94}
end_time: 2020-09-24 03:19:29.565003
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n1g/bert-base-adam-training_b96_fp16_4.log {5: 463.85, 1: 462.35, 3: 466.94, 4: 462.14}
end_time: 2020-09-24 03:16:58.796182
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n1g/bert-base-adam-training_b96_fp16_2.log {5: 463.85, 1: 462.35, 3: 466.94, 4: 462.14, 2: 462.35}
end_time: 2020-09-24 02:26:50.557894
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/2n8g/bert-base-adam-training_b96_fp16_5.log {5: 5366.7}
end_time: 2020-09-24 02:20:55.793547
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/2n8g/bert-base-adam-training_b96_fp16_1.log {5: 5366.7, 1: 5426.07}
end_time: 2020-09-24 02:23:47.979051
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/2n8g/bert-base-adam-training_b96_fp16_3.log {5: 5366.7, 1: 5426.07, 3: 5448.97}
end_time: 2020-09-24 02:25:18.862542
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/2n8g/bert-base-adam-training_b96_fp16_4.log {5: 5366.7, 1: 5426.07, 3: 5448.97, 4: 5439.94}
end_time: 2020-09-24 02:22:21.485900
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/2n8g/bert-base-adam-training_b96_fp16_2.log {5: 5366.7, 1: 5426.07, 3: 5448.97, 4: 5439.94, 2: 5410.84}
end_time: 2020-09-24 03:34:57.059096
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n8g/bert-base-adam-training_b96_fp16_5.log {5: 3339.15}
end_time: 2020-09-24 03:29:01.089925
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n8g/bert-base-adam-training_b96_fp16_1.log {5: 3339.15, 1: 3260.58}
end_time: 2020-09-24 03:31:53.242659
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n8g/bert-base-adam-training_b96_fp16_3.log {5: 3339.15, 1: 3260.58, 3: 3260.74}
end_time: 2020-09-24 03:33:31.687091
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n8g/bert-base-adam-training_b96_fp16_4.log {5: 3339.15, 1: 3260.58, 3: 3260.74, 4: 3310.51}
end_time: 2020-09-24 03:30:27.478401
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n8g/bert-base-adam-training_b96_fp16_2.log {5: 3339.15, 1: 3260.58, 3: 3260.74, 4: 3310.51, 2: 3287.12}
end_time: 2020-09-24 03:27:35.906235
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n4g/bert-base-adam-training_b96_fp16_5.log {5: 1727.93}
end_time: 2020-09-24 03:22:04.678285
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n4g/bert-base-adam-training_b96_fp16_1.log {5: 1727.93, 1: 1734.78}
end_time: 2020-09-24 03:24:55.125125
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n4g/bert-base-adam-training_b96_fp16_3.log {5: 1727.93, 1: 1734.78, 3: 1731.19}
end_time: 2020-09-24 03:26:14.931147
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n4g/bert-base-adam-training_b96_fp16_4.log {5: 1727.93, 1: 1734.78, 3: 1731.19, 4: 1726.71}
end_time: 2020-09-24 03:23:23.394265
/workspace/examples/bert/test_scripts/fp16_ngc_bert_b96/pytorch/1n4g/bert-base-adam-training_b96_fp16_2.log {5: 1727.93, 1: 1734.78, 3: 1731.19, 4: 1726.71, 2: 1723.52}
{'bert-base-adam-training': {'1n1g': {'average_speed': 463.53,
'batch_size_per_device': 96,
'median_speed': 462.35,
'speedup': 1.0},
'1n4g': {'average_speed': 1728.83,
'batch_size_per_device': 96,
'median_speed': 1727.93,
'speedup': 3.74},
'1n8g': {'average_speed': 3291.62,
'batch_size_per_device': 96,
'median_speed': 3287.12,
'speedup': 7.11},
'2n8g': {'average_speed': 5418.5,
'batch_size_per_device': 96,
'median_speed': 5426.07,
'speedup': 11.74},
'4n8g': {'average_speed': 10382.92,
'batch_size_per_device': 96,
'median_speed': 10349.12,
'speedup': 22.38}}}
Saving result to ./result/_result.json
该小节提供针对 NVIDIA PyTorch 框架的 BERT 模型测试的性能结果和完整 log 日志。
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 32 | 119.6 | 1.00 |
1 | 4 | 32 | 457.72 | 3.83 |
1 | 8 | 32 | 921.32 | 7.7 |
2 | 8 | 32 | 1499.4 | 12.54 |
4 | 8 | 32 | 2885.81 | 24.13 |
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 48 | 121.94 | 1.00 |
1 | 4 | 48 | 464.66 | 3.81 |
1 | 8 | 48 | 928.01 | 7.61 |
2 | 8 | 48 | 1584.32 | 12.99 |
4 | 8 | 48 | 3039.3 | 24.92 |
由于使用 FP16 时,可以选择 dynamic loss scale
或者 static loss scale
,但是不同实现会带来些微(-0.62%~-42.79%)的性能差异,所以附上两份数据。
dynamic loss scale
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 64 | 444.51 | 1.0 |
1 | 4 | 64 | 1671.66 | 3.76 |
1 | 8 | 64 | 3251.7 | 7.32 |
2 | 8 | 64 | 4936.92 | 11.11 |
4 | 8 | 64 | 9331.72 | 20.99 |
static loss scale
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 64 | 426.53 | 1.0 |
1 | 4 | 64 | 1567.68 | 3.68 |
1 | 8 | 64 | 3130.63 | 7.34 |
dynamic loss scale
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 96 | 462.35 | 1.0 |
1 | 4 | 96 | 1727.93 | 3.74 |
1 | 8 | 96 | 3287.12 | 7.11 |
2 | 8 | 96 | 5426.07 | 11.74 |
4 | 8 | 96 | 10349.12 | 22.38 |
static loss scale
node_num | gpu_num_per_node | batch_size_per_device | samples/s(PyTorch) | speedup |
---|---|---|---|---|
1 | 1 | 96 | 465.23 | 1.0 |
1 | 4 | 96 | 1705.8 | 3.67 |
1 | 8 | 96 | 3378.99 | 7.26 |
同时,可支持的 max batch size=96。
NVIDIA的 PyTorch 官方测评结果详见 BERT For PyTorch - Performance Results
详细 Log 信息可下载:ngc_pytorch_bert.tar