Latență redusă și debit mai mare cu implementarea DeepSeek multi-nod

Implementarea multi-GPU sporește simultan performanța modelului MoE atât în ceea ce privește viteza, cât și scara

AutoriPerplexity Research

În majoritatea sistemelor, latența și debitul (throughput) sunt adesea obiective conflictuale care necesită compromisuri în timpul proiectării și implementării. De exemplu, în modelele lingvistice mari și dense, creșterea dimensiunii lotului (batch size) poate îmbunătăți debitul, dar crește și latența; creșterea paralelismului de tensori în cadrul unei singure mașini poate reduce latența, dar scade numărul de replici, ducând la un debit mai mic.

Modelele Mixture of Experts (MoE), precum DeepSeek-V3/R1, au demonstrat recent capabilități excelente ale modelelor și eficiență operațională. De exemplu, modelul DeepSeek-V3/R1 are în total 671B parametri, dar fiecare token utilizează doar 37B parametri în timpul inferenței. Această arhitectură de model prezintă atât provocări, cât și oportunități pentru sistemele de inferență.

Acest articol demonstrează că, contrar sistemelor convenționale, modelele MoE precum DeepSeek-V3/R1 pot atinge simultan un debit mai mare și o latență mai mică atunci când utilizează mai multe GPU-uri în implementări multi-nod în majoritatea scenariilor.

Arhitecturi de implementare

Implementare pe un singur nod prin NVLink, cu atenția și calculul experților distribuite pe GPU-uri.
Implementare pe mai multe noduri prin InfiniBand, cu grupuri de GPU conectate în interiorul nodurilor și între noduri.

Datorită numărului mare de experți de mici dimensiuni pe care îi are modelul, implementările trebuie să fie distribuite pe mai multe dispozitive. Am luat în considerare atât implementările pe un singur nod pe un singur nod cu 8xH200 GPU-uri, cât și implementările multi-nod pe 8xH100 GPU-uri.

Ambele arhitecturi de implementare valorifică paralelismul de date (Data Parallelism), orchestrat prin intermediul programatorului nostru intern de cereri. Implementarea paralelismului de date presupune lansarea mai multor instanțe ale motorului de inferență, fiecare operând independent pentru a deservi și menține cererile. Programatorul de cereri, care interacționează cu motorul prin GRPC, este responsabil cu distribuirea cererilor cât mai uniform posibil, facilitând în același timp reutilizarea KV și trimiterea cererilor cu prefix parțial potrivit către serverele care conțin memoria cache. Instanțele motorului nu se întind pe mai multe noduri. Ele pot utiliza opțional paralelismul de tensori pentru a partaja atenția pe mai multe dispozitive. Instanțele sunt interconectate prin NVLink în cazul unui singur nod sau prin InfiniBand pentru cazul multi-nod, expediind și colectând experți.

Configurația de implementare pe un singur nod oferă o latență superioară cu dimensiuni mici ale lotului; cu toate acestea, performanța se degradează rapid în condiții de sarcină crescută.

Pentru a implementa motorul de servire, lansăm câte un pod per nod care găzduiește mai multe instanțe ale motorului. PyTorch este responsabil pentru configurarea comunicării distribuite și negocierea inițializării NVSHMEM. Pentru comunicare, ne bazăm pe nuclee CUDA personalizate descrise într-o postare de blog anterioară. Implementarea celor două implementări este practic identică, modelul alegând nucleele corecte de utilizat pe baza țesăturii (fabric) care implementează paralelismul experților.

Tehnici de paralelizare

Înainte de a ne scufunda în comparațiile noastre de performanță, este esențial să înțelegem strategiile cheie de paralelizare care fac posibilă implementarea modelelor MoE masive precum DeepSeek-V3/R1.

Paralelism de tensori

În inferența LLM, paralelismul de tensori (Tensor Parallelism - TP) este utilizat de obicei pentru a reduce utilizarea memoriei și calculul pe fiecare GPU, reducând astfel latența. De obicei, putem partaja proiecțiile Linear în straturile de atenție și MLP de-a lungul dimensiunilor rândurilor sau coloanelor și putem partaja operațiunile de atenție de-a lungul dimensiunii capului de atenție.

Cu TP, arhitectura Llama-3 nu are nicio unelte duplicată de calcul pentru proiecțiile Linear și operațiunile de atenție pe diferite GPU-uri, ceea ce reprezintă o metodă ideală de partajare. Cu toate acestea, în modelele DeepSeek-V3/R1, TP nu poate realiza acest lucru.

