Rilascio di Swift Transformers Esegui LLM on-device sui dispositivi Apple

Rilascio di Swift Transformers per eseguire LLM on-device su dispositivi Apple

Ho molto rispetto per gli sviluppatori iOS/Mac. Ho iniziato a scrivere app per iPhone nel 2007, quando non esistevano nemmeno API o documentazione. I nuovi dispositivi hanno adottato alcune decisioni poco familiari nello spazio dei vincoli, con una combinazione di potenza, spazio sullo schermo, idiomatismi dell’interfaccia utente, accesso alla rete, persistenza e latenza diversa da quella a cui eravamo abituati prima. Eppure, questa comunità è riuscita presto a creare applicazioni di alta qualità che si adattavano perfettamente al nuovo paradigma.

Credo che l’Intelligenza Artificiale sia un nuovo modo di costruire software e so che molti sviluppatori Swift vogliono incorporare funzionalità di IA nelle loro app. L’ecosistema dell’IA si è molto evoluto, con migliaia di modelli che risolvono una vasta gamma di problemi. Inoltre, i LLM (Linguaggio Naturale Modello) sono recentemente emersi come strumenti quasi universali: possono essere adattati a nuovi domini purché si possa modellare il nostro compito per lavorare su dati testuali o simili al testo. Stiamo assistendo a un momento fondamentale nella storia dell’informatica, in cui i LLM stanno uscendo dai laboratori di ricerca e diventando strumenti informatici per tutti.

Tuttavia, utilizzare un modello LLM come Llama in un’app comporta diverse attività che molte persone affrontano e risolvono da sole. Abbiamo esplorato questo spazio e vorremmo continuare a lavorarci insieme alla comunità. Vogliamo creare un insieme di strumenti e blocchi di costruzione che aiutino gli sviluppatori a costruire più velocemente.

Oggi, pubblichiamo questa guida per accompagnarti nei passaggi necessari per eseguire un modello come Llama 2 sul tuo Mac utilizzando Core ML. Stiamo anche rilasciando librerie e strumenti in versione alpha per supportare gli sviluppatori in questo percorso. Chiamiamo tutti gli sviluppatori Swift interessati all’IA – che sono tutti gli sviluppatori Swift, vero? – a contribuire con PR, segnalazioni di bug o opinioni per migliorare insieme tutto ciò.

Andiamo!

Video: Modello di chat Llama 2 (7B) in esecuzione su un MacBook Pro M1 con Core ML.

Rilasciato Oggi

  • swift-transformers, un pacchetto Swift in fase di sviluppo per implementare un’API simile a quella dei transformer in Swift focalizzata sulla generazione di testo. È un’evoluzione di swift-coreml-transformers con obiettivi più ampi: integrazione con Hub, supporto a tokenizzatori arbitrari e modelli plug-and-play.
  • swift-chat, un’app semplice che mostra come utilizzare il pacchetto.
  • Una versione aggiornata di exporters, un pacchetto di conversione Core ML per modelli di transformer.
  • Una versione aggiornata di transformers-to-coreml, uno strumento di conversione Core ML senza codice basato su exporters.
  • Alcuni modelli convertiti, come Llama 2 7B o Falcon 7B, pronti per l’uso con questi strumenti di generazione di testo.

Panoramica delle Attività

