Estos contenidos se han traducido de forma automática para su comodidad, pero Huawei Cloud no garantiza la exactitud de estos. Para consultar los contenidos originales, acceda a la versión en inglés.
Centro de ayuda/ ModelArts/ Prácticas recomendadas/ Entrenamiento de modelos/ Ejemplo: creación de una imagen personalizada para entrenamiento (MPI + CPU/GPU)
Actualización más reciente 2024-09-20 GMT+08:00

Ejemplo: creación de una imagen personalizada para entrenamiento (MPI + CPU/GPU)

En esta sección se describe cómo crear una imagen y utilizarla para entrenamiento en la plataforma de ModelArts. El motor de IA utilizado para el entrenamiento es MPI y los recursos son CPU o GPU.

Esta sección solo se aplica a los trabajos de entrenamiento de la nueva versión.

Escenarios

En este ejemplo, cree una imagen personalizada escribiendo un Dockerfile en un host de Linux x86_64 que ejecute el sistema operativo Ubuntu 18.04.

Objetivo: crear e instalar imágenes de contenedor del siguiente software y utilizar las imágenes y las CPU/GPU para entrenamiento en ModelArts.

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

Procedimiento

Antes de usar una imagen personalizada para crear un trabajo de entrenamiento, familiarícese con Docker y tenga experiencia en desarrollo. El procedimiento detallado es el siguiente:

  1. Requisitos previos
  2. Paso 1 Crear un bucket de OBS y una carpeta
  3. Paso 2 Preparar los archivos y cargarlos en OBS
  4. Paso 3 Preparar un servidor de imágenes
  5. Paso 4 Crear una imagen personalizada
  6. Paso 5 Cargar una imagen en SWR
  7. Paso 6 Crear un trabajo de entrenamiento en ModelArts

Requisitos previos

Ha creado un ID de Huawei y ha habilitado los servicios en Huawei Cloud. Además, la cuenta no está en mora ni congelada.

Paso 1 Crear un bucket de OBS y una carpeta

Cree un bucket y una carpeta en OBS para almacenar la muestra de conjunto de datos y el código de entrenamiento. Tabla 1 enumera las carpetas que se crearán. En el ejemplo, el nombre del bucket y los nombres de las carpetas junto con los nombres reales.

Para obtener detalles sobre cómo crear un bucket de OBS y una carpeta, consulte Creación de un bucket y Creación de una carpeta.

Asegúrese de que el directorio de OBS que utiliza y ModelArts están en la misma región.

Tabla 1 Carpeta para crear

Nombre

Descripción

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

Almacena el script de arranque de MPI y el archivo de script de entrenamiento.

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

Almacena los archivos de log de entrenamiento.

Paso 2 Preparar los archivos y cargarlos en OBS

Prepare el script de inicio de MPI run_mpi.sh y el script de entrenamiento mpi-verification.py y cárguelos en la carpeta obs://test-modelarts/mpi/demo-code/ del bucket de OBS.

  • El contenido del script de inicio de MPI run_mpi.sh es el siguiente:
    #!/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

    El script run_mpi.sh utiliza finales de línea LF. Si se utilizan finales de línea CRLF, la ejecución del trabajo de entrenamiento fallará y se mostrará el error "$'\r': command not found" en los logs.

  • El contenido del script de entrenamiento mpi-verification.py es el siguiente:
    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'])

Paso 3 Preparar un servidor de imágenes

Obtener un servidor Linux x86_64 que ejecute Ubuntu 18.04. Un ECS o su PC local servirán.

Para obtener detalles sobre cómo comprar un ECS, consulte Compra e inicio de sesión en un ECS de Linux. Seleccione una imagen pública. Se recomienda una imagen de Ubuntu 18.04.
Figura 1 Creación de un ECS con una imagen pública (x86)

Paso 4 Crear una imagen personalizada

Objetivo: crear e instalar imágenes de contenedor del siguiente software y utilizar el servicio de entrenamiento de ModelArts para ejecutarlas.

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

