基于transformers库 对因果大语言模型进行多GPU微调代码详细解读

大家好,这是小白哥,因为最近在学LLM,因为网上资源太多太杂了,导致看着很简单,但自己跑起来一大堆错误,所以我在这记录一下我的LLM初体验,附赠详细的教程以及对代码解读。准备好了开始发车!!

实验环境

因为在实验室卑微,所以能捞到的GPU不多,我的实验环境如下:
四卡,但是基本都在占满的情况

Thu Sep  5 19:55:30 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.07              Driver Version: 550.90.07      CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  Tesla V100-DGXS-32GB           Off |   00000000:07:00.0 Off |                    0 |
| N/A   64C    P0            143W /  300W |   29913MiB /  32768MiB |     87%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   1  Tesla V100-DGXS-32GB           Off |   00000000:08:00.0 Off |                    0 |
| N/A   58C    P0            241W /  300W |   20777MiB /  32768MiB |     96%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   2  Tesla V100-DGXS-32GB           Off |   00000000:0E:00.0 Off |                    0 |
| N/A   48C    P0             42W /  300W |       2MiB /  32768MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
|   3  Tesla V100-DGXS-32GB           Off |   00000000:0F:00.0 Off |                    0 |
| N/A   65C    P0            265W /  300W |   29993MiB /  32768MiB |     74%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A      1927      G   /usr/lib/xorg/Xorg                              9MiB |
|    0   N/A  N/A      2665      G   /usr/bin/gnome-shell                           15MiB |
|    0   N/A  N/A     28598      C   python                                      29882MiB |
|    1   N/A  N/A     40814      C   python                                      20772MiB |
|    3   N/A  N/A      3693      C   python                                      29988MiB |
+-------------------------------------------------------------------------------

依赖库安装

本文需要到的依赖库;

accelerate\transformers\peft\datasets

accelerate 用于多卡训练
peft 用于微调
这两个库可以看一下详细介绍:

微调数据以及模型

微调数据不用什么说的了,随便找一些文本资料即可
模型因为恰好本地有Baichuang-7B的权重,本着多一事不如少一事,我就使用这个进行

代码详解

代码主要分为四部分:

1. 数据预处理
2. 模型以及分词器构建
3. 微调模型
4. 分布式训练

5. 总结

接下来依次详细介绍每个部分代码,附赠详细的解释
总结部分有整体的代码,拿来可用~~~

## 先导入需要的库
from accelerate import PartialState
from datasets import load_dataset
from peft import TaskType, LoraConfig, get_peft_model
from transformers import Trainer
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from dataclasses import dataclass, field
import transformers
from itertools import chain
import torch
import warnings
# 忽略不重要的信息
warnings.filterwarnings("ignore")

数据预处理

"""
load_dataset 是transformers自带的一个加载数据函数,'text'是指加载的文本txt文件,data_dir 为txt目录 split一般对于HF提供的数据集这么写来下载固定某些子集,比如dataset = load_dataset(path='squad', split='train')即下载其train的数据集
对于本地的数据集,可以通过
dataset = load_dataset('csv', data_files={'train':['my_train_file_1.csv','my_train_file_2.csv'],'test': 'my_test_file.csv'})来构造或者跑两次这个函数即可
"""
train_dataset = load_dataset("text", data_dir="train_data", split="train")
eval_dataset = load_dataset("text", data_dir="eval_data", split="train")


def tokenization(example):
    return tokenizer(example["text"])
'''
定义map函数对其每单行数据分词, remove_columns=["text"] 即丢掉原始的text列
写在with里面是只需要主程序对数据处理就行了
'''
with training_args.main_process_first(desc="dataset map tokenization"):
    train_dataset = train_dataset.map(tokenization, remove_columns=["text"], num_proc=training_args.num_proc)
    eval_dataset = eval_dataset.map(tokenization, remove_columns=["text"], num_proc=training_args.num_proc)

