Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Servizi Web di Amazon

Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Servizi Web di Amazon

Qual è il framework e la configurazione ottimali per ospitare modelli linguistici di grandi dimensioni (LLM) per applicazioni di intelligenza artificiale generativa per la generazione di testo? Nonostante l'abbondanza di opzioni per servire i LLM, è difficile rispondere a questa domanda a causa delle dimensioni dei modelli, delle diverse architetture dei modelli, dei requisiti prestazionali delle applicazioni e altro ancora. IL Amazon Sage Maker Contenitore LMI (Large Model Inference). semplifica il servizio dei LLM riunendo una serie di strutture e tecniche diverse che ottimizzano l'implementazione dei LLM. Il contenitore LMI ha un potente stack di servizio chiamato Servizio DJL che è agnostico rispetto al LLM sottostante. Fornisce parametri di configurazione a livello di sistema che possono essere ottimizzati per ottenere le migliori prestazioni dell'infrastruttura di hosting per un dato LLM. Supporta inoltre ottimizzazioni recenti come il batch continuo, noto anche come batch iterativo o batch progressivo, che fornisce miglioramenti significativi nella produttività.

In un precedente settimana, abbiamo mostrato come utilizzare il contenitore LMI per distribuire la famiglia di modelli Falcon su SageMaker. In questo post dimostriamo come migliorare il throughput e la latenza del servizio Falcon-40B con tecniche come il batching continuo. Forniamo inoltre una comprensione intuitiva dei parametri di configurazione forniti dal contenitore LMI SageMaker che può aiutarti a trovare la migliore configurazione per la tua applicazione reale.

Fondamenti di inferenza generativa di testo per LLM

Diamo prima un'occhiata ad alcuni concetti fondamentali su come eseguire l'inferenza per i LLM per la generazione di testo.

Passaggio in avanti, attivazioni e cache KV

Data una sequenza di input di token, vengono eseguiti in a forward pass attraverso tutti gli strati del LLM (come Falcon) per generare il token successivo. UN forward pass si riferisce al processo in cui i dati di input vengono passati attraverso una rete neurale per produrre un output. Nel caso della generazione di testo, il passaggio in avanti implica l'inserimento di un seme o contesto iniziale nel modello linguistico e la generazione del carattere o token successivo nella sequenza. Per generare una sequenza di testo, il processo viene spesso eseguito in modo iterativo, ovvero viene ripetuto per ogni passaggio o posizione nella sequenza di output. Ad ogni iterazione, il modello genera il carattere o token successivo, che diventa parte del testo generato, e questo processo continua fino a quando non viene generata la lunghezza di testo desiderata.

La generazione di testo con modelli linguistici come Falcon o GPT lo è autoregressive. Ciò significa che il modello genera un token alla volta condizionando i token precedentemente generati. In altre parole, ad ogni iterazione, il modello prende come input il testo precedentemente generato e prevede il token successivo in base a quel contesto. Come accennato in vLLM: servizio LLM facile, veloce ed economico con PagedAttention, in questo processo di decodifica autoregressiva, tutti i token di input per LLM producono la loro chiave di attenzione e i tensori del valore, e questi tensori vengono mantenuti nella memoria della GPU per generare i token successivi. Questi tensori di chiave e valore memorizzati nella cache vengono spesso definiti come KV cache.

Fasi di precompilazione e decodifica

In un processo di decodifica autoregressiva, come quello utilizzato nella generazione di testi con modelli linguistici come Falcon, ci sono tipicamente due fasi principali: la prefill fase e il decode fase. Queste fasi sono cruciali per generare un testo coerente e contestualmente rilevante.

