Llama distribuito 2 su CPU

Llama distribuito su CPU

Un esempio di gioco di simulazione di bulk su hardware di consumo utilizzando Python, tramite llama.cpp e PySpark.

Immagine dell'autore tramite DALL-E

Perché?

Questo esercizio riguarda l’utilizzo di Llama 2, un LLM (Large Language Model) di Meta AI, per riassumere molti documenti contemporaneamente. La sintesi scalabile di testo non strutturato, semi-strutturato e strutturato può esistere come una funzionalità a sé stante e anche far parte di pipeline di dati che alimentano i modelli di apprendimento automatico a valle.

In particolare, vogliamo dimostrare la fattibilità simultanea di:

  • Eseguire Llama 2 su CPUs (cioè rimuovendo i vincoli di capacità della GPU)
  • Integrazione fluida di un LLM con Apache Spark (una parte fondamentale degli ecosistemi Big Data)
  • Nessun utilizzo di endpoint di terze parti (cioè i modelli devono essere eseguiti localmente a causa di infrastrutture air-gapped o requisiti di riservatezza)

Come?

Gran parte del lavoro difficile è già stato fatto per noi!

Il progetto llama.cpp consente di eseguire LLM semplificati su CPU riducendo la risoluzione (“quantizzazione”) dei loro pesi numerici. Questi file di modello pronti all’uso sono facilmente disponibili.

Inoltre, i collegamenti llama-cpp-python forniscono un accesso semplice all’utilizzo di llama.cpp all’interno di Python.

Infine, l’applicazione applyInPandas() di Spark (documentazione) consente di suddividere le fonti di dati giganti in blocchi delle dimensioni di Pandas e di elaborarli in modo indipendente. Si noti che questo approccio può essere un anti-pattern se le funzioni Spark vettorializzate possono ottenere lo stesso risultato, ma nel nostro caso, stiamo essenzialmente utilizzando Spark come un semplice orchestratore per scalare l’utilizzo di llama.cpp. Probabilmente ci sono modi più efficienti per utilizzare llama.cpp nella lavorazione a batch, ma questo è attraente data la semplicità e i benefici automatici della tolleranza agli errori e della scalabilità di Spark.

Piano

Licenza Creative Commons (CC BY-SA 3.0)

Come test divertente, utilizzeremo Llama 2 per riassumere Guerra e pace di Leo Tolstoj, un romanzo di oltre 1200 pagine con oltre 360 capitoli. Tratteremo ogni capitolo come un documento. Si noti che Llama 2 già “conosce” il romanzo; chiedergli informazioni su un personaggio chiave genera questa risposta (utilizzando llama-2–7b-chat.ggmlv3.q8_0.bin):

Prompt:Descrivi brevemente il personaggio di Anna Pavlovna in 'Guerra e pace'Risposta:Anna Pavlovna è un personaggio importante nel romanzo "Guerra e pace" di Leo Tolstoj. È una ricca, intelligente e affascinante donna della società conosciuta per la sua bellezza e arguzia. Nonostante la sua bellezza e il suo fascino, è anche descritta come manipolatrice e astuta, usando spesso la sua intelligenza e arguzia per ottenere ciò che vuole. Nel corso del romanzo, Anna Pavlovna viene mostrata come un personaggio complesso e poliedrico, con qualità ammirabili e difetti. È particolarmente legata al personaggio principale Pierre Bezukhov e svolge un ruolo importante nel suo sviluppo personale ed emotivo.

Passaggi:

  1. Installare il modello di chat quantizzato 7B e llama-cpp-python.
  2. Scaricare il romanzo, suddiviso per capitolo, creare un DataFrame Spark.
  3. Partizionare per capitolo e generare riassunti.

Installazione

La configurazione di un cluster Spark esce dal nostro ambito; si presume che Spark sia in esecuzione localmente, tramite un servizio gestito (come Synapse o Elastic Map Reduce) o una distribuzione personalizzata come Kubernetes.

Ci sono due artefatti che devono essere installati su tutti i nodi worker, che siano essi macchine fisiche, VM o pod in un pool serverless:

  • Il modello LLama 2 nel formato GGML (posizionato in /models)
  • Il modulo llama-cpp-python (installato tramite pip)

Stiamo utilizzando la versione 7B chat “Q8” di Llama 2, che puoi trovare qui. I link di download potrebbero cambiare, ma l’installazione su un singolo nodo “bare metal” è simile a quanto segue:

Assicurati di poter utilizzare il modello tramite python3 e questo esempio. Per riassumere, ogni contesto Spark deve essere in grado di leggere il modello da /models e accedere al modulo llama-cpp-python.

Elaborazione del testo del romanzo

I comandi Bash seguenti scaricano il romanzo e stampano il conteggio delle parole.

