Configuração de RDMA na prática + conceção do uplink do cluster

Os artigos anteriores da linha N defenderam o RDMA (N02) e percorreram as escolhas de topologia (N04, N05). Esta é a parte prática: instalar os controladores, provar que o caminho funciona, ativar o GPUDirect, validar o NCCL e depois subir um nível para pensar em como todo o cluster se liga ao mundo.

Partimos de Ubuntu 22.04 ou 24.04, placas de rede Mellanox/NVIDIA ConnectX-5 ou ConnectX-6 e InfiniBand HDR/NDR ou RoCEv2 sobre uma malha Ethernet sem perdas. Os comandos são os que escrevemos mesmo nas bancadas de teste da Kentino antes de expedir um cluster Kentino AI de 4 nós.

Controladores: MLNX_OFED ou o rdma-core upstream?

Via O que obtém Quando a escolher
MLNX_OFED (agora NVIDIA DOCA-OFED) Pacote de controladores testado pela NVIDIA, peermem do GPUDirect, perftest, mlxconfig Clusters de IA em produção com ConnectX-6/7 e GPUDirect
Upstream rdma-core + nativo do kernel mlx5 O que o Ubuntu inclui, sem repositórios extra Máquinas de laboratório, nó único, sem GPUDirect, sem ferramentas de firmware

Para tudo o que transporte tráfego NCCL em produção, instale o MLNX_OFED. O mlx5 upstream funciona, mas perde o mlxconfig, o perftest incluído e — sobretudo — um nvidia-peermem do lado do kernel testado com a mesma árvore OFED.

Uma instalação limpa em Ubuntu 22.04 com o kernel 5.15.x:

tar xf MLNX_OFED_LINUX-*.tgz && cd MLNX_OFED_LINUX-*
sudo ./mlnxofedinstall --add-kernel-support --with-nvmf --force
sudo /etc/init.d/openibd restart && sudo systemctl enable openibd

A flag --add-kernel-support é importante. Se a omitir num kernel fora da matriz OFED, a compilação DKMS falha silenciosamente — e acaba a correr o mlx5 de base sem o saber. Confirme a pilha em espaço de utilizador com dpkg -l | grep -E 'libibverbs|rdma-core|mlnx-ofed'.

Pôr a ligação ativa e confirmar que a NIC vê a malha

Três comandos dizem-lhe tudo o que é importante:

sudo mst start && mst status   # firmware tools
ibstat                          # port state, width, speed
ibv_devinfo -v                  # GIDs, max_qp, MTU, hw revision

Uma porta saudável mostra State: Active, Physical state: LinkUp, a Rate: esperada (por ex. 200) e a Link layer: correta (InfiniBand ou Ethernet). Os dois campos que escapam às pessoas:

  • Link layer: InfiniBand vs Ethernet. Uma ConnectX de modo duplo comuta com mlxconfig -d /dev/mst/mt4125_pciconf0 set LINK_TYPE_P1=2 (1=IB, 2=Ethernet). É preciso reiniciar.
  • Rate: corresponde à velocidade esperada. Uma porta NDR de 200 Gb/s que arrancou a 100 Gb/s é a falha silenciosa mais comum: cabo defeituoso, comprimento de DAC errado ou porta do switch forçada a uma velocidade inferior. Verifique antes de fazer benchmarks.

Em RoCE, confirme também que está selecionada a v2 (a v1 usa o ethertype 0x8915; a v2 usa UDP/4791 e é o que todas as pilhas modernas utilizam): sudo cma_roce_mode -d mlx5_0 -p 1 -m 2.

Gestor de sub-rede (apenas InfiniBand)

InfiniBand não é Ethernet — nada encaminha na malha enquanto um gestor de sub-rede (SM) não atribuir LID. Numa malha de laboratório, sudo apt install opensm && sudo systemctl enable --now opensm num dos nós. Em produção, use o SM embutido no switch. Dois SM de software a competir entre si são uma tarde de depuração que ninguém precisa. O RoCE não tem SM — o encaminhamento é tarefa da malha Ethernet, razão pela qual a configuração de RoCE é sobretudo uma questão de QoS no switch e não no anfitrião.

Provar que o RDMA funciona mesmo: perftest

Antes de qualquer execução de NCCL ou de uma framework, prove o cabo com o perftest. Dois nós, primeiro o servidor:

# server (node A)        # client (node B)
ib_send_bw -d mlx5_0 -F --report_gbits -D 10
ib_send_bw -d mlx5_0 -F --report_gbits -D 10 10.10.1.1

Números esperados numa malha em condições:

Ligação ib_send_bw (mensagem grande) ib_send_lat (2 bytes)
100 Gb/s EDR / 100 GbE RoCE 95–98 Gb/s 1.0–1.5 µs
200 Gb/s HDR / 200 GbE RoCE 188–197 Gb/s 0.9–1.3 µs
400 Gb/s NDR 370–395 Gb/s 0.8–1.1 µs

Se estiver 20% abaixo destes números, não comece a afinar o NCCL. A malha é que está mal. Verifique por esta ordem: (1) MTU, (2) PFC em RoCE, (3) o par cabo/transcetor, (4) geração e número de pistas PCIe da NIC, (5) colocação NUMA. Latências abaixo do microssegundo são atingíveis em NDR dentro do mesmo rack; qualquer valor acima de 5 µs numa malha RoCE com um só switch está avariado.

GPUDirect RDMA: ativar o caminho de DMA

Todo o sentido do RDMA num cluster de IA está em a NIC ler e escrever diretamente na memória da GPU, contornando o anfitrião. Para isso é preciso o nvidia-peermem (ou, em kernels mais recentes, DMA-BUF — a NVIDIA recomenda agora DMA-BUF onde o kernel o suporte, mas a maioria das pilhas em produção continua a usar o peermem).

sudo modprobe nvidia-peermem
lsmod | grep nvidia_peermem
echo nvidia-peermem | sudo tee /etc/modules-load.d/nvidia-peermem.conf

Se não carregar, o kernel não foi compilado contra uma API de memória de pares RDMA compatível com OFED. A ordem de instalação conta: primeiro OFED, depois o controlador NVIDIA e só então o nvidia-peermem — o peermem é compilado contra os cabeçalhos do OFED no momento da instalação.

Prove o caminho ponta a ponta com uma escrita RDMA de GPU para GPU:

# server                                # client
ib_write_bw -d mlx5_0 --use_cuda=0 -F --report_gbits -D 10
ib_write_bw -d mlx5_0 --use_cuda=0 -F --report_gbits -D 10 10.10.1.1

--use_cuda=0 regista a memória CUDA da GPU 0 como buffer de RDMA. Se o resultado ficar a poucos pontos percentuais do caso com memória do anfitrião, o GPUDirect está a funcionar. Se for 5× mais lento, o caminho está a passar pela memória do anfitrião — normalmente um problema no carregamento do peermem ou uma topologia PCIe em que a NIC e a GPU ficam em nós NUMA opostos.

MTU e PFC em RoCE (é aqui que os clusters RoCE vivem ou morrem)

O RoCEv2 sobre uma malha Ethernet sem perdas precisa de três coisas a funcionar em conjunto:

  1. Uma MTU grande de ponta a ponta. Defina 9000 em todas as NIC, portas de switch e saltos de router. O RoCE escolhe a maior MTU ao estilo IB que caiba dentro da MTU Ethernet — 9000 em Ethernet dá ao RoCE uma MTU de 4096 bytes, que é o que se pretende.
  2. PFC na prioridade do RDMA. Pausa ao nível da ligação que evita descartes numa classe de tráfego designada. Prática corrente: RDMA na prioridade 3, tudo o resto na prioridade 0.
  3. Marcação ECN nos switches e nas NIC. O ECN é o sinal de congestão de longo prazo; o PFC é o travão de emergência de curto prazo. O ECN faz o trabalho a maior parte do tempo; o PFC só dispara quando o ECN não consegue acompanhar.

Do lado do anfitrião, com o mlnx_qos:

sudo mlnx_qos -i enp1s0f0 --pfc 0,0,0,1,0,0,0,0   # PFC on prio 3
sudo mlnx_qos -i enp1s0f0 --trust dscp
echo 106 | sudo tee /sys/class/infiniband/mlx5_0/tc/1/traffic_class

O valor DSCP (26) e a prioridade PFC (3) têm de coincidir em todos os saltos. A malha de switches tem de espelhar isto: PFC ativo na prioridade 3, marcação ECN nessas filas, configuração de buffers sem perdas e margem por porta dimensionada para o BDP da ligação mais longa.

