
Questo è il seguito di a post recente sul tema della creazione di soluzioni personalizzate basate sul cloud per lo sviluppo di modelli di machine learning (ML) utilizzando servizi di provisioning di istanze di basso livello. Il nostro focus in questo post sarà su AmazonEC2.
I fornitori di servizi cloud (CSP) offrono in genere soluzioni completamente gestite per l’addestramento di modelli ML nel cloud. Amazon SageMakerad esempio, l’offerta di servizi gestiti di Amazon per lo sviluppo ML semplifica notevolmente il processo di formazione. SageMaker non solo automatizza l’esecuzione della formazione end-to-end, dal provisioning automatico dei tipi di istanza richiesti, alla configurazione dell’ambiente di formazione, all’esecuzione del carico di lavoro di formazione, al salvataggio degli artefatti della formazione e alla chiusura di tutto, ma offre anche una serie di servizi ausiliari che supportano lo sviluppo ML, come ad esempio ottimizzazione automatica del modello, librerie di formazione distribuite ottimizzate per la piattaformae altro ancora. Tuttavia, come spesso accade con le soluzioni di alto livello, la maggiore facilità d’uso della formazione SageMaker è abbinata a un certo livello di perdita di controllo sul flusso sottostante.
Nel nostro messaggio precedente abbiamo notato alcune delle limitazioni talvolta imposte dai servizi di formazione gestiti come SageMaker, inclusi privilegi utente ridotti, inaccessibilità di alcuni tipi di istanze, controllo ridotto sul posizionamento dei dispositivi multi-nodo e altro ancora. Alcuni scenari richiedono un livello più elevato di autonomia rispetto alle specifiche dell’ambiente e al flusso di formazione. In questo post, illustriamo un approccio per affrontare questi casi creando una soluzione di formazione personalizzata basata su Amazon EC2.
Molte grazie a Max Rabin per i suoi contributi a questo post.
Nel nostro post precedente abbiamo elencato un insieme minimo di funzionalità che avremmo richiesto da una soluzione di formazione automatizzata e abbiamo proceduto a dimostrare, passo dopo passo, un modo per implementarle in Google Cloud Platform (GCP). E sebbene la stessa sequenza di passaggi si applichi a qualsiasi altra piattaforma cloud, i dettagli possono essere molto diversi a causa delle sfumature uniche di ciascuna di esse. La nostra intenzione in questo post sarà quella di proporre un’implementazione basata su Amazon EC2 utilizzando il crea_istanze comando del Kit SDK AWS Python (versione 1.34.23). Come nel nostro post precedente, inizieremo con un semplice comando per la creazione di un’istanza EC2 e lo integreremo gradualmente con componenti aggiuntivi che incorporeranno le funzionalità di gestione desiderate. IL crea_istanze Il comando supporta molti controlli. Ai fini della nostra dimostrazione, ci concentreremo solo su quelli rilevanti per la nostra soluzione. Assumeremo l’esistenza di a VPC predefinito e un Profilo dell’istanza IAM con le autorizzazioni appropriate (incluso l’accesso ai servizi Amazon EC2, S3 e CloudWatch).
Tieni presente che esistono diversi modi per utilizzare Amazon EC2 per soddisfare il set minimo di funzionalità che abbiamo definito. Abbiamo scelto di dimostrare una possibile implementazione. Ti preghiamo di non interpretare la nostra scelta di AWS, EC2 o qualsiasi dettaglio dell’implementazione specifica che abbiamo scelto come un’approvazione. La migliore soluzione di formazione ML per te dipenderà in gran parte dalle esigenze specifiche e dai dettagli del tuo progetto.
Iniziamo con un esempio minimo di una singola richiesta di istanza EC2. Abbiamo scelto una GPU accelerata g5.xlarge tipo di istanza e un tipo recente AMI di apprendimento profondo (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,
)
Il primo miglioramento che vorremmo applicare è che il nostro carico di lavoro di formazione venga avviato automaticamente non appena la nostra istanza è operativa, senza alcuna necessità di intervento manuale. Verso questo obiettivo, utilizzeremo il Dati utente argomento del crea_istanze API che ti consente di farlo specificare cosa eseguire all’avvio. Nel blocco di codice sottostante proponiamo una sequenza di comandi che imposta l’ambiente di addestramento (ovvero aggiorna il file SENTIERO variabile di ambiente per puntare all’ambiente PyTorch predefinito incluso nella nostra immagine), scarica il nostro codice di formazione da Amazon S3installa le dipendenze del progetto, esegue lo script di training e sincronizza gli artefatti di output sullo storage S3 persistente. La dimostrazione presuppone che il codice di training sia già stato creato e caricato nel cloud e che contenga due file: un file dei requisiti (requisiti.txt) e uno script di formazione autonomo (train.py). In pratica, il contenuto preciso della sequenza di avvio dipenderà dal progetto. Includiamo un puntatore al nostro profilo di istanza IAM predefinito necessario per accedere 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
)
Tieni presente che lo script precedente sincronizza gli artefatti dell’addestramento solo al termine dell’addestramento. Una soluzione più tollerante agli errori sincronizzerebbe i checkpoint del modello intermedio durante il processo di formazione.
Quando ti alleni utilizzando un servizio gestito, le tue istanze vengono arrestate automaticamente non appena lo script viene completato per assicurarti di pagare solo ciò di cui hai bisogno. Nel blocco di codice seguente, aggiungiamo un comando di autodistruzione alla fine del file our Dati utente sceneggiatura. Lo facciamo utilizzando l’AWS CLI istanze di terminazione comando. Il comando richiede che conosciamo il file ID-istanza e l’hosting regione della nostra istanza che estraiamo dal metadati dell’istanza. Il nostro script aggiornato presuppone che il nostro profilo dell’istanza IAM disponga dell’autorizzazione appropriata per la terminazione dell’istanza.
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
"""
Consigliamo vivamente di introdurre meccanismi aggiuntivi per verificare l’eliminazione appropriata delle istanze per evitare la possibilità di avere istanze inutilizzate (“orfane”) nel sistema che accumulino costi inutili. In un post recente abbiamo mostrato come utilizzare le funzioni serverless per risolvere questo tipo di problema.
Amazon EC2 ti consente di applicare metadati personalizzati alla tua istanza utilizzando i tag dell’istanza EC2. Ciò consente di trasmettere informazioni all’istanza relative al carico di lavoro di formazione e/o all’ambiente di formazione. Qui usiamo il Specifiche dei tag impostazione per passare un nome di istanza e un ID processo di formazione univoco. Utilizziamo l’ID univoco per definire un percorso S3 dedicato per i nostri artefatti di lavoro. Tieni presente che dobbiamo abilitare esplicitamente l’istanza per accedere ai tag dei metadati tramite il file Opzioni metadati collocamento.
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}'}
)
}),
)
L’utilizzo dei tag dei metadati per passare informazioni alle nostre istanze sarà particolarmente utile nelle prossime sezioni.
Naturalmente, abbiamo bisogno della capacità di analizzare i log di output della nostra applicazione sia durante che dopo l’addestramento. Ciò richiede che vengano periodicamente sincronizzati con la registrazione persistente. In questo post lo implementiamo utilizzando Amazon CloudWatch. Di seguito definiamo un file di configurazione JSON minimo per abilitare la raccolta di log di CloudWatch che aggiungiamo al nostro codice sorgente come tar-ball cw_config.json. Per i dettagli consultare la documentazione ufficiale Impostazione e configurazione di CloudWatch.
{
"logs": {
"logs_collected": {
"files": {
"collect_list": (
{
"file_path": "/output.log",
"log_group_name": "/aws/ec2/training-jobs",
"log_stream_name": "job-id"
}
)
}
}
}
}
In pratica, vorremmo che il nome_stream_log per identificare in modo univoco la mansione formativa. A tal fine, utilizziamo il file sed comando per sostituire la stringa generica “job-id” con il tag dei metadati job id della sezione precedente. Il nostro script migliorato include anche il comando di avvio di CloudWatch e modifiche per trasmettere l’output standard agli elementi designati output.log definito nel file di configurazione di 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
"""
Al giorno d’oggi, è abbastanza comune che i lavori di formazione vengano eseguiti su più nodi in parallelo. Modificare il nostro codice di richiesta dell’istanza per supportare più nodi è semplicemente questione di modificare il file num_istanze collocamento. La sfida è come configurare l’ambiente in modo da supportare la formazione distribuita, ovvero in modo da consentire (e ottimizzare) il trasferimento di dati tra le istanze.
Per ridurre al minimo la latenza di rete tra le istanze e massimizzare il throughput, aggiungiamo un puntatore a un default gruppo di posizionamento cluster nel Posizionamento campo della nostra richiesta di istanza ec2. La seguente riga di comando dimostra la creazione di un gruppo di posizionamento del cluster.
aws ec2 create-placement-group --group-name cluster-placement-group \
--strategy cluster
Affinché le nostre istanze possano comunicare tra loro, devono essere consapevoli della presenza l’una dell’altra. In questo post dimostreremo una configurazione dell’ambiente minima richiesta per l’esecuzione formazione parallela dei dati In PyTorch. Per PyTorch DistributedDataParallel (DDP), ciascuna istanza deve conoscere l’IP del nodo master, la porta master, il numero totale di istanze e la sua seriale rango tra tutti i nodi. Lo script seguente mostra la configurazione di a formazione parallela dei dati lavoro utilizzando le variabili di ambiente MASTER_ADDR, PORTA_MASTER, NUM_NODIE RANGO_NODO.
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())
Il rango del nodo può essere recuperato da ami-launch-index. Il numero di nodi e la porta master sono noti al momento crea_istanze chiamata e possono essere passati come tag di istanza EC2. Tuttavia, l’indirizzo IP del nodo master viene determinato solo una volta creata l’istanza master e può essere comunicato solo alle istanze successive al crea_istanze chiamata. Nel blocco di codice seguente, abbiamo scelto di passare l’indirizzo master a ciascuna delle istanze utilizzando una chiamata dedicata a Kit SDK AWS Python crea_tag API. Usiamo la stessa chiamata per aggiornare il tag name di ciascuna istanza in base al suo valore launch-index.
Di seguito la soluzione completa per l’addestramento multinodo:
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}'})
)
Un modo popolare per ridurre i costi di formazione è utilizzare gli sconti Istanze Spot di Amazon EC2. L’utilizzo efficace delle istanze Spot richiede l’implementazione di un modo per rilevare le interruzioni (ad esempio, ascoltando avvisi di cessazione) e adottando le misure appropriate (ad esempio, riprendendo i carichi di lavoro incompleti). Di seguito, mostriamo come modificare il nostro script per utilizzare le istanze Spot utilizzando il file OpzioniInstanceMarket Impostazione dell’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"
}
}
)
Si prega di consultare i nostri post precedenti (ad esempio, Qui E Qui) per alcune idee su come implementare una soluzione per la gestione del ciclo di vita delle istanze Spot.
I servizi cloud gestiti per lo sviluppo dell’intelligenza artificiale possono semplificare la formazione dei modelli e abbassare la soglia di accesso per i potenziali operatori storici. Tuttavia, ci sono alcune situazioni in cui è necessario un maggiore controllo sul processo di formazione. In questo post abbiamo illustrato un approccio per creare un ambiente di formazione gestito personalizzato su Amazon EC2. Naturalmente, i dettagli precisi della soluzione dipenderanno in gran parte dalle esigenze specifiche dei progetti in questione.
Come sempre, non esitate a rispondere a questo post con commenti, domande o correzioni.
Fonte: towardsdatascience.com