Partilhar via


Guia de treinamento de GPU distribuída (SDK v1)

APLICA-SE A: Python SDK azureml v1

Saiba mais sobre como usar o código de treinamento de GPU distribuída no Azure Machine Learning (ML). Este artigo não ensinará sobre treinamento distribuído. Ele ajudará você a executar seu código de treinamento distribuído existente no Azure Machine Learning. Ele oferece dicas e exemplos para você seguir para cada estrutura:

  • Interface de Passagem de Mensagens (MPI)
    • Horovod
    • DeepSpeed
    • Variáveis de ambiente do Open MPI
  • PyTorch
    • Inicialização do grupo de processos
    • Opções de inicialização
    • DistributedDataParallel (por lançamento de processo)
    • Usando torch.distributed.launch (por inicialização de nó)
    • Relâmpago PyTorch
    • Abraçando Transformadores de Rosto
  • TensorFlow
    • Variáveis de ambiente para TensorFlow (TF_CONFIG)
  • Acelere o treinamento de GPU com a InfiniBand

Pré-requisitos

Analise esses conceitos básicos de treinamento de GPU distribuída, como paralelismo de dados, paralelismo de dados distribuídos e paralelismo de modelo.

Gorjeta

Se você não sabe que tipo de paralelismo usar, mais de 90% do tempo você deve usar o paralelismo de dados distribuídos.

IPM

O Azure Machine Learning oferece um trabalho MPI para iniciar um determinado número de processos em cada nó. Você pode adotar essa abordagem para executar o treinamento distribuído usando o iniciador por processo ou o iniciador por nó, dependendo se process_count_per_node está definido como 1 (o padrão) para o iniciador por nó ou igual ao número de dispositivos/GPUs para o iniciador por processo. O Azure Machine Learning constrói o comando de inicialização MPI completo (mpirun) nos bastidores. Você não pode fornecer seus próprios comandos completos head-node-launcher como mpirun ou DeepSpeed launcher.

Gorjeta

A imagem base do Docker usada por um trabalho MPI do Azure Machine Learning precisa ter uma biblioteca MPI instalada. O Open MPI está incluído em todas as imagens base da GPU do Azure Machine Learning. Ao usar uma imagem personalizada do Docker, você é responsável por garantir que a imagem inclua uma biblioteca MPI. Recomenda-se abrir o MPI, mas você também pode usar uma implementação MPI diferente, como o Intel MPI. O Azure Machine Learning também fornece ambientes com curadoria para estruturas populares.

Para executar o treinamento distribuído usando o MPI, siga estas etapas:

  1. Use um ambiente do Azure Machine Learning com a estrutura de aprendizado profundo e MPI preferida. O Azure Machine Learning fornece um ambiente com curadoria para estruturas populares.
  2. Defina MpiConfiguration com process_count_per_node e node_count. process_count_per_node deve ser igual ao número de GPUs por nó para inicialização por processo ou definido como 1 (o padrão) para inicialização por nó se o script de usuário for responsável por iniciar os processos por nó.
  3. Passe o MpiConfiguration objeto para o distributed_job_config parâmetro de ScriptRunConfig.
from azureml.core import Workspace, ScriptRunConfig, Environment, Experiment
from azureml.core.runconfig import MpiConfiguration

curated_env_name = 'AzureML-PyTorch-1.6-GPU'
pytorch_env = Environment.get(workspace=ws, name=curated_env_name)
distr_config = MpiConfiguration(process_count_per_node=4, node_count=2)

run_config = ScriptRunConfig(
  source_directory= './src',
  script='train.py',
  compute_target=compute_target,
  environment=pytorch_env,
  distributed_job_config=distr_config,
)

# submit the run configuration to start the job
run = Experiment(ws, "experiment_name").submit(run_config)

Horovod

Use a configuração de trabalho MPI ao usar o Horovod para treinamento distribuído com a estrutura de aprendizado profundo.

Certifique-se de que seu código siga estas dicas:

  • O código de treinamento é instrumentado corretamente com o Horovod antes de adicionar as partes do Azure Machine Learning
  • Seu ambiente do Azure Machine Learning contém Horovod e MPI. Os ambientes de GPU com curadoria PyTorch e TensorFlow vêm pré-configurados com o Horovod e suas dependências.
  • Crie um MpiConfiguration com a distribuição desejada.