A continuación se describe cómo crear una imagen personalizada escribiendo un Dockerfile.

  1. Instale Docker.

    A continuación se utiliza Linux x86_64 OS como ejemplo para describir cómo obtener un paquete de instalación de Docker. Para obtener más detalles, consulte los documentos oficiales de Docker. Ejecute los siguientes comandos para instalar Docker:

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

    Si se ejecuta el comando docker images, Docker se ha instalado. Si es así, omita este paso.

  2. Verifique la versión del motor de Docker. Ejecute el siguiente comando:
    docker version | grep -A 1 Engine
    Se muestra la siguiente información:
     Engine:
      Version:          18.09.0

    Se recomienda utilizar el Docker Engine de esta versión o posterior para crear una imagen personalizada.

  3. Cree una carpeta denominada context.
    mkdir -p context
  4. Descargue el archivo de instalación de Miniconda3.

    Descargue el archivo de instalación de Miniconda3 py37 4.12.0 (Python 3.7.13) desde https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh.

  5. Descargue el archivo de instalación de openmpi 3.0.0.

    Descargue el archivo de openmpi 3.0.0 editado con Horovod v0.22.1 desde https://github.com/horovod/horovod/files/1596799/openmpi-3.0.0-bin.tar.gz.

  6. Guarde los archivos de Miniconda3 y de openmpi 3.0.0 en la carpeta context. A continuación se muestra la carpeta context:
    context
    ├── Miniconda3-py37_4.12.0-Linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  7. Escriba el Dockerfile de la imagen de contenedor.
    Cree un archivo vacío denominado Dockerfile en la carpeta context y escriba el siguiente contenido en el archivo:
    # The host must be connected to the public network for creating a container image.
    
    # Basic container image at 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
    
    # The default user of the basic container image is root.
    # USER root
    
    # Copy the Miniconda3 (Python 3.7.13) installation files to the /tmp directory of the basic container image.
    COPY Miniconda3-py37_4.12.0-Linux-x86_64.sh /tmp
    
    # Install Miniconda3 to the /home/ma-user/miniconda3 directory of the basic container image.
    # 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
    
    # Create the final container image.
    FROM nvidia/cuda:11.1.1-runtime-ubuntu18.04
    
    # Install vim, cURL, net-tools, and the SSH tool in Huawei Mirrors.
    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
    
    # Install the Open MPI 3.0.0 file written using Horovod v0.22.1.
    # 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
    
    # Add user ma-user (UID = 1000, GID = 100).
    # A user group whose GID is 100 of the basic container image exists. User ma-user can directly use it.
    RUN useradd -m -d /home/ma-user -s /bin/bash -g 100 -u 1000 ma-user
    
    # Copy the /home/ma-user/miniconda3 directory from the builder stage to the directory with the same name in the current container image.
    COPY --chown=ma-user:100 --from=builder /home/ma-user/miniconda3 /home/ma-user/miniconda3
    
    # Configure the preset environment variables of the container image.
    # Set PYTHONUNBUFFERED to 1 to avoid log loss.
    ENV PATH=$PATH:/home/ma-user/miniconda3/bin \
        PYTHONUNBUFFERED=1
    
    # Set the default user and working directory of the container image.
    USER ma-user
    WORKDIR /home/ma-user
    
    # Configure sshd to support SSH password-free login.
    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

    Para obtener detalles sobre cómo escribir un Dockerfile, consulte los Documentos oficiales de Docker.

  8. Verifique que se haya creado el Dockerfile. A continuación se muestra la carpeta context:
    context
    ├── Dockerfile
    ├── Miniconda3-py37_4.12.0-Linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  9. Cree la imagen de contenedor. Ejecute el siguiente comando en el directorio donde se almacena Dockerfile para crear la imagen de contenedor mpi:3.0.0-cuda11.1:
    1
    docker build . -t mpi:3.0.0-cuda11.1
    
    La siguiente información de log mostrada durante la creación de la imagen indica que la imagen se ha creado.
    naming to docker.io/library/mpi:3.0.0-cuda11.1

