更新时间:2025-03-04 gmt 08:00

从0制作自定义镜像用于创建训练作业(mpi cpu/gpu)-九游平台

本章节介绍如何从0到1制作镜像,并使用该镜像在modelarts平台上进行训练。镜像中使用的ai引擎是mpi,训练使用的资源是cpu或gpu。

本实践教程仅适用于新版训练作业。

场景描述

本示例使用linux x86_64架构的主机,操作系统ubuntu-18.04,通过编写dockerfile文件制作自定义镜像。

目标:构建安装如下软件的容器镜像,并在modelarts平台上使用cpu/gpu规格资源运行训练作业。

  • ubuntu-18.04
  • cuda-11.1
  • python-3.7.13
  • openmpi-3.0.0

操作流程

使用自定义镜像创建训练作业时,需要您熟悉docker软件的使用,并具备一定的开发经验。详细步骤如下所示:

  1. 前提条件
  2. step1 创建obs桶和文件夹
  3. step2 准备脚本文件并上传至obs中
  4. step3 准备镜像主机
  5. step4 制作自定义镜像
  6. step5 上传镜像至swr服务
  7. step6 在modelarts上创建训练作业

前提条件

已注册华为账号并开通华为云,且在使用modelarts前检查账号状态,账号不能处于欠费或冻结状态。

step1 创建obs桶和文件夹

在obs服务中创建桶和文件夹,用于存放样例数据集以及训练代码。需要创建的文件夹列表如表1所示,示例中的桶名称“test-modelarts” 和文件夹名称均为举例,请替换为用户自定义的名称。

创建obs桶和文件夹的操作指导请参见创建桶新建文件夹

请确保您使用的obs与modelarts在同一区域。

表1 obs桶文件夹列表

文件夹名称

用途

“obs://test-modelarts/mpi/demo-code/”

用于存储mpi启动脚本与训练脚本文件。

“obs://test-modelarts/mpi/log/”

用于存储训练日志文件。

step2 准备脚本文件并上传至obs中

