Vai al contenuto principale

Guida a Unsloth: ottimizza e accelera il fine-tuning degli LLM

Fine-tuning del modello Llama 3.1 per risolvere problemi di algebra specializzati con alta accuratezza e risultati dettagliati usando Unsloth.
· 11 min leggi

Scopri con l'IA

Ti è mai capitato di incontrare problemi di memoria durante il fine-tuning dei modelli o di trovarti di fronte a un processo che impiega un tempo interminabile per concludersi? Se stai cercando una soluzione più efficiente e veloce rispetto all’uso della libreria Transformers per il fine-tuning dei large language model (LLM), sei nel posto giusto!

In questo tutorial, vedremo come fare fine-tuning del modello Llama 3.1 3B utilizzando solo 9 GB di VRAM, ottenendo velocità 2 volte superiori rispetto ai metodi tradizionali basati su Transformers. Tratteremo anche come eseguire un’inferenza rapida, convertire il modello nei formati compatibili con vLLM e GGUF e caricare senza problemi il modello salvato su Hugging Face Hub con poche righe di codice.

Se questi concetti ti sono nuovi, assicurati di seguire il corso Master Large Language Models (LLMs) Concepts per costruire solide basi prima di passare al fine-tuning.

Fast Fine-tuning LLMs with Unsloth Feature Image

Immagine dell’autore

Che cos’è Unsloth?

Unsloth AI è un framework Python progettato per il fine-tuning veloce e l’accesso a large language model. Offre una semplice API e prestazioni 2 volte più rapide rispetto a Transformers. 

Funzionalità principali

  • API semplice: una delle caratteristiche distintive di Unsloth è la sua API semplice, che riduce notevolmente la complessità del fine-tuning degli LLM.
  • Integrazione con Transformers: Unsloth è costruito sopra la libreria Transformers, una scelta popolare per lavorare con gli LLM. Questa integrazione consente a Unsloth di sfruttare le funzionalità dell’ecosistema Hugging Face aggiungendo al contempo miglioramenti propri per semplificare il fine-tuning.
  • Richiede risorse limitate: puoi fare fine-tuning dei large language model usando le GPU gratuite di Colab o anche un laptop con GPU.
  • Salvataggio in formati vLLM e GGUF: con una sola riga di codice puoi unire, convertire e pubblicare il modello quantizzato su Hugging Face Hub. 
  • Uso efficiente della memoria: puoi caricare un modello quantizzato a 4 bit, fare fine-tuning e fonderlo con il modello completo senza superare i 12 GB di VRAM GPU.

Accedere agli LLM con Unsloth

Accedere al modello Llama-3.1 con Unsloth è piuttosto semplice. Non caricheremo il modello a 16 bit. Invece, useremo la versione a 4 bit disponibile su Hugging Face per risparmiare memoria GPU e velocizzare l’inferenza.

  1. Installa il pacchetto Python Unsloth.
%%capture
%pip install unsloth
  1. Carica il modello e il tokenizer usando la funzione FastLanguageModel. 
from transformers import TextStreamer
from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Meta-Llama-3.1-8B-bnb-4bit"
)

Loading the unsloth/Meta-Llama-3.1-8B-bnb-4bit

  1. Abilita l’inferenza veloce di Unsloth.
FastLanguageModel.for_inference(model)
  1. Definisci lo stile del prompt usando il prompt di sistema, l’istruzione, l’input e la risposta. 
  2. Crea un prompt di esempio usando lo stile del prompt. 
  3. Converti il testo in token e poi in rappresentazioni numeriche.
  4. Configura il text streamer.
  5. Usa il modello per generare la risposta in streaming.
prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
{}

### Input:
{}

