Use tarefas de várias instâncias para executar aplicativos Message Passing Interface (MPI) no Batch

As tarefas de várias instâncias permitem que você execute uma tarefa do Lote do Azure em vários nós de computação simultaneamente. Essas tarefas permitem cenários de computação de alto desempenho, como aplicações MPI (Interface de Passagem de Mensagens) no Batch. Neste artigo, você aprenderá como executar tarefas de múltiplas instâncias usando a biblioteca Azure.Compute.Batch.

Observação

Embora os exemplos neste artigo se concentrem em Azure. Compute.Batch, MS-MPI e nós de computação Windows, os conceitos de tarefa de várias instâncias discutidos aqui são aplicáveis a outras plataformas e tecnologias (Python e Intel MPI em nós linux, por exemplo).

Visão geral da tarefa de várias instâncias

No Batch, cada tarefa normalmente é executada em um único nó de computação -- você envia várias tarefas para um job, e o serviço Batch agenda cada tarefa para execução em um nó. No entanto, ao configurar as definições de várias instâncias de uma tarefa, você instrui o Batch a criar uma tarefa primária e várias subtarefas que são então executadas em vários nós.

Diagrama mostrando uma visão geral das configurações de várias instâncias.

Quando você envia uma tarefa com configurações de múltiplas instâncias para um trabalho, o Batch executa várias etapas específicas de tarefas de múltiplas instâncias:

  1. O serviço Batch cria uma tarefa principal e várias subtarefas com base nas configurações de múltiplas instâncias. O número total de tarefas (as principais e todas as subtarefas) corresponde ao número de instâncias (nós de computação) que você especificar nas configurações de várias instâncias.
  2. O Batch designa um dos nós de computação como o master e agenda a execução da tarefa principal no master. Ele programa as subtarefas para serem executadas nos nós de computação restantes alocados à tarefa com várias instâncias, uma subtarefa por nó.
  3. As tarefas principais e todas as subtarefas baixam os arquivos de recurso comum que você especificar nas configurações de várias instâncias.
  4. Depois que os arquivos de recursos comuns tiverem sido baixados, a tarefa principal e as subtarefas executarão o comando de coordenação especificado nas configurações de várias instâncias. O comando de coordenação normalmente é usado para preparar nós para executar a tarefa. Isso pode incluir iniciar serviços em segundo plano (como os do Microsoft MPIsmpd.exe) e verificar se os nós estão prontos para processar mensagens entre nós.
  5. A tarefa principal executará o comando de aplicativo no nó mestre após o comando de coordenação ter sido concluído com êxito pela tarefa principal e por todas as subtarefas. O comando do aplicativo é a linha de comando da tarefa de várias instâncias em si, que é executada somente pela tarefa principal. Em uma solução baseada em MS-MPI, é onde você executa seu aplicativo habilitado para MPI usando o mpiexec.exe.

Observação

Embora seja funcionalmente distinto, a "tarefa de várias instâncias" não é um tipo de tarefa exclusivo, como BatchStartTask ou BatchJobPreparationTask. A tarefa com várias instâncias é simplesmente uma tarefa Batch padrão (BatchTask em Azure.Compute.Batch) cujas configurações de várias instâncias foram definidas. Neste artigo, nos referimos a isso como a tarefa de várias instâncias.

Requisitos para tarefas de várias instâncias

As tarefas de várias instâncias exigem um pool com comunicação entre nós habilitada e com a execução de tarefas simultâneas desabilitada. Para desabilitar a execução de tarefa simultânea, defina a propriedade BatchAccountPoolData.TaskSlotsPerNode como 1.

Observação

O lote limita o tamanho de um pool que tenha comunicação entre nós habilitada.

Este trecho de código mostra como criar um pool para tarefas com várias instâncias usando a biblioteca Azure.ResourceManager.Batch.

ArmClient armClient = new ArmClient(new DefaultAzureCredential());