准备本案例所需的mpi启动脚本run_mpi.sh文件和训练脚本mpi-verification.py文件,并上传至obs桶的“obs://test-modelarts/mpi/demo-code/”文件夹下。

  • mpi启动脚本run_mpi.sh文件内容如下:
    #!/bin/bash
    my_home=/home/ma-user
    my_sshd_port=${my_sshd_port:-"38888"}
    my_task_index=${ma_task_index:-${vc_task_index:-${vk_task_index}}}
    my_mpi_slots=${my_mpi_slots:-"${ma_num_gpus}"}
    my_mpi_tune_file="${my_home}/env_for_user_process"
    if [ -z ${my_mpi_slots} ]; then
        echo "[run_mpi] my_mpi_slots is empty, set it be 1"
        my_mpi_slots="1"
    fi
    printf "my_home: ${my_home}\nmy_sshd_port: ${my_sshd_port}\nmy_mpi_btl_tcp_if: ${my_mpi_btl_tcp_if}\nmy_task_index: ${my_task_index}\nmy_mpi_slots: ${my_mpi_slots}\n"
    env | grep -e '^ma_|^shared_|^s3_|^path|^vc_worker_|^scc|^cred' | grep -v '=$' > ${my_mpi_tune_file}
    # add -x to each line
    sed -i 's/^/-x /' ${my_mpi_tune_file}
    sed -i "s|{{my_sshd_port}}|${my_sshd_port}|g" ${my_home}/etc/ssh/sshd_config
    # start sshd service
    bash -c "$(which sshd) -f ${my_home}/etc/ssh/sshd_config"
    # confirm the sshd is up
    netstat -anp | grep lis | grep ${my_sshd_port}
    if [ $my_task_index -eq 0 ]; then
        # generate the hostfile of mpi
        for ((i=0; i<$ma_num_hosts; i  ))
        do
            eval hostname=${ma_vj_name}-${ma_task_name}-${i}.${ma_vj_name}
            echo "[run_mpi] hostname: ${hostname}"
            ip=""
            while [ -z "$ip" ]; do
                ip=$(ping -c 1 ${hostname} | grep "ping" | sed -e 's/ping .* .([0-9.] ). .*/\1/g')
                sleep 1
            done
            echo "[run_mpi] resolved ip: ${ip}"
            # test the sshd is up
            while :
            do
                if [ cat < /dev/null >/dev/tcp/${ip}/${my_sshd_port} ]; then
                    break
                fi
                sleep 1
            done
            echo "[run_mpi] the sshd of ip ${ip} is up"
            echo "${ip} slots=$my_mpi_slots" >> ${my_home}/hostfile
        done
        printf "[run_mpi] hostfile:\n`cat ${my_home}/hostfile`\n"
    fi
    ret_code=0
    if [ $my_task_index -eq 0 ]; then
        echo "[run_mpi] start exec command time: "$(date  "%y-%m-%d-%h:%m:%s")
        np=$(( ${ma_num_hosts} * ${my_mpi_slots} ))
        echo "[run_mpi] command: mpirun -np ${np} -hostfile ${my_home}/hostfile -mca plm_rsh_args \"-p ${my_sshd_port}\" -tune ${my_mpi_tune_file} ... $@"
        # execute mpirun at worker-0
        # mpirun
        mpirun \
            -np ${np} \
            -hostfile ${my_home}/hostfile \
            -mca plm_rsh_args "-p ${my_sshd_port}" \
            -tune ${my_mpi_tune_file} \
            -bind-to none -map-by slot \
            -x nccl_debug -x nccl_socket_ifname -x nccl_ib_hca -x nccl_ib_timeout -x nccl_ib_gid_index -x nccl_ib_tc \
            -x horovod_mpi_threads_disable=1 \
            -x path -x ld_library_path \
            -mca pml ob1 -mca btl ^openib -mca plm_rsh_no_tree_spawn true \
            "$@"
        ret_code=$?
        if [ $ret_code -ne 0 ]; then
            echo "[run_mpi] exec command failed, exited with $ret_code"
        else
            echo "[run_mpi] exec command successfully, exited with $ret_code"
        fi
        # stop 1...n worker by killing the sleep proc
        sed -i '1d' ${my_home}/hostfile
        if [ `cat ${my_home}/hostfile | wc -l` -ne 0 ]; then
            echo "[run_mpi] stop 1 to (n - 1) worker by killing the sleep proc"
            sed -i 's/${my_mpi_slots}/1/g' ${my_home}/hostfile
            printf "[run_mpi] hostfile:\n`cat ${my_home}/hostfile`\n"
            mpirun \
            --hostfile ${my_home}/hostfile \
            --mca plm_rsh_args "-p ${my_sshd_port}" \
            -x path -x ld_library_path \
            pkill sleep \
            > /dev/null 2>&1
        fi
        echo "[run_mpi] exit time: "$(date  "%y-%m-%d-%h:%m:%s")
    else
        echo "[run_mpi] the training log is in worker-0"
        sleep 365d
        echo "[run_mpi] exit time: "$(date  "%y-%m-%d-%h:%m:%s")
    fi
    exit $ret_code

    “run_mpi.sh”脚本需要以lf作为换行符。使用crlf作为换行符会导致训练作业运行失败,日志中会打印“$'\r': command not found”的错误信息。

  • 训练脚本mpi-verification.py文件内容如下:
    import os
    import socket
    if __name__ == '__main__':
        print(socket.gethostname())
        # https://www.open-mpi.org/faq/?category=running#mpi-environmental-variables
        print('ompi_comm_world_size: '   os.environ['ompi_comm_world_size'])
        print('ompi_comm_world_rank: '   os.environ['ompi_comm_world_rank'])
        print('ompi_comm_world_local_rank: '   os.environ['ompi_comm_world_local_rank'])

step3 准备镜像主机

准备一台linux x86_64架构的主机,操作系统使用ubuntu-18.04。您可以准备相同规格的弹性云服务器ecs或者应用本地已有的主机进行自定义镜像的制作。

购买ecs服务器的具体操作请参考购买并登录linux弹性云服务器“cpu架构”选择“x86计算”“镜像”选择“公共镜像”,推荐使用ubuntu18.04的镜像。

step4 制作自定义镜像

目标:构建安装好如下软件的容器镜像,并使用modelarts训练服务运行。

  • ubuntu-18.04
  • cuda-11.1
  • python-3.7.13
  • openmpi-3.0.0

