Paithon Book Paithon Book
Esegui il codice

LLMOps: operare i grandi modelli#

Il 30 novembre 2022 OpenAI mette online ChatGPT. Cinque giorni dopo Sam Altman annota su Twitter che ha superato il milione di utenti. Lo stesso giorno, rispondendo a due persone diverse, dice le due cose rimaste celebri: i costi di calcolo sono eye-watering, da far venire le lacrime agli occhi, e siamo nell’ordine di qualche centesimo di dollaro a conversazione. Il dettaglio interessante è quello che non dice: il modello dietro ChatGPT, un GPT-3.5, era già pronto da mesi, addestrato e poi rifinito perché rispondesse come ci si aspetta da un assistente e non come da un completatore di testi. La cosa nuova, quella che teneva svegli gli ingegneri, era operarlo invece che costruirlo: servirlo a milioni di persone, in fretta, in modo affidabile, senza che la bolletta della GPU divorasse l’azienda.

È lo stesso salto dal notebook alla produzione raccontato fin qui. Ma quando il «modello» è un grande modello linguistico, in sigla LLM (large language model), da miliardi di parametri, i problemi visti finora si ripresentano amplificati, e con sfumature nuove. Due soprattutto. La prima: il modello, spesso, non lo addestri tu. Lo prendi già fatto (pesi aperti da ospitare, o un’API di terzi da interrogare) e il tuo lavoro è adattarlo e servirlo, non allenarlo da zero. La seconda: l’output non è più una classe o un numero, ma testo aperto, difficile da misurare quanto è difficile giudicare un tema di italiano. Quel territorio ha un nome: LLMOps.

Che cosa cambia con gli LLM#

Il token, prima di tutto, perché da qui in avanti si conta tutto così, i tempi come i costi: è il pezzetto di testo (una parola corta, o un frammento di parola) che il modello legge e scrive come unità.

Detto questo, il baricentro si sposta, e conviene essere precisi su cosa si sposta dove. Fin qui il problema era caricare i pesi dal disco alla memoria: si fa una volta all’avvio, poi non ci si pensa più. Qui il problema è un altro viaggio, molto più corto ma molto più frequente: portare quei pesi dalla memoria ai circuiti che fanno i conti, e questo viaggio va rifatto per intero a ogni singolo token. Il modello scrive la risposta un token alla volta, e ogni token lo decide guardando tutti quelli già scritti (è il modo di generare, autoregressivo, studiato nei grandi modelli linguistici); a ogni giro, tutti i miliardi di numeri devono ripassare dalla memoria ai circuiti. È lì che se ne va il tempo, ed è lì che se ne va la bolletta.

Quattro barre orizzontali, lunghe in proporzione alla memoria disponibile (8, 16, 24 e 48 gigabyte), ciascuna con accanto quanti miliardi di parametri ci entrano se ogni peso è scritto a quattro bit: circa 10, 26, 42 e 90. In fondo la regola che produce quei numeri. Quattro barre orizzontali, lunghe in proporzione alla memoria disponibile (8, 16, 24 e 48 gigabyte), ciascuna con accanto quanti miliardi di parametri ci entrano se ogni peso è scritto a quattro bit: circa 10, 26, 42 e 90. In fondo la regola che produce quei numeri.

Fig. 35.9 La domanda pratica che precede ogni altra. Non «quale modello è migliore», ma «quale entra», perché sotto quella soglia nessuna ottimizzazione serve. A sinistra ci sono i gigabyte di memoria disponibili, a destra i miliardi di parametri che ci stanno dentro, scritti come si usa nel settore con la B dei miliardi (70B sono settanta miliardi di parametri: attenzione a non confonderla con la G dei gigabyte, che sta dall’altra parte).#

I quattro numeri di destra non vanno imparati, escono dalla riga in fondo, che è tutta la figura. Si tolgono tre gigabyte, che servono per tenere in memoria la conversazione in corso, e si divide il resto per mezzo gigabyte a miliardo, che è quanto occupa un miliardo di parametri se ogni peso è scritto con quattro cifre binarie invece delle solite sedici (il perché è più avanti, in «Comprimere per servire»). Da 16 gigabyte, per esempio, restano 13, e tredici diviso mezzo fa 26.

Il vincolo di Fig. 35.9 viene prima di tutte le tecniche che seguono, e ne fissa l’ordine. Prima si stabilisce cosa entra nella memoria che si ha, poi si discute di quanto vada veloce: un modello che non ci sta non è lento, semplicemente non parte.

Fin qui servire un modello era come guidare un’automobile: la accendi, parte, sterzi dove vuoi, la fermi. Un LLM è un transatlantico. Ha una massa enorme (miliardi di «manopole» che devono stare tutte in memoria) e ogni manovra richiede tempo e spazio: non lo parcheggi in garage, non lo giri in una stradina. Servirlo non è più una questione di accenderlo, ma di manovrarlo: dove lo ormeggi (quanta memoria serve per ospitarlo), quanti passeggeri imbarchi in una volta sola per far quadrare i conti, come eviti che resti fermo in rada a bruciare carburante mentre aspetta. E c’è un dettaglio contro l’intuizione: la parte lenta non è pensare, è ricordare. A ogni nuova parola il modello deve rileggere l’intera stiva dei suoi numeri, e quella rilettura (non il calcolo) è ciò che scandisce il ritmo.