ResourceIdentifier batchAccountResourceId =
    BatchAccountResource.CreateResourceIdentifier("subscriptionId", "resourceGroupName", "accountName");
BatchAccountResource batchAccount = armClient.GetBatchAccountResource(batchAccountResourceId);

BatchAccountPoolCollection poolCollection = batchAccount.GetBatchAccountPools();

BatchAccountPoolData poolData = new BatchAccountPoolData()
{
    VmSize = "standard_d1_v2",
    DeploymentConfiguration = new BatchDeploymentConfiguration()
    {
        VmConfiguration = new BatchVmConfiguration(
            imageReference: new BatchImageReference()
            {
                Publisher = "MicrosoftWindowsServer",
                Offer = "WindowsServer",
                Sku = "2019-datacenter-core",
                Version = "latest"
            },
            nodeAgentSkuId: "batch.node.windows amd64")
    },
    ScaleSettings = new BatchAccountPoolScaleSettings()
    {
        FixedScale = new BatchAccountFixedScaleSettings() { TargetDedicatedNodes = 3 }
    },

    // Multi-instance tasks require inter-node communication, and those nodes
    // must run only one task at a time.
    InterNodeCommunication = InterNodeCommunicationState.Enabled,
    TaskSlotsPerNode = 1
};

Observação

Se você tentar executar uma tarefa de várias instâncias em um pool com a comunicação entre nós desabilitada ou com o valor de taskSlotsPerNode maior que 1, a tarefa nunca será agendada; ela permanecerá indefinidamente no estado "ativo".

Pools com InterComputeNodeCommunication habilitado não permitirão o desprovisionamento do nó automaticamente.

Usar uma StartTask para instalar MPI

Para executar aplicativos MPI com uma tarefa de várias instâncias, primeiro você precisa instalar uma implementação MPI (MS-MPI ou Intel MPI, por exemplo) em nós de computação no pool. Este é um bom momento para usar um BatchAccountPoolStartTask, executado sempre que um nó ingressa em um pool ou é reiniciado. Esse snippet de código adiciona uma tarefa inicial à definição do pool que especifica o pacote de instalação MS-MPI como um arquivo de recurso. A linha de comando da tarefa de inicialização é executada após o download do arquivo de recursos para o nó. Nesse caso, a linha de comando executa uma instalação autônoma do MS-MPI.

// Add a start task to the pool which we use for installing MS-MPI on
// the nodes as they join the pool (or when they are restarted).
poolData.StartTask = new BatchAccountPoolStartTask()
{
    CommandLine = "cmd /c MSMpiSetup.exe -unattend -force",
    UserIdentity = new BatchUserIdentity()
    {
        AutoUser = new BatchAutoUserSpecification() { ElevationLevel = BatchUserAccountElevationLevel.Admin }
    },
    WaitForSuccess = true,
};
poolData.StartTask.ResourceFiles.Add(new BatchResourceFile()
{
    HttpUri = new Uri("https://mystorageaccount.blob.core.windows.net/mycontainer/MSMpiSetup.exe"),
    FilePath = "MSMpiSetup.exe"
});

// Create the fully configured pool.
ArmOperation<BatchAccountPoolResource> pool = await poolCollection.CreateOrUpdateAsync(
    WaitUntil.Completed, "MultiInstanceSamplePool", poolData);

Acesso remoto direto à memória (RDMA)

Ao escolher um tamanho compatível com RDMA, como A9, para os nós de computação no seu pool do Batch, sua aplicação MPI pode aproveitar a rede RDMA (acesso remoto direto à memória) de baixa latência e alto desempenho do Azure.

Procure por tamanhos especificados como "compatíveis com RDMA" em Tamanhos de máquinas virtuais no Azure (para pools VirtualMachineConfiguration) ou Tamanhos de Serviços de Nuvem (para pools CloudServicesConfiguration).

Observação

