Aveți probleme cu inteligența artificială sau cu dezvoltarea full-stack? Experții noștri sunt aici pentru a vă ghida: consiliere personalizată, integrare tehnică și multe altele. Contactați-ne la [email protected].

Tehnici de optimizare a inferenței LLM

Optimizarea inferenței este o parte esențială a aplicațiilor de inteligență artificială generativă implementate în producție. Utilizarea eficientă a LLM-urilor la scară largă este o provocare și multe tehnici au fost dezvoltate în ultimii ani pentru a face inferența mai rapidă și mai ieftină. Să trecem în revistă aceste tehnici în acest articol.

Tehnici de optimizare a inferenței LLM

Un accent pe arhitectura LLM-urilor

Modelele lingvistice mari (LLM) se bazează toate pe arhitectura transformatorului inventată în 2017 de Vaswani et al. Arhitectura transformatorului obține o precizie superioară, o învățare în câteva etape și abilități aproape umane în diverse sarcini lingvistice. Cu toate acestea, aceste modele de bază, care cuprind adesea zeci până la sute de miliarde de parametri, sunt costisitoare pentru formare și consumatoare de resurse în timpul inferenței. Costurile de inferență cresc în cazul contextelor de intrare lungi, care necesită o putere de procesare semnificativă din cauza datelor de intrare mari. Aceasta face din inferența eficientă o provocare esențială, în special în gestionarea resurselor de memorie și de calcul.

Arhitectura transformatorului
Arhitectura transformatorului

Mai precis, cele mai cunoscute LLM-uri sunt LLM-uri numai pentru decodor, precum GPT-3, GPT-4, LLaMA, Mistral, DeepSeek etc. Aceste modele sunt antrenate în prealabil pe o sarcină de modelare cauzală, funcționând ca predictori ai cuvântului următor. Acestea procesează o secvență de cuvinte ca intrare și produc următoarele cuvinte în mod autoregresiv până când este atinsă o condiție de oprire.

Inferența LLM în modelele exclusiv de decodare implică două faze-cheie: faza de prefill și faza de decodare. În faza de prefill, modelul procesează simbolurile de intrare pentru a calcula stările intermediare (chei și valori) pentru generarea primului simbol nou. Această fază, care seamănă cu o operație matrice-matrice, este puternic paralelizată și utilizează eficient capacitățile GPU. În schimb, faza de decodare generează jetoane, unul câte unul, bazându-se pe stările jetoanelor anterioare. Această operație matrice-vector este legată de memorie, deoarece transferul de date către GPU, mai degrabă decât viteza de calcul, dictează în primul rând latența, ceea ce duce la subutilizarea puterii de calcul GPU.

Optimizarea fazei de decodare este un punct central pentru abordarea provocărilor legate de inferență. Soluțiile includ dezvoltarea unor mecanisme eficiente de atenție și o mai bună gestionare a cheilor și a valorilor pentru a reduce blocajele de memorie. Postul evidențiază abordări practice pentru îmbunătățirea performanței de inferență, presupunând că cititorii au o înțelegere de bază a arhitecturii transformatorului și a mecanismelor de atenție. Aceste optimizări sunt esențiale pentru îmbunătățirea randamentului și reducerea latenței în implementările LLM din lumea reală.

O altă complicație provine din utilizarea unor tokenizere diferite în cadrul LLM-urilor, ceea ce afectează comparabilitatea token-urilor. Tokenurile, aproximativ echivalente cu patru caractere englezești, variază ca reprezentare în funcție de tokenizator, ceea ce face ca comparațiile directe ale debitului de inferență (de exemplu, tokenuri pe secundă) să fie înșelătoare. Această variabilitate subliniază necesitatea unor metrici de evaluare standardizate pentru a evalua și compara cu exactitate performanța LLM în timpul inferenței.

Repartizare