Exemplo de Horovod

DeepSpeed

Não use o iniciador personalizado do DeepSpeed para executar treinamento distribuído com a biblioteca DeepSpeed no Azure Machine Learning. Em vez disso, configure um trabalho MPI para iniciar o trabalho de treinamento com MPI.

Certifique-se de que seu código siga estas dicas:

  • Seu ambiente do Azure Machine Learning contém DeepSpeed e suas dependências, Open MPI e mpi4py.
  • Crie um MpiConfiguration com a sua distribuição.

Exemplo de DeepSpeed

Variáveis de ambiente do Open MPI

Ao executar trabalhos MPI com imagens Open MPI, as seguintes variáveis de ambiente para cada processo são iniciadas:

  1. OMPI_COMM_WORLD_RANK - a classificação do processo
  2. OMPI_COMM_WORLD_SIZE - o tamanho do mundo
  3. AZ_BATCH_MASTER_NODE - endereço principal com porta, MASTER_ADDR:MASTER_PORT
  4. OMPI_COMM_WORLD_LOCAL_RANK - a classificação local do processo no nó
  5. OMPI_COMM_WORLD_LOCAL_SIZE - número de processos no nó

Gorjeta

Apesar do nome, a NODE_RANKvariável OMPI_COMM_WORLD_NODE_RANK ambiente não corresponde ao . Para usar o iniciador por nó, defina process_count_per_node=1 e use OMPI_COMM_WORLD_RANK como o NODE_RANK.

PyTorch

O Azure Machine Learning dá suporte à execução de trabalhos distribuídos usando os recursos de treinamento distribuído nativos do PyTorch (torch.distributed).

Gorjeta

Para paralelismo de dados, a orientação oficial do PyTorch é usar DistributedDataParallel (DDP) sobre DataParallel para treinamento distribuído de nó único e multinó. PyTorch também recomenda o uso de DistributedDataParallel sobre o pacote de multiprocessamento. Portanto, a documentação e os exemplos do Azure Machine Learning se concentrarão no treinamento DistributedDataParallel.

Inicialização do grupo de processos

A espinha dorsal de qualquer treinamento distribuído é baseada em um grupo de processos que se conhecem e podem se comunicar uns com os outros usando um back-end. Para o PyTorch, o grupo de processos é criado chamando torch.distributed.init_process_group em todos os processos distribuídos para formar coletivamente um grupo de processos.

torch.distributed.init_process_group(backend='nccl', init_method='env://', ...)

Os back-ends de comunicação mais comuns usados são mpi, nccle gloo. Para treinamento nccl baseado em GPU é recomendado para o melhor desempenho e deve ser usado sempre que possível.

