# 从0制作自定义镜像用于创建训练作业（Pytorch+Ascend）
本章节介绍如何从0到1制作镜像，并使用该镜像在ModelArts平台上进行训练。镜像中使用的AI引擎是PyTorch，训练使用的资源是专属资源池的Ascend芯片。
#### 场景描述
本示例使用Linux x86_64架构的主机，操作系统ubuntu-22.04，通过编写Dockerfile文件制作自定义镜像。
目标：构建安装如下软件的容器镜像，并在ModelArts平台上使用NPU规格资源运行训练作业。
- ubuntu-22.04
- Ascend-cann-nnae-8.5.0
- python-3.9
- pytorch-2.6.0
- pytorch_npu-2.6.0.post5
- Miniconda3-25.9.1-3
 
#### 准备工作
准备一套可以连接外部网络，装有Linux系统并安装26.1.3及以上版本docker的虚拟机或物理机用作镜像构建节点，以下称"构建节点"。
可以通过执行docker pull、apt-get update/upgrade和pip install命令判断是否可正常访问外部可用的开源软件仓库，如果可以正常访问表示环境已连接外部网络。
![](https://support.huaweicloud.com/docker-modelarts/public_sys-resources/notice_3.0-zh-cn.png)
- 上述的虚拟机或物理机需要为arm64架构。
- 建议构建节点安装的Linux系统版本为Ubuntu 22.04。
- 本指导使用/opt目录作为构建任务承载目录，请确保该目录下可用存储空间大于30GB。
- Docker的安装可以参考官方文档：[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)。MiniConda与tflite安装包为第三方安装包，ModelArts不对其安全相关问题进行负责，如用户有安全方面的需求，可以对该安装包进行加固后发布成同样名称的文件上传到构建节点。
 
#### 制作自定义镜像
1. 确认Docker Engine版本。执行如下命令。 
   ```
   docker version | grep -A 1 Engine
   ```
   命令回显如下。
   ```
   Engine:   
    Version: 26.1.3
   ```
   ![](https://support.huaweicloud.com/docker-modelarts/public_sys-resources/note_3.0-zh-cn.png)
   推荐使用大于等于该版本的Docker Engine来制作自定义镜像。
   
   
2. 准备名为context的文件夹。 
   ```
   mkdir -p context
   ```
   
   
3. 准备可用的pip源文件pip.conf 。本示例使用华为开源镜像站提供的pip源，其pip.conf文件内容如下。 
   ```
   [global]
   index-url = 
   https://repo.huaweicloud.com/repository/pypi/simple
   trusted-host = repo.huaweicloud.com
   timeout = 120
   ```
   ![](https://support.huaweicloud.com/docker-modelarts/public_sys-resources/note_3.0-zh-cn.png)
   在华为[开源镜像站](https://mirrors.huaweicloud.com/home)中，搜索pypi，可以查看pip.conf文件内容。
   
   
4. 下载Miniconda3安装文件。使用地址[++https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/++](https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/)，下载Miniconda3安装文件。Miniconda3-py39_25.9.1-3-Linux-aarch64.sh
5. 下载 torch_npu whl。下载torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl文件：请单击[++下载地址++](https://gitcode.com/Ascend/pytorch/releases/download/v7.3.0-pytorch2.6.0/torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl)下载。
6. 将上述pip源文件、*.list文件、* .run文件、 \*.whl文件、Miniconda3安装文件放置在context文件夹内，context文件夹内容如下。
   
   ```
   context
   ├── Miniconda3-py39_25.9.1-3-Linux-aarch64.sh
   ├── pip.conf
   ├── torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl
   ```
   
   
7. 编写容器镜像Dockerfile文件。 
   在context文件夹内新建名为Dockerfile的空文件，并将下述内容写入其中。
   ```
   # 容器镜像构建主机需要连通公网
   # ===== builder stage =====
   FROM ubuntu:22.04 AS builder
   ENV DEBIAN_FRONTEND=noninteractive
   # 准备 pip.conf / miniconda 安装器 / torch_npu whl
   RUN mkdir -p /root/.pip/
   COPY pip.conf /root/.pip/pip.conf
   COPY Miniconda3-py39_25.9.1-3-Linux-aarch64.sh /tmp
   COPY torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl /tmp
   # 使用标准 jammy arm64 源（ports.ubuntu.com），并安装基础依赖
   RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && \
       printf "deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse\n" > /etc/apt/sources.list && \
       apt-get update && \
       apt-get install -y --no-install-recommends \
           zip wget ca-certificates vim curl python3 python3-pip tzdata gnupg && \
       ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
       dpkg-reconfigure --frontend noninteractive tzdata && \
       echo Y | apt-get install -y --no-install-recommends openssh-server && \
       apt-get clean && \
       rm -rf /var/lib/apt/lists/* && \
       mv /etc/apt/sources.list.bak /etc/apt/sources.list
   # 创建 ma-user 用户 (uid=1000, gid=100)，处理可能已存在 uid/gid 的情况
   RUN default_user=$(getent passwd 1000 | awk -F ':' '{print $1}') || echo "uid: 1000 does not exist" && \
       default_group=$(getent group 100 | awk -F ':' '{print $1}') || echo "gid: 100 does not exist" && \
       if [ ! -z "${default_user}" ] && [ "${default_user}" != "ma-user" ]; then \
           userdel -r ${default_user}; \
       fi && \
       if [ ! -z "${default_group}" ] && [ "${default_group}" != "ma-group" ]; then \
           groupdel -f ${default_group}; \
       fi && \
       if ! getent group 100 > /dev/null; then \
           groupadd -g 100 ma-group; \
       fi && \
       useradd -d /home/ma-user -m -u 1000 -g 100 -s /bin/bash ma-user && \
       chmod -R 750 /home/ma-user
   # 临时文件 chown 给 ma-user（让后续 rm 能成功，/tmp 有 sticky bit）
   RUN chown 1000:100 /tmp/Miniconda3-py39_25.9.1-3-Linux-aarch64.sh /tmp/torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl
   # 切到 ma-user 安装 miniconda
   USER ma-user
   WORKDIR /home/ma-user
   # 安装 miniconda-py39（默认 base = python 3.9.x）
   RUN bash /tmp/Miniconda3-py39_25.9.1-3-Linux-aarch64.sh -b -p /home/ma-user/miniconda3 && \
       rm -f /tmp/Miniconda3-py39_25.9.1-3-Linux-aarch64.sh
   # 配置 conda 源
   RUN /home/ma-user/miniconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main && \
       /home/ma-user/miniconda3/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge && \
       /home/ma-user/miniconda3/bin/conda config --set show_channel_urls yes
   # 创建 pytorch_2_6 虚拟环境（python 3.9）+ 安装 torch/torchvision/torchaudio/torch_npu
   RUN /bin/bash -c '\
       source /home/ma-user/miniconda3/bin/activate base && \
       conda create -y --override-channels \
       -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main \
       -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge \
       -n pytorch_2_6 python=3.9 && \
       conda run -n pytorch_2_6 \
       pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn \
       torchvision==0.21.0 torchaudio==2.6.0 numpy==1.26.4 && \
       conda run -n pytorch_2_6 \
       pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn \
       torch==2.6.0 && \
       conda run -n pytorch_2_6 \
       pip install /tmp/torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl && \
        conda run -n pytorch_2_6 pip install protobuf==3.20 pyyaml importlib_metadata && \
       rm -f /tmp/torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl \
   '
   # 在 pytorch_2_6 环境中安装 CANN toolkit/ops
   RUN /bin/bash -c '\
       source /home/ma-user/miniconda3/bin/activate base && \
       conda run -n pytorch_2_6 \
       pip install ascend-cann-toolkit==8.5.0 \
       --extra-index-url https://ascend.devcloud.huaweicloud.com/cann/pypi/simple \
       --trusted-host ascend.devcloud.huaweicloud.com && \
       conda run -n pytorch_2_6 \
       pip install ascend-cann-910b-ops==8.5.0 \
       --extra-index-url https://ascend.devcloud.huaweicloud.com/cann/pypi/simple \
       --trusted-host ascend.devcloud.huaweicloud.com \
   '
   RUN /bin/bash -c '\
       source /home/ma-user/miniconda3/bin/activate base && \
       conda run -n pytorch_2_6 \
       pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn \
       ipykernel==6.7.0 \
   '
   # ===== 最终镜像 =====
   FROM ubuntu:22.04
   ENV DEBIAN_FRONTEND=noninteractive
   # 装基础工具（vim/curl），使用 ports.ubuntu.com jammy 源
   RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && \
       printf "deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted universe multiverse\n\
   deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse\n" > /etc/apt/sources.list && \
       apt-get update && \
       apt-get install -y --no-install-recommends \
           ca-certificates vim curl && \
       apt-get clean && \
       rm -rf /var/lib/apt/lists/* && \
       mv /etc/apt/sources.list.bak /etc/apt/sources.list
   # 创建 ma-user 用户 (uid=1000, gid=100)
   RUN default_user=$(getent passwd 1000 | awk -F ':' '{print $1}') || echo "uid: 1000 does not exist" && \
       default_group=$(getent group 100 | awk -F ':' '{print $1}') || echo "gid: 100 does not exist" && \
       if [ ! -z "${default_user}" ] && [ "${default_user}" != "ma-user" ]; then \
           userdel -r ${default_user}; \
       fi && \
       if [ ! -z "${default_group}" ] && [ "${default_group}" != "ma-group" ]; then \
           groupdel -f ${default_group}; \
       fi && \
       if ! getent group 100 > /dev/null; then \
           groupadd -g 100 ma-group; \
       fi && \
       useradd -d /home/ma-user -m -u 1000 -g 100 -s /bin/bash ma-user && \
       chmod -R 750 /home/ma-user
   # 从 builder 复制 miniconda（含 pytorch_2_6 虚拟环境）
   COPY --chown=ma-user:100 --from=builder /home/ma-user/miniconda3 /home/ma-user/miniconda3
   # CANN 环境变量（CANN 8.5.0 装在 pytorch_2_6 环境的 site-packages 下）
   ENV CANN_HOME=/home/ma-user/miniconda3/envs/pytorch_2_6/lib/python3.9/site-packages/ascend/cann-8.5.0
   ENV PATH=/home/ma-user/miniconda3/envs/pytorch_2_6/bin:${CANN_HOME}/bin:${CANN_HOME}/compiler/ccec_compiler/bin:$PATH
   ENV LD_LIBRARY_PATH=${CANN_HOME}/aarch64-linux/lib64:${CANN_HOME}/aarch64-linux/lib64/device/lib64:${CANN_HOME}/aarch64-linux/lib64/plugin/opskernel:${CANN_HOME}/aarch64-linux/lib64/plugin/nnengine:${CANN_HOME}/opp/built-in/op_impl/ai_core/tbe/op_tiling/lib/linux/aarch64
   ENV PYTHONPATH=${CANN_HOME}/python/site-packages:${CANN_HOME}/opp/op_impl/built-in/ai_core/tbe:$PYTHONPATH
   ENV ASCEND_AICPU_PATH=${CANN_HOME}
   ENV ASCEND_OPP_PATH=${CANN_HOME}/opp
   ENV ASCEND_HOME_PATH=${CANN_HOME}
   ENV ASCEND_TOOLKIT_HOME=${CANN_HOME}
   ENV TOOLCHAIN_HOME=${CANN_HOME}
   ENV PYTHONUNBUFFERED=1
   ENV USE_TRITON=0
   # 设置容器镜像默认用户与工作目录
   USER ma-user
   WORKDIR /home/ma-user
   ```
   关于Dockerfile文件编写的更多指导内容参见[Docker官方文档](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)。
   
   
8. 确认已创建完成Dockerfile文件。此时context文件夹内容如下。 
   ```
   context
   ├── Miniconda3-py39_25.9.1-3-Linux-aarch64.sh
   ├── pip.conf
   ├── torch_npu-2.6.0.post5-cp39-cp39-manylinux_2_28_aarch64.whl
   ├── Dockerfile
   ```
   ```
   ```
   
   
9. 构建容器镜像。在Dockerfile文件所在的目录执行如下命令构建容器镜像。 
   ```
   docker build . -t pytorch:2.6.0-npu
   ```
   ![](https://support.huaweicloud.com/docker-modelarts/public_sys-resources/note_3.0-zh-cn.png)
   如果构建中访问https://registry-1.docker.io/v2/ 出现connection refused或者Client.Timeout exceeded问题，需要配置下docker代理。
   vi /etc/docker/daemon.json
   文件中写入以下内容，并保存文件
   {
   "registry-mirrors":\[
   "https://docker.m.daocloud.io",
   "https://docker.jianmuhub.com",
   "https://huecker.io",
   "https://dockerhub.timeweb.cloud",
   "https://dockerhub1.beget.com",
   "https://noohub.ru"\]
   }
   依次执行systemctl daemon-reload和systemctl restart docker
   重新构建
   构建过程结束时出现如下构建日志说明镜像构建成功。
   ```
   Successfully tagged pytorch:2.6.0-npu
   ```
   
   
 
#### 上传镜像至SWR服务
1. 登录[容器镜像服务控制台](https://console.huaweicloud.com/swr/#/swr/dashboard)，选择区域。
2. 在控制台主页面单击"创建组织"，输入组织名称完成组织创建。您可以自定义组织名称，本示例使用"deep-learning"，实际操作时请重新命名一个组织名称。后续所有命令中使用到组织名称deep-learning时，均需要替换为此处实际创建的组织名称。
3. 创建完毕后，在控制台主页面单击"登录指令"，获取登录访问指令。以root用户登录ECS环境，输入登录指令，具体可参考[在ECS上构建自定义镜像并在Notebook中使用](https://support.huaweicloud.com/docker-modelarts/docker-modelarts_0011-0.html)。
4. 上传镜像至容器镜像服务镜像仓库。
   1. 使用docker tag命令给上传镜像打标签。
      ```
      #region和domain信息请替换为实际值，组织名称deep-learning也请替换为自定义的值。
      sudo docker tag pytorch:2.6.0-npu swr.{region-id}.{domain}/deep-learning/pytorch:2.6.0-npu
      #此处以华为云cn-north-4为例
      sudo docker tagpytorch:2.6.0-npu swr.cn-north-4.myhuaweicloud.com/deep-learning/pytorch:2.6.0-npu
      ```
      
   
   2. 使用docker push命令上传镜像。
      ```
      #region和domain信息请替换为实际值，组织名称deep-learning也请替换为自定义的值。
      sudo docker push swr.{region-id}.{domain}/deep-learning/pytorch:2.6.0-npu
      #此处以华为云cn-north-4为例
      sudo docker push swr.cn-north-4.myhuaweicloud.com/deep-learning/pytorch:2.6.0-npu
      ```
      
    
5. 完成镜像上传后，在**容器镜像服务**控制台的"我的镜像"页面可查看已上传的自定义镜像。
"swr.cn-north-4.myhuaweicloud.com/deep-learning/pytorch:2.6.0-npu"即为此自定义镜像的"SWR_URL"。
图1查看上传镜像   
![](https://support.huaweicloud.com/docker-modelarts/figure/zh-cn_image_0000002763002471.png "点击放大")
#### 在ModelArts上创建训练作业
1. 登录[ModelArts管理控制台](https://console.huaweicloud.com/modelarts/)，检查当前账号是否已完成访问授权的配置。如未完成，请参考[快速配置ModelArts委托授权](https://support.huaweicloud.com/permission-modelarts/modelarts_24_0085.html)**。**针对之前使用访问密钥授权的用户，建议清空授权，然后使用委托进行授权。
2. 在左侧导航栏按需选择以下操作。
   - 新版控制台：选择"模型开发与训练 \> 模型训练"，进入训练作业列表。
   
   - 旧版控制台：选择"模型训练 \> 训练作业"，进入训练作业列表。
    
3. 在"创建训练作业"页面，填写相关参数信息，然后单击"提交"。
   - 创建方式：选择"自定义算法"。
   
   - 启动方式：选择"自定义"。
   
   - 镜像地址：swr.cn-north-4.myhuaweicloud.com/deep-learning/pytorch:2.6.0-npu
   
   - 代码目录：设置为OBS中存放启动脚本文件的目录，例如："obs://test-modelarts/pytorch/demo-code/"，训练代码会被自动下载至训练容器的"${MA_JOB_DIR}/demo-code"目录中，"demo-code"为OBS存放代码路径的最后一级目录，可以根据实际修改。
   
   - 启动命令："/home/ma-user/miniconda3/bin/python ${MA_JOB_DIR}/demo-code/pytorch-verification.py" ，此处的"demo-code"为用户自定义的OBS存放代码路径的最后一级目录，可以根据实际修改。
   
   - 资源池：选择专属资源池。
   
   - 类型：选择驱动/固件版本匹配的专属资源池Ascend规格。
   
   - 作业日志路径：设置为OBS中存放训练日志的路径。例如："obs://test-modelarts/pytorch/log/"
    
4. 在"规格确认"页面，确认训练作业的参数信息，确认无误后单击"提交"。
5. 训练作业创建完成后，后台将自动完成容器镜像下载、代码目录下载、执行启动命令等动作。 训练作业一般需要运行一段时间，根据您的训练业务逻辑和选择的资源不同，训练时长将持续几十分钟到几小时不等。您可以在作业详情页面，查看日志信息。
   
 