La fase di precompilazione include quanto segue:

  • Contesto iniziale – La fase di precompilazione inizia con un contesto iniziale o testo seme fornito dall'utente. Questo contesto iniziale può essere una frase, una frase o anche solo una singola parola. Stabilisce il punto di partenza per la generazione del testo e fornisce il contesto per ciò che verrà dopo.
  • Condizionamento del modello – Il contesto fornito viene utilizzato per condizionare il modello linguistico. Il modello prende questo contesto come input e genera il token successivo (parola o carattere) nella sequenza in base alla sua comprensione del contesto.
  • Generazione di token – Il modello genera un token alla volta, prevedendo cosa dovrebbe accadere dopo nel testo. Questo token viene aggiunto al contesto, estendendolo di fatto.
  • Processo iterativo – Il processo di generazione dei token viene ripetuto in modo iterativo. Ad ogni passaggio, il modello genera un token considerando il contesto aggiornato, che ora include i token generati nei passaggi precedenti.

La fase di preriempimento continua finché non viene soddisfatta una condizione di arresto predeterminata. Questa condizione può essere una lunghezza massima per il testo generato, un token specifico che segnala la fine del testo o qualsiasi altro criterio impostato dall'utente o dall'applicazione.

La fase di decodifica include quanto segue:

  • Completamento – Dopo la fase di precompilazione, hai un testo parzialmente generato che potrebbe essere incompleto o tagliato ad un certo punto. La fase di decodifica ha il compito di completare il testo per renderlo coerente e grammaticalmente corretto.
  • Continuazione dall'ultimo token – In fase di decodifica il modello parte dall’ultimo token generato durante la fase di prefill. Utilizza questo token come contesto iniziale e genera il token successivo per continuare il testo.
  • Completamento iterativo – Come nella fase di precompilazione, il processo di generazione dei token è nuovamente iterativo. Il modello genera un token alla volta, condizionando i token precedenti nella sequenza.
  • Condizione di arresto – La fase di decodifica prevede anche una condizione di arresto, che potrebbe essere la stessa della fase di precompilazione, come il raggiungimento di una lunghezza massima o l'incontro con un token di fine testo. Quando questa condizione è soddisfatta, il processo di generazione si interrompe.

La combinazione delle fasi di precompilazione e decodifica consente ai modelli autoregressivi di generare testo che si basa su un contesto iniziale e produce sequenze di testo coerenti, contestualmente rilevanti e contestualmente coerenti.

Fare riferimento a Un sistema di servizio distribuito per modelli generativi basati su trasformatori per una spiegazione dettagliata del processo.

Ottimizzazione della produttività utilizzando il batching dinamico

Finora abbiamo parlato solo di un singolo input. In pratica, ci aspettiamo di gestire più richieste che arrivano in modo casuale dai client dell'applicazione per l'inferenza contemporaneamente o in modo scaglionato. Nel modo tradizionale è possibile utilizzare il batching di base per aumentare il throughput e l'utilizzo delle risorse di calcolo della GPU. Il batching consiste effettivamente nel combinare le rappresentazioni numeriche di più di una richiesta in un batch ed eseguire esecuzioni parallele dei passaggi in avanti autoregressivi. Questo dosaggio intelligente viene effettuato al momento del servizio. Il server DJLServing di SageMaker LMI può essere configurato per raggruppare più richieste per elaborarle in parallelo impostando i seguenti parametri in servire.proprietà:

  • max_batch_delay = 100 – Il ritardo massimo per l'aggregazione batch in millisecondi. Il valore predefinito è 100 millisecondi.
  • dimensione del lotto = 32 – La dimensione dinamica del batch. Il valore predefinito è 1.

Ciò dimostra sostanzialmente che DJLServing metterà in coda le richieste per 100 millisecondi alla volta o se il numero di richieste in coda è fino al batch_size specificato, il batch verrà programmato per l'esecuzione sul backend per l'inferenza. Questo è noto come dynamic batching. È dinamico perché la dimensione del batch può cambiare tra batch a seconda del numero di richieste aggiunte in quel periodo di tempo. Tuttavia, poiché le richieste potrebbero avere caratteristiche diverse (ad esempio, alcune richieste potrebbero avere la forma di 20 token di input e 500 token di output, mentre altre potrebbero essere invertite, con 500 token di input ma solo 20 per output), alcune richieste potrebbero completare l'elaborazione più velocemente di altri nello stesso batch. Ciò potrebbe comportare un sottoutilizzo della GPU durante l'attesa che tutte le richieste in corso nel batch completino la fase di decodifica, anche se in coda sono presenti ulteriori richieste in attesa di essere elaborate. Il diagramma seguente illustra questo processo.

