QLoRA: come ottimizzare un LLM su una singola GPU |  di Shaw Talebi |  Febbraio 2024

 | Intelligenza-Artificiale

Importazioni

Importiamo moduli da Hugging Face’s trasforma, peftE set di dati biblioteche.

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
from peft import prepare_model_for_kbit_training
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
import transformers

Inoltre, affinché alcuni dei moduli precedenti funzionino, è necessario che siano installate le seguenti dipendenze.

!pip install auto-gptq
!pip install optimum
!pip install bitsandbytes

Carica modello base e tokenizzatore

Successivamente, carichiamo il modello quantizzato da Hugging Face. Qui usiamo una versione di Mistral-7B-Instruct-v0.2 preparato da TheBlokeche ne ha liberamente quantizzato e condiviso migliaia LLM.

Nota che stiamo utilizzando la versione “Instruct” di Mistral-7b. Ciò indica che il modello ha subito messa a punto delle istruzioni, un processo di messa a punto Quello mira a migliorare le prestazioni del modello nel rispondere alle domande e rispondere ai suggerimenti dell’utente.

Oltre a specificare il repository del modello che vogliamo scaricare, impostiamo anche i seguenti argomenti: mappa_dispositivo, trust_remote_codeE revisione. mappa_dispositivo consente al metodo di capire automaticamente come allocare al meglio le risorse computazionali per caricare il modello sulla macchina. Prossimo, trust_remote_code=Falso impedisce l’esecuzione dei file del modello personalizzato sul computer. Poi, finalmente, revisione specifica quale versione del modello vogliamo utilizzare dal repository.

model_name = "TheBloke/Mistral-7B-Instruct-v0.2-GPTQ"
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto",
trust_remote_code=False,
revision="main")

Una volta caricato, vediamo che il modello con parametri 7B ci porta solo 4,16 GB di memoriache può essere facilmente inserito nella memoria della CPU o della GPU disponibile gratuitamente su Colab.

Successivamente, carichiamo il tokenizzatore per il modello. Ciò è necessario perché il modello prevede che il testo venga codificato in un modo specifico. ho discusso tokenizzazione In articoli precedenti di questa serie.

tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)

Utilizzando il modello base

Successivamente, possiamo utilizzare il modello per la generazione del testo. Come primo passaggio, proviamo a inserire un commento di prova nel modello. Possiamo farlo in 3 passaggi.

Primocreiamo il prompt nel formato corretto. Vale a dire, Mistral-7b-Instruct prevede che il testo di input inizi e termini rispettivamente con i token speciali (INST) e (/INST). Secondotokenizziamo il prompt. Terzopassiamo il prompt nel modello per generare il testo.

Il codice per farlo è mostrato di seguito con il commento di prova, “Ottimo contenuto, grazie!

model.eval() # model in evaluation mode (dropout modules are deactivated)

# craft prompt
comment = "Great content, thank you!"
prompt=f'''(INST) {comment} (/INST)'''

# tokenize input
inputs = tokenizer(prompt, return_tensors="pt")

# generate output
outputs = model.generate(input_ids=inputs("input_ids").to("cuda"),
max_new_tokens=140)

print(tokenizer.batch_decode(outputs)(0))

La risposta del modello è mostrata di seguito. Anche se inizia bene, la risposta sembra continuare senza una buona ragione e non sembra qualcosa che direi.

I'm glad you found the content helpful! If you have any specific questions or 
topics you'd like me to cover in the future, feel free to ask. I'm here to
help.

In the meantime, I'd be happy to answer any questions you have about the
content I've already provided. Just let me know which article or blog post
you're referring to, and I'll do my best to provide you with accurate and
up-to-date information.

Thanks for reading, and I look forward to helping you with any questions you
may have!

Ingegneria rapida

Qui è dove ingegneria tempestiva è utile. Da articolo precedente in questa serie ho trattato questo argomento in modo approfondito, dirò solo che il prompt engineering implica creare istruzioni che portino a risposte migliori del modello.

In genere, scrivere buone istruzioni è qualcosa di fatto attraverso tentativi ed errori. Per fare ciò, ho provato diverse iterazioni di prompt utilizzando insieme.aiche ha un’interfaccia utente gratuita per molti LLM open source, come Mistral-7B-Instruct-v0.2.

Una volta ricevute le istruzioni di cui ero soddisfatto, ho creato un modello di prompt che combina automaticamente queste istruzioni con un commento utilizzando una funzione lambda. Il codice per questo è mostrato di seguito.

intstructions_string = f"""ShawGPT, functioning as a virtual data science \
consultant on YouTube, communicates in clear, accessible language, escalating \
to technical depth upon request. \
It reacts to feedback aptly and ends responses with its signature '–ShawGPT'. \
ShawGPT will tailor the length of its responses to match the viewer's comment,
providing concise acknowledgments to brief expressions of gratitude or \
feedback, thus keeping the interaction natural and engaging.

Please respond to the following comment.
"""