### Response:
{}"""


inputs = tokenizer(
    [
        prompt_style.format(
            "You are a professional machine learning engineer",  
            "How would you deal with NaN validation loss?",  
            "", 
        )
    ],
    return_tensors="pt",
).to("cuda")

text_streamer = TextStreamer(tokenizer)
_ = model.generate(**inputs, streamer=text_streamer, max_new_tokens=128)

Abbiamo ricevuto una risposta utile su come gestire la NaN validation loss.

Output generated using unsloth/Meta-Llama-3.1-8B-bnb-4bit

Se hai difficoltà a eseguire il codice qui sopra su Kaggle, ecco il Notebook di esempio: Accessing LLMs using Unsloth.

Seguendo il corso Introduction to LLMs in Python imparerai le basi dell’architettura Transformer, inclusi costruzione, fine-tuning e valutazione. È il tuo punto d’ingresso nel mondo del fine-tuning degli LLM in Python.

Fine-tuning del modello Llama 3.1 con Unsloth

In questa guida impareremo a fare fine-tuning di Llama 3.1 sul dataset lighteval/MATH usando Unsloth. Il dataset è composto da problemi di algebra con soluzioni in formato markdown. L’intero dataset è suddiviso in cinque livelli di difficoltà.

Per comprendere meglio questa guida, ti consiglio di studiare la teoria alla base di ogni passaggio del fine-tuning degli LLM leggendo An Introductory Guide to Fine-Tuning LLMs. Dedica qualche minuto a rivedere la guida: ti aiuterà a comprendere il processo.

1. Configurazione

Stiamo usando un notebook Kaggle come IDE cloud e dobbiamo configurarlo prima di lavorare con modello o dati.

  1. Per iniziare, dobbiamo impostare le variabili d’ambiente per le API di Hugging Face e Weights & Biases utilizzando i secrets di Kaggle.

Setting up Hugging Face and W&B API key as environment variable.

  1. Imposta l’acceleratore su GPU P100.
  2. Carica la variabile d’ambiente e accedi alla CLI di Hugging Face usando la chiave API.
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(hf_token)
  1. Accedi a Weights & Biases usando la chiave API per avviare il progetto. 
import wandb

wb_token = user_secrets.get_secret("wandb")

wandb.login(key=wb_token)
run = wandb.init(
    project='Fine-tune Llama-3.1-8B-bnb-4bit on Math Dataset', 
    job_type="training", 
    anonymous="allow"
)

2. Caricamento del modello e del tokenizer

Come nella sezione sull’inferenza del modello, caricheremo la versione quantizzata a 4 bit di Llama 3.1 utilizzando una lunghezza massima di sequenza di 2048 e data type None.

from unsloth import FastLanguageModel
import torch

max_seq_length = 2048 
dtype = None 

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Meta-Llama-3.1-8B-bnb-4bit",
    max_seq_length = max_seq_length,
    dtype = dtype
)

3. Caricamento e processamento del dataset

Poi caricheremo e processeremo il dataset. Per farlo, dobbiamo prima creare uno stile di prompt che ci aiuti a ottenere i risultati desiderati. 

Lo stile del prompt include un prompt di sistema, l’istruzione e dei placeholder per input e risposta. 

prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
You are a math genius who can solve any level of algebraic problems. Please answer the following math question.

### Input:
{}

### Response:
{}"""

Dopodiché, crea la funzione che userà lo stile del prompt per inserire problemi e soluzioni di matematica, convertendoli nel testo appropriato. Assicurati di aggiungere EOS_TOKEN per evitare ripetizioni.

EOS_TOKEN = tokenizer.eos_token # Must add EOS_TOKEN
def formatting_prompts_func(examples):
    inputs       = examples["problem"]
    outputs      = examples["solution"]
    texts = []
    for input, output in zip(inputs, outputs):
        text = prompt_style.format(input, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }

Carica 500 campioni dal dataset, applica la funzione formatting_prompts_func al dataset e visualizza il primo campione dalla colonna del testo.

from datasets import load_dataset

dataset = load_dataset("lighteval/MATH", split="train[0:500]", trust_remote_code=True)
dataset = dataset.map(
    formatting_prompts_func,
    batched=True,
)
dataset["text"][0]

Il testo contiene un prompt di sistema, un’istruzione, la domanda del problema di algebra e la soluzione a quel problema. 

Math Dataset from Hugging Face

4. Configurazione del modello

Aggiungiamo la LoRA (Low-Rank Adapter) al modello utilizzando i moduli lineari del modello base. 

model = FastLanguageModel.get_peft_model(
    model,
    r=16, 
    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0, 
    bias="none", 
   
    use_gradient_checkpointing="unsloth", 
    random_state=3407,
    use_rslora=False, 
    loftq_config=None,
)

Configura il trainer con modello, tokenizer, lunghezza massima di sequenza e argomenti di training. È molto simile alla configurazione di un trainer usando le librerie Transformers e TRL.

from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text",
    max_seq_length = max_seq_length,
    dataset_num_proc = 2,
    args = TrainingArguments(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        max_steps = 60,
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        output_dir = "outputs",
    ),
)

5. Addestramento del modello

L’addestramento del modello ha richiesto circa 19 minuti, un risultato notevole. La training loss si è ridotta in modo significativo a ogni step. 

trainer_stats = trainer.train()

Model training loss

Puoi consultare metriche dettagliate sulle prestazioni del modello e sull’hardware accedendo alla dashboard di Weights & Biases.

W&B dashboard. Detailed model evaluation.

Puoi anche usare torch e trainer_stats per generare un tuo report.  

