Este conteúdo foi traduzido por máquina para sua conveniência e a Huawei Cloud não pode garantir que o conteúdo foi traduzido com precisão. Para exibir o conteúdo original, use o link no canto superior direito para mudar para a página em inglês.
Atualizado em 2024-09-14 GMT+08:00

Exemplo: criar uma imagem personalizada para treinamento (MPI + CPU/GPU)

Esta seção descreve como criar uma imagem e usá-la para treinamento na plataforma ModelArts. O mecanismo de IA usado para o treinamento é MPI e os recursos são CPUs ou GPUs.

Esta seção se aplica somente aos trabalhos de treinamento da nova versão.

Cenários

Neste exemplo, crie uma imagem personalizada gravando um Dockerfile em um host de Linux x86_64 executando o sistema operacional Ubuntu 18.04.

Objetivo: crie e instale imagens de contêiner dos seguintes softwares e use as imagens e CPUs/GPUs para treinamento no ModelArts.

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

Procedimento

Antes de usar uma imagem personalizada para criar um trabalho de treinamento, familiarize-se com o Docker e tenha experiência em desenvolvimento. O seguinte é o procedimento detalhado:

  1. Pré-requisitos
  2. Etapa 1 Criar um bucket e uma pasta do OBS
  3. Etapa 2 Preparar os arquivos de script e carregá-los para o OBS
  4. Etapa 3 Preparar um servidor de imagens
  5. Etapa 4 Criar uma imagem personalizada
  6. Etapa 5 Carregar a imagem para o SWR
  7. Etapa 6 Criar um trabalho de treinamento no ModelArts

Pré-requisitos

Você registrou um ID da Huawei e ativou os serviços da Huawei Cloud, e a conta não está em atraso ou congelada.

Etapa 1 Criar um bucket e uma pasta do OBS

Crie um bucket e pastas no OBS para armazenar o conjunto de dados de amostra e o código de treinamento. Tabela 1 lista as pastas a serem criadas. Substitua o nome do bucket e os nomes da pasta no exemplo por nomes reais.

Para obter detalhes sobre como criar um bucket e uma pasta do OBS, consulte Criação de um bucket e Criação de uma pasta.

Verifique se o diretório do OBS que você usa e o ModelArts estão na mesma região.

Tabela 1 Pasta a criar

Nome

Descrição

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

Armazena o script de inicialização de MPI e o arquivo de script de treinamento.

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

Armazene arquivos de log de treinamento.

Etapa 2 Preparar os arquivos de script e carregá-los para o OBS

Prepare o script de inicialização do MPI run_mpi.sh e o script de treinamento mpi-verification.py e carregue-os na pasta obs://test-modelarts/mpi/demo-code/ do bucket do OBS.

  • O conteúdo do script de inicialização do MPI run_mpi.sh é o seguinte:
    #!/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

    O script run_mpi.sh usa terminações de linha LF. Se as terminações de linha CRLF forem usadas, a execução do trabalho de treinamento falhará e o erro "$'\r': command not found" será exibido nos logs.

  • O conteúdo do script de treinamento mpi-verification.py é o seguinte:
    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'])

Etapa 3 Preparar um servidor de imagens

Obtenha um servidor Linux x86_64 executando o Ubuntu 18.04. Um ECS ou seu PC local servirão.

Para obter detalhes sobre como comprar um ECS, consulte Compra e logon em um ECS Linux. Selecione uma imagem pública. Uma imagem do Ubuntu 18.04 é recomendada.
Figura 1 Criar um ECS usando uma imagem pública (x86)

Etapa 4 Criar uma imagem personalizada

Objetivo: crie e instale imagens de contêiner do software a seguir e use o serviço de treinamento do ModelArts para executar as imagens.

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

