Práticas recomendadas para executar aplicativos HPC altamente associados no Compute Engine

Este documento fornece as práticas recomendadas para ajustar os recursos do Google Cloud para ter o desempenho ideal da interface de transmissão de mensagens (MPI, na sigla em inglês). As cargas de trabalho de computação de alto desempenho (HPC, na sigla em inglês) costumam usar MPI para se comunicar entre processos e instâncias. O ajuste adequado dos sistemas subjacentes e da infraestrutura de rede é essencial para o desempenho ideal da MPI. Se você executar código baseado em MPI no Google Cloud, use essas práticas para conseguir o melhor desempenho possível.

Pressuposições e requisitos

Normalmente, programadores de carga de trabalho como o Slurm ou o HTCondor são usados para gerenciar instâncias. As recomendações e práticas recomendadas neste documento se aplicam a todos os programadores e gerentes de fluxo de trabalho.

A implementação dessas práticas recomendadas usando os vários programadores ou ferramentas de fluxo de trabalho está além do escopo deste documento. Outros documentos e tutoriais fornecem ferramentas para implementação e diretrizes para essas ferramentas.

As diretrizes deste documento são gerais e podem não beneficiar todos os aplicativos. Recomendamos fazer a comparação dos seus aplicativos para encontrar a configuração mais eficiente ou econômica.

Configuração do Compute Engine

Esta seção inclui as práticas recomendadas para conseguir o melhor desempenho de computação para seu aplicativo. Usar o tipo de máquina e as configurações corretas no sistema pode ter um impacto significativo no desempenho da MPI.

Usar uma política de colocação compacta

A política de colocação permite controlar a colocação das máquinas virtuais (VMs, na sigla em inglês) em data centers. A política de veiculação compacta fornece topologias de menor latência para veiculação de VM em uma única zona de disponibilidade. As APIs atuais permitem criar até 22 VMs otimizadas para computação (C2) que estão fisicamente próximas umas das outras. Se você precisar de mais de 22 instâncias C2 (mais de 660 núcleos físicos), divida suas instâncias em várias políticas de posicionamento. Recomendamos usar o número mínimo de políticas de canal que acomodar sua carga de trabalho.

Para usar políticas de posicionamento, primeiro crie uma política de colocação colocalizada com o número necessário de VMs em uma determinada região:

gcloud compute resource-policies create group-placement \
    PLACEMENT_POLICY_NAME --collocation=collocated \
    --vm-count=NUMBER_OF_VMS

Em seguida, crie instâncias usando a política na zona obrigatória:

gcloud compute instances create instance-1 \
    instance-2...instance-n --zone=us-central1-a \
    --resource-policies=PLACEMENT_POLICY_NAME \
    --maintenance-policy=TERMINATE ...

Em alguns casos, talvez você não tenha controle direto sobre como as VMs são criadas. Por exemplo, as VMs podem ser criadas por meio do uso de algumas ferramentas de terceiros não integradas. Para aplicar uma política de veiculação a VMs existentes, primeiro interrompa as VMs e execute o seguinte comando:

gcloud compute instances add-resource-policies \
    instance-1 instance-2...instance-n --zone=us-central1-a \
    --resource-policies=PLACEMENT_POLICY_NAME

Usar instâncias com otimização de computação

Recomendamos o uso de instâncias C2 para executar aplicativos HPC. Essas instâncias têm mapeamento fixo de núcleo virtual para físico e expõem a arquitetura de célulaNUMA ao sistema operacional convidado, ambos críticos para o desempenho de aplicativos HPC estritamente associados.

As instâncias C2 têm até 60 vCPUS (30 núcleos físicos) e 240 GB de RAM. Eles também podem ter até 3 TB de armazenamento SSD local e são compatíveis com até 32 Gbps de capacidade de rede. As instâncias C2 também utilizam processadores escalonáveis Intel Xeon (segunda geração) (Cascade Lake) que oferecem mais largura de banda de memória e uma velocidade de relógio maior (até 3,8 GHz) em comparação com outros tipos de instância. As instâncias do C2 normalmente oferecem até 40% de melhoria no desempenho em comparação com os tipos de instância N1.

