【大模型指令微调: 从零学会炼丹】第三章: Q-LoRa 微调phi-3.5-mini

大模型指令微调: 从零学会炼丹

系列目录

第一章: 微调数据集构建
第二章: 数据集预处理
第三章: Q-LoRa微调Phi-3.5-mini

第三章: Q-LoRA 微调Phi-3.5 mini

方案选择

对于大多数独立开发者或企业部门来说, 从底层开始训练大模型是不现实的(缺少数据, 硬件资源, 人力, 成本等等). 而常见的通用大模型(如GPT系列,LLaMa系列, Qwen系列) 在垂直领域的任务场景上表现往往差强人意,因此使用垂直业务数据和知识对大模型进行微调是一个很好的选择.

  • 微调策略分为全参数微调和高效参数微调(PEFT).PEFT是一个宽泛的概念,泛指仅需要部分调整大模型的参数即可提升其在垂直领域的表现能力的所有方法, 本文主要以Q-LoRA技术实现对Phi-3.5-mini 进行PEFT.
  • LoRA:冻结大模型的参数,在模型结构中增加两个低秩矩阵, 使用数据对低秩矩阵进行训练而不是微调模型参数,可以显著减少训练的参数量. 本质上来说LoRA相当于对大模型的残差进行拟合,并将Lora和大模型的预测结果进行求和, 在不影响大模型输出的情况下, 提升在特定任务的性能.
  • Q-LoRA: 在加载大模型时先对其进行量化, 在进行LoRA 微调

综上, 本文将结合Q-LoRA 技术使用前文构建的数据集对Phi-3.5-mini 进行微调

硬件配置

GPU:RTX 4090D 24GB (实验最终仅需要16GB 显存足够), Cuda 12.3
Python:3.12

环境搭建

首先, 我们先安装实验所需要使用到的库, 另外请确保在本步骤前已完成CUDA和 Pytorch GPU 版本的安装, 如果没有安装请参考第零章: 深度学习基础环境搭建: CUDA+Pytorch安装进行安装.

pip install bitsandbytes transformers peft accelerate datasets scipy einops evaluate trl rouge_score scikit-learn

让我们了解下其中重要的包.

  • bitsandbytes: 在本实验中, 使用此库量化加载模型, 减少训练的硬件开销.
  • transformers: 为各种NLP 任务提供加载和训练预训练模型的实用包.
  • peft: 通过peft库中的Lora函数对量化后的模型进行微调
  • datasets: 使用此库可以加载huggingface中的数据集或者本地的数据集.
  • scikit-learn: 使用此库的train_test_split 函数来切分数据集

导入所需要的库

from datasets import load_from_disk, Dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    AutoTokenizer,
    TrainingArguments,
    set_seed,
)
from tqdm import tqdm
import torch
import time
import os
from peft import LoraConfig, get_peft_model
from sklearn.model_selection import train_test_split
from trl import SFTTrainer

seed = 42
set_seed(seed)

在本次实验中不需要跟踪模型的训练指标, 因此禁用掉W&B

# disable Weights and Biases
os.environ['WANDB_DISABLED']="true"

本实验需要从huggingface 中下载模型, 因此需要本地打开代理后添加下面的环境配置,如果没有代理的话也可以从huggingface镜像站提前下载模型文件到本地后,直接加载

os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'

加载数据集

从文件夹中加载上一章保存好的数据集, 并将其切分为训练集和验证集.

执行下面的代码从本地加载数据集

dataset = load_from_disk('./preprocess/data/processed_dataset')

加载后, 可以看到数据集包含以下内容

Dataset({
    features: ['text', 'input_ids', 'attention_mask'],
    num_rows: 391
})
  • text:按照instruction 模板处理后的文本
  • input_idsattention_mask: 经过tokenizer 处理后的向量和attention mask

执行下面的代码按照一定比例(9:1)切分数据集为测试集和验证集. test_size 为验证集在数据集中的占比. 请注意, 使用train_test_split 函数切分后的数据集为字典格式, 需要使用Dataset.from_dict方法将其转回dataset格式.

train_dataset, eval_dataset = train_test_split(dataset, test_size=0.1, random_state=seed)