La generazione autoregressiva rende l’inferenza di un LLM memory-bound, non compute-bound. Per produrre un solo token il modello deve leggere tutti i suoi pesi dalla memoria della GPU. In una mixture of experts ne legge i soli parametri attivi, e con poche sequenze in volo il risparmio è reale: i totali dicono se il modello ci sta, gli attivi quanto costa un token. Appena il mazzo di richieste si riempie, però, token diversi chiamano esperti diversi e la lettura torna quasi completa. L’aritmetica per token è modesta, il traffico di memoria è enorme. Un conto d’ordine di grandezza lo rende concreto: un modello da 7 miliardi di parametri in 16 bit pesa circa 14 GB, e una GPU con banda di memoria attorno a 2 TB/s impiega \(14/2000 \approx 0{,}007\) s, cioè circa 7 ms, solo per far scorrere quei pesi. Una singola sequenza è così limitata a circa \(1/0{,}007 \approx 140\) token al secondo, mentre le unità di calcolo restano quasi inattive. A questo si somma la KV cache vista nel capitolo sui Transformer (key e value dei token già letti, tenuti in memoria per non ricalcolarli) che cresce con la lunghezza del contesto e va sommata ai pesi. Due grandezze, quindi, governano tutto: la memoria (contiene pesi e cache) e la sua banda (limita quanti token al secondo si producono). Buona parte dell’ingegneria di LLMOps è lotta contro questi due limiti.

Servire un LLM#

Riprendiamo la cosa che si è appena detta, perché tutto quello che segue ne discende: a fare da freno è il ricordare. Per scrivere un token il modello deve rileggersi tutti i suoi numeri, e quella rilettura costa più del calcolo che ci fa sopra.

Se è così, però, c’è una conseguenza che salva i conti. La rilettura è la stessa qualunque cosa il modello stia scrivendo. Farla per servire una persona sola, o per servirne cento nello stesso istante, costa quasi uguale: i pesi passano una volta e si usano per tutte e cento le risposte in corso. È lo stesso mazzo di richieste, il batch, che la sezione sul servire un modello usava per tenere occupata la scheda; qui quel mazzo è il motivo per cui un LLM è economicamente sostenibile, e non un’ottimizzazione fra le altre.

Solo che formare il mazzo, qui, è molto più difficile, e per due ragioni.

Due sale a confronto sullo stesso orologio, quattro posti ciascuna e una coda di dieci richieste. Nel batching statico le richieste partono insieme e chi finisce lascia il posto vuoto fino alla fine della più lunga: all'ultima iterazione mostrata resta un posto pieno e tre fermi, con la coda ancora intera e tre richieste concluse. Nel continuous batching ogni posto che si libera viene ripreso all'iterazione dopo: i quattro posti sono sempre pieni, la coda si è quasi svuotata e le richieste concluse sono cinque. In fondo il conto dei posti-iterazione occupati, 28 su 48 contro 48 su 48. Due sale a confronto sullo stesso orologio, quattro posti ciascuna e una coda di dieci richieste. Nel batching statico le richieste partono insieme e chi finisce lascia il posto vuoto fino alla fine della più lunga: all'ultima iterazione mostrata resta un posto pieno e tre fermi, con la coda ancora intera e tre richieste concluse. Nel continuous batching ogni posto che si libera viene ripreso all'iterazione dopo: i quattro posti sono sempre pieni, la coda si è quasi svuotata e le richieste concluse sono cinque. In fondo il conto dei posti-iterazione occupati, 28 su 48 contro 48 su 48.

Fig. 35.10 Quattro posti sulla stessa GPU e una coda di dieci richieste, sullo stesso orologio. Il batching statico non rinnova il mazzo finché la risposta più lunga non ha finito, e alla dodicesima iterazione ha tre richieste concluse, tre posti fermi e la coda intatta. Il continuous batching, il mazzo continuo, riprende ogni posto all’iterazione dopo che si è liberato: alla stessa iterazione ne ha concluse cinque, i quattro posti pieni e la coda quasi finita. Contando i posti tenuti per un’iterazione, il primo ne usa 28 su 48 e il secondo tutti e 48, senza contare quel che costa far entrare una richiesta nel mazzo.#

La prima è quella che Fig. 35.10 mette in evidenza: le risposte non durano tutte uguale, e non si sa in anticipo quanto dureranno. Se si forma il mazzo e lo si tiene insieme fino alla fine (è il batching statico), chi ha finito presto lascia il suo posto vuoto e nessuno lo occupa finché non ha finito anche il più lento. In un mazzo grande basta una risposta lunga per tenere fermi tutti gli altri.

La seconda riguarda la memoria. Mentre scrive, il modello tiene degli appunti su ciò che ha già letto, per non doverlo rileggere da capo a ogni parola nuova: sono la KV cache già incontrata nel capitolo sui Transformer. Ogni risposta in corso porta con sé i propri appunti, e quegli appunti crescono a ogni token senza che si sappia fin dove. Chi gestisce la memoria si trova quindi davanti a una scelta scomoda: o riserva a ciascuno lo spazio del caso peggiore, e allora in memoria ci stanno pochissime conversazioni, o rischia di restare senza spazio a metà di una risposta.

Una sala sola, cinquanta coperti, la fila alla porta. Il modo ingenuo di gestirla fa due errori. Il primo: a ogni comitiva si riserva in anticipo un tavolone lungo, nel caso arrivino altri amici, e due sedie su tre restano vuote «per sicurezza» mentre la gente in fila se ne va. Il secondo: prima di far accomodare qualcuno si aspetta che un tavolo si liberi del tutto, e intanto le sedie già libere non le usa nessuno.

Un buon maître fa il contrario, e le sue due mosse portano i nomi con cui si parla di questa faccenda dappertutto.

Tavoloni non ne riserva. Sistema gli ospiti su gruppetti di sedie sparsi dove c’è posto, tutti della stessa misura, e tiene un foglietto con scritto chi siede dove; le sedie vuote scendono a meno di una su venticinque. È la PagedAttention: gli appunti di ogni conversazione non stanno più in un blocco unico prenotato in anticipo, ma in tanti pezzetti sparsi, e un indice dice quali pezzetti sono di chi.

