RDMA punkt-til-punkt-kommunikasjon for LLM-systemer
Elegant verktøy for å håndtere nye LLM-kommunikasjonsmønstre
LLM-systemer har tradisjonelt stolt på kollektive kommunikasjonsmetoder for strukturerte parallelle mønstre innen trening og inferens. Nye arbeidsmengder, som disaggregert inferens, Mixture-of-Experts (MoE)-rutting og asynkron finjustering av forsterkende læring, krever imidlertid fleksibiliteten til punkt-til-punkt-kommunikasjon.
Vår nyeste forskningsartikkel introduserer TransferEngine, et bærbar RDMA-basert kommunikasjonsbibliotek som fungerer på tvers av forskjellige nettverkskort (NIC-er), inkludert NVIDIA ConnectX-7 og AWS Elastic Fabric Adapter (EFA). Vi demonstrerer effektiviteten i tre produksjonssystemer hos Perplexity AI:
Disaggregert prefill og dekoding: Lag-for-lag-overføring av KvCache ved hjelp av sideskrevne skriv og UVM-overvåkere, noe som gjør at prefill- og dekodingsinstanser kan skalere uavhengig.
Vekt有些overføring for RL-ettertrening på under 2 sekunder: Lynrask vektsynkronisering mellom trenings- og inferensnoder.
Muliggjøring av billion-parametermodeller på AWS EFA: Lavlatens MoE-dirigering og kombinasjonsoperasjoner ved hjelp av sprednings- og barriereprimitiver, noe som oppnår bransjeledende latens på ConnectX-7 og den første levedyktige implementeringen som er kompatibel med AWS EFA.
Denne artikkelen fokuserer på selve TransferEngine, kommunikasjonsbiblioteket som driver disse systemene. Les hele forskningsartikkelen på arXiv og utforsk koden på GitHub.
Motivasjon
Kollektive kommunikasjonsmetoder utmerker seg ved strukturerte parallelle mønstre som tensorparallellisme og dataparallellisme, men de pålegger begrensninger som begrenser deres anvendelighet for nye arbeidsmengder:
Fast medlemskap: Kollektiver krever opprettelse av et globalt kommunikasjonsmiljø ved initialisering, med synkronisert oppsett på tvers av alle deltakere. For disaggregert inferens er dette problematisk siden antallet prefill- og dekodingsinstanser må skalere dynamisk som svar på trafikkonsum.
Ensartede tensormønstre: Kollektiver antar at alle deltakere utveksler tensorer med identiske former og datatyper. I MoE-dirigering er antallet tokens som sendes til hver ekspert datastyrt og oppdages under kjøring, noe som tvinger tette alle-til-alle-kollektiver til å fylle opp til verste-fall-bufferstørrelser.
Operasjonsrekkefølge: Kollektive API-er håndhever synkronisert semantikk, selv når applikasjoner bare trenger å vite at et sett med operasjoner er fullført. For eksempel kan alle sider fra prefill-instansen til dekodingsinstansen ankomme i vilkårlig rekkefølge ved KvCache-overføring. På samme måte kan alle vekttensorer overføres uavhengig uten rekkefølgebeslutninger i RL-vekt oppdateringer.
RDMA-basert punkt-til-punkt-kommunikasjon adresserer naturlig disse begrensningene, men tre barrierer har forhindret innføringen:
Bibliotekbegrensninger: Selv om torch.distributed og NCCL tilbyr punkt-til-punkt-API-er som Send/Recv, arver disse operasjonene fortsatt kollektiv semantikk og begrensninger.
Maskinvaremangfold: NVIDIA ConnectX-NIC-er med Reliable Connection (RC)-transport er det vanligste RDMA-oppsettet, noe som garanterer levering i riktig rekkefølge. Sky tilbydere som AWS bruker annen maskinvare, Elastic Fabric Adapter (EFA) med Scalable Reliable Datagram (SRD)-transport leverer meldinger ute av rekkefølge. EFA krever også aggregering av fire 100 Gbps NIC-er (eller to 200 Gbps NIC-er) for å oppnå 400 Gbps båndbredde.
Programvareportabilitet: Eksisterende RDMA-biblioteker lider av leverandørlåsing. DeepEP krever GPU-initiert RDMA (IBGDA) som bare er tilgjengelig på ConnectX. NVSHMEM viser alvorlig ytelsesforringelse på EFA. Biblioteker som Mooncake og NIXL mangler EFA-støtte eller er i tidlig utvikling. Som et resultat finnes det ingen levedyktig løsning på tvers av leverandører for LLM-inferens.
TransferEngine
TransferEngine adresserer disse barrierene gjennom fire kjernefunksjoner:
Felles transportforutsetninger: I stedet for å rette seg mot et spesifikt NIC, forutsetter TransferEngine bare at den underliggende transporten er pålitelig, men uordnet, det vil si skjæringspunktet mellom garantiene levert av både RC- og SRD-transporter.
ImmCounter-synkronisering: Et nytt primitiv for fullføringsvarsling som fungerer uten å være avhengig av meldingsrekkefølge, slik at samme kode kan kjøre på både ordnede (RC) og uordnede (SRD) transporter.
Multi-NIC-støtte: Transparent håndtering av flere NIC-er per GPU, som automatisk deler opp overføringer for å maksimere båndbredden.
Minimal API-flate: Et lite sett med operasjoner som er tilstrekkelig til å bygge komplekse kommunikasjonsmønstre.
TransferEngine starter en arbeidstråd per GPU. Hver arbeidstråd administrerer en DomainGroup som koordinerer 1 til 4 RDMA NIC-er, avhengig av maskinvareplattformen (en enkelt ConnectX-7 gir 400 Gbps, mens EFA krever fire 100 Gbps-adaptere eller to 200 Gbps-adaptere). Innenfor en DomainGroup er hver Domain maskinvarespesifikk og administrerer et enkelt NIC, og håndterer køparadministrasjon, arbeidsoppføring og fullføringspoll.
Hver TransferEngine-instans eksponerer en enkelt hovedadresse for peer-identifikasjon og oppdagelse. Eksterne peers utveksler NetAddr-strukturer som koder nettverksadresser og kan serialiseres for ut-av-band-kommunikasjon. Alle peers i et system må bruke samme antall NIC-er per GPU. Denne enhetlige konfigurasjonen gjør at TransferEngine kan dele opp overføringer intelligent: Når en overføring startes, vet både kilde og destinasjon nøyaktig hvilke NIC-er som er tilgjengelige, noe som muliggjør automatisk lastbalansering på tvers av alle adaptere.
API-design
TransferEngine eksponerer et minimalt API som abstraherer bort kompleksiteten i de underliggende RDMA-grensesnittene.
#[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>;
}API-et består av flere nøkkelkomponenter:
Mininregistrering
RDMA-operasjoner krever at minneområder registreres hos NIC-en før dataoverføring. Funksjonen reg_mr tar imot en peker, lengde og enhet, og returnerer to håndtak:
MrHandle: Et lokalt håndtak som brukes som kilde for overføringer
MrDesc: En serialiserbar deskriptor som kan utveksles med eksterne peers for å gi dem tillatelse til å skrive til det registrerte minnet
Strukturen MrDesc inneholder en liste over eksterne nettverksadresser og eksterne nøkler (én per NIC) som gir ekstern tilgang til minneområdet. Dette designet håndterer naturlig oppsett med flere NIC-er.
Tosidig Send/Recv
For små meldinger og RPC-lignende kontrollplanmeldinger tilbyr TransferEngine submit_send og submit_recvs:
submit_send: Sender meldingen til en ekstern peer identifisert av NetAddr. Meldingen kopieres internt, slik at den som rutter, umiddelbart kan gjenbruke eller frigi bufferen.
submit_recvs: Legger ut en pool av mottaksbuffere. Når en melding ankommer, kalles det et tilbakekall med de mottatte dataene. Bufferen returneres automatisk til poolen etter at tilbakekallet er fullført.
Ensidig skrivning
For bulkdataoverføringer tilbyr TransferEngine null-kopierings ensidige skriveoperasjoner:
submit_single_write: Overfører et sammenhengende område fra kilde til destinasjon
submit_paged_writes: Overfører ikke-sammenhengende sider spesifisert av indekser, steg og offset
Begge operasjonene godtar en valgfri 32-biters umiddelbar verdi som leveres atomisk til mottakeren ved fullføring.
Motoren deler automatisk opp store overføringer på tvers av flere NIC-er for å maksimere båndbredden.
Peer-grupper og bulkoperasjoner
For operasjoner rettet mot flere peers (vanlig i MoE-dirigering) tilbyr TransferEngine optimerte primitiver:
add_peer_group: Forhåndsregistrerer et sett med peer-adresser for raskt oppslag
submit_scatter: Sender forskjellige dataskiver til hver peer i gruppen
submit_barrier: Sender en umiddelbar-bare varsling til alle peers (null-byte-overføring)
Disse operasjonene er optimert for lav latens ved å hurtigbufre interne datastrukturer og bygge maler for RDMA-arbeidsforespørsler, slik at man unngår repeterende oppsettskostnader.
UVM Watcher: GPU-til-CPU-signalisering
En viktig utfordring i LLM-systemer er å koordinere RDMA-overføringer med GPU-fremgang. Ideelt sett bør dataoverføring starte så snart GPU-en produserer utdata, men avlesning av GPU-en fra CPU-en er lite effektivt.
TransferEngine løser dette med UVM Watchers: enhetlige virtuelle minneplasseringer som kan oppdateres av GPU-kjerner (til og med innenfor CUDA-grafer) og effektivt polles av CPU-en ved hjelp av GDRCopy. Når en UVM-overvåker tildeles, registreres et tilbakekall som utløses hver gang GPU-en oppdaterer verdien.
Dette muliggjør mønstre som lag-for-lag KvCache-overføring: Etter at hvert transformatorlag er fullført, øker GPU-en overvåkeren, noe som utløser en RDMA-overføring av det lagets KvCache mens de etterfølgende lagene fortsetter å beregne.
Fullføringsvarsling
TransferEngine tilbyr mekanismer for fullføringsvarsling for både initierende side (den som kaller submit_write) og mottakersiden (målet for skriveoperasjoner).
Varsling på initiativtakerens side: Når en skriveoperasjon sendes inn, kan den som rutter, velge å bli varslet ved fullføring gjennom enten en tilbakekallsfunksjon eller et atomisk flagg. Denne varslingen bekrefter at dataene har blitt vellykket overført fra kildebufferen.
Varsling på mottakersiden: Hovedinnovasjonen er ImmCounter, en komponent som sporer tellere indeksert av 32-biters umiddelbare tall. Når en skriveoperasjon bærer med seg en umiddelbar verdi, økes mottakerens ImmCounter atomisk ved datalevering. Funksjonen expect_imm_count lar mottakeren registrere et tilbakekall som utløses når en spesifikk umiddelbar verdi når en måltelling. Tellerne kan synkroniseres transparent med GPU-en via GDRCopy, polles direkte eller overvåkes gjennom tilbakekall.
Denne mekanismen på mottakersiden gir pålitelig synkronisering uten å kreve meldingsrekkefølge. Den fungerer like godt på ConnectX NIC-er (som garanterer levering i riktig rekkefølge over RC-transport) og EFA NIC-er (som leverer meldinger ute av rekkefølge over SRD-transport).
Implementering
TransferEngine er implementert i Rust, med grundig optimering av allokeringer, tråding og synkronisering for å minimere latens og oppnå høy gjennomstrømning. Motoren starter én arbeidstråd per DomainGroup, der hver er tilknyttet en CPU-kjerne på NUMA-noden som enhetene er tilkoblet, noe som minimerer både tidsplanleggings- og minnetilgangslatens. Datastrukturer som er spesifikke for et domene, allokeres etter tilknytning for å sikre at minnet reserveres i riktig NUMA-node. Én arbeidstråd håndterer opptil 4 Domain-er (hver med et enkelt NIC), mens en annen dedikert tråd poller GPU-en for å oppdatere UVM-overvåkere. Kommunikasjon på tvers av tråder utføres via låsefrie køer.
API-et videresender forespørsler til DomainGroup som betjener den aktuelle enheten, hvor den første også betjener verten. I en stram løkke poller dommearbeideren etter nytt arbeid, og prioriterer innsending av nye forespørsler. Avhengig av maskinvare og konfigurasjon blir forespørsler delt opp og lastbalansert på tvers av de tilgjengelige Domain-ene. Den første skriveoperasjonen i en sammensatt forespørsel sendes umiddelbart til NIC-ens sendekø. Når nye forespørsler er oppbrukt, fortsetter arbeideren med å behandle ventende operasjoner ved å legge ut skriv for å fylle opp maskinvarepipelinen. Deretter polles fullføringskøer for å spørre etter fullførte overføringer og økninger i umiddelbare tellere. Hendelser aggregeres for å levere per-overføring-varsler, og overfører oppgaven til en dedikert tilbakekallstråd som deles av alle grupper.
Oppdeling inne i en DomainGroup er fleksibel. Overføringer kan rettes mot spesifikke NIC-er etter indeks. En enkel skriveoperasjon kan deles opp. Sideinndelte overføringer, spredning og barriereoperasjoner, som alle oversettes til flere skriveoperasjoner, kan fordeles på tvers av alle NIC-er.
Maskinvarespesifikke optimeringer
Domenene (Domain-ene) i TransferEngine er spesialiserte og optimert for maskinvaren under deres kontroll:
AWS EFA: EFA-implementeringen bruker libfabric og administrerer ett fabric-domene per NIC i DomainGroup. Viktige implementeringsbeslutninger og optimeringer inkluderer:
Null-størrelse skriv: EFA avviker fra RDMA-spesifikasjonen ved å kreve gyldige måldeskriptorer selv for null-størrelse skriv. Derfor håndhever TransferEngine gyldige deskriptorer for alle overføringer.
Arbeidsforespørselsforming: Forhåndsutfyller og beholder de vanlige feltene i libfabric-strukturer før utsending, noe som reduserer overhead per operasjon for bulkoverføringer og peergruppeoperasjoner
NVIDIA ConnectX-7: ConnectX-7-implementeringen bruker libibverbs med RC-transport. Viktige implementeringsbeslutninger og optimeringer inkluderer:
UD-køparer for håndtrykk: Hver Domain bruker et Unreliable Datagram (UD)-køpar for å utveksle RC-tilkoblingshåndtrykk.
Separate RC-køparer: Oppretter 2 RC-køparer per peer, én for tosidige Send/Recv-operasjoner og en annen for ensidige Write- og WriteImm-operasjoner. Denne separasjonen er nødvendig fordi både Recv- og WriteImm-fullføringer forbruker arbeidsforespørsler i innsendingsrekkefølge, noe som tillater Recv-semantikk på høyt nivå samtidig som det støtter WriteImm uten interferens.
Arbeidsforespørselsforming: Forhåndsutfyller vanlige felt i arbeidsforespørselsstrukturer for bulkoverføringer og peergrupper.
Kjedekobling av arbeidsforespørsler: Kobler opptil 4 arbeidsforespørsler sammen via next-pekeren til ibv_send_wr, noe som reduserer antall dørklokkeslag (doorbell rings) til NIC-en.
Avslappet rekkefølge: Aktiverer IBV_ACCESS_RELAXED_ORDERING for å tillate PCIe-transaksjoner ut av rekkefølge mellom NIC-en og GPU-minnet.
Konklusjon
Ved å rette seg mot skjæringspunktet mellom garantiene på tvers av ulike RDMA-transporter og introdusere rekkefølgeuavhengig synkronisering via ImmCounter, tilbyr TransferEngine et bærbar fundament for punkt-til-punkt-kommunikasjon i LLM-systemer. Biblioteket er åpen kildekode og tilgjengelig på GitHub, og vi oppfordrer miljøet til å utforske og bygge videre på det for nye kommunikasjonsmønstre i storskala maskinlæringssystemer.
Sitat
@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}
}