Para reduzir a sobrecarga de comunicação entre máquinas, recomendamos consolidar sua carga de trabalho em um número menor de VMs C2-standard-60 (com a mesma contagem total de núcleos) em vez de iniciar um número maior de VMs C2 menores.

Desativar o Hyper-Threading

Alguns aplicativos MPI têm melhor desempenho desativando o Hyper-Threading no SO convidado. O Hyper-Threading aloca dois núcleos virtuais (vCPU) por núcleo físico no nó. Para muitas tarefas de computação gerais ou tarefas que exigem muita E/S, o Hyper-Threading pode aumentar significativamente a capacidade do aplicativo. Para jobs vinculados a computação em que ambos os núcleos virtuais são vinculados à computação, o Hyper-Threading pode dificultar o desempenho geral do aplicativo e adicionar variação não determinística aos jobs. Desativar o Hyper-Threading permite um desempenho mais previsível e pode diminuir o tempo do job.

É possível desativar o Hyper-Threading em instâncias C2, N1-Ultramem e N2, seja on-line ou reinicializando.

Para desativar o Hyper-Threading on-line, use um script como o Manage_Hyperthread.sh, que define as CPUs ativas como off-line.

Para desativar o Hyper-Threading reinicializando, adicione o seguinte à string GRUB_CMDLINE_LINUX em /etc/default/grub, em que sNUM_CPU é o número de vCPUs na instância dividido por dois. Por exemplo, para uma instância C2-standard-60, NUM_CPU seria 30.

noht nosmt nr_cpus=NUM_CPU

Depois de alterar o arquivo grub, execute o comando a seguir para atualizar o arquivo de configuração do sistema GRUB e reinicializar o sistema:

sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

Se o seu sistema tiver um modo de inicialização legado, execute o seguinte comando:

sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Para verificar se o SMP (Hyper-Threading) está desativado no sistema após a reinicialização, use o seguinte comando:

lscpu | grep -e Socket -e Core -e Thread

A saída é semelhante à seguinte, em que Thread(s) per core é 1 quando o Hyper-Threading está desativado e 2 quando o Hyper-Threading está ativado:

Thread(s) per core:    1
Core(s) per socket:    15
Socket(s):             2

Ajustar limites do usuário

Os sistemas Unix têm limites padrão em recursos do sistema, como arquivos abertos e números de processos que qualquer usuário pode usar. Esses limites impedem que um usuário monopolize os recursos do sistema e afete o trabalho de outros usuários. No contexto do HPC, no entanto, esses limites geralmente são desnecessários porque os nós de computação no cluster não são compartilhados diretamente entre os usuários.

É possível ajustar os limites do usuário editando o arquivo /etc/security/limits.conf e fazendo login no nó novamente. Para automação, é possível fazer essas alterações em uma imagem de VM ou ajustar os limites no momento da implantação usando ferramentas como o Deployment Manager, o Terraform ou o Ansible.

Ao ajustar os limites do usuário, altere os valores dos seguintes limites:

  • nproc: número máximo de processos
  • memlock: espaço máximo de endereços bloqueados na memória (KB)
  • stack: tamanho máximo da pilha (KB)
  • nofile: número máximo de arquivos abertos
  • cpu: tempo máximo de CPU (minutos)
  • rtprio: prioridade máxima em tempo real permitida para processos não privilegiados (Linux 2.6.12 e versões posteriores)

Esses limites são configurados no arquivo de configuração do sistema /etc/security/limits.conf para a maioria dos sistemas Unix e Linux, incluindo Debian, CentOS e Red Hat.

Para alterar os limites do usuário, use um editor de texto para alterar os seguintes valores:

  • No /etc/security/limits.conf:

    *            -     nproc     unlimited
    *            -     memlock   unlimited
    *            -     stack     unlimited
    *            -     nofile    1048576
    *            -     cpu       unlimited
    *            -     rtprio    unlimited
    
  • No /etc/security/limits.d/20-nproc.conf:

    *            -    nproc      unlimited
    

Configurar chaves de host SSH

O Intel MPI requer chaves de host para todos os nós de cluster no arquivo ~/.ssh/known_hosts do nó que executa mpirun. Você também precisa salvar suas chaves SSH em authorized_keys.