E appena una sedia si libera ci fa accomodare la prossima persona in fila, senza aspettare che se ne vada l’intera comitiva, ed è il continuous batching, il mazzo continuo.

E c’è una ragione per cui conviene riempirla, quella sala: il cameriere fa un giro solo, e con quel giro serve tutti i tavoli pronti, dieci o cinquanta che siano, perché il tempo se ne va nel giro e non nei piatti che porta. Più coperti vogliono dire più clienti serviti nella stessa serata, ed è ciò che permette a un LLM di rispondere a migliaia di persone con lo stesso hardware. Un prezzo però c’è, e lo paga chi è già seduto, perché con la sala piena anche il giro si allunga un po’ e il piatto arriva più tardi. Dove mettere l’ago dipende dal locale, visto che una mensa vuole coperti e un ristorante vuole il piatto puntuale.

Il «tavolone prenotato» è la gestione ingenua della KV cache: si riserva un blocco di memoria contiguo grande quanto il contesto massimo possibile, anche se la sequenza resterà corta. Ne nascono due sprechi, frammentazione interna (lo spazio riservato e mai usato) ed esterna (i buchi fra blocchi di taglia diversa), che negli approcci precedenti bruciavano tra il 60% e l’80% della memoria della cache. La soluzione di vLLM, la PagedAttention [KLZ+23], prende in prestito un’idea vecchia di sessant’anni dai sistemi operativi: la paginazione della memoria virtuale. La cache di ogni sequenza è spezzata in blocchi di taglia fissa, sistemati in modo non contiguo dove c’è spazio, con una block table che mappa posizioni logiche a fisiche: proprio il foglietto del maître. Lo spreco scende sotto il 4%, e i blocchi possono perfino essere condivisi tra sequenze (un prompt comune, o le ipotesi di una beam search) senza duplicarli.

L’altra metà è il continuous batching (o in-flight batching): invece di attendere che tutte le sequenze di un batch finiscano (costringendo le più brevi ad aspettare la più lunga) lo scheduler lavora a livello di singola iterazione, e appena una sequenza emette il suo token di fine, un’altra richiesta ne prende il posto nel batch. Finché c’è una coda, la sala resta piena. Insieme, PagedAttention e continuous batching permettono batch molto più grandi a parità di memoria: nella misura riportata dagli autori, contro i sistemi che c’erano allora, un throughput da due a quattro volte maggiore a parità di latenza. Resta il compromesso di fondo, già incontrato nella sezione sul servire un modello: batch più grandi alzano il throughput ma allungano la coda della latenza; il punto di equilibrio dipende dal prodotto.

Speculative decoding: far indovinare a un modello piccolo#

C’è una seconda strada per accelerare la generazione, e vive dove il batching non arriva: quando le richieste sono poche e quello che conta è vedere la risposta subito. Ha anche una proprietà rara: il testo che esce resta testo del modello grande.

In alto un modello bozza, piccolo e veloce, genera in sequenza quattro token candidati. In basso il modello grande li verifica tutti insieme in un'unica passata parallela: accetta i primi tre e al quarto lo corregge, scrivendo di suo il token che sceglie lui. In alto un modello bozza, piccolo e veloce, genera in sequenza quattro token candidati. In basso il modello grande li verifica tutti insieme in un'unica passata parallela: accetta i primi tre e al quarto lo corregge, scrivendo di suo il token che sceglie lui.

Fig. 35.11 Il modello piccolo tira a indovinare, che gli costa poco; il grande verifica tutte le sue proposte in un colpo solo, che gli costa quanto scriverne una. Se il piccolo azzecca quasi tutto, quella singola verifica consegna quattro token (i tre accettati più quello che il grande scrive di suo) al prezzo di uno: sono tre giri risparmiati. E se il piccolo sbaglia, la correzione del grande è comunque quella giusta.#

La proprietà rara annunciata sopra si legge nella metà inferiore di Fig. 35.11: il modello grande non si fida mai del piccolo, lo controlla, e il controllo mette a confronto le due probabilità. Se il grande dava a quella parola almeno la fiducia che le dava il piccolo, la prende senz’altro; se gliene dava meno, la prende tanto più di rado quanto più i due erano in disaccordo, e quando la scarta scrive lui. Il conto è costruito perché quello che esce sia sorteggiato come lo avrebbe sorteggiato il grande da solo: se davanti a una frase quel modello sceglie sempre la stessa parola, esce parola per parola il suo testo; se sorteggia fra più continuazioni, escono le stesse continuazioni con le stesse probabilità. Cambia il tempo, non il testo.

Un revisore esperto non manda in stampa una riga senza aver ricontrollato il manuale di stile, settecento pagine, e se le rilegge da capo ogni volta. Controllare una riga o quattro gli costa quasi uguale, perché il tempo se ne va nel manuale. Vale lo stesso per il modello, che per scrivere una parola si rilegge tutti i suoi numeri e con quella rilettura potrebbe controllarne quattro.

Accanto al revisore siede uno stagista veloce, che il manuale non lo apre. Butta giù quattro righe in avanti tirando a indovinare, e ci prende spesso, perché scrivere non è difficile dappertutto: dopo «la capitale della Francia è» viene «Parigi», e a sbagliare si fa fatica. Sui pochi punti che contano (un numero, una svolta del discorso) invece sbaglia. Lo stagista è un secondo modello, piccolo e rapido, il modello bozza; il revisore è quello grande.

Il revisore legge le quattro proposte in un colpo, con una rilettura sola del manuale. Quelle su cui è convinto almeno quanto lo era lo stagista passano subito; quelle su cui è convinto meno di lui passano solo ogni tanto, tanto più di rado quanto più i due la pensavano diversamente, e alla prima bocciata butta il resto e riscrive la riga di suo pugno. Senza il suo assenso non passa niente.