Batching-ul este o strategie cheie pentru îmbunătățirea utilizării GPU și a randamentului în modelele lingvistice mari (LLM). Prin procesarea simultană a mai multor cereri utilizând același model, batching-ul distribuie costul de memorie al ponderilor modelului între cereri, permițând loturilor mai mari să utilizeze mai multă putere de calcul GPU. Cu toate acestea, există o limită pentru dimensiunea loturilor, deoarece loturile excesiv de mari pot provoca o depășire a memoriei din cauza cerințelor de memorie ale LLM-urilor, în special în ceea ce privește cache-ul cheie-valoare (KV) (mai multe despre acest lucru mai târziu).

Tehnici de repartizare pe loturi
Tehnici de repartizare pe loturi

Gruparea tradițională sau statică are limitări deoarece solicitările dintr-un lot generează adesea un număr diferit de jetoane de finalizare, ceea ce duce la timpi de execuție variați. Acest lucru face ca toate cererile să aștepte finalizarea celei mai lente, ceea ce poate fi problematic atunci când lungimile de generare variază semnificativ. Pentru a rezolva această problemă, au fost dezvoltate tehnici avansate, cum ar fi batching-ul în zbor, pentru a optimiza performanța.

Batching-ul în zbor, cunoscut și sub denumirea de batching continuu, abordează provocările reprezentate de natura dinamică a sarcinilor de lucru LLM, care pot varia de la simple răspunsuri chatbot la rezumarea complexă a documentelor sau generarea de coduri. Aceste sarcini produc rezultate de dimensiuni extrem de diferite, ceea ce face dificilă gruparea și executarea eficientă a cererilor în paralel. Spre deosebire de batchingul static, batchingul în zbor permite serverului să evacueze imediat secvențele finalizate din lot și să înceapă să proceseze cereri noi în timp ce altele sunt încă în desfășurare. Această abordare sporește semnificativ utilizarea GPU prin adaptarea la timpii variabili de execuție a cererilor în scenarii reale.

Implementare multi-GPU cu paralelizarea modelelor

Paralelizarea modelelor este o strategie esențială pentru gestionarea cerințelor de memorie și de calcul ale modelelor de învățare automată la scară largă prin distribuirea acestora pe mai multe GPU-uri. Această abordare permite gestionarea modelelor mai mari sau a loturilor de intrare care depășesc capacitatea de memorie a unui singur dispozitiv, ceea ce o face esențială atât pentru formare, cât și pentru inferență atunci când constrângerile de memorie sunt stricte. Există diverse tehnici de divizare a ponderilor modelului, inclusiv paralelismul de conducte, paralelismul tensorial și paralelismul de secvențe, fiecare abordând aspecte diferite ale distribuției modelului. Spre deosebire de paralelismul datelor, care se concentrează pe replicarea ponderilor modelului între dispozitive pentru a procesa loturi de intrare mai mari în timpul formării, aceste metode sunt mai relevante pentru reducerea amprentei de memorie atât în timpul formării, cât și în timpul inferenței.

Mai multe GPU-uri NVIDIA
Mai multe GPU-uri NVIDIA

Paralelismul conductei împarte modelul pe verticală în bucăți secvențiale, fiecare bucată conținând un subset de straturi atribuite unui dispozitiv separat. De exemplu, într-o configurație pipeline cu patru căi, fiecare dispozitiv gestionează un sfert din straturile modelului, transmițând ieșirile la următorul dispozitiv în succesiune. Deși acest lucru reduce semnificativ cerințele de memorie per dispozitiv, introduce ineficiențe cunoscute sub numele de "bule de conducte", în care dispozitivele pot sta inactive în așteptarea ieșirilor din straturile anterioare. Microbatching-ul, care împarte loturile de intrare în subloturi mai mici pentru procesare secvențială, poate reduce aceste bule, dar nu le poate elimina complet, deoarece timpii morți persistă în timpul trecerilor înainte și înapoi.

