In diesem Artikel wird gezeigt, wie Sie den neuen Lama-2 mithilfe von PEFT, QLoRa und Huggingface optimieren, um Ihren eigenen Codegenerator zu generieren. Daher konzentriert sich dieser Artikel darauf, wie Sie Ihr eigenes llama2 für ein schnelles Training zur Erledigung bestimmter Aufgaben anpassen können.
einige Wissenspunkte
Im Vergleich zur vorherigen Generation hat llama2 die Anzahl der Token um 40 % auf 2T erhöht, die Kontextlänge verdoppelt und die Group Query Attention (GQA)-Technologie angewendet, um die Inferenz auf dem schwereren 70B-Modell zu beschleunigen. Auf der Standardtransformatorarchitektur erreicht die Kontextlänge unter Verwendung der RMSNorm-Normalisierung, der SwiGLU-Aktivierung und der Einbettung gedrehter Positionen 4096 und wendet den Adam-Optimierer mit Cosinus-Lernratenplanung, Gewichtsabfall 0,1 und Gradientenbeschneidung an.
Die überwachte Feinabstimmungsphase (Supervised Fine-Tuning, SFT) zeichnet sich dadurch aus, dass Qualitätsproben Vorrang vor Quantität geben, da viele Berichte zeigen, dass die Verwendung hochwertiger Daten die Leistung des endgültigen Modells verbessern kann.
Abschließend wird das Modell durch einen RLHF-Schritt (Reinforcement Learning with Human Feedback) an die Benutzerpräferenzen angepasst. Eine große Sammlung von Beispielen, in denen Menschen in Vergleichen ihre bevorzugten Modellergebnisse auswählen. Diese Daten werden zum Trainieren des Belohnungsmodells verwendet.
Der Hauptpunkt ist, dass LLaMA 2-CHAT bereits so gut ist wie OpenAI ChatGPT, sodass wir es als unseren lokalen Ersatz verwenden können
Datensatz
Für den Feinabstimmungsprozess verwenden wir einen Datensatz von etwa 18.000 Beispielen, wobei das Modell aufgefordert wird, Python-Code zu erstellen, um eine bestimmte Aufgabe zu lösen. Dies ist eine Extraktion aus dem Originaldatensatz [2], in dem nur Python-Sprachbeispiele ausgewählt wurden. Jede Zeile enthält eine Beschreibung der zu lösenden Aufgabe, ggf. ein Beispiel für die Dateneingabe für die Aufgabe, und stellt einen generierten Codeausschnitt zur Lösung der Aufgabe bereit [3].
# Load dataset from the hub
dataset = load_dataset(dataset_name, split=dataset_split)
# Show dataset size
print(f"dataset size: {len(dataset)}")
# Show an example
print(dataset[randrange(len(dataset))])
Eingabeaufforderung erstellen
Um eine Feinabstimmung der Anweisung durchzuführen, müssen wir jedes Datenbeispiel in eine Anweisung umwandeln, deren Hauptteile im Folgenden beschrieben werden:
def format_instruction(sample):
return f"""### Instruction:
Use the Task below and the Input given to write the Response, which is a programming code that can solve the following Task:
### Task:
{sample['instruction']}
### Input:
{sample['input']}
### Response:
{sample['output']}
"""
Die resultierende Ausgabe sieht folgendermaßen aus:
### Instruction:
Use the Task below and the Input given to write the Response, which is a programming code that can solve the following Task:
### Task:
Develop a Python program that prints "Hello, World!" whenever it is run.
### Input:
### Response:
#Python program to print "Hello World!"
print("Hello, World!")
Feinabstimmung des Modells
Zu Demonstrationszwecken verwenden wir die Google Colab-Umgebung. Für den ersten Testlauf reicht die T4-Instanz aus, aber wenn es darum geht, das gesamte Datensatztraining auszuführen, müssen Sie A100 verwenden.
Darüber hinaus können Sie sich auch beim Huggingface-Hub anmelden, um Modelle hochzuladen und zu teilen. Dies ist natürlich optional.
from huggingface_hub import login
from dotenv import load_dotenv
import os
# Load the enviroment variables
load_dotenv()
# Login to the Hugging Face Hub
login(token=os.getenv("HF_HUB_TOKEN"))
PEFT, Lora und QLora
Zu den üblichen Schritten zum Trainieren eines LLM gehören: zunächst das Vortraining eines Basismodells auf Milliarden oder Billionen Token und dann die Feinabstimmung dieses Modells, um es auf nachgelagerte Aufgaben zu spezialisieren.
Parameter Efficient Fine-Tuning (PEFT) ermöglicht es uns, den RAM- und Speicherbedarf durch die Feinabstimmung einer kleinen Anzahl zusätzlicher Parameter erheblich zu reduzieren, da alle Modellparameter eingefroren bleiben. Und PEFT verbessert auch die Wiederverwendbarkeit und Portabilität des Modells, es ist einfach, dem Basismodell kleine Prüfpunkte hinzuzufügen, und das Basismodell kann durch Hinzufügen von PEFT-Parametern in mehreren Szenarien wiederverwendet werden. Da das Grundmodell schließlich nicht angepasst wird, kann das gesamte in der Vortrainingsphase erworbene Wissen beibehalten werden, wodurch ein katastrophales Vergessen vermieden wird.
PEFT behält das vorab trainierte Basismodell bei und fügt darüber neue Ebenen oder Parameter hinzu. Diese Schichten werden „Adapter“ genannt. Wir fügen diese Schichten dem vorab trainierten Basismodell hinzu und trainieren nur die Parameter dieser neuen Schichten. Ein ernstes Problem bei diesem Ansatz besteht jedoch darin, dass diese Schichten eine erhöhte Latenz in der Inferenzphase verursachen, was den Prozess in vielen Fällen ineffizient macht.
Bei der LoRa-Technologie (Low Rank Adaptation of Large Language Models) hingegen fügen Sie, anstatt neue Schichten hinzuzufügen, Werte zu den Parametern jeder Schicht des Modells hinzu, und zwar auf eine Weise, die dieses gefürchtete Latenzproblem während der Inferenzphase vermeidet. LoRa trainiert und speichert Änderungen in zusätzlichen Gewichten, während alle Gewichte des vorab trainierten Modells eingefroren werden. Das heißt, wir verwenden die Änderungen der vorab trainierten Modellmatrix, um eine neue Gewichtsmatrix zu trainieren, und zerlegen diese neue Matrix wie folgt in zwei Matrizen mit niedrigem Rang:
Die Autoren von LoRA [1] schlugen vor, dass die Änderung der Gewichtsänderungsmatrix ∆W in zwei Matrizen mit niedrigem Rang A und B zerlegt werden kann. Anstatt die Parameter in ∆W direkt zu trainieren, trainiert LoRA direkt die Parameter in A und b, sodass die Anzahl der trainierbaren Parameter viel kleiner ist. Angenommen, die Dimension von A beträgt 100 * 1 und die Dimension von B beträgt 1 * 100, dann beträgt die Anzahl der Parameter in ∆W 100 * 100 = 10000. Die Anzahl der in A und B geschulten Personen beträgt nur 100 + 100 = 200, während die Anzahl der in ∆W geschulten Personen 10000 beträgt
Die Größe dieser Matrizen mit niedrigem Rang wird durch den Parameter r definiert. Je kleiner dieser Wert ist, desto weniger Parameter müssen trainiert werden und desto höher ist die Geschwindigkeit. Zu wenige Parameter können jedoch zu Informations- und Leistungseinbußen führen, daher ist auch die Auswahl des r-Parameters ein Thema, das berücksichtigt werden muss.
Schließlich wendet QLoRa[6] die Quantisierung auf die LoRa-Methode an, um durch Optimierung der Speichernutzung ein „leichteres“ und kostengünstigere Training zu erreichen.
Optimieren Sie den Prozess
In unserem Beispiel verwenden wir QLoRa. Geben Sie also die BitsAndBytes-Konfiguration an, laden Sie das 4-Bit-quantisierte vorab trainierte Modell herunter und definieren Sie LoraConfig.
# Get the type
compute_dtype = getattr(torch, bnb_4bit_compute_dtype)
# BitsAndBytesConfig int-4 config
bnb_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
)
# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained(model_id,
quantization_config=bnb_config, use_cache = False, device_map=device_map)
model.config.pretraining_tp = 1
# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
Nachfolgend finden Sie die Parameterdefinition:
# Activate 4-bit precision base model loading
use_4bit = True
# Compute dtype for 4-bit base models
bnb_4bit_compute_dtype = "float16"
# Quantization type (fp4 or nf4)
bnb_4bit_quant_type = "nf4"
# Activate nested quantization for 4-bit base models (double quantization)
use_double_nested_quant = False
# LoRA attention dimension
lora_r = 64
# Alpha parameter for LoRA scaling
lora_alpha = 16
# Dropout probability for LoRA layers
lora_dropout = 0.1
Die nächsten Schritte sollten allen Hugging Face-Benutzern bekannt sein: das Festlegen von Trainingsparametern und das Erstellen eines Trainers. Bei der Feinabstimmung von Anweisungen rufen wir die SFTTrainer-Methode auf, die die PEFT-Modelldefinition und andere Schritte kapselt.
# Define the training arguments
args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=num_train_epochs,
per_device_train_batch_size=per_device_train_batch_size, # 6 if use_flash_attention else 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
)
# Create the trainer
trainer = 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,
)
# train the model
trainer.train() # there will not be a progress bar since tqdm is disabled
# save model in local
trainer.save_model()
Die meisten dieser Parameter werden normalerweise in anderen Feinabstimmungsskripten auf llm verwendet, daher werden wir nicht zu viel erklären:
# Number of training epochs
num_train_epochs = 1
# Enable fp16/bf16 training (set bf16 to True with an A100)
fp16 = False
bf16 = True
# Batch size per GPU for training
per_device_train_batch_size = 4
# Number of update steps to accumulate the gradients for
gradient_accumulation_steps = 1
# Enable gradient checkpointing
gradient_checkpointing = True
# Maximum gradient normal (gradient clipping)
max_grad_norm = 0.3
# Initial learning rate (AdamW optimizer)
learning_rate = 2e-4
# Weight decay to apply to all layers except bias/LayerNorm weights
weight_decay = 0.001
# Optimizer to use
optim = "paged_adamw_32bit"
# Learning rate schedule
lr_scheduler_type = "cosine" #"constant"
# Ratio of steps for a linear warmup (from 0 to learning rate)
warmup_ratio = 0.03
# Group sequences into batches with same length
# Saves memory and speeds up training considerably
group_by_length = False
# Save checkpoint every X updates steps
save_steps = 0
# Log every X updates steps
logging_steps = 25
# Disable tqdm
disable_tqdm= True
Gewicht zusammenführen
Wie oben erwähnt, trainiert LoRa „modifizierte Gewichte“ auf dem Basismodell, sodass das endgültige Modell das vorab trainierte Modell und die Adaptergewichte in einem Modell kombinieren muss.
from peft import AutoPeftModelForCausalLM
model = AutoPeftModelForCausalLM.from_pretrained(
args.output_dir,
low_cpu_mem_usage=True,
return_dict=True,
torch_dtype=torch.float16,
device_map=device_map,
)
# Merge LoRA and base model
merged_model = model.merge_and_unload()
# Save the merged model
merged_model.save_pretrained("merged_model",safe_serialization=True)
tokenizer.save_pretrained("merged_model")
# push merged model to the hub
merged_model.push_to_hub(hf_model_repo)
tokenizer.push_to_hub(hf_model_repo)
Argumentation
Am Ende ist es der Prozess des Denkens.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# Get the tokenizer
tokenizer = AutoTokenizer.from_pretrained(hf_model_repo)
# Load the model
model = AutoModelForCausalLM.from_pretrained(hf_model_repo, load_in_4bit=True,
torch_dtype=torch.float16,
device_map=device_map)
# Create an instruction
instruction="Optimize a code snippet written in Python. The code snippet should create a list of numbers from 0 to 10 that are divisible by 2."
input=""
prompt = f"""### Instruction:
Use the Task below and the Input given to write the Response, which is a programming code that can solve the Task.
### Task:
{instruction}
### Input:
{input}
### Response:
"""
# Tokenize the input
input_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()
# Run the model to infere an output
outputs = model.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)
# Print the result
print(f"Prompt:\n{prompt}\n")
print(f"Generated instruction:\n{tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")
Das Ergebnis ist wie folgt:
Prompt:
### Instruction:
Use the Task below and the Input given to write the Response, which is a programming code that can solve the Task.
### Task:
Optimize a code snippet written in Python. The code snippet should create a list of numbers from 0 to 10 that are divisible by 2.
### Input:
arr = []
for i in range(10):
if i % 2 == 0:
arr.append(i)
### Response:
Generated instruction:
arr = [i for i in range(10) if i % 2 == 0]
Ground truth:
arr = [i for i in range(11) if i % 2 == 0]
Es sieht immer noch gut aus
Zusammenfassen
Das Obige ist der vollständige Prozess der Feinabstimmung von llama2. Einer der wichtigsten Schritte hier ist tatsächlich die Generierung von Hinweisen. Ein guter Hinweis ist auch für die Leistung des Modells sehr hilfreich.
[1] Lama-2-Papier https://arxiv.org/pdf/2307.09288.pdf
[2] Python-Code-Datensatz http://sahil2801/code_instructions_120k
[3] Der in diesem Artikel verwendete Datensatz https://huggingface.co/datasets/iamtarun/python_code_instructions_18k_alpaca
[4] LoRA: Low-Rank-Anpassung großer Sprachmodelle. arXiv:2106.09685
[5]. QLoRa: Effiziente Feinabstimmung quantisierter LLMs arXiv:2305.14314
https://avoid.overfit.cn/post/9794c9eef1df4e55adf514b3d727ee3b
Autor: Eduardo Muñoz