Compartilhar via


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

APLICA-SE A:SDK do Python do Azure MLv1

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

  • Interface de Passagem de Mensagem (MPI)
    • Horovod
    • DeepSpeed
    • Variáveis de ambiente do Open MPI
  • PyTorch
    • Inicialização do grupo de processos
    • Opções de inicialização
    • DistributedDataParallel (inicialização por processo)
    • Uso de torch.distributed.launch (inicialização por nó)
    • PyTorch Lightning
    • Hugging Face Transformers
  • TensorFlow
    • Variáveis de ambiente para TensorFlow (TF_CONFIG)
  • Acelere o treinamento de GPU com o InfiniBand

Pré-requisitos

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

Dica

Se você não souber qual tipo de paralelismo usar, em 90% das vezes, use Paralelismo de dados distribuídos.

MPI

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

Dica

A imagem base do Docker usada por um trabalho de MPI de Machine Learning precisa da instalação de uma biblioteca de MPI. O Open MPI está incluído em todas as imagens base de GPU do Azure Machine Learning. Ao usar uma imagem personalizada do Docker, você é responsável por verificar se a imagem inclui uma biblioteca de MPI. Recomendamos o Open MPI, mas você também pode usar outra implementação de MPI, como o Intel MPI. O Azure Machine Learning também oferece ambientes coletados para estruturas populares.

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

  1. Use um ambiente do Azure Machine Learning com a estrutura de aprendizado profundo e a MPI de sua preferência. O Azure Machine Learning fornece um ambiente coletado 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 a inicialização por processo ou definido como 1 (o padrão) para inicialização por nó se o script do usuário for responsável por iniciar os processos por nó.
  3. Passe o objeto MpiConfiguration para o parâmetro distributed_job_config 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 de MPI ao usar o Horovod para treinamento distribuído com a estrutura de aprendizado profundo.

Verifique se seu código segue estas dicas:

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

Exemplo do Horovod

DeepSpeed

Não use o inicializador personalizado do DeepSpeed para executar o 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.

Verifique se seu código segue estas dicas:

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

Exemplo do DeepSpeed

Variáveis de ambiente do Open MPI

Ao executar trabalhos de MPI com imagens do Open MPI, as seguintes variáveis de ambiente para cada processo iniciado:

  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 a 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ó

Dica

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

PyTorch

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

Dica

Para o paralelismo de dados, a orientação oficial do PyTorch é usar DistributedDataParallel (DDP) em DataParallel para treinamento distribuído de nó único e de vários nós. O PyTorch também recomenda o uso do DistributedDataParallel no pacote multiprocessamento. A documentação e os exemplos do Azure Machine Learning focarão assim no treinamento de DistributedDataParallel.

Inicialização do grupo de processos

O backbone de um treinamento distribuído é baseado em um grupo de processos que se conhecem e podem se comunicar entre si por um back-end. No PyTorch, o grupo de processos é criado ao chamar torch.distributed.init_process_group em todos os processos distribuídos para formar coletivamente um grupo do processo.

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

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

init_method informa como cada processo pode descobrir um ao outro, como eles inicializam e verificar 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 de variável de ambiente (env://). init_method é o método de inicialização recomendado a ser usado no código de treinamento para executar o PyTorch distribuído no Azure Machine Learning. O PyTorch procurará as variáveis de ambiente a seguir para inicialização:

  • MASTER_ADDR - Endereço IP do computador que hospeda o processo com a classificação 0.
  • MASTER_PORT - uma porta livre no computador que hospeda o processo com a classificação 0.
  • WORLD_SIZE - o número total de processos. Deve ser igual ao número total de dispositivos (GPU) usados para o treinamento distribuído.
  • RANK - a classificação (global) do processo atual. Os valores possíveis são 0 a (tamanho mundial - 1).

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

Além deles, 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 (n º de processos no nó - 1). Essas informações são úteis porque muitas operações, como a preparação de dados, só devem ser executadas uma vez por nó --- geralmente em local_rank = 0.
  • NODE_RANK - a classificação do nó para treinamento em vários nós. Os valores possíveis são 0 a (n º total de nós - 1).

Opções de inicialização do PyTorch

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

  • Inicializador por processo: o sistema inicia todos os processos distribuídos para você, com todas as informações relevantes (como variáveis de ambiente) para configurar o grupo de processos.
  • Inicializador por nó: você fornece ao Azure Machine Learning o inicializador do utilitário que será executado em cada nó. O inicializador do utilitário manipulará a inicialização de cada um dos processos em um determinado nó. Dentro de cada nó, localmente, RANK e LOCAL_RANK são configurados pelo inicializador. O utilitário torch.distributed.launch e o PyTorch Lightning pertencem a essa categoria.

Não há diferenças fundamentais entre essas opções de inicialização. A escolha depende muito da sua preferência ou das convenções das estruturas/bibliotecas criadas com base no PyTorch padrão (como o Lightning ou o Hugging Face).

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

DistributedDataParallel (inicialização por processo)

Você não precisa usar um utilitário inicializador como o torch.distributed.launch. Para executar um trabalho distribuído do PyTorch:

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

O Azure Machine Learning definirá as variáveis de ambiente MASTER_ADDR, MASTER_PORT, WORLD_SIZE e NODE_RANK em cada nó e as variáveis de ambiente RANK e LOCAL_RANK no nível do processo.

Para usar essa opção para o treinamento de vários processos por nó, use o SDK >= 1.22.0 do Python para o Azure Machine Learning. Process_count foi introduzido no 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)

Dica

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

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