init_method Informa como cada processo pode descobrir um ao outro, como eles inicializam e verificam o grupo de processos usando o back-end de comunicação. Por padrão, se init_method não for especificado, o PyTorch usará o método de inicialização da variável de ambiente (env://). init_method é o método de inicialização recomendado a ser usado em seu código de treinamento para executar o PyTorch distribuído no Aprendizado de Máquina do Azure. O PyTorch procurará as seguintes variáveis de ambiente para inicialização:

  • MASTER_ADDR - Endereço IP da máquina que irá hospedar o processo com rank 0.
  • MASTER_PORT - Uma porta livre na máquina que hospedará o processo com classificação 0.
  • WORLD_SIZE - O número total de processos. Deve ser igual ao número total de dispositivos (GPU) usados para treinamento distribuído.
  • RANK - A classificação (global) do processo em curso. Os valores possíveis são 0 a (tamanho do mundo - 1).

Para obter mais informações sobre a inicialização do grupo de processos, consulte a documentação do PyTorch.

Além destes, muitos aplicativos também precisarão das seguintes variáveis de ambiente:

  • LOCAL_RANK - A classificação local (relativa) do processo dentro do nó. Os valores possíveis são 0 a (# de processos no nó - 1). Essas informações são úteis porque muitas operações, como a preparação de dados, devem ser executadas apenas uma vez por nó --- geralmente em local_rank = 0.
  • NODE_RANK - A classificação do nó para treinamento de vários nós. Os valores possíveis são 0 a (total # de nós - 1).

Opções de lançamento do PyTorch

O trabalho do Azure Machine Learning PyTorch dá suporte a dois tipos de opções para iniciar o treinamento distribuído:

  • Per-process-launcher: O sistema iniciará todos os processos distribuídos para você, com todas as informações relevantes (como variáveis de ambiente) para configurar o grupo de processos.
  • Iniciador por nó: você fornece ao Azure Machine Learning o iniciador de utilitário que será executado em cada nó. O iniciador de utilitários tratará de iniciar cada um dos processos em um determinado nó. Localmente dentro de cada nó, RANK e LOCAL_RANK são configurados pelo iniciador. O utilitário torch.distributed.launch e o PyTorch Lightning pertencem a esta categoria.

Não há diferenças fundamentais entre essas opções de lançamento. A escolha depende em grande parte da sua preferência ou das convenções das estruturas/bibliotecas construídas em cima do baunilha PyTorch (como Lightning ou Hugging Face).

As seções a seguir apresentam mais detalhes sobre como configurar trabalhos do Azure Machine Learning PyTorch para cada uma das opções de inicialização.

DistributedDataParallel (por lançamento de processo)

Você não precisa usar um utilitário lançador como torch.distributed.launcho . Para executar um trabalho distribuído do PyTorch:

  1. Especificar o script de treinamento e os argumentos
  2. Crie um PyTorchConfiguration e especifique o process_count e node_count. O process_count corresponde ao número total de processos que você deseja executar para o seu trabalho. process_count deve normalmente ser igual a # GPUs per node x # nodes. Se process_count não for especificado, o Azure Machine Learning iniciará, por padrão, um processo por nó.

O Aprendizado de Máquina do Azure definirá as MASTER_ADDRvariáveis , MASTER_PORT, WORLD_SIZEe NODE_RANK de ambiente em cada nó e definirá as variáveis de nível RANK de processo e LOCAL_RANK de ambiente.

Para usar essa opção para treinamento de vários processos por nó, use o SDK >= 1.22.0Python do Azure Machine Learning . Process_count foi introduzido na versão 1.22.0.

from azureml.core import ScriptRunConfig, Environment, Experiment
from azureml.core.runconfig import PyTorchConfiguration

curated_env_name = 'AzureML-PyTorch-1.6-GPU'
pytorch_env = Environment.get(workspace=ws, name=curated_env_name)
distr_config = PyTorchConfiguration(process_count=8, node_count=2)

run_config = ScriptRunConfig(
  source_directory='./src',
  script='train.py',
  arguments=['--epochs', 50],
  compute_target=compute_target,
  environment=pytorch_env,
  distributed_job_config=distr_config,
)

run = Experiment(ws, 'experiment_name').submit(run_config)

Gorjeta

Se o script de treinamento passar informações como classificação local ou classificação como argumentos de script, você poderá fazer referência à(s) variável(ões) de ambiente nos argumentos:

arguments=['--epochs', 50, '--local_rank', $LOCAL_RANK]

Exemplo de Pytorch por lançamento de processo

Usando torch.distributed.launch (per-node-launch)

O PyTorch fornece um utilitário de inicialização em torch.distributed.launch que você pode usar para iniciar vários processos por nó. O torch.distributed.launch módulo gera vários processos de treinamento em cada um dos nós.

As etapas a seguir demonstram como configurar um trabalho do PyTorch com um iniciador por nó no Aprendizado de Máquina do Azure. O trabalho alcança o equivalente a executar o seguinte comando:

python -m torch.distributed.launch --nproc_per_node <num processes per node> \
  --nnodes <num nodes> --node_rank $NODE_RANK --master_addr $MASTER_ADDR \
  --master_port $MASTER_PORT --use_env \
  <your training script> <your script arguments>
  1. Forneça o torch.distributed.launch comando para o command parâmetro do ScriptRunConfig construtor. O Aprendizado de Máquina do Azure executa esse comando em cada nó do cluster de treinamento. --nproc_per_node deve ser menor ou igual ao número de GPUs disponíveis em cada nó. MASTER_ADDR, MASTER_PORT e NODE_RANK são todos definidos pelo Aprendizado de Máquina do Azure, para que você possa apenas fazer referência às variáveis de ambiente no comando. O Aprendizado de Máquina do Azure define MASTER_PORT para 6105, mas você pode passar um valor diferente para o --master_port argumento do comando torch.distributed.launch, se desejar. (O utilitário de inicialização redefinirá as variáveis de ambiente.)
  2. Crie um PyTorchConfiguration e especifique o node_countarquivo .
from azureml.core import ScriptRunConfig, Environment, Experiment
from azureml.core.runconfig import PyTorchConfiguration

curated_env_name = 'AzureML-PyTorch-1.6-GPU'
pytorch_env = Environment.get(workspace=ws, name=curated_env_name)
distr_config = PyTorchConfiguration(node_count=2)
launch_cmd = "python -m torch.distributed.launch --nproc_per_node 4 --nnodes 2 --node_rank $NODE_RANK --master_addr $MASTER_ADDR --master_port $MASTER_PORT --use_env train.py --epochs 50".split()

run_config = ScriptRunConfig(
  source_directory='./src',
  command=launch_cmd,
  compute_target=compute_target,
  environment=pytorch_env,
  distributed_job_config=distr_config,
)

run = Experiment(ws, 'experiment_name').submit(run_config)

Gorjeta

Treinamento multi-GPU de nó único: Se você estiver usando o utilitário de inicialização para executar o treinamento PyTorch multi-GPU de nó único, não será necessário especificar o distributed_job_config parâmetro de ScriptRunConfig.

launch_cmd = "python -m torch.distributed.launch --nproc_per_node 4 --use_env train.py --epochs 50".split()

run_config = ScriptRunConfig(
 source_directory='./src',
 command=launch_cmd,
 compute_target=compute_target,
 environment=pytorch_env,
)

Exemplo de inicialização por nó do PyTorch

Relâmpago PyTorch

PyTorch Lightning é uma biblioteca de código aberto leve que fornece uma interface de alto nível para o PyTorch. O Lightning abstrai muitas das configurações de treinamento distribuído de nível inferior necessárias para o baunilha PyTorch. O Lightning permite que você execute seus scripts de treinamento em configurações de GPU única, multi-GPU de nó único e multi-GPU de vários nós. Nos bastidores, ele lança vários processos para você semelhantes ao torch.distributed.launch.

Para treinamento de nó único (incluindo multi-GPU de nó único), você pode executar seu código no Aprendizado de Máquina do Azure sem precisar especificar um distributed_job_configarquivo . Para executar um experimento usando vários nós com várias GPUs, há 2 opções:

  • Usando a configuração do PyTorch (recomendado): Defina PyTorchConfiguration e especifique communication_backend="Nccl", node_counte process_count (observe que este é o número total de processos, ou seja, num_nodes * process_count_per_node). No módulo do Lightning Trainer, especifique ambos num_nodes e gpus seja consistente com PyTorchConfigurationo . Por exemplo, num_nodes = node_count e gpus = process_count_per_node.

  • Usando a configuração do MPI:

    • Defina MpiConfiguration e especifique ambos e node_count process_count_per_node. No Lightning Trainer, especifique ambos num_nodes e gpus sejam, respectivamente, iguais a node_count e process_count_per_node a partir de MpiConfiguration.

    • Para treinamento de vários nós com MPI, o Lightning requer que as seguintes variáveis de ambiente sejam definidas em cada nó do cluster de treinamento:

      • MASTER_ADDR
      • MASTER_PORT
      • NODE_RANK
      • LOCAL_RANK

      Defina manualmente estas variáveis de ambiente que o Lightning requer nos scripts de treinamento principais:

    import os
    from argparse import ArgumentParser
    
    def set_environment_variables_for_mpi(num_nodes, gpus_per_node, master_port=54965):
         if num_nodes > 1:
             os.environ["MASTER_ADDR"], os.environ["MASTER_PORT"] = os.environ["AZ_BATCH_MASTER_NODE"].split(":")
         else:
             os.environ["MASTER_ADDR"] = os.environ["AZ_BATCHAI_MPI_MASTER_NODE"]
             os.environ["MASTER_PORT"] = str(master_port)
    
         try:
             os.environ["NODE_RANK"] = str(int(os.environ.get("OMPI_COMM_WORLD_RANK")) // gpus_per_node)
             # additional variables
             os.environ["MASTER_ADDRESS"] = os.environ["MASTER_ADDR"]
             os.environ["LOCAL_RANK"] = os.environ["OMPI_COMM_WORLD_LOCAL_RANK"]
             os.environ["WORLD_SIZE"] = os.environ["OMPI_COMM_WORLD_SIZE"]
         except:
             # fails when used with pytorch configuration instead of mpi
             pass
    
    if __name__ == "__main__":
         parser = ArgumentParser()
         parser.add_argument("--num_nodes", type=int, required=True)
         parser.add_argument("--gpus_per_node", type=int, required=True)
         args = parser.parse_args()
         set_environment_variables_for_mpi(args.num_nodes, args.gpus_per_node)
    
         trainer = Trainer(
          num_nodes=args.num_nodes,
          gpus=args.gpus_per_node
      )
    

    O Lightning lida com a computação do tamanho do mundo a partir das bandeiras --gpus do Trainer e --num_nodesdo .

    from azureml.core import ScriptRunConfig, Experiment
    from azureml.core.runconfig import MpiConfiguration
    
    nnodes = 2
    gpus_per_node = 4
    args = ['--max_epochs', 50, '--gpus_per_node', gpus_per_node, '--accelerator', 'ddp', '--num_nodes', nnodes]
    distr_config = MpiConfiguration(node_count=nnodes, process_count_per_node=gpus_per_node)
    
    run_config = ScriptRunConfig(
       source_directory='./src',
       script='train.py',
       arguments=args,
       compute_target=compute_target,
       environment=pytorch_env,
       distributed_job_config=distr_config,
    )
    
    run = Experiment(ws, 'experiment_name').submit(run_config)
    

Abraçando Transformadores de Rosto

O Hugging Face fornece muitos exemplos para usar sua biblioteca de Transformers para torch.distributed.launch executar treinamento distribuído. Para executar esses exemplos e seus próprios scripts de treinamento personalizados usando a API do Transformers Trainer, siga a seção Usando torch.distributed.launch .

Exemplo de código de configuração de trabalho para ajustar o modelo grande BERT na tarefa MNLI de classificação de texto usando o run_glue.py script em um nó com 8 GPUs:

from azureml.core import ScriptRunConfig
from azureml.core.runconfig import PyTorchConfiguration

distr_config = PyTorchConfiguration() # node_count defaults to 1
launch_cmd = "python -m torch.distributed.launch --nproc_per_node 8 text-classification/run_glue.py --model_name_or_path bert-large-uncased-whole-word-masking --task_name mnli --do_train --do_eval --max_seq_length 128 --per_device_train_batch_size 8 --learning_rate 2e-5 --num_train_epochs 3.0 --output_dir /tmp/mnli_output".split()

run_config = ScriptRunConfig(
  source_directory='./src',
  command=launch_cmd,
  compute_target=compute_target,
  environment=pytorch_env,
  distributed_job_config=distr_config,
)

Você também pode usar a opção por inicialização de processo para executar treinamento distribuído sem usar torch.distributed.launcho . Uma coisa a ter em mente se usar esse método é que os transformadores TrainingArguments esperam que a classificação local seja passada como um argumento (--local_rank). torch.distributed.launch cuida disso quando --use_env=False, mas se você estiver usando a inicialização por processo, precisará passar explicitamente a classificação local como um argumento para o script --local_rank=$LOCAL_RANK de treinamento, pois o Aprendizado de Máquina do Azure define apenas a variável de LOCAL_RANK ambiente.

TensorFlow

Se você estiver usando o TensorFlow distribuído nativo em seu código de treinamento, como a API do tf.distribute.Strategy TensorFlow 2.x, poderá iniciar o trabalho distribuído por meio do Aprendizado de Máquina do Azure usando o TensorflowConfiguration.

Para fazer isso, especifique um TensorflowConfiguration objeto para o distributed_job_config parâmetro do ScriptRunConfig construtor. Se você estiver usando tf.distribute.experimental.MultiWorkerMirroredStrategyo , especifique o worker_count no TensorflowConfiguration correspondente ao número de nós para seu trabalho de treinamento.

from azureml.core import ScriptRunConfig, Environment, Experiment
from azureml.core.runconfig import TensorflowConfiguration

curated_env_name = 'AzureML-TensorFlow-2.3-GPU'
tf_env = Environment.get(workspace=ws, name=curated_env_name)
distr_config = TensorflowConfiguration(worker_count=2, parameter_server_count=0)

run_config = ScriptRunConfig(
  source_directory='./src',
  script='train.py',
  compute_target=compute_target,
  environment=tf_env,
  distributed_job_config=distr_config,
)

# submit the run configuration to start the job
run = Experiment(ws, "experiment_name").submit(run_config)

Se o script de treinamento usar a estratégia de servidor de parâmetros para treinamento distribuído, como para o TensorFlow 1.x herdado, você também precisará especificar o número de servidores de parâmetros a serem usados no trabalho, por exemplo, tf_config = TensorflowConfiguration(worker_count=2, parameter_server_count=1).

TF_CONFIG

No TensorFlow, a variável de ambiente TF_CONFIG é necessária para o treinamento em várias máquinas. Para trabalhos do TensorFlow, o Aprendizado de Máquina do Azure configurará e definirá a variável TF_CONFIG apropriadamente para cada trabalhador antes de executar seu script de treinamento.

Você pode acessar TF_CONFIG a partir do seu script de treinamento se precisar: os.environ['TF_CONFIG'].

Exemplo TF_CONFIG definido em um nó de trabalhador principal:

TF_CONFIG='{
    "cluster": {
        "worker": ["host0:2222", "host1:2222"]
    },
    "task": {"type": "worker", "index": 0},
    "environment": "cloud"
}'

Exemplo do TensorFlow

Acelerando o treinamento distribuído de GPU com o InfiniBand

À medida que o número de VMs treinando um modelo aumenta, o tempo necessário para treinar esse modelo deve diminuir. A diminuição do tempo, idealmente, deve ser linearmente proporcional ao número de VMs de treinamento. Por exemplo, se o treinamento de um modelo em uma VM leva 100 segundos, o treinamento do mesmo modelo em duas VMs deve, idealmente, levar 50 segundos. O treinamento do modelo em quatro VMs deve levar 25 segundos, e assim por diante.

InfiniBand pode ser um fator importante na obtenção deste escalonamento linear. A InfiniBand permite a comunicação GPU-a-GPU de baixa latência entre nós em um cluster. InfiniBand requer hardware especializado para operar. Algumas séries de VMs do Azure, especificamente as séries NC, ND e H, agora têm VMs compatíveis com RDMA com suporte a SR-IOV e InfiniBand. Essas VMs se comunicam pela rede InfiniBand de baixa latência e alta largura de banda, que é muito mais eficiente do que a conectividade baseada em Ethernet. O SR-IOV para InfiniBand permite um desempenho quase bare-metal para qualquer biblioteca MPI (o MPI é usado por muitas estruturas de treinamento distribuídas e ferramentas, incluindo o software NCCL da NVIDIA.) Esses SKUs destinam-se a atender às necessidades de cargas de trabalho de aprendizado de máquina aceleradas por GPU com uso intensivo de computação. Para obter mais informações, consulte Acelerando o treinamento distribuído no Azure Machine Learning com SR-IOV.

Normalmente, as SKUs de VM com um 'r' em seu nome contêm o hardware InfiniBand necessário, e aquelas sem um 'r' normalmente não. ('r' é uma referência a RDMA, que significa "acesso remoto direto à memória.") Por exemplo, o SKU Standard_NC24rs_v3 da VM é habilitado para InfiniBand, mas o SKU Standard_NC24s_v3 não. Além dos recursos da InfiniBand, as especificações entre esses dois SKUs são basicamente as mesmas – ambos têm 24 núcleos, 448 GB de RAM, 4 GPUs do mesmo SKU, etc. Saiba mais sobre SKUs de máquinas habilitadas para RDMA e InfiniBand.

Aviso

O SKU Standard_NC24r da máquina de geração mais antiga é habilitado para RDMA, mas não contém hardware SR-IOV necessário para InfiniBand.

Se você criar um AmlCompute cluster de um desses tamanhos habilitados para RDMA e habilitados para InfiniBand, a imagem do sistema operacional virá com o driver Mellanox OFED necessário para habilitar o InfiniBand pré-instalado e pré-configurado.

Próximos passos