Comprar um switch a um fabricante com modelos de configuração RoCE documentados (NVIDIA Spectrum, Arista, Cisco Nexus 9000) poupa uma semana. Fazer a configuração de PFC à mão num whitebox Broadcom genérico é possível, mas é um projeto. Já o fizemos. Não o recomendamos.

O DCQCN (Data Center Quantized Congestion Notification) é a malha de controlo que liga o PFC e o ECN: o ECN marca pacotes quando uma fila enche, o recetor devolve um CNP, o emissor abranda e volta a acelerar quando a fila esvazia. O PFC é o recurso de último grau quando o DCQCN não consegue reagir a tempo. No firmware moderno das ConnectX-6/7 está ativo por omissão e, entre 4 e 16 nós, os valores predefinidos chegam. O jogo da afinação (alpha, taxa-alvo, limiares de bytes/temporizadores) é para quem opera a escalas em que 0.5% no allreduce compensa duas semanas de trabalho.

NCCL: as variáveis que contam

O NCCL é a camada que usa RDMA para PyTorch, JAX, DeepSpeed, vLLM com paralelismo de tensores e afins. Deteta tudo automaticamente e quase sempre bem. Quatro variáveis de ambiente aparecem em todos os scripts de arranque em produção:

Variável O que faz Quando a definir
NCCL_IB_DISABLE 1 força sockets TCP em vez de IB/RoCE Apenas para depuração
NCCL_SOCKET_IFNAME Interface para o arranque do NCCL (não para o caminho de dados) Sempre — aponte para a NIC de gestão, para que o arranque não vá parar à malha RDMA
NCCL_IB_HCA Que HCA o NCCL usa no plano de dados Nós com várias NIC — explícito é melhor do que automático
NCCL_NET_GDR_LEVEL Com que agressividade usar GPUDirect RDMA em função da topologia PCIe PIX/PHB quando as GPU e as NIC partilham um switch PCIe / nó NUMA

Um arranque funcional num cluster de 4 nós, com 4 GPU por nó:

export NCCL_SOCKET_IFNAME=eno1            # 1 GbE management network
export NCCL_IB_HCA=mlx5_0,mlx5_1          # both RDMA NICs
export NCCL_IB_GID_INDEX=3                # RoCE v2 GID
export NCCL_NET_GDR_LEVEL=PHB
export NCCL_DEBUG=INFO                    # one-shot, then drop to WARN

mpirun -np 16 -N 4 --hostfile hosts -x NCCL_SOCKET_IFNAME \
    -x NCCL_IB_HCA -x NCCL_IB_GID_INDEX -x NCCL_NET_GDR_LEVEL \
    -x NCCL_DEBUG ./build/all_reduce_perf -b 8 -e 8G -f 2 -g 1

A saída de NCCL_DEBUG=INFO é leitura obrigatória na primeira execução. Diz-lhe que transporte o NCCL escolheu (Channel ... via NET/IB/0 GDR) por rank e por canal. Se vir via NET/Socket em qualquer ponto, o caminho RDMA não está a ser usado — e não testou aquilo que julga ter testado.

Validar com o nccl-tests

nccl-tests valida a pilha inteira — controlador, OFED, peermem, NCCL, rede — de ponta a ponta. O número que importa é a largura de banda de barramento (busbw), e não a largura de banda do algoritmo (algbw). A largura de banda de barramento normaliza em função do tamanho do anel/árvore e é a que se compara com a velocidade de linha da NIC.

Dimensão do cluster busbw esperada em allreduce (mensagem grande)
1 nó, 4 GPU por NVLink 200–400 GB/s
1 nó, 8 GPU por NVLink 250–500 GB/s
2 nós, 4 GPU cada, RDMA a 200 GbE 20–24 GB/s
4 nós, 4 GPU cada, RDMA a 200 GbE + GDR 20–22 GB/s

O allreduce entre nós fica limitado a cerca de metade da velocidade de linha da NIC (o allreduce envia e recebe cada byte uma vez por rank). 200 Gb/s ≈ um teto de 25 GB/s; observar 22 GB/s é saudável. Se o número cair 5× ao passar de 1 para 2 nós, o RDMA não está a ser usado no salto entre nós. Leia a saída de NCCL_DEBUG=INFO.

Agora afaste o zoom: o projeto do uplink do cluster

Tudo o que ficou para trás diz respeito ao plano de dados — a malha que as GPU usam para falar entre si. A outra metade de um cluster útil é o uplink: a forma como esta malha se liga ao mundo exterior. É constante as pessoas construírem o uplink errado.