Exemplo de Pytorch por processo de inicialização

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 inicializar vários processos por nó. O módulo torch.distributed.launch 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 inicializador por nó no Azure Machine Learning. 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 comando torch.distributed.launch para o parâmetro command do construtor ScriptRunConfig. O Azure Machine Learning executa esse comando em cada nó do cluster de treinamento. O --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 definidos pelo Azure Machine Learning, para que você possa referenciar apenas as variáveis de ambiente no comando. O Azure Machine Learning define MASTER_PORT como 6105, mas você pode transmitir um valor diferente para o argumento --master_port do comando torch.distributed.launch desejado. (O utilitário de inicialização redefinirá as variáveis de ambiente.)
  2. Crie um PyTorchConfiguration e especifique o node_count.
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)

Dica

Treinamento de várias GPU de nó único: se você estiver usando o utilitário de inicialização para executar o treinamento de PyTorch de várias GPUs de nó único, não será necessário especificar o parâmetro distributed_job_config 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

PyTorch Lightning

O PyTorch Lightning é uma biblioteca leve de código aberto que oferece uma interface de alto nível para o PyTorch. O Lightning abstrai muitas das configurações de treinamento distribuídas de nível inferior necessárias para o PyTorch Vanilla. O Lightning permite executar scripts de treinamento em configurações de GPU única, várias GPUs de nó único e vários nós. Nos bastidores, ele inicia vários processos para você de forma semelhante ao torch.distributed.launch.

Para o treinamento de nó único (incluindo várias GPUs de nó único), você pode executar seu código no Azure Machine Learning sem a necessidade de especificar um distributed_job_config. Para executar um experimento usando vários nós com várias GPUs, há duas opções:

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

  • Usando a configuração de MPI:

    • Defina MpiConfiguration e especifique node_count e process_count_per_node. No Lightning Trainer, especifique num_nodes e gpus para que sejam respectivamente o mesmo que node_count e process_count_per_node 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 essas variáveis de ambiente necessárias que o Lightning exige 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 lidará com a computação de tamanho mundial dos sinalizadores do Treinador --gpus e --num_nodes.

    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)
    

Hugging Face Transformers

O Hugging Face oferece muitos exemplos de uso da biblioteca Transformers com torch.distributed.launch para executar treinamento distribuído. Para executar esses exemplos e os próprios scripts de treinamento personalizados usando a API Transformers Trainer, siga a seção Usar torch.distributed.launch.

Amostra de código de configuração de trabalho para ajuste fino do modelo grande BERT na tarefa MNLI de classificação de texto usando o script run_glue.py 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 inicialização por processo para executar o treinamento distribuído sem usar o torch.distributed.launch. Ao usar esse método, lembre-se de que os transformadores TrainingArguments esperam que a classificação local seja passada como um argumento (--local_rank). O torch.distributed.launch cuida disso quando --use_env=False, mas se você estiver usando a inicialização por processo, precisará transmitir explicitamente a classificação local como um argumento para o --local_rank=$LOCAL_RANK do script de treinamento, pois o Azure Machine Learning só define a variável de ambiente LOCAL_RANK.

TensorFlow

Se estiver usando o TensorFlow distribuído nativo no código de treinamento, por exemplo, a API tf.distribute.Strategy do TensorFlow 2.x, você também poderá inicializar o trabalho distribuído por meio do Azure Machine Learning usando TensorflowConfiguration.

Para isso, especifique um objeto TensorflowConfiguration para o parâmetro distributed_job_config do construtor ScriptRunConfig. Se você estiver usando tf.distribute.experimental.MultiWorkerMirroredStrategy, 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 seu script de treinamento usar a estratégia de servidor de parâmetros para treinamento distribuído, como para TensorFlow 1.x herdado, também será necessário especificar o número de servidores de parâmetro 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 treinamento em vários computadores. Nos trabalhos do TensorFlow, o Azure Machine Learning vai configurar e definir a variável TF_CONFIG de maneira adequada para cada trabalho antes de executar o script de treinamento.

Se precisar, você pode acessar TF_CONFIG a partir de seu script de treinamento: os.environ['TF_CONFIG'].

Exemplo de conjunto TF_CONFIG em um nó de trabalho principal:

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

Exemplo do TensorFlow

Acelerar o treinamento de GPU com o InfiniBand

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

A InfiniBand pode ser um fator importante na obtenção desse dimensionamento linear. A InfiniBand permite comunicação de GPU para GPU de baixa latência entre os nós em um cluster. O InfiniBand requer um 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 e 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 eficaz do que a conectividade baseada em Ethernet. O SR-IOV para InfiniBand permite o desempenho quase bare-metal de uma biblioteca de MPI (a MPI é usada por muitas estruturas e ferramentas de treinamento distribuídas, inclusive o software NCCL da NVIDIA). Essas SKUs são planejadas para atender às necessidades de cargas de trabalho de Machine Learning aceleradas por GPU que consomem muitos recursos de computação. Para obter mais informações, confira Acelerar o treinamento distribuído no Azure Machine Learning com o SR-IOV.

Normalmente, SKUs de VM com um "r" em seu nome contêm o hardware de InfiniBand necessário, e aqueles sem um "r" normalmente não. ('r' é uma referência a RDMA, que significa "acesso remoto direto à memória"). Por exemplo, a SKU Standard_NC24rs_v3 da VM é habilitada para InfiniBand, mas a SKU Standard_NC24s_v3 não é. Além dos recursos de InfiniBand, as especificações entre essas duas SKUs são basicamente as mesmas – ambas têm 24 núcleos, 448 GB de RAM, 4 GPUs da mesma SKU, etc. Saiba mais sobre as SKUs de máquina 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 o hardware SR-IOV necessário para a InfiniBand.

Se você criar um cluster AmlCompute de um desses tamanhos compatíveis com 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óximas etapas