Se lo stagista ne azzecca tre su quattro, escono quattro righe nel tempo di una. Se sbaglia quasi sempre si va più piano che senza di lui, perché il revisore riscrive tutto e per giunta ha aspettato le bozze. E se ha già la scrivania piena di lavoro suo non stava aspettando nessuno: le proposte dello stagista non gli fanno guadagnare niente.

Il metodo è dovuto a Leviathan, Kalman e Matias di Google Research [LKM23] e, indipendentemente, a Chen e colleghi di DeepMind [CBI+23]. Il passo è:

  1. il modello bozza \(p_{\text{b}}\) genera \(\gamma\) token in autoregressione;

  2. il modello target \(p_{\text{t}}\) valuta le \(\gamma+1\) posizioni in parallelo, in una sola passata, il costo è quello di un forward, non di \(\gamma\);

  3. ogni token proposto \(x_i\) è accettato con probabilità \(\min\!\bigl(1,\ p_{\text{t}}(x_i)/p_{\text{b}}(x_i)\bigr)\); al primo rifiuto si campiona un token correttivo dalla distribuzione residua normalizzata \(\bigl[p_{\text{t}}(x)-p_{\text{b}}(x)\bigr]_+\) e si scarta la coda.

Questa regola di accettazione-rifiuto è ciò che rende il metodo esatto: la distribuzione dei token emessi è identica a quella del solo modello target. L’uscita è la stessa, solo più in fretta, senza nessuno scambio fra qualità e velocità.

Il guadagno dipende dal tasso di accettazione \(\alpha\): sotto l’ipotesi semplificatrice (dichiarata dagli autori) che le accettazioni siano indipendenti con tasso costante \(\alpha\), il numero atteso di token per passata è

\[ \frac{1-\alpha^{\gamma+1}}{1-\alpha}, \]

che per \(\alpha=0{,}8\) e \(\gamma=4\) dà circa \(3{,}4\) token contro \(1\). In pratica si osservano accelerazioni di 2–3 volte. Il modello bozza dev’essere molto più economico del target e allineato nella distribuzione, altrimenti \(\alpha\) crolla e il costo delle bozze rifiutate mangia il guadagno.

Le varianti che evitano di mantenere un secondo modello (in letteratura self-drafting, da non confondere con il self-speculative decoding, che è un metodo preciso e fa la bozza saltando strati del modello stesso) vanno distinte proprio sulla proprietà appena rivendicata. Alcune cambiano solo chi propone e tengono la verifica standard, quindi restano esatte: il prompt lookup, che pesca le proposte dal testo già presente nel contesto, ed EAGLE, che fa proporre le bozze a una testina leggera addestrata sulle rappresentazioni interne del modello grande. Medusa, nella configurazione che propone e misura, no: al posto del campionamento per rifiuto adotta la typical acceptance, un criterio a soglia scelto apposta per accettare più token al prezzo di allontanarsi dalla distribuzione del modello target. Il campionamento per rifiuto resta disponibile anche lì, ma senza il guadagno in più. È un compromesso legittimo, e va saputo: chi lo adotta credendo di stare ancora nel metodo esatto sta scambiando qualità per velocità senza essersene accorto.

Un avvertimento pratico: il metodo aiuta nel regime memory-bound, cioè batch piccoli e bassa latenza. A batch molto grandi la GPU è già satura di lavoro utile e il vantaggio si assottiglia: si combina male, non bene, con la spinta al throughput del batching visto poco sopra.

Comprimere per servire#

Se il vincolo è la memoria (quanta ce n’è, e quanto in fretta la si legge), la leva più diretta è far pesare meno i pesi. L’idea l’abbiamo già vista nella sezione sul servire un modello: la quantizzazione, cioè riscrivere i decimali finissimi dei pesi come interi grossolani, arrotondati ai gradini di una scala; passando da sedici a quattro cifre binarie ogni peso occupa quattro volte meno. È da qui, per inciso, che veniva il mezzo gigabyte a miliardo della prima figura.

Sugli LLM questa leva conta doppio, per due ragioni. La prima è che qui il tempo se ne va nel rileggere i pesi: alleggerirli non fa solo risparmiare spazio, accorcia direttamente il tempo di ogni token. La seconda è che riaddestrare un modello da centinaia di miliardi di parametri è fuori portata per quasi tutti, quindi la quantizzazione va fatta dopo l’addestramento, senza rimettere mano alla ricetta originale.

Barre che confrontano la memoria occupata dallo stesso modello da 7 miliardi di parametri a precisioni diverse: circa 28 gigabyte con numeri a 32 bit, 14 a 16 bit, 7 a 8 bit e 3,5 a 4 bit. Accanto alla barra più corta, l'annotazione che a 4 bit quel modello entra in un portatile. Barre che confrontano la memoria occupata dallo stesso modello da 7 miliardi di parametri a precisioni diverse: circa 28 gigabyte con numeri a 32 bit, 14 a 16 bit, 7 a 8 bit e 3,5 a 4 bit. Accanto alla barra più corta, l'annotazione che a 4 bit quel modello entra in un portatile.

Fig. 35.12 Lo stesso modello, quattro ingombri. Le sigle sono i nomi tecnici delle quattro scritture (quanti bit occupa ciascun numero, e se ha o no la virgola: FP32 sono trentadue bit con la virgola, INT4 quattro bit senza), ma a decidere sono i gigabyte, e la decisione è un sì o un no: ventotto vogliono una macchina da centro dati, tre e mezzo entrano in un portatile.#