Visualizzazione semplice del raggruppamento dinamico

Visiva di batching dinamico: nota le finestre inattive alla fine delle richieste 2 e 3

Ottimizzazione della produttività utilizzando il dosaggio continuo

Con continuous batching, conosciuto anche come iterative or rolling batching, sfruttiamo le differenze tra le fasi di precompilazione e decodifica. Per attivare l'invio in batch continuo, DJServing fornisce le seguenti configurazioni aggiuntive come dausing.properties:

  • motore=MPI – Ti invitiamo a utilizzare il motore MPI per il batching continuo.
  • opzione.rolling_batch=auto o lmi-dist: consigliamo di utilizzare auto perché selezionerà automaticamente l'algoritmo batch mobile più appropriato insieme ad altre ottimizzazioni in futuro.
  • opzione.max_rolling_batch_size=32 – Questo limita il numero di richieste simultanee. Il valore predefinito è 32.

Con il batch continuo, lo stack di servizio (DJLServing) non attende che tutte le richieste in corso in un batch completino la fase di decodifica. Piuttosto, durante le interruzioni logiche (alla fine di un'iterazione nella fase di decodifica), inserisce ulteriori richieste in attesa in coda mentre il batch corrente è ancora in elaborazione (da cui il nome lotto mobile). Esegue questo controllo per le richieste in sospeso alla fine di ogni iterazione della fase di decodifica. Ricorda, per ogni richiesta, dobbiamo eseguire la fase di precompilazione seguita dalla fase di decodifica sequenziale. Poiché possiamo elaborare tutti i token dal prompt iniziale di una richiesta in parallelo per la fase di precompilazione, ogni volta che viene inserita una nuova richiesta, interrompiamo temporaneamente la fase di decodifica delle richieste in corso del batch: salviamo temporaneamente la sua cache KV e attivazioni in memoria ed eseguire la fase di precompilazione delle nuove richieste.

La dimensione di questa cache può essere configurata con la seguente opzione:

Una volta completata la precompilazione, uniamo le nuove richieste e le vecchie richieste in pausa in un nuovo batch batch, che può procedere con la fase di decodifica in parallelo. Tieni presente che le vecchie richieste in pausa possono continuare la fase di decodifica da dove si erano interrotte e le nuove richieste inizieranno dal loro primo nuovo token.

Visualizzazione batch continua o iterativa

Visuale batch continuo o iterativo: notare che i tempi di inattività vengono sostituiti con richieste successive

Potresti aver già capito che il batching continuo è un approccio quasi simile con il quale parallelizziamo naturalmente le attività nella nostra vita quotidiana. Abbiamo messaggi, e-mail, notifiche telefoniche (potenzialmente nuove richieste) che arrivano in momenti casuali (analogamente a più richieste che arrivano in modo casuale e scaglionato per le GPU). Tutto questo avviene mentre completiamo le nostre attività in volo: comporre e-mail, scrivere codice, partecipare a riunioni (analoghe alle attività di elaborazione attualmente nelle GPU). Nelle pause logiche, mettiamo in pausa le nostre attività in volo e controlliamo le nostre notifiche per decidere se è necessaria qualche azione da parte nostra e, in tal caso, la aggiungiamo alle nostre attività in volo (batch mobile nella vita reale) o inserirlo in una lista di cose da fare (la coda).

Mettendo tutto insieme: come pensare all'utilizzo della memoria delle GPU

Ti consigliamo di eseguire il test di carico del tuo modello per vedere quale configurazione è la più conveniente per il tuo caso d'uso aziendale. Per comprendere meglio, visualizziamo l'impronta di memoria delle GPU mentre il modello viene caricato e mentre le richieste successive vengono elaborate in un batch in sequenza. Per questo post, supponiamo di caricare il modello Falcon-40B su una delle istanze di tipo G5 installate con GPU NVIDIA A10G, ciascuna con 24 GB di memoria. Tieni presente che una comprensione simile è applicabile ai tipi di istanza p3, p4 e p5, forniti con le serie GPU V100, A100 e H100.

Quella che segue è la panoramica per ottenere un valore approssimativo della memoria totale richiesta per servire Falcon-40B:

  • Taglia del modello = Numero di parametri del modello (40 miliardi per Falcon-40B) x 4 byte per parametro (per FP32) = 160 GB
  • Memoria totale approssimativa richiesta per caricare Falcon-40B per l'inferenza = Dimensioni del modello (=160 GB) + KV Cache (Attention Cache) (=*20 GB) + Overhead di memoria aggiuntivo da parte di ML Frameworks (circa 2 GB)
Memoria Visiva

Memoria visiva: comprensione dell'impronta di memoria di un modello Falcon-40B caricato

Per Falcon-40B, se comprimiamo il modello quantizzandolo nel tipo di dati bfloat16 (2 byte), la dimensione del modello diventa circa 80 GB. Come puoi vedere, questa è ancora più grande della memoria supportata da un dispositivo acceleratore, quindi dobbiamo adottare una tecnica di partizionamento del modello (sharding) con uno speciale parallelismo tensoriale (TP) e suddividere il modello su più dispositivi di accelerazione. Supponiamo di aver scelto g5.24xlarge, che ha 4 dispositivi GPU A10G. Se configuriamo DJLServing (serving.properties) con quanto segue, possiamo aspettarci che gli 80 GB di peso del modello saranno divisi equamente tra tutte e 4 le GPU:

Con tensor_parallel_degree impostato su 4, circa 20 GB dei 24 GB di memoria della GPU (circa l'84%) sono già utilizzati prima ancora che una singola richiesta venga elaborata. Il restante 16% della GPU verrà utilizzato per la cache KV per le richieste in entrata. È possibile che per il tuo scenario aziendale e i relativi requisiti di latenza e velocità effettiva, 2-3 GB di memoria rimanente siano più che sufficienti. In caso contrario, puoi aumentare la dimensione dell'istanza a g5.48xlarge, che ha 8 GPU e utilizza tensor_parallel_degree impostato su 8. In tal caso, solo circa 10 GB dei 24 GB di memoria disponibile di ciascuna GPU vengono utilizzati per i pesi del modello e noi hanno circa il 60% della GPU rimanente per le attivazioni e la cache KV. Intuitivamente possiamo vedere che questa configurazione può permetterci di avere un throughput più elevato. Inoltre, poiché ora disponiamo di un buffer più grande, possiamo aumentare il file max_rolling_batch_prefill_tokens ed max_rolling_batch_size parametri per ottimizzare ulteriormente il throughput. Insieme, questi due parametri controlleranno le preallocazioni delle precompilazioni di attivazione e della cache KV per il modello. Un numero maggiore per questi due parametri sarà correlato a un throughput maggiore, presupponendo che tu abbia abbastanza buffer per la cache KV nella memoria della GPU.

Batch continuo con PagedAttention

PagedAttention è un nuovo algoritmo di ottimizzazione sviluppato da UC Berkeley che migliora il processo di batch continuo consentendo alla cache di attenzione (cache KV) di essere non contigua allocando memoria in pagine o blocchi di dimensione fissa. Questo si ispira alla memoria virtuale e ai concetti di paging utilizzati dai sistemi operativi.

Come per il vLLM carta, la cache di attenzione di ciascuna sequenza di token viene partizionata in blocchi e mappata su blocchi fisici tramite una tabella di blocchi. Durante il calcolo dell'attenzione, un kernel PagedAttention può utilizzare la tabella dei blocchi per recuperare in modo efficiente i blocchi dalla memoria fisica. Ciò si traduce in una significativa riduzione dello spreco di memoria e consente dimensioni batch maggiori, un maggiore utilizzo della GPU e un throughput più elevato.

Confronto delle prestazioni

Per garantire un test di carico efficace della configurazione di distribuzione, è consigliabile iniziare considerando lo scenario aziendale e definendo chiaramente le caratteristiche dell'input e dell'output per l'applicazione basata su LLM. Ad esempio, se stai lavorando su un caso d'uso di riepilogo di un call center, l'input potrebbe consistere in un testo più grande, come una trascrizione di una chat di 500 token tra un agente del servizio clienti e un cliente, ma l'output potrebbe essere relativamente più piccolo, circa 100 gettoni, che rappresentano un riassunto della trascrizione. D'altra parte, se stai lavorando su uno scenario di generazione di codice, l'input potrebbe contenere fino a 15 token, come "scrivere un'implementazione efficiente in Python per descrivere tutte le risorse EC2, inclusa l'impaginazione", ma l'output potrebbe essere molto più grande, raggiungendo i 500 token. È anche importante considerare se il raggiungimento di una latenza inferiore o l'ottimizzazione della velocità effettiva è la priorità assoluta per il tuo scenario specifico.

Dopo aver acquisito una comprensione completa dello scenario aziendale, puoi analizzare e determinare la configurazione ottimale per il tuo ambiente di hosting. In questo contesto, l'ambiente di hosting comprende vari elementi chiave, tra cui il tipo di istanza e altri parametri di configurazione come tensore_grado_parallelo, max_rolling_batch_size, max_rolling_batch_prefill_tokense altro ancora. Il nostro obiettivo è identificare la configurazione più efficace per supportare i nostri requisiti di tempo di risposta, produttività e qualità dell'output del modello.

Nella nostra analisi, abbiamo confrontato le prestazioni per illustrare i vantaggi del batching continuo rispetto al batching dinamico tradizionale. Abbiamo utilizzato le configurazioni dettagliate nella tabella seguente in servire.properties per il batching dinamico e il batch iterativo, utilizzando un contenitore LMI su SageMaker.

Dosaggio dinamico Dosaggio continuo Batch continuo con PagedAttention

motore=Python

option.model_id=tiiuae/falcon-40b

opzione.tensor_parallel_degree=8

opzione.dtype=fp16

dimensione_lotto=4

max_batch_delay=100

opzione.trust_remote_code = vero

motore = MPI

opzione.model_id = {{s3_url}}

opzione.trust_remote_code = vero

opzione.tensor_parallel_degree = 8

opzione.max_rolling_batch_size = 32

opzione.rolling_batch = automatico

opzione.dtipo = fp16

opzione.max_rolling_batch_prefill_tokens = 1024

opzione.paged_attention = Falso

motore = MPI

opzione.model_id = {{s3_url}}

opzione.trust_remote_code = vero

opzione.tensor_parallel_degree = 8

opzione.max_rolling_batch_size = 32

opzione.rolling_batch = automatico

opzione.dtipo = fp16

opzione.max_rolling_batch_prefill_tokens = 1024

opzione.paged_attention = Vero

Le due configurazioni sono state confrontate per Falcon-40B con il tipo di dati FP16 distribuito su ml.g5.48xlarge in un paio di scenari diversi che rappresentano applicazioni del mondo reale:

  • Un numero limitato di token di input con un numero elevato di token generati – In questo scenario, il numero di token di input è stato fissato a 32 e sono stati generati 128 nuovi token
Strategia di batching Velocità effettiva (token/sec) Latenza p90 (secondi)
Dosaggio dinamico 5.53 58.34
Dosaggio continuo 56.04 4.74
Batch continuo con PagedAttention 59.18 4.76
  • Un input di grandi dimensioni con un numero limitato di token generati – Qui, fissiamo il numero di token di input su 256 e chiediamo a LLM di riassumere l'input in 32 token
Strategia di batching Velocità effettiva (token/sec) Latenza p90 (secondi)
Dosaggio dinamico 19.96 59.31
Dosaggio continuo 46.69 3.88
Batch continuo con PagedAttention 44.75 2.67

Possiamo vedere che il batch continuo con PagedAttention fornisce un miglioramento della produttività 10 volte maggiore nello scenario 1 e 2.3 volte nello scenario 2 rispetto all'utilizzo del batch dinamico su SageMaker durante l'utilizzo del contenitore LMI.

Conclusione

In questo post, abbiamo esaminato il modo in cui gli LLM utilizzano la memoria e spiegato come il batch continuo migliora il throughput utilizzando un contenitore LMI su SageMaker. Abbiamo dimostrato i vantaggi del batching continuo per Falcon-40B utilizzando un contenitore LMI su SageMaker mostrando i risultati dei benchmark. Puoi trovare il codice su Repository GitHub.


Informazioni sugli autori

Abhigyan ShivadityaAbhi Shivaditya è Senior Solutions Architect presso AWS e collabora con organizzazioni aziendali globali strategiche per facilitare l'adozione dei servizi AWS in aree quali l'intelligenza artificiale, il calcolo distribuito, il networking e lo storage. La sua esperienza risiede nel Deep Learning nei domini del Natural Language Processing (NLP) e della Computer Vision. Abhi assiste i clienti nella distribuzione efficiente di modelli di machine learning ad alte prestazioni all'interno dell'ecosistema AWS.

Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.Dhawal Patel è un Principal Machine Learning Architect presso AWS. Ha lavorato con organizzazioni che vanno dalle grandi imprese alle startup di medie dimensioni su problemi legati all'informatica distribuita e all'intelligenza artificiale. Si concentra sull'apprendimento profondo, inclusi i domini della PNL e della visione artificiale. Aiuta i clienti a ottenere l'inferenza del modello ad alte prestazioni su SageMaker.

Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.Pinak Panigrahi collabora con i clienti per creare soluzioni basate sull'apprendimento automatico per risolvere problemi aziendali strategici su AWS. Quando non è occupato con l'apprendimento automatico, lo si può trovare mentre fa un'escursione, legge un libro o guarda uno sport.

Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.Abhi Sodhani ricopre la posizione di Senior AI/ML Solutions Architect presso AWS, dove è specializzato nell'offrire competenze tecniche e indicazioni su soluzioni AI e ML generative ai clienti. Il suo obiettivo principale è assistere le aziende native digitali nella realizzazione del pieno potenziale delle tecnologie di intelligenza artificiale generativa e ML, consentendo loro di raggiungere i propri obiettivi aziendali in modo efficace. Al di là dei suoi sforzi professionali, Abhi mostra una forte passione per le attività intellettuali come la lettura, oltre a impegnarsi in attività che promuovono il benessere fisico e mentale, come lo yoga e la meditazione.

Migliora le prestazioni dei modelli Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Data Intelligence. Ricerca verticale. Ai.Qing Lan è un ingegnere di sviluppo software in AWS. Ha lavorato su diversi prodotti impegnativi in ​​Amazon, tra cui soluzioni di inferenza ML ad alte prestazioni e sistema di registrazione ad alte prestazioni. Il team di Qing ha lanciato con successo il primo modello di miliardi di parametri in Amazon Advertising con una latenza molto bassa richiesta. Qing ha una conoscenza approfondita dell'ottimizzazione dell'infrastruttura e dell'accelerazione del Deep Learning.

Timestamp:

Di più da Apprendimento automatico di AWS