Esegui il deploy di GPT-J 6B per l’inferenza utilizzando Hugging Face Transformers e Amazon SageMaker

Deploy GPT-J 6B for inference using Hugging Face Transformers and Amazon SageMaker.

Circa 6 mesi fa, EleutherAI ha rilasciato GPT-J 6B, un’alternativa open-source al GPT-3 di OpenAI. GPT-J 6B è il successore con 6 miliardi di parametri della famiglia GPT-NEO di EleutherAI, una famiglia di modelli di linguaggio basati su trasformatori e basati sull’architettura GPT per la generazione di testo.

Il obiettivo principale di EleutherAI è addestrare un modello equivalente in dimensioni a GPT-3 e renderlo disponibile al pubblico con una licenza aperta.

Negli ultimi 6 mesi, GPT-J ha suscitato molto interesse da parte di ricercatori, data scientist e persino sviluppatori di software, ma è rimasto molto difficile distribuire GPT-J in produzione per casi d’uso e prodotti reali.

Ci sono alcune soluzioni ospitate per utilizzare GPT-J per carichi di lavoro di produzione, come l’API di inferenza di Hugging Face, o per sperimentare usando il playground 6b di EleutherAI, ma ci sono pochi esempi su come distribuirlo facilmente nel proprio ambiente.

In questo post del blog, imparerai come distribuire facilmente GPT-J utilizzando Amazon SageMaker e Hugging Face Inference Toolkit con poche righe di codice per inferenze scalabili, affidabili e sicure in tempo reale utilizzando un’istanza GPU di dimensioni regolari con NVIDIA T4 (~500$/mese).

Ma prima di entrare nel dettaglio, voglio spiegare perché la distribuzione di GPT-J in produzione è una sfida.


Contesto

I pesi del modello con 6 miliardi di parametri rappresentano una memoria di circa 24GB. Per caricarla in float32, sarebbe necessario almeno il doppio della dimensione del modello di RAM CPU: 1x per i pesi iniziali e un altro 1x per caricare il checkpoint. Quindi, per GPT-J, sarebbero necessari almeno 48GB di RAM CPU solo per caricare il modello.

Per rendere il modello più accessibile, EleutherAI fornisce anche pesi in float16 e transformers ha nuove opzioni per ridurre l’occupazione di memoria durante il caricamento di modelli di linguaggio di grandi dimensioni. Combinando tutto questo, dovrebbe richiedere circa 12,1GB di RAM CPU per caricare il modello.

from transformers import GPTJForCausalLM
import torch

model = GPTJForCausalLM.from_pretrained(
    "EleutherAI/gpt-j-6B",
        revision="float16",
        torch_dtype=torch.float16,
        low_cpu_mem_usage=True
)

La limitazione di questo esempio è che ci vuole molto tempo prima che il modello venga caricato in memoria e sia pronto per l’uso. Nei miei esperimenti, ci sono voluti 3 minuti e 32 secondi per caricare il modello con il frammento di codice sopra su un’istanza AWS EC2 P3.2xlarge (il modello non era archiviato su disco). Questa durata può essere ridotta archiviando il modello già su disco, riducendo il tempo di caricamento a 1 minuto e 23 secondi, che è comunque molto lungo per i carichi di lavoro di produzione in cui è necessario considerare la scalabilità e l’affidabilità.

Ad esempio, Amazon SageMaker ha un limite di 60 secondi per rispondere alle richieste, il che significa che il modello deve essere caricato e le previsioni eseguite entro 60 secondi, il che, secondo me, ha molto senso per mantenere il modello/endpoint scalabile e affidabile per il tuo carico di lavoro. Se hai previsioni più lunghe, potresti utilizzare la trasformazione batch.

In Transformers, i modelli caricati con il metodo from_pretrained seguono la pratica consigliata di PyTorch, che richiede circa 1,97 secondi per BERT [REF]. PyTorch offre un modo alternativo aggiuntivo per salvare e caricare modelli utilizzando torch.save(model, PATH) e torch.load(PATH).

“Salvare un modello in questo modo salverà l’intero modulo utilizzando il modulo pickle di Python. Lo svantaggio di questo approccio è che i dati serializzati sono legati alle classi specifiche e alla struttura delle directory esatta utilizzata durante il salvataggio del modello.”

Questo significa che quando salviamo un modello con transformers==4.13.2, potrebbe essere potenzialmente incompatibile quando si cerca di caricarlo con transformers==4.15.0. Tuttavia, il caricamento dei modelli in questo modo riduce il tempo di caricamento di ~12 volte, fino a 0,166 secondi per BERT.

Applicando questo a GPT-J significa che possiamo ridurre il tempo di caricamento da 1 minuto e 23 secondi a 7,7 secondi, che è circa 10,5 volte più veloce.

Figura 1. Tempo di caricamento del modello di BERT e GPTJ

Tutorial

Con questo metodo di salvataggio e caricamento dei modelli, abbiamo ottenuto prestazioni di caricamento del modello per GPT-J compatibili con scenari di produzione. Ma dobbiamo tenere presente che dobbiamo allineare:

Allinea la versione di PyTorch e Transformers quando salvi il modello con torch.save(modello, PERCORSO) e carichi il modello con torch.load(PERCORSO) per evitare incompatibilità.

Salva GPT-J usando torch.save

Per creare il nostro file di modello compatibile con torch.load() carichiamo GPT-J usando Transformers e il metodo from_pretrained, e poi lo salviamo con torch.save().

from transformers import AutoTokenizer,GPTJForCausalLM
import torch

# carica il modello fp 16
modello = GPTJForCausalLM.from_pretrained("EleutherAI/gpt-j-6B", revision="float16", torch_dtype=torch.float16)
# salva il modello con torch.save
torch.save(modello, "gptj.pt")

Ora siamo in grado di caricare il nostro modello GPT-J con torch.load() per eseguire previsioni.

from transformers import pipeline
import torch

# carica il modello
modello = torch.load("gptj.pt")
# carica il tokenizer
tokenizer = AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6B")

# crea il pipeline
gen = pipeline("text-generation",model=modello,tokenizer=tokenizer,device=0)

# esegui la previsione
gen("Il mio nome è philipp")
#[{'generated_text': 'Il mio nome è philipp k. e vivo appena fuori Detroit....

Crea model.tar.gz per il punto finale in tempo reale di Amazon SageMaker

Dato che possiamo caricare il nostro modello rapidamente ed eseguire inferenze su di esso, procediamo al suo deployment su Amazon SageMaker.

Esistono due modi per effettuare il deployment di transformers su Amazon SageMaker. Puoi “Effettuare il deployment di un modello dal Hugging Face Hub” direttamente o “Effettuare il deployment di un modello con model_data archiviato su S3″. Visto che non stiamo usando il metodo predefinito di Transformers, dobbiamo optare per la seconda opzione e fare il deploy del nostro endpoint con il modello archiviato su S3.

A tale scopo, dobbiamo creare un artefatto model.tar.gz che contenga i pesi del nostro modello e i file aggiuntivi necessari per l’inferenza, ad esempio tokenizer.json.

Forniamo artefatti model.tar.gz caricati e accessibili pubblicamente, che possono essere utilizzati con HuggingFaceModel per effettuare il deployment di GPT-J su Amazon SageMaker.

Vedi “Effettuare il deployment di GPT-J come endpoint di Amazon SageMaker” per sapere come usarli.

Se desideri o hai ancora bisogno di creare il tuo model.tar.gz, ad esempio a causa di linee guida sulla conformità, puoi utilizzare lo script di supporto convert_gpt.py per questo scopo, che crea il model.tar.gz e lo carica su S3.

# clona la directory
git clone https://github.com/philschmid/amazon-sagemaker-gpt-j-sample.git

# cambia directory in amazon-sagemaker-gpt-j-sample
cd amazon-sagemaker-gpt-j-sample

# crea e carica model.tar.gz
pip3 install -r requirements.txt
python3 convert_gptj.py --bucket_name {model_storage}

Il convert_gpt.py dovrebbe stampare un URI S3 simile a questo. s3://hf-sagemaker-inference/gpt-j/model.tar.gz.

Effettuare il deployment di GPT-J come endpoint di Amazon SageMaker

Per effettuare il deployment del nostro endpoint Amazon SageMaker, utilizzeremo l’SDK Python di Amazon SageMaker e la classe HuggingFaceModel.

Il frammento di codice seguente utilizza la funzione get_execution_role, che è disponibile solo all’interno delle istanze Amazon SageMaker Notebook o Studio. Se desideri effettuare il deployment di un modello al di fuori di esse, consulta la documentazione.

Il model_uri definisce la posizione del nostro artefatto del modello GPT-J. Useremo quello disponibile pubblicamente fornito da noi.

from sagemaker.huggingface import HuggingFaceModel
import sagemaker

# IAM role con i permessi per creare un endpoint
role = sagemaker.get_execution_role()

# URI S3 pubblico per l'artefatto gpt-j
model_uri="s3://huggingface-sagemaker-models/transformers/4.12.3/pytorch/1.9.1/gpt-j/model.tar.gz"

# crea la classe del modello Hugging Face
huggingface_model = HuggingFaceModel(
    model_data=model_uri,
    transformers_version='4.12.3',
    pytorch_version='1.9.1',
    py_version='py38',
    role=role, 
)

# distribuisci il modello su SageMaker Inference
predictor = huggingface_model.deploy(
    initial_instance_count=1, # numero di istanze
    instance_type='ml.g4dn.xlarge' #'ml.p3.2xlarge' # tipo di istanza ec2
)

Se vuoi utilizzare il tuo model.tar.gz, sostituisci il model_uri con il tuo S3 Uri.

La distribuzione dovrebbe richiedere circa 3-5 minuti.

Esegui le predizioni

Possiamo eseguire le predizioni utilizzando le istanze predictor create dal nostro metodo .deploy. Per inviare una richiesta al nostro endpoint, utilizziamo predictor.predict con il nostro inputs.

predictor.predict({
    "inputs": "Puoi per favore fornirci ulteriori dettagli sulla tua "
})

Se vuoi personalizzare le tue predizioni utilizzando ulteriori kwargs come min_length, consulta “Best practice di utilizzo” di seguito.

Best practice di utilizzo

Quando si utilizzano modelli generativi, nella maggior parte dei casi si desidera configurare o personalizzare la previsione per adattarla alle proprie esigenze, ad esempio utilizzando la ricerca beam, configurando la lunghezza massima o minima della sequenza generata o regolando la temperatura per ridurre la ripetizione. La libreria Transformers fornisce diverse strategie e kwargs per farlo, il toolkit di inferenza di Hugging Face offre la stessa funzionalità utilizzando l’attributo parameters del payload della tua richiesta. Di seguito puoi trovare esempi su come generare testo senza parametri, con ricerca beam e utilizzando configurazioni personalizzate. Se desideri conoscere diverse strategie di decodifica, consulta questo post del blog.

Richiesta predefinita

Questo è un esempio di una richiesta predefinita utilizzando la ricerca greedy.

Tempo di inferenza dopo la prima richiesta: 3s

predictor.predict({
    "inputs": "Puoi per favore fornirci ulteriori dettagli sulla tua "
})

Richiesta di ricerca beam

Questo è un esempio di una richiesta utilizzando la ricerca beam con 5 beam.

Tempo di inferenza dopo la prima richiesta: 3.3s

predictor.predict({
    "inputs": "Puoi per favore fornirci ulteriori dettagli sulla tua ",
  "parameters" : {
    "num_beams": 5,
  }
})

Richiesta parametrizzata

Questo è un esempio di una richiesta utilizzando un parametro personalizzato, ad esempio min_length per generare almeno 512 token.

Tempo di inferenza dopo la prima richiesta: 38s

predictor.predict({
    "inputs": "Puoi per favore fornirci ulteriori dettagli sulla tua ",
  "parameters" : {
    "max_length": 512,
    "temperature": 0.9,
  }
})

Esempio con pochi esempi (avanzato)

Questo è un esempio di come potresti utilizzare eos_token_id per interrompere la generazione su un certo token, ad esempio \n, . o ### per predizioni con pochi esempi. Di seguito è riportato un esempio con pochi esempi per generare tweet per parole chiave.

Tempo di inferenza dopo la prima richiesta: 15-45s

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6B")

end_sequence="###"
temperature=4
max_generated_token_length=25
prompt= """chiave: mercati
tweet: Prendi feedback dalla natura e dai mercati, non dalle persone.
###
chiave: bambini
tweet: Forse moriamo per poter tornare bambini.
###
chiave: startup
tweet: Le startup non dovrebbero preoccuparsi di come spegnere gli incendi, dovrebbero preoccuparsi di come accenderli.
###
chiave: hugging face
tweet:"""

predictor.predict({
    'inputs': prompt,
  "parameters" : {
    "max_length": int(len(prompt) + max_generated_token_length),
    "temperature": float(temperature),
    "eos_token_id": int(tokenizer.convert_tokens_to_ids(end_sequence)),
    "return_full_text":False
  }
})

Per eliminare il tuo endpoint puoi eseguire.

predictor.delete_endpoint()

Conclusione

Siamo riusciti con successo a distribuire GPT-J, un modello di linguaggio da 6 miliardi di parametri creato da EleutherAI, utilizzando Amazon SageMaker. Abbiamo ridotto il tempo di caricamento del modello da 3,5 minuti a 8 secondi per poter eseguire inferenze scalabili e affidabili.

Ricorda che l’utilizzo di torch.save() e torch.load() può creare problemi di incompatibilità. Se desideri saperne di più sul ridimensionamento dei tuoi endpoint Amazon SageMaker, dai un’occhiata al mio altro post sul blog: “MLOps: End-to-End Hugging Face Transformers con Hub & SageMaker Pipelines”.


Grazie per la lettura! Se hai qualche domanda, non esitare a contattarmi tramite Github o sul forum. Puoi anche connetterti con me su Twitter o LinkedIn.