Para adicionar chaves de host, execute o seguinte comando:

ssh-keyscan -H 'cat HOSTFILE' >> ~/.ssh/known_hosts

Outra maneira de fazer isso é adicionar StrictHostKeyChecking=no ao arquivo ~/.ssh/config executando o seguinte:

Host *
StrictHostKeyChecking no

Armazenamento

O desempenho de muitos aplicativos HPC depende muito do desempenho do sistema de armazenamento subjacente. Isso é especialmente verdadeiro para aplicativos que leem ou gravam muitos dados ou que criam ou acessam muitos arquivos ou objetos. Isso também ocorre quando muitas classificações acessam o sistema de armazenamento simultaneamente.

Escolher um sistema de arquivos NFS ou sistema de arquivos paralelo

Veja a seguir as principais opções de armazenamento para aplicativos altamente associados. Cada opção tem o próprio custo, perfil de desempenho, APIs e semântica de consistência:

  • As soluções baseadas em NFS, como Filestore e NetApp Cloud Volumes, são as mais fáceis de implantar opções de armazenamento compartilhado. Ambas as opções são totalmente gerenciadas no Google Cloud e são melhores quando o aplicativo não tem requisitos extremos de E/S para um único conjunto de dados, e limita-se a nenhum compartilhamento de dados entre os nós de computação durante a execução e as atualizações do aplicativo. Para limites de desempenho, consulte a documentação do Filestore e do Cloud Volumes da NetApp.
  • Os sistemas de arquivos paralelos baseados em POSIX são mais usados pelos aplicativos MPI. As opções baseadas em POSIX incluem Luster de código aberto e a oferta Luster totalmente compatível, o DDN Storage EXAScaler Cloud. Quando os nós de computação geram e compartilham dados, eles frequentemente dependem do desempenho máximo fornecido pelos sistemas de arquivos paralelos e da compatibilidade com a semântica POSIX completa. Sistemas de arquivos paralelos, como o Luster, fornecem dados para os maiores supercomputadores e podem ser compatíveis com milhares de clientes. O Luster também é compatível com bibliotecas de dados e E/S, como NetCDF e HDF5, além de MPI-IO, permitindo E/S paralela para uma ampla conjunto de domínios de aplicativos.

Escolher uma infraestrutura de armazenamento

Os requisitos de desempenho de aplicativo precisam orientar a infraestrutura ou o nível de armazenamento do sistema de arquivos escolhido. Por exemplo, se você implantar SSDs para aplicativos que não precisam de operações de E/S altas por segundo (IOPS, na sigla em inglês), poderá aumentar os custos sem muito benefício.

Os serviços de armazenamento gerenciado Filestore e NetApp Cloud Volumes oferecem vários níveis de desempenho que escalonam com base na capacidade.

Para determinar a infraestrutura correta para o Lustre de código aberto ou DDN Storage EXAScaler Cloud, é preciso entender a vCPU e a capacidade que é necessário para alcançar o desempenho necessário com o disco permanente padrão, o disco permanente SSD ou o SSD local. Para mais informações sobre como determinar a infraestrutura correta, consulte Informações de desempenho do armazenamento em blocos e Como otimizar o desempenho do disco permanente. Por exemplo, se você usa o Luster, pode implantar soluções de baixo custo e alta largura de banda usando o disco permanente SSD para o servidor de metadados (MDS) e o disco permanente padrão para os servidores de armazenamento (OSSs).

Configurações de rede

O desempenho da rede MPI é fundamental para muitos aplicativos HPC. Isso é especialmente verdadeiro para aplicativos altamente associados em que processos MPI em diferentes nós se comunicam com frequência ou com grande volume de dados. Nesta seção, você encontra as práticas recomendadas para ajustar as configurações de rede e otimizar o desempenho de MPI.

Aumentar as configurações de tcp_*mem

As máquinas C2 são compatíveis com até 32 Gbps de largura de banda, o que requer memória TCP maior do que as configurações padrão do Linux. Para melhorar o desempenho da rede, aumente o valor tcp_mem.