Quando ho pubblicato tweet mostrando Falcon o Llama 2 in esecuzione sul mio Mac, ho ricevuto molte domande da altri sviluppatori su come convertire quei modelli in Core ML, perché vogliono usarli anche nelle loro app. La conversione è un passaggio fondamentale, ma è solo la prima parte del puzzle. La vera ragione per cui scrivo queste app è affrontare gli stessi problemi che affronterebbe qualsiasi altro sviluppatore e identificare aree in cui possiamo aiutare. In questa parte del post, affronteremo alcune di queste attività, spiegando dove (e dove no) abbiamo strumenti per aiutare.

  • Conversione in Core ML. Utilizzeremo Llama 2 come esempio concreto.
  • Tecniche di ottimizzazione per far funzionare il tuo modello (e l’app) in modo veloce e consumare il minimo di memoria possibile. Questa è un’area che permea l’intero progetto e non esiste una soluzione magica che si possa applicare.
  • swift-transformers, la nostra nuova libreria per aiutare con alcune attività comuni.
    • Tokenizzatori. La tokenizzazione è il modo per convertire l’input di testo nell’effettivo insieme di numeri che vengono elaborati dal modello (e tornare al testo dalle previsioni generate). Questo è molto più complesso di quanto sembri, poiché ci sono molte opzioni e strategie diverse.
    • Modelli e wrapper dell’Hub. Se vogliamo supportare la vasta gamma di modelli sull’Hub, non possiamo permetterci di codificare duramente le impostazioni del modello. Abbiamo creato un’astrazione semplice LanguageModel e varie utility per scaricare file di configurazione del modello e del tokenizzatore dall’Hub.
    • Algoritmi di generazione. I modelli di linguaggio sono addestrati per prevedere una distribuzione di probabilità per il token successivo che può comparire dopo una sequenza di testo. Dobbiamo chiamare il modello più volte per generare l’output del testo e selezionare un token ad ogni passo. Ci sono molti modi per decidere quale token dovremmo scegliere successivamente.
    • Modelli Supportati. Non tutti i tipi di modelli sono supportati (ancora).
  • swift-chat. Si tratta di una piccola app che semplicemente mostra come utilizzare swift-transformers in un progetto.
  • Parti Mancanti / Prossimi Passi. Alcuni elementi importanti ma non ancora disponibili, come indicazioni per lavori futuri.
  • Risorse. Link a tutti i progetti e gli strumenti.

Conversione in Core ML

Core ML è il framework nativo di Apple per il Machine Learning, ed è anche il nome del formato di file che utilizza. Dopo aver convertito un modello da (ad esempio) PyTorch a Core ML, è possibile utilizzarlo nelle app Swift. Il framework Core ML seleziona automaticamente l’hardware migliore su cui eseguire il modello: la CPU, la GPU o un’unità tensore specializzata chiamata Neural Engine. È anche possibile combinare diverse di queste unità di calcolo, a seconda delle caratteristiche del sistema e dei dettagli del modello.

Per vedere come appare la conversione di un modello nella vita reale, vedremo la conversione del modello Llama 2, recentemente rilasciato. Il processo può essere complicato, ma offriamo alcuni strumenti per aiutare. Questi strumenti non funzionano sempre, poiché vengono introdotti continuamente nuovi modelli e occorre apportare modifiche e adattamenti.

Il nostro approccio consigliato è:

  1. Utilizzare lo spazio di conversione transformers-to-coreml:

Questo è uno strumento automatizzato basato su exporters (vedi sotto) che funziona per il tuo modello o non funziona. Non richiede alcuna codifica: inserisci l’identificatore del modello Hub, seleziona il compito per cui intendi utilizzare il modello e fai clic su Applica. Se la conversione riesce, puoi inviare i pesi di Core ML convertiti all’Hub e hai finito!

Puoi visitare lo spazio o utilizzarlo direttamente qui:

  1. Utilizza exporters, un pacchetto di conversione Python basato su coremltools di Apple (vedi sotto).

Questa libreria offre molte più opzioni per configurare il compito di conversione. Inoltre, consente di creare la propria classe di configurazione di conversione, che può essere utilizzata per un controllo aggiuntivo o per risolvere problemi di conversione.

  1. Utilizza coremltools, il pacchetto di conversione di Apple.

Questo è l’approccio a livello più basso e quindi fornisce il massimo controllo. Può ancora fallire per alcuni modelli (soprattutto quelli nuovi), ma hai sempre l’opzione di esaminare il codice sorgente e cercare di capire il motivo.

La buona notizia su Llama 2 è che abbiamo fatto il lavoro di preparazione e il processo di conversione funziona utilizzando uno qualsiasi di questi metodi. La cattiva notizia è che non è riuscito a convertire quando è stato rilasciato e abbiamo dovuto apportare alcune correzioni per supportarlo. Esaminiamo brevemente cosa è successo nell’allegato in modo che tu possa avere un’idea di cosa fare quando le cose vanno storte.