Para tirar proveito dos RDMA nos nós de computação Linux, você deverá usar Intel MPI nos nós.

Crie uma tarefa de várias instâncias com Azure. Compute.Batch

Agora que já abordamos os requisitos do pool e a instalação do pacote MPI, vamos criar a tarefa de múltiplas instâncias. Neste snippet, criamos um BatchTaskCreateOptions padrão e configuramos sua propriedade MultiInstanceSettings . Conforme mencionado anteriormente, a tarefa com várias instâncias não é um tipo de tarefa distinto, mas uma tarefa padrão do Batch configurada com definições de várias instâncias.

// Create the multi-instance task. Its command line is the "application command"
// and will be executed *only* by the primary, and only after the primary and
// subtasks execute the CoordinationCommandLine.
BatchTaskCreateOptions myMultiInstanceTask = new BatchTaskCreateOptions(
    id: "mymultiinstancetask",
    commandLine: "cmd /c mpiexec.exe -wdir %AZ_BATCH_TASK_SHARED_DIR% MyMPIApplication.exe")
{
    // Configure the task's MultiInstanceSettings. The CoordinationCommandLine will be executed by
    // the primary and all subtasks.
    MultiInstanceSettings = new MultiInstanceSettings(
        @"cmd /c start cmd /c ""%MSMPI_BIN%\smpd.exe"" -d")
    {
        NumberOfInstances = numberOfNodes
    }
};

myMultiInstanceTask.MultiInstanceSettings.CommonResourceFiles.Add(new ResourceFile()
{
    HttpUri = new Uri("https://mystorageaccount.blob.core.windows.net/mycontainer/MyMPIApplication.exe"),
    FilePath = "MyMPIApplication.exe"
});

// Submit the task to the job. Batch will take care of splitting it into subtasks and
// scheduling them for execution on the nodes.
await myBatchClient.CreateTaskAsync("mybatchjob", myMultiInstanceTask);

Tarefa principal e subtarefas

Quando você cria as configurações de várias instâncias de uma tarefa, pode especificar o número de nós de computação que devem executar a tarefa. Ao enviar a tarefa para um trabalho, o serviço Batch cria uma tarefa primária e subtarefas em número suficiente para que, juntas, correspondam ao número de nós que você especificou.

Essas tarefas são atribuídas a uma ID do tipo número inteiro no intervalo de 0 a numberOfInstances - 1. A tarefa com ID igual a 0 é a tarefa principal e todas as outras IDs são subtarefas. Por exemplo, se você criar as configurações de várias instâncias abaixo para uma tarefa, a tarefa principal terá uma ID igual a 0 e as subtarefas terão IDs de 1 a 9.

int numberOfNodes = 10;
myMultiInstanceTask.MultiInstanceSettings = new MultiInstanceSettings("coord-cmd")
{
    NumberOfInstances = numberOfNodes
};

Nó mestre

Quando você envia uma tarefa de várias instâncias, o serviço de Lote designa um dos nós de computação como o "mestre" e agenda para ocorrer nele a execução a tarefa principal. Ele agenda a execução das subtarefas para ocorrer no restante dos nós alocados para a tarefa de várias instâncias.

comando de coordenação

O comando de coordenação é executado pela tarefa principal e pelas subtarefas.

A invocação do comando de coordenação é bloqueante — o Batch não executa o comando da aplicação até que o comando de coordenação tenha retornado com êxito para todas as subtarefas. O comando de coordenação, portanto, deve iniciar todos os serviços em segundo plano necessários, verificar se eles estão prontos para uso e sair. Por exemplo, este comando de coordenação para uma solução com a versão 7 do MS-MPI inicia o serviço SMPD no nó e sai:

cmd /c start cmd /c ""%MSMPI_BIN%\smpd.exe"" -d