Para aumentar os limites de memória TCP, atualize os seguintes valores em /etc/sysctl.conf:

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216

Para carregar os novos valores em /etc/sysctl.conf, execute sysctl -p.

Usar o perfil de latência de rede

Melhore o desempenho de alguns aplicativos ativando a pesquisa ocupada. A pesquisa ocupada ajuda a reduzir a latência no caminho de recebimento da rede, permitindo que o código da camada de soquete pesquise a fila de recebimento de um dispositivo de rede e desativando interrupções de rede. Avalie a latência do aplicativo para ver se ele ajuda muito com as pesquisas.

O perfil de latência de rede persiste entre as reinicializações. Se o sistema tiver tuned-adm instalado, o perfil de latência de rede poderá ser ativado executando o seguinte comando no CentOS:

tuned-adm profile network-latency

Se o sistema não tiver o tuned-adm instalado, ative a pesquisa ocupada adicionando o seguinte a /etc/sysctl.conf:

net.core.busy_poll = 50
net.core.busy_read = 50

Para carregar os novos valores em /etc/sysctl.conf, execute sysctl -p.

Bibliotecas MPI e aplicativos do usuário

As configurações da biblioteca MPI e as configurações do aplicativo HPC podem afetar o desempenho do aplicativo. Para alcançar o melhor desempenho em aplicativos HPC, é importante ajustar essas configurações. Nesta seção, você encontra as práticas recomendadas para ajustar as bibliotecas de MPI e os aplicativos de usuário para otimizar o desempenho da MPI.

Usar Intel MPI

Para um melhor desempenho, recomendamos que você use a Intel MPI.

Para versões Intel MPI 2018, especifique os componentes de comunicação subjacentes para usar TCP em vez do provedor de memória compartilhada ao executar no Google Cloud:

mpirun -hostfile HOSTFILE -np NUM_PROCESSES \
    -ppn PROCESSES_PER_NODE -genv I_MPI_FABRICS "shm:tcp" APPLICATION

Para as versões Intel MPI 2019+, especifique os tecidos de comunicação subjacentes para usar o TCP sobre o provedor RxM OFI para melhorar o desempenho:

mpirun -hostfile HOSTFILE -np NUM_PROCESSES \
    -ppn PROCESSES_PER_NODE -genv I_MPI_FABRICS "ofi_rxm;tcp" APPLICATION

Usar mpitune para fazer o ajuste coletivo de MPI

As implementações de MPI, como Intel MPI e OpenMPI, têm muitos parâmetros de configuração internos que podem afetar o desempenho de comunicação. Esses parâmetros são especialmente relevantes para a comunicação coletiva de MPI, que permite especificar algoritmos e parâmetros de configuração que podem ter um desempenho muito diferente no ambiente do Google Cloud. Recomendamos que você ajuste os parâmetros de configuração com base nas características dos seus aplicativos.

Para especificar manualmente os algoritmos e os parâmetros de configuração da comunicação coletiva MPI, recomendamos o uso de mpitune. Para executar um comando mpitune, é preciso ter acesso de gravação ao diretório ou executar o comando como raiz.

Ao usar mpitune, você ajusta para cada combinação do número de VMs e do número de processos por VM. Por exemplo, se você estiver usando as versões Intel MPI 2018, poderá ajustar para 22 VMs e 30 processos por VM executando o seguinte:

mpitune -hf HOSTFILE -fl 'shm:tcp' -pr 30:30 -hr 22:22

O comando mpitune anterior gera um arquivo de configuração no diretório Intel MPI que pode ser usado posteriormente para executar aplicativos.

Para usar a configuração de ajuste para um aplicativo, adicione a opção -tune ao comando mpirun:

mpirun -tune -hostfile HOSTFILE -genv I_MPI_FABRICS 'shm:tcp' -np 660 -ppn 30 ./app

Para ativar o ajuste, forneça explicitamente a variável de ambiente I_MPI_FABRICS, e o número de nós por VM e o número de processos por nó precisam corresponder aos valores usados durante o ajuste.

Encontre os arquivos de ajuste gerados no diretório de instalação da Intel MPI em intel/mpi/2018.1/etc64.