Lezioni importanti apprese

Ho seguito il processo di conversione per alcuni modelli recenti (Llama 2, Falcon, StarCoder) e ho applicato ciò che ho imparato sia agli exporters che allo spazio transformers-to-coreml. Questo è un riassunto di alcuni punti salienti:

  • Se devi utilizzare coremltools, utilizza l’ultima versione: 7.0b1. Nonostante sia tecnicamente una versione beta, l’ho utilizzata per settimane ed è davvero ottima: stabile, include molte correzioni, supporta PyTorch 2 e ha nuove funzionalità come strumenti avanzati di quantizzazione.
  • exporters non applica più una softmax agli output durante la conversione di compiti di generazione di testo. Abbiamo capito che questo era necessario per alcuni algoritmi di generazione.
  • exporters ora utilizza per impostazione predefinita lunghezze di sequenza fisse per i modelli di testo. Core ML ha un modo per specificare “forme flessibili”, in modo che la sequenza di input possa avere una lunghezza compresa tra 1 e, ad esempio, 4096 token. Abbiamo scoperto che gli input flessibili funzionano solo su CPU, ma non su GPU o il Neural Engine. Presto arriveranno ulteriori approfondimenti!

Continueremo ad aggiungere le migliori pratiche ai nostri strumenti in modo che tu non debba scoprire gli stessi problemi nuovamente.

Ottimizzazione

Non ha senso convertire i modelli se non vengono eseguiti rapidamente sull’hardware di destinazione e rispettano le risorse di sistema. I modelli menzionati in questo post sono piuttosto grandi per l’uso locale e li utilizziamo consapevolmente per spingere ai limiti ciò che è possibile con la tecnologia attuale e comprendere dove si trovano i collo di bottiglia.

Ci sono alcune aree chiave di ottimizzazione che abbiamo identificato. Sono un argomento molto importante per noi e oggetto di lavoro attuale e futuro. Alcuni di essi includono:

  • Memorizzare nella cache le chiavi di attenzione e i valori delle generazioni precedenti, proprio come fanno i modelli transformers nell’implementazione PyTorch. Il calcolo dei punteggi di attenzione deve essere eseguito sull’intera sequenza generata finora, ma tutte le coppie chiave-valore passate sono state già calcolate in esecuzioni precedenti. Attualmente non stiamo utilizzando alcun meccanismo di memorizzazione nella cache per i modelli Core ML, ma stiamo pianificando di farlo!
  • Utilizzare forme discrete invece di una piccola lunghezza di sequenza fissa. Il motivo principale per non utilizzare forme flessibili è che non sono compatibili con la GPU o il Neural Engine. Un motivo secondario è che la generazione diventerebbe più lenta all’aumentare della lunghezza della sequenza, a causa dell’assenza di memorizzazione nella cache come già menzionato. Utilizzare un insieme discreto di forme fisse, unitamente alla memorizzazione nella cache delle coppie chiave-valore, dovrebbe consentire dimensioni di contesto più ampie e un’esperienza di chat più naturale.
  • Tecniche di quantizzazione. Le abbiamo già esplorate nel contesto dei modelli di diffusione stabile e siamo davvero entusiasti delle opzioni che potrebbero offrire. Ad esempio, la palettizzazione a 6 bit riduce la dimensione del modello ed è efficiente con le risorse. La quantizzazione a bit misti, una nuova tecnica, può ottenere una quantizzazione a 4 bit (in media) con un impatto ridotto sulla qualità del modello. Stiamo pianificando di lavorare su questi argomenti anche per i modelli di linguaggio!

Per le applicazioni di produzione, considera di iterare con modelli più piccoli, specialmente durante lo sviluppo, e poi applica tecniche di ottimizzazione per selezionare il modello più piccolo che puoi permetterti per il tuo caso d’uso.

swift-transformers

