In questo post del blog, il team di ingegneri di MosaicML condivide le best practice su come sfruttare i modelli linguistici di grandi dimensioni (LLM) open source più diffusi per l'uso in produzione. Forniamo anche linee guida per la distribuzione di servizi di inferenza creati attorno a questi modelli per aiutare gli utenti nella selezione dei modelli e dell'hardware di distribuzione. Abbiamo lavorato con diversi backend basati su PyTorch in produzione; queste linee guida sono tratte dalla nostra esperienza con FasterTransformers, vLLM, TensorRT-LLM di NVIDIA, di prossima uscita, e altri.
Informazioni sulla generazione di testo LLM
I modelli linguistici di grandi dimensioni (LLM) generano testo in un processo in due fasi: "prefill", in cui i token nel prompt di input vengono elaborati in parallelo, e "decodifica", in cui il testo viene generato un 'token' alla volta in modo autoregressivo. Ogni token generato viene aggiunto all'input e reinserito nel modello per generare il token successivo. La generazione si arresta quando l'LLM restituisce un token di arresto speciale o quando viene soddisfatta una condizione definita dall'utente (ad esempio, è stato generato un numero massimo di token). Se desideri maggiori informazioni su come gli LLM utilizzano i blocchi del decoder, consulta questo post del blog.
I token possono essere parole o sotto-parole; le regole esatte per la suddivisione del testo in token variano da modello a modello. Ad esempio, puoi confrontare come i modelli Llama tokenizzano il testo con come i modelli OpenAI tokenizzano il testo. Sebbene i provider di inferenza LLM parlino spesso di prestazioni in metriche basate sui token (ad esempio, token/secondo), questi numeri non sono sempre confrontabili tra i tipi di modello date queste variazioni. Per un esempio concreto, il team di Anyscale ha scoperto che la tokenizzazione di Llama 2 è più lunga del 19% rispetto alla tokenizzazione di ChatGPT (ma ha comunque un costo complessivo molto inferiore). E i ricercatori di HuggingFace hanno anche scoperto che Llama 2 richiedeva circa il 20% di token in più per l'addestramento sulla stessa quantità di testo di GPT-4.
Metriche importanti per il serving di LLM
Quindi, come dovremmo pensare esattamente alla velocità di inferenza?
Il nostro team utilizza quattro metriche chiave per il serving di LLM:
- Tempo al primo token (TTFT): la velocità con cui gli utenti iniziano a visualizzare l'output del modello dopo aver inserito la query. Tempi di attesa brevi per una risposta sono essenziali nelle interazioni in tempo reale, ma meno importanti nei carichi di lavoro offline. Questa metrica è determinata dal tempo necessario per elaborare il prompt e quindi generare il primo token di output.
- Tempo per token di output (TPOT): tempo per generare un token di output per ogni utente che esegue query sul nostro sistema. Questa metrica corrisponde al modo in cui ogni utente percepirà la "velocità" del modello. Ad esempio, un TPOT di 100 millisecondi/token sarebbe di 10 token al secondo per utente, ovvero circa 450 parole al minuto, che è più veloce di quanto una persona tipica possa leggere.
- Latenza: il tempo complessivo necessario al modello per generare la risposta completa per un utente. La latenza complessiva della risposta può essere calcolata utilizzando le due metriche precedenti: latenza = (TTFT) + (TPOT) * (il numero di token da generare).
- Throughput: il numero di token di output al secondo che un server di inferenza può generare per tutti gli utenti e le richieste.
Il nostro obiettivo? Il tempo più veloce per il primo token, il throughput più elevato e il tempo più rapido per token di output. In altre parole, vogliamo che i nostri modelli generino testo il più velocemente possibile per il maggior numero di utenti che possiamo supportare.
In particolare, esiste un compromesso tra throughput e tempo per token di output: se elaboriamo 16 query utente contemporaneamente, avremo un throughput più elevato rispetto all'esecuzione sequenziale delle query, ma impiegheremo più tempo per generare token di output per ogni utente.
Se hai obiettivi di latenza di inferenza complessiva, ecco alcune euristiche utili per la valutazione dei modelli:
- La lunghezza dell'output domina la latenza complessiva della risposta: per la latenza media, di solito puoi semplicemente prendere la lunghezza del token di output prevista/massima e moltiplicarla per un tempo medio complessivo per token di output per il modello.
- La lunghezza dell'input non è significativa per le prestazioni, ma importante per i requisiti hardware: l'aggiunta di 512 token di input aumenta la latenza meno della produzione di 8 token di output aggiuntivi nei modelli MPT. Tuttavia, la necessità di supportare input lunghi può rendere più difficile il serving dei modelli. Ad esempio, consigliamo di utilizzare A100-80GB (o versioni successive) per il serving di MPT-7B con la sua lunghezza di contesto massima di 2048 token.
- La latenza complessiva si ridimensiona in modo sublineare con le dimensioni del modello: sullo stesso hardware, i modelli più grandi sono più lenti, ma il rapporto di velocità non corrisponderà necessariamente al rapporto di conteggio dei parametri. La latenza di MPT-30B è circa 2,5 volte quella della latenza di MPT-7B. La latenza di Llama2-70B è circa 2 volte quella della latenza di Llama2-13B.
I potenziali clienti ci chiedono spesso di fornire una latenza di inferenza media. Ti consigliamo di dedicare del tempo a caratterizzare la lunghezza dell'input previsto e dell'output desiderato prima di ancorarti a obiettivi di latenza specifici ("abbiamo bisogno di meno di 20 ms per token").
Difficoltà nell'inferenza LLM
L'ottimizzazione dell'inferenza LLM beneficia di tecniche generali come:
- Fusione di operatori: la combinazione di diversi operatori adiacenti spesso si traduce in una latenza migliore.
- Quantizzazione: le attivazioni e i pesi vengono compressi per utilizzare un numero inferiore di bit.
- Compressione: sparsità o distillazione.
- Parallelizzazione: parallelismo tensoriale su più dispositivi o parallelismo della pipeline per modelli più grandi.
Oltre a questi metodi, ci sono molte importanti ottimizzazioni specifiche per Transformer. Un ottimo esempio è la memorizzazione nella cache KV (chiave-valore). Il meccanismo di attenzione nei modelli basati su Transformer solo decoder è inefficiente dal punto di vista computazionale. Ogni token partecipa a tutti i token visti in precedenza e quindi ricalcola molti degli stessi valori man mano che viene generato ogni nuovo token. Ad esempio, durante la generazione dell'N-esimo token, l'(N-1)-esimo token partecipa all'(N-2)-esimo, (N-3)-esimo... 1° token. Allo stesso modo, durante la generazione dell'(N+1)-esimo token, l'attenzione per l'N-esimo token deve nuovamente esaminare l'(N-1)-esimo, (N-2)-esimo, (N-3)-esimo, ... 1° token. La memorizzazione nella cache KV, ovvero il salvataggio di chiavi/valori intermedi per i livelli di attenzione, viene utilizzata per conservare tali risultati per un successivo riutilizzo, evitando calcoli ripetuti.
La larghezza di banda della memoria è fondamentale
I calcoli negli LLM sono dominati principalmente da operazioni di moltiplicazione matrice-matrice; queste operazioni con piccole dimensioni sono in genere vincolate alla larghezza di banda della memoria sulla maggior parte dell'hardware. Quando si generano token in modo autoregressivo, una delle dimensioni della matrice di attivazione (definita dalla dimensione del batch e dal numero di token nella sequenza) è piccola a piccole dimensioni del batch. Pertanto, la velocità dipende dalla velocità con cui possiamo caricare i parametri del modello dalla memoria della GPU alle cache/registri locali, piuttosto che dalla velocità con cui possiamo calcolare i dati caricati. La larghezza di banda della memoria disponibile e raggiunta nell'hardware di inferenza è un predittore migliore della velocità di generazione dei token rispetto alle loro prestazioni di calcolo di picco.
L'utilizzo dell'hardware di inferenza è molto importante in termini di costi di serving. Le GPU sono costose e abbiamo bisogno che svolgano più lavoro possibile. I servizi di inferenza condivisi promettono di mantenere bassi i costi combinando i carichi di lavoro di molti utenti, colmando le lacune individuali e raggruppando le richieste sovrapposte. Per i modelli di grandi dimensioni come Llama2-70B, otteniamo solo un buon rapporto costo/prestazioni con dimensioni di batch elevate. Avere un sistema di serving di inferenza in grado di operare con dimensioni di batch elevate è fondamentale per l'efficienza dei costi. Tuttavia, un batch di grandi dimensioni significa una dimensione della cache KV più grande e ciò a sua volta aumenta il numero di GPU necessarie per il serving del modello. C'è un tiro alla fune qui e gli operatori di servizi condivisi devono fare alcuni compromessi sui costi e implementare ottimizzazioni dei sistemi.
Utilizzo della larghezza di banda del modello (MBU)
Quanto è ottimizzato un server di inferenza LLM?
Come spiegato brevemente in precedenza, l'inferenza per gli LLM a dimensioni di batch inferiori, soprattutto in fase di decodifica, è limitata dalla velocità con cui possiamo caricare i parametri del modello dalla memoria del dispositivo alle unità di calcolo. La larghezza di banda della memoria determina la velocità con cui avviene lo spostamento dei dati. Per misurare l'utilizzo dell'hardware sottostante, introduciamo una nuova metrica chiamata Utilizzo della larghezza di banda del modello (MBU). L'MBU è definito come (larghezza di banda della memoria raggiunta) / (larghezza di banda della memoria di picco) dove la larghezza di banda della memoria raggiunta è ((dimensione totale dei parametri del modello + dimensione della cache KV) / TPOT).
Ad esempio, se un parametro 7B in esecuzione con precisione a 16 bit ha un TPOT uguale a 14 ms, sta spostando 14 GB di parametri in 14 ms, il che si traduce in un utilizzo della larghezza di banda di 1 TB/sec. Se la larghezza di banda di picco della macchina è di 2 TB/sec, stiamo eseguendo con un MBU del 50%. Per semplicità, questo esempio ignora la dimensione della cache KV, che è piccola per dimensioni di batch inferiori e lunghezze di sequenza più brevi. Valori MBU prossimi al 100% implicano che il sistema di inferenza sta utilizzando efficacemente la larghezza di banda della memoria disponibile. L'MBU è anche utile per confrontare diversi sistemi di inferenza (hardware + software) in modo normalizzato. L'MBU è complementare alla metrica Utilizzo dei Flop del modello (MFU; introdotta in il documento PaLM), che è importante nelle impostazioni vincolate al calcolo.
La figura 1 mostra una rappresentazione pittorica dell'MBU in un grafico simile a un grafico roofline. La linea inclinata continua dell'area ombreggiata in arancione mostra il throughput massimo possibile se la larghezza di banda della memoria è completamente saturata al 100%. Tuttavia, in realtà per dimensioni di batch basse (punto bianco), le prestazioni osservate sono inferiori al massimo: la misura di quanto inferiore è una misura dell'MBU. Per dimensioni di batch elevate (area gialla), il sistema è vincolato al calcolo e il throughput raggiunto come frazione del throughput massimo possibile viene misurato come Utilizzo dei Flop del modello (MFU).
MBU e MFU determinano quanto spazio è disponibile per spingere ulteriormente la velocità di inferenza su una determinata configurazione hardware. La figura 2 mostra l'MBU misurato per diversi gradi di parallelismo tensoriale con il nostro server di inferenza basato su TensorRT-LLM. L'utilizzo della larghezza di banda della memoria di picco viene raggiunto quando si trasferiscono grandi blocchi di memoria contigui. Quando modelli più piccoli come MPT-7B vengono distribuiti su più GPU, osserviamo un MBU inferiore poiché stiamo spostando blocchi di memoria più piccoli in ogni GPU.
La figura 3 mostra l'MBU osservato empiricamente per diversi gradi di parallelismo tensoriale e dimensioni del batch sulle GPU NVIDIA H100. L'MBU diminuisce all'aumentare delle dimensioni del batch. Tuttavia, man mano che scaliamo le GPU, la diminuzione relativa dell'MBU è meno significativa. Vale anche la pena notare che la scelta di hardware con una maggiore larghezza di banda della memoria può aumentare le prestazioni con meno GPU. Con una dimensione del batch pari a 1, possiamo ottenere un MBU più elevato del 60% su 2xH100-80GB rispetto al 55% su 4xA100-40GB GPU (Figura 2).
Risultati del benchmark
Latenza
Abbiamo misurato il tempo al primo token (TTFT) e il tempo per token di output (TPOT) su diversi gradi di parallelismo tensoriale per i modelli MPT-7B e Llama2-70B. Man mano che i prompt di input si allungano, il tempo per generare il primo token inizia a consumare una parte sostanziale della latenza totale. Il parallelismo tensoriale su più GPU aiuta a ridurre questa latenza.
A differenza dell'addestramento del modello, il ridimensionamento a più GPU offre rendimenti decrescenti significativi per la latenza di inferenza. Ad esempio, per Llama2-70B, il passaggio da 4x a 8x GPU riduce la latenza solo di 0,7x a dimensioni di batch ridotte. Uno dei motivi è che un parallelismo più elevato ha un MBU inferiore (come discusso in precedenza). Un altro motivo è che il parallelismo tensoriale introduce un overhead di comunicazione su un nodo GPU.
| Tempo al primo token (ms) | ||||
|---|---|---|---|---|
| Modello | 1xA100-40GB | 2xA100-40GB | 4xA100-40GB | 8xA100-40GB |
| MPT-7B | 46 (1x) | 34 (0,73x) | 26 (0,56x) | - |
| Llama2-70B | Non rientra | 154 (1x) | 114 (0,74x) | |
Tabella 1: Tempo al primo token dato che le richieste di input sono di 512 token di lunghezza con una dimensione del batch di 1. I modelli più grandi come Llama2 70B necessitano di almeno 4xA100-40B GPU per rientrare nella memoria
A dimensioni di batch più grandi, un parallelismo tensoriale più elevato porta a una diminuzione relativa più significativa della latenza dei token. La figura 4 mostra come varia il tempo per token di output per MPT-7B. Con una dimensione del batch pari a 1, il passaggio da 2x a 4x riduce la latenza dei token solo di circa il 12%. Con una dimensione del batch pari a 16, la latenza con 4x è inferiore del 33% rispetto a 2x. Ciò è in linea con la nostra precedente osservazione secondo cui la diminuzione relativa dell'MBU è inferiore a gradi più elevati di parallelismo tensoriale per una dimensione del batch pari a 16 rispetto a una dimensione del batch pari a 1.
La figura 5 mostra risultati simili per Llama2-70B, tranne per il fatto che il miglioramento relativo tra 4x e 8x è meno pronunciato. Confrontiamo anche il ridimensionamento della GPU su due diversi hardware. Poiché H100-80GB ha una larghezza di banda della memoria della GPU 2,15 volte superiore rispetto a A100-40GB, possiamo vedere che la latenza è inferiore del 36% con una dimensione del batch pari a 1 e inferiore del 52% con una dimensione del batch pari a 16 per i sistemi 4x.
Throughput
Possiamo scambiare throughput e tempo per token raggruppando le richieste. Il raggruppamento delle query durante la valutazione della GPU aumenta il throughput rispetto all'elaborazione sequenziale delle query, ma ogni query richiederà più tempo per essere completata (ignorando gli effetti di accodamento).
Esistono alcune tecniche comuni per il raggruppamento delle richieste di inferenza:
- Batch statico: il client inserisce più prompt nelle richieste e una risposta viene restituita dopo che tutte le sequenze nel batch sono state completate. I nostri server di inferenza supportano questa funzionalità, ma non la richiedono.
- Batch dinamico: i prompt vengono raggruppati al volo all'interno del server. In genere, questo metodo ha prestazioni inferiori rispetto al batch statico, ma può avvicinarsi all'ottimale se le risposte sono brevi o di lunghezza uniforme. Non funziona bene quando le richieste hanno parametri diversi.
- Batch continuo: l'idea di raggruppare le richieste man mano che arrivano è stata introdotta in questo eccellente documento ed è attualmente il metodo SOTA. Invece di attendere che tutte le sequenze in un batch terminino, raggruppa le sequenze a livello di iterazione. Può ottenere un throughput da 10 a 20 volte migliore rispetto al batch dinamico.
Il batch continuo è in genere l'approccio migliore per i servizi condivisi, ma ci sono situazioni in cui gli altri due potrebbero essere migliori. In ambienti QPS bassi, il batch dinamico può sovraperformare il batch continuo. A volte è più facile implementare ottimizzazioni GPU di basso livello in un framework di batch più semplice. Per i carichi di lavoro di inferenza batch offline, il batch statico può evitare un overhead significativo e ottenere un throughput migliore.
Dimensione del batch
Il funzionamento del batch dipende fortemente dal flusso di richieste. Ma possiamo ottenere un limite superiore sulle sue prestazioni eseguendo il benchmark del batch statico con richieste uniformi.
| Dimensione del batch | |||||||
|---|---|---|---|---|---|---|---|
| Hardware | 1 | 4 | 8 | 16 | 32 | 64 | 128 |
| 1 x A10 | 0,4 (1x) | 1,4 (3,5x) | 2,3 (6x) | 3,5 (9x) | Errore OOM (Out of Memory) | ||
| 2 x A10 | 0,8 | 2,5 | 4,0 | 7,0 | 8,0 | ||
| 1 x A100 | 0,9 (1x) | 3,2 (3,5x) | 5,3 (6x) | 8,0 (9x) | 10,5 (12x) | 12,5 (14x) | |
| 2 x A100 | 1,3 | 3,0 | 5,5 | 9,5 | 14,5 | 17,0 | 22,0 |
| 4 x A100 | 1,7 | 6,2 | 11,5 | 18,0 | 25,0 | 33,0 | 36,5 |
Tabella 2: Throughput MPT-7B di picco (req/sec) con batch statico e un backend basato su FasterTransformers. Richieste: 512 token di input e 64 token di output. Per input più grandi, il limite OOM sarà a dimensioni di batch inferiori.
Compromesso di latenza
La latenza delle richieste aumenta con la dimensione del batch. Con una GPU NVIDIA A100, ad esempio, se massimizziamo il throughput con una dimensione del batch di 64, la latenza aumenta di 4 volte mentre il throughput aumenta di 14 volte. I servizi di inferenza condivisi in genere scelgono una dimensione del batch bilanciata. Gli utenti che ospitano i propri modelli devono decidere il compromesso latenza/throughput appropriato per le proprie applicazioni. In alcune applicazioni, come i chatbot, la bassa latenza per risposte rapide è la massima priorità. In altre applicazioni, come l'elaborazione in batch di PDF non strutturati, potremmo voler sacrificare la latenza per elaborare un singolo documento per elaborarli tutti velocemente in parallelo.
La figura 7 mostra la curva di throughput rispetto alla latenza per il modello 7B. Ogni linea su questa curva è ottenuta aumentando la dimensione del batch da 1 a 256. Questo è utile per determinare quanto possiamo rendere grande la dimensione del batch, soggetta a diversi vincoli di latenza. Ricordando il nostro grafico roofline sopra, scopriamo che queste misurazioni sono coerenti con ciò che ci aspetteremmo. Dopo una certa dimensione del batch, ovvero
