Partilhar via


Usar tarefas de várias instâncias para executar aplicativos MPI (Message Passing Interface) em lote

As tarefas de várias instâncias permitem executar 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 aplicativos MPI (Message Passing Interface) em lote. Neste artigo, você aprenderá a executar tarefas de várias instâncias usando a biblioteca .NET em lote.

Nota

Embora os exemplos neste artigo se concentrem nos nós de computação Batch .NET, MS-MPI e 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 de tarefas 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 trabalho, e o serviço Batch agenda cada tarefa para execução em um nó. No entanto, ao definir as configurações de várias instâncias de uma tarefa, você diz ao Batch para criar uma tarefa primária e várias subtarefas que sã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 várias instâncias para um trabalho, o Batch executa várias etapas exclusivas para tarefas de várias instâncias:

  1. O serviço em lote cria uma primária e várias subtarefas com base nas configurações de várias instâncias. O número total de tarefas (principal mais todas as subtarefas) corresponde ao número de instâncias (nós de computação) especificadas nas configurações de várias instâncias.
  2. O lote designa um dos nós de computação como mestre e agenda a tarefa principal a ser executada no mestre. Ele agenda as subtarefas para serem executadas no restante dos nós de computação alocados para a tarefa de várias instâncias, uma subtarefa por nó.
  3. As subtarefas principal e todas as subtarefas baixam todos os arquivos de recursos comuns especificados nas configurações de várias instâncias.
  4. Depois que os arquivos de recursos comuns forem baixados, as tarefas primária e secundária executarão o comando de coordenação especificado nas configurações de várias instâncias. O comando coordination é normalmente usado para preparar nós para executar a tarefa. Isso pode incluir iniciar serviços em segundo plano (como o Microsoft MPI) smpd.exee verificar se os nós estão prontos para processar mensagens entre nós.
  5. A tarefa primária executa o comando do aplicativo no nó mestre depois que o comando de coordenação foi concluído com êxito pelas subtarefas primária e todas as subtarefas. O comando application é a linha de comando da própria tarefa de várias instâncias e é executado apenas pela tarefa principal. Em uma solução baseada em MS-MPI , é aqui que você executa seu aplicativo habilitado para MPI usando mpiexec.exeo .

Nota

Embora seja funcionalmente distinta, a "tarefa de várias instâncias" não é um tipo de tarefa exclusivo como StartTask ou JobPreparationTask. A tarefa de várias instâncias é simplesmente uma tarefa em lote padrão (CloudTask em Batch .NET) cujas configurações de várias instâncias foram configuradas. 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 simultânea de tarefas desabilitada. Para desativar a execução simultânea de tarefas, defina a propriedade CloudPool.TaskSlotsPerNode como 1.

Nota

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

Este trecho de código mostra como criar um pool para tarefas de várias instâncias usando a biblioteca .NET em lote.

CloudPool myCloudPool =
    myBatchClient.PoolOperations.CreatePool(
        poolId: "MultiInstanceSamplePool",
        targetDedicatedComputeNodes: 3
        virtualMachineSize: "standard_d1_v2",
        VirtualMachineConfiguration: new VirtualMachineConfiguration(
        imageReference: new ImageReference(
                        publisher: "MicrosoftWindowsServer",
                        offer: "WindowsServer",
                        sku: "2019-datacenter-core",
                        version: "latest"),
        nodeAgentSkuId: "batch.node.windows amd64");

// Multi-instance tasks require inter-node communication, and those nodes
// must run only one task at a time.
myCloudPool.InterComputeNodeCommunicationEnabled = true;
myCloudPool.TaskSlotsPerNode = 1;

Nota

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

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

Usar um StartTask para instalar o 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) nos nós de computação no pool. Este é um bom momento para usar um StartTask, que é executado sempre que um nó se junta a um pool ou é reiniciado. Este trecho de código cria um StartTask que especifica o pacote de instalação do MS-MPI como um arquivo de recurso. A linha de comando da tarefa inicial é executada depois que o arquivo de recurso é baixado para o nó. Nesse caso, a linha de comando executa uma instalação autônoma do MS-MPI.

// Create a StartTask for the pool which we use for installing MS-MPI on
// the nodes as they join the pool (or when they are restarted).
StartTask startTask = new StartTask
{
    CommandLine = "cmd /c MSMpiSetup.exe -unattend -force",
    ResourceFiles = new List<ResourceFile> { new ResourceFile("https://mystorageaccount.blob.core.windows.net/mycontainer/MSMpiSetup.exe", "MSMpiSetup.exe") },
    UserIdentity = new UserIdentity(new AutoUserSpecification(elevationLevel: ElevationLevel.Admin)),
    WaitForSuccess = true
};
myCloudPool.StartTask = startTask;

