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:
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 unas carpetas en OBS para almacenar la muestra de conjunto de datos y el código de entrenamiento. Tabla 1 enumera las carpetas que se van a crear. En el ejemplo, el nombre del bucket y los nombres de las carpetas junto con los nombres reales.
Para obtener más información sobre cómo crear un bucket de OBS y una carpeta, véase 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.
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 de Linux x86_64 que ejecute Ubuntu 18.04. Un ECS o su PC local servirán.
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.
- 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, se ha instalado Docker. Si es así, omita este paso.
- 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.
- Cree una carpeta denominada context.
mkdir -p context
- 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.
- 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.
- 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
- 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.
- 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
- 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
- Inicie sesión en la consola de SWR y seleccione la región de destino.
Figura 2 Consola de SWR
- 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
- Haga clic en Generate Login Command en la esquina superior derecha para obtener un comando de acceso.
Figura 4 Comando de acceso
- Inicie sesión en el entorno local como usuario root e ingrese el comando de inicio de sesión.
- Cargue la imagen en SWR.
- 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
- 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
- Ejecute el siguiente comando para etiquetar la imagen cargada:
- 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
- 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 información, véase Configuración de la autorización de la delegación. Si ha sido autorizado mediante claves de acceso, borre la autorización y configure la autorización de la delegación.
- Inicie sesión en la consola de gestión de ModelArts. En el panel de navegación izquierdo, seleccione Training Management > Training Jobs (New).
- 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: escriba 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/.
- Verifique los parámetros del trabajo de entrenamiento y haga clic en Submit.
- 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 en 2 y ejecute el trabajo de entrenamiento. Figura 6 y Figura 7 muestran la información del log.