#将字典类型转为dataset 对象
train_dataset = Dataset.from_dict(train_dataset)
eval_dataset = Dataset.from_dict(eval_dataset)

量化加载预训练模型

执行下面的代码配置bitsandbytes. 我们将对模型进行4位量化加载, 配置如下. 除了我们使用的4位量化外, bnb还提供了8位量化加载, 详情见huggingface 官方文档

compute_dtype = getattr(torch, "float16")
bnb_config = BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_quant_type='nf4',
        bnb_4bit_compute_dtype=compute_dtype,
        bnb_4bit_use_double_quant=False,
    )

执行下面的代码自动下载并加载huggingface 中的预训练模型, 如果是加载本地的大模型, 请将model_id修改为本地模型路径.

# 从本地model 文件夹加载Phi-3.5-mini-instruct
model_id = "microsoft/Phi-3.5-mini-instruct"

original_model = AutoModelForCausalLM.from_pretrained(
    model_id, 
    trust_remote_code=True, 
    device_map="auto",
    quantization_config=bnb_config,
    torch_dtype=torch.bfloat16
)

执行下面的代码加载tokenizer

tokenizer = AutoTokenizer.from_pretrained(
    model_id,
    trust_remote_code=True,
    padding_side="right",
    add_eos_token=True,
    add_bos_token=True,
    use_fast=False
)

创建Lora配置

执行下面的代码创建Lora配置, 其中r代表低秩矩阵的秩,lora_alpha代表激活的参数比例,可以根据自己的硬件情况灵活调整.
请注意target_modules中的qkv_proj, 在phi-3.5 中qkv为一层,但在其他模型中可能为分开的三层,具体需要查看模型的结构

config = LoraConfig(
    r=32,  # Rank
    lora_alpha=32,
    target_modules=[
        'qkv_proj',  # 修改为 qkv_proj
        'dense'
    ],
    bias="none",
    lora_dropout=0.1,
    task_type="CAUSAL_LM",
)

配置完成后可以通过下面的辅助函数查看训练的参数量, 在本案例中, 可训练的参数为13 Million占比为33%.

original_model.gradient_checkpointing_enable()

peft_model = get_peft_model(original_model, config)

def print_trainable_parameters(model):
    """
    Prints the number of trainable parameters in the model.
    """
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    return f"trainable params: {trainable_params} || all params: {all_param} || trainable%: {100 * trainable_params / all_param}"

print(print_trainable_parameters(peft_model)) #trainable params: 12582912 || all params: 3833662464 || trainable%: 0.3282216970888755

配置训练超参数

在这里定义训练的超参数并创建trainer 实例, 具体的细节需要结合自身的硬件进行调整

output_dir = f'./model/peft-bom-validation-training-{str(int(time.time()))}'

peft_training_args = TrainingArguments(
    output_dir = output_dir,
    warmup_steps=1,
    per_gpu_train_batch_size=1,
    per_gpu_eval_batch_size=1,
    gradient_accumulation_steps=1,
    learning_rate=2e-4,
    optim="paged_adamw_8bit",
    logging_steps=25,
    logging_dir="./logs",
    save_strategy="steps",
    save_steps=15,
    eval_strategy="steps",
    eval_steps=15,
    do_eval=True,
    gradient_checkpointing=True,
    report_to="none",
    overwrite_output_dir = 'True',
    group_by_length=True,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
)

peft_model.config.use_cache = False

trainer = SFTTrainer(
    model=original_model,
    train_dataset=train_dataset,
    peft_config=config,
    dataset_text_field="text",
    max_seq_length=4096,
    tokenizer=tokenizer,
    args=peft_training_args,
    eval_dataset=eval_dataset,
)

执行下面的代码进行训练

trainer.train()

综上, 我们完成对phi-3.5-mini 的Q-Lora训练, 这在垂直领域的部署中格外的重要, 相应的其他模型的训练方法也可以参照本实验, 到此本系列的主要内容已经讲述完毕, 后续将逐步补充如何将模型导入到Ollama 中以及如何配置CUDA和Pytorch等和本实验相关的内容.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值