Quello che Fig. 35.12 racconta non è un risparmio graduale, ed è per questo che la quantizzazione conta più di quanto un taglio del 75% suggerisca. Le quattro barre sono quattro risposte a una domanda che ammette solo sì o no, cioè «ci sta nella memoria che ho?», e non quattro sconti sempre più generosi. Sul portatile da 8 gigabyte della prima figura, che dopo il margine per la conversazione ne lascia liberi cinque, le prime tre righe sono tutte e tre un no, e la differenza fra loro non serve a niente: l’unica che cambia la vita è la quarta.

In un trasloco quasi tutto si schiaccia in scatoloni fitti fitti, e si risparmia un mucchio di spazio. Quasi tutto. I bicchieri buoni e il vaso della nonna, pigiati come il resto, si rompono, e hai rovinato il trasloco per due centimetri di spazio.

Fra i miliardi di numeri di un modello succede lo stesso. Una manciata è fragile e portante, e arrotondarla come le altre fa crollare la qualità. Riconoscerla è il difficile, perché non si vede dalla stazza: quei pochi numeri sono quelli da cui passa tutto, e non i più grossi, come il corridoio di casa, che è il pezzo più stretto e ci deve passare tutto quello che entra ed esce.

Poi ognuno ha il suo modo di proteggere i pochi delicati. C’è chi li tiene in una scatola a parte, senza schiacciarli. C’è chi li imbottisce prima e poi li pigia insieme agli altri, e occupano poco lo stesso. E c’è chi schiaccia un oggetto per volta, risistemando dopo ognuno quelli che restano, così l’ammaccatura non si accumula tutta sull’ultimo.

Il guadagno non arriva a quattro volte esatte, e la colpa è delle etichette. Ogni scatolone ne porta una che dice come è stato chiuso, e occupa spazio anche lei. Scatoloni più piccoli vogliono dire etichette più fitte, quindi meno spazio guadagnato e meno roba rotta, e si resta poco sotto le quattro volte. All’arrivo le scatole delicate si aprono per controllare.

E perché proprio quattro volte, e non otto? Perché il furgone è quello che è, e quello che conta è quanta roba arriva intera a destinazione. Stringendo, nel furgone ci sta più roba, e fino a un certo punto ne arriva intera di più; stringendo ancora, si rompe più di quanto se ne guadagni, e il furgone arriva pieno di cocci. Il punto in cui la bilancia si rovescia è stato cercato, ed è lì, alle quattro volte. Con la premessa che si stia imballando a occhio: chi guarda prima che cosa sta mettendo in scatola riesce a stringere un po” di più.

La mappa affine \(r = S\,(q - Z)\) della sezione sul deployment vale qui identica, e il principio che la regge, guardare che cosa un peso fa invece di quanto vale, lo costruisce Meno bit: qui interessa che cosa cambia quando il modello non si può riaddestrare. Tre metodi post-training si sono affermati, e condividono l’intuizione che non tutti i numeri contano uguale, e tutti e tre guardano le attivazioni, cioè i numeri che attraversano il modello mentre risponde: si distinguono per che cosa ne fanno, ed è la distinzione che di solito si perde.

Il primo, LLM.int8(), ci cerca dentro i valori anomali [DLBZ22]. Scopre che oltre una certa scala ne emergono, e che sono concentrati in poche dimensioni. Siccome la scala di quantizzazione la fissa il valore più grande, quelle poche dimensioni enormi schiacciano tutte le altre in pochi gradini, e una scala sola per tutti distrugge la qualità. La soluzione è una moltiplicazione di matrici a precisione mista, con la stragrande maggioranza dei valori in int8 e le poche dimensioni anomale tenute in 16 bit. Così la qualità regge fino a 175 miliardi di parametri.

Il secondo, GPTQ, le usa per stimare la curvatura [FAHA23]. Fa passare per il modello un piccolo insieme di testi di calibrazione, e dagli ingressi di ogni strato ricava la matrice hessiana, cioè l’informazione del second’ordine che dice quanto l’uscita di quello strato soffre se un peso si sposta. Poi quantizza uno strato alla volta, un peso dopo l’altro, correggendo man mano sui pesi rimasti l’errore appena introdotto. Così scende a 3 o 4 bit per peso; un modello da 175 miliardi di parametri gli costa circa quattro ore su una sola A100, con degrado trascurabile.

Il terzo, AWQ (Activation-aware Weight Quantization), le usa per decidere quali pesi proteggere [LTT+24]. I pesi che contano non sono i più grandi ma quelli attraversati dai valori più grandi, e sono circa l’uno per cento: i canali salienti. La mossa poi sta nel riscalarli prima di quantizzarli, non nel tenerli in 16 bit, e questo evita sia la retropropagazione sia la ricostruzione su un obiettivo di regressione: è l’obiezione che gli autori di AWQ muovono a GPTQ, cioè che aderendo al proprio insieme di calibrazione rischi di generalizzare peggio fuori da quello.

Un’avvertenza sul «circa quattro volte». A 8 bit per tensore i due scalari di calibrazione sono trascurabili e il conto torna esatto; a 4 bit non più, perché né GPTQ né AWQ usano una scala per tensore, ma gruppi di pesi (tipicamente 128), ed è proprio quella granularità a rendere i 4 bit praticabili. Ogni gruppo si porta dietro la sua scala e il suo zero (mettiamo sedici bit per la prima e quattro per il secondo, venti in tutto): spalmati su 128 pesi fanno \(4{,}16\) bit effettivi per peso, su 32 pesi ne fanno \(4{,}62\). Il rapporto reale rispetto ai 16 bit è quindi \(3{,}8\times\), non \(4\times\): qualche punto percentuale di bit in più, speso per comprare qualità.