Paralelismul tensorial, în schimb, împarte straturile individuale pe orizontală în blocuri de calcul mai mici care pot fi executate independent pe mai multe dispozitive. Acest lucru este deosebit de eficient pentru componentele transformatorului, cum ar fi blocurile de atenție și perceptronii multistrat (MLP), unde, de exemplu, diferite capete de atenție pot fi alocate unor dispozitive separate pentru calcul paralel. Cu toate acestea, paralelismul tensorial este mai puțin eficient pentru operații precum LayerNorm și Dropout, care nu pot fi ușor divizate și trebuie să fie replicate între dispozitive, ceea ce duce la utilizarea redundantă a memoriei pentru stocarea activărilor. Această limitare evidențiază necesitatea unor abordări complementare pentru optimizarea eficienței memoriei.

Paralelismul secvențelor abordează ineficiențele de memorie ale unor operații precum LayerNorm și Dropout prin împărțirea acestora de-a lungul dimensiunii secvenței de intrare, valorificând independența lor între elementele secvenței. Această metodă reduce amprenta de memorie a activărilor redundante, ceea ce o face o completare valoroasă a paralelismului tensorial. Aceste tehnici de paralelizare nu se exclud reciproc și pot fi combinate pentru a optimiza în continuare modelele lingvistice mari (LLM). În plus, strategiile de optimizare specifice pentru modulul de atenție pot spori scalabilitatea și pot reduce cerințele de memorie per-GPU, permițând o formare și o inferență mai eficiente pentru modelele mari.

Optimizarea atenției

Lucrarea din 2017 *Attention Is All You Need* de Vaswani et al. a introdus modelul Transformer, având ca piatră de temelie autoatenția. Atenția de sine permite modelului să evalueze relevanța diferitelor cuvinte dintr-o propoziție în raport cu celelalte, îmbunătățind înțelegerea contextuală pentru sarcini precum procesarea limbajului natural. Lucrarea a formalizat autoatenția, în special prin mecanismul SDPA (scaled dot-product attention), care mapează interogarea și perechile cheie-valoare la o ieșire, făcând din aceasta o componentă esențială în rețelele neuronale moderne. Iată câteva dintre cele mai importante tehnici de optimizare a calculelor de atenție:

Hârtia de atenție
Hârtia de atenție

Atenția multi-head (MHA) se bazează pe SDPA prin rularea în paralel a mai multor operațiuni de atenție, fiecare cu proiecții distincte ale matricilor de interogare, cheie și valoare. Aceste operațiuni paralele, sau "capete", se concentrează pe diferite subspații de reprezentare, îmbogățind înțelegerea de către model a datelor de intrare. Rezultatele acestor capuri sunt concatenate și proiectate liniar, menținând o eficiență computațională comparabilă cu atenția cu un singur cap prin reducerea dimensionalității fiecărui cap (de exemplu, împărțirea dimensiunii modelului la numărul de capete, cum ar fi 8).

Multi-query attention (MQA) optimizează MHA pentru inferență prin partajarea proiecțiilor de chei și valori între mai multe capete de atenție, păstrând în același timp mai multe proiecții de interogare. Acest lucru reduce cererea de lățime de bandă de memorie și dimensiunea cache-ului cheie-valoare (KV), permițând dimensiuni mai mari ale loturilor și o mai bună utilizare a calculatorului. Cu toate acestea, MQA poate reduce ușor acuratețea, iar modelele care îl utilizează necesită antrenament sau reglaj fin cu MQA activat pentru a menține performanța.

Grouped-query attention (GQA) echilibrează MHA și MQA prin gruparea capetelor de interogare și partajarea proiecțiilor cheie-valoare în cadrul fiecărui grup, obținând o calitate apropiată de MHA cu o eficiență computațională mai apropiată de MQA. Modele precum Llama 2 70B utilizează GQA, iar cele instruite cu MHA pot fi adaptate la GQA cu o instruire suplimentară minimă. Atât MQA, cât și GQA reduc solicitările de memorie cache KV, deși rămân necesare optimizări suplimentare în gestionarea cache-ului.