Modelele DeepSeek-V3/R1 utilizează Multi-Latent Attention (MLA). Un strat MLA utilizează mai întâi o proiecție Linear kv_a_proj pentru a calcula vectorul latent, apoi utilizează o altă proiecție Linear kv_b_proj pentru a-l transforma în spațiul fiecărui cap de atenție. Deoarece toate capetele de atenție partajează același vector latent, TP nu poate partaja vectorul latent, astfel încât toate rangurile TP trebuie să replice parametrii și calculul kv_a_proj și kv_b_proj. În mod similar, deoarece MLA stochează vectorul latent în memoria cache KV, fiecare rang TP stochează o copie identică a memoriei cache KV.

În ciuda unei anumite duplicări în MLA, paralelismul de tensori oferă în continuare o reducere parțială a cerințelor de calcul, făcându-l valoros pentru scenariile care necesită viteze mari de ieșire.

Paralelism de experți

Modelele DeepSeek-V3/R1 înlocuiesc straturile MLP cu straturi MoE. Un strat MoE are 256 de experți rutați și un expert partajat. Fiecare token este expediat către 8 experți rutați diferiți pentru calcul, iar rezultatele sunt însumate ponderat. De asemenea, fiecare token efectuează calcule în expertul partajat, iar rezultatul este adăugat la rezultatul de la experții rutați.

Paralelismul de experți (Expert Parallelism - EP) servește drept abordare tipică de partajare pentru straturile MoE, fiecare GPU gestionând 256 / EP experți rutați, menținând în același timp o copie a expertului partajat. În comparație cu TP, avantajul EP este că poate distribui calculul pe mai multe GPU-uri, reducând calculul și utilizarea memoriei pe fiecare GPU.

Înainte de a efectua calculul experților, toate GPU-urile trebuie să efectueze o comunicare AllToAll pentru a expedia tokenurile către GPU-urile unde se află experții corespunzători; după calculul experților, este necesară o altă comunicare AllToAll pentru a colecta rezultatele calculelor de la diferite GPU-uri și a efectua însumarea ponderată. Am implementat o versiune optimizată a acestor două nuclee de comunicare AllToAll, Dispatch și Combine, utilizând NVSHMEM. În postarea noastră de blog anterioară, am detaliat implementarea, iar nucleele noastre au fost lansate ca open-source pe GitHub.

Paralelism de date

Cu EP, putem distribui calculul MoE pe 128 sau chiar mai multe GPU-uri. Cu toate acestea, calculul MLA nu poate fi partiționat cu EP. În acest moment, putem introduce paralelismul de date (Data Parallelism - DP). Fiecare grup DP are o copie completă a stratului MLA. Fiecare grup DP acceptă intrări diferite și efectuează independent calculul stratului MLA.

DP și TP ale stratului MLA pot fi combinate, un grup DP fiind împărțit în mai multe ranguri TP. EP al stratului MoE poate fi combinat cu DP/TP al stratului MLA. EP = DP * TP. De exemplu, pe 16 mașini, EP128 DP32 TP4 înseamnă distribuirea experților rutați pe 128 de GPU-uri, fiecare 4 GPU-uri formând un grup DP, pentru un total de 32 de grupuri DP independente.

Un singur nod vs. Multi-nod

Parametrii de 671B ai DeepSeek depășesc capacitatea de memorie a unei singure mașini H100 cu 8 GPU-uri (80 GB * 8), dar o singură mașină H200 cu 8 GPU-uri poate găzdui pe deplin întregul model (141 GB * 8). Utilizând configurația EP8 DP8 TP1, modelul utilizează aproximativ 100 GB de memorie per GPU, lăsând aproximativ 40 GB pentru memoria cache KV și alte rezultate intermediare. Un token ocupă 70.272 bytes de memorie cache KV. Presupunând că fiecare cerere are 5.000 de tokenuri, fiecare GPU poate găzdui aproximativ 100 de cereri.