Il compromesso è sempre lo stesso (meno bit significano meno memoria e più velocità, ma più rischio per la qualità) e vale la regola d’oro della sezione sul deployment: la quantizzazione va misurata su dati di validazione, mai data per gratuita. E i 4 bit sono il punto in cui la convenienza si rovescia, almeno quando si arrotonda e basta. Dettmers e Zettlemoyer lo ricavano confrontando, a parità di bit totali occupati, modelli di taglia diversa scritti a precisioni diverse: l’accuratezza zero-shot sale mano a mano che si scende da 16 a 4 bit, e torna a scendere a 3 [DZ23]. Vale su cinque famiglie di modelli densi, da 19 milioni a 176 miliardi di parametri, e quasi dappertutto, con qualche eccezione che gli autori nominano; e i blocchi da 128 pesi o meno sono la loro raccomandazione operativa, non la condizione che tiene in piedi il verdetto, perché a 3 bit il ribaltamento arriva comunque.

Due riserve, e la prima gliela muovono gli autori a sé stessi: dalla loro misura resta fuori tutta la famiglia che si tara su dei dati, e la nominano citando GPTQ (AWQ, che è della stessa famiglia, sarebbe arrivato qualche mese dopo). Non è una riserva di forma: sulla perplessità di WikiText-2 un GPTQ a 2 bit batte un arrotondamento a 3. Sotto i 4 bit non è chiuso: è chiuso per chi arrotonda senza guardare i dati. La seconda: il verdetto è sul compromesso fra memoria e qualità, non sulla velocità, e gli autori scrivono che a molte richieste al secondo, cioè proprio nel regime della sala piena, quelle leggi di scala con la latenza non c’entrano quasi più.

L’altra leva: togliere pesi invece di accorciarli#

La quantizzazione scrive gli stessi pesi con meno cifre. La potatura (pruning) fa una cosa diversa: ne butta via una parte.

Quanto se ne possa buttare, e perché toglierne il novanta per cento non renda un modello dieci volte più veloce, li costruisce Meno pesi. Qui interessa quello che rende gli LLM un caso a sé.

Comprimere i numeri è come riscrivere gli stessi appunti con una grafia più piccola: ci sono ancora tutti. Potare è strappare delle pagine. Su una rete piccola si può strappare e poi rileggere tutto da capo per rimettere insieme il senso, ed è quello che si fa; su un modello da miliardi di numeri quella rilettura costerebbe quanto costruirlo, e nessuno la fa dopo un rilascio. Per questo qui si strappa molto meno, e si sceglie con cura: le pagine da togliere sono quelle scritte in piccolo e che nessuno rilegge mai, non quelle scritte in piccolo e basta. Con questa cautela si arriva a buttarne circa metà senza danni evidenti; oltre, il conto si fa salato.

Sugli LLM la potatura post-training è più delicata che sulle reti di visione, perché non si può riaddestrare. SparseGPT e Wanda affrontano proprio questo: il secondo, in particolare, sceglie cosa togliere pesando ogni peso per la norma dell’attivazione corrispondente; lo stesso principio di AWQ, che i pesi importanti si riconoscono guardando cosa ci passa attraverso, non quanto sono grandi. Con questi metodi il \(50\%\) di sparsità è raggiungibile senza riaddestramento e con degrado contenuto; oltre, il conto si fa salato.

La ragione per cui su un LLM la potatura è più difficile è tutta in quella riga: non si può riaddestrare. Su una rete piccola il riaddestramento è il passaggio che riporta la rete dov’era, e Meno pesi lo misura; qui quel passaggio non c’è, perché riaddestrare un modello da miliardi di parametri non è una cosa che si fa a valle di un deploy. SparseGPT e Wanda esistono proprio per sostituirlo: invece di riaddestrare tutto, aggiustano strato per strato i pesi rimasti, guardando che cosa ci passa attraverso. È un rimedio locale, e si vede dal traguardo: metà dei pesi, non i nove decimi. Da qui la regola pratica: a parità di rischio si comincia dalla quantizzazione, che il riaddestramento non lo chiede. E vale la regola di sempre, misurare, perché il degrado si distribuisce in modo diseguale fra i compiti e una media aggregata lo nasconde.

Valutare l’invalutabile#

Un modello servito e compresso va poi tenuto d’occhio: funziona ancora bene? E qui casca l’asino, perché tutti i modi consueti di dargli un voto qui si rompono.

Il primo è la misura che il modello porta con sé, la perplessità, vista nel capitolo sui Transformer: dice quanto il modello è indeciso a ogni token, ed è come contare le facce del dado che gli servirebbe per tirare a indovinare al suo posto. Due facce vuol dire che esita fra due parole, mille facce che non ne ha idea. È una misura ottima mentre il modello impara la lingua, ma non dice quasi niente di ciò che conta poi: la risposta è utile? corretta? ben scritta?

Il secondo sono gli esami standard, i benchmark, sempre visti nel capitolo sui Transformer, che sono compiti in classe uguali per tutti i modelli. Vanno letti con un sospetto preciso, quello della contaminazione: se le domande dell’esame erano già finite dentro i testi su cui il modello si è allenato, il suo bel voto non dice niente, perché quelle domande le aveva già viste.

E poi c’è il problema di fondo, che nessuno dei due risolve: per una richiesta aperta («scrivi una mail di scuse al cliente») non esiste la risposta giusta con cui confrontarsi.

La stessa domanda viene posta a due modelli diversi, che producono due risposte. Le due risposte, insieme alla domanda, vengono passate a un terzo modello che fa da giudice e dichiara quale preferisce. Nessun riferimento assoluto entra nel confronto: si stabilisce solo un ordine fra le due. La stessa domanda viene posta a due modelli diversi, che producono due risposte. Le due risposte, insieme alla domanda, vengono passate a un terzo modello che fa da giudice e dichiara quale preferisce. Nessun riferimento assoluto entra nel confronto: si stabilisce solo un ordine fra le due.

