Perfezionamento di un modello Llama-2 7B per la generazione di codice Python

Perfezionamento modello Llama-2 7B per generazione codice Python

Una demo su come ottimizzare il nuovo Llama-2 utilizzando PEFT, QLoRa e le utilità di Huggingface

Immagine creata dall'autore in Leonardo.ai

Circa 2 settimane fa, il mondo dell’IA generativa è stato sconvolto dal rilascio del nuovo modello Llama-2 dell’azienda Meta. Il suo predecessore, Llama-1, è stato un punto di svolta nell’industria LLM, poiché con il rilascio dei suoi pesi insieme alle nuove tecniche di fine-tuning, è stata creata una massa di modelli LLM open-source che hanno portato all’emergere di modelli ad alte prestazioni come Vicuna, Koala, …

In questo articolo, discuteremo brevemente alcuni punti rilevanti di questo modello, ma ci concentreremo nel mostrare come possiamo addestrare rapidamente il modello per un compito specifico utilizzando librerie e strumenti standard in questo mondo. Non faremo un’analisi esaustiva del nuovo modello, ci sono già numerosi articoli pubblicati sull’argomento.

Nuovo modello Llama-2

A metà luglio, Meta ha rilasciato la sua nuova famiglia di modelli pre-addestrati e fine-tunati chiamati Llama-2, con un carattere open source e commerciale per facilitarne l’uso e l’espansione. Il modello base è stato rilasciato con una versione chat e dimensioni 7B, 13B e 70B. Insieme ai modelli, sono stati pubblicati gli articoli corrispondenti che descrivono le loro caratteristiche e i punti rilevanti del processo di apprendimento, fornendo informazioni molto interessanti sull’argomento.

Una versione aggiornata di Llama 1, addestrata su una nuova combinazione di dati pubblicamente disponibili. La dimensione del corpus di pre-training è stata aumentata del 40%, la lunghezza del contesto del modello è stata raddoppiata e l’attenzione raggruppata delle query è stata adottata. Sono state rilasciate varianti con 7B, 13B e 70B di parametri, insieme a varianti da 34B riportate nell’articolo ma non rilasciate.[1]

Per il pre-training, sono stati utilizzati il 40% in più di token, raggiungendo 2T, la lunghezza del contesto è stata raddoppiata e la tecnica di attenzione raggruppata delle query (GQA) è stata applicata per velocizzare l’inferenza sul modello più pesante da 70B. Sull’architettura standard del transformer, vengono utilizzate la normalizzazione RMSNorm, l’attivazione SwiGLU e l’embedding posizionale rotatorio, la lunghezza del contesto raggiunge i 4096 token e viene applicato un ottimizzatore Adam con un programma di tassi di apprendimento coseno, una decadenza del peso del 0,1 e il clipping del gradiente.

La fase di Fine-Tuning Supervisionato (SFT) è caratterizzata da una prioritizzazione di esempi di qualità rispetto alla quantità, poiché numerosi rapporti mostrano che l’uso di dati di alta qualità porta a un miglioramento delle prestazioni finali del modello. Infine, viene applicato uno step di Reinforcement Learning con Feedback Umano (RLHF) per allineare il modello alle preferenze dell’utente. Viene raccolta una moltitudine di esempi in cui gli annotatori selezionano la loro uscita preferita del modello in confronto binario. Questi dati vengono utilizzati per addestrare un modello di ricompensa, in cui l’attenzione è rivolta all’aiuto e alla sicurezza.

In breve:

· Addestrato su 2T Token

· Uso commerciale consentito

· Modelli di chat per casi d’uso di dialogo

· Finestra di contesto predefinita di 4096 (può essere aumentata)

· Versioni dei parametri 7B, 13B e 70B

· Il modello da 70B ha adottato l’attenzione raggruppata delle query (GQA)

· I modelli di chat possono utilizzare strumenti e plugin

· LLaMA 2-CHAT è buono quanto OpenAI ChatGPT

Il dataset per il fine-tuning