Am dorit să înțelegem diferențele de performanță dintre implementările pe un singur nod și cele multi-nod sub diferite configurații. Am folosit o mașină H200 pentru implementarea pe un singur nod și până la 16 mașini H100 pentru implementările multi-nod. Pentru fiecare mediu de implementare, am folosit combinații de TP 1, 2, 4, 8 și dimensiuni de lot per GPU de 1, 2, 4, 8, 16, 32, 64, 128. Am presupus că fiecare cerere a avut o lungime a memoriei cache KV de 5.000 de tokenuri. De asemenea, am presupus că Multi-Token Prediction (MTP) prezice 1 token suplimentar (adică lungimea interogării fiecărei cereri este 2) și am presupus în mod conservator o rată de acceptare de 60%. Figura de mai jos arată debitul și viteza de ieșire pentru diferite configurații.

Frontiere Pareto ale latenței și debitului pentru diferite configurații de paralelism expert.

Axa orizontală reprezintă viteza de ieșire per cerere în tokeni/s. Axa verticală utilizează o scară logaritmică pentru a afișa debitul per mașină în tokeni/s. Am marcat frontiera Pareto (Pareto Frontier) pentru fiecare configurație EP cu linii de culori diferite.

În scenariile cu cerințe de viteză de ieșire extrem de ridicate, utilizarea EP8 DP1 TP8 pe un singur nod cu o dimensiune a lotului de 1 poate realiza o viteză de ieșire care depășește 100 de tokeni/s, dar debitul este extrem de scăzut, echivalent cu viteza de ieșire. În acest scenariu, întregul lot are doar 2 tokeni, care pot fi expediați către cel mult 2*8=16 experți, activând un total de cel mult 57B parametri.

În intervalul de viteză de ieșire de 80-40 tokeni/s, pe măsură ce debitul crește, viteza de ieșire scade semnificativ. În schimb, EP128 are un debit de aproximativ 5 ori mai mare decât implementarea pe un singur nod la aceeași viteză de ieșire.

Acest fenomen poate fi explicat examinând modul în care se comportă implementările pe un singur nod: creșterea dimensiunii lotului se corelează direct cu o creștere a experților activați. Atunci când dimensiunea lotului este 1, numărul mediu de experți activați per GPU este 2 * 8 / 8 = 2. Când lotul este suficient de mare, toți experții sunt activați, ceea ce înseamnă că fiecare GPU activează 256 / 8 = 32 experți. Activarea mai multor experți înseamnă că GPU-ul trebuie să citească mai mulți parametri din memorie, crescând semnificativ presiunea asupra lățimii de bandă a memoriei. Deoarece faza de decodare a modelelor lingvistice mari este deja limitată de lățimea de bandă a memoriei, mai degrabă decât de performanța de calcul, creșterea dimensiunii lotului în implementarea pe un singur nod reduce semnificativ viteza de ieșire.

Comparația celor patru configurații de implementare multi-nod (EP16, EP32, EP64 și EP128) relevă faptul că valorile EP mai mari deplasează frontiera Pareto către îmbunătățiri simultane ale debitului și ale vitezei de ieșire.

Utilizarea unui număr EP mai mare înseamnă că fiecărui GPU i se alocă mai puțini experți. De exemplu, EP128 înseamnă că fiecare GPU este responsabil pentru 256 / 128 = 2 experți, astfel încât presiunea asupra lățimii de bandă a memoriei este redusă semnificativ. Cu alte cuvinte, utilizând un număr EP mai mare, obținem în mod efectiv o lățime de bandă de memorie mai mare. Atunci când dimensiunea lotului per GPU este mai mică de 64, creșterea dimensiunii lotului nu afectează semnificativ viteza de calcul a experților, deoarece creșterea numărului de intrări nu crește semnificativ presiunea asupra lățimii de bandă a memoriei. Prin urmare, observăm că atunci când se utilizează EP128, creșterea dimensiunii lotului nu afectează viteza de ieșire la fel de semnificativ.

Interesant este că, la dimensiuni mai mari ale lotului (64 de cereri per GPU), am observat un fenomen nou: debitul implementării pe un singur nod este ușor mai mare decât cel al implementării multi-nod. O parte din motiv este că NVLink intra-nod are o lățime de bandă mai mare decât InfiniBand inter-nod. O altă parte se datorează limitărilor din implementarea noastră. Vom analiza acest fenomen mai detaliat mai târziu.

Datorită limitărilor de capacitate a memoriei, configurația EP8 DP8 TP1 nu poate atinge o dimensiune a lotului de 128 per GPU, astfel încât implementarea multi-nod rămâne o alegere mai bună în scenariile care urmăresc un debit mai mare.

Suprapunerea calculului și a comunicării