swift-transformers è un pacchetto Swift in fase di sviluppo che mira a fornire un’API simile a transformers agli sviluppatori Swift. Vediamo cosa ha e cosa manca.

Tokenizzatori

La tokenizzazione risolve due compiti complementari: adattare l’input di testo al formato tensoriale utilizzato dal modello e convertire i risultati del modello in testo. Il processo è sfumato, ad esempio:

  • Usiamo parole, caratteri, gruppi di caratteri o byte?
  • Come dovremmo gestire le lettere minuscole rispetto alle lettere maiuscole? Dovremmo occuparci anche della differenza?
  • Dovremmo rimuovere caratteri ripetuti, come spazi, o sono importanti?
  • Come gestiamo le parole che non sono nel vocabolario del modello?

Ci sono alcuni algoritmi di tokenizzazione generali e molti passaggi di normalizzazione e pre-elaborazione diversi che sono cruciali per l’utilizzo efficace del modello. La libreria transformers ha preso la decisione di astrarre tutte queste operazioni nella stessa libreria (tokenizers), e rappresentare le decisioni come file di configurazione che vengono memorizzati nell’Hub insieme al modello. Ad esempio, questo è un estratto dalla configurazione del tokenizzatore Llama 2 che descrive solo il passaggio di normalizzazione:

  "normalizer": {
    "type": "Sequence",
    "normalizers": [
      {
        "type": "Prepend",
        "prepend": "▁"
      },
      {
        "type": "Replace",
        "pattern": {
          "String": " "
        },
        "content": "▁"
      }
    ]
  },

Si legge così: la normalizzazione è una sequenza di operazioni applicate nell’ordine. Prima, si Prepend il carattere _ alla stringa di input. Poi si sostituiscono tutti gli spazi con _. C’è una lunga lista di operazioni potenziali, possono essere applicate a corrispondenze di espressioni regolari e devono essere eseguite in un ordine molto specifico. Il codice nella libreria tokenizers si occupa di tutti questi dettagli per tutti i modelli nell’Hub.

Al contrario, i progetti che utilizzano modelli di linguaggio in altri domini, come le app Swift, di solito ricorrono alla codifica rigida di queste decisioni come parte del codice sorgente dell’app. Questo va bene per un paio di modelli, ma poi diventa difficile sostituire un modello con un altro diverso e è facile commettere errori.

Ciò che stiamo facendo in swift-transformers è replicare queste astrazioni in Swift, in modo da scriverle una volta sola e tutti possono usarle nelle loro app. Siamo appena all’inizio, quindi la copertura è ancora limitata. Sentiti libero di aprire issue nel repo o contribuire con le tue!

In particolare, al momento supportiamo i tokenizzatori BPE (Byte-Pair Encoding), una delle tre principali famiglie in uso oggi. I modelli GPT, Falcon e Llama utilizzano tutti questo metodo. Il supporto per i tokenizzatori Unigram e WordPiece verrà aggiunto in seguito. Non abbiamo ancora portato tutti i possibili normalizzatori, pre-tokenizzatori e post-processori, solo quelli che abbiamo incontrato durante le nostre conversioni dei modelli Llama 2, Falcon e GPT.

Ecco come utilizzare il modulo Tokenizers in Swift:

import Tokenizers

func testTokenizer() async throws {
    let tokenizer = try await AutoTokenizer.from(pretrained: "pcuenq/Llama-2-7b-chat-coreml")
    let inputIds = tokenizer("Oggi ha preso un treno per l'Ovest")
    assert(inputIds == [1, 20628, 1183, 3614, 263, 7945, 304, 278, 3122])
}

Tuttavia, di solito non è necessario tokenizzare il testo di input da soli: il codice Generation se ne occuperà.

Wrapper di modelli e Hub

Come spiegato in precedenza, transformers utilizza ampiamente file di configurazione memorizzati nell’Hub. Abbiamo preparato un semplice modulo Hub per scaricare i file di configurazione dall’Hub, che viene utilizzato per istanziare il tokenizzatore e recuperare i metadati sul modello.

