Comunicaç95o RDMA ponto a ponto para sistemas LLM
Ferramenta elegante para abordar padrõ95es de comunicaç95o emergentes em LLM
Tradicionalmente, os sistemas de LLM dependem de comunicaç5595es coletivas para padrõ95es paralelos estruturados em treinamento e inferê95ncia. No entanto, cargas de trabalho emergentes, como inferê95ncia desagregada, roteamento de Mixture-of-Experts (MoE) e ajuste fino assí95ncrono de aprendizado por reforç95o, exigem a flexibilidade da comunicaç95o ponto a ponto.
Nosso artigo de pesquisa mais recente apresenta o TransferEngine, uma biblioteca de comunicaç95o portá95til 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á95cia em trê95s sistemas de produç95o 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ç95o
As comunicaç95es coletivas sã95o excelentes para padrõ95es paralelos estruturados, como Paralelismo de Tensor e Paralelismo de Dados, mas impõ95em restriç95es 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ç95o ponto a ponto baseada em RDMA aborda naturalmente essas limitaç95es, mas trê95s barreiras impediram sua adoç95o:
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é9s 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.
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õ95e uma API mí95nima que abstrai a complexidade das interfaces RDMA subjacentes.
#[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á95rios componentes principais:
Registro de Memó95ria
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ç95o de Um Lado
Para transferê95ncias de dados em massa, o TransferEngine fornece operaç95es de gravaç95o de um lado com zero có95pia:
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ç95es aceitam um valor imediato opcional de 32 bits que é9 entregue ao receptor atomicamente apó9s a conclusã95o.
O mecanismo fragmenta automaticamente transferê95ncias grandes entre mú95ltiplas NICs para maximizar a largura de banda.
Grupos de Pares e Operaç95es em Massa
Para operaç95es que visam vá95rios 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ç95es sã95o otimizadas para baixa latê95ncia armazenando em cache estruturas de dados internas e criando modelos de solicitaç95es de trabalho RDMA, evitando a sobrecarga de configuraç95o repetitiva.
Observador UVM: Sinalizaç95o de GPU para CPU
Um desafio fundamental nos sistemas de LLM é9 coordenar as transferê95ncias RDMA com o progresso da GPU. Idealmente, a transferê95ncia de dados deve começ95ar assim que a GPU produz uma saí95da, mas sondar a GPU a partir da CPU é9 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õ95es como a transferê95ncia de KvCache camada por camada: apó9s a conclusã95o de cada camada de transformador, a GPU incrementa o observador, disparando uma transferê95ncia RDMA do KvCache dessa camada, enquanto as camadas subsequentes continuam o processamento.
Notificaç95o de Conclusã95o
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ç95o confiá95vel sem exigir a ordenaç95o 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ç95o
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ç95es Especí95ficas 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ã95o
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
@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}
}