Per il nostro processo di fine-tuning, prenderemo un dataset contenente circa 18.000 esempi in cui al modello viene chiesto di creare un codice Python che risolva un dato compito. Si tratta di un’estrazione del dataset originale [2], in cui vengono selezionati solo gli esempi nel linguaggio Python. Ogni riga contiene la descrizione del compito da risolvere, un esempio di input di dati per il compito, se applicabile, e il frammento di codice generato che risolve il compito viene fornito [3].

# Carica il dataset dal hubdataset = load_dataset(dataset_name, split=dataset_split)# Mostra la dimensione del datasetprint(f"dimensione del dataset: {len(dataset)}")# Mostra un esempioprint(dataset[randrange(len(dataset))])

Creazione del prompt

Per eseguire un fine-tuning dell’istruzione, dobbiamo trasformare ciascuno dei nostri esempi di dati come se fosse un’istruzione, delineando le sue sezioni principali come segue:

def format_instruction(sample): return f"""### Istruzione: Utilizzare il Task di seguito e l'Input fornito per scrivere la Risposta, che è un codice di programmazione in grado di risolvere il seguente Task: ### Task:{sample['instruction']} ### Input:{sample['input']} ### Risposta:{sample['output']}"""

Output:

### Istruzione: Utilizzare il Task di seguito e l'Input fornito per scrivere la Risposta, che è un codice di programmazione in grado di risolvere il seguente Task: ### Task: Sviluppare un programma Python che stampa "Ciao, Mondo!" ogni volta che viene eseguito. ### Input: ### Risposta: #Programma Python per stampare "Ciao, Mondo!" print("Ciao, Mondo!")
Immagine di Irvan Smith da Unsplash

Fine-tuning del modello

Per svolgere questa fase, abbiamo utilizzato l’ambiente Google Colab, dove abbiamo sviluppato un notebook che ci consente di eseguire l’addestramento in modo interattivo e uno script Python per eseguire l’addestramento in modalità non assistita. Per i primi test, un’istanza T4 con una grande capacità di RAM è sufficiente, ma quando si tratta di eseguire l’intero dataset ed epoche, abbiamo scelto di utilizzare un’istanza A100 per accelerare l’addestramento e garantire che il tempo di esecuzione sia ragionevole.

Per poter condividere il modello, effettueremo l’accesso all’Huggingface hub utilizzando il token appropriato, in modo che alla fine dell’intero processo caricheremo i file del modello in modo che possano essere condivisi con gli altri utenti.

from huggingface_hub import loginfrom dotenv import load_dotenvimport os# Carica le variabili di ambiente.load_dotenv()# Accedi all'Hugging Face Hub.login(token=os.getenv("HF_HUB_TOKEN"))

Tecniche di fine-tuning: PEFT, Lora e QLora

Negli ultimi mesi sono stati pubblicati alcuni articoli che mostrano come le tecniche di PEFT possano essere utilizzate per addestrare modelli di linguaggio di grandi dimensioni con una riduzione drastica dei requisiti di RAM e di conseguenza consentendo il fine-tuning di questi modelli su una singola GPU di dimensioni ragionevoli. I passaggi usuali per addestrare un LLM consistono, prima, in un pre-training intensivo su miliardi o trilioni di token per ottenere un modello di base e poi viene eseguito un fine-tuning su questo modello per specializzarlo su un compito secondario. In questa fase di fine-tuning è dove la tecnica PEFT ha il suo scopo.

Parameter Efficient Fine-Tuning (PEFT) ci consente di ridurre notevolmente i requisiti di RAM e di archiviazione, effettuando il fine-tuning solo di un piccolo numero di parametri aggiuntivi, con praticamente tutti i parametri del modello rimanenti congelati. PEFT è stato trovato per produrre una buona generalizzazione con dataset di volume relativamente basso. Inoltre, migliora la riutilizzabilità e la portabilità del modello, poiché i piccoli checkpoint ottenuti possono essere facilmente aggiunti al modello di base e il modello di base può essere facilmente fine-tuned e riutilizzato in scenari multipli aggiungendo i parametri PEFT. Infine, poiché il modello di base non viene modificato, tutte le conoscenze acquisite nella fase di pre-training vengono preservate, evitando così l’oblio catastrofico.