// Commit the fully configured pool to the Batch service to actually create
// the pool and its compute nodes.
await myCloudPool.CommitAsync();

Acesso remoto direto à memória (RDMA)

Quando você escolhe um tamanho compatível com RDMA, como A9, para os nós de computação em seu pool de lotes, seu aplicativo MPI pode aproveitar a rede RDMA (acesso remoto direto à memória) de alto desempenho e baixa latência.

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

Nota

Para aproveitar o RDMA nos nós de computação do Linux, você deve usar o Intel MPI nos nós.

Criar uma tarefa de várias instâncias com o Batch .NET

Agora que já abordamos os requisitos do pool e a instalação do pacote MPI, vamos criar a tarefa de várias instâncias. Neste trecho, criamos um CloudTask padrão e, em seguida, configuramos sua propriedade MultiInstanceSettings. Como mencionado anteriormente, a tarefa de várias instâncias não é um tipo de tarefa distinto, mas uma tarefa em lote padrão configurada com configuraçõ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.
CloudTask myMultiInstanceTask = new CloudTask(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.
myMultiInstanceTask.MultiInstanceSettings =
    new MultiInstanceSettings(numberOfNodes) {
    CoordinationCommandLine = @"cmd /c start cmd /c ""%MSMPI_BIN%\smpd.exe"" -d",
    CommonResourceFiles = new List<ResourceFile> {
    new ResourceFile("https://mystorageaccount.blob.core.windows.net/mycontainer/MyMPIApplication.exe",
                     "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.JobOperations.AddTaskAsync("mybatchjob", myMultiInstanceTask);

Tarefa principal e subtarefas

Ao criar as configurações de várias instâncias para uma tarefa, você especifica o número de nós de computação que devem executar a tarefa. Quando você envia a tarefa para um trabalho, o serviço Lote cria uma tarefa primária e subtarefas suficientes que, juntas, correspondem ao número de nós especificado.

Essas tarefas recebem um ID inteiro no intervalo de 0 a numberOfInstances - 1. A tarefa com ID 0 é a tarefa principal e todas as outras IDs são subtarefas. Por exemplo, se você criar as seguintes configurações de várias instâncias para uma tarefa, a tarefa principal terá uma ID de 0 e as subtarefas terão IDs de 1 a 9.

int numberOfNodes = 10;
myMultiInstanceTask.MultiInstanceSettings = new MultiInstanceSettings(numberOfNodes);

Nó principal

Quando você envia uma tarefa de várias instâncias, o serviço em lote designa um dos nós de computação como o nó "mestre" e agenda a tarefa principal para ser executada no nó mestre. As subtarefas são agendadas para serem executadas no restante dos nós alocados para a tarefa de várias instâncias.

Comando de coordenação

O comando de coordenação é executado tanto pelas tarefas primárias como pelas subtarefas.

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

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

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

Comando do aplicativo

Quando a tarefa primária e todas as subtarefas terminarem de executar o comando de coordenação, a linha de comando da tarefa de várias instâncias será executada apenas pela tarefa primária. Chamamos isso de comando de aplicativo para distingui-lo do comando de coordenação.

Para aplicativos MS-MPI, use o comando application para executar seu aplicativo habilitado para MPI com mpiexec.exeo . Por exemplo, aqui está um comando de 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

Nota

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

Variáveis de ambiente

O lote cria várias variáveis de ambiente específicas para tarefas de várias instâncias nos nós de computação alocados para uma tarefa de várias instâncias. Suas linhas de comando de coordenação e aplicativo podem fazer referência a essas variáveis de ambiente, assim como os scripts e programas que eles executam.

As seguintes variáveis de ambiente são criadas pelo serviço Batch 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 essas e outras variáveis de ambiente do nó de computação em lote, incluindo seu conteúdo e visibilidade, consulte Variáveis de ambiente do nó de computação.

Gorjeta

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

Ficheiros de recursos

Há dois conjuntos de arquivos de recursos a serem considerados para tarefas de várias instâncias: arquivos de recursos comuns que todas as tarefas baixam (tanto as tarefas primárias quanto as subtarefas) e os arquivos de recursos especificados para a própria tarefa de várias instâncias, que apenas a tarefa principal baixa.

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

Os arquivos de recursos especificados para a própria tarefa de várias instâncias são baixados para o diretório de trabalho da tarefa, AZ_BATCH_TASK_WORKING_DIR, por padrão. Como mencionado, ao contrário dos arquivos de recursos comuns, apenas a tarefa principal baixa arquivos de recursos especificados para a própria tarefa de várias instâncias.

Importante

Sempre use as variáveis AZ_BATCH_TASK_SHARED_DIR de ambiente e AZ_BATCH_TASK_WORKING_DIR faça 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 de várias instâncias. Quando a primária é encerrada, todas as subtarefas são encerradas. O código de saída do primário é o código de saída da tarefa e, portanto, é usado para determinar o sucesso ou falha da tarefa para fins de nova tentativa.

Se qualquer uma das subtarefas falhar, sair com um código de retorno diferente de zero, por exemplo, toda a tarefa de várias instâncias falhará. A tarefa de várias instâncias é então encerrada e repetida, até seu limite de repetição.

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

TaskConstraints para uma tarefa de várias instâncias, como as propriedades MaxTaskRetryCount, MaxWallClockTime e RetentionTime , são honradas como são para uma tarefa padrão e se aplicam às subtarefas primárias 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 recente de um nó de computação reflete a ID de uma subtarefa se a tarefa recente fizer parte de uma tarefa de várias instâncias.

Obter informações sobre subtarefas

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

Nota

Salvo indicação em contrário, os métodos Batch .NET que operam no próprio CloudTask de várias instâncias aplicam-se apenas à tarefa principal. Por exemplo, quando você chama o método CloudTask.ListNodeFiles em uma tarefa de várias instâncias, somente os arquivos da tarefa principal são retornados.

O trecho de código a seguir mostra como obter informações de subtarefa, bem como solicitar o conteúdo do arquivo dos nós nos quais eles foram executados.

// Obtain the job and the multi-instance task from the Batch service
CloudJob boundJob = batchClient.JobOperations.GetJob("mybatchjob");
CloudTask myMultiInstanceTask = boundJob.GetTask("mymultiinstancetask");

// Now obtain the list of subtasks for the task
IPagedEnumerable<SubtaskInformation> subtasks = myMultiInstanceTask.ListSubtasks();

// Asynchronously iterate over the subtasks and print their stdout and stderr
// output if the subtask has completed
await subtasks.ForEachAsync(async (subtask) =>
{
    Console.WriteLine("subtask: {0}", subtask.Id);
    Console.WriteLine("exit code: {0}", subtask.ExitCode);

    if (subtask.State == SubtaskState.Completed)
    {
        ComputeNode node =
            await batchClient.PoolOperations.GetComputeNodeAsync(subtask.ComputeNodeInformation.PoolId,
                                                                 subtask.ComputeNodeInformation.ComputeNodeId);

        NodeFile stdOutFile = await node.GetNodeFileAsync(subtask.ComputeNodeInformation.TaskRootDirectory + "\\" + Constants.StandardOutFileName);
        NodeFile stdErrFile = await node.GetNodeFileAsync(subtask.ComputeNodeInformation.TaskRootDirectory + "\\" + Constants.StandardErrorFileName);
        stdOut = await stdOutFile.ReadAsStringAsync();
        stdErr = await stdErrFile.ReadAsStringAsync();

        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 um aplicativo MS-MPI em nós de computação em lote. Siga as etapas abaixo para executar o exemplo.

Preparação

  1. Faça o download dos instaladores MS-MPI SDK e Redist e instale-os. Após a instalação, você pode verificar se as variáveis de ambiente MS-MPI foram definidas.
  2. Crie uma versão Release do programa MPI de exemplo 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 (que você criou na etapa 2) e MSMpiSetup.exe (que você baixou na etapa 1). Você carregará este arquivo zip como um pacote de aplicativo na próxima etapa.
  4. Use o portal do Azure para criar um aplicativo em lote chamado "MPIHelloWorld" e especifique o arquivo zip criado na etapa anterior como versão "1.0" do pacote do aplicativo. Consulte Carregar e gerenciar aplicativos para obter mais informações.

Gorjeta

A criação de uma versão Release do MPIHelloWorld.exe garante que você não precise incluir dependências adicionais (por exemplo, msvcp140d.dll ou vcruntime140d.dll) no pacote do aplicativo.

Execução

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

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

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

  3. Insira suas credenciais de conta de Lote e Armazenamento no AccountSettings.settings projeto Microsoft.Azure.Batch.Samples.Common.

  4. Crie e execute a solução MultiInstanceTasks para executar o aplicativo de exemplo MPI em nós de computação em um pool de lotes.

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

Gorjeta

Você pode baixar o Visual Studio Community gratuitamente se ainda não tiver o Visual Studio.

A saída de MultiInstanceTasks.exe é semelhante à seguinte:

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óximos passos