Fig. 35.13 Nessuna risposta giusta, solo un confronto. Il giudice non dice se una risposta è corretta: dice quale delle due preferisce, ed è una domanda a cui si può rispondere anche quando la prima non ha risposta.#

Il cambio di domanda in Fig. 35.13 è ciò che rende il metodo praticabile, e insieme ciò che ne fissa i limiti. Un ordine fra due risposte si può stabilire senza un riferimento assoluto. Ma un giudice che preferisce porta con sé i propri gusti, e due di quei gusti si ripetono sempre uguali: premia chi gli è stato presentato per primo, e premia chi scrive di più. Un terzo, la simpatia per chi scrive come scriverebbe lui, è sospettato e non dimostrato. Non sono errori di programmazione, che qualcuno prima o poi correggerà: sono la conseguenza di aver chiesto una preferenza invece di una verifica.

Chi corregge il tema, se non c’è una risposta esatta? A scuola lo fa un insegnante, che legge, soppesa e dà un voto. Ma di temi da correggere ne arrivano migliaia al minuto, e un insegnante costa tempo. La scorciatoia è promuovere a esaminatore uno studente molto bravo, che legge e dà il voto in un lampo, a costo quasi nullo.

Il suo voto vale qualcosa? Messo a scegliere il migliore fra due temi, va d’accordo con un insegnante in carne e ossa più di quattro volte su cinque, che è quanto due insegnanti vanno d’accordo fra loro. Per quello che costa, è un ottimo affare.

Ha però le sue manie, sempre le stesse. A parità di tutto il resto dà il voto più alto al tema che ha letto per primo. Premia il tema lungo, scambiando l’abbondanza di parole per competenza, anche quando una risposta breve e centrata sarebbe migliore; i più svegli ci cascano molto meno, ma nessuno ne è immune. Si sospetta che apprezzi anche chi scrive come scrive lui, ma i temi raccolti non bastano a dirlo. Contro la mania dell’ordine un rimedio c’è: dargli i due temi anche nell’ordine opposto, e tenere per buono solo chi vince tutte e due le volte; se i due giri si contraddicono, è pari. La mania del tema lungo resta.

Il guaio grosso arriva se la classe capisce come ragiona l’esaminatore. Da quel momento tutti scrivono lungo, e per primi quando possono: i voti salgono e i temi peggiorano. Quel voto serve a tenere d’occhio la classe, non a decidere che cosa si insegna.

Il pattern si chiama LLM-as-a-judge [ZCS+23]: si usa un modello forte (nel lavoro originale, GPT-4) per assegnare un punteggio o per scegliere la migliore fra due risposte. Zheng e colleghi lo validano su due banchi di prova (MT-Bench, ottanta domande a più turni, e Chatbot Arena, confronti a coppie raccolti dal pubblico e aggregati con un punteggio Elo) e misurano che il giudice-GPT-4 concorda con le preferenze umane oltre l’80% delle volte: lo stesso livello di accordo che due esseri umani hanno fra loro. Quell’oltre l’80% ha un protocollo, e sta nella tabella: è l’85% sui soli voti non pari, dove due giudici a caso concorderebbero già nella metà dei casi (fra due umani, 81%); contando anche i pareggi e le incoerenze si scende al 66%, quanto due umani fra loro (che stanno al 63% sul primo turno e al 67% sul secondo), là dove il caso darebbe 33. Il giudice automatico non è poi neutro, e i suoi bias hanno nomi precisi: il position bias (tende a preferire la risposta presentata per prima) e il verbosity bias (favorisce le risposte lunghe), misurati tutti e due, il secondo con un attacco che allunga la risposta senza aggiungerci niente e a cui GPT-4 resiste molto meglio degli altri giudici provati (ci casca nell’8,7% dei casi, contro il 91,3%); il terzo, il self-enhancement bias, gli autori lo osservano senza poterlo dimostrare, perché qualche giudice preferisce sé stesso (GPT-4 di dieci punti di win rate, Claude-v1 di venticinque) ma preferisce anche modelli diversi da sé, e GPT-3.5 non preferisce sé stesso. Il position bias si mitiga chiamando il giudice due volte a ordini scambiati e dichiarando vincitore solo chi vince in tutti e due i giri, pari quando i due giri si contraddicono; il resto non sparisce. È la stessa lezione del reward model del capitolo sui Transformer: un giudice appreso è un surrogato del giudizio umano, e ottimizzare troppo contro un surrogato porta al reward hacking.

Alla valutazione si affianca, quando il servizio è acceso, la sicurezza di ciò che esce. Al modello, durante l’addestramento, si è già insegnato quali risposte sono preferibili e quali no: sono le due tecniche del capitolo sui Transformer che là si chiamano per sigla, RLHF e DPO. Quell’insegnamento lo rende meno incline a rispondere in modo dannoso, ma non offre garanzie: restano una disposizione appresa, e una disposizione si aggira. Per questo i sistemi reali aggiungono dei guardrail, che in italiano sono proprio i guard rail dell’autostrada: filtri e classificatori indipendenti dal modello, che ispezionano quello che entra e quello che esce. Servono a bloccare contenuti dannosi e dati personali, ma anche due mosse che hanno un nome preciso: le istruzioni nascoste dentro un testo che il modello deve leggere, scritte apposta perché le prenda per ordini (la prompt injection), e i tentativi di farsi dire ciò che il modello non dovrebbe dire, aggirandone le regole (il jailbreak). Nessuno di questi strumenti è perfetto; messi insieme, riducono il rischio senza azzerarlo.

Il ciclo LLMOps#