Le tecniche PEFT più ampiamente utilizzate mirano a mantenere intatto il modello di base pre-addestrato e ad aggiungere nuovi layer o parametri in cima ad esso. Questi layer vengono chiamati “Adapters” e la tecnica del loro adattamento è chiamata “adapter-tuning“, aggiungiamo questi layer al modello di base pre-addestrato e addestriamo solo i parametri di questi nuovi layer. Tuttavia, un problema serio di questo approccio è che questi layer portano a un aumento della latenza nella fase di inferenza, rendendo il processo inefficiente in molti scenari. Nella tecnica LoRa, Low-Rank Adaptation of Large Language Models, l’idea non è quella di includere nuovi layer, ma di aggiungere valori ai parametri in modo da evitare questo problema di latenza nella fase di inferenza. LoRa addestra e memorizza le modifiche dei pesi aggiuntivi mentre congela tutti i pesi del modello pre-addestrato. Pertanto, addestriamo una nuova matrice dei pesi con le modifiche nella matrice del modello pre-addestrato e questa nuova matrice viene decomposta in 2 matrici a basso rango come spiegato qui:

Facciamo sì che tutti i parametri di un LLM siano nella matrice W0 e le modifiche di peso aggiuntive nella matrice ∆W, i pesi finali diventano W0 + ∆W. Gli autori di LoRA [1] hanno proposto che il cambiamento nella matrice di modifiche di peso ∆W possa essere decomposto in due matrici a basso rango A e B. LoRA non addestra direttamente i parametri in ∆W, ma i parametri in A e B. Quindi il numero di parametri addestrabili è molto inferiore. Supponiamo ipoteticamente che la dimensione di A sia 100 * 1 e quella di B sia 1 * 100, il numero di parametri in ∆W sarà 100 * 100 = 10000. Ci sono solo 100 + 100 = 200 parametri da addestrare in A e B, invece di 10000 da addestrare in ∆W

[4]. Spiegazione del Dr. Dataman in Fine-tuning di un GPT – LoRA

La dimensione di queste matrici a basso rango è definita dal parametro r. Più piccolo è questo valore, meno parametri da addestrare, quindi meno sforzo e più velocità, ma d’altra parte, una potenziale perdita di informazioni e prestazioni. Se desideri una spiegazione più dettagliata, puoi fare riferimento all’articolo originale o ci sono molti articoli che lo spiegano in dettaglio, come [4].

Infine, QLoRa [6] consiste nell’applicare la quantizzazione al metodo LoRa consentendo la quantizzazione normale a 4 bit, nf4, un tipo ottimizzato per pesi normalmente distribuiti; la doppia quantizzazione per ridurre l’impronta di memoria e l’ottimizzazione della memoria unificata NVIDIA. Queste sono tecniche per ottimizzare l’utilizzo della memoria al fine di ottenere un addestramento “più leggero” e meno costoso.

Immagine dell'autore da Leonardo.ai

Implementare QLoRa nel nostro esperimento richiede di specificare la configurazione BitsAndBytes, scaricare il modello preaddestrato con quantizzazione a 4 bit e definire una LoraConfig. Infine, dobbiamo recuperare il tokenizer.

# Ottenere il tipo di calcolo del modello base a 4 bitcompute_dtype = getattr(torch, bnb_4bit_compute_dtype)# Configurazione BitsAndBytesConfig a 4 bitbnb_config = BitsAndBytesConfig(    load_in_4bit=use_4bit,    bnb_4bit_use_double_quant=use_double_nested_quant,    bnb_4bit_quant_type=bnb_4bit_quant_type,    bnb_4bit_compute_dtype=compute_dtype)# Caricare il modello e il tokenizermodel = AutoModelForCausalLM.from_pretrained(model_id,   quantization_config=bnb_config, use_cache = False, device_map=device_map)model.config.pretraining_tp = 1# Caricare il tokenizertokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)tokenizer.pad_token = tokenizer.eos_tokentokenizer.padding_side = "right"