Așa cum s-a prezentat pe scurt mai sus în legătură cu paralelismul de experți, GPU-urile sunt inactive în timpul comunicării stratului MoE. Pentru a reduce risipa și a reduce latența, trebuie să găsim sarcini de calcul independente de date pentru a umple acest timp de inactivitate.

Programe pentru calcul și comunicație fără suprapunere, cu suprapunere la dispatch și cu o conductă în cinci etape.

Partea superioară a figurii de mai sus arată fluxul de calcul al unui singur strat. Calculul MoE depinde de Dispatch, iar calculul stratului următor depinde de rezultatul Combine.

Plasăm expertul partajat pe fiecare GPU. În acest fel, calculul expertului partajat nu necesită comunicare AllToAll. Prin urmare, putem efectua calculul expertului partajat imediat după Dispatch Send, apoi să așteptăm finalizarea Dispatch Recv. Numim această schemă de suprapunere „Dispatch Overlap”.

Dispatch Overlap oferă o implementare simplă și o aplicabilitate largă. Această tehnică ascunde timpul de calcul al expertului partajat pentru toate dimensiunile EP și dimensiunile loturilor.

Pentru a crește și mai mult suprapunerea calculului și a comunicării, am folosit micro-batching menționat în raportul tehnic DeepSeek pentru a întrerupe dependența datelor. Așa cum se arată în partea inferioară a figurii, am împărțit calculul unui strat Transformer în 5 etape:

Etapa 1: InputNorm, QKVProj, AppendKV, BMM

Etapa 2: BMM, Attn, OProj, PostNorm, Gate

Etapa 3: Dispatch Send, Expert partajat

Etapa 4: Dispatch Recv, MoE, Combine Send

Etapa 5: Combine Recv

În primele 3 straturi Transformer dense, folosim întregul lot. În următoarele 58 de straturi Transformer MoE, împărțim uniform lotul în două micro-loturi. Cele două micro-loturi execută alternativ, decalate cu 3 etape. Deoarece nu există nicio dependență de date între aceste două micro-loturi, putem comuta la calculul altui micro-lot după Dispatch Send și după Combine Send.

Detalierea latenței

În continuare, comparăm efectele suprapunerii printr-un experiment, precum și diferențele de performanță dintre implementarea pe un singur nod EP8 și implementarea multi-nod EP128. Pentru o comparație ușoară, am folosit GPU-uri H100 pentru următorul experiment. Am folosit TP1, o dimensiune a lotului de 128 per GPU, o lungime a interogării de 2 per cerere și o lungime a memoriei cache KV de 5000.

Latența pe strat, pe kernel, pentru configurațiile expert-paralele și cele cu suprapunere comparate.

Figura de mai sus arată timpul total petrecut pe un singur strat MoE Transformer și ponderea latenței diferitelor tipuri de nuclee. Cu excepția Dispatch, Combine și GroupGEMM, timpul de execuție al celorlalte nuclee ar trebui să fie egal în seriile EP8, EP128 NoOverlap și EP128 DispatchOverlap, deoarece dimensiunea lotului este aceeași.

Suprapunere

Să comparăm mai întâi efectele celor trei metode de suprapunere. NoOverlap a durat în total 2667 µs, DispatchOverlap a durat 2651 µs, economisind 16 µs sau doar 0,6%. MicroBatch a arătat o îmbunătățire foarte semnificativă, durând 1896 µs, o accelerare de 29%. Atât timpul Dispatch, cât și cel Combine au fost reduse semnificativ. Dispatch a scăzut de la 593 µs la 367 µs, iar Combine de la 1012 µs la 237 µs.

Rețineți că pentru nucleele de calcul, împărțirea unui lot de dimensiune 128 în două loturi de dimensiune 64 crește timpul total de execuție. Prin urmare, deși timpul petrecut pentru comunicare s-a redus cu 1001 µs, timpul total s-a redus doar cu 771 µs. Vom explica motivul utilizând modelul Roofline în secțiunea următoare.

Din acest motiv, micro-batching-ul nu îmbunătățește întotdeauna performanța.

Modificări ale vitezei de ieșire și ale debitului per nod cu micro-batching, reprezentate în funcție de dimensiunea lotului per GPU.

Figura de mai sus arată îmbunătățirea performanței Microbatch comparativ cu DispatchOverlap pentru dimensiunile lotului 4-128. Când dimensiunea lotului este mai mică de 32, Microbatch scade performanța cu 5%-40%. Când dimensiunea lotului este mai mare sau egală cu 32, Microbatch poate îmbunătăți performanța cu 10%-35%.

