Comunica&ccedil95o RDMA ponto a ponto para sistemas LLM

Ferramenta elegante para abordar padr&otilde95es de comunica&ccedil95o emergentes em LLM

AutoresPerplexity Research

Tradicionalmente, os sistemas de LLM dependem de comunica&ccedil5595es coletivas para padr&otilde95es paralelos estruturados em treinamento e infer&ecirc95ncia. No entanto, cargas de trabalho emergentes, como infer&ecirc95ncia desagregada, roteamento de Mixture-of-Experts (MoE) e ajuste fino ass&iacute95ncrono de aprendizado por refor&ccedil95o, exigem a flexibilidade da comunica&ccedil95o ponto a ponto.

Nosso artigo de pesquisa mais recente apresenta o TransferEngine, uma biblioteca de comunica&ccedil95o port&aacute95til baseada em RDMA que funciona em diferentes Controladores de Interface de Rede (NICs), incluindo NVIDIA ConnectX-7 e AWS Elastic Fabric Adapter (EFA). Demonstramos sua efic&aacute95cia em tr&ecirc95s sistemas de produ&ccedil95o na Perplexity:

Prefill e Decode Desagregados: Transferência de KvCache camada a camada usando gravações paginadas e observadores UVM, permitindo que as instâncias de prefill e decode escalem de forma independente.

Transferência de Pesos para Pós-Treinamento de RL em menos de 2 segundos: Sincronização de pesos extremamente rápida entre nós de treinamento e inferência.

Habilitando Modelos com Trilhões de Parâmetros na AWS EFA: Operações de dispatch e combine de MoE de baixa latência usando primitivas scatter e barrier, alcançando latência de última geração no ConnectX-7 e a primeira implementação viável compatível com AWS EFA.

Este artigo se concentra no próprio TransferEngine, a biblioteca de comunicação que impulsiona esses sistemas. Leia o artigo de pesquisa completo no arXiv e explore o código no GitHub.

Motiva&ccedil95o

As comunica&ccedil95es coletivas s&atilde95o excelentes para padr&otilde95es paralelos estruturados, como Paralelismo de Tensor e Paralelismo de Dados, mas imp&otilde95em restri&ccedil95es que limitam sua aplicabilidade a cargas de trabalho emergentes:

Membresia fixa: Coletivos exigem o estabelecimento de um world de comunicação global no momento da inicialização, com configuração sincronizada entre todos os participantes. Para inferência desagregada, isso é problemático, pois o número de instâncias de prefill e decode precisa escalar dinamicamente em resposta aos padrões de tráfego.

Formas uniformes de tensores: Coletivos presumem que todos os participantes troquem tensores com formas e tipos de dados idênticos. No roteamento de MoE, o número de tokens enviados a cada especialista depende dos dados e é descoberto em tempo de execução, forçando coletivos densos all-to-all a preencherem até os tamanhos de buffer do pior caso.

Ordenação de operações: APIs coletivas impõem semântica sincronizada, mesmo quando os aplicativos só precisam saber que um conjunto de operações foi concluído. Por exemplo, na transferência de KvCache, todas as páginas da instância de prefill para a instância de decode podem chegar em qualquer ordem. Da mesma forma, em atualizações de pesos de RL, todos os tensores de pesos podem ser transferidos independentemente, sem restrições de ordenação.

A comunica&ccedil95o ponto a ponto baseada em RDMA aborda naturalmente essas limita&ccedil95es, mas tr&ecirc95s barreiras impediram sua ado&ccedil95o:

Limitações da biblioteca: Embora torch.distributed e NCCL forneçam APIs ponto a ponto como Send/Recv, essas operações ainda herdam semântica e limitações de coletivos.

Diversidade de hardware: As NICs NVIDIA ConnectX com transporte Reliable Connection (RC) são a configuração RDMA mais comum, garantindo entrega em ordem. Provedores de nuvem como a AWS usam hardware diferente; com Elastic Fabric Adapter (EFA) e transporte Scalable Reliable Datagram (SRD), as mensagens são entregues fora de ordem. O EFA também exige a agregação de quatro NICs de 100 Gbps (ou duas NICs de 200 Gbps) para atingir largura de banda de 400 Gbps.

Portabilidade de software: As bibliotecas RDMA existentes sofrem com dependência de fornecedor. DeepEP requer RDMA iniciado pela GPU (IBGDA), disponível apenas no ConnectX. NVSHMEM apresenta degradação severa de desempenho na EFA. Bibliotecas como Mooncake e NIXL não oferecem suporte à EFA ou ainda estão em desenvolvimento inicial. Como resultado, não existe uma solução viável e multiplataforma para inferência de LLM.