Observe o uso de start neste comando de coordenação. Isso é necessário porque o aplicativo smpd.exe não retorna imediatamente após a execução. Sem o uso do comando start, o comando de coordenação não retornaria e, portanto, bloquearia a execução do comando do aplicativo.

Comando de aplicativo

Depois que a tarefa principal e todas as subtarefas concluírem a execução do comando de coordenação, a linha de comando da tarefa de várias instâncias é executada somentepela tarefa principal. Nós o chamamos de comando de aplicativo para distingui-lo do comando de coordenação.

Para os aplicativos MS-MPI, use o comando de aplicativo para executar o aplicativo habilitado para MPI com mpiexec.exe. Por exemplo, eis um comando do aplicativo para uma solução usando o MS-MPI versão 7:

cmd /c ""%MSMPI_BIN%\mpiexec.exe"" -c 1 -wdir %AZ_BATCH_TASK_SHARED_DIR% MyMPIApplication.exe

Observação

Como o mpiexec.exe do MS-MPI usa a variável CCP_NODES por padrão (consulte Variáveis de ambiente), a linha de comando do aplicativo de exemplo acima a exclui.

Variáveis de ambiente

O Batch cria diversas variáveis de ambiente específicas para tarefas de múltiplas instâncias nos nós de computação atribuídos a uma tarefa de múltiplas instâncias. Suas linhas de comando de coordenação e do aplicativo podem fazer referência a essas variáveis de ambiente, assim como podem fazê-lo os scripts e programas executados por elas.

As variáveis de ambiente a seguir são criadas pelo serviço de Lote para uso por tarefas de várias instâncias:

  • CCP_NODES
  • AZ_BATCH_NODE_LIST
  • AZ_BATCH_HOST_LIST
  • AZ_BATCH_MASTER_NODE
  • AZ_BATCH_TASK_SHARED_DIR
  • AZ_BATCH_IS_CURRENT_NODE_MASTER

Para obter detalhes completos sobre estas e as outras variáveis de ambiente do nó de computação do Batch, incluindo seus conteúdos e sua visibilidade, consulte Variáveis de ambiente do nó de computação.

Dica

O exemplo de código Batch Linux MPI contém um exemplo de como várias dessas variáveis de ambiente podem ser usadas.

Arquivos de recurso

Há dois conjuntos de arquivos de recursos a serem considerados para tarefas de várias instâncias: arquivos de recurso comuns que todas as tarefas baixam (principal e subtarefas) e arquivos de recurso especificados para a própria tarefa de várias instâncias, que é baixado somente pela tarefa principal.

Você pode especificar um ou mais arquivos de recurso comum nas configurações de várias instâncias de uma tarefa. Esses arquivos de recursos comuns são baixados do Armazenamento do Azure para o diretório compartilhado da tarefa de cada nó pela tarefa principal e por cada subtarefa. Você pode acessar, nas linhas de comando do aplicativo e de coordenação, o diretório compartilhado da tarefa usando a variável de ambiente AZ_BATCH_TASK_SHARED_DIR. O caminho AZ_BATCH_TASK_SHARED_DIR é idêntico em todos os nós alocados para a tarefa multi-instância, portanto você pode compartilhar um único comando de coordenação entre a tarefa principal e todas as subtarefas. O Batch não "compartilha" o diretório no sentido de um acesso remoto, mas você pode usá-lo como um ponto de montagem ou de compartilhamento, conforme mencionado anteriormente na dica sobre variáveis de ambiente.

Os arquivos de recursos que você especificar para a tarefa de várias instâncias em si serão baixados para o diretório de trabalho da tarefa, AZ_BATCH_TASK_WORKING_DIR por padrão. Conforme mencionado, em contraste com arquivos de recursos comuns, apenas a tarefa principal baixa arquivos de recurso especificados para a tarefa de várias instâncias em si.

Importante