EP8 vs. EP128

Să ne întoarcem la figura anterioară și să comparăm EP8 și EP128 Microbatch. EP8 a durat în total 1802 µs, puțin mai puțin decât cele 1896 µs ale lui EP128. Pe lângă timpul de execuție crescut al nucleului adus de Microbatch menționat mai sus, principalele diferențe sunt în GroupGEMM utilizat pentru calculul MoE și cele două nuclee de comunicare, Dispatch și Combine.

GroupGEMM al lui EP8 a durat 555 µs, în timp ce GroupGEMM al lui EP128 a durat 270 µs, reducându-se la jumătate. Acesta este avantajul principal al implementării multi-nod.

Din păcate, timpul petrecut pentru comunicare a crescut cu 213 µs, ceea ce a compensat în mare măsură avantajul GroupGEMM. În teste de performanță separate ale nuclerelor noastre de comunicare, am constatat că acestea pot realiza doar jumătate din lățimea de bandă InfiniBand. Vom continua să optimizăm nucleele noastre de comunicare.

Un alt nucleu care rămâne în urmă în mod semnificativ este GEMM. Microbatch a crescut GEMM cu 95 µs. Vom analiza GEMM mai în profunzime în secțiunea Roofline de mai jos. Considerăm că implementarea actuală GEMM nu a obținut încă performanțe optime.

Roofline

Modelul Roofline este un instrument bun pentru analizarea performanței nucleului. Axa sa orizontală este intensitatea aritmetică (Arithmetic Intensity), raportul dintre FLOP și octeții I/O ai memoriei. Valoarea axei orizontale poate fi calculată direct din semantica nucleului. Axa verticală reprezintă performanța obținută, calculată prin împărțirea FLOP la latența de referință (benchmark latency).

Limita superioară teoretică a performanței nucleului este determinată direct de specificațiile GPU-ului. Performanța de vârf FP8 a H100 este de 1979 TFLOP/s, reprezentată ca o linie orizontală în modelul Roofline. Lățimea de bandă a memoriei H100 este de 3,35 TB/s, reprezentată ca panta unei linii care trece prin origine. Cele două linii oferă limitele de performanță pentru nucleele limitate de calcul (compute-bound) și, respectiv, limitate de memorie (memory-bound).

Mai jos, discutăm despre performanța nuclerelor GroupGEMM și GEMM.

GroupGEMM

Nucleul GroupGEMM din MoE efectuează următorul calcul: Există în total g grupuri, al i-lea grup are m_i tokeni, efectuând o înmulțire de matrice de [m_i, k] x [k, n] -> [m_i, n]. În testarea performanței, presupunem că numărul de tokeni din fiecare grup este același, notat ca m_i = m. Atunci numărul FLOP pentru GroupGEMM este 2 * g * m * k * n, iar octeții I/O ai memoriei sunt g * (m * k + n * k + m * n).

În modelul DeepSeek-V3/R1, există 256 de experți, și fiecare token este expediat către 8 experți pentru calcul. Presupunând o dimensiune a lotului de 128, lungimea interogării de 2, utilizând configurația EP128 DP128, numărul mediu de tokeni primiți de fiecare expert (adică m) este 128 * 2 * 8 * 128 / 256 = 1024. În mod similar, putem calcula m pentru alte configurații și dimensiuni ale lotului.

Am utilizat implementarea GroupGEMM a DeepGEMM pentru testarea performanței. Punctele de testare au acoperit combinații de configurații EP8, EP16, EP32, EP64, EP128 cu TP1 și dimensiuni ale lotului 1-128.

Performanța GroupGEMM relativ la liniile de referință pentru calcul și lățime de bandă a memoriei.

Figura de mai sus arată modelul Roofline pentru GroupGEMM sub diferite configurații EP. Diferite EP corespund unui număr diferit de grupuri. Figura ilustrează linii de performanță aproape suprapuse, indicând faptul că performanța GroupGEMM este determinată în mod predominant de numărul total de tokeni (reprezentat ca g * m).

Stelele marchează punctele de date corespunzătoare unei dimensiuni a lotului de 128 per GPU pentru fiecare configurație EP. Comparând aceste puncte de date marcate cu stele, putem vedea că pe măsură ce EP crește (și DP crește sincron), numărul de tokeni per expert m crește și el. La EP8, m=128, în timp ce la EP128, m=2048.

