Falcon 7B使用LoRA在GPU上进行微调

2024年07月09日 由 alex 发表 51 0

对 Falcon 7B 等大型语言模型进行微调是一项资源密集型的复杂任务。低阶适应(Low-Rank Adaptation,LoRA)和参数高效微调(Parameter-Efficient Fine-Tuning,PEFT)等技术提供了高效的处理方法。LoRA 通过引入低阶矩阵,减少了可训练参数的数量,从而降低了微调过程的计算要求。而 PEFT 则侧重于选择性地调整一小部分模型参数,从而保留了大部分预训练模型的知识,同时还能使其适应特定任务。LoRA 和 PEFT 共同为 Falcon 7B 的微调提供了一种有效而经济的方法,无需大量计算资源就能为特定应用定制大型模型。


设置环境

请按照相应 GitHub repo 中的详细说明进行操作。


数据准备

  • 数据集:下载数据集 SQuAD-explorer,并将 json 文件放到 ./data 目录中。在此 repo/ 文章中,你将找到默认的目录/文件 data/train-v2.0.json。
  • 下面的代码描述了数据加载和预处理的过程。


"""
load_data.py
"""
import json
import pandas as pd

with open('data/train-v2.0.json', 'r') as file:
    squad_data = json.load(file)
questions = []
answers = []
total_questions = sum(len(paragraph['qas']) for article in squad_data['data'] for paragraph in article['paragraphs'])
percent = 0.0001 # Change this to modify the dataset size
target_questions = int(percent * total_questions)
collected_questions = 0
for article in squad_data['data']:
    for paragraph in article['paragraphs']:
        context = paragraph['context']
        for qa in paragraph['qas']:
            if collected_questions >= target_questions:
                break
            question = qa['question']
            if 'answers' in qa and len(qa['answers']) > 0:
                answer = qa['answers'][0]['text']
                questions.append(question)
                answers.append(answer)
                collected_questions += 1
        if collected_questions >= target_questions:
            break
    if collected_questions >= target_questions:
        break
df_squad = pd.DataFrame({
    'question': questions,
    'answer': answers
})


"""
data_preperation.py
"""
def gen_prompt(text_input):
    return f"""
    <human>: {text_input["question"]}
    <assistant>: {text_input["answer"]}
    """.strip()
def gen_and_tok_prompt(text_input, tokenizer):
    full_input = gen_prompt(text_input)
    tok_full_prompt = tokenizer(full_input, padding=True, truncation=True, return_tensors="pt")
    return {
        'input_ids': tok_full_prompt['input_ids'][0],
        'attention_mask': tok_full_prompt['attention_mask'][0]
    }
def tokenize_and_check_length(batch, tokenizer):
    tokenized_batch = [gen_and_tok_prompt(x, tokenizer) for x in batch]
    input_ids_lengths = [len(x['input_ids']) for x in tokenized_batch]
    attention_mask_lengths = [len(x['attention_mask']) for x in tokenized_batch]
    assert all(length == input_ids_lengths[0] for length in input_ids_lengths), "Inconsistent lengths in input_ids"
    assert all(length == attention_mask_lengths[0] for length in attention_mask_lengths), "Inconsistent lengths in attention_mask"
    input_ids = [x['input_ids'].tolist() for x in tokenized_batch]
    attention_mask = [x['attention_mask'].tolist() for x in tokenized_batch]
    return {
        'input_ids': input_ids,
        'attention_mask': attention_mask
    }
def tokenize_dataset(data, tokenizer):
    tokenizer.pad_token = tokenizer.eos_token
    return data.map(lambda x: tokenize_and_check_length([x], tokenizer), batched=True, remove_columns=["question", "answer"])


下面是上述代码的简要说明:

  • 生成提示:gen_prompt() 将输入文本格式化为结构化对话。
  • 标记化提示:gen_and_tok_prompt() 将格式化后的对话标记化,创建标记 ID 和注意力掩码。
  • 批量处理和验证:tokenize_and_check_length() 处理一批对话,确保所有标记化的提示具有一致的长度,这对高效训练模型至关重要。
  • 数据集准备:tokenize_dataset() 将标记化和验证过程应用于整个数据集,使其为模型训练做好准备。


参数高效微调(PEFT)

传统的训练方法无法处理大型语言模型(LLM)的纯 4 位量化参数。不过,通过利用最先进的参数高效微调(PEFT)方法,你可以使用特定任务适配器有效地训练这些模型。这种方法在最初的 QLoRA 论文中有详细介绍,它可以在不需要全精度参数的情况下进行高效微调。Hugging Face 提供了一个官方支持的 PEFT 库来促进这一过程。