Um cluster de treino Kentino AI de 4 nós tem três relações com o exterior:

  1. A rede corporativa / WAN — registos de modelos, armazenamento de conjuntos de dados (S3, NFS, MinIO), Git, registos de contentores, telemetria.
  2. As estações de trabalho dos programadores — engenheiros a entrar por SSH, a lançar trabalhos, a copiar checkpoints para fora, a usar o Jupyter.
  3. Outros clusters — um segundo cluster de treino, um cluster de inferência, um cluster de CI/avaliação.

As contas da largura de banda

Se 4 nós fizerem allreduce a ~22 GB/s cada, a malha interna move cerca de 700 Gb/s agregados no sentido este-oeste. O uplink não tem de igualar isso. Tem de igualar a taxa de ingestão de dados:

  • 4 nós × 4 GPU × ~1 GB/s por GPU (modelo de imagem/vídeo) = 16 GB/s ≈ 128 Gb/s de leitura contínua a partir do armazenamento de objetos.
  • O pré-treino de LLM sobre texto tokenizado é muito menor — 1–4 Gb/s, porque os tokens são densos e um lote dura bastante tempo.
  • Um fine-tune que recarrega checkpoints com frequência atinge picos de 40 Gb/s durante alguns segundos e depois cai para quase zero.
Carga de trabalho Ingestão contínua Pico Uplink
Pré-treino de LLM (texto tokenizado) 1–4 Gb/s 20 Gb/s 25 GbE
Treino de modelos de imagem/vídeo 50–150 Gb/s 200 Gb/s LAG de 2× 100 GbE ou 1× 200 GbE
Fine-tuning / RLHF com rotação de checkpoints 5–20 Gb/s 50 Gb/s 25–100 GbE
Cluster de inferência atrás de um balanceador de carga 5–50 Gb/s depende do modelo 25–100 GbE

Erro comum: gastar 40 mil € num uplink de 400 GbE só porque a malha interna é de 400 GbE. Alvo errado. O alvo certo é a taxa de leitura do conjunto de dados. Já construímos clusters de 4 nós com um uplink de 25 GbE que trabalharam a fundo durante semanas com dados de tokens de LLM.

Largura de banda agregada vs atribuída

Um nó com quatro NIC de 100 GbE tem 400 Gb/s agregados de capacidade de linha. Essa capacidade é atribuída por fluxo, não é um reservatório comum. Uma única ligação TCP entre dois IP usa uma NIC — no máximo 100 Gb/s. O ECMP e o hashing por fluxo distribuem fluxos diferentes pelas quatro NIC.

  • O allreduce são muitos fluxos em paralelo — um por canal e por par. O NCCL distribui-se nativamente por várias NIC (NCCL_IB_HCA com ambas listadas). Para o NCCL, 4× 100 GbE fica funcionalmente perto de 400 Gb/s.
  • Um único fluxo de conjunto de dados (um GET HTTP a puxar um shard de 1 TB) é um só fluxo. 4× 100 GbE dão-lhe 100 Gb/s, não 400. Para aproveitar o agregado, o dataloader tem de abrir fluxos em paralelo — e é isso que o DALI, o WebDataset e o Streaming da MosaicML fazem por conceção.

Separação das malhas

Plano de dados
  • RoCE a 100/200/400 GbE ou InfiniBand HDR/NDR
  • NCCL, leitura de conjuntos de dados, escrita de checkpoints
  • Sem perdas, PFC/ECN, switches dedicados
  • Estéril — sem tráfego alheio à IA
Plano de gestão
  • 1 GbE ou 10 GbE
  • SSH, Prometheus, NTP, syslog, IPMI/BMC
  • Switches baratos, L2/L3 normal, sem QoS especial
  • Funciona sempre, mesmo com a malha de dados em baixo

Tenha sempre duas malhas. O plano de gestão não pode depender do plano de dados para funcionar — precisa de acesso SSH justamente quando a malha RDMA está avariada.

O plano de gestão não é opcional. Quando a malha de dados falha — transcetor defeituoso, PFC mal configurado, switch em baixo —, precisa de um caminho SSH que não dependa da malha avariada. Depurar uma tempestade RoCE pela ligação que está em tempestade é um erro que se comete exatamente uma vez. O IPMI/BMC e o NTP também vivem aqui; o desvio de relógio é invisível até a sua framework distribuída começar a produzir gradientes errados.

