多机多卡训练

本章节以双机(单节点8卡)为示例,介绍通过环境变量设置资源信息,在多机多卡场景下启动训练任务。

前提条件

操作步骤

多机训练的配置的主要步骤为:下载Rec SDK镜像并创建容器后,修改SSH配置并启动服务,然后配置物理机节点之间的免密登录,最后配置little_demo模型,进而拉起训练进程,实现多机训练。双机时可任选一节点为主节点。

  1. 双机节点创建并配置容器。

    1. 确定节点需要使用的端口号。所有节点都需要用同一未被占用的端口号。可在物理机上执行以下命令,查询端口是否使用。以端口12345为例。
      ss -tuln | grep 12345

      若ss指令不存在需安装iproute软件包。

      • 结果为空,表示端口未使用 => 继续执行1.b
      • 结果不为空,表示端口已使用 => 重复查询其他端口号
    2. 在容器内修改sshd_config文件。
      1
      vi /etc/ssh/sshd_config
      
      1. 取消“#Port 22”所在行的注释,并将端口号修改为未使用的端口号(如“12345”),便于mpi通过指定端口进入其他节点容器。同时,Host侧防火墙策略中需允许访问该端口。
      2. 可选:取消“#ListenAddress 0.0.0.0”的注释,并将“0.0.0.0”修改为当前节点IP。如果采用导出最新容器镜像并复制到其他环境的方式部署集群环境,在其他节点启动并进入容器后,需手动修改容器内SSH服务侦听IP为对应节点IP。

        如果不执行该步骤,不会影响集群训练,但是会侦听宿主机全零IP的对应端口。出于安全考虑,建议进行修改。

    3. 在容器内执行以下命令,生成SSH host keys启动SSH服务。
      1
      2
      cd /etc/ssh/ && ssh-keygen -A
      /usr/sbin/sshd
      

      可执行ss -tuln | grep 12345查看侦听端口是否为配置的端口。

      如需停止容器内SSH服务,可在容器内执行:

      1
      kill -9 `ps -ef | grep sshd | grep -v grep | awk '{print $2}'` > /dev/null 2>&1
      
    4. 设置容器内环境变量。

      将启动训练前需要配置的环境变量设置到容器内的~/.bashrc文件,便于主节点免密登录到其他节点容器内时能直接使用,无需配置环境变量。

      示例如下,按需配置:

      1. 打开“~/.bashrc”文件。
        1
        vi ~/.bashrc
        
      2. 在文件末尾添加以下内容。
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        source /etc/profile
        source /usr/local/Ascend/ascend-toolkit/set_env.sh
        source /usr/local/Ascend/driver/bin/setenv.bash
        source /usr/local/Ascend/tfplugin/set_env.sh
        export PATH=/usr/local/openmpi/bin:$PATH
        export PATH=/usr/local/python3.7.5/bin:$PATH
        export PATH=/usr/local/gcc7.3.0/bin:$PATH
        export LD_LIBRARY_PATH=/usr/local/python3.7.5/lib:$LD_LIBRARY_PATH
        export LD_LIBRARY_PATH=/usr/local/gcc7.3.0/lib64:$LD_LIBRARY_PATH
        export LD_LIBRARY_PATH=/usr/local/openmpi/lib:$LD_LIBRARY_PATH
        
      3. 按“Esc”键,输入:wq!,按“Enter”保存并退出编辑。
      4. 执行source ~/.bashrc使环境变量配置生效。

  2. 配置little_demo模型。

    1. 使用以下命令查看8卡芯片的device IP,命令参考如下。
      1
      for i in {0..7}; do hccn_tool -i $i -ip -g ; done
      
    2. 配置资源信息。
      • ranktable方式启动时,所有节点中均需要“hccl_json_16p_2_host.json”配置文件,且文件内容一样。“hccl_json_16p_2_host.json”文件配置样例参考如下。

        示例为双机节点“hccl_json_16p_2_host.json”配置,主节点的device信息需配置在第一个device中。{device_ip}和{host_ip}需要根据真实环境配置进行替换,rank_id需要一直升序。

        {
            "server_count":"2",
            "server_list":[
                {
                    "device":[
                        { "device_id":"0", "device_ip":"{device_0_ip}", "rank_id":"0" },
                        { "device_id":"1", "device_ip":"{device_1_ip}", "rank_id":"1" },
                        { "device_id":"2", "device_ip":"{device_2_ip}", "rank_id":"2" },
                        { "device_id":"3", "device_ip":"{device_3_ip}", "rank_id":"3" },
                        { "device_id":"4", "device_ip":"{device_4_ip}", "rank_id":"4" },
                        { "device_id":"5", "device_ip":"{device_5_ip}", "rank_id":"5" },
                        { "device_id":"6", "device_ip":"{device_6_ip}", "rank_id":"6" },
                        { "device_id":"7", "device_ip":"{device_7_ip}", "rank_id":"7" }
                    ],
                    "server_id":"{host_1_ip}"
                },
                {
                    "device":[
                        { "device_id":"0", "device_ip":"{device_8_ip}", "rank_id":"8" },
                        { "device_id":"1", "device_ip":"{device_9_ip}", "rank_id":"9" },
                        { "device_id":"2", "device_ip":"{device_10_ip}", "rank_id":"10" },
                        { "device_id":"3", "device_ip":"{device_11_ip}", "rank_id":"11" },
                        { "device_id":"4", "device_ip":"{device_12_ip}", "rank_id":"12" },
                        { "device_id":"5", "device_ip":"{device_13_ip}", "rank_id":"13" },
                        { "device_id":"6", "device_ip":"{device_14_ip}", "rank_id":"14" },
                        { "device_id":"7", "device_ip":"{device_15_ip}", "rank_id":"15" }
                    ],
                    "server_id":"{host_2_ip}"
                }
            ],
            "status":"completed",
            "version":"1.0"
        }
      • no ranktable启动时,不需要配置hccl json文件,但需要在所有备节点little_demo模型代码的main.py中手动设置环境变量,修改如下:
        在main.py顶部import os的下一行添加如下代码:
        # no ranktable时设置CM_WORKER_IP环境变量为当前节点ip,{host_ip}为当前节点ip。
        if not os.getenv("RANK_TABLE_FILE", ""):
            os.environ['CM_WORKER_IP'] = "{host_ip}"
    3. 修改run.sh脚本(仅需修改主节点)。
      1. num_server修改为实际节点数(比如2)。
      2. 删除mpi_args变量值中的“-mca btl_tcp_if_exclude docker0”字符串。
      3. “interface”的值修改为配置当前host ip的网卡名。可通过ip addr进行查询。
      4. 使用ranktable启动方案时,还需修改export RANK_TABLE_FILE变量值中的json文件名称,使其与•ranktable方式启动时,所有节点中均需要...中配置的双机的hccl json文件名一致,例如:
        1
        export RANK_TABLE_FILE="${cur_path}/hccl_json_${local_rank_size}p.json"
        

        修改为:

        1
        export RANK_TABLE_FILE="${cur_path}/hccl_json_16p_2_host.json"  
        
      5. 通过horovodrun启动指令指定端口号并修改host参数,修改如下内容:

        在run.sh脚本末尾,将

        1
        xxx --mpi-args "${mpi_args}" --mpi -H localhost:${local_rank_size} 
        

        修改为:

        xxx --mpi-args "${mpi_args}" -p 12345 --mpi -H {host_1_ip}:8,{host_2_ip}:8
        • -p 12345:容器内ssh server侦听端口号。
        • 8:单节点参与训练的device数。

  3. 在每个节点上互相设置免密登录。

    1. 在每个节点执行以下命令,设置免密登录。其中{target_host_user}为对端节点的用户名,{target_host_ip}为对端节点的IP。
      ssh-copy-id -i ~/.ssh/id_rsa.pub {target_host_user}@{target_host_ip}

      若提示没有id_rsa.pub,可使用如下命令生成:

      1
      ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
      
    2. 检查免密登录是否设置成功。
      ssh-keygen -R {target_host_ip}  # 删除当前节点中目标ip的host key缓存
      ssh-keygen -R "[{target_host_ip}]:12345"  # 删除当前host上保存的目标ip对应端口的host key缓存
      ssh {target_host_user}@{target_host_ip}   # 验证免密登录目标ip
      ssh {target_host_user}@{target_host_ip} -p 12345  # 验证免密登录目标ip指定端口

  4. 在主节点执行以下命令,拉起模型。只需要在主节点拉起即可,备节点不需要手动拉起训练任务,mpi会自动跳转过去拉起训练任务。

    • rank table模式:
      1
      bash run.sh main.py
      
    • 去rank table模式,其中{host_1_ip}为主节点IP。
      bash run.sh main.py {host_1_ip}