Parametri definiti,

# Attivare il modello con precisione a 4 bituse_4bit = True# Tipo di calcolo per i modelli di base a 4 bitbnb_4bit_compute_dtype = "float16"# Tipo di quantizzazione (fp4 o nf4)bnb_4bit_quant_type = "nf4"# Attivare la quantizzazione nidificata per i modelli di base a 4 bit (doppia quantizzazione)use_double_nested_quant = False# Dimensione dell'attenzione LoRAlora_r = 64# Parametro Alpha per la ridimensionamento LoRAlora_alpha = 16# Probabilità di dropout per i livelli LoRAlora_dropout = 0.1

E i passaggi successivi sono ben noti a tutti gli utenti di Hugging Face, impostando gli argomenti di addestramento e creando un Trainer. Poiché stiamo eseguendo un fine-tuning di un’istruzione, chiamiamo il metodo SFTTrainer che incapsula la definizione del modello PEFT e altri passaggi.

# Definire gli argomenti di addestramentoargs = TrainingArguments(    output_dir=output_dir,    num_train_epochs=num_train_epochs,    per_device_train_batch_size=per_device_train_batch_size, # 6 se use_flash_attention altrimenti 4,    gradient_accumulation_steps=gradient_accumulation_steps,    gradient_checkpointing=gradient_checkpointing,    optim=optim,    logging_steps=logging_steps,    save_strategy="epoch",    learning_rate=learning_rate,    weight_decay=weight_decay,    fp16=fp16,    bf16=bf16,    max_grad_norm=max_grad_norm,    warmup_ratio=warmup_ratio,    group_by_length=group_by_length,    lr_scheduler_type=lr_scheduler_type,    disable_tqdm=disable_tqdm,    report_to="tensorboard",    seed=42)# Creare il trainiertrainer = SFTTrainer(    model=model,    train_dataset=dataset,    peft_config=peft_config,    max_seq_length=max_seq_length,    tokenizer=tokenizer,    packing=packing,    formatting_func=format_instruction,    args=args,)# Addestrare il modellotrainer.train() # non ci sarà una barra di avanzamento poiché tqdm è disabilitato# Salvare il modello localmentetrainer.save_model()

I parametri possono essere trovati nel mio repository su GitHub, la maggior parte di essi sono comunemente utilizzati in altri script di fine-tuning su LLMs e sono i seguenti:

# Numero di epoche di addestramentonum_train_epochs = 1# Abilita addestramento fp16/bf16 (imposta bf16 su True con un A100)fp16 = Falsebf16 = True# Dimensione del batch per GPU per l'addestramentoper_device_train_batch_size = 4# Numero di passaggi di aggiornamento per accumulare i gradienti pergradient_accumulation_steps = 1# Abilita il checkpointing del gradientegradient_checkpointing = True# Massimo normale del gradiente (gradient clipping)max_grad_norm = 0.3# Learning rate iniziale (ottimizzatore AdamW)learning_rate = 2e-4# Decay del peso da applicare a tutti gli strati tranne i pesi dei bias/LayerNormweight_decay = 0.001# Ottimizzatore da usareoptim = "paged_adamw_32bit"# Programma del learning rate da usarel_scheduler_type = "cosine" #"constant"# Rapporto di passaggi per un warmup lineare (da 0 al learning rate)warmup_ratio = 0.03# Raggruppa le sequenze in batch con la stessa lunghezza# Risparmia memoria e accelera notevolmente l'addestramentogroup_by_length = False# Salva il checkpoint ogni X passi di aggiornamentisave_steps = 0# Registra ogni X passi di aggiornamentilogging_steps = 25# Disabilita tqdmdisable_tqdm= True

Unisci il modello base e i pesi dell’adattatore

Come abbiamo già menzionato, abbiamo addestrato “pesi di modifica” sul modello di base, il nostro modello finale richiede di unire il modello preaddestrato e gli adattatori in un singolo modello.