A seguir, descrevemos como criar uma imagem personalizada gravando um Dockerfile.

  1. Instale o Docker.

    O seguinte usa o sistema operacional Linux x86_64 como um exemplo para descrever como obter um pacote de instalação do Docker. Para obter detalhes, consulte documentos oficiais do Docker. Execute os seguintes comandos para instalar o Docker:

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

    Se o comando docker images é executado, o Docker foi instalado. Nesse caso, pule essa etapa.

  2. Verifique a versão do mecanismo do Docker. Execute o seguinte comando:
    docker version | grep -A 1 Engine
    As seguintes informações são exibidas:
     Engine:
      Version:          18.09.0

    Recomendamos que você use mecanismo do Docker desta versão ou posterior para criar uma imagem personalizada.

  3. Crie uma pasta chamada context.
    mkdir -p context
  4. Baixe o arquivo de instalação do Miniconda3.

    Baixe o arquivo de instalação do Miniconda3 py37 4.12.0 (Python 3.7.13) de https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh.

  5. Baixe o arquivo de instalação do openmpi 3.0.0.

    Baixe o arquivo editado de openmpi 3.0.0 usando Horovod v0.22.1 de https://github.com/horovod/horovod/files/1596799/openmpi-3.0.0-bin.tar.gz.

  6. Armazene os arquivos de Miniconda3 e openmpi 3.0.0 na pasta context. O seguinte mostra a pasta context:
    context
    ├── Miniconda3-py37_4.12.0-Linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  7. Grave o Dockerfile da imagem do contêiner.
    Crie um arquivo vazio chamado Dockerfile na pasta context e grave o seguinte conteúdo para o arquivo:
    # 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 obter detalhes sobre como gravar um Dockerfile, consulte documentos oficiais do Docker.

  8. Verifique se o Dockerfile foi criado. O seguinte mostra a pasta context:
    context
    ├── Dockerfile
    ├── Miniconda3-py37_4.12.0-Linux-x86_64.sh
    └── openmpi-3.0.0-bin.tar.gz
  9. Crie a imagem do contêiner. Execute o comando a seguir no diretório em que o Dockerfile está armazenado para criar a imagem de contêiner mpi:3.0.0-cuda11.1:
    1
    docker build . -t mpi:3.0.0-cuda11.1
    
    As seguintes informações de log exibidas durante a criação da imagem indicam que a imagem foi criada.
    naming to docker.io/library/mpi:3.0.0-cuda11.1

Etapa 5 Carregar a imagem para o SWR

  1. Faça logon no console do SWR e selecione a região de destino.
    Figura 2 Console do SWR
  2. Clique em Create Organization no canto superior direito e insira um nome de organização para criar uma organização. Personalize o nome da organização. Substitua o nome da organização deep-learning nos comandos subsequentes pelo nome real da organização.
    Figura 3 Criar uma organização
  3. Clique em Generate Login Command no canto superior direito para obter um comando de logon.
    Figura 4 Comando de logon
  4. Efetue logon no ambiente local como o usuário root e digite o comando de logon.
  5. Carregue a imagem para o SWR.
    1. Execute o seguinte comando para marcar a imagem carregada:
      #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. Execute o seguinte comando para carregar a imagem:
      #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. Depois que a imagem for carregada, escolha My Images no painel de navegação esquerdo do console do SWR para exibir as imagens personalizadas enviadas.

    swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1 é o URL da imagem personalizada do SWR.

Etapa 6 Criar um trabalho de treinamento no ModelArts

  1. Faça logon no console de gerenciamento do ModelArts, verifique se a autorização de acesso foi configurada para sua conta. Para obter detalhes, consulte Configuração da autorização da agência. Se você tiver sido autorizado usando chaves de acesso, limpe a autorização e configure a autorização da agência.
  2. Faça logon no console de gerenciamento do ModelArts. No painel de navegação à esquerda, escolha Training Management > Training Jobs (New).
  3. Na página Create Training Job, defina os parâmetros necessários e clique em Submit.
    • Created By: Custom algorithms
    • Boot Mode: Custom images
    • Image path: swr.cn-north-4.myhuaweicloud.com/deep-learning/mpi:3.0.0-cuda11.1
    • Code Directory: caminho do OBS para o script de inicialização, por exemplo, 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: adicione MY_SSHD_PORT = 38888.
    • Resource Pool: Public resource pools
    • Resource Type: selecione GPU.
    • Compute Nodes: digite 1 ou 2.
    • Persistent Log Saving: ativado
    • Job Log Path: defina este parâmetro como o caminho do OBS para armazenar logs de treinamento, por exemplo, obs://test-modelarts/mpi/log/.
  4. Verifique os parâmetros do trabalho de treinamento e clique em Submit.
  5. Aguarde até que o trabalho de treinamento seja concluído.

    Depois que um trabalho de treinamento é criado, as operações como baixa de imagem de contêiner, baixa de diretório de código e execução de comandos de inicialização são executadas automaticamente no back-end. Geralmente, a duração do treinamento varia de dezenas de minutos a várias horas, dependendo do procedimento de treinamento e dos recursos selecionados. Depois que o trabalho de treinamento é executado, o log semelhante ao seguinte é emitido.

    Figura 5 Execute logs do worker-0 com um nó de computação e especificações de GPU

    Defina Compute Nodes como 2 e execute o trabalho de treinamento. Figura 6 e Figura 7 mostram as informações do log.

    Figura 6 Execute logs do worker-0 com dois nós de computação e especificações de GPU
    Figura 7 Execute logs do worker-1 com dois nós de computação e especificações de GPU