TransferEngine

O TransferEngine aborda essas barreiras atrav&eacute9s de quatro capacidades essenciais:

Pressupostos comuns de transporte: Em vez de visar uma NIC específica, o TransferEngine pressupõe apenas que o transporte subjacente seja confiável, mas desordenado, isto é, a interseção das garantias fornecidas pelos transportes RC e SRD.

Sincronização ImmCounter: Uma nova primitiva para notificação de conclusão que funciona sem depender da ordenação de mensagens, permitindo que o mesmo código seja executado em transportes ordenados (RC) e desordenados (SRD).

Suporte a múltiplas NICs: Gerenciamento transparente de várias NICs por GPU, particionando automaticamente as transferências para maximizar a largura de banda.

Superfície de API mínima: Um pequeno conjunto de operações suficiente para construir padrões complexos de comunicação.

Design do sistema TransferEngine, mostrando os caminhos de solicitação e callback, workers de CPU, buffers UVM, GPUs e NICs em dois domínios GPU/NUMA.

O TransferEngine cria um tópico de trabalho por GPU. Cada tópico gerencia um DomainGroup que coordena de 1 a 4 NICs de RDMA, dependendo da plataforma de hardware (uma única ConnectX-7 fornece 400 Gbps, enquanto a EFA exige quatro adaptadores de 100 Gbps ou dois adaptadores de 200 Gbps). Dentro de um DomainGroup, cada Domain é específico de hardware e gerencia uma única NIC, cuidando do gerenciamento de queue pair, da submissão de trabalho e da sondagem de conclusões.

Cada instância do TransferEngine expõe um único endereço principal para identificação e descoberta de pares. Pares remotos trocam structs NetAddr que codificam endereços de rede e podem ser serializadas para comunicação fora de banda. Todos os pares em um sistema devem usar o mesmo número de NICs por GPU. Essa configuração uniforme permite ao TransferEngine particionar as transferências de forma inteligente: ao iniciar uma transferência, origem e destino sabem exatamente quais NICs estão disponíveis, possibilitando balanceamento automático de carga entre todos os adaptadores.

Design da API

O TransferEngine exp&otilde95e uma API m&iacute95nima que abstrai a complexidade das interfaces RDMA subjacentes.

rust
#[serde] struct NetAddr(Bytes); #[serde] struct MrDesc{ ptr: u64, rkeys: Vec<(NetAddr, u64)> } struct MrHandle(NonNull<c_void>); type Offset = u64; struct Pages{ indices: Vec<u32>, stride: u64, offset: Offset } struct PeerGroupHandle(u64); struct ScatterDst{ len: u64, src: Offset, dst: (MrDesc,Offset)} enum OnDone { Callback(fn () -> ()), Flag(Atomic<bool>) } trait TransferEngine { fn main_address() -> NetAddr; // Memory Region Management fn reg_mr(ptr, len, device) -> (MrHandle, MrDesc); // Two-sided Send/Recv fn submit_send(addr: NetAddr, msg: &[u8], cb: fn () -> ()); fn submit_recvs(len: u64, cnt: u64, cb: fn (&[u8]) -> ()); // One-sided Write fn expect_imm_count(imm: u32, count: u32, cb: fn () -> ()); fn submit_single_write(len: u64, imm: Option<u32> src: (MrHandle, Offset), dst: (MrDesc, Offset), OnDone); fn submit_paged_writes(page_len: u64, imm: Option<u32>, src: (MrHandle, Pages), dst: (MrDesc, Pages), OnDone); // One-sided Write to a group of peers fn add_peer_group(addrs: Vec<NetAddr>) -> PeerGroupHandle; fn submit_scatter(h: Option<PeerGroupHandle>, OnDone, imm: Option<u32>, src: MrHandle, dst: Vec<ScatterDst>); fn submit_barrier(h: Option<PeerGroupHandle>, OnDone, imm: u32, dst: Vec<MrDesc>); // Watcher for CPU-GPU synchronization fn alloc_uvm_watcher(cb: fn(u64,u64) -> ()) -> NonNull<u64>; }

A API consiste em v&aacute95rios componentes principais:

Registro de Mem&oacute95ria

As operações de RDMA exigem que regiões de memória sejam registradas com a NIC antes da transferência de dados. A função reg_mr recebe um ponteiro, um comprimento e um dispositivo, retornando dois handles:

MrHandle: Um handle local usado como origem para transferências

MrDesc: Um descritor serializável que pode ser trocado com pares remotos para permitir que eles gravem na memória registrada

