Una solución sencilla para gestionar la formación de aprendizaje automático basada en la nube: parte 2
Esta es una secuela de un publicación reciente sobre el tema de la creación de soluciones personalizadas basadas en la nube para el desarrollo de modelos de aprendizaje automático (ML) utilizando servicios de aprovisionamiento de instancias de bajo nivel. Nuestro enfoque en esta publicación estará en AmazonEC2.
Los proveedores de servicios en la nube (CSP) suelen ofrecer soluciones totalmente administradas para entrenar modelos de aprendizaje automático en la nube. Amazon SageMaker, por ejemplo, la oferta de servicios gestionados de Amazon para el desarrollo de ML, simplifica significativamente el proceso de formación. SageMaker no solo automatiza la ejecución de la capacitación de un extremo a otro, desde el aprovisionamiento automático de los tipos de instancias solicitados hasta la configuración del entorno de capacitación, la ejecución de su carga de trabajo de capacitación, el almacenamiento de los artefactos de capacitación y el cierre de todo, sino que también ofrece una serie de servicios auxiliares que respaldan el desarrollo de ML, como ajuste automático del modelo, bibliotecas de capacitación distribuidas optimizadas para plataforma, y más. Sin embargo, como suele ocurrir con las soluciones de alto nivel, la mayor facilidad de uso del entrenamiento de SageMaker va acompañada de un cierto nivel de pérdida de control sobre el flujo subyacente.
En nuestro Publicación anterior Notamos algunas de las limitaciones que a veces imponen los servicios de capacitación administrados como SageMaker, incluidos privilegios de usuario reducidos, inaccesibilidad de algunos tipos de instancias, control reducido sobre la ubicación de dispositivos de múltiples nodos y más. Algunos escenarios requieren un mayor nivel de autonomía sobre la especificación del entorno y el flujo de capacitación. En esta publicación, ilustramos un enfoque para abordar estos casos mediante la creación de una solución de capacitación personalizada construida sobre Amazon EC2.
Muchas gracias a Max Rabin por sus contribuciones a este post.
En nuestra publicación anterior, enumeramos un conjunto mínimo de características que necesitaríamos de una solución de capacitación automatizada y procedimos a demostrar, paso a paso, una forma de implementarlas en Plataforma en la nube de Google (GCP). Y aunque la misma secuencia de pasos se aplicaría a cualquier otra plataforma en la nube, los detalles pueden ser bastante diferentes debido a los matices únicos de cada una. Nuestra intención en este post será proponer una implementación basada en Amazon EC2 utilizando el crear_instancias comando de la SDK de Python de AWS (versión 1.34.23). Como en nuestra publicación anterior, comenzaremos con un comando simple de creación de instancia EC2 y lo complementaremos gradualmente con componentes adicionales que incorporarán las funciones de administración que deseemos. El crear_instancias El comando admite muchos controles. Para los propósitos de nuestra demostración, nos centraremos solo en aquellos que sean relevantes para nuestra solución. Supondremos la existencia de un VPC predeterminada y un Perfil de instancia de IAM con los permisos adecuados (incluido el acceso a los servicios Amazon EC2, S3 y CloudWatch).
Tenga en cuenta que existen varias formas de utilizar Amazon EC2 para cumplir con el conjunto mínimo de características que definimos. Hemos elegido demostrar una posible implementación. No interprete nuestra elección de AWS, EC2 o cualquier detalle de la implementación específica que hayamos elegido como un respaldo. La mejor solución de formación de ML para usted dependerá en gran medida de las necesidades y detalles específicos de su proyecto.
Comenzamos con un ejemplo mínimo de una única solicitud de instancia EC2. Hemos elegido una GPU acelerada g5.xgrande tipo de instancia y una reciente AMI de aprendizaje profundo (con un sistema operativo Ubuntu 20.4).
import boto3region = 'us-east-1'
job_id = 'my-experiment' # replace with unique id
num_instances = 1
image_id = 'ami-0240b7264c1c9e6a9' # replace with image of choice
instance_type = 'g5.xlarge' # replace with instance of choice
ec2 = boto3.resource('ec2', region_name=region)
instances = ec2.create_instances(
MaxCount=num_instances,
MinCount=num_instances,
ImageId=image_id,
InstanceType=instance_type,
)
La primera mejora que nos gustaría aplicar es que nuestra carga de trabajo de capacitación se inicie automáticamente tan pronto como nuestra instancia esté en funcionamiento, sin necesidad de intervención manual. Para lograr este objetivo, utilizaremos la Datos del usuario argumento de la crear_instancias API que le permite especificar qué ejecutar en el lanzamiento. En el bloque de código siguiente, proponemos una secuencia de comandos que configura el entorno de entrenamiento (es decir, actualiza el CAMINO variable de entorno para apuntar al entorno PyTorch prediseñado incluido en nuestra imagen), descarga nuestro código de entrenamiento desde amazon s3, instala las dependencias del proyecto, ejecuta el script de capacitación y sincroniza los artefactos de salida con el almacenamiento persistente de S3. La demostración supone que el código de capacitación ya se creó y se cargó en la nube y que contiene dos archivos: un archivo de requisitos (requisitos.txt) y un guión de formación independiente (tren.py). En la práctica, el contenido preciso de la secuencia de inicio dependerá del proyecto. Incluimos un puntero a nuestro perfil de instancia de IAM predefinido que es necesario para acceder a S3.
import boto3region = 'us-east-1'
job_id = 'my-experiment' # replace with unique id
num_instances = 1
image_id = 'ami-0240b7264c1c9e6a9' # replace with image of choice
instance_type = 'g5.xlarge' # replace with instance of choice
instance_profile_arn = 'instance-profile-arn' # replace with profile arn
ec2 = boto3.resource('ec2', region_name=region)
script = """#!/bin/bash
# environment setup
export PATH=/opt/conda/envs/pytorch/bin/python:$PATH
# download and unpack code
aws s3 cp s3://my-s3-path/my-code.tar .
tar -xvf my-code.tar
# install dependencies
python3 -m pip install -r requirements.txt
# run training workload
python3 train.py
# sync output artifacts
aws s3 sync artifacts s3://my-s3-path/artifacts
"""
instances = ec2.create_instances(
MaxCount=num_instances,
MinCount=num_instances,
ImageId=image_id,
InstanceType=instance_type,
IamInstanceProfile={'Arn':instance_profile_arn},
UserData=script
)
Tenga en cuenta que el script anterior sincroniza los artefactos de entrenamiento solo al final del entrenamiento. Una solución más tolerante a fallas sincronizaría los puntos de control del modelo intermedio durante todo el trabajo de capacitación.
Cuando entrena utilizando un servicio administrado, sus instancias se cierran automáticamente tan pronto como se completa el script para garantizar que solo pague por lo que necesita. En el bloque de código siguiente, agregamos un comando de autodestrucción al final de nuestro Datos del usuario guion. Hacemos esto usando la CLI de AWS terminar-instancias dominio. El comando requiere que conozcamos el ID de instancia y el hospedaje región de nuestra instancia que extraemos del metadatos de instancia. Nuestro script actualizado supone que nuestro perfil de instancia de IAM tiene la autorización de terminación de instancia adecuada.
script = """#!/bin/bash
# environment setup
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H \
"X-aws-ec2-metadata-token-ttl-seconds: 21600")
INST_MD=http://169.254.169.254/latest/meta-data
CURL_FLAGS="-H \"X-aws-ec2-metadata-token: ${TOKEN}\" -s"
INSTANCE_ID=$(curl $CURL_FLAGS $INST_MD/instance-id)
REGION=$(curl $CURL_FLAGS $INST_MD/placement/region)
export PATH=/opt/conda/envs/pytorch/bin/python:$PATH# download and unpack code
aws s3 cp s3://my-s3-path/my-code.tar .
tar -xvf my-code.tar
# install dependencies
python3 -m pip install -r requirements.txt
# run training workload
python3 train.py
# sync output artifacts
aws s3 sync artifacts s3://my-s3-path/artifacts
# self-destruct
aws ec2 terminate-instances --instance-ids $INSTANCE_ID \
--region $REGION
"""
Recomendamos encarecidamente introducir mecanismos adicionales para verificar la eliminación adecuada de instancias para evitar la posibilidad de que instancias no utilizadas (“huérfanas”) en el sistema acumulen costos innecesarios. en un publicación reciente Mostramos cómo se pueden utilizar funciones sin servidor para abordar este tipo de problema.
Amazon EC2 le permite aplicar metadatos personalizados a su instancia usando etiquetas de instancia EC2. Esto le permite pasar información a la instancia sobre la carga de trabajo de capacitación y/o el entorno de capacitación. Aquí utilizamos el EtiquetaEspecificaciones configuración para pasar un nombre de instancia y una identificación de trabajo de capacitación única. Usamos la identificación única para definir una ruta S3 dedicada para nuestros artefactos de trabajo. Tenga en cuenta que debemos habilitar explícitamente la instancia para acceder a las etiquetas de metadatos a través del Opciones de metadatos configuración.
import boto3region = 'us-east-1'
job_id = 'my-experiment' # replace with unique id
num_instances = 1
image_id = 'ami-0240b7264c1c9e6a9' # replace with image of choice
instance_type = 'g5.xlarge' # replace with instance of choice
instance_profile_arn = 'instance-profile-arn' # replace with profile arn
ec2 = boto3.resource('ec2', region_name=region)
script = """#!/bin/bash
# environment setup
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H \
"X-aws-ec2-metadata-token-ttl-seconds: 21600")
INST_MD=http://169.254.169.254/latest/meta-data
CURL_FLAGS="-H \"X-aws-ec2-metadata-token: ${TOKEN}\" -s"
INSTANCE_ID=$(curl $CURL_FLAGS $INST_MD/instance-id)
REGION=$(curl $CURL_FLAGS $INST_MD/placement/region)
JOB_ID=$(curl $CURL_FLAGS $INST_MD/tags/instance/JOB_ID)
export PATH=/opt/conda/envs/pytorch/bin/python:$PATH
# download and unpack code
aws s3 cp s3://my-s3-path/$JOB_ID/my-code.tar .
tar -xvf my-code.tar
# install dependencies
python3 -m pip install -r requirements.txt
# run training workload
python3 train.py
# sync output artifacts
aws s3 sync artifacts s3://my-s3-path/$JOB_ID/artifacts
# self-destruct
aws ec2 terminate-instances --instance-ids $INSTANCE_ID \
--region $REGION
"""
instances = ec2.create_instances(
MaxCount=num_instances,
MinCount=num_instances,
ImageId=image_id,
InstanceType=instance_type,
IamInstanceProfile={'Arn':instance_profile_arn},
UserData=script,
MetadataOptions={"InstanceMetadataTags":"enabled"},
TagSpecifications=[{
'ResourceType': 'instance',
'Tags': [
{'Key': 'NAME', 'Value': 'test-vm'},
{'Key': 'JOB_ID', 'Value': f'{job_id}'}
]
}],
)
El uso de etiquetas de metadatos para pasar información a nuestras instancias será particularmente útil en las siguientes secciones.
Naturalmente, requerimos la capacidad de analizar los registros de salida de nuestra aplicación tanto durante como después del entrenamiento. Esto requiere que se sincronicen periódicamente con el registro persistente. En esta publicación implementamos esto usando Amazon CloudWatch. A continuación definimos un archivo de configuración JSON mínimo para habilitar la recopilación de registros de CloudWatch que agregamos a nuestro código fuente tar-ball como cw_config.json. Consulte la documentación oficial para obtener detalles sobre Instalación y configuración de CloudWatch.
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/output.log",
"log_group_name": "/aws/ec2/training-jobs",
"log_stream_name": "job-id"
}
]
}
}
}
}
En la práctica, nos gustaría que nombre_secuencia_registro para identificar de forma única el trabajo de formación. Para ello utilizamos el sed comando para reemplazar la cadena genérica «job-id» con la etiqueta de metadatos de identificación del trabajo de la sección anterior. Nuestro script mejorado también incluye el comando de inicio de CloudWatch y modificaciones para canalizar la salida estándar al puerto designado. salida.log definido en el archivo de configuración de CloudWatch.
script = """#!/bin/bash
# environment setup
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H \
"X-aws-ec2-metadata-token-ttl-seconds: 21600")
INST_MD=http://169.254.169.254/latest/meta-data
CURL_FLAGS="-H \"X-aws-ec2-metadata-token: ${TOKEN}\" -s"
INSTANCE_ID=$(curl $CURL_FLAGS $INST_MD/instance-id)
REGION=$(curl $CURL_FLAGS $INST_MD/placement/region)
JOB_ID=$(curl $CURL_FLAGS $INST_MD/tags/instance/JOB_ID)
export PATH=/opt/conda/envs/pytorch/bin/python:$PATH# download and unpack code
aws s3 cp s3://my-s3-path/$JOB_ID/my-code.tar .
tar -xvf my-code.tar
# configure cloudwatch
sed -i "s/job-id/${JOB_ID}/g" cw_config.json
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config -m ec2 -c file:cw_config.json -s
# install dependencies
python3 -m pip install -r requirements.txt 2>&1 | tee -a output.log
# run training workload
python3 train.py 2>&1 | tee -a output.log
# sync output artifacts
aws s3 sync artifacts s3://my-s3-path/$JOB_ID/artifacts
# self-destruct
aws ec2 terminate-instances --instance-ids $INSTANCE_ID \
--region $REGION
"""
Hoy en día, es bastante común que los trabajos de formación se ejecuten en varios nodos en paralelo. Modificar nuestro código de solicitud de instancia para admitir múltiples nodos es una simple cuestión de modificar el núm_instancias configuración. El desafío es cómo configurar el entorno de manera que admita la capacitación distribuida, es decir, de una manera que permita (y optimice) la transferencia de datos entre las instancias.
Para minimizar la latencia de la red entre las instancias y maximizar el rendimiento, agregamos un puntero a un valor predefinido. grupo de colocación de clúster en el Colocación campo de nuestra solicitud de instancia ec2. La siguiente línea de comando demuestra la creación de un grupo de ubicación de clúster.
aws ec2 create-placement-group --group-name cluster-placement-group \
--strategy cluster
Para que nuestras instancias se comuniquen entre sí, deben ser conscientes de la presencia de cada una. En esta publicación demostraremos una configuración de entorno mínima requerida para ejecutar entrenamiento paralelo de datos en PyTorch. Para PyTorch Datos distribuidos en paralelo (DDP), cada instancia necesita conocer la IP del nodo maestro, el puerto maestro, el número total de instancias y su número de serie. rango entre todos los nodos. El siguiente script muestra la configuración de un entrenamiento paralelo de datos trabajo usando las variables de entorno MASTER_ADDR, MASTER_PORT, NUM_NODOSy NODE_RANK.
import os, ast, socket
import torch
import torch.distributed as dist
import torch.multiprocessing as mpdef mp_fn(local_rank, *args):
# discover topology settings
num_nodes = int(os.environ.get('NUM_NODES',1))
node_rank = int(os.environ.get('NODE_RANK',0))
gpus_per_node = torch.cuda.device_count()
world_size = num_nodes * gpus_per_node
node_rank = nodes.index(socket.gethostname())
global_rank = (node_rank * gpus_per_node) + local_rank
print(f'local rank {local_rank} '
f'global rank {global_rank} '
f'world size {world_size}')
# init_process_group assumes the existence of MASTER_ADDR
# and MASTER_PORT environment variables
dist.init_process_group(backend='nccl',
rank=global_rank,
world_size=world_size)
torch.cuda.set_device(local_rank)
# Add training logic
if __name__ == '__main__':
mp.spawn(mp_fn,
args=(),
nprocs=torch.cuda.device_count())
El rango del nodo se puede recuperar del índice-de-lanzamiento-ami. El número de nodos y el puerto maestro se conocen en el momento de crear_instancias invocación y se pueden pasar como etiquetas de instancia EC2. Sin embargo, la dirección IP del nodo maestro solo se determina una vez que se crea la instancia maestra y solo se puede comunicar a las instancias siguiendo el crear_instancias llamar. En el bloque de código siguiente, elegimos pasar la dirección maestra a cada una de las instancias mediante una llamada dedicada al SDK de Python de AWS crear_etiquetas API. Usamos la misma llamada para actualizar la etiqueta de nombre de cada instancia de acuerdo con su valor de índice de lanzamiento.
La solución completa para el entrenamiento de múltiples nodos aparece a continuación:
import boto3region = 'us-east-1'
job_id = 'my-multinode-experiment' # replace with unique id
num_instances = 4
image_id = 'ami-0240b7264c1c9e6a9' # replace with image of choice
instance_type = 'g5.xlarge' # replace with instance of choice
instance_profile_arn = 'instance-profile-arn' # replace with profile arn
placement_group = 'cluster-placement-group' # replace with placement group
ec2 = boto3.resource('ec2', region_name=region)
script = """#!/bin/bash
# environment setup
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H \
"X-aws-ec2-metadata-token-ttl-seconds: 21600")
INST_MD=http://169.254.169.254/latest/meta-data
CURL_FLAGS="-H \"X-aws-ec2-metadata-token: ${TOKEN}\" -s"
INSTANCE_ID=$(curl $CURL_FLAGS $INST_MD/instance-id)
REGION=$(curl $CURL_FLAGS $INST_MD/placement/region)
JOB_ID=$(curl $CURL_FLAGS $INST_MD/tags/instance/JOB_ID)
export NODE_RANK=$(curl $CURL_FLAGS $INST_MD/ami-launch-index)
export NUM_NODES=$(curl $CURL_FLAGS $INST_MD/NUM_NODES)
export MASTER_PORT=$(curl $CURL_FLAGS $INST_MD/tags/instance/MASTER_PORT)
export PATH=/opt/conda/envs/pytorch/bin/python:$PATH
# download and unpack code
aws s3 cp s3://my-s3-path/$JOB_ID/my-code.tar .
tar -xvf my-code.tar
# configure cloudwatch
sed -i "s/job-id/${JOB_ID}_${NODE_RANK}/g" cw_config.json
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config -m ec2 -c file:cw_config.json -s
# install dependencies
python3 -m pip install -r requirements.txt 2>&1 | tee -a output.log
# retrieve master address
# should be available but just in case tag application is delayed...
while true; do
export MASTER_ADDR=$(curl $CURL_FLAGS $INST_MD/tags/instance/MASTER_ADDR)
if [[ $MASTER_ADDR == "<?xml"* ]]; then
echo 'tags missing, sleep for 5 seconds' 2>&1 | tee -a output.log
sleep 5
else
break
fi
done
# run training workload
python3 train.py 2>&1 | tee -a output.log
# sync output artifacts
aws s3 sync artifacts s3://my-s3-path/$JOB_ID/artifacts
# self-destruct
aws ec2 terminate-instances --instance-ids $INSTANCE_ID \
--region $REGION
"""
instances = ec2.create_instances(
MaxCount=num_instances,
MinCount=num_instances,
ImageId=image_id,
InstanceType=instance_type,
IamInstanceProfile={'Arn':instance_profile_arn},
UserData=script,
MetadataOptions={"InstanceMetadataTags":"enabled"},
TagSpecifications=[{
'ResourceType': 'instance',
'Tags': [
{'Key': 'NAME', 'Value': 'test-vm'},
{'Key': 'JOB_ID', 'Value': f'{job_id}'},
{'Key': 'MASTER_PORT', 'Value': '7777'},
{'Key': 'NUM_NODES', 'Value': f'{num_instances}'}
]
}],
Placement={'GroupName': placement_group}
)
if num_instances > 1:
# find master_addr
for inst in instances:
if inst.ami_launch_index == 0:
master_addr = inst.network_interfaces_attribute[0]['PrivateIpAddress']
break
# update ec2 tags
for inst in instances:
res = ec2.create_tags(
Resources=[inst.id],
Tags=[
{'Key': 'NAME', 'Value': f'test-vm-{inst.ami_launch_index}'},
{'Key': 'MASTER_ADDR', 'Value': f'{master_addr}'}]
)
Una forma popular de reducir los costos de capacitación es utilizar descuentos Instancias puntuales de Amazon EC2. El uso eficaz de instancias Spot requiere que implemente una forma de detectar interrupciones (por ejemplo, escuchando avisos de terminación) y tomar las medidas adecuadas (por ejemplo, reanudar cargas de trabajo incompletas). A continuación, mostramos cómo modificar nuestro script para usar instancias Spot usando el Opciones de mercado de instancias Configuración de API.
import boto3region = 'us-east-1'
job_id = 'my-spot-experiment' # replace with unique id
num_instances = 1
image_id = 'ami-0240b7264c1c9e6a9' # replace with image of choice
instance_type = 'g5.xlarge' # replace with instance of choice
instance_profile_arn = 'instance-profile-arn' # replace with profile arn
placement_group = 'cluster-placement-group' # replace with placement group
instances = ec2.create_instances(
MaxCount=num_instances,
MinCount=num_instances,
ImageId=image_id,
InstanceType=instance_type,
IamInstanceProfile={'Arn':instance_profile_arn},
UserData=script,
MetadataOptions={"InstanceMetadataTags":"enabled"},
TagSpecifications=[{
'ResourceType': 'instance',
'Tags': [
{'Key': 'NAME', 'Value': 'test-vm'},
{'Key': 'JOB_ID', 'Value': f'{job_id}'},
]
}],
InstanceMarketOptions = {
'MarketType': 'spot',
'SpotOptions': {
"SpotInstanceType": "one-time",
"InstanceInterruptionBehavior": "terminate"
}
}
)
Consulte nuestras publicaciones anteriores (por ejemplo, aquí y aquí) para obtener algunas ideas sobre cómo implementar una solución para la gestión del ciclo de vida de instancias Spot.
Los servicios gestionados en la nube para el desarrollo de la IA pueden simplificar la formación de modelos y reducir el listón de entrada para los potenciales titulares. Sin embargo, existen algunas situaciones en las que se requiere un mayor control sobre el proceso de formación. En esta publicación, hemos ilustrado un enfoque para crear un entorno de capacitación administrado personalizado sobre Amazon EC2. Por supuesto, los detalles precisos de la solución dependerán en gran medida de las necesidades específicas de los proyectos en cuestión.
Como siempre, no dude en responder a esta publicación con comentarios, preguntas o correcciones.