Os nomes de arquivo codificam o dispositivo, o número de nós e outras informações, como estas:

mpiexec_shm-tcp_nn_22_np_660_ppn_30.conf

É possível reutilizar o arquivo em um conjunto diferente de VMs com configurações semelhantes copiando o arquivo para o diretório correspondente e adicionando a opção -tune ao comando mpirun. Também é possível adicionar o arquivo de configuração como um argumento após o parâmetro -tune.

Usar o modo híbrido MPI OpenMP

Muitos aplicativos MPI são compatíveis com um modo híbrido que pode ser usado para ativar o OpenMP em aplicativos MPI. No modo híbrido, cada processo de MPI pode usar um número fixo de linhas de execução para acelerar a execução de determinadas estruturas de loop.

Recomendamos que você explore a opção de modo híbrido quando quiser otimizar o desempenho do aplicativo. Usar o modo híbrido pode resultar em menos processos MPI em cada VM, levando a menos comunicação entre processos e menor tempo de comunicação geral.

A ativação do modo híbrido ou do OpenMP depende do aplicativo. Em muitos casos, é possível ativar o modo híbrido definindo a variável de ambiente a seguir:

export OMP_NUM_THREADS=NUM_THREADS

Quando você usar essa abordagem híbrida, recomendamos que o número total de threads não exceda o número de núcleos físicos na VM. As VMs C2-standard-60 têm dois soquetes NUMA de 15 núcleos e 30 vCPUs cada. Recomendamos que você não tenha nenhum processo MPI com linhas de execução OpenMP abrangendo vários nós NUMA.

Compilar aplicativos usando instruções vetoriais e a biblioteca de kernel matemático

As VMs C2 são compatíveis com as instruções vetoriais AVX2, AVX512. Melhore o desempenho de muitos aplicativos HPC compilando-os com as instruções do AVX. Alguns aplicativos têm melhor desempenho se você usar o AVX2 em vez do AVX512. Recomendamos que você tente as duas instruções do AVX para sua carga de trabalho. Para um melhor desempenho da computação científica, também recomendamos o uso da Biblioteca de Kernel Intel (Intel MXL).

Usar a numeração de CPU apropriada

O C2-standard-60 tem dois soquetes NUMA, e as CPUs são numeradas em relação aos nós NUMA da seguinte maneira:

NUMA node0 CPU(s):     0-14,30-44
NUMA node1 CPU(s):     15-29,45-59

O diagrama a seguir ilustra a atribuição de números de CPU a cada uma das CPUs nos nós NUMA de uma instância C2-standard-60. O soquete 0 corresponde a NUMA node0 com CPUs de 0 a 4 e 30-44. O soquete 1 corresponde ao node1 do NUMA com as CPUs 15-29 e 45-59.

Numeração de núcleo virtual para nós C2-standard-60.

Os filhos de hiper-thread que são mapeados para um único núcleo na VM são (0,30)(1,31)..(29,59).

A Intel MPI usa os números de CPU NUMA para fixar o processador de jobs MPI. Se você quiser usar um único hyper-thread por núcleo em todos os nós que são consistentes entre as execuções, use os números de CPU de 0 a 29.

A MPI aberta usa números de CPU lógicos, conforme informado pela Localidade de hardware portátil (hwloc). Se você usar o Open MPI, os números de hiper-thread serão numerados consecutivamente da seguinte maneira:

  • Socket 0: 0 (core 0 HT 0), 1 (core 0 HT 1), 2 (core 1 HT 0) ,...,28 (core 14 HT 0), 29 (core 14, HT 1)

  • Socket 1: 30 (core 0 HT 0), 31 (core 0 HT 1), 2 (core 1 Hz 0) ,...,58 (core 14 HT 0), 59 (core 14, HT 1)

A saída será assim:

lstopo-no-graphics
Machine (240GB total)
  NUMANode L#0 (P#0 120GB) + Package L#0 + L3 L#0 (25MB)
    L2 L#0 (1024KB) + L1d L#0 (32KB) + L1i L#0 (32KB) + Core L#0
      PU L#0 (P#0)
      PU L#1 (P#30)
    L2 L#1 (1024KB) + L1d L#1 (32KB) + L1i L#1 (32KB) + Core L#1
      PU L#2 (P#1)
      PU L#3 (P#31)