Pe măsură ce m crește, intensitatea aritmetică crește și ea. În majoritatea configurațiilor, GroupGEMM este limitat de lățimea de bandă a memoriei, astfel încât creșterea lui m îmbunătățește performanța.

GEMM

Nucleul GEMM corespunde proiecțiilor Linear din model, cum ar fi proiecția Q/K/V/O. Pentru o înmulțire de matrice de [m, k] x [k, n] -> [m, n], numărul FLOP este 2 * m * k * n, iar octeții I/O ai memoriei sunt m * k + n * k + m * n. De asemenea, putem testa latența pentru dimensiuni ale lotului 1-128.

Performanța GEMM relativ la liniile de referință pentru calcul și lățime de bandă a memoriei.

Figura de mai sus arată modelul Roofline pentru GEMM sub diferite configurații EP. Putem vedea că performanța GEMM este limitată de lățimea de bandă a memoriei. Pe măsură ce dimensiunea lotului crește, intensitatea aritmetică crește și ea, îmbunătățind astfel performanța.

Microbatch

Atunci când utilizăm micro-batching-ul, împărțim lotul în mod egal în două părți. Din cele două figuri de mai sus, putem vedea că atunci când m devine m/2, eficiența înmulțirii de matrice scade. Prin urmare, executarea a două înmulțiri de matrice de dimensiune m/2 durează mai mult decât executarea unei înmulțiri de matrice de dimensiune m.

Predicție multi-token

Pe parcursul acestui articol, am presupus utilizarea Multi-Token Prediction (MTP) pentru decodarea speculativă. MTP modifică lungimea interogării per cerere de la 1 la 2. Pentru înmulțirea de matrice, acest lucru este echivalent cu schimbarea lui m în m * 2, crescând astfel eficiența înmulțirii de matrice. Pe de altă parte, dacă desenăm modelul Roofline pentru MLA, am constata că creșterea lungimii interogării crește semnificativ eficiența nucleului MLA.

Prin urmare, utilizarea MTP joacă un rol important în eficiența modelului.

Implementare și optimizări

În această secțiune, vom prezenta câteva detalii de implementare și optimizare pentru modelul nostru DeepSeek-V3/R1.

Cuantizare

DeepSeek-V3/R1 a fost antrenat nativ pe FP8 utilizând o schemă de cuantizare per bloc (per-block quantization), cu ponderi cuantizate static și activări cuantizate din zbor (on-the-fly). În loc să se calculeze static un factor de scalare per canal sau per matrice, factorii de scalare sunt calculați pe vectori de 128 de elemente pentru activări și plăci (tiles) de 128x128 elemente pentru matrice, limitând degradarea acurateței din cauza cuantizării.

La Perplexity, ne bazăm pe o combinație de nuclee CUDA și Triton pentru a sprijini inferența, CUDA fiind utilizat pentru cele mai sensibile la performanță și rar modificate nuclee (cum ar fi atenția și GEMM), cu Triton implementând o gamă largă de nuclee de activare, normalizare și utilitare. Triton ne-a permis să adaptăm rapid nucleele la schema de cuantizare pe blocuri.

Pentru straturile liniare și MoE, combinăm nucleele Deep GEMM cu propriile noastre nuclee Triton GEMM, deoarece am observat că pentru anumite dimensiuni de matrice și dimensiuni reduse ale lotului, Split-K oferă o latență mai mică. Dacă stratul necuantizat efectuează o înmulțire (M, K) x (K, N), acesta are nevoie de un factor de scalare (M x ceil_div(K, 128)) x (ceil_div(K, 128), ceil_div(N, 128)) pentru cuantizarea pe blocuri. Pentru cuantizarea pe blocuri, factorii de scalare pentru activări sunt calculați din zbor, în loc să fie pre-calibrați. Deoarece factorii de scalare a activării sunt agregați numai de-a lungul dimensiunii K și nu de-a lungul dimensiunii M, nucleele necesită doar modificări minore pentru a sprijini schemă.

Calcul batched SiLU-and-multiply, care arată tensorii de intrare ai experților, tensorii de ieșire și factorii de scalare.