FlashAttention îmbunătățește mecanismele de atenție prin reordonarea calculelor pentru a utiliza mai eficient ierarhiile de memorie GPU. Spre deosebire de procesarea tradițională strat cu strat, FlashAttention fuzionează operațiile și utilizează "tiling" pentru a calcula odată porțiuni mici ale matricei de ieșire, minimizând operațiile de citire/scriere în memorie. Acest algoritm de atenție exactă, conștient de I/O, se integrează perfect în modelele existente fără modificări, oferind accelerări semnificative prin optimizarea mișcării datelor.

Caching cheie-valoare

KV caching este o tehnică de optimizare critică utilizată în timpul fazei de decodare a modelelor lingvistice mari (LLM) pentru a îmbunătăți eficiența calculelor de autoatenție. În această fază, fiecare jeton generat depinde de tensorii cheie (K) și valoare (V) ai tuturor jetonilor anteriori, inclusiv cei calculați în timpul etapei de prefill și a etapelor de decodare ulterioare. În loc să recalculeze acești tensori pentru fiecare jeton la fiecare pas de timp, memoria cache KV îi stochează în memoria GPU, adăugând noi tensori în cache pe măsură ce aceștia sunt calculați. De obicei, se păstrează o cache KV separată pentru fiecare strat al modelului, reducând semnificativ calculele redundante și accelerând procesul de decodare.

Caching cheie-valoare
Caching cheie-valoare

Cerințele de memorie pentru LLM-uri pe GPU-uri sunt determinate în principal de două componente: ponderile modelului și cache-ul KV. Ponderile modelului, care constau în parametrii modelului, ocupă o memorie substanțială; de exemplu, un model cu 7 miliarde de parametri precum Llama 2 7B cu o precizie de 16 biți necesită aproximativ 14 GB. Pe de altă parte, memoria cache KV stochează tensori de autoatenție pentru a evita recalcularea, dimensiunea sa fiind determinată de factori precum numărul de straturi, capetele de atenție, dimensiunile capetelor și precizia. Pentru fiecare jeton, dimensiunea memoriei cache este calculată ca fiind 2 * num_layers * (num_heads * dim_head) * precision_in_bytes, unde factorul 2 ia în considerare atât matricele K, cât și matricele V. Pentru un lot de intrări, dimensiunea totală a cache-ului KV variază în funcție de dimensiunea lotului și de lungimea secvenței, putând atinge dimensiuni semnificative, cum ar fi ~2 GB pentru un model Llama 2 7B cu o lungime a secvenței de 4 096 și o dimensiune a lotului de 1.

Gestionarea eficientă a cache-ului KV ridică probleme din cauza creșterii sale liniare în funcție de dimensiunea lotului și de lungimea secvenței, ceea ce poate limita randamentul și complica gestionarea intrărilor cu contexte lungi. O ineficiență comună rezultă din supraaprovizionarea statică, în care memoria este rezervată pentru lungimea maximă a secvenței acceptate (de exemplu, 2 048 de jetoane), indiferent de dimensiunea reală a intrării. Acest lucru duce la risipirea sau fragmentarea semnificativă a memoriei, deoarece o mare parte din spațiul rezervat rămâne adesea neutilizat pe toată durata de viață a cererii, ocupând resurse valoroase de memorie GPU.

Pentru a aborda aceste ineficiențe, algoritmul PagedAttention introduce o nouă abordare inspirată de paginarea sistemului de operare. Acesta împarte memoria cache KV în blocuri de mărime fixă, fiecare reprezentând un număr stabilit de jetoane, care pot fi stocate necontenit în memorie. Un tabel de blocuri urmărește aceste blocuri, recuperându-le după cum este necesar în timpul calculelor de atenție. Pe măsură ce sunt generate noi jetoane, blocuri suplimentare sunt alocate dinamic. Această metodă minimizează risipa de memorie prin eliminarea nevoii de alocare contiguă și supraaprovizionare, permițând dimensiuni mai mari ale loturilor și îmbunătățirea randamentului, ceea ce o transformă într-un progres semnificativ în gestionarea memoriei cache KV pentru LLM-uri.