此处介绍如何通过编写dockerfile文件制作自定义镜像的操作步骤。

  1. 安装docker。

    以linux x86_64架构的操作系统为例,获取docker安装包。您可以使用以下指令安装docker。关于安装docker的更多指导内容参见。

    curl -fssl get.docker.com -o get-docker.sh
    sh get-docker.sh

    如果docker images命令可以执行成功,表示docker已安装,此步骤可跳过。

  2. 确认docker engine版本。执行如下命令。
    docker version | grep -a 1 engine
    命令回显如下。
     engine:
      version:          18.09.0

    推荐使用大于等于该版本的docker engine来制作自定义镜像。

  3. 准备名为context的文件夹。
    mkdir -p context
  4. 下载miniconda3安装文件。

    使用地址https://repo.anaconda.com/miniconda/miniconda3-py37_4.12.0-linux-x86_64.sh,下载miniconda3 py37 4.12.0安装文件(对应python 3.7.13)。

  5. 下载openmpi 3.0.0安装文件。

    使用地址https://github.com/horovod/horovod/files/1596799/openmpi-3.0.0-bin.tar.gz,下载horovod v0.22.1已经编译好的openmpi 3.0.0文件。

  6. 将上述miniconda3安装文件、openmpi 3.0.0文件放置在context文件夹内,context文件夹内容如下。
    context
    ├── miniconda3-py37_4.12.0-linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  7. 编写容器镜像dockerfile文件。
    在context文件夹内新建名为dockerfile的空文件,并将下述内容写入其中。
    # 容器镜像构建主机需要连通公网
    # 基础容器镜像, https://github.com/nvidia/nvidia-docker/wiki/cuda
    #
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    # require docker engine >= 17.05
    #
    # builder stage
    from nvidia/cuda:11.1.1-runtime-ubuntu18.04 as builder
    # 基础容器镜像的默认用户已经是 root
    # user root
    # 复制 miniconda3 (python 3.7.13) 安装文件到基础容器镜像中的 /tmp 目录
    copy miniconda3-py37_4.12.0-linux-x86_64.sh /tmp
    # 安装 miniconda3 到基础容器镜像的 /home/ma-user/miniconda3 目录中
    # https://conda.io/projects/conda/en/latest/user-guide/install/linux.html#installing-on-linux
    run bash /tmp/miniconda3-py37_4.12.0-linux-x86_64.sh -b -p /home/ma-user/miniconda3
    # 构建最终容器镜像
    from nvidia/cuda:11.1.1-runtime-ubuntu18.04
    # 安装 vim / curl / net-tools / ssh 工具(依然使用华为开源镜像站)
    run cp -a /etc/apt/sources.list /etc/apt/sources.list.bak && \
        sed -i "s@http://.*archive.ubuntu.com@http://repo.huaweicloud.com@g" /etc/apt/sources.list && \
        sed -i "s@http://.*security.ubuntu.com@http://repo.huaweicloud.com@g" /etc/apt/sources.list && \
        echo > /etc/apt/apt.conf.d/00skip-verify-peer.conf "acquire { https::verify-peer false }" && \
        apt-get update && \
        apt-get install -y vim curl net-tools iputils-ping \
        openssh-client openssh-server && \
        ssh -v && \
        mkdir -p /run/sshd && \
        apt-get clean && \
        mv /etc/apt/sources.list.bak /etc/apt/sources.list && \
        rm /etc/apt/apt.conf.d/00skip-verify-peer.conf
    # 安装 horovod v0.22.1 已经编译好的 openmpi 3.0.0 文件
    # https://github.com/horovod/horovod/blob/v0.22.1/docker/horovod/dockerfile
    # https://github.com/horovod/horovod/files/1596799/openmpi-3.0.0-bin.tar.gz
    copy openmpi-3.0.0-bin.tar.gz /tmp
    run cd /usr/local && \
        tar -zxf /tmp/openmpi-3.0.0-bin.tar.gz && \
        ldconfig && \
        mpirun --version
    # 增加 ma-user 用户 (uid = 1000, gid = 100)
    # 注意到基础容器镜像已存在 gid = 100 的组,因此 ma-user 用户可直接使用
    run useradd -m -d /home/ma-user -s /bin/bash -g 100 -u 1000 ma-user
    # 从上述 builder stage 中复制 /home/ma-user/miniconda3 目录到当前容器镜像的同名目录
    copy --chown=ma-user:100 --from=builder /home/ma-user/miniconda3 /home/ma-user/miniconda3
    # 设置容器镜像预置环境变量
    # 请务必设置 pythonunbuffered=1, 以免日志丢失
    env path=$path:/home/ma-user/miniconda3/bin \
        pythonunbuffered=1
    # 设置容器镜像默认用户与工作目录
    user ma-user
    workdir /home/ma-user
    # 配置 sshd,使得 ssh 可以免密登录
    run ma_home=/home/ma-user && \
        # setup sshd dir
        mkdir -p ${ma_home}/etc && \
        ssh-keygen -f ${ma_home}/etc/ssh_host_rsa_key -n '' -t rsa  && \
        mkdir -p ${ma_home}/etc/ssh ${ma_home}/var/run  && \
        # setup sshd config (listen at {{my_sshd_port}} port)
        echo "port {{my_sshd_port}}\n\
    hostkey ${ma_home}/etc/ssh_host_rsa_key\n\
    authorizedkeysfile ${ma_home}/.ssh/authorized_keys\n\
    pidfile ${ma_home}/var/run/sshd.pid\n\
    strictmodes no\n\
    usepam no" > ${ma_home}/etc/ssh/sshd_config && \
        # generate ssh key
        ssh-keygen -t rsa -f ${ma_home}/.ssh/id_rsa -p '' && \
        cat ${ma_home}/.ssh/id_rsa.pub >> ${ma_home}/.ssh/authorized_keys && \
        # disable ssh host key checking for all hosts
        echo "host *\n\
      stricthostkeychecking no" > ${ma_home}/.ssh/config

    关于dockerfile文件编写的更多指导内容参见。

  8. 确认已创建完成dockerfile文件。此时context文件夹内容如下。
    context
    ├── dockerfile
    ├── miniconda3-py37_4.12.0-linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  9. 构建容器镜像。在dockerfile文件所在的目录执行如下命令构建容器镜像mpi:3.0.0-cuda11.1。
    1
    dockerbuild.-tmpi:3.0.0-cuda11.1
    
    构建过程结束时出现如下构建日志说明镜像构建成功。
    naming to docker.io/library/mpi:3.0.0-cuda11.1