BGP unnumbered para Clos encaminhado

Acima de ~8 nós, uma topologia leaf-spine em L2 torna-se penosa — limites do spanning tree, escalabilidade da tabela MAC, tempestades de broadcast, sem multi-caminho nativo. A resposta moderna é um Clos encaminhado em L3: todas as ligações leaf-spine são unnumbered, o BGP transporta as rotas e o ECMP distribui os fluxos pelos spines.

O BGP unnumbered estabelece sessões sobre endereços IPv6 link-local atribuídos automaticamente pelo kernel, pelo que dispensa por completo a contabilidade de /31 por ligação. Um leaf a correr FRRouting em Linux fica mais ou menos assim:

router bgp 65001
 neighbor swp1 interface remote-as external
 neighbor swp2 interface remote-as external
 address-family ipv4 unicast
  network 10.1.1.0/24
  redistribute connected

Cada leaf e cada spine é o seu próprio AS, o eBGP anuncia os loopbacks e o ECMP entre spines sai de graça. A RFC 7938 documenta o padrão; Cumulus/NVIDIA, Arista, Cisco e Juniper suportam hoje BGP unnumbered. Com 4 nós, um único switch chega. Com 8–16 nós e dois spines, o Clos encaminhado começa a compensar. Acima de 16 nós, é a única resposta sensata.

Ligar a outros clusters

Não coloque um cluster de treino e um cluster de inferência na mesma malha RDMA. O treino são allreduce enormes e em rajada; a inferência são mensagens pequenas em regime estacionário; os requisitos de QoS divergem; e um treino mal comportado deixa o caminho de inferência à fome. Duas malhas separadas a encontrarem-se num router L3 com encaminhamento IP normal é a resposta certa. O tráfego de controlo entre clusters — filas de trabalhos, envio de registos, transferência de artefactos — segue pelo plano de gestão ou por uma ligação Ethernet dedicada entre clusters. Um ficheiro de pesos de 70B tem ~140 GB; um checkpoint tem o dobro. A 25 GbE, demora ~90 segundos; a 100 GbE, ~22 segundos. Planeie pelo valor mais alto.

O que fazer a seguir

Um fluxo de trabalho razoável para pôr o RDMA de pé num cluster novo:

  1. Primeiro cable tudo e verifique a camada de ligação. Execute o ibstat em todos os nós. Mesma velocidade, mesma MTU, mesma camada de ligação. Resolva a camada física antes de mexer em software.
  2. Instale o MLNX_OFED, não apenas o rdma-core, se o GPUDirect ou o NCCL fizerem parte do plano. Faça corresponder a compilação do OFED ao kernel em execução.
  3. Execute o perftest entre todos os pares de nós antes de tocar nas frameworks. Números a menos de 5% do teórico = saudável. Qualquer coisa abaixo = pare e corrija.
  4. Carregue o nvidia-peermem e prove-o com o ib_write_bw --use_cuda=0. O resultado deve igualar o caso com memória do anfitrião.
  5. Configure PFC, ECN e DCQCN se estiver em RoCE. Em IB este passo não existe; é essa metade da razão por que as pessoas escolhem IB.
  6. Execute o nccl-tests em allreduce com 2 nós, depois 4, depois 8. Inspecione a saída de NCCL_DEBUG=INFO na primeira execução de cada dimensão de cluster. Confirme que NET/IB ... GDR aparece.
  7. Separe as malhas. A gestão em 1/10 GbE baratos. Os dados na malha RDMA. Não misture.
  8. Dimensione o uplink para a sua taxa de ingestão de dados, não para a velocidade da malha interna. A maioria dos clusters precisa de muito menos largura de banda externa do que tem.
  9. Acima de 8 nós, planeie um Clos encaminhado com BGP unnumbered. Abaixo de 8, um único switch chega.

Os artigos seguintes da linha N tratam da dissecação da latência (N06) e da complexidade de encaminhamento (N07) — é aí que vivem a afinação do DCQCN e as patologias do hashing ECMP. A linha K continua a partir daqui com treino distribuído (K02), clusters de inferência (K03) e armazenamento (K04) — todos eles a pressupor que a pilha RDMA desta página já funciona.


Isto faz parte da Kentino Wiki, uma série de referência sobre computação de IA e os sistemas que a ligam. Correções são bem-vindas em info@kentino.com.