A estrutura MrDesc contém uma lista de endereço de rede remoto e chave remota (uma por NIC) que concedem acesso remoto à região de memória. Esse design lida naturalmente com configurações de múltiplas NICs.

Envio/Recebimento de Dois Lados

Para mensagens pequenas e mensagens da camada de controle no estilo RPC, o TransferEngine fornece submit_send e submit_recvs:

submit_send: Envia a mensagem para um par remoto identificado por NetAddr. A mensagem é copiada internamente, de modo que o chamador pode reutilizar ou liberar o buffer imediatamente.

submit_recvs: Publica um conjunto de buffers de recebimento. Quando uma mensagem chega, uma callback é invocada com os dados recebidos. O buffer é automaticamente retornado ao pool após a conclusão da callback.

Grava&ccedil95o de Um Lado

Para transfer&ecirc95ncias de dados em massa, o TransferEngine fornece opera&ccedil95es de grava&ccedil95o de um lado com zero c&oacute95pia:

submit_single_write: Transfere uma região contígua da origem para o destino

submit_paged_writes: Transfere páginas não contíguas especificadas por índices, strides e offsets

Ambas as opera&ccedil95es aceitam um valor imediato opcional de 32 bits que &eacute9 entregue ao receptor atomicamente ap&oacute9s a conclus&atilde95o.

O mecanismo fragmenta automaticamente transfer&ecirc95ncias grandes entre m&uacute95ltiplas NICs para maximizar a largura de banda.

Grupos de Pares e Opera&ccedil95es em Massa

Para opera&ccedil95es que visam v&aacute95rios pares (comum no roteamento de MoE), o TransferEngine fornece primitivas otimizadas:

add_peer_group: Pré-registra um conjunto de endereços de pares para pesquisa rápida

submit_scatter: Envia diferentes fatias de dados para cada par no grupo

submit_barrier: Envia uma notificação imediata apenas (transferência de zero bytes) para todos os pares

Essas opera&ccedil95es s&atilde95o otimizadas para baixa lat&ecirc95ncia armazenando em cache estruturas de dados internas e criando modelos de solicita&ccedil95es de trabalho RDMA, evitando a sobrecarga de configura&ccedil95o repetitiva.

Observador UVM: Sinaliza&ccedil95o de GPU para CPU

Um desafio fundamental nos sistemas de LLM &eacute9 coordenar as transfer&ecirc95ncias RDMA com o progresso da GPU. Idealmente, a transfer&ecirc95ncia de dados deve come&ccedil95ar assim que a GPU produz uma sa&iacute95da, mas sondar a GPU a partir da CPU &eacute9 ineficiente.

O TransferEngine resolve isso com UVM Watchers: locais de memória virtual unificada que podem ser atualizados por kernels da GPU (inclusive dentro de grafos CUDA) e sondados de forma eficiente pela CPU usando GDRCopy. Quando um UVM watcher é alocado, uma callback é registrada e é invocada sempre que a GPU atualiza o valor.

Isso permite padr&otilde95es como a transfer&ecirc95ncia de KvCache camada por camada: ap&oacute9s a conclus&atilde95o de cada camada de transformador, a GPU incrementa o observador, disparando uma transfer&ecirc95ncia RDMA do KvCache dessa camada, enquanto as camadas subsequentes continuam o processamento.

Notifica&ccedil95o de Conclus&atilde95o

O TransferEngine fornece mecanismos de notificação de conclusão tanto para o lado iniciador (o chamador de submit_write) quanto para o lado receptor (o alvo das operações de Write).

Notificação do lado iniciador: Quando uma operação de escrita é submetida, o chamador pode optar por ser notificado na conclusão por meio de uma função de callback ou de um flag atômico. Essa notificação confirma que os dados foram transferidos com sucesso do buffer de origem.

Notificação do lado receptor: A principal inovação é o ImmCounter, um componente que rastreia contadores indexados por números imediatos de 32 bits. Quando uma operação de escrita carrega um valor imediato, o ImmCounter do receptor é incrementado atomically na entrega dos dados. A função expect_imm_count permite que o receptor registre uma callback que dispara quando um valor imediato específico atinge uma contagem-alvo. Os contadores podem ser sincronizados de forma transparente com a GPU via GDRCopy, sondados diretamente ou monitorados por meio de callbacks.

Este mecanismo do lado do receptor fornece sincroniza&ccedil95o confi&aacute95vel sem exigir a ordena&ccedil95o de mensagens. Ele funciona igualmente bem em NICs ConnectX (que garantem entrega em ordem pelo transporte RC) e NICs EFA (que entregam mensagens fora de ordem pelo transporte SRD).

Implementa&ccedil95o