Funcția de activare SiLU utilizată de DeepSeek-V3/R1 a necesitat schimbări substanțiale pentru a suporta graficele CUDA, cuantizarea pe blocuri și numărul de tokeni rutați dinamic. Cuantizarea pe blocuri poate fi problematică deoarece introduce reduceri orizontale, totuși nucleul a împărțit deja activările de-a lungul dimensiunii lor ascunse în blocuri de 1024 de elemente. În cadrul unui bloc, tensorul de cuantizat a fost împărțit în continuare în blocuri de 128 pentru a calcula cea mai mare valoare absolută, Triton generând reduceri maxime cross-warp eficiente, adăugând un overhead minim.

Pentru a sprijini rutarea MoE în cadrul graficelor CUDA, nucleele trebuie să fie conștiente de informațiile de rutare care indică numărul de tokeni per expert, în loc să programeze lucrul pe baza dimensiunii tampoanelor (buffers) care au fost alocate pentru a deține limita superioară a numărului de tokeni. Nu putem împărți problema în funcție de dimensiunile tensorului de intrare, așa că lansăm un număr fix de nuclee persistente care citesc informațiile de rutare pentru a determina câți tokeni sunt populați și împart lucrul de procesare a activărilor între ele în mod dinamic.

Am integrat deja o parte din nucleele noastre în proiectul FlashInfer, iar în viitor vom lansa mai mult din codul nostru ca open-source.

Stratul MLA

Folosim FlashInfer pentru calculul MLA. FlashInfer acceptă setări flexibile ale tabelelor de pagini (Page Table) și performanțe extrem de ridicate.

Am combinat q_a_proj și kv_a_proj într-un singur qkv_a_proj. Latența a scăzut de la 15,4 µs + 14,8 µs = 30,2 µs la 16,7 µs.

Am descompus kv_b_proj în două matrice, k_b_proj și v_b_proj. Am scris un nucleu BMM cu cuantizare pe blocuri FP8 pentru calculele legate de aceste două matrice.

Grafic CUDA

Graficul CUDA (CUDA Graph) poate reduce semnificativ overhead-ul de lansare a nucleului, ceea ce este crucial pentru performanță. Creăm un grafic CUDA pentru fiecare dimensiune a lotului.

Înainte de a dezvolta nucleul nostru AllToAll, am folosit torch.all_to_all_single() pentru comunicarea AllToAll. Această operațiune necesită ca toate GPU-urile să utilizeze aceeași dimensiune a lotului. Cu toate acestea, diferite grupuri DP pot rula dimensiuni diferite ale lotului.

Pentru a asigura compatibilitatea all_to_all_single() cu diferite grupuri DP care utilizează dimensiuni diferite ale lotului, am folosit mai întâi o operațiune allreduce() înainte de fiecare rulare a modelului pentru a obține dimensiunea maximă a lotului dintre toate grupurile DP. Apoi am făcut ca toate grupurile DP să utilizeze această dimensiune a lotului pentru rulare.

Deși această abordare garantează că putem utiliza graficul CUDA, are trei dezavantaje. În primul rând, necesită o operațiune allreduce() suplimentară. În al doilea rând, grupurile DP cu dimensiuni mai mici ale lotului sunt obligate să adauge umplutură (pad). În al treilea rând, face ca codul nostru de implementare să fie complex.

După ce am implementat propriul nostru nucleu AllToAll, nu mai cerem ca toate GPU-urile să utilizeze aceeași dimensiune a lotului. Prin urmare, nu mai trebuie să efectuăm operațiuni allreduce() suplimentare sau să adăugăm umplutură la dimensiunile loturilor.

Ruterul MoE

Ruterul MoE este implementat în Triton, bazându-se pe o sortare modificată derivată din biblioteca standard care urmărește, de asemenea, indicii elementelor sortate. Implementarea este partajată între toate modelele MoE, deoarece rutarea Mixtral este un caz special al rutelor DeepSeek unde grupul Top-K este același cu grupul tuturor experților. Nucleele sparse consumă direct indicii și scorurile Top-K, în timp ce schemele dense de expediere/combinare bazate pe all-to-all necesită ca informațiile de rutare să fie agregate per-expert în loc de o bază per-token.

Muncă viitoare

În lucrările viitoare, intenționăm să optimizăm în continuare performanța modelului DeepSeek.

Cea mai importantă optimizare viitoare este dezagregarea preumplerii (Prefill Disaggregation). Faza de preumplere (Prefill) și faza de decodare (Decode) ale modelului DeepSeek-V3/R1 au caracteristici de calcul foarte diferite. Ambele pot utiliza strategii de optimizare și scheme de implementare diferite.

