mpirun启动

查看源文件

概述

OpenMPI(Open Message Passing Interface)是一个开源的、高性能的消息传递编程库,用于并行计算和分布式内存计算,它通过在不同进程之间传递消息来实现并行计算,适用于许多科学计算和机器学习任务。使用OpenMPI进行并行训练是一种通用的在计算集群或多核机器上利用并行计算资源来加速训练过程的方法。OpenMPI在分布式训练的场景中,起到在Host侧同步数据以及进程间组网的功能。

与rank table启动不同的是,在Ascend硬件平台上通过OpenMPI的mpirun命令运行脚本,用户不需要配置RANK_TABLE_FILE环境变量。

mpirun启动支持Ascend和GPU,此外还同时支持PyNative模式和Graph模式。

相关命令:

  1. mpirun启动命令如下,其中DEVICE_NUM是所在机器的GPU数量:

    mpirun -n DEVICE_NUM python net.py
    
  2. mpirun还可以配置以下参数,更多配置可以参考mpirun文档

    • --output-filename log_output:将所有进程的日志信息保存到log_output目录下,不同卡上的日志会按rank_id分别保存在log_output/1/路径下对应的文件中。

    • --merge-stderr-to-stdout:合并stderr到stdout的输出信息中。

    • --allow-run-as-root:如果通过root用户执行脚本,则需要加上此参数。

    • -mca orte_abort_on_non_zero_status 0:当一个子进程异常退出时,OpenMPI会默认abort所有的子进程,如果不想自动abort子进程,可以加上此参数。

    • -bind-to none:OpenMPI会默认给拉起的子进程指定可用的CPU核数,如果不想限制进程使用的核数,可以加上此参数。

OpenMPI启动时会设置若干OPMI_*的环境变量,用户应避免在脚本中手动修改这些环境变量。

操作实践

mpirun启动脚本在Ascend和GPU硬件平台下一致,下面以Ascend为例演示如何编写启动脚本:

您可以在这里下载完整的样例代码:startup_method

目录结构如下:

└─ sample_code
    ├─ startup_method
       ├── net.py
       ├── hostfile
       ├── run_mpirun_1.sh
       ├── run_mpirun_2.sh
    ...

其中,net.py是定义网络结构和训练过程,run_mpirun_1.shrun_mpirun_2.sh是执行脚本,hostfile是配置多机多卡的文件。

1. 安装OpenMPI

下载OpenMPI-4.1.4源码openmpi-4.1.4.tar.gz。参考OpenMPI官网教程安装。

2. 准备Python训练脚本

这里以数据并行为例,训练一个MNIST数据集的识别网络,网络结构和训练过程与数据并行网络一致。

首先指定运行模式、硬件设备等,与单卡脚本不同,并行脚本还需指定并行模式等配置项,并通过init初始化HCCL或NCCL通信。此处不设置device_target会自动指定为MindSpore包对应的后端硬件设备。

import mindspore as ms
from mindspore.communication import init

ms.set_context(mode=ms.GRAPH_MODE)
ms.set_auto_parallel_context(parallel_mode=ms.ParallelMode.DATA_PARALLEL, gradients_mean=True)
init()
ms.set_seed(1)

然后构建如下网络:

from mindspore import nn

class Network(nn.Cell):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.fc = nn.Dense(28*28, 10, weight_init="normal", bias_init="zeros")
        self.relu = nn.ReLU()

    def construct(self, x):
        x = self.flatten(x)
        logits = self.relu(self.fc(x))
        return logits
net = Network()

最后是数据集处理和定义训练过程:

import os
from mindspore import nn
import mindspore as ms
import mindspore.dataset as ds
from mindspore.communication import get_rank, get_group_size

def create_dataset(batch_size):
    dataset_path = os.getenv("DATA_PATH")
    rank_id = get_rank()
    rank_size = get_group_size()
    dataset = ds.MnistDataset(dataset_path, num_shards=rank_size, shard_id=rank_id)
    image_transforms = [
        ds.vision.Rescale(1.0 / 255.0, 0),
        ds.vision.Normalize(mean=(0.1307,), std=(0.3081,)),
        ds.vision.HWC2CHW()
    ]
    label_transform = ds.transforms.TypeCast(ms.int32)
    dataset = dataset.map(image_transforms, 'image')
    dataset = dataset.map(label_transform, 'label')
    dataset = dataset.batch(batch_size)
    return dataset

data_set = create_dataset(32)
loss_fn = nn.CrossEntropyLoss()
optimizer = nn.SGD(net.trainable_params(), 1e-2)

def forward_fn(data, label):
    logits = net(data)
    loss = loss_fn(logits, label)
    return loss, logits

grad_fn = ms.value_and_grad(forward_fn, None, net.trainable_params(), has_aux=True)
grad_reducer = nn.DistributedGradReducer(optimizer.parameters)

for epoch in range(10):
    i = 0
    for data, label in data_set:
        (loss, _), grads = grad_fn(data, label)
        grads = grad_reducer(grads)
        optimizer(grads)
        if i % 10 == 0:
            print("epoch: %s, step: %s, loss is %s" % (epoch, i, loss))
        i += 1

3. 准备启动脚本

单机多卡

首先下载MNIST数据集,并解压到当前文件夹。

然后执行单机多卡启动脚本,以单机8卡为例:

export DATA_PATH=./MNIST_Data/train/
mpirun -n 8 --output-filename log_output --merge-stderr-to-stdout python net.py

日志文件会保存到log_output目录下,结果保存在log_output/1/rank.*/stdout中,结果如下:

epoch: 0, step: 0, loss is 2.3413472
epoch: 0, step: 10, loss is 1.6298866
epoch: 0, step: 20, loss is 1.3729795
epoch: 0, step: 30, loss is 1.2199347
epoch: 0, step: 40, loss is 0.85778403
epoch: 0, step: 50, loss is 1.0849445
epoch: 0, step: 60, loss is 0.9102987
epoch: 0, step: 70, loss is 0.7571399
epoch: 0, step: 80, loss is 0.7989929
epoch: 0, step: 90, loss is 1.0189024
epoch: 0, step: 100, loss is 0.6298542
...

多机多卡

在运行多机多卡训练前,首先需要按照如下配置:

  1. 保证每个节点上都有相同的OpenMPI、NCCL、Python以及MindSpore版本。

  2. 配置主机间免密登陆,可参考以下步骤进行配置:

    • 每台主机确定同一个用户作为登陆用户(不推荐root);

    • 执行ssh-keygen -t rsa -P ""生成密钥;

    • 执行ssh-copy-id DEVICE-IP设置需要免密登陆的机器IP;

    • 执行ssh DEVICE-IP,若不需要输入密码即可登录,则说明以上配置成功;

    • 在所有机器上执行以上命令,确保两两互通。

配置成功后,就可以通过mpirun指令启动多机任务,目前有两种方式启动多机训练任务:

  • 通过mpirun -H方式。启动脚本如下:

    export DATA_PATH=./MNIST_Data/train/
    mpirun -n 16 -H DEVICE1_IP:8,DEVICE2_IP:8 --output-filename log_output --merge-stderr-to-stdout python net.py
    

    表示在ip为DEVICE1_IP和DEVICE2_IP的机器上分别起8个进程运行程序。在其中一个节点执行:

    bash run_mpirun_1.sh
    
  • 通过mpirun --hostfile方式。为方便调试,建议用这种方法来执行多机多卡脚本。首先需要构造hostfile文件如下:

    DEVICE1 slots=8
    192.168.0.1 slots=8
    

    每一行格式为[hostname] slots=[slotnum],hostname可以是ip或者主机名。上例表示在DEVICE1上有8张卡;ip为192.168.0.1的机器上也有8张卡。

    2机16卡的执行脚本如下,需要传入变量HOSTFILE,表示hostfile文件的路径:

    export DATA_PATH=./MNIST_Data/train/
    HOSTFILE=$1
    mpirun -n 16 --hostfile $HOSTFILE --output-filename log_output --merge-stderr-to-stdout python net.py
    

    在其中一个节点执行:

    bash run_mpirun_2.sh ./hostfile
    

执行完毕后,日志文件会保存到log_output目录下,结果保存在log_output/1/rank.*/stdout中。