used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
used_memory_for_lora = round(used_memory - start_gpu_memory, 3)
used_percentage = round(used_memory         /max_memory*100, 3)
lora_percentage = round(used_memory_for_lora/max_memory*100, 3)
print(f"{trainer_stats.metrics['train_runtime']} seconds used for training.")
print(f"{round(trainer_stats.metrics['train_runtime']/60, 2)} minutes used for training.")
print(f"Peak reserved memory = {used_memory} GB.")
print(f"Peak reserved memory for training = {used_memory_for_lora} GB.")
print(f"Peak reserved memory % of max memory = {used_percentage} %.")
print(f"Peak reserved memory for training % of max memory = {lora_percentage} %.")

Il modello è stato addestrato in 20 minuti e il picco di memoria riservata per l’addestramento è stato di circa 3,7 GB. È relativamente basso rispetto al metodo tradizionale, che prevede il caricamento del modello completo, la quantizzazione e infine il fine-tuning. In quel caso servono almeno 15 GB di memoria GPU.

1207.5478 seconds used for training.
20.13 minutes used for training.
Peak reserved memory = 9.73 GB.
Peak reserved memory for training = 3.746 GB.
Peak reserved memory % of max memory = 61.241 %.
Peak reserved memory for training % of max memory = 23.578 %.

6. Test del modello dopo il fine-tuning

Per testare il modello dopo il fine-tuning, dobbiamo attivare l’inferenza veloce. Quindi inseriremo un esempio di domanda di algebra dal dataset nello stile del prompt e lo convertiremo in token. Poi useremo il modello per generare la risposta e decodificheremo l’output per mostrarne il testo. 

Il testo generato era in formato Markdown, ma lo abbiamo convertito per visualizzare il testo con le equazioni matematiche corrette.

from IPython.display import display, Markdown

FastLanguageModel.for_inference(model)
inputs = tokenizer(
    [
        prompt_style.format(
            "If the system of equations \begin{align*} 3x+y&=a,\\ 2x+5y&=2a, \end{align*} has a solution $(x,y)$ when $x=2$, compute $a$.", 
            "", 
        )
    ],
    return_tensors="pt",
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=250,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs)
Markdown(response[0].split("\n\n### Response:")[1])

Come si vede, la risposta generata è in linea con il dataset e ci fornisce le soluzioni ai problemi di algebra.

Output of the fine-tuned model

7. Salvataggio del modello e del tokenizer

Salviamo il nostro modello fine-tunato e pubblichiamolo su Hugging Face Hub così potrai condividerlo o distribuirlo facilmente.

new_model_online = "kingabzpro/Llama-3.1-8B-MATH"
new_model_local = "Llama-3.1-8B-MATH"
model.save_pretrained(new_model_local) # Local saving
tokenizer.save_pretrained(new_model_local) # Local saving

Il codice seguente creerà un nuovo repository su Hugging Face e poi caricherà la LoRA e il tokenizer con i metadati.

model.push_to_hub(new_model_online) # Online saving
tokenizer.push_to_hub(new_model_online) # Online saving

Fine-tuned model saved on Hugging Face

Fonte: kingabzpro/Llama-3.1-8B-MATH

Se riscontri problemi durante il fine-tuning dell’LLM con Unsloth, consulta il Fine-tuning LLMs using Unsloth Notebook su Kaggle. Contiene codice stabile che puoi eseguire per riprodurre i risultati.

Il passo successivo nel tuo percorso di AI è usare gli LLM per sviluppare applicazioni efficaci. Trovi maggiori informazioni seguendo il tutorial How to Build LLM Applications with LangChain.

Merge ed esportazione del modello

Fondere la LoRA con il modello base richiede ulteriore VRAM e spazio locale. Per non superare i limiti, creeremo un nuovo notebook Kaggle con GPU P100 come acceleratore. Attiveremo anche l’API di Hugging Face per accedere alla chiave API.

  1. Installa il pacchetto Python Unsloth.
%%capture
%pip install unsloth
  1. Carica la chiave API dai secrets di Kaggle e accedi alla CLI di Hugging Face. 
from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(hf_token)
  1. Carica la LoRA e il tokenizer fornendo il link al repository del modello su Hugging Face. 
  2. Abilita l’inferenza veloce del modello. 
from unsloth import FastLanguageModel

new_model_name = "kingabzpro/Llama-3.1-8B-MATH"
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = new_model_name, # YOUR MODEL YOU USED FOR TRAINING
    max_seq_length = 2048,
    dtype = None,
    load_in_4bit = True,
)
FastLanguageModel.for_inference(model); 

Loading the fine-tuned adopter

  1. Copia e incolla lo stile del prompt dalla sezione precedente. 
prompt_style = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
You are a math genius who can solve any level of algebraic problem. Please answer the following math question.

### Input:
{}

