Resolver problemas de ParallelRunStep

APLICA-SE A:Python SDK azureml v1

Neste artigo, irá aprender a resolver problemas quando obtém erros ao utilizar a classe ParallelRunStep do SDK do Azure Machine Learning.

Para obter sugestões gerais sobre a resolução de problemas de um pipeline, veja Resolução de problemas de pipelines de machine learning.

Testar scripts localmente

O ParallelRunStep é executado como um passo nos pipelines de ML. Poderá querer testar os scripts localmente como um primeiro passo.

Requisitos de script de entrada

O script de entrada de um ParallelRunSteptem de conter uma run() função e, opcionalmente, contém uma função init() :

  • init(): utilize esta função para qualquer preparação dispendiosa ou comum para processamento posterior. Por exemplo, utilize-o para carregar o modelo para um objeto global. Esta função será chamada apenas uma vez no início do processo.

    Nota

    Se o seu init método criar um diretório de saída, especifique esse parents=True e exist_ok=True. O init método é chamado a partir de cada processo de trabalho em cada nó em que a tarefa está em execução.

  • run(mini_batch): a função será executada para cada mini_batch instância.
    • mini_batch: ParallelRunStep invocará o método run e transmitirá uma lista ou pandas DataFrame como um argumento para o método. Cada entrada no mini_batch será um caminho de ficheiro se a entrada for um FileDataset ou um pandas DataFrame se a entrada for um TabularDataset.
    • response: o método run() deve devolver um pandas DataFrame ou uma matriz. Para append_row output_action, estes elementos devolvidos são acrescentados ao ficheiro de saída comum. Para summary_only, os conteúdos dos elementos são ignorados. Para todas as ações de saída, cada elemento de saída devolvido indica uma execução bem-sucedida do elemento de entrada no mini-lote de entrada. Certifique-se de que estão incluídos dados suficientes no resultado de execução para mapear a entrada para executar o resultado de saída. O resultado da execução será escrito no ficheiro de saída e não é garantido que esteja em ordem, deve utilizar alguma chave na saída para mapeá-la para a entrada.

      Nota

      Espera-se um elemento de saída para um elemento de entrada.

%%writefile digit_identification.py
# Snippets from a sample script.
# Refer to the accompanying digit_identification.py
# (https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/machine-learning-pipelines/parallel-run)
# for the implementation script.

import os
import numpy as np
import tensorflow as tf
from PIL import Image
from azureml.core import Model


def init():
    global g_tf_sess

    # Pull down the model from the workspace
    model_path = Model.get_model_path("mnist")

    # Construct a graph to execute
    tf.reset_default_graph()
    saver = tf.train.import_meta_graph(os.path.join(model_path, 'mnist-tf.model.meta'))
    g_tf_sess = tf.Session()
    saver.restore(g_tf_sess, os.path.join(model_path, 'mnist-tf.model'))


def run(mini_batch):
    print(f'run method start: {__file__}, run({mini_batch})')
    resultList = []
    in_tensor = g_tf_sess.graph.get_tensor_by_name("network/X:0")
    output = g_tf_sess.graph.get_tensor_by_name("network/output/MatMul:0")

    for image in mini_batch:
        # Prepare each image
        data = Image.open(image)
        np_im = np.array(data).reshape((1, 784))
        # Perform inference
        inference_result = output.eval(feed_dict={in_tensor: np_im}, session=g_tf_sess)
        # Find the best probability, and add it to the result list
        best_result = np.argmax(inference_result)
        resultList.append("{}: {}".format(os.path.basename(image), best_result))

    return resultList

Se tiver outro ficheiro ou pasta no mesmo diretório que o script de inferência, pode referenciar o mesmo ao encontrar o diretório de trabalho atual. Se quiser importar os pacotes, também pode acrescentar a pasta do pacote a sys.path.

script_dir = os.path.realpath(os.path.join(__file__, '..',))
file_path = os.path.join(script_dir, "<file_name>")

packages_dir = os.path.join(file_path, '<your_package_folder>')
if packages_dir not in sys.path:
    sys.path.append(packages_dir)
from <your_package> import <your_class>

Parâmetros para ParallelRunConfig