step5 上传镜像至swr服务

  1. 登录容器镜像服务控制台,选择区域,要和modelarts区域保持一致,否则无法选择到镜像。
  2. 单击右上角“创建组织”,输入组织名称完成组织创建。请自定义组织名称,本示例使用“deep-learning”,下面的命令中涉及到组织名称“deep-learning”也请替换为自定义的值。
  3. 单击右上角“登录指令”,获取登录访问指令,本文选择复制临时登录指令。
  4. 以root用户登录本地环境,输入复制的swr临时登录指令。
  5. 上传镜像至容器镜像服务镜像仓库。
    1. 使用docker tag命令给上传镜像打标签。
      #region和domain信息请替换为实际值,组织名称deep-learning也请替换为自定义的值。
      sudo docker tag mpi:3.0.0-cuda11.1 swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
    2. 使用docker push命令上传镜像。
      #region和domain信息请替换为实际值,组织名称deep-learning也请替换为自定义的值。
      sudo docker push swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
  6. 完成镜像上传后,在“容器镜像服务控制台>我的镜像”页面可查看已上传的自定义镜像。

    “swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1”即为此自定义镜像的“swr_url”

step6 在modelarts上创建训练作业

  1. 登录modelarts管理控制台,检查当前账号是否已完成访问授权的配置。如未完成,请参考快速配置modelarts委托授权针对之前使用访问密钥授权的用户,建议清空授权,然后使用委托进行授权。
  2. 在modelarts管理控制台,左侧导航栏中选择“模型训练 > 训练作业”,默认进入“训练作业”列表。
  3. “创建训练作业”页面,填写相关参数信息,然后单击“提交”
    • 创建方式:选择“自定义算法”
    • 启动方式:选择“自定义”
    • 镜像地址:“swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1”
    • 代码目录:设置为obs中存放启动脚本文件的目录,例如:“obs://test-modelarts/mpi/demo-code/”
    • 启动命令:bash ${ma_job_dir}/demo-code/run_mpi.sh python ${ma_job_dir}/demo-code/mpi-verification.py
    • 环境变量:添加“my_sshd_port = 38888”
    • 资源池:选择公共资源池
    • 类型:选择gpu规格
    • 计算节点个数:选择“1”“2”
    • 永久保存日志:打开
    • 作业日志路径:设置为obs中存放训练日志的路径。例如:“obs://test-modelarts/mpi/log/”
  4. “规格确认”页面,确认训练作业的参数信息,确认无误后单击“提交”
  5. 训练作业创建完成后,后台将自动完成容器镜像下载、代码目录下载、执行启动命令等动作。

    训练作业一般需要运行一段时间,根据您的训练业务逻辑和选择的资源不同,训练时长将持续几十分钟到几小时不等。训练作业执行成功后,日志信息如图1所示。

    图1 1个计算节点gpu规格worker-0运行日志信息

    计算节点个数选择为2,训练作业也可以运行。日志信息如图2图3所示。

    图2 2个计算节点worker-0运行日志信息
    图3 2个计算节点worker-1运行日志信息

相关文档

网站地图