Sempre use as variáveis de ambiente AZ_BATCH_TASK_SHARED_DIR e AZ_BATCH_TASK_WORKING_DIR para fazer referência a esses diretórios em suas linhas de comando. Não tente construir os caminhos manualmente.

Tempo de vida da tarefa

O tempo de vida da tarefa principal controla o tempo de vida de toda a tarefa com várias instâncias. Quando a tarefa principal é finalizada, todas as subtarefas são encerradas. O código de saída da tarefa principal é o código de saída da tarefa e, portanto, é usado para determinar o êxito ou a falha da tarefa para fins de repetição.

Se qualquer uma das subtarefas falhar, por exemplo, encerrando com um código de retorno diferente de zero, toda a tarefa com várias instâncias falhará. A tarefa de múltiplas instâncias é então encerrada e executada novamente, até atingir seu limite de tentativas.

Quando você exclui uma tarefa de várias instâncias, a tarefa principal e todas as subtarefas são também excluídas pelo serviço do Lote. Todos os diretórios de subtarefa e seus arquivos serão excluídos dos nós de computação, como ocorre com uma tarefa padrão.

BatchTaskConstraints para uma tarefa de várias instâncias, como as propriedades MaxTaskRetryCount, MaxWallClockTime e RetentionTime, são respeitadas da mesma forma que em uma tarefa padrão e aplicam-se à tarefa principal e a todas as subtarefas. No entanto, se você alterar a propriedade RetentionTime depois de adicionar a tarefa de várias instâncias ao trabalho, essa alteração será aplicada somente à tarefa principal e todas as subtarefas continuarão a usar o RetentionTime original.

A lista de tarefas recentes de um nó de computação reflete a ID da subtarefa se a tarefa recente fizer parte de uma tarefa de várias instâncias.

Obtenha informações sobre subtarefas

Para obter informações sobre subtarefas usando a biblioteca Azure.Compute.Batch, chame o método BatchClient.GetTaskSubTasks. Esse método retorna informações sobre todas as subtarefas e sobre o nó de computação que executou as tarefas. Com essas informações, você pode determinar o diretório raiz de cada subtarefa, a id do pool, seu estado atual, o código de saída e muito mais. Você pode usar essas informações em combinação com o método BatchClient.GetNodeFile para obter os arquivos da subtarefa. Observe que esse método não retorna informações sobre a tarefa principal (ID 0).

Observação

Salvo indicação em contrário, os métodos Azure.Compute.Batch que operam no próprio BatchTask de várias instâncias se aplicam somente à tarefa primária. Por exemplo, quando você chama o GetTaskFiles método em uma tarefa de várias instâncias, somente os arquivos da tarefa primária são retornados.

O snippet de código a seguir mostra como obter as informações sobre subtarefas, bem como solicitar o conteúdo do arquivo dos nós em que elas são executadas.

// Obtain the multi-instance task from the Batch service
BatchTask myMultiInstanceTask = await batchClient.GetTaskAsync("mybatchjob", "mymultiinstancetask");
_ = myMultiInstanceTask;

// Now iterate over the subtasks for the task and print their stdout and stderr
// output if the subtask has completed
await foreach (BatchSubtask subtask in batchClient.GetSubTasksAsync("mybatchjob", "mymultiinstancetask"))
{
    Console.WriteLine("subtask: {0}", subtask.Id);
    Console.WriteLine("exit code: {0}", subtask.ExitCode);

    if (subtask.State == BatchSubtaskState.Completed)
    {
        BatchNode node = await batchClient.GetNodeAsync(
            subtask.NodeInfo.PoolId,
            subtask.NodeInfo.NodeId);

        BinaryData stdOut = await batchClient.GetNodeFileAsync(
            subtask.NodeInfo.PoolId, node.Id, $"{subtask.NodeInfo.TaskRootDirectory}/stdout.txt");
        BinaryData stdErr = await batchClient.GetNodeFileAsync(
            subtask.NodeInfo.PoolId, node.Id, $"{subtask.NodeInfo.TaskRootDirectory}/stderr.txt");

        Console.WriteLine("node: {0}:", node.Id);
        Console.WriteLine("stdout.txt: {0}", stdOut);
        Console.WriteLine("stderr.txt: {0}", stdErr);
    }
    else
    {
        Console.WriteLine("\tSubtask {0} is in state {1}", subtask.Id, subtask.State);
    }
}

