RDMA komunikacija tačka-u-tačku za LLM sisteme
Elegantan alat za rešavanje novih LLM komunikacionih šablona
LLM sistemi su se tradicionalno oslanjali na kolektivne komunikacije za strukturirane paralelne šablone u obuci i zaključivanju. Međutim, nova radna opterećenja, kao što su disagregirano zaključivanje, Mixture-of-Experts (MoE) rutersko usmeravanje i asinhrono usavršavanje putem potkrepljujućeg učenja (reinforcement learning fine-tuning), zahtevaju fleksibilnost tačka-u-tačku (point-to-point) komunikacije.
Naš najnoviji istraživački rad predstavlja TransferEngine, prenosivu RDMA komunikacionu biblioteku koja funkcioniše preko različitih mrežnih interfejs kontrolera (NIC), uključujući NVIDIA ConnectX-7 i AWS Elastic Fabric Adapter (EFA). Demonstriramo njenu efikasnost u tri produkcijska sistema u kompaniji Perplexity AI:
Disagregirani prefild i dekodiranje: Prenos KvCache-a sloj po sloj korišćenjem pejdžovanih upisa (paged writes) i UVM posmatrača, što omogućava nezavisno skaliranje instanci prefilda i dekodiranja.
Prenos težina za RL post-obuku za manje od 2 sekunde: Izuzetno brza sinhronizacija težina između čvorova za obuku i zaključivanje.
Omogućavanje modela sa bilionima parametara na AWS EFA: MoE prosleđivanje i kombinovanje operacija niske latencije korišćenjem scatter i barrier primitiva, čime se postiže najsavremenija latencija na ConnectX-7 i prva održiva implementacija kompatibilna sa AWS EFA.
Ovaj članak se fokusira na sam TransferEngine, komunikacionu biblioteku koja pokreće ove sisteme. Pročitajte kompletan istraživački rad na arXiv-u i istražite kod na GitHub-u.
Motivacija
Kolektivne komunikacije se ističu u strukturiranim paralelnim šablonima kao što su Tensor Parallelism i Data Parallelism, ali nameću ograničenja koja umanjuju njihovu primenljivost na nova radna opterećenja:
Fiksno članstvo: Kolektivi zahtevaju uspostavljanje globalnog komunikacionog sveta u vreme inicijalizacije, uz sinhronizovano podešavanje među svim učesnicima. Za disagregirano zaključivanje ovo predstavlja problem pošto broj instanci prefilda i dekodiranja mora dinamički da se skalira kao odgovor na saobraćajne šablone.
Ujednačeni oblici tenzora: Kolektivi podrazumevaju da svi učesnici razmenjuju tenzore identičnih oblika i tipova podataka. Kod MoE usmeravanja, broj tokena poslatih svakom ekspertu zavisi od podataka i utvrđuje se u vreme izvršavanja, što primorava guste all-to-all kolektive da vrše dopunu (pad) do veličina bafera za najgori slučaj.
Redosled operacija: Kolektivni API-ji nameću sinhronizovanu semantiku, čak i kada aplikacije treba samo da znaju da je skup operacija završen. Na primer, u prenosu KvCache-a, sve stranice od instanci prefilda do instanci dekodiranja mogu stići bilo kojim redosledom. Slično tome, u ažuriranju RL težina, svi tenzori težina mogu se prenositi nezavisno bez ograničenja redosleda.
RDMA komunikacija tačka-u-tačku prirodno rešava ova ograničenja, ali su tri prepreke sprečile njeno usvajanje:
Ograničenja biblioteke: Iako torch.distributed i NCCL obezbeđuju API-je tačka-u-tačku kao što su Send/Recv, ove operacije i dalje nasleđuju kolektivnu semantiku i ograničenja.
Raznovrsnost hardvera: NVIDIA ConnectX mrežne kartice sa Reliable Connection (RC) transportom predstavljaju najčešće RDMA podešavanje, koje garantuje isporuku po redosledu. Pružaoci usluga u oblaku poput AWS-a koriste drugačiji hardver; Elastic Fabric Adapter (EFA) sa Scalable Reliable Datagram (SRD) transportom isporučuje poruke van redosleda. EFA takođe zahteva agregiranje četiri mrežne kartice od 100 Gbps (ili dve od 200 Gbps) radi postizanja propusnog opsega od 400 Gbps.
Softverska prenosivost: Postojeće RDMA biblioteke pate od zaključavanja za dobavljača (vendor lock-in). DeepEP zahteva RDMA iniciran od strane GPU-a (IBGDA) dostupan samo na ConnectX-u. NVSHMEM pokazuje ozbiljno pogoršanje performansi na EFA. Biblioteke poput Mooncake i NIXL nemaju podršku za EFA ili su u ranoj fazi razvoja. Zbog toga ne postoji održivo rešenje za više dobavljača za LLM zaključivanje.
TransferEngine
TransferEngine rešava ove prepreke kroz četiri ključne mogućnosti:
Zajedničke pretpostavke o transportu: Umesto ciljanja specifičnog mrežnog kontrolera, TransferEngine pretpostavlja samo da je osnovni transport pouzdan, ali nesequenciran (neuređen), tj. predstavlja presek garancija koje obezbeđuju i RC i SRD transporti.
ImmCounter sinhronizacija: Nova primitiva za obaveštenje o završetku koja radi bez oslanjanja na redosled poruka, omogućavajući da se isti kod izvršava i na uređenim (RC) i na neuređenim (SRD) transportima.
Podrška za više mrežnih kartica: Transparentno upravljanje sa više mrežnih kartica po GPU-u, uz automatsko deljenje (sharding) prenosa radi maksimiziranja propusnog opsega.
Minimalna površina API-ja: Mali skup operacija dovoljnih za izgradnju složenih komunikacionih šablona.
TransferEngine pokreće po jednu radnu nit (thread) po GPU-u. Svaka nit upravlja sa DomainGroup grupom koja koordiniše 1 do 4 RDMA mrežne kartice, u zavisnosti od hardverske platforme (jedan ConnectX-7 obezbeđuje 400 Gbps, dok EFA zahteva četiri adaptera od 100 Gbps ili dva adaptera od 200 Gbps). Unutar DomainGroup grupe, svaki Domain je specifičan za hardver i upravlja jednom mrežnom karticom, rukujući upravljanjem redom parova (queue pair), podnošenjem poslova i anketiranjem (polling) završetaka.
Svaka TransferEngine instanca izlaže jednu glavnu adresu za identifikaciju i otkrivanje partnera (peer). Udaljeni partneri razmenjuju NetAddr strukture koje kodiraju mrežne adrese i mogu se serijalizovati za komunikaciju van opsega. Svi partneri u sistemu moraju koristiti isti broj mrežnih kartica po GPU-u. Ova ujednačena konfiguracija omogućava TransferEngine-u da inteligentno deli prenose: prilikom pokretanja prenosa, i izvor i odredište tačno znaju koje su mrežne kartice dostupne, čime se omogućava automatsko balansiranje opterećenja na svim adapterima.
API dizajn
TransferEngine izlaže minimalan API koji apstrahuje složenost osnovnih RDMA interfejsa.
#[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 se sastoji od nekoliko ključnih komponenti:
Registracija memorije
RDMA operacije zahtevaju da memorijski regioni budu registrovani na mrežnoj kartici pre prenosa podataka. Funkcija reg_mr uzima pokazivač, dužinu i uređaj, vraćajući dve ručke (handle):
MrHandle: Lokalna ručka koja se koristi kao izvor za prenose
MrDesc: Serijalizovani deskriptor koji se može razmenjivati sa udaljenim partnerima kako bi im se omogućilo upisivanje u registrovanu memoriju
MrDesc struktura sadrži listu udaljenih mrežnih adresa i udaljenih ključeva (po jedan za svaku mrežnu karticu) koji odobravaju udaljeni pristup memorijskom regionu. Ovaj dizajn prirodno obrađuje podešavanja sa više mrežnih kartica.
Dvosmerno slanje/primanje (Send/Recv)
Za male poruke i poruke kontrolne ravni u stilu RPC-a, TransferEngine obezbeđuje submit_send i submit_recvs:
submit_send: Šalje poruku udaljenom partneru identifikovanom preko NetAddr. Poruka se kopira interno, tako da pozivalac može odmah ponovo da koristi ili oslobodi bafer.
submit_recvs: Postavlja skup bafera za prijem. Kada poruka stigne, poziva se povratni poziv (callback) sa primljenim podacima. Bafer se automatski vraća u skup nakon završetka povratnog poziva.
Jednosmerni upis (Write)
Za masovne prenose podataka, TransferEngine obezbeđuje jednosmerne operacije upisa bez kopiranja (zero-copy):
submit_single_write: Prenosi susedni region sa izvora na odredište
submit_paged_writes: Prenosi nesusedne stranice navedene pomoću indeksa, koračnih veličina (strides) i pomaka (offsets)
Obie operacije prihvataju opcionalnu 32-bitnu neposrednu vrednost koja se isporučuje primaocu atomski po završetku.
Motor automatski deli velike prenose na više mrežnih kartica radi maksimiziranja propusnog opsega.
Grupe partnera i masovne operacije
Za operacije usmerene na više partnera (uobičajeno u MoE usmeravanju), TransferEngine obezbeđuje optimizovane primitive:
add_peer_group: Unapred registruje skup adresa partnera radi brze pretrage
submit_scatter: Šalje različite delove podataka svakom partneru u grupi
submit_barrier: Šalje obaveštenje koje sadrži samo neposrednu vrednost svim partnerima (prenos nula bajtova)
Ove operacije su optimizovane za nisku latenciju keširanjem internih struktura podataka i šabloniranjem RDMA zahteva za posao, čime se izbegava ponovljeni režiski trošak podešavanja.
UVM posmatrač: Signalizacija sa GPU-a na CPU
Ključni izazov u LLM sistemima jeste koordinacija RDMA prenosa sa napretkom GPU-a. Idealno bi bilo da prenos podataka počne čim GPU proizvede izlaz, ali je anketiranje (polling) GPU-a sa CPU-a neefikasno.
TransferEngine ovo rešava pomoću UVM posmatrača (UVM Watchers): unificiranih lokacija virtuelne memorije koje GPU kerneli mogu da ažuriraju (čak i unutar CUDA grafova) i koje CPU efikasno anketira korišćenjem GDRCopy-ja. Kada se UVM posmatrač alocira, registruje se povratni poziv koji se pokreće kad god GPU ažurira vrednost.
Ovo omogućava šablone kao što je prenos KvCache-a sloj po sloj: nakon što se svaki transformatorski sloj završi, GPU uvećava vrednost posmatrača, pokrećući RDMA prenos KvCache-a tog sloja dok naredni sloj nastavlja sa računanjem.
Obaveštenje o završetku
TransferEngine obezbeđuje mehanizme obaveštenja o završetku i za stranu inicijatora (pozivaoca funkcije submit_write) i za stranu primaoca (cilj operacija upisa).
Obaveštenje na strani inicijatora: Kada se operacija upisa podnese, pozivalac može izabrati da bude obavešten po završetku putem funkcije povratnog poziva ili atomske zastavice. Ovo obaveštenje potvrđuje da su podaci uspešno preneseni iz izvornog bafera.
Obaveštenje na strani primaoca: Ključna inovacija je ImmCounter, komponenta koja prati brojače indeksirane 32-bitnim neposrednim brojevima. Kada operacija upisa nosi neposrednu vrednost, ImmCounter primaoca se atomski uvećava po isporuci podataka. Funkcija expect_imm_count omogućava primaocu da registruje povratni poziv koji se aktivira kada određena neposredna vrednost dostigne ciljni broj. Brojači se mogu transparentno sinhronizovati sa GPU-om preko GDRCopy-ja, anketirati direktno ili nadgledati kroz povratne pozive.
Ovaj mehanizam na strani primaoca obezbeđuje pouzdanu sinhronizaciju bez zahtevanja redosleda poruka. Podjednako dobro radi na ConnectX mrežnim karticama (koje garantuju isporuku po redosledu preko RC transporta) i EFA mrežnim karticama (koje isporučuju poruke van redosleda preko SRD transporta).
Implementacija
TransferEngine je implementiran u programskom jeziku Rust, uz pažljivu optimizaciju alokacija, niti (threading) i sinhronizacije radi minimiziranja latencije i postizanja visokog protoka. Motor pokreće po jednu radnu nit po DomainGroup grupi, pri čemu je svaka prikačena za CPU jezgarno na NUMA čvoru za koji su uređaji povezani, čime se minimizira latencija i planiranja i pristupa memoriji. Strukture podataka specifične za domen alociraju se nakon kačenja kako bi se osiguralo da je memorija rezervisana u ispravnom NUMA čvoru. Jedna radna nit rukuje sa do 4 Domain domena (od kojih svaki upravlja jednom mrežnom karticom), dok druga namenska nit anketira GPU radi ažuriranja UVM posmatrača. Međunitska komunikacija se obavlja preko redova bez zaključavanja (lock-free queues).
API prosleđuje zahteve DomainGroup grupi koja opslužuje odgovarajući uređaj, pri čemu prvi takođe opslužuje domaćina (host). U čvrstoj petlji, radni domen anketira nove poslove, dajući prioritet podnošenju novih zahteva. U zavisnosti od hardvera i konfiguracije, zahtevi se dele i balansiraju se opterećenja na dostupnim Domain domenima. Prvi upis složenog zahteva se odmah postavlja u red za slanje mrežne kartice. Kada se novi zahtevi iscrpe, radnik prelazi na obradu operacija na čekanju, postavljajući upise radi popunjavanja hardverskog cevovoda. Nakon toga, redovi za završetak se anketiraju radi provere završenih prenosa i uvećanja neposrednih brojača. Događaji se agregiraju radi isporuke obaveštenja po prenosu, predajući prenos namenskoj niti za povratne pozive koju dele sve grupe.
Deljenje (sharding) unutar DomainGroup grupe je fleksibilno. Prenosi mogu ciljati specifične mrežne kartice po indeksu. Jedan upis se može podeliti. Pejdžovani prenosi, scatter i operacije barijere, koji se svi prevode u višestruke upise, mogu se deliti preko svih mrežnih kartica.
Optimizacije specifične za hardver
Domain domeni unutar TransferEngine-a su specijalizovani i optimizovani za hardver pod njihovom kontrolom:
AWS EFA: EFA implementacija koristi libfabric, upravljajući domenom tkanine (fabric domain) po mrežnoj kartici unutar DomainGroup grupe. Ključne odluke i optimizacije implementacije uključuju:
Upisi veličine nula: EFA odstupa od RDMA specifikacije zahtevajući validne ciljne deskriptore čak i za upise veličine nula. Stoga, TransferEngine primenjuje validne deskriptore za sve prenose.
Šabloniranje zahteva za posao: Unapred popunjava i zadržava zajednička polja libfabric struktura pre postavljanja, čime se smanjuje režijski trošak po operaciji za masovne prenose i operacije grupa partnera
NVIDIA ConnectX-7: ConnectX-7 implementacija koristi libibverbs sa RC transportom. Ključne odluke i optimizacije implementacije uključuju:
UD parovi redova za rukovanja (handshakes): Svaki Domain koristi par redova nepouzdanog datagrama (UD - Unreliable Datagram) za razmenu rukovanja RC veze.
Zasebni RC parovi redova: Kreira 2 RC para redova po partneru, jedan za dvosmerne Send/Recv operacije, a drugi za jednosmerne Write i WriteImm operacije. Ovo odvajanje je neophodno jer završeci i za Recv i za WriteImm troše zahteve za posao po redosledu postavljanja, omogućavajući dvosmernu semantiku visokog nivoa uz podršku za WriteImm bez interferencije.
Šabloniranje zahteva za posao: Unapred popunjava zajednička polja struktura zahteva za posao za masovne prenose i grupe partnera.
Ulančavanje zahteva za posao: Povezuje do 4 zahteva za posao preko next pokazivača strukture ibv_send_wr, čime se smanjuje broj zvonjenja zvončića (doorbell rings) ka mrežnoj kartici.
Opušteni redosled: Omogućava IBV_ACCESS_RELAXED_ORDERING radi dozvoljavanja PCIe transakcija van redosleda između mrežne kartice i GPU memorije.
Zaključak
Ciljanjem preseka garancija na različitim RDMA transportima i uvođenjem sinhronizacije nezavisne od redosleda preko ImmCounter-a, TransferEngine obezbeđuje prenosivu osnovu za komunikaciju tačka-u-tačku u LLM sistemima. Biblioteka je otvorena (open source) i dostupna na GitHub-u, i ohrabrujemo zajednicu da je istraži i nadograđuje je za nova komunikaciona rešenja u mašinskom učenju velikih razmera.
Citiranje
@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}
}