Per quanto riguarda i modelli, abbiamo creato un semplice tipo LanguageModel come wrapper per un modello Core ML, focalizzandoci sul compito di generazione di testo. Utilizzando i protocolli, possiamo interrogare qualsiasi modello con la stessa API.

Per recuperare i metadati appropriati per il modello che stai usando, swift-transformers si basa su alcuni campi di metadati personalizzati che devono essere aggiunti al file Core ML durante la conversione. swift-transformers utilizzerà queste informazioni per scaricare tutti i file di configurazione necessari dall’Hub. Questi sono i campi che utilizziamo, come presentati nella visualizzazione del modello di Xcode:

exporters e transformers-to-coreml aggiungeranno automaticamente questi campi per te. Assicurati di aggiungerli manualmente se usi coremltools.

Algoritmi di generazione

I modelli di linguaggio sono addestrati per prevedere una distribuzione di probabilità del token successivo che può apparire come continuazione di una sequenza di input. Per comporre una risposta, dobbiamo chiamare il modello più volte fino a quando non produce un token di terminazione speciale, o raggiungiamo la lunghezza desiderata. Ci sono molti modi per decidere quale sia il prossimo miglior token da utilizzare. Al momento supportiamo due di essi:

  • Decodifica greedy. Questo è l’algoritmo ovvio: selezionare il token con la probabilità più alta, aggiungerlo alla sequenza e ripetere. Ciò produrrà sempre lo stesso risultato per la stessa sequenza di input.
  • Campionamento top-k. Seleziona i top-k (dove k è un parametro) token più probabili e quindi campiona casualmente tra di essi utilizzando parametri come temperature, che aumenterà la variabilità a scapito di poter far perdere il controllo al modello e deviare dal contenuto.

Metodi aggiuntivi come il “campionamento a nucleo” verranno introdotti in futuro. Consigliamo questo post sul blog (aggiornato di recente) per una panoramica eccellente sui metodi di generazione e su come funzionano. Metodi sofisticati come la generazione assistita possono essere anche molto utili per l’ottimizzazione!

Modelli supportati

Fino ad ora, abbiamo testato swift-transformers con alcuni modelli per convalidare le principali decisioni di progettazione. Non vediamo l’ora di provarne molti altri!

  • Llama 2.
  • Falcon.
  • Modelli StarCoder, basati su una variante dell’architettura GPT.
  • Famiglia GPT, compresi GPT2, distilgpt, GPT-NeoX, GPT-J.

swift-chat

swift-chat è una semplice app dimostrativa costruita su swift-transformers. Il suo scopo principale è mostrare come utilizzare swift-transformers nel tuo codice, ma può anche essere utilizzata come strumento di test per i modelli.

Per utilizzarla, scarica un modello Core ML dall’Hub o creane uno tuo e selezionalo dall’interfaccia utente. Tutti i file di configurazione del modello pertinenti verranno scaricati dall’Hub, utilizzando le informazioni dei metadati per identificare il tipo di modello.

La prima volta che carichi un nuovo modello, ci vorrà del tempo per prepararlo. In questa fase, il framework CoreML compilerà il modello e deciderà su quali dispositivi di calcolo eseguirlo, in base alle specifiche della tua macchina e alla struttura del modello. Queste informazioni vengono memorizzate nella cache e riutilizzate nelle esecuzioni future.

L’app è intenzionalmente semplice per renderla leggibile e concisa. Mancano anche alcune funzionalità, principalmente a causa delle attuali limitazioni della dimensione del contesto del modello. Ad esempio, non prevede alcuna disposizione per “prompt di sistema”, che sono utili per specificare il comportamento del tuo modello di linguaggio e persino la sua personalità.

Parti mancanti / Prossimi passi

Come detto, stiamo appena iniziando! Le nostre prossime priorità includono:

  • Modelli encoder-decoder come T5 e Flan.
  • Altri tokenizzatori: supporto per Unigram e WordPiece.
  • Altri algoritmi di generazione.
  • Supporto per la memorizzazione nella cache chiave-valore per l’ottimizzazione.
  • Utilizzare forme di sequenza discrete per la conversione. Insieme alla memorizzazione nella cache chiave-valore, ciò consentirà contesti più ampi.