ParallelRunConfig é a configuração principal, por ParallelRunStep exemplo, no pipeline do Azure Machine Learning. Pode utilizá-lo para moldar o script e configurar os parâmetros necessários, incluindo todas as seguintes entradas:

  • entry_script: um script de utilizador como um caminho de ficheiro local que será executado em paralelo em vários nós. Se source_directory estiver presente, utilize um caminho relativo. Caso contrário, utilize qualquer caminho acessível no computador.

  • mini_batch_size: o tamanho do mini-lote transmitido para uma única run() chamada. (opcional; o valor predefinido é 10 ficheiros para FileDataset e 1MB para TabularDataset.)

    • Para FileDataset, é o número de ficheiros com um valor mínimo de 1. Pode combinar vários ficheiros num mini-lote.
    • Para TabularDataset, é o tamanho dos dados. Os valores de exemplo são 1024, 1024KB, 10MBe 1GB. O valor recomendado é 1MB. O mini-lote de TabularDataset nunca irá ultrapassar os limites dos ficheiros. Por exemplo, se tiver .csv ficheiros com vários tamanhos, o ficheiro mais pequeno é de 100 KB e o maior é 10 MB. Se definir mini_batch_size = 1MB, os ficheiros com um tamanho inferior a 1 MB serão tratados como um mini-lote. Os ficheiros com um tamanho superior a 1 MB serão divididos em vários mini-lotes.

      Nota

      Os TabularDatasets suportados pelo SQL não podem ser particionados. TabularDatasets de um único ficheiro parquet e grupo de linha única não podem ser particionados.

  • error_threshold: o número de falhas TabularDataset de registo para e falhas de ficheiros para FileDataset isso deve ser ignorado durante o processamento. Se a contagem de erros para toda a entrada for superior a este valor, a tarefa será abortada. O limiar de erro destina-se a toda a entrada e não ao mini-lote individual enviado para o run() método. O intervalo é [-1, int.max]. A -1 parte indica ignorar todas as falhas durante o processamento.

  • output_action: Um dos seguintes valores indica como a saída será organizada:

    • summary_only: o script de utilizador irá armazenar a saída. ParallelRunStep utilizará o resultado apenas para o cálculo do limiar de erro.
    • append_row: Para todas as entradas, apenas será criado um ficheiro na pasta de saída para acrescentar todas as saídas separadas por linha.
  • append_row_file_name: Para personalizar o nome do ficheiro de saída para append_row output_action (opcional; o valor predefinido é parallel_run_step.txt).

  • source_directory: caminhos para pastas que contêm todos os ficheiros a executar no destino de computação (opcional).

  • compute_target: só AmlCompute é suportado.

  • node_count: o número de nós de computação a utilizar para executar o script de utilizador.

  • process_count_per_node: o número de processos de trabalho por nó para executar o script de entrada em paralelo. Para um computador GPU, o valor predefinido é 1. Para um computador CPU, o valor predefinido é o número de núcleos por nó. Um processo de trabalho irá chamar run() repetidamente ao transmitir o mini lote que obtém. O número total de processos de trabalho na sua tarefa é process_count_per_node * node_count, que decide o número máximo de run() execuções em paralelo.

  • environment: a definição de ambiente python. Pode configurá-lo para utilizar um ambiente Python existente ou para configurar um ambiente temporário. A definição também é responsável por definir as dependências de aplicação necessárias (opcional).

  • logging_level: Verbosidade do registo. Os valores no aumento da verbosidade são: WARNING, INFOe DEBUG. (opcional; o valor predefinido é INFO)

  • run_invocation_timeout: o run() tempo limite da invocação do método em segundos. (opcional; o valor predefinido é 60)

  • run_max_try: contagem máxima de tentativas de run() um mini-lote. Um run() falha se for emitida uma exceção ou se não for devolvida nada quando run_invocation_timeout é atingido (opcional; o valor predefinido é 3).

Pode especificar mini_batch_size, , process_count_per_nodenode_count, , logging_level, run_invocation_timeoute run_max_try como PipelineParameter, para que, quando submeter novamente uma execução de pipeline, possa ajustar os valores dos parâmetros. Neste exemplo, vai utilizar PipelineParameter para mini_batch_size e Process_count_per_node e irá alterar estes valores quando voltar a submeter outra execução.

Visibilidade dos dispositivos CUDA