O TransferEngine é implementado em Rust, otimizando cuidadosamente alocações, threading e sincronização para minimizar a latência e alcançar alta taxa de transferência. O engine cria um tópico de trabalho por DomainGroup, cada um fixado a um núcleo de CPU no nó NUMA ao qual os dispositivos estão conectados, minimizando tanto o agendamento quanto a latência de acesso à memória. Estruturas de dados específicas de um domínio são alocadas após a fixação para garantir que a memória seja reservada no nó NUMA correto. Um tópico de trabalho lida com até 4 Domains (cada um gerenciando uma única NIC), enquanto outro tópico dedicado faz a sondagem da GPU para atualizar os UVM watchers. A comunicação entre tópicos é feita por meio de filas lock-free.

A API encaminha solicitações ao DomainGroup que atende ao dispositivo apropriado, sendo que o primeiro também atende ao host. Em um loop apertado, o tópico do domínio faz sondagem por novo trabalho, priorizando a submissão de novas solicitações. Dependendo do hardware e da configuração, as solicitações são particionadas e balanceadas entre os Domains disponíveis. A primeira Write de uma solicitação composta é imediatamente postada na fila de envio da NIC. Assim que as novas solicitações se esgotam, o tópico passa a avançar as operações pendentes, postando writes para preencher o pipeline de hardware. Em seguida, as filas de conclusão são sondadas para consultar transferências concluídas e incrementos de contadores imediatos. Os eventos são agregados para entregar notificações por transferência, repassando a transferência para um tópico de callback dedicado compartilhado por todos os grupos.

O particionamento dentro de um DomainGroup é flexível. As transferências podem ser direcionadas a NICs específicas por índice. Uma única Write pode ser dividida. Transferências paginadas, scatter e operações barrier, que todas se traduzem em múltiplas Writes, podem ser particionadas entre todas as NICs.

Otimiza&ccedil95es Espec&iacute95ficas de Hardware

Os Domains dentro do TransferEngine são especializados e otimizados para o hardware sob seu controle:

AWS EFA: A implementação da EFA usa libfabric, gerenciando um domínio de fabric por NIC dentro do DomainGroup. As principais decisões de implementação e otimizações incluem:

Gravações de tamanho zero: A EFA se desvia da especificação RDMA ao exigir descritores de destino válidos mesmo para gravações de tamanho zero. Portanto, o TransferEngine impõe descritores válidos para todas as transferências.

Template de solicitações de trabalho: Pré-preenche e retém os campos comuns de structs de libfabric antes da submissão, reduzindo a sobrecarga por operação em transferências em massa e operações de grupos de pares

NVIDIA ConnectX-7: A implementação da ConnectX-7 usa libibverbs com transporte RC. As principais decisões de implementação e otimizações incluem:

Queue pairs UD para handshakes: Cada Domain usa uma queue pair de Datagramas Não Confiáveis (UD) para trocar handshakes de conexão RC.

Queue pairs RC separadas: Cria 2 queue pairs RC por par, uma para operações duas vias Send/Recv e outra para operações one-sided Write e WriteImm. Essa separação é necessária porque tanto as conclusões de Recv quanto as de WriteImm consomem work requests na ordem de postagem, permitindo semântica de Recv de alto nível ao mesmo tempo que oferece suporte a WriteImm sem interferência.

Template de solicitações de trabalho: Pré-preenche campos comuns de estruturas de solicitações de trabalho para transferências em massa e grupos de pares.

Encadeamento de solicitações de trabalho: Vincula até 4 solicitações de trabalho por meio do ponteiro next de ibv_send_wr, reduzindo o número de doorbell rings para a NIC.

Ordem relaxada: Habilita IBV_ACCESS_RELAXED_ORDERING para permitir transações PCIe fora de ordem entre a NIC e a memória da GPU.

Conclus&atilde95o

Ao visar a interseção de garantias entre diferentes transportes RDMA e introduzir sincronização independente da ordem por meio do ImmCounter, o TransferEngine fornece uma base portátil para comunicação ponto a ponto em sistemas de LLM. A biblioteca é open source e está disponível no GitHub, e incentivamos a comunidade a explorá-la e construir sobre ela para padrões de comunicação emergentes em sistemas de machine learning em larga escala.

Citação

BibTeX
@online{perplexity2025RdmaPointToPointCommunicationForLlmSystems,
  author = {{Perplexity} and Nandor Licker and Kevin Hu and Vladimir Zaytsev and Lequn Chen},
  title = {{RDMA point-to-point communication for LLM systems}},
  date = {2025-11-05},
  url = {https://www.perplexity.ai/hub/blog/rdma-point-to-point-communication-for-llm-systems}
}