Fateci sapere cosa pensate che dovremmo lavorare successivamente o dirigetevi ai repository per Good First Issues per mettervi alla prova!

Conclusioni

Abbiamo introdotto un insieme di strumenti per aiutare gli sviluppatori Swift a incorporare modelli linguistici nelle loro app. Non vedo l’ora di vedere cosa creerai con loro e sono ansioso di migliorarli con l’aiuto della comunità! Non esitare a metterti in contatto 🙂

Appendice: Convertire Llama 2 nel Modo Difficile

Puoi tranquillamente ignorare questa sezione a meno che tu non abbia riscontrato problemi di conversione di Core ML con PyTorch e sia pronto a combattere 🙂

Nella mia esperienza, ci sono due motivi frequenti per cui i modelli PyTorch non riescono a convertirsi in Core ML utilizzando coremltools:

  • Operazioni o varianti di operazioni PyTorch non supportate

PyTorch ha molte operazioni e tutte devono essere mappate in una rappresentazione intermedia (MIL, per Model Intermediate Language), che a sua volta viene convertita in istruzioni native di Core ML. L’insieme di operazioni PyTorch non è statico, quindi devono essere aggiunte anche a coremltools quelle nuove. Inoltre, alcune operazioni sono veramente complesse e possono funzionare su combinazioni esotiche dei loro argomenti. Un esempio di operazione molto complessa recentemente aggiunta è l’attenzione con prodotto scalare, introdotta in PyTorch 2. Un esempio di operazione parzialmente supportata è einsum: non tutte le equazioni possibili vengono tradotte in MIL.

  • Casi limite e incompatibilità di tipo

Anche per le operazioni PyTorch supportate, è molto difficile garantire che il processo di traduzione funzioni su tutti i possibili input e su tutti i diversi tipi di input. Tieni presente che una singola operazione PyTorch può avere implementazioni backend multiple per dispositivi diversi (CPU, CUDA), tipi di input (intero, float) o precisione (float16, float32). Il prodotto di tutte le combinazioni è sorprendente e talvolta il modo in cui un modello utilizza il codice PyTorch attiva un percorso di traduzione che potrebbe non essere stato considerato o testato.

Questo è ciò che è successo quando ho provato a convertire Llama 2 utilizzando coremltools:

Confrontando diverse versioni di transformers, ho potuto vedere che il problema è iniziato quando è stata introdotta questa riga di codice. Fa parte di una recente rifattorizzazione di transformers per gestire meglio le maschere causali in tutti i modelli che le utilizzano, quindi questo sarebbe un grande problema anche per altri modelli, non solo per Llama.

Ciò che lo screenshot dell’errore ci sta dicendo è che c’è una incompatibilità di tipo nel tentativo di riempire il tensore della maschera. Deriva dal 0 nella riga: viene interpretato come un int, ma il tensore da riempire contiene float, e l’uso di tipi diversi è stato respinto dal processo di traduzione. In questo caso specifico, ho trovato una soluzione alternativa per coremltools, ma fortunatamente questo è raramente necessario. In molti casi, puoi correggere il tuo codice (un 0.0 in una copia locale di transformers avrebbe funzionato), o creare un “operazione speciale” per gestire il caso eccezionale. La nostra libreria exporters supporta molto bene le operazioni personalizzate e speciali. Guarda questo esempio per un’equazione mancante di einsum, o questo per una soluzione alternativa per far funzionare i modelli di StarCoder fino all’uscita di una nuova versione di coremltools.

Fortunatamente, la copertura di coremltools per le nuove operazioni è buona e il team reagisce molto velocemente.

Risorse

  • swift-transformers.
  • swift-chat.
  • exporters.
  • transformers-to-coreml.
  • Alcuni modelli Core ML per la generazione di testo:
    • Llama-2-7b-chat-coreml
    • Falcon-7b-instruct