Exemplo de código

O exemplo de código MultiInstanceTasks no GitHub demonstra como usar uma tarefa de várias instâncias para executar uma aplicação MS-MPI nos nós de computação do Batch. Siga as etapas abaixo para executar o exemplo.

Preparação

  1. Baixe o SDK do MS-MPI e os instaladores dos pacotes distribuíveis e instale-os. Após a instalação, você pode verificar se as variáveis de ambiente do MS-MPI foram definidas.
  2. Compile uma versão Release do programa de exemplo MPI MPIHelloWorld. Este é o programa que será executado em nós de computação pela tarefa de várias instâncias.
  3. Crie um arquivo zip contendo MPIHelloWorld.exe (compilado na etapa 2) e MSMpiSetup.exe (baixado na etapa 1). Você vai carregar esse arquivo zip como um pacote de aplicativos na próxima etapa.
  4. Use o portal do Azure para criar um aplicativo do Lote chamado "MPIHelloWorld" e especifique o arquivo zip que você criou na etapa anterior como a versão "1.0" do pacote de aplicativos. Veja Carregar e gerenciar aplicativos para saber mais.

Dica

Compile uma versão de Lançamento do MPIHelloWorld.exe para que você não tenha que incluir as dependências adicionais (por exemplo, msvcp140d.dll ou vcruntime140d.dll) em seu pacote de aplicativos.

Execução

  1. Baixe o arquivo .zip do azure-batch-samples no GitHub.

  2. Abra a solução MultiInstanceTasks no Visual Studio 2019. O arquivo de solução MultiInstanceTasks.sln está localizado em:

    azure-batch-samples\CSharp\ArticleProjects\MultiInstanceTasks\

  3. Insira as credenciais das contas do Batch e do Storage em AccountSettings.settings no projeto Microsoft.Azure.Batch.Samples.Common.

  4. Compilar e executar a solução MultiInstanceTasks para executar o aplicativo de exemplo MPI nos nós de computação de um pool do Batch.

  5. Opcional: use o portal do Azure ou o Batch Explorer para examinar o pool, o trabalho e a tarefa de exemplo ("MultiInstanceSamplePool", "MultiInstanceSampleJob", "MultiInstanceSampleTask") antes de excluir os recursos.

Dica

Você poderá baixar o Visual Studio Community gratuitamente se já não tiver o Visual Studio.

A saída de MultiInstanceTasks.exe deverá ser semelhante a esta:

Creating pool [MultiInstanceSamplePool]...
Creating job [MultiInstanceSampleJob]...
Adding task [MultiInstanceSampleTask] to job [MultiInstanceSampleJob]...
Awaiting task completion, timeout in 00:30:00...

Main task [MultiInstanceSampleTask] is in state [Completed] and ran on compute node [tvm-1219235766_1-20161017t162002z]:
---- stdout.txt ----
Rank 2 received string "Hello world" from Rank 0
Rank 1 received string "Hello world" from Rank 0

---- stderr.txt ----

Main task completed, waiting 00:00:10 for subtasks to complete...

---- Subtask information ----
subtask: 1
        exit code: 0
        node: tvm-1219235766_3-20161017t162002z
        stdout.txt:
        stderr.txt:
subtask: 2
        exit code: 0
        node: tvm-1219235766_2-20161017t162002z
        stdout.txt:
        stderr.txt:

Delete job? [yes] no: yes
Delete pool? [yes] no: yes

Sample complete, hit ENTER to exit...

Próximas etapas