prompt_template =
lambda comment: f'''(INST) {intstructions_string} \n{comment} \n(/INST)'''

prompt = prompt_template(comment)

The Prompt
-----------

(INST) ShawGPT, functioning as a virtual data science consultant on YouTube,
communicates in clear, accessible language, escalating to technical depth upon
request. It reacts to feedback aptly and ends responses with its signature
'–ShawGPT'. ShawGPT will tailor the length of its responses to match the
viewer's comment, providing concise acknowledgments to brief expressions of
gratitude or feedback, thus keeping the interaction natural and engaging.

Please respond to the following comment.

Great content, thank you!
(/INST)

Possiamo vedere la potenza di un buon suggerimento confrontando la risposta del nuovo modello (sotto) con quella precedente. Qui il modello risponde in modo conciso e appropriato e si identifica come ShawGPT.

Thank you for your kind words! I'm glad you found the content helpful. –ShawGPT

Preparare il modello per la formazione

Vediamo come possiamo migliorare le prestazioni del modello attraverso la messa a punto. Possiamo iniziare abilitando il checkpoint del gradiente e l’addestramento quantizzato. Checkpoint del gradiente è una tecnica di risparmio di memoria che cancella attivazioni specifiche e le ricalcola durante il passaggio all’indietro (6). Allenamento quantizzato è abilitato utilizzando il metodo importato da peft.

model.train() # model in training mode (dropout modules are activated)

# enable gradient check pointing
model.gradient_checkpointing_enable()

# enable quantized training
model = prepare_model_for_kbit_training(model)

Successivamente, possiamo impostare la formazione con LoRA tramite un oggetto di configurazione. Qui, miriamo a livelli di interrogazione nel modello e utilizzare an rango intrinseco 8. Utilizzando questa configurazione, possiamo creare una versione del modello che può essere sottoposta a messa a punto con LoRA. Stampando il numero di parametri addestrabili osserviamo una riduzione di oltre 100X.

# LoRA config
config = LoraConfig(
r=8,
lora_alpha=32,
target_modules=("q_proj"),
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)

# LoRA trainable version of model
model = get_peft_model(model, config)

# trainable parameter count
model.print_trainable_parameters()

### trainable params: 2,097,152 || all params: 264,507,392 || trainable%: 0.7928519441906561
# Note: I'm not sure why its showing 264M parameters here.

Preparare il set di dati di addestramento

Ora possiamo importare i nostri dati di allenamento. Il set di dati utilizzato qui è disponibile su HuggingFace Dataset Hub. Ho generato questo set di dati utilizzando commenti e risposte dal mio Canale Youtube. Il codice per preparare e caricare il dataset sull’Hub è disponibile all’indirizzo Deposito GitHub.

# load dataset
data = load_dataset("shawhin/shawgpt-youtube-comments")

Successivamente, dobbiamo preparare il set di dati per l’addestramento. Ciò implica garantire che gli esempi abbiano una lunghezza adeguata e siano tokenizzati. Il codice per questo è mostrato di seguito.

# create tokenize function
def tokenize_function(examples):
# extract text
text = examples("example")

#tokenize and truncate text
tokenizer.truncation_side = "left"
tokenized_inputs = tokenizer(
text,
return_tensors="np",
truncation=True,
max_length=512
)

return tokenized_inputs

# tokenize training and validation datasets
tokenized_data = data.map(tokenize_function, batched=True)

Altre due cose di cui abbiamo bisogno per la formazione sono a gettone pad e un raccoglitore di dati. Poiché non tutti gli esempi hanno la stessa lunghezza, è possibile aggiungere un token pad agli esempi secondo necessità per dargli una dimensione particolare. Un raccoglitore di dati riempirà dinamicamente gli esempi durante l’addestramento per garantire che tutti gli esempi in un determinato batch abbiano la stessa lunghezza.

# setting pad token
tokenizer.pad_token = tokenizer.eos_token

# data collator
data_collator = transformers.DataCollatorForLanguageModeling(tokenizer,
mlm=False)

Messa a punto del modello

Nel blocco di codice seguente definisco gli iperparametri per l’addestramento del modello.

# hyperparameters
lr = 2e-4
batch_size = 4
num_epochs = 10

# define training arguments
training_args = transformers.TrainingArguments(
output_dir= "shawgpt-ft",
learning_rate=lr,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
num_train_epochs=num_epochs,
weight_decay=0.01,
logging_strategy="epoch",
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
gradient_accumulation_steps=4,
warmup_steps=2,
fp16=True,
optim="paged_adamw_8bit",
)