Para destinos de computação equipados com GPUs, a variável CUDA_VISIBLE_DEVICES de ambiente será definida em processos de trabalho. Em AmlCompute, pode encontrar o número total de dispositivos GPU na variável AZ_BATCHAI_GPU_COUNT_FOUNDde ambiente , que é definido automaticamente. Se quiser que cada processo de trabalho tenha uma GPU dedicada, defina process_count_per_node igual ao número de dispositivos GPU num computador. Cada processo de trabalho atribuirá um índice exclusivo a CUDA_VISIBLE_DEVICES. Se um processo de trabalho parar por qualquer motivo, o próximo processo de trabalho iniciado utilizará o índice de GPU lançado.

Se o número total de dispositivos GPU for inferior process_count_per_nodea , os processos de trabalho serão atribuídos ao índice de GPU até que todos tenham sido utilizados.

Dado que o total de dispositivos GPU é 2 e process_count_per_node = 4 , por exemplo, o processo 0 e o processo 1 terão o índice 0 e 1. O processo 2 e 3 não terão uma variável de ambiente. Para uma biblioteca que utilize esta variável de ambiente para atribuição de GPU, o processo 2 e 3 não terá GPUs e não tentará adquirir dispositivos GPU. Se o processo 0 parar, libertará o índice GPU 0. O próximo processo, que é o processo 4, terá o índice GPU 0 atribuído.

Para obter mais informações, veja Sugestão do CUDA Pro: Controlar a Visibilidade da GPU com CUDA_VISIBLE_DEVICES.

Parâmetros para criar o ParallelRunStep

Crie o ParallelRunStep com o script, a configuração do ambiente e os parâmetros. Especifique o destino de computação que já anexou à área de trabalho como o destino da execução do script de inferência. Utilize ParallelRunStep para criar o passo do pipeline de inferência de lotes, que utiliza todos os seguintes parâmetros:

  • name: o nome do passo, com as seguintes restrições de nomenclatura: exclusivo, 3-32 carateres e regex ^[a-z]([-a-z0-9]*[a-z0-9])?$.
  • parallel_run_config: um ParallelRunConfig objeto, conforme definido anteriormente.
  • inputs: um ou mais conjuntos de dados do Azure Machine Learning de tipo único a serem particionados para processamento paralelo.
  • side_inputs: Um ou mais dados de referência ou conjuntos de dados utilizados como entradas laterais sem necessidade de particionar.
  • output: um OutputFileDatasetConfig objeto que representa o caminho do diretório no qual os dados de saída serão armazenados.
  • arguments: uma lista de argumentos transmitidos ao script de utilizador. Utilize unknown_args para os obter no script de entrada (opcional).
  • allow_reuse: se o passo deve reutilizar os resultados anteriores quando executado com as mesmas definições/entradas. Se este parâmetro for False, será sempre gerada uma nova execução para este passo durante a execução do pipeline. (opcional; o valor predefinido é True.)
from azureml.pipeline.steps import ParallelRunStep

parallelrun_step = ParallelRunStep(
    name="predict-digits-mnist",
    parallel_run_config=parallel_run_config,
    inputs=[input_mnist_ds_consumption],
    output=output_dir,
    allow_reuse=True
)

Depurar scripts do contexto remoto

A transição da depuração de um script de classificação localmente para a depuração de um script de classificação num pipeline real pode ser um salto difícil. Para obter informações sobre como localizar os seus registos no portal, veja a secção pipelines de machine learning sobre a depuração de scripts a partir de um contexto remoto. As informações nessa secção também se aplicam a um ParallelRunStep.

Por exemplo, o ficheiro 70_driver_log.txt de registo contém informações do controlador que inicia o código ParallelRunStep.

Devido à natureza distribuída das tarefas ParallelRunStep, existem registos de várias origens diferentes. No entanto, são criados dois ficheiros consolidados que fornecem informações de alto nível:

  • ~/logs/job_progress_overview.txt: este ficheiro fornece uma informação de alto nível sobre o número de mini-lotes (também conhecidos como tarefas) criados até agora e o número de mini-lotes processados até agora. Neste final, mostra o resultado da tarefa. Se a tarefa falhar, irá mostrar a mensagem de erro e onde iniciar a resolução de problemas.

  • ~/logs/sys/master_role.txt: este ficheiro fornece a vista do nó principal (também conhecido como orquestrador) da tarefa em execução. Inclui a criação de tarefas, a monitorização do progresso, o resultado da execução.

Os registos gerados a partir do script de entrada com o programa auxiliar EntryScript e as instruções de impressão serão encontrados nos seguintes ficheiros:

  • ~/logs/user/entry_script_log/<node_id>/<process_name>.log.txt: estes ficheiros são os registos escritos a partir de entry_script com o programa auxiliar EntryScript.

  • ~/logs/user/stdout/<node_id>/<process_name>.stdout.txt: estes ficheiros são os registos de stdout (por exemplo, instrução de impressão) de entry_script.

  • ~/logs/user/stderr/<node_id>/<process_name>.stderr.txt: estes ficheiros são os registos do stderr de entry_script.

Para uma compreensão concisa dos erros no script, existe:

  • ~/logs/user/error.txt: este ficheiro tentará resumir os erros no script.

Para obter mais informações sobre erros no script, existem:

  • ~/logs/user/error/: contém rastreios de pilha completos de exceções emitidas durante o carregamento e a execução do script de entrada.

Quando precisar de uma compreensão completa de como cada nó executou o script de classificação, veja os registos de processos individuais de cada nó. Os registos de processos podem ser encontrados na sys/node pasta agrupadas por nós de trabalho:

  • ~/logs/sys/node/<node_id>/<process_name>.txt: este ficheiro fornece informações detalhadas sobre cada mini-lote à medida que é recolhido ou concluído por um trabalhador. Para cada mini-lote, este ficheiro inclui:

    • O endereço IP e o PID do processo de trabalho.
    • O número total de itens, a contagem de itens processados com êxito e a contagem de itens falhada.
    • Hora de início, duração, tempo de processo e tempo de execução do método.

Também pode ver os resultados das verificações periódicas da utilização de recursos para cada nó. Os ficheiros de registo e os ficheiros de configuração estão nesta pasta:

  • ~/logs/perf: defina --resource_monitor_interval para alterar o intervalo de verificação em segundos. O intervalo predefinido é 600, que é aproximadamente 10 minutos. Para parar a monitorização, defina o valor como 0. Cada <node_id> pasta inclui:

    • os/: informações sobre todos os processos em execução no nó. Uma verificação executa um comando do sistema operativo e guarda o resultado num ficheiro. No Linux, o comando é ps. No Windows, utilize tasklist.
      • %Y%m%d%H: o nome da subpasta é a hora a hora.
        • processes_%M: O ficheiro termina com o minuto da hora de verificação.
    • node_disk_usage.csv: Utilização detalhada do disco do nó.
    • node_resource_usage.csv: Descrição geral da utilização de recursos do nó.
    • processes_resource_usage.csv: Descrição geral da utilização de recursos de cada processo.

Como devo proceder para registo do meu script de utilizador a partir de um contexto remoto?

ParallelRunStep pode executar vários processos num nó com base em process_count_per_node. Para organizar os registos de cada processo no nó e combinar a instrução de impressão e registo, recomendamos que utilize o logger ParallelRunStep, conforme mostrado abaixo. Obtém um logger do EntryScript e faz com que os registos apareçam na pasta registos/utilizador no portal.

Um script de entrada de exemplo com o logger:

from azureml_user.parallel_run import EntryScript

def init():
    """Init once in a worker process."""
    entry_script = EntryScript()
    logger = entry_script.logger
    logger.info("This will show up in files under logs/user on the Azure portal.")


def run(mini_batch):
    """Call once for a mini batch. Accept and return the list back."""
    # This class is in singleton pattern and will return same instance as the one in init()
    entry_script = EntryScript()
    logger = entry_script.logger
    logger.info(f"{__file__}: {mini_batch}.")
    ...

    return mini_batch

Para onde é que a mensagem do Python logging se afunda?

ParallelRunStep define um processador no logger de raiz, o que afunda a mensagem para logs/user/stdout/<node_id>/processNNN.stdout.txt.

logging predefinição para nivelar INFO . Por predefinição, os níveis abaixo INFO não serão apresentados, como DEBUG.

Como posso escrever num ficheiro para aparecer no portal?

Os ficheiros na logs pasta serão carregados e apresentados no portal. Pode obter a pasta logs/user/entry_script_log/<node_id> como abaixo e compor o caminho do ficheiro para escrever:

from pathlib import Path
from azureml_user.parallel_run import EntryScript

def init():
    """Init once in a worker process."""
    entry_script = EntryScript()
    log_dir = entry_script.log_dir
    log_dir = Path(entry_script.log_dir)  # logs/user/entry_script_log/<node_id>/.
    log_dir.mkdir(parents=True, exist_ok=True) # Create the folder if not existing.

    proc_name = entry_script.agent_name  # The process name in pattern "processNNN".
    fil_path = log_dir / f"{proc_name}_<file_name>" # Avoid conflicting among worker processes with proc_name.

Como lidar com o início de sessão em novos processos?

Pode gerar novos processos no script de entrada com subprocess o módulo, ligar-se aos respetivos pipes de entrada/saída/erro e obter os respetivos códigos de retorno.

A abordagem recomendada é utilizar a run() função com capture_output=True. Os erros serão apresentados em logs/user/error/<node_id>/<process_name>.txt.

Se quiser utilizar Popen()o , deve redirecionar stdout/stderr para ficheiros, como:

from pathlib import Path
from subprocess import Popen

from azureml_user.parallel_run import EntryScript


def init():
    """Show how to redirect stdout/stderr to files in logs/user/entry_script_log/<node_id>/."""
    entry_script = EntryScript()
    proc_name = entry_script.agent_name  # The process name in pattern "processNNN".
    log_dir = Path(entry_script.log_dir)  # logs/user/entry_script_log/<node_id>/.
    log_dir.mkdir(parents=True, exist_ok=True) # Create the folder if not existing.
    stdout_file = str(log_dir / f"{proc_name}_demo_stdout.txt")
    stderr_file = str(log_dir / f"{proc_name}_demo_stderr.txt")
    proc = Popen(
        ["...")],
        stdout=open(stdout_file, "w"),
        stderr=open(stderr_file, "w"),
        # ...
    )

Nota

Um processo de trabalho executa o código "sistema" e o código do script de entrada no mesmo processo.

Se não for stdout ou stderr especificado, um subprocesso criado com Popen() no script de entrada herdará a definição do processo de trabalho.

stdout irá escrever para logs/sys/node/<node_id>/processNNN.stdout.txt e stderr para logs/sys/node/<node_id>/processNNN.stderr.txt.

Como devo proceder para escrever um ficheiro no diretório de saída e, em seguida, vê-lo no portal?

Pode obter o diretório de saída da EntryScript classe e escrevê-lo. Para ver os ficheiros escritos, na vista De passo Executar no portal do Azure Machine Learning, selecione o separador Saídas + registos . Selecione a ligação Saídas de dados e, em seguida, conclua os passos descritos na caixa de diálogo.

Utilize EntryScript no script de entrada, como neste exemplo:

from pathlib import Path
from azureml_user.parallel_run import EntryScript

def run(mini_batch):
    output_dir = Path(entry_script.output_dir)
    (Path(output_dir) / res1).write...
    (Path(output_dir) / res2).write...

Como posso transmitir uma entrada lateral, como um ficheiro ou ficheiros que contenham uma tabela de pesquisa, a todos os meus trabalhadores?

O utilizador pode transmitir dados de referência para o script com side_inputs parâmetro de ParalleRunStep. Todos os conjuntos de dados fornecidos como side_inputs serão montados em cada nó de trabalho. O utilizador pode obter a localização da montagem ao transmitir o argumento.

Crie um Conjunto de Dados que contenha os dados de referência, especifique um caminho de montagem local e registe-o na área de trabalho. Transmita-o para o side_inputs parâmetro do seu ParallelRunStep. Além disso, pode adicionar o respetivo caminho na arguments secção para aceder facilmente ao caminho montado.

Nota

Utilize FileDatasets apenas para side_inputs.

local_path = "/tmp/{}".format(str(uuid.uuid4()))
label_config = label_ds.as_named_input("labels_input").as_mount(local_path)
batch_score_step = ParallelRunStep(
    name=parallel_step_name,
    inputs=[input_images.as_named_input("input_images")],
    output=output_dir,
    arguments=["--labels_dir", label_config],
    side_inputs=[label_config],
    parallel_run_config=parallel_run_config,
)