Pentru stratul MLA, în faza de decodare, utilizăm absorbția matricială (Matrix Absorption) pentru a reduce numărul FLOP al calculului MLA. În faza de preumplere, proiectarea mai întâi a vectorului latent în spațiul K/V și apoi calcularea în forma Multi-Head Attention (MHA) ar funcționa mai bine.

Dacă preumplerea și decodarea rulează pe același GPU, pentru a reduce impactul preumplerii asupra vitezei de ieșire a decodării, utilizăm de obicei preumplerea pe bucăți (chunked prefill) pentru a împărți interogarea în mai multe bucăți pentru preumplere. Deoarece memoria cache KV stochează vectorul latent, devine dificil să se convertească MLA în forma MHA.

Pentru stratul MoE, în faza de decodare, utilizăm un EP și DP cât mai mari posibil pentru a crește numărul de tokeni de intrare per expert, îmbunătățind astfel performanța GroupGEMM. În faza de preumplere, deoarece numărul de tokeni este deja suficient de mare, GroupGEMM este deja limitat de calcul. Prin urmare, pentru preumplere, putem utiliza EP și DP mai mici.

Dacă preumplerea și decodarea rulează pe același GPU, atâta timp cât orice grup DP efectuează preumplerea, latența straturilor MoE de pe toate GPU-urile va crește, afectând semnificativ viteza de ieșire a decodării.

Pe lângă dezagregarea preumplerii, intenționăm să optimizăm și următoarele aspecte:

Performanța AllToAll: Nucleul nostru AllToAll poate obține în prezent doar 1/3 din lățimea de bandă InfiniBand. Vom continua să optimizăm acest nucleu.

Decodare speculativă în stil EAGLE: În datele de mai sus, am presupus utilizarea decodării speculative pentru a prezice 1 token. EAGLE poate utiliza o structură de arbore pentru a prezice mai multe tokeni, îmbunătățind lungimea de acceptare, ceea ce poate crește semnificativ viteza de ieșire.

Nucleul GEMM: În modelul Roofline prezentat anterior, putem descoperi că eficiența nucleului GEMM este încă departe de limita teoretică. Vom continua să optimizăm acest nucleu.

GB200 NVL72: În cea mai recentă soluție GB200 NVL72 de la NVIDIA, 72 de GPU-uri Blackwell sunt interconectate prin NVLink de mare viteză. Pentru modelele cu arhitectură MoE, aceasta este o oportunitate și o provocare foarte mare.

Concluzie

Implementarea multi-nod a modelelor DeepSeek MoE realizează ceea ce este de obicei imposibil cu LLM-urile dense: îmbunătățirea simultană atât a debitului, cât și a latenței. Distribuind experții pe mai multe GPU-uri, reducem presiunea asupra lățimii de bandă a memoriei per dispozitiv, permițând o procesare mai rapidă și un debit mai mare al sistemului. Experimentele noastre arată că configurațiile EP128 obțin un debit de până la 5 ori mai mare la viteze de ieșire echivalente în comparație cu implementările pe un singur nod.

Tehnicile de suprapunere calcul-comunicare, cum ar fi micro-batching-ul, reduc semnificativ overhead-ul de comunicare multi-nod, implementarea noastră arătând o accelerare de până la 40%. Nucleele noastre personalizate de comunicare AllToAll și implementările optimizate ale nucleelor au permis implementarea eficientă a modelului cu 671B parametri.

Pe măsură ce arhitecturile MoE câștigă popularitate pentru capabilitățile lor, aceste strategii de implementare oferă perspective valoroase pentru scalarea eficientă a unor astfel de modele.

Referințe

Raport tehnic DeepSeek-V3

Prezentare generală a sistemului de inferență DeepSeek-V3/R1

Megatron-LM: Antrenarea modelelor lingvistice cu miliarde de parametri utilizând paralelismul de modele

EAGLE: Eșantionarea speculativă necesită regândirea incertitudinii caracteristicilor

Citare

BibTeX
@online{perplexity2025LowerLatencyAndHigherThroughputWithMultiNodeDeepseekDeployment,
  author = {{Perplexity} and Lequn Chen and Nandor Licker and Erin McKnight},
  title = {{Lower latency and higher throughput with multi-node DeepSeek deployment}},
  date = {2025-04-18},
  url = {https://perplexity.coolkeypoint.com/hub/blog/lower-latency-and-higher-throughput-with-multi-node-deepseek-deployment}
}