使用SLURM进行多节点训练
本教程介绍了一个框架,关于如何使用在许多超级计算中心可用的SLURM工作负载管理器在多个节点上的多个GPU上进行分布式训练。 代码基于我们的单节点多GPU训练教程。 如果您不熟悉PyTorch中的分布式训练概念,请先前往那里了解基础知识。
注意
本教程的完整脚本可以在examples/multi_gpu/distributed_sampling_multinode.py找到。
你可以在旁边找到示例*.sbatch文件,并根据你的需求进行调整。
用于管理启动的提交脚本
由于我们现在在多个节点上运行,我们不能再使用我们的__main__入口点并从那里启动进程。
这就是工作负载管理器的作用,它允许我们准备一个特殊的*.sbatch文件。
这个文件是一个标准的bash脚本,其中包含有关如何设置进程和环境的说明。
我们的示例从通常的shebang #!/bin/bash 开始,并带有特殊注释,指示SLURM系统应为我们的训练运行保留哪些资源。
具体配置通常取决于您的站点(以及您的使用限制!)。
以下是一个最小示例,适用于我们可用的相当不受限制的配置:
#!/bin/bash
#SBATCH --job-name=pyg-multinode-tutorial # identifier for the job listings
#SBATCH --output=pyg-multinode.log # outputfile
#SBATCH --partition=gpucloud # ADJUST this to your system
#SBATCH -N 2 # number of nodes you want to use
#SBATCH --ntasks=4 # number of processes to be run
#SBATCH --gpus-per-task=1 # every process wants one GPU!
#SBATCH --gpu-bind=none # NCCL can't deal with task-binding...
此示例将在两个节点上各创建两个进程,每个进程保留一个GPU。
在接下来的部分中,我们必须为torch.distributed设置一些环境变量,以便正确执行集合过程。
理论上,你也可以在Python进程中设置这些变量:
export MASTER_PORT=$(expr 10000 + $(echo -n $SLURM_JOBID | tail -c 4))
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
echo "MASTER_ADDR:MASTER_PORT="${MASTER_ADDR}:${MASTER_PORT}
如果您不希望让您的脚本随机打开一个端口并监听传入的连接,您也可以使用共享文件系统上的文件。
现在唯一剩下要添加的就是训练脚本的执行:
srun python distributed_sampling_multinode.py
注意python调用是如何以srun命令为前缀的,因此将启动--ntasks副本。
最后,要将*.sbatch文件本身提交到工作队列中,请在您的shell中使用sbatch工具:
sbatch distributed_sampling_multinode.sbatch
使用配置了pyxis容器的集群
如果你的集群支持NVIDIA开发的pyxis插件,你可以使用一个现成的PyG容器,该容器每月都会更新最新的NVIDIA和PyG内容,更多信息请参见这里。
该容器设置了所有必要的环境变量,你现在可以直接从命令提示符使用srun运行示例:
srun --partition=<partitionname> -N<num_nodes> --ntasks=<number of GPUS in total> --gpus-per-task=1 --gpu-bind=none --container-name=pyg-test --container-image=<image_url> --container-mounts='.:/workspace' python3 distributed_sampling_multinode.py
请注意,--container-mounts='.:/workspace' 将当前文件夹(应包含示例代码)挂载到容器的默认启动文件夹 workspace 中。
如果您希望最终在容器中自定义包而无需访问docker(在公共HPC上非常可能),您可以按照本教程创建自己的镜像。
修改训练脚本
由于SLURM现在负责创建多个Python进程,并且我们无法共享任何数据(每个进程都将加载完整的数据集!),我们的__main__部分现在必须查询由SLURM或pyxis容器生成的进程设置的环境:
# Get the world size from the WORLD_SIZE variable or directly from SLURM:
world_size = int(os.environ.get('WORLD_SIZE', os.environ.get('SLURM_NTASKS')))
# Likewise for RANK and LOCAL_RANK:
rank = int(os.environ.get('RANK', os.environ.get('SLURM_PROCID')))
local_rank = int(os.environ.get('LOCAL_RANK', os.environ.get('SLURM_LOCALID')))
run(world_size, rank, local_rank)
torch.distributed.init_process_group() 函数现在将从环境中获取 MASTER_ADDR:
def run(world_size: int, rank: int, local_rank: int):
dist.init_process_group('nccl', world_size=world_size, rank=rank)
我们还必须根据是否要将其用于节点本地目的(如选择GPU)或全局任务(如数据分割)来替换rank的使用
train_idx = data.train_mask.nonzero(as_tuple=False).view(-1)
train_idx = train_idx.split(train_idx.size(0) // world_size)[rank]
虽然我们需要将模型分配给节点本地的GPU,因此使用local_rank:
model = SAGE(dataset.num_features, 256, dataset.num_classes).to(local_rank)
model = DistributedDataParallel(model, device_ids=[local_rank])