Optimizarea modelului

În această secțiune discutăm diverse tehnici de optimizare a modelelor lingvistice mari (LLM) pentru a reduce consumul de memorie al acestora și pentru a îmbunătăți performanța pe GPU. Metodele cheie includ cuantizarea, sparitatea și distilarea, fiecare vizând diferite aspecte ale eficienței modelului. Aceste tehnici modifică ponderile modelelor, utilizează accelerarea hardware-ului GPU și transferă cunoștințele către modele mai mici, permițând modelelor mai mari să ruleze pe hardware limitat, menținând în același timp performanța. Aceste metode pot degrada acuratețea modelului, astfel încât trebuie utilizate cu precauție.

Cuantizarea reduce precizia ponderilor și a activărilor unui model, de obicei de la 32 sau 16 biți la 8 sau mai puțini biți, permițând modelelor să ocupe mai puțină memorie și să transfere date mai eficient. În timp ce cuantificarea ponderilor este simplă datorită naturii lor fixe după formare, cuantificarea activărilor este mai complexă din cauza valorilor aberante care extind gama lor dinamică. Tehnici precum LLM.int8() abordează acest aspect prin aplicarea selectivă a unei precizii mai mari anumitor activări sau prin reutilizarea intervalului dinamic al ponderilor cuantificate pentru activări, deși GPU-urile pot necesita convertirea ponderilor înapoi la o precizie mai mare pentru operații.

Sparitatea implică reducerea valorilor modelului aproape de zero, creând matrici rare care necesită mai puțină memorie. GPU-urile acceptă sparitatea structurată, cum ar fi reprezentarea a două din patru valori ca zerouri, ceea ce accelerează calculele. Combinarea sparsității cu cuantizarea poate spori și mai mult viteza de execuție. Cercetările continuă să exploreze reprezentările dispersate optime pentru LLM-uri, indicând o cale promițătoare pentru îmbunătățirea vitezelor de inferență.

Distilarea transferă cunoștințele de la un model "profesor" mai mare la un model "elev" mai mic, comprimând dimensiunea și păstrând în același timp performanța. De exemplu, DistilBERT realizează o reducere a dimensiunii cu 40% și o creștere a vitezei cu 60% în comparație cu BERT, păstrând 97% din capacitățile sale. Distilarea poate implica mimarea rezultatelor profesorului sau utilizarea datelor generate de profesor pentru formare, cu metode precum "Distilling Step by Step!", care încorporează raționamente pentru o învățare eficientă. Cu toate acestea, licențele restrictive pentru multe LLM-uri avansate limitează disponibilitatea modelelor de profesori adecvate pentru distilare.

Inferență speculativă

Inferența speculativă, cunoscută și sub denumirea de eșantionare speculativă sau generare asistată, este o metodă de paralelizare a execuției modelelor autoregresive de limbaj de mari dimensiuni (LLM), cum ar fi modelele de tip GPT, care generează de obicei text jeton cu jeton. În execuția standard, fiecare jeton depinde de toate jetonurile anterioare pentru context, ceea ce face imposibilă generarea paralelă, deoarece al n-lea jeton trebuie generat înainte de al (n+1)-lea. Inferența speculativă rezolvă această problemă prin utilizarea unui model preliminar "mai ieftin" pentru a prezice simultan mai multe token-uri viitoare, care sunt apoi verificate sau respinse în paralel de modelul principal, permițând generarea mai rapidă a textului.