在本项目/文章中,PEFT 使用通过 LoraConfig 对象配置的 LoRA 适配器。通过指定 r、lora_alpha 和 target_modules(如 query_key_value)等参数,可以针对特定任务(如因果语言建模(task_type="CAUSAL_LM"))对模型进行微调,甚至可以量化权重。这确保了训练的效率和效果,优化了模型在下游任务中的表现。


低等级自适应(LoRA)

LoraConfig 对象用于配置 LoRA(低秩自适应)适配器,这是一种通过重新参数化层矩阵权重来微调语言模型的技术。这可以提高模型在特定下游任务中的性能。


2


参数


from peft import LoraConfig
def prepare_model_for_training(model):
    model.gradient_checkpointing_enable()
    model = prepare_model_for_kbit_training(model)
    
    config = LoraConfig(
        r=16,
        lora_alpha=32,
        target_modules=["query_key_value"],
        lora_dropout=0.05,
        bias="none",
        task_type="CAUSAL_LM"
    )
    return get_peft_model(model, config)


  • r:更新矩阵的秩。阶数越高(如 32、128 或 256),矩阵越大,可以存储更多的层权重信息,但也需要更多的参数,使训练更具挑战性。
  • lora_alpha:LoRA 更新矩阵的缩放因子。数值越大(如 8、32 或 64),权重更新就越激进,而数值越小,更新就越保守。
  • target_modules(目标模块): target_modules 参数指定了基础模型中将应用 LoRA 更新矩阵的特定模块。这些层将被 LoRA 适配器重新参数化,以提高模型在指定任务上的性能。 query_key_value:本例中使用的 query_key_value 是转换器模型中注意力机制的一部分。它处理查询、键和值的投影,这对于计算注意力分数和生成输入的加权表示法至关重要。通过对 query_key_value 重新参数化,LoRA 可以直接影响注意力机制,从而提高学习效率和模型性能。其他例子包括["attention", "dense_final"]或["query_key_value", "dense", "dense_h_to_4h"]。
  • lora_dropout:LoRA 层的 dropout 概率。dropout 可以在训练过程中随机放弃一些权重,从而防止过拟合。可以使用 0.0、0.3 或 0.5 等值,较高的概率可以减少过拟合,较低的概率可以让模型学习到更复杂的模式。
  • bias:决定是否要训练偏置参数。选项包括 "可训练"(允许在训练过程中更新偏置参数)或 "固定"(保持偏置参数不变)。
  • task_type:指定使用 LoRA 适配器的任务类型。例如,"NLI"(自然语言推理)或 "MT"(机器翻译)。任务类型会影响 LoRA 适配器应用于基础模型的方式。在本例中,CAUSAL_LM 代表因果语言建模。它表示 LoRA 适配器是为因果语言建模任务而配置的,在该任务中,模型会根据前面的标记预测序列中的下一个标记。这种任务类型会影响模型和 LoRA 更新的应用方式,以有效执行这种顺序预测。


训练流水线

训练循环如下所示


"""
main.py
"""
import sys
import pandas as pd
from datasets import Dataset
from data_preparation import tokenize_dataset
from model_utils import load_model_and_tokenizer, prepare_model_for_training, save_model, print_trainable_parameters
from training import fine_tune_model
from load_data import df_squad
import numpy as np
import torch
from datetime import datetime
print(f"NumPy version: {np.__version__}")
print(f"CUDA is available: {torch.cuda.is_available()}")
df_faq = df_squad # Check Git Repo to see how this is obtained.
data = Dataset.from_pandas(df_faq[['question', 'answer']])
model_name = "tiiuae/falcon-7b-instruct"
model, tokenizer = load_model_and_tokenizer(model_name)
if torch.cuda.is_available():
    model.cuda()
data = tokenize_dataset(data, tokenizer)
model = prepare_model_for_training(model)
print_trainable_parameters(model)
training_args = {
    "gradient_accumulation_steps": 4,
    "num_train_epochs": 3,
    "learning_rate": 2e-4,
    "fp16": True,
    "save_total_limit": 4,
    "logging_steps": 25,
    "output_dir": "output_dir",  # give the location where you want to store checkpoints
    "save_strategy": 'epoch',
    "optim": "paged_adamw_8bit",
    "lr_scheduler_type": 'cosine',
    "warmup_ratio": 0.05,
    "per_device_train_batch_size": 1,  # Adjust based on your GPU memory
    # "device": "cuda" if torch.cuda.is_available() else "cpu"
}
fine_tune_model(model, tokenizer, data, training_args)
current_time = datetime.now().strftime('%Y%m%d_%H%M%S')
save_model(model, f'trained_models/MODEL_{current_time}')