Quando você usa o OpenMPI, é possível utilizar um único hiperprocessamento por núcleo em todos os nós consistentes entre as execuções usando os números de CPU 0,2,4,..58. Para forçar o MPI a fixar um processo em um núcleo, use a opção --bind-to core ao executar openMPI e valide a vinculação correta usando a opção --report-bindings.

Configurações de segurança

Melhore o desempenho da MPI desativando alguns recursos de segurança integrados do Linux. Há benefícios variados em desativar cada um desses recursos. Se você tem certeza de que seus sistemas estão bem protegidos, avalie a possibilidade de desativar os recursos de segurança a seguir.

Desativar firewalls do Linux

Para imagens do Google Cloud CentOS Linux, o firewall é ativado por padrão. Para desativar o firewall, interrompa e desative o daemon firewalld executando os seguintes comandos:

sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo systemctl mask --now firewalld

Desativar o SELinux

O SELinux no CentOS é ativado por padrão. Para desativar o SELinux, edite o arquivo /etc/selinux/config e substitua a linha SELINUX=enforcing ou SELINUX=permissive por SELINUX=disabled.

É necessário reinicializar para que essa alteração entre em vigor.

Desativar a mitigação de Meltdown e Spectre

Os seguintes patches de segurança são ativados por padrão nos sistemas Linux:

  • Variante 1, Spectre: CVE-2017-5753
  • Variante 2, Spectre: CVE-2017-5715
  • Variante 3, EBlindown: CVE-2017-5754
  • Variante 4, Ignorar armazenamento especulativo: CVE-2018-3639

As vulnerabilidades de segurança descritas nessas CVEs podem ser encontradas em microprocessadores modernos, incluindo os processadores implantados no Google Cloud. É possível desativar uma ou mais dessas mitigações e incorrer nos riscos de segurança associados, usando a linha de comando do kernel na inicialização (persistida durante as reinicializações) ou usando debugfs no ambiente de execução (não persiste na reinicialização).

Para desativar permanentemente as mitigações de segurança anteriores, siga estas etapas:

  1. Modifique o arquivo /etc/default/grub:

    sudo sed -i 's/^GRUB_CMDLINE_LINUX=\"\(.*\)\"/GRUB_CMDLINE_LINUX=\"\1 spectre_v2=off nopti spec_store_bypass_disable=off\"/' /etc/default/grub
    
  2. Depois de alterar o arquivo grub, execute o comando a seguir para atualizar o arquivo de configuração do sistema GRUB e reinicializar o sistema:

    sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
    

    Se o seu sistema tiver um modo de inicialização legado, execute o seguinte comando:

    sudo grub2-mkconfig -o /boot/grub2/grub.cfg
    
  3. Reiniciar.

Se um sistema já estiver em execução, desative as mitigações de segurança anteriores executando os seguintes comandos. Isso não persiste nas reinicializações.

echo 0 > /sys/kernel/debug/x86/pti_enabled
echo 0 > /sys/kernel/debug/x86/retp_enabled
echo 0 > /sys/kernel/debug/x86/ibrs_enabled
echo 0 > /sys/kernel/debug/x86/ssbd_enabled

Para informações sobre como diferentes mitigações podem afetar seus sistemas e como controlá-los, consulte a documentação do Red Hat para Como controlar o impacto no desempenho de microcódigo e patches de segurança e Ataques ao canal do kernel usando o armazenamento especulativo de armazenamento.

Para encontrar as vulnerabilidades afetadas de uma CPU, execute o seguinte:

grep . /sys/devices/system/cpu/vulnerabilities/*

Para descobrir quais mitigações estão ativadas, execute o seguinte:

grep . /sys/kernel/debug/x86/*_enabled

Lista de verificação resumida

A tabela a seguir resume as práticas recomendadas para o uso de MPI no Compute Engine.

Área Tarefas
Configuração do Compute Engine
Armazenamento
Configurações de rede
Bibliotecas MPI e aplicativos do usuário
Configurações de segurança

A seguir