Procesul implică generarea unui proiect de continuare a mai multor jetoane folosind o metodă mai puțin consumatoare de resurse, urmată de verificarea paralelă de către modelul principal folosind proiectul ca context speculativ. Dacă modelul de verificare se potrivește cu simbolurile proiectului, acestea sunt acceptate; în caz contrar, simbolurile care nu se potrivesc și cele ulterioare sunt eliminate, iar procesul se repetă cu un nou proiect. Proiectele de cuvinte cheie pot fi generate folosind diverse abordări, cum ar fi formarea mai multor modele, reglarea mai multor capete pe un model pre-format pentru a prezice viitoarele cuvinte cheie sau utilizarea unui model de proiect mai mic alături de un model de verificare mai mare și mai capabil, fiecare cu propriile compromisuri.

Inferență dezagregată

Inferența dezagregată este o tehnică în care sarcinile de calcul sunt împărțite pe diferite echipamente hardware pentru a optimiza performanța, costul și utilizarea resurselor. Mai exact, aceasta separă fazele de precompletare și decodificare. Prin dezagregarea acestor faze, fiecare poate fi atribuită hardware-ului cel mai potrivit pentru cerințele sale de calcul, îmbunătățind eficiența și scalabilitatea.

Inferență dezagregată
Inferență dezagregată

Precompletarea este intensivă din punct de vedere al calculului, necesitând multiplicări matriceale semnificative pentru a procesa întregul prompt de intrare și a produce cache-uri KV. Această fază beneficiază de hardware de înaltă performanță precum GPU sau TPU, care excelează la calcule paralele. Deoarece precompletarea este o sarcină unică pentru fiecare cerere de inferență, aceasta poate fi transferată către un nod de calcul centralizat, puternic, optimizat pentru astfel de sarcini de lucru. Această configurație permite procesarea mai rapidă a solicitărilor mari și reduce sarcina asupra dispozitivelor mai puțin capabile, ceea ce o face ideală pentru mediile bazate pe cloud sau centrele de date în care este disponibil hardware cu randament ridicat.

Decodarea, în schimb, este legată de memorie și implică generarea iterativă a jetoanelor, bazându-se foarte mult pe accesarea cache-urilor KV. Aceasta necesită mai puțină putere de calcul, dar necesită acces rapid la memorie, ceea ce o face potrivită pentru hardware mai puțin puternic, optimizat pentru memorie, cum ar fi procesoarele sau dispozitivele periferice. Prin mutarea decodificării pe hardware separat - potențial mai aproape de utilizatorul final, cum ar fi serverele locale sau dispozitivele periferice - inferența dezagregată reduce latența și lățimea de bandă a rețelei. Această separare permite o implementare flexibilă, în care precompletarea rulează pe servere cloud high-end, iar decodificarea are loc pe dispozitive locale sau periferice, optimizând alocarea resurselor și permițând scalarea eficientă pentru aplicații precum chatbots în timp real sau sisteme AI interactive.

Concluzie

Multe tehnici de optimizare a inferențelor au fost inventate recent pentru a îmbunătăți performanța LLM-urilor.

Implementarea acestor tehnici necesită o înțelegere profundă a arhitecturii LLM și a hardware-ului pe care îl utilizați, astfel încât, în general, este mai ușor să utilizați un motor de inferență existent care a implementat deja aceste tehnici, cum ar fi vLLM, TensorRT-LLM, LMDeploy etc. De fapt, am implementat aceste tehnici în propriul nostru motor de inferență la NLP Cloud și am scris o postare pe blog despre motoarele de inferență dacă doriți să vă implementați propriile modele: îl puteți citi aici.

Dacă nu puteți sau nu doriți să vă implementați propriile LLM-uri, puteți utiliza NLP Cloud și să profitați de modele AI generative rapide la scară largă în producție. Încercați acum inferența rapidă pe NLP Cloud!

Dacă aveți întrebări despre motoarele de inferență în general, vă rugăm să nu ezitați să ne întrebați, este întotdeauna o plăcere să vă sfătuim!

Julien
CTO la NLP Cloud