'''
因为原始文本是不等长的,所以这段代码对每行数据拼接起来再依次按照max_seq_len来截断
*是解包 [[1,2],[3,4]] -> [1,2],[3,4]
chain -> [1,2],[3,4] ->  [1,2,3,4]
train_dataset.map(group_texts, num_proc=training_args.num_proc, batched=True) batched=True是把数据变为
bxl 来批量处理
'''
def group_texts(examples):
    # Concatenate all texts.
    concatenated_examples = {k: list(chain(*examples[k])) for k in examples.keys()}
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the small remainder, and if the total_length < block_size  we exclude this batch and return an empty dict.
    # We could add padding if the model supported it instead of this drop, you can customize this part to your needs.
    total_length = (total_length // training_args.max_seq_length) * training_args.max_seq_length
    # Split by chunks of max_len.
    result = {
        k: [t[i: i + training_args.max_seq_length] for i in range(0, total_length, training_args.max_seq_length)]
        for k, t in concatenated_examples.items()
    }
    result["labels"] = result["input_ids"].copy()
    return result


with training_args.main_process_first(desc="dataset map tokenization"):
    train_dataset = train_dataset.map(group_texts, num_proc=training_args.num_proc, batched=True)
    eval_dataset = eval_dataset.map(group_texts, num_proc=training_args.num_proc, batched=True)

加不加split=‘train’的区别,可以看到不加其实是生成一个DatasetDict(默认key是train) 加了是生成一个Dataset其实就是多了一层嵌套
加不加split主要是这种区别

模型以及分词器构建

看着有点长,确实有点长,慢慢看详细注释
最关键就是这一部分


'''
transformers.TrainingArguments 对argparse.Argumentparse的改进,其实就是把平时我们训练时的参数变成类的形式定义
其里面包含了许多对Trainer过程的参数默认定义,你当然可以加上自己的,格式如下
  lora_r: int = field(default=8)
**********************
ataclass 和 field
dataclass: Python 3.7 引入的一个模块,用于简化数据类的定义。数据类是一种专门用于存储数据的类,dataclass 可以自动为类生成一些常用方法,比如 __init__()、__repr__() 和 __eq__()。
field(): 在 dataclass 中,field 函数用于为类字段设置额外的参数或配置。它可以用来指定默认值、默认工厂函数、字段的元数据等。
'''
@dataclass
class CustomArguments(transformers.TrainingArguments):
    # LoRA_r
    lora_r: int = field(default=8)
    # 数据处理时的并行进程数
    num_proc: int = field(default=1)
    # 最大序列长度
    max_seq_length: int = field(default=32)
    # 验证策略,如不想进行验证,可以设置为 ‘no’
    eval_strategy: str = field(default="steps")
    # 每多少步进行一次验证
    eval_steps: int = field(default=100)
    # 随机种子
    seed: int = field(default=0)
    # 优化器
    optim: str = field(default="adamw_torch")
    # 训练epoch数
    num_train_epochs: int = field(default=2)
    # 每个设备上的批量大小
    per_device_train_batch_size: int = field(default=1)

    # 学习率
    learning_rate: float = field(default=5e-5)
    # 权重衰减
    weight_decay: float = field(default=0)
    # 预热步数
    warmup_steps: int = field(default=10)
    # 学习率规划期类型
    lr_scheduler_type: str = field(default="linear")
    # 是否使用梯度检查点
    gradient_checkpointing: bool = field(default=False)
    # 是否使用bf16作为混合精度训练类型
    bf16: bool = field(default=True)
    # 梯度累加步数
    gradient_accumulation_steps: int = field(default=1)

    # 日志记录的步长频率
    logging_steps: int = field(default=3)
    # checkpoint保存策略
    save_strategy: str = field(default="steps")
    # checkpoint保存的步长频率
    save_steps: int = field(default=3)
    # 总的保存checkpoint的数量
    save_total_limit: int = field(default=2)


parser = transformers.HfArgumentParser(CustomArguments)
training_args, = parser.parse_args_into_dataclasses()
## type(training_args) type(training_args)
model_path = 'model/models--baichuan-inc--Baichuan-7B'

'''
量化的指标,其实就是确认模型用什么字节精度的内存来存比如
fb16 fp32
因为资源有限 量化为4bit 
1. load_in_4bit=True
含义: 这个参数启用了4-bit量化,将模型中的线性层权重从浮点数表示转换为4-bit表示。这样可以显著减少模型的内存占用,并提升推理速度,尤其在大模型上表现明显。
用途: 使用4-bit量化来减少模型的内存占用和计算成本。
2. bnb_4bit_use_double_quant=True
含义: 启用双重量化(Double Quantization)。在这个模式下,第一次量化的量化常数会被再次量化。这可以进一步压缩模型,同时保持精度。
用途: 提供更高的量化压缩率,同时保留较好的模型精度。
3. bnb_4bit_quant_type="nf4"
含义: 指定使用的量化类型。nf4 是一种特定的量化类型,全称为 Normal Float 4-bit,这种量化方法在4-bit浮点数表示中提供了较高的精度,适用于深度学习模型。
用途: 选择适合模型的量化类型。nf4 比 fp4(普通的4-bit浮点数)具有更好的数值稳定性。
4. bnb_4bit_compute_dtype=torch.bfloat16
含义: 设置计算时使用的数据类型(dtype),这里选择了 bfloat16(Brain Floating Point 16-bit),这是一种常用于深度学习的16位浮点数格式,它能在保持精度的情况下减少内存占用。
用途: 在计算过程中使用 bfloat16 数据类型,以减少内存使用并加速计算。

'''
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

tokenizer = AutoTokenizer.from_pretrained(model_path,trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    low_cpu_mem_usage=True,
    quantization_config=bnb_config,
    device_map={"": PartialState().process_index},
    trust_remote_code=True
)

'''
这段代码是对需要微调的结构部分进行处理
W_pack 是注意力里面的映射函数 类似kqv_proj o_proj 计算主要注意力后的一个proj
gate_proj,down_proj 是FFN里
注意这些模块名字是需要自己去源码里面找的 当然你可以写其他的 但是找不到它就不会生效 一般就是modeling_modelname.py里面
'''
peft_config = LoraConfig(
    r=training_args.lora_r,
    target_modules=["W_pack",
                    "o_proj",
                    "gate_proj",
                    "down_proj",
                    "up_proj"
                    ],
    task_type=TaskType.CAUSAL_LM,
    lora_alpha=16,
    lora_dropout=0.05
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

量化前:
在这里插入图片描述
量化后
在这里插在这里插入图片描述
图片描述
定义LORA前
在这里插入图片描述
定义LORA后
在这里插入图片描述
针对W_pach看 主要是多了几个LORA的层 lora_droupout、lora_A、lora_B lora_embedding_A\B 、lora_magnitude_vecto

(W_pack): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=4096, out_features=12288, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Dropout(p=0.05, inplace=False)
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=4096, out_features=8, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=8, out_features=12288, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )

分布式训练

这里其实就是靠accelerate这个强大的分布式训练工具

具体的操作可以详细看
accelerate
这是我的注册config流程 注意NO是可以直接回车跳过的
accelerate launch train.py --output_dir output --lora_r 16 最后运行就能愉快的调跑起来了在这里插入图片描述

总结

贴一下全部的代码吧

from accelerate import PartialState
from datasets import load_dataset
from peft import TaskType, LoraConfig, get_peft_model
from transformers import Trainer
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from dataclasses import dataclass, field
import transformers
from itertools import chain
import torch
import warnings

warnings.filterwarnings("ignore")


@dataclass
class CustomArguments(transformers.TrainingArguments):
    # LoRA_r
    lora_r: int = field(default=8)
    # 数据处理时的并行进程数
    num_proc: int = field(default=1)
    # 最大序列长度
    max_seq_length: int = field(default=32)
    # 验证策略,如不想进行验证,可以设置为 ‘no’
    eval_strategy: str = field(default="steps")
    # 每多少步进行一次验证
    eval_steps: int = field(default=100)
    # 随机种子
    seed: int = field(default=0)
    # 优化器
    optim: str = field(default="adamw_torch")
    # 训练epoch数
    num_train_epochs: int = field(default=2)
    # 每个设备上的批量大小
    per_device_train_batch_size: int = field(default=1)

    # 学习率
    learning_rate: float = field(default=5e-5)
    # 权重衰减
    weight_decay: float = field(default=0)
    # 预热步数
    warmup_steps: int = field(default=10)
    # 学习率规划期类型
    lr_scheduler_type: str = field(default="linear")
    # 是否使用梯度检查点
    gradient_checkpointing: bool = field(default=False)
    # 是否使用bf16作为混合精度训练类型
    bf16: bool = field(default=True)
    # 梯度累加步数
    gradient_accumulation_steps: int = field(default=1)

    # 日志记录的步长频率
    logging_steps: int = field(default=3)
    # checkpoint保存策略
    save_strategy: str = field(default="steps")
    # checkpoint保存的步长频率
    save_steps: int = field(default=3)
    # 总的保存checkpoint的数量
    save_total_limit: int = field(default=2)


parser = transformers.HfArgumentParser(CustomArguments)
training_args, = parser.parse_args_into_dataclasses()

model_path = 'model/models--baichuan-inc--Baichuan-7B'

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

tokenizer = AutoTokenizer.from_pretrained(model_path,trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    low_cpu_mem_usage=True,
    quantization_config=bnb_config,
    device_map={"": PartialState().process_index},
    trust_remote_code=True
)
peft_config = LoraConfig(
    r=training_args.lora_r,
    target_modules=["q_proj",
                    "v_proj",
                    "k_proj",
                    "o_proj",
                    "gate_proj",
                    "down_proj",
                    "up_proj"
                    ],
    task_type=TaskType.CAUSAL_LM,
    lora_alpha=16,
    lora_dropout=0.05
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()

train_dataset = load_dataset("text", data_dir="train_data", split="train")
eval_dataset = load_dataset("text", data_dir="eval_data", split="train")


def tokenization(example):
    return tokenizer(example["text"])


with training_args.main_process_first(desc="dataset map tokenization"):
    train_dataset = train_dataset.map(tokenization, remove_columns=["text"], num_proc=training_args.num_proc)
    eval_dataset = eval_dataset.map(tokenization, remove_columns=["text"], num_proc=training_args.num_proc)


def group_texts(examples):
    # Concatenate all texts.
    concatenated_examples = {k: list(chain(*examples[k])) for k in examples.keys()}
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the small remainder, and if the total_length < block_size  we exclude this batch and return an empty dict.
    # We could add padding if the model supported it instead of this drop, you can customize this part to your needs.
    total_length = (total_length // training_args.max_seq_length) * training_args.max_seq_length
    # Split by chunks of max_len.
    result = {
        k: [t[i: i + training_args.max_seq_length] for i in range(0, total_length, training_args.max_seq_length)]
        for k, t in concatenated_examples.items()
    }
    result["labels"] = result["input_ids"].copy()
    return result


with training_args.main_process_first(desc="dataset map tokenization"):
    train_dataset = train_dataset.map(group_texts, num_proc=training_args.num_proc, batched=True)
    eval_dataset = eval_dataset.map(group_texts, num_proc=training_args.num_proc, batched=True)

if __name__ == '__main__':
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=eval_dataset
    )
    trainer.train()
    trainer.save_model("./test_train")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值