"""
training.py
"""
from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
import torch
def fine_tune_model(model, tokenizer, data, training_args):
    training_args = TrainingArguments(**training_args)
    
    trainer = Trainer(
        model=model,
        train_dataset=data,
        args=training_args,
        data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
    )
    
    model.config.use_cache = False
    if torch.cuda.is_available():
        model.cuda()
    
    trainer.train()


上述代码描述了使用 LoRA 对猎鹰 7B 进行微调的训练流水线。该流水线利用高效的训练技术来优化 GPU 内存和资源的使用。


推理

以下代码用于推理:


"""
question_answer.py
"""
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftConfig, PeftModel
import torch
def perform_inference(model_path, prompt):
    config = PeftConfig.from_pretrained(model_path)
    model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path, trust_remote_code=True)
    tokenizer = AutoTokenizer.from_pretrained(config.base_model_name_or_path)
    model_inf = PeftModel.from_pretrained(model, model_path)
    encoding = tokenizer(prompt, return_tensors="pt").to(model.device)
    gen_config = model_inf.generation_config
    gen_config.max_new_tokens = 200
    gen_config.temperature = 0.2
    gen_config.top_p = 0.7
    gen_config.num_return_sequences = 1
    gen_config.pad_token_id = tokenizer.eos_token_id
    gen_config.eos_token_id = tokenizer.eos_token_id
    with torch.inference_mode():
        outputs = model.generate(input_ids=encoding.input_ids, attention_mask=encoding.attention_mask, generation_config=gen_config)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

while True:
    query = input("Please enter your question: ")
    response = perform_inference('trained_models/new_model', query)
    print(response)The inference pipeline is designed to generate responses from the fine-tuned Falcon LoRA. This process involves loading the model and tokenizer, preparing the prompt for generation, and generating the response based on the model’s configuration. Below are the detailed steps and components involved in the inference process.


1. 导入所需程序库:

首先要导入必要的库。这些库包括用于处理模型和标记化器的 transformers 库、用于管理 Low-Rank Adaptation 模型的 peft 库,以及用于张量运算和启用推理模式的 Torch 库。


2. 函数 perform_inference(model_path,prompt):

该函数是推理过程的核心。它接受训练模型的路径和文本提示作为输入,并返回生成的响应。


3. 加载模型配置:

该函数从指定路径加载预训练 LoRA 模型的配置。该配置包含基础模型及其训练参数的必要细节。


4. 加载模型和标记符:

使用配置中指定的路径加载因果语言建模的基础模型。同样,与基础模型相关的标记器也会被加载。标记器负责将文本转换成模型可以处理的格式。


5. 加载微调后的 LoRA 模型:

使用 LoRA 调整过的微调模型将从指定路径加载。该模型包含了微调过程中所做的调整,以更好地处理其所训练的特定任务。


6. 准备输入提示:

用户提供的输入提示会被标记化并转换成张量。然后将这些张量传输到模型的设备(通常是 GPU,如果有的话),以便进行高效处理。


7. 配置生成参数:

设置控制文本生成行为的各种参数。这些参数包括

max_new_tokens: 要生成的新标记的最大数量。

温度: 通过在应用 softmax 之前缩放对数来控制预测的随机性。

top_p: 执行核采样,即只考虑最有可能的标记。

num_return_sequences(返回序列数): 指定要返回的不同序列的数量。

pad_token_id 和 eos_token_id: 分别定义填充标记和序列末端标记。


8. 生成响应:

模型将进入推理模式以禁用梯度计算,从而减少内存使用并加快处理速度。然后将输入提示传递给模型,模型会根据设置的参数生成响应。


9. 解码并返回响应:

生成的输出(最初为张量格式)被解码为人类可读的字符串。处理过程中使用的特殊标记会被跳过,以生成干净的响应。


10. 交互式推理循环:

该模型建立了一个交互式循环,不断接收用户输入,并使用执行推理函数生成响应。用户可以输入他们的提示,模型将实时生成并显示响应。模型路径通常指定为 "trained_models/new_model"。


该推理管道允许与微调 LLM 进行实时交互。其设计目的是利用微调过程中优化的配置和参数,高效生成对用户提供的提示的响应。可对配置参数进行调整,以微调响应生成的行为,满足特定要求。


文章来源:https://medium.com/@sulaiman.shamasna/falcon-7b-fine-tuning-on-gpu-with-lora-a4209c91b093
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消