Tirando le somme: l’anello dell’MLOps torna intatto (dati, addestramento, valutazione, consegna, sorveglianza), ma con gli LLM cambia quello che ci gira dentro, cioè le cose di cui si conserva ogni versione. Spesso non sono i pesi, che arrivano già fatti da qualcun altro: è il prompt, la riga d’istruzione con cui si spiega al modello che cosa deve fare. Quella riga è codice a tutti gli effetti, ed è fragile come abbiamo visto nel capitolo sui Transformer, dove basta una parola diversa per cambiare la risposta: quindi se ne conserva ogni versione, la si prova, e prima di sostituirla si mettono in campo la vecchia e la nuova su due metà del pubblico per vedere quale funziona meglio (è il test A/B della sezione sul monitoraggio). Il monitoraggio, a sua volta, insegue bersagli nuovi: le allucinazioni (risposte sicure di sé e sbagliate), la deriva dell’uso rispetto a ciò per cui il sistema era tarato, e il costo per token, che scala con quanto testo entra ed esce; un prompt gonfio è una bolletta più salata. E poiché il testo aperto non si collauda con i test unitari del software classico, serve una valutazione continua: una batteria di esempi che gira a ogni cambio di prompt o di modello, spesso con l’LLM-as-a-judge a fare da metro automatico.

Resta fuori, di proposito, tutto ciò che sta sopra il modello: ancorare le risposte a documenti recuperati al momento (il retrieval-augmented generation nella sua forma avanzata), far usare al modello strumenti esterni, comporre più passi in un agente. È il capitolo sugli Agenti, che abbiamo già percorso.

Da ricordare

  • Con un grande modello linguistico la parte lenta è la rilettura: per scrivere una sola parola il modello deve rileggersi tutti i suoi numeri, e sono miliardi. Il calcolo, in confronto, è quasi fermo.

  • La prima domanda è quale ci sta nella memoria che si ha, prima ancora di quale sia migliore: se non ci sta, non è lento, proprio non parte.

  • Servendo tante richieste insieme quella rilettura si paga una volta per tutte, quindi conviene servirne più che si può: per questo un buon maître non riserva tavoloni e riempie ogni sedia appena si libera.

  • Si può far indovinare in anticipo un modello piccolo e far verificare al grande in blocco quello che ha indovinato: se il piccolo azzecca si va molto più veloci, e passa solo quello che il grande avrebbe potuto scrivere lui. Serve però quando le richieste sono poche: con la sala piena il grande ha già da fare per conto suo.

  • Per farlo entrare si comprime: si arrotondano i numeri, o se ne buttano via una parte. Ma alcuni numeri sono fragili e portanti, come i bicchieri buoni in un trasloco, e vanno trattati a parte.

  • Giudicare un testo aperto non ha una risposta esatta: si usa un altro modello come esaminatore, comodo ed economico, sapendo che ha sempre le stesse due manie, il tema che ha letto per primo e quello più lungo.

  • E quello che il modello scrive va comunque filtrato all’ingresso e all’uscita: quel che ha imparato a non dire è una disposizione, e una disposizione si aggira.

  • Quello che qui si conserva versione per versione non sono i pesi, che spesso arrivano già fatti: è l’istruzione con cui si parla al modello, che è fragile come il codice e come il codice va provata prima di sostituirla.

Da ricordare

  • Con gli LLM il collo di bottiglia si sposta sulla generazione autoregressiva: l’inferenza è memory-bound (leggere i pesi domina sul calcolo), e la KV cache vista nel capitolo sui Transformer occupa memoria che cresce col contesto.

  • Servire un LLM significa batchare tante richieste per ammortizzare la lettura dei pesi: la PagedAttention di vLLM pagina la KV cache come un sistema operativo, e lo spreco passa dal 60–80% a meno del 4% [KLZ+23]; il continuous batching tiene il batch sempre pieno. Insieme, nella misura degli autori, da due a quattro volte di throughput a parità di latenza.

  • Lo speculative decoding è esatto grazie alla regola di accettazione-rifiuto [LKM23], e non tutte le sue varianti lo restano: EAGLE e il prompt lookup sì, Medusa no, perché la typical acceptance scambia la distribuzione del target per un tasso di accettazione più alto.

  • Comprimere per servire: quantizzazione post-training (riaddestrare è fuori portata) con la mappa affine \(r = S(q - Z)\) della sezione sul deployment, ma per gruppi di pesi, non per tensore: a 4 bit i bit effettivi sono circa 4,2. I tre metodi guardano tutti le attivazioni e ne fanno cose diverse: LLM.int8() ci isola le dimensioni anomale [DLBZ22], GPTQ ne ricava l’hessiana su un insieme di calibrazione e scende a 3–4 bit [FAHA23], AWQ le usa per scegliere l’1% di pesi da proteggere [LTT+24]. Sempre da misurare. Che i 4 bit siano l’ottimo lo si misura a parità di bit totali occupati e sulla sola accuratezza zero-shot [DZ23], e per chi arrotonda senza guardare i dati: i metodi che si tarano su un insieme di calibrazione restano fuori da quella misura, e sotto i 4 bit non è chiuso.

  • Valutare l’invalutabile: la perplessità non basta e i benchmark si contaminano; per l’output aperto si usa LLM-as-a-judge, che sui soli voti non pari concorda con l’uomo l’85% delle volte contro l’81% fra due umani [ZCS+23], coi suoi bias di posizione e di verbosità (l’auto-preferenza gli autori non riescono a dimostrarla). In produzione servono guardrail su ingresso e uscita.

  • Il ciclo LLMOps versiona i prompt come codice e monitora allucinazioni, deriva e costo per token, con valutazione continua. RAG avanzato, tool use e agenti hanno un capitolo dedicato, che abbiamo già percorso.