Successivamente, leggiamo il file di testo in Python, rimuovendo l’intestazione e il piè di pagina di Project Gutenberg. Divideremo il testo utilizzando l’espressione regolare CHAPTER .+ per creare una lista di stringhe di capitoli e creeremo un DataFrame Spark da esse (questo codice assume l’esistenza di una SparkSession chiamata spark).

Il codice dovrebbe produrre il seguente output:

numero di capitoli = 365numero massimo di parole per capitolo = 3636+------------------------------------------------------------+-------+|                                                        text|chapter|+------------------------------------------------------------+-------+|\n\n“Well, Prince, so Genoa and Lucca are now just family...|      1||\n\nLa sala da disegno di Anna Pávlovna si stava gradualmente riempiendo. T...|      2||\n\nLa reception di Anna Pávlovna era in pieno svolgimento. Le dame...|      3||\n\nProprio in quel momento un altro visitatore entrò nella sala da disegno: P...|      4||\n\n"E cosa ne pensi di questa ultima commedia, la cor...|      5||\n\nDopo aver ringraziato Anna Pávlovna per la sua affascinante soirée,...|      6||\n\nSi sentì il fruscio di un abito da donna nella stanza accanto...|      7||\n\nGli amici rimasero in silenzio. Nessuno aveva voglia di iniziare a parlare...|      8||\n\nEra già passata l'una quando Pierre lasciò il suo amico...|      9||\n\nIl principe Vasíli mantenne la promessa che aveva fatto al principe...|     10|+------------------------------------------------------------+-------+

Perfetto! Ora abbiamo un DataFrame con 365 righe, ognuna contenente il testo completo e il numero del capitolo. L’ultimo passo è creare un nuovo DataFrame con i riassunti di ciascun capitolo.

Elaborazione con Spark

Di seguito il codice Python per generare un riassunto di un singolo capitolo (vedi la chiamata a limit(1) per ottenere una singola riga). Spieghiamo il codice qui di seguito:

La funzione llama2_summarize() è il codice che viene applicato per gruppo da Spark. Poiché stiamo raggruppando per colonna chapter, la funzione viene chiamata su ogni riga del capitolo; l’argomento df è semplicemente un Pandas DataFrame con una singola riga. Nota che stiamo leggendo il modello ad ogni chiamata di llama2_summarize(); questa è una scorciatoia che prendiamo per semplicità, ma non è molto efficiente.

Infine, utilizzando Spark, eseguiamo il groupby() e chiamiamo applyInPandas(), impostando lo schema per includere il riassunto del capitolo e il numero.

L’output (formattato per una migliore leggibilità) assomiglia a questo:

riassuntoIl capitolo tratta di una conversazione tra il principe Vasíli Kurágin e Anna Pávlovna Schérer, una famosa socialite e favorita dell'imperatrice Márya Fëdorovna. Stanno discutendo vari argomenti politici, tra cui la possibilità di una guerra con la Francia e il ruolo dell'Austria nel conflitto. Il principe Vasíli spera di ottenere un incarico per suo figlio tramite l'imperatrice vedova, mentre Anna Pávlovna è entusiasta delle potenzialità della Russia nel salvare l'Europa dalla tirannia di Napoleone. La conversazione tocca anche argomenti personali, come la insoddisfazione del principe Vasíli per suo figlio minore e il suggerimento di Anna Pávlovna che sposi il suo figlio scapestrato Anatole ad una ricca ereditiera.capitolo1

(Nota l’uso di Napoleone nonostante non appaia nel capitolo! Ancora una volta, si tratta di un esercizio divertente piuttosto che di un esempio realistico che utilizza documenti veramente inediti.)

Il tempo di esecuzione per questo test di un singolo capitolo è di circa 2 minuti su una VM a 64 core. Ci sono molte scelte che abbiamo trascurato che influiscono sul tempo di esecuzione, come la dimensione/quantizzazione del modello e i parametri del modello. Il risultato chiave è che scalando adeguatamente il nostro cluster Spark, possiamo riassumere tutti i capitoli in pochi minuti. Pertanto, è possibile elaborare quotidianamente centinaia di migliaia (o addirittura milioni!) di documenti utilizzando grandi cluster Spark composti da macchine virtuali economiche.

Sommario

Non abbiamo nemmeno menzionato l’adattamento dei parametri standard LLM come temperature e top_p che controllano la “creatività” e la casualità dei risultati, o l’ingegneria delle prompt, che è praticamente una disciplina a sé stante. Abbiamo anche scelto il modello Llama 2 7B senza giustificazione; potrebbero esserci modelli più piccoli e più performanti o famiglie di modelli più adatti al nostro caso d’uso specifico.

Invece, abbiamo mostrato come distribuire facilmente i carichi di lavoro (quantizzati) di LLM utilizzando Spark con uno sforzo abbastanza minimo. I prossimi passi potrebbero includere:

  • Caricamento/caching più efficiente dei modelli
  • Optimizzazione dei parametri per diversi casi d’uso
  • Prompt personalizzati