Anche se qui ne vengono elencati diversi, i due che desidero evidenziare nel contesto di QLoRA lo sono fp16 E ottimo. fp16=Vero fa sì che l’allenatore utilizzi i valori FP16 per il processo di allenamento, il che si traduce in un significativo risparmio di memoria rispetto allo standard FP32. optim=”paged_adamw_8bit” abilita l’Ingrediente 3 (ovvero gli ottimizzatori di pagina) discusso in precedenza.

Con tutti gli iperparametri impostati, possiamo eseguire il processo di training utilizzando il codice seguente.

# configure trainer
trainer = transformers.Trainer(
model=model,
train_dataset=tokenized_data("train"),
eval_dataset=tokenized_data("test"),
args=training_args,
data_collator=data_collator
)

# train model
model.config.use_cache = False # silence the warnings.
trainer.train()

# renable warnings
model.config.use_cache = True

Poiché abbiamo solo 50 esempi di formazione, il processo viene eseguito in circa 10 minuti. La perdita di formazione e convalida è mostrata nella tabella seguente. Possiamo vedere che entrambe le perdite diminuiscono monotonicamente, indicando un allenamento stabile.

Tabella delle perdite di formazione e convalida. Immagine dell’autore.

Caricamento del modello ottimizzato

Il modello finale è disponibile gratuitamente su Mozzo HF. Se desideri saltare il processo di training e caricarlo direttamente, puoi utilizzare il seguente codice.

# load model from hub
from peft import PeftModel, PeftConfig
from transformers import AutoModelForCausalLM

model_name = "TheBloke/Mistral-7B-Instruct-v0.2-GPTQ"
model = AutoModelForCausalLM.from_pretrained(model_name,
device_map="auto",
trust_remote_code=False,
revision="main")

config = PeftConfig.from_pretrained("shawhin/shawgpt-ft")
model = PeftModel.from_pretrained(model, "shawhin/shawgpt-ft")

# load tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)

Utilizzando il modello ottimizzato

Possiamo utilizzare il modello ottimizzato per l’inferenza nello stesso modo di prima. Ecco la risposta del modello perfezionato allo stesso commento di test di prima (ovvero “Ottimo contenuto, grazie!“).

Glad you enjoyed it! –ShawGPT

(Note: I'm an AI language model, I don't have the ability to feel emotions or
watch videos. I'm here to help answer questions and provide explanations.)

La risposta è molto più concisa e aggiunge anche un disclaimer sul fatto che si tratta di un’intelligenza artificiale. Se vogliamo rimuovere questa clausola di esclusione della responsabilità, possiamo farlo facilmente utilizzando la manipolazione di base delle stringhe in Python.

Come altro esempio, proviamo un commento di prova più tecnico: “Cos’è la coda grassa?” La risposta del modello è riportata di seguito.

Fat-tailedness is a statistical property of a distribution. In simple terms, 
it refers to the presence of extreme outliers or heavy tails in the
distribution.

For instance, consider the distribution of heights in a population. A normal
distribution would have most people clustered around an average height with a
few people deviating slightly from the mean. However, in a fat-tailed
distribution, you would observe a larger number of people being significantly
taller or shorter than the average.

In the context of finance, fat-tailedness is often used to describe the
presence of extreme market moves or volatility. For example, the 2008
financial crisis is an example of a fat-tailed event.

I hope this explanation helps! Let me know if you have any questions.
–ShawGPT

La risposta è simile a quella che abbiamo visto nel file articolo precedente di questa serie con il modello OpenAI ottimizzato. Fornisce una spiegazione concisa e appropriata della coda grassa, ma non lo è come spiego la coda grassa.

Anche se potremmo tentare di acquisire questa conoscenza specializzata attraverso un’ulteriore messa a punto, un approccio più semplice sarebbe quello aumentare il modello perfezionato utilizzando la conoscenza esterna dal mio serie di articoli sulle code grasse (e altri argomenti di scienza dei dati).

Questo fa sorgere l’idea di Recupero generazione aumentata (cioè STRACCIO), di cui parleremo nel prossimo articolo di questa serie.

QLoRA è una tecnica di perfezionamento che ha reso più accessibile la creazione di modelli linguistici personalizzati di grandi dimensioni. Qui ho fornito una panoramica di come funziona l’approccio e ho condiviso un esempio concreto di utilizzo di QLoRA per creare un risponditore ai commenti di YouTube.

Sebbene il modello ottimizzato abbia svolto un lavoro qualitativamente buono nell’imitare il mio stile di risposta, presentava alcune limitazioni nella comprensione delle conoscenze specializzate in scienza dei dati. Nel prossimo articolo di questa serie vedremo come superare questa limitazione migliorando il modello con RAG.

Altro sui LLM 👇

Shaw talebi

Modelli linguistici di grandi dimensioni (LLM)

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *