华为云AI开发平台ModelArts示例:从 0 到 1 制作自定义镜像并用于训练(MindSpore+GPU)_云淘科技
本章节介绍如何从0到1制作镜像,并使用该镜像在ModelArts平台上进行训练。镜像中使用的AI引擎是MindSpore,训练使用的资源是GPU。
本实践教程仅适用于新版训练作业。
场景描述
本示例使用 Linux x86_64 架构的主机,操作系统ubuntu-18.04,通过编写 Dockerfile 文件制作自定义镜像。
目标:构建安装如下软件的容器镜像,并在ModelArts平台上使用GPU规格资源运行训练任务。
ubuntu-18.04
cuda-11.1
python-3.7.13
mlnx ofed-5.4
mindspore gpu-1.8.1
操作流程
使用自定义镜像创建训练作业时,需要您熟悉 docker 软件的使用,并具备一定的开发经验。详细步骤如下所示:
前提条件
Step1 创建OBS桶和文件夹
Step2 创建数据集并上传至OBS
Step3 准备训练脚本并上传至OBS
Step4 准备镜像主机
Step5 制作自定义镜像
Step6 上传镜像至SWR服务
Step7 在ModelArts上创建训练作业
前提条件
已注册华为帐号并开通华为云,且在使用ModelArts前检查账号状态,账号不能处于欠费或冻结状态。
Step1 创建OBS桶和文件夹
在 OBS 服务中创建桶和文件夹,用于存放样例数据集以及训练代码。需要创建的文件夹列表如表1所示,示例中的桶名称“test-modelarts” 和文件夹名称均为举例,请替换为用户自定义的名称。
创建 OBS 桶和文件夹的操作指导请参见创建桶和新建文件夹。
请确保您使用的 OBS 与 ModelArts 在同一区域。
文件夹名称 |
用途 |
---|---|
“obs://test-modelarts/mindspore-gpu/resnet/” |
用于存储训练脚本文件。 |
“obs://test-modelarts/mindspore-gpu/cifar-10-batches-bin/” |
用于存储数据集文件。 |
“obs://test-modelarts/mindspore-gpu/output/” |
用于存储训练输出文件。 |
“obs://test-modelarts/mindspore-gpu/log/” |
用于存储训练日志文件。 |
Step2 创建数据集并上传至OBS
进入网站http://www.cs.toronto.edu/~kriz/cifar.html,下载“CIFAR-10 binary version (suitable for C programs)”,解压后将数据上传至OBS桶的“obs://test-modelarts/mindspore-gpu/cifar-10-batches-bin/”文件夹下。OBS桶中数据集如下所示:
图1 数据集
Step3 准备训练脚本并上传至OBS
准备本案例所需的resnet文件和脚本run_mpi.sh,并上传至OBS桶的“obs://test-modelarts/mindspore-gpu/resnet/”文件夹下。
resnet文件下载地址:https://gitee.com/mindspore/models/tree/r1.8/official/cv/resnet
run_mpi.sh文件内容如下:
#!/bin/bash MY_HOME=/home/ma-user MY_SSHD_PORT=${MY_SSHD_PORT:-"36666"} MY_MPI_BTL_TCP_IF=${MY_MPI_BTL_TCP_IF:-"eth0,bond0"} 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} MY_SSHD_PORT: ${MY_SSHD_PORT} MY_MPI_BTL_TCP_IF: ${MY_MPI_BTL_TCP_IF} MY_TASK_INDEX: ${MY_TASK_INDEX} MY_MPI_SLOTS: ${MY_MPI_SLOTS} " env | grep -E '^MA_|^AWS_|^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/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: `cat ${MY_HOME}/hostfile` " 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=INFO -x NCCL_SOCKET_IFNAME=${MY_MPI_BTL_TCP_IF} -x NCCL_SOCKET_FAMILY=AF_INET \ -x HOROVOD_MPI_THREADS_DISABLE=1 \ -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: `cat ${MY_HOME}/hostfile` " mpirun \ --hostfile ${MY_HOME}/hostfile \ --mca btl_tcp_if_include ${MY_MPI_BTL_TCP_IF} \ --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
“obs://test-modelarts/mindspore-gpu/resnet/”文件夹下内容如下图,包含resnet文件和run_mpi.sh:
图2 resnet文件和run_mpi.sh
Step4 准备镜像主机
准备一台 Linux x86_64 架构的主机,操作系统使用ubuntu-18.04。您可以准备相同规格的 弹性云服务器ECS 或者应用本地已有的主机进行自定义镜像的制作。
购买ECS服务器的具体操作请参考购买并登录弹性云服务器。镜像选择公共镜像,推荐使用ubuntu18.04的镜像。
图3 创建ECS服务器-选择X86架构的公共镜像
Step5 制作自定义镜像
目标:构建安装好如下软件的容器镜像,并使用 ModelArts 训练服务运行。
ubuntu-18.04
cuda-11.1
python-3.7.13
mlnx ofed-5.4
mindspore gpu-1.8.1
此处介绍如何通过编写 Dockerfile 文件制作自定义镜像的操作步骤 。
安装 Docker。
以 Linux x86_64 架构的操作系统为例,获取 Docker 安装包。您可以使用以下指令安装 Docker。关于安装 Docker 的更多指导内容参见 Docker 官方文档。
curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh
如果 docker images 命令可以执行成功,表示 Docker 已安装,此步骤可跳过。
确认 Docker Engine 版本。执行如下命令。
docker version | grep -A 1 Engine
命令回显如下。
Engine: Version: 18.09.0
推荐使用大于等于该版本的 Docker Engine 来制作自定义镜像。
准备名为 context 的文件夹。
mkdir -p context
准备可用的pip源文件 pip.conf 。本示例使用华为开源镜像站提供的pip源,其 pip.conf 文件内容如下。
[global] index-url = https://repo.huaweicloud.com/repository/pypi/simple trusted-host = repo.huaweicloud.com timeout = 120
在华为开源镜像站 https://mirrors.huaweicloud.com/home 中,搜索 pypi ,也可以查看 pip.conf 文件内容。
下载mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl文件。
使用网站https://ms-release.obs.cn-north-4.myhuaweicloud.com/1.8.1/MindSpore/gpu/x86_64/cuda-11.1/mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl,mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl文件。
下载 Miniconda3 安装文件。
使用地址 https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh, 下载 Miniconda3 py37 4.12.0 安装文件(对应 python 3.7.13)。
编写容器镜像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-devel-ubuntu18.04 AS builder # 基础容器镜像的默认用户已经是 root # USER root # 使用华为开源镜像站提供的 pypi 配置 RUN mkdir -p /root/.pip/ COPY pip.conf /root/.pip/pip.conf # 拷贝待安装文件到基础容器镜像中的 /tmp 目录 COPY Miniconda3-py37_4.12.0-Linux-x86_64.sh /tmp COPY mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl /tmp # https://conda.io/projects/conda/en/latest/user-guide/install/linux.html#installing-on-linux # 安装 Miniconda3 到基础容器镜像的 /home/ma-user/miniconda3 目录中 RUN bash /tmp/Miniconda3-py37_4.12.0-Linux-x86_64.sh -b -p /home/ma-user/miniconda3 # 使用 Miniconda3 默认 python 环境 (即 /home/ma-user/miniconda3/bin/pip) 安装 mindspore whl RUN cd /tmp && \ /home/ma-user/miniconda3/bin/pip install --no-cache-dir \ /tmp/mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl \ easydict PyYAML # 构建最终容器镜像 FROM nvidia/cuda:11.1.1-cudnn8-runtime-ubuntu18.04 COPY MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64.tgz /tmp # 安装 vim / curl / net-tools / mlnx ofed / 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 libfile-find-rule-perl-perl \ openssh-client openssh-server && \ ssh -V && \ mkdir -p /run/sshd && \ # mlnx ofed apt-get install -y python libfuse2 dpatch libnl-3-dev autoconf libnl-route-3-dev pciutils libnuma1 libpci3 m4 libelf1 debhelper automake graphviz bison lsof kmod libusb-1.0-0 swig libmnl0 autotools-dev flex chrpath libltdl-dev && \ cd /tmp && \ tar -xvf MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64.tgz && \ MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64/mlnxofedinstall --user-space-only --basic --without-fw-update -q && \ cd - && \ rm -rf /tmp/* && \ 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 # 设置容器镜像默认用户与工作目录 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}} \ HostKey ${MA_HOME}/etc/ssh_host_rsa_key \ AuthorizedKeysFile ${MA_HOME}/.ssh/authorized_keys \ PidFile ${MA_HOME}/var/run/sshd.pid \ StrictModes no \ 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 * \ StrictHostKeyChecking no" > ${MA_HOME}/.ssh/config # 设置容器镜像预置环境变量 # 请务必设置 PYTHONUNBUFFERED=1, 以免日志丢失 ENV PATH=/home/ma-user/miniconda3/bin:$PATH \ LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH \ PYTHONUNBUFFERED=1
关于 Dockerfile 文件编写的更多指导内容参见 Docker 官方文档。
下载MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64.tgz。
进入地址https://network.nvidia.com/products/infiniband-drivers/linux/mlnx_ofed/,点击“Download”,“Version”选择“5.4-3.5.8.0-LTS”,“OSDistributionVersion”选择“Ubuntu 18.04”,“Architecture”选择“x86_64”,下载MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64.tgz。
下载openmpi-3.0.0-bin.tar.gz。
使用地址https://github.com/horovod/horovod/files/1596799/openmpi-3.0.0-bin.tar.gz, 下载openmpi-3.0.0-bin.tar.gz文件。
将上述 Dockerfile文件、 Miniconda3 安装文件等放置在 context 文件夹内,context 文件夹内容如下。
context ├── Dockerfile ├── MLNX_OFED_LINUX-5.4-3.5.8.0-ubuntu18.04-x86_64.tgz ├── Miniconda3-py37_4.12.0-Linux-x86_64.sh ├── mindspore_gpu-1.8.1-cp37-cp37m-linux_x86_64.whl ├── openmpi-3.0.0-bin.tar.gz └── pip.conf
构建容器镜像。在Dockerfile文件所在的目录执行如下命令构建容器镜像mindspore:1.8.1-ofed-cuda11.1。
1 |
docker build . -t mindspore:1.8.1-ofed-cuda11.1 |
构建过程结束时出现如下构建日志说明镜像构建成功。
Successfully tagged mindspore:1.8.1-ofed-cuda11.1
Step6 上传镜像至SWR服务
登录容器镜像服务控制台,选择区域。
图4 容器镜像服务控制台
单击右上角“创建组织”,输入组织名称完成组织创建。请自定义组织名称,本示例使用“deep-learning”,下面的命令中涉及到组织名称“deep-learning”也请替换为自定义的值。
图5 创建组织
单击右上角“登录指令”,获取登录访问指令。
图6 登录指令
以root用户登录本地环境,输入登录访问指令。
上传镜像至容器镜像服务镜像仓库。
使用docker tag命令给上传镜像打标签。
#region和domain信息请替换为实际值,组织名称deep-learning也请替换为自定义的值。 sudo docker tag mindspore:1.8.1-ofed-cuda11.1 swr.{region-id}.{domain}/deep-learning/mindspore:1.8.1-ofed-cuda11.1 #此处以华为云cn-north-4为例 sudo docker tag mindspore:1.8.1-ofed-cuda11.1 swr.cn-north-4.myhuaweicloud.com/deep-learning/mindspore:1.8.1-ofed-cuda11.1
使用docker push命令上传镜像。
#region和domain信息请替换为实际值,组织名称deep-learning也请替换为自定义的值。 sudo docker push swr.{region-id}.{domain}/deep-learning/mindspore:1.8.1-ofed-cuda11.1 #此处以华为云cn-north-4为例 sudo docker push swr.cn-north-4.myhuaweicloud.com/deep-learning/mindspore:1.8.1-ofed-cuda11.1
完成镜像上传后,在“容器镜像服务控制台>我的镜像”页面可查看已上传的自定义镜像。
“swr.cn-north-4.myhuaweicloud.com/deep-learning/mindspore:1.8.1-ofed-cuda11.1”即为此自定义镜像的“SWR_URL”。
Step7 在ModelArts上创建训练作业
登录ModelArts管理控制台,检查当前账号是否已完成访问授权的配置。如未完成,请参考使用委托授权。针对之前使用访问密钥授权的用户,建议清空授权,然后使用委托进行授权。
在左侧导航栏中选择“训练管理 > 训练作业”,默认进入“训练作业”列表。
在“创建训练作业”页面,填写相关参数信息,然后单击“下一步”。
创建方式:选择“自定义算法”。
镜像来源:选择“自定义”。
镜像地址:Step6 上传镜像至SWR服务中创建的镜像。“swr.cn-north-4.myhuaweicloud.com/deep-learning/mindspore:1.8.1-ofed-cuda11.1”。
代码目录:设置为OBS中存放启动脚本文件的目录,例如:“obs://test-modelarts/mindspore-gpu/resnet/”,训练代码会被自动下载至训练容器的“${MA_JOB_DIR}/resnet”目录中,“resnet”为OBS存放代码路径的最后一级目录,可以根据实际修改。
启动命令:“bash ${MA_JOB_DIR}/resnet/run_mpi.sh python ${MA_JOB_DIR}/resnet/train.py” ,此处的“resnet”为用户自定义的OBS存放代码路径的最后一级目录,可以根据实际修改。
训练输入:点击“增加训练输入”,参数名称设置为“data_path”,选择OBS中存放数据集的目录,例如“obs://test-modelarts/mindspore-gpu/cifar-10-batches-bin/”,获取方式设置为“超参”。
训练输出:点击“增加训练输出”,参数名称设置为“output_path”,选择OBS存放训练输出的路径,例如:“obs://test-modelarts/mindspore-gpu/output/”,获取方式设置为“超参”,预下载至本地目录设置为“不下载”。
超参:点击“增加超参”,增加如下四个超参:
run_distribute=True
device_num=1(根据规格中的GPU卡数设置)
device_target=GPU
epoch_size=2
环境变量:点击“增加环境变量”,增加环境变量:MY_SSHD_PORT=38888。
资源池:选择公共资源池。
资源类型:选择GPU规格。
计算节点个数:1个或者2个。
永久保存日志:打开。
作业日志路径:设置为OBS中存放训练日志的路径。例如:“obs://test-modelarts/mindspore-gpu/log/”。
在“规格确认”页面,确认训练作业的参数信息,确认无误后单击“提交”。
训练作业创建完成后,后台将自动完成容器镜像下载、代码目录下载、执行启动命令等动作。
训练作业一般需要运行一段时间,根据您的训练业务逻辑和选择的资源不同,训练时长将持续几十分钟到几小时不等。训练作业执行成功后,日志信息如下所示。
图7 GPU 规格运行日志信息(1个计算节点)
图8 GPU 规格运行日志信息(2个计算节点)
父主题: 模型训练
同意关联代理商云淘科技,购买华为云产品更优惠(QQ 78315851)
内容没看懂? 不太想学习?想快速解决? 有偿解决: 联系专家