Depois disso, pode aceder ao mesmo no script de inferência (por exemplo, no método init() da seguinte forma:

parser = argparse.ArgumentParser()
parser.add_argument('--labels_dir', dest="labels_dir", required=True)
args, _ = parser.parse_known_args()

labels_path = args.labels_dir

Como utilizar conjuntos de dados de entrada com a autenticação do principal de serviço?

O utilizador pode transmitir conjuntos de dados de entrada com a autenticação do principal de serviço utilizada na área de trabalho. A utilização deste conjunto de dados em ParallelRunStep requer que o conjunto de dados seja registado para que este construa a configuração ParallelRunStep.

service_principal = ServicePrincipalAuthentication(
    tenant_id="***",
    service_principal_id="***",
    service_principal_password="***")

ws = Workspace(
    subscription_id="***",
    resource_group="***",
    workspace_name="***",
    auth=service_principal
    )

default_blob_store = ws.get_default_datastore() # or Datastore(ws, '***datastore-name***')
ds = Dataset.File.from_files(default_blob_store, '**path***')
registered_ds = ds.register(ws, '***dataset-name***', create_new_version=True)

Como Verificar o Progresso e Analisá-lo

Esta secção é sobre como verificar o progresso de uma tarefa ParallelRunStep e verificar a causa de um comportamento inesperado.

Como verificar o progresso da tarefa?

Além de analisar o estado geral da StepRun, a contagem de mini-lotes agendados/processados e o progresso da geração de saída podem ser visualizados em ~/logs/job_progress_overview.<timestamp>.txt. O ficheiro roda diariamente, pode verificar o ficheiro com o carimbo de data/hora maior para obter as informações mais recentes.

O que devo verificar se não há progresso durante algum tempo?

Pode aceder ~/logs/sys/errror para ver se existe alguma exceção. Se não existir nenhum, é provável que o script de entrada esteja a demorar muito tempo, pode imprimir informações de progresso no seu código para localizar a parte demorada ou adicionar "--profiling_module", "cProfile" ao de ParallelRunStep para gerar um ficheiro de perfil com o arguments nome em <process_name>.profile~/logs/sys/node/<node_id> pasta.

Quando é que um trabalho vai parar?

se não for cancelada, a tarefa irá parar com o estado:

  • Concluído. Se todos os mini-lotes tiverem sido processados e a saída tiver sido gerada para append_row o modo.
  • Falha. Se error_threshold a entrada Parameters for ParallelRunConfig for excedida, ou ocorreu um erro de sistema durante a tarefa.

Onde encontrar a causa principal da falha?

Pode seguir a oportunidade potencial ~logs/job_result.txt para localizar a causa e o registo de erros detalhado.

A falha do nó afetará o resultado da tarefa?

Não se existirem outros nós disponíveis no cluster de computação designado. O orquestrador iniciará um novo nó como substituição e ParallelRunStep é resiliente a essa operação.

O que acontece se init a função no script de entrada falhar?

ParallelRunStep tem um mecanismo para tentar novamente durante um determinado tempo para dar oportunidade de recuperação de problemas transitórios sem atrasar a falha da tarefa por muito tempo, o mecanismo é o seguinte:

  1. Se após o início de um nó, init em todos os agentes continuar a falhar, deixaremos de tentar após 3 * process_count_per_node falhas.
  2. Se após o início do trabalho, init em todos os agentes de todos os nós continuar a falhar, deixaremos de tentar se a tarefa for executada durante mais de 2 minutos e houver 2 * node_count * process_count_per_node falhas.
  3. Se todos os agentes estiverem bloqueados init durante mais de 3 * run_invocation_timeout + 30 segundos, a tarefa falhará devido a nenhum progresso durante demasiado tempo.

O que vai acontecer no OutOfMemory? Como posso verificar a causa?

ParallelRunStep definirá a tentativa atual de processar o estado de falha do mini-lote e tentará reiniciar o processo falhado. Pode verificar ~logs/perf/<node_id> se encontra o processo que consome memória.

Por que motivo tenho muitos ficheiros processNNN?

ParallelRunStep iniciará novos processos de trabalho em substituição dos que saíram anormalmente e cada processo irá gerar um processNNN ficheiro como registo. No entanto, se o processo falhou devido à exceção durante a init função do script de utilizador e se o erro se repetiu continuamente por 3 * process_count_per_node vezes, não será iniciado nenhum novo processo de trabalho.

Passos seguintes