from peft import AutoPeftModelForCausalLMmodel = AutoPeftModelForCausalLM.from_pretrained(    args.output_dir,    low_cpu_mem_usage=True,    return_dict=True,    torch_dtype=torch.float16,    device_map=device_map,    )# Unisci LoRA e il modello basemerged_model = model.merge_and_unload()# Salva il modello unitomerged_model.save_pretrained("merged_model",safe_serialization=True)tokenizer.save_pretrained("merged_model")# push merged model to the hubmerged_model.push_to_hub(hf_model_repo)tokenizer.push_to_hub(hf_model_repo)

Puoi trovare e scaricare il modello nel mio account di Hugging Face edumunozsala/llama-2–7b-int4-python-code-20k. Provalo!

Inferenza o generazione di codice Python

E infine, ti mostreremo come puoi scaricare il modello dall’Hugging Face Hub e chiamare il modello per generare un risultato accurato:

import torchfrom transformers import AutoModelForCausalLM, AutoTokenizer# Ottieni il tokenizertokenizer = AutoTokenizer.from_pretrained(hf_model_repo)# Carica il modellomodel = AutoModelForCausalLM.from_pretrained(hf_model_repo, load_in_4bit=True,                                              torch_dtype=torch.float16,                                             device_map=device_map)# Crea un'istruzioneristruzione="Ottimizza un frammento di codice scritto in Python. Il frammento di codice dovrebbe creare una lista di numeri da 0 a 10 che sono divisibili per 2."input=""prompt = f"""### Istruzione:Utilizza il Compito sottostante e l'Input fornito per scrivere la Risposta, che è un codice di programmazione che può risolvere il Compito.### Compito:{istruzione}### Input:{input}### Risposta:"""# Tokenizza l'inputinput_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()# Esegui il modello per inferire un outputoutputs = model.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)# Stampa il risultatostampa(f"Istruzione:\n{prompt}\n")stampa(f"Istruzione generata:\n{tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")

Istruzione:### Istruzione:Utilizza il Compito sottostante e l'Input fornito per scrivere la Risposta, che è un codice di programmazione che può risolvere il Compito.### Compito:Ottimizza un frammento di codice scritto in Python. Il frammento di codice dovrebbe creare una lista di numeri da 0 a 10 che sono divisibili per 2.### Input:arr = []for i in range(10): if i % 2 == 0: arr.append(i)### Risposta:Istruzione generata:arr = [i for i in range(10) if i % 2 == 0]Verità fondamentale:arr = [i for i in range(11) if i % 2 == 0]

Grazie a Maxime Labonne per un eccellente articolo [9] e a Philipp Schmid che fornisce un codice stimolante [8]. I loro articoli sono un must-read per tutti coloro interessati a Llama 2 e al fine-tuning del modello.

E questo è tutto ciò che ho da menzionare, spero che tu trovi utile questo articolo e i plausi sono benvenuti! Puoi seguirmi e iscriverti ai miei articoli, o persino connetterti con me tramite Linkedin. Il codice è disponibile nel mio Repository Github.

Riferimenti

[1] Llama-2 paper

[2] Link all’insieme di dati originale nell’Huggingface hub

[3] Link all’insieme di dati utilizzato nell’Huggingface hub

[4] Ottimizzazione di un GPT – LoRA di Chris Kuo/Dr. Dataman

[5] Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, & Weizhu Chen. (2021). LoRA: Adattamento a basso rango di modelli di linguaggio di grandi dimensioni. arXiv:2106.09685

[6]. QLoRa: Ottimizzazione efficiente di modelli di linguaggio quantizzati

[7] Il finetuning efficiente dei parametri con pochi esempi è migliore e più economico dell’apprendimento in contesto

[8] Guida estesa: Istruzioni per l’adattamento di Llama 2 di Philipp Schmid.

[9] Personalizza il tuo modello Llama 2 in un notebook Colab di Maxime Labonne

[10]. Il mio Repository Github