### Response:
{}"""
  1. Testa il modello con un problema di matematica di esempio dal dataset per verificare che funzioni correttamente.
from IPython.display import display, Markdown

inputs = tokenizer(
    [
        prompt_style.format(
            "Solve the equation $|y-6| + 2y = 9$ for $y$.",  # input
            "",  # output - leave this blank for generation!
        )
    ],
    return_tensors="pt",
).to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=250,
    pad_token_id=tokenizer.eos_token_id,
)
response = tokenizer.batch_decode(outputs)
Markdown(response[0].split("\n\n### Response:")[1])

Il modello funziona perfettamente ed è pronto per essere fuso con il modello base.

output of the fine-tuned model.

  1. Crea la directory temporanea e poi cambia puntatore di directory. Perché lo facciamo? La directory working di Kaggle ci offre solo 20 GB, non sufficienti per il merge e la pubblicazione del modello completo. Creando un’altra cartella nella root potremo accedere ai 60 GB di storage temporaneo.
%mkdir ../temp
%cd /kaggle/temp
  1. Con il seguente comando fonderemo la LoRA con il modello base e la pubblicheremo su Hugging Face Hub. È utile quando vuoi distribuire e servire il modello nel cloud usando il pacchetto Python vLLM.
model.push_to_hub_merged(new_model_name, tokenizer, save_method = "merged_16bit")

Merging and saving the 16bit model.

  1. Se vuoi usare il modello fine-tunato in locale, puoi facilmente convertirlo nel formato llama.cpp, poi quantizzarlo e pubblicarlo su Hugging Face Hub. 
model.push_to_hub_gguf(new_model_name, tokenizer, quantization_method = "q4_k_m")

saving the quantized model in GGUF model format

Il modello completo a 16 bit, l’adapter e i file GGUF quantizzati sono disponibili nel repository del tuo modello. Per usare il modello fine-tunato in locale, scarica semplicemente il file GGUF quantizzato e inizia a usarlo con Jan, Misty, GPT4ALL o Ollama.

Segui il tutorial Fine-tuning Llama 3.2 and Using It Locally: A Step-by-Step Guide per imparare a fare fine-tuning del modello Llama 3.2 su un dataset di customer support, fondere ed esportare il modello su Hugging Face Hub e convertire il modello fine-tunato in formato GGUF così da usarlo in locale con l’applicazione Jan.

Fonte: kingabzpro/Llama-3.1-8B-MATH

Se riscontri problemi durante il caricamento e il merge della LoRA con il modello base, consulta il notebook Kaggle Merging LoRA Adopter with Unsloth per ulteriore assistenza. 

Ti consiglio anche di leggere il blog 12 LLM Projects For All Levels. Include un elenco di progetti LLM per principianti, studenti intermedi, laureandi ed esperti.

Conclusioni

In questo tutorial abbiamo visto come fare fine-tuning in modo efficace del modello Llama 3.1 3B con risorse di calcolo ridotte e ottenendo velocità 2 volte superiori ai metodi tradizionali.

 Abbiamo anche imparato come eseguire un’inferenza rapida del modello, fondere la LoRA con il modello base e pubblicarlo su Hugging Face Hub. 

Inoltre, abbiamo convertito il modello nel formato llama.cpp e lo abbiamo quantizzato in modo che il modello fine-tunato possa essere usato facilmente in locale sul laptop con le applicazioni Jan o GPT4ALL.

Per migliorare le prestazioni del modello, consigliamo vivamente di fare fine-tuning sull’intero dataset, ottimizzare gli iperparametri e lavorare sullo stile del prompt. Anche dopo il fine-tuning, puoi migliorare le prestazioni delle applicazioni AI in molti modi usando function calling e pipeline RAG.

Scopri quale soluzione è migliore per i tuoi casi specifici seguendo la guida RAG vs Fine-Tuning: A Comprehensive Tutorial with Practical Example. Include un codice di esempio da provare sul tuo dataset per confrontare i risultati.

Abid Ali Awan

Autore

Abid Ali Awan

In quanto data scientist certificato, sono appassionato di sfruttare tecnologie all’avanguardia per creare applicazioni di machine learning innovative. Con una solida esperienza in riconoscimento vocale, analisi e reportistica dei dati, MLOps, AI conversazionale e NLP, ho affinato le mie competenze nello sviluppo di sistemi intelligenti in grado di avere un impatto concreto. Oltre alla mia expertise tecnica, sono anche un comunicatore efficace, con il talento di rendere chiari e sintetici concetti complessi. Di conseguenza, sono diventato un blogger molto seguito in ambito data science, condividendo idee ed esperienze con una community in crescita di professionisti dei dati. Attualmente mi concentro sulla creazione e sull’editing di contenuti, lavorando con large language model per sviluppare contenuti potenti e coinvolgenti che possano aiutare aziende e singoli a valorizzare al meglio i propri dati.

Argomenti

Correlato