多机多卡场景

环境准备

准备集群环境时,集群中的各个单机需要按照环境准备部署好环境,确保单机可以正常运行。

此外还需要进行按照如下操作进行集群配置。

  • 分布式训练场景下,HCCL会使用Host服务器的部分端口进行集群信息收集,需要操作系统预留该部分端口。HCCL建议使用60000-60015端口,也可以通过环境变量HCCL_IF_BASE_PORT指定Host网卡起始端口,此场景下需要预留以指定端口起始的16个端口。如果用户在物理机场景训练,则需要在物理机上配置以下命令;若是在容器场景训练,则需要在容器中配置以下命令。
    • 若操作系统端口号预留仅需临时生效,可执行如下命令,临时生效的配置重启后会失效:
      sysctl -w net.ipv4.ip_local_reserved_ports=60000-60015
    • 若操作系统端口号预留需永久生效,可执行如下操作:
      1. 以root用户登录服务器,编辑“/etc/sysctl.conf”文件。
        vim /etc/sysctl.conf
      2. 在“/etc/sysctl.conf”文件末尾加上net.ipv4.ip_local_reserved_ports=60000-60015,保存并退出。
      3. 执行如下命令使配置生效。
        sysctl -p
  • 集合通信仅支持1/2/4/8P粒度的分配。
  • 以下操作除模型修改外,只需配置执行一次。
  1. 准备组网,以两台8卡服务器组网为例。

    通过交换机或光口直连的方式完成计算设备组网搭建。

  2. 配置device IP。

    1. 在AI Server0上配置device IP,以下IP为示例。
         hccn_tool -i 0 -ip -s address 10.***.***.2 netmask 255.255.255.0
         hccn_tool -i 1 -ip -s address 10.***.***.3 netmask 255.255.255.0
         hccn_tool -i 2 -ip -s address 10.***.***.4 netmask 255.255.255.0
         hccn_tool -i 3 -ip -s address 10.***.***.5 netmask 255.255.255.0
         hccn_tool -i 4 -ip -s address 10.***.***.6 netmask 255.255.255.0
         hccn_tool -i 5 -ip -s address 10.***.***.7 netmask 255.255.255.0
         hccn_tool -i 6 -ip -s address 10.***.***.8 netmask 255.255.255.0
         hccn_tool -i 7 -ip -s address 10.***.***.9 netmask 255.255.255.0
    1. 在AI Server1上配置device IP,以下IP为示例。
         hccn_tool -i 0 -ip -s address 10.***.***.2 netmask 255.255.255.0
         hccn_tool -i 1 -ip -s address 10.***.***.3 netmask 255.255.255.0
         hccn_tool -i 2 -ip -s address 10.***.***.4 netmask 255.255.255.0
         hccn_tool -i 3 -ip -s address 10.***.***.5 netmask 255.255.255.0
         hccn_tool -i 4 -ip -s address 10.***.***.6 netmask 255.255.255.0
         hccn_tool -i 5 -ip -s address 10.***.***.7 netmask 255.255.255.0
         hccn_tool -i 6 -ip -s address 10.***.***.8 netmask 255.255.255.0
         hccn_tool -i 7 -ip -s address 10.***.***.9 netmask 255.255.255.0

    配置device IP需遵守以下规则:

    1. 针对Atlas 训练系列产品,AI Server中的第0/4、1/5、2/6、3/7号device需处于同一网段,第0/1/2/3号device在不同网段,第4/5/6/7号device在不同网段;对于集群场景,各AI Server对应的位置的device需处于同一网段,AI Server0和AI Server1的0号网卡需处于同一网段、1号网卡需要在同一网段。
    2. 针对Atlas A2 训练系列产品/Atlas A3 训练系列产品,多台节点的NPU在同一网段即可。
    3. 每个IP都不能冲突,相同网段下的IP需在最后8位做区分。
    1. 使用hccn_tool 配置网络检测对象IP,从device0 - device7 配置8次。
      hccn_tool -i 0 -netdetect -s address xx.xx.xx.xx
      表1 参数说明

      参数

      说明

      -i

      设备ID,取值范围:0~7。

      -s address

      NPU网络检测对象IP,主要用于检测NPU网口网络连接状态。该IP需确保能和NPU网口IP地址互通,通常将检测对象IP配置为与NPU网口IP地址相同网段的网关地址,训练节点会定时检测NPU网口和网关地址通信是否正常。

    2. 执行如下命令查看网络健康状态,确保两台机器间所有卡都连通。
      for  i  in  {0..7}; do  hccn_tool -i $i -net_health -g; done 

      回显如下所示:

      net health status: Success
      表2 回显说明

      字段

      说明

      net health status

      网络健康状态。

      状态信息:

      0:Success;1:Socket fail;2:Receive timeout;3:Unreachable;4:Time exceeded;5:Fault;6:Init;7:Thread error;8:Detect ip set;其它:Unknown。

    3. 使用hccn_tool工具验证device IP是否配置正确。
      1. 查询每个device的ip:
        hccn_tool -i 0 -ip -g
      2. 打印查询结果:
        ipaddr:10.***.***.2
        netmask:255.255.***.0

        返回上述打印则表示已经连通。

  3. 关闭防火墙。

    • Debian系列防火墙关闭命令。
      ufw disable
    • openEuler系列防火墙关闭命令。
      systemctl stop firewalld

  4. 确认交换机状态正常。

    执行以下命令,返回值不为空则正常。

    for i in {0..7}; do hccn_tool -i $i -lldp -g; done

  5. 修改模型。可参考多机多卡示例,将模型脚本上传至AI Server0和AI Server1任意路径下。
  6. 由于主节点允许处理的并发建链数受Linux内核参数“somaxconn”与“tcp_max_syn_backlog”的限制,所以,针对大规模集群组网,若“somaxconn”与“tcp_max_syn_backlog”取值较小会导致部分客户端概率性提前异常退出,导致集群初始化失败。

    大规模集群组网场景下,建议用户根据集群数量适当调整“somaxconn”与“tcp_max_syn_backlog”参数的值,例如:

    sysctl -w net.core.somaxconn=65535
    sysctl -w net.ipv4.tcp_max_syn_backlog=65535

    如果用户在物理机场景训练,则需要在物理机上配置以上命令;若是在容器场景训练,则需要在容器中配置以上命令。

  7. 拉起多机多卡训练。需注意以下要点:

    • 在所有脚本统一主节点的MASTER_PORT和MASTER_ADDR。
    • 配置相应的rank与world_size。

  8. 查看host日志。

    所有host日志统一保存在~/ascend/log路径下,用户可以在该路径下查看每个host的device日志。

模型脚本配置

本节以适配样例(DDP单NPU单进程场景)章节的代码为样例,为用户介绍将单卡训练脚本修改为多卡训练脚本的核心步骤。

  1. 在主函数中添加如下代码。

    1. 添加分布式逻辑,即在环境变量中获取local_rank参数。
      local_rank = int(os.environ.get("LOCAL_RANK", 0)) 
    2. 用local_rank自动获取device号。
      device = torch.device(f'npu:{local_rank}')
    3. 初始化,将通信方式设置为HCCL。
      torch.distributed.init_process_group(backend="hccl",rank=(args.node_rank)*(args.nproc_per_node) + local_rank)

  2. 定义模型后,开启DDP模式。

    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)

  3. 在获取训练数据集后,设置train_sampler。

    train_sampler = torch.utils.data.distributed.DistributedSampler(train_data)

  4. 将train_sampler赋值至DataLoader中的sampler。

    train_dataloader = DataLoader(dataset = train_data, batch_size=batch_size, sampler = train_sampler)

启动脚本配置

有5种脚本启动方式可拉起多卡训练:

附录拉起多卡训练脚本示例中,以一个简单模型脚本为样例,展示了每种拉起方式脚本代码的修改方法以及各种拉起方式的适配方法,用户可以参考学习。

  • 集合通信存在如下约束:
    • 数据并行模式中不同device上执行的计算图相同。
    • 针对Atlas 训练系列产品:allreduce和reduce_scatter仅支持int8、int32、float16和float32数据类型。
    • 针对Atlas A2 训练系列产品/Atlas A3 训练系列产品:allreduce和reduce_scatter仅支持int8、int32、float16、float32和bf16数据类型。
    • 针对Atlas 训练系列产品,如果使用1台训练服务器(Server),要求实际参与集合通信的NPU数目只能为1/2/4/8,且0-3卡和4-7卡各为一个组网。使用2张卡或4张卡训练时,不支持跨组网创建设备集群。
    • 针对Atlas 训练系列产品,在Server集群场景下(即由集群管理主节点和一组训练服务器组成训练服务器集群),要求参与集合通信的NPU数目只能为1*n、2*n、4*n、8*n(其中n为参与训练的Server个数,上限为512)。且n为2的指数倍情况下,集群性能最好,建议用户优先采用此种方式进行集群组网。
    • 针对Atlas A2 训练系列产品/Atlas A3 训练系列产品,在Server集群场景(即由集群管理主节点和一组训练服务器组成训练服务器集群)下要求参与集合通信的NPU数目为(1~8)*n(其中n为参与训练的Server个数,上限为1152),其中,n为2的指数倍情况下,集群性能最好,建议用户优先采用此种方式进行集群组网。同时建议每个Server中参与集合通信的NPU数量保持一致,若不一致,会造成性能劣化。
  • 针对Atlas A2 训练系列产品/Atlas A3 训练系列产品,若用户准备进行2卡训练,可将8卡训练脚本进行改写,改为2卡训练脚本。可参见以下修改方法:
    1. 若8卡脚本的batch_size是单卡脚本的batch_size的8倍,则将8卡训练时的batch_size和learning_rate同时除以4,作为2卡训练时的batch_size和learning_rate。
    2. 如果使用for循环启动训练入口脚本,则将for循环的次数改为2次。
    3. world_size修改为2 * nnodes(节点个数),并确保训练脚本中dist.init_process_group()中world_size参数为2 * nnodes。
    4. nproc_per_node修改为2。
  • 一个Device对应执行一个训练进程,当前不支持多进程在同一个Device上进行训练。

多机多卡示例

以torchrun方式启动为例,通过一个简单的自定义模型,展示多机多卡的模型代码和启动脚本样例。

  1. 模型脚本配置示例,以下示例以一个简单的自定义模型为例,适配NPU需要注意和修改的内容已加粗标注。
    # 模型代码
    import os
    import argparse
    import tempfile
    import torch
    import torch_npu
    import torch.distributed as dist
    import torch.nn as nn
    import torch.optim as optim
    
    from torch.utils.data import DataLoader
    from torchvision import datasets, transforms
    from torch.utils.data.distributed import DistributedSampler
    # DDP
    from torch.nn.parallel import DistributedDataParallel as DDP
    
    def cleanup():
        dist.destroy_process_group()
    
    class ToyModel(nn.Module):
        def __init__(self):
            super(ToyModel, self).__init__()
            self.net1 = nn.Linear(10, 10)
            self.relu = nn.ReLU()
            self.net2 = nn.Linear(10, 5)
    
        def forward(self, x):
            return self.net2(self.relu(self.net1(x)))
    
    def parse_args():
        # 配置传参逻辑
        parser = argparse.ArgumentParser(description="command line arguments")
        parser.add_argument('--batch_size', type=int, default=64)
        parser.add_argument('--epochs', type=int, default=10)
        parser.add_argument('--learning_rate', type=float, default=0.0001)
        parser.add_argument("--node_rank", type=int)
        parser.add_argument("--nproc_per_node", type=int)
        return parser.parse_args()
    
    def data_process(inputs, labels):
        squeezed_tensor = inputs.squeeze(0).squeeze(0)
        inputs = squeezed_tensor[:, :10]
        labels = labels.repeat(28, 5) * (1/140)
        return inputs, labels
    
    def main():
        # 获取分布式超参数
        args = parse_args() 
    
        # 添加分布式逻辑
        local_rank = int(os.environ.get("LOCAL_RANK", 0))
        world_size = int(os.environ.get("WORLD_SIZE", 1))
    
        # 初始化DDP,将通信方式设置为hccl
        dist.init_process_group("hccl", rank=(args.node_rank)*(args.nproc_per_node) + local_rank, world_size=world_size)
    
        transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.5,), (0.5,))
        ])
        # 将模型转移到NPU上
        torch_npu.npu.set_device(local_rank)
        #获取device号
        device = torch.device(f'npu:{local_rank}')       
        model = ToyModel().to(device)
    
        # 定义模型后,开启DDP模式
        model = DDP(model, device_ids=[local_rank], find_unused_parameters=True) 
    
        train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform = transform)
        # 在获取训练数据集后,设置train_sampler和train_loader
        train_sampler = DistributedSampler(train_dataset, num_replicas=world_size, rank=local_rank)
        train_loader = DataLoader(train_dataset, batch_size=args.batch_size, sampler=train_sampler)
    
        criterion = nn.CrossEntropyLoss()
        optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)
        step = 0
    
        for epoch in range(args.epochs):
            model.train()
            for inputs, labels in train_loader:
                # 数据预处理,将数据集的数据转成需要的shape
                inputs, labels = data_process(inputs, labels) 
                # 将数据转到NPU处理
                inputs, labels = inputs.to(device), labels.to(device) 
    
                optimizer.zero_grad()
                outputs = model(inputs)
                # 将loss转到NPU处理
                loss = criterion(outputs, labels).to(device) 
                loss.backward()
                optimizer.step()
                print(f"step == {step}")
                step += 1
        cleanup()
    
    
    if __name__ == "__main__":
        main()
  2. 启动脚本配置样例,通过bash启动即可,启动脚本配置需要依次在每个节点执行:
    # 运行NPU多机多卡的脚本
    MASTER_ADDR=xxxx                               # 主节点IP地址
    MASTER_PORT=6004                               # 端口号选一个未被占用的端口即可
    NODE_RANK=0                                    # 本机序号,主节点为0,其他节点按照实际场景配置
    NNODES=2                                       # 节点个数
    NPUS_PER_NODE=8                                # 每个节点的卡数
    WORLD_SIZE=$(($NPUS_PER_NODE*$NNODES))         # 总共使用的卡数
    
    
    export NPU_ASD_ENABLE=1    # 特征值检测
    source ./test/env_npu.sh        # env_npu.sh配置的环境变量
    
    DISTRIBUTED_ARGS="
        --nproc_per_node $NPUS_PER_NODE \
        --nnodes $NNODES \
        --node_rank $NODE_RANK \
        --master_addr $MASTER_ADDR \
        --master_port $MASTER_PORT
    "
    
    torchrun $DISTRIBUTED_ARGS train_16p.py \                    # train_16p.py为上述模型脚本配置示例代码名称,用户可自行定义
                               --nproc_per_node $NPUS_PER_NODE   # 每个设备的卡数
                               --node_rank $NODE_RANK            # 本设备是第几个设备(若单机则为0)
                               --learning_rate 0.0001            # 学习率
                               --batch_size 1                    # 训练批次大小
                               --epochs 1                        # 训练迭代轮数