Paso 5 Cargar una imagen en SWR

  1. Inicie sesión en la consola de SWR y seleccione la región de destino.
    Figura 2 Consola de SWR
  2. Haga clic en Create Organization en la esquina superior derecha e introduzca un nombre de organización para crear una organización. Personalice el nombre de la organización. Sustituya el nombre de la organización deep-learning en comandos posteriores con el nombre real de la organización.
    Figura 3 Creación de una organización
  3. Haga clic en Generate Login Command en la esquina superior derecha para obtener un comando de acceso.
    Figura 4 Comando de acceso
  4. Inicie sesión en el entorno local como usuario root e ingrese el comando de inicio de sesión.
  5. Cargue la imagen en SWR.
    1. Ejecute el siguiente comando para etiquetar la imagen cargada:
      #Replace the region and domain information with the actual values, and replace the organization name deep-learning with your custom value.
      sudo docker tag mpi:3.0.0-cuda11.1 swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
    2. Ejecute el siguiente comando para subir la imagen:
      #Replace the region and domain information with the actual values, and replace the organization name deep-learning with your custom value.
      sudo docker push swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
  6. Después de cargar la imagen, seleccione My Images en el panel de navegación izquierdo de la consola de SWR para ver las imágenes personalizadas cargadas.

    swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1 es la dirección URL de SWR de la imagen personalizada.

Paso 6 Crear un trabajo de entrenamiento en ModelArts

  1. Inicie sesión en la consola de gestión de ModelArts y compruebe si se ha configurado la autorización de acceso para su cuenta. Para obtener más detalles, consulte Configuración de autorización de delegación. Si se le ha autorizado mediante claves de acceso, borre la autorización y configure la autorización de delegación.
  2. Inicie sesión en la consola de gestión de ModelArts. En el panel de navegación izquierdo, seleccione Training Management > Training Jobs (New).
  3. En la página Create Training Job, configure los parámetros y haga clic en Submit.
    • Created By: Custom algorithms
    • Boot Mode: Custom images
    • Ruta de acceso a la imagen: swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
    • Code Directory: ruta de acceso de OBS al script de inicio, por ejemplo, obs://test-modelarts/mpi/demo-code/.
    • Boot Command: bash ${MA_JOB_DIR}/demo-code/run_mpi.sh python ${MA_JOB_DIR}/demo-code/mpi-verification.py
    • Environment Variable: Agregue MY_SSHD_PORT = 38888.
    • Resource Pool: Public resource pools
    • Resource Type: seleccione GPU.
    • Compute Nodes: Introduzca 1 o 2.
    • Persistent Log Saving: habilitado
    • Job Log Path: Establezca este parámetro en la ruta de OBS para almacenar logs de entrenamiento, por ejemplo, obs://test-modelarts/mpi/log/.
  4. Verifique los parámetros del trabajo de entrenamiento y haga clic en Submit.
  5. Espere hasta que finalice el trabajo de entrenamiento.

    Después de crear un trabajo de entrenamiento, las operaciones como la descarga de imágenes de contenedor, la descarga de directorios de código y la ejecución de comandos de arranque se realizan automáticamente en el backend. Por lo general, la duración del entrenamiento oscila entre decenas de minutos y varias horas, dependiendo del procedimiento de entrenamiento y de los recursos seleccionados. Una vez ejecutado el trabajo de entrenamiento, se muestra un log similar al siguiente.

    Figura 5 Ejecutar logs de worker-0 con un nodo de cómputo y especificaciones de GPU

    Configure Compute Nodes como 2 y ejecute el trabajo de entrenamiento. Figura 6 y Figura 7 muestran la información del log.

    Figura 6 Ejecutar logs de worker-0 con dos nodos de cómputo y especificaciones de GPU
    Figura 7 Ejecutar logs de worker-1 con dos nodos de cómputo y especificaciones de GPU