使用PEFT库进行ChatGLM3-6B模型的LORA高效微调

PEFT库进行ChatGLM3-6B模型LORA高效微调

  • LORA微调ChatGLM3-6B模型
    • 安装相关库
    • 使用ChatGLM3-6B
    • 模型GPU显存占用
    • 准备数据集
    • 加载模型
    • 加载数据集
    • 数据处理
    • 数据集处理
    • 配置LoRA
    • 配置训练超参数
    • 开始训练
    • 保存LoRA模型
    • 模型推理
    • 从新加载
    • 合并模型
    • 使用微调后的模型

LORA微调ChatGLM3-6B模型

本文基于transformers、peft等框架,对ChatGLM3-6B模型进行Lora微调。

LORA(Low-Rank Adaptation)是一种高效的模型微调技术,它可以通过在预训练模型上添加额外的低秩权重矩阵来微调模型,从而仅需更新很少的参数即可获得良好的微调性能。这相比于全量微调大幅减少了训练时间和计算资源的消耗。

安装相关库

pip install ransformers==4.37.2 peft==0.8.0 accelerate==0.27.0 bitsandbytes

使用ChatGLM3-6B

直接调用ChatGLM3-6B模型来生成对话

from transformers import AutoTokenizer, AutoModel

model_id = "/root/work/chatglm3-6b"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
#model = AutoModel.from_pretrained(model_id, trust_remote_code=True).half().cuda()
model = AutoModel.from_pretrained(model_id, trust_remote_code=True, device='cuda')

model = model.eval()
response, history = model.chat(tokenizer, "你好", history=history)
print(response)

在这里插入图片描述

模型GPU显存占用

默认情况下,模型以半精度(float16)加载,模型权重需要大概 13GB显存。

获取当前模型占用的GPU显存

memory_bytes = model.get_memory_footprint()
# 转换为GB
memory_gb = memory_footprint_bytes / (1024 ** 3)  
print(f"{memory_gb :.2f}GB")

注意:与实际进程占用有差异,差值为预留给PyTorch的显存

准备数据集

准备数据集其实就是指令集构建,LLM的微调一般指指令微调过程。所谓指令微调,就是使用的微调数据格式、形式。

训练目标是让模型具有理解并遵循用户指令的能力。因此在指令集构建时,应该针对目标任务,针对性的构建任务指令集。

这里使用alpaca格式的数据集,格式形式如下:

[
	{
	  "instruction": "用户指令(必填)",
	  "input": "用户输入(选填)",
	  "output": "模型回答(必填)",
	},
    "system": "系统提示词(选填)",
    "history": [
      ["第一轮指令(选填)", "第一轮回答(选填)"],
      ["第二轮指令(选填)", "第二轮回答(选填)"]
    ]
]
instruction:用户指令,要求AI执行的任务或问题

input:用户输入,是完成用户指令所必须的输入内容,就是执行指令所需的具体信息或上下文

output:模型回答,根据给定的指令和输入生成答案

这里根据企业私有文档数据,生成相关格式的训练数据集,大概格式如下:

[
  {
    "instruction": "内退条件是什么?",
    "input": "",
    "output": "内退条件包括与公司签订正式劳动合同并连续工作满20年及以上,以及距离法定退休年龄不足5年。特殊工种符合国家相关规定可提前退休的也可在退休前5年内提出内退申请。"
  },
]

加载模型

from transformers import AutoModel, AutoTokenizer

model_id = "/root/work/chatglm3-6b"
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)

加载数据集

from datasets import load_dataset

data_id="/root/work/jupyterlab/zd.json"
dataset = load_dataset("json", data_files=data_id)
print(dataset["train"])

在这里插入图片描述

数据处理

Lora训练数据是需要经过tokenize编码处理,然后后再输入模型进行训练。一般需要将输入文本编码为input_ids,将输出文本编码为labels,编码之后的结果都是多维的向量。

需要定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典。

# tokenize_func 函数
def tokenize_func(example, tokenizer, ignore_label_id=-100):
    """
    对单个数据样本进行tokenize处理。

    参数:
    example (dict): 包含'content'和'summary'键的字典,代表训练数据的一个样本。
    tokenizer (transformers.PreTrainedTokenizer): 用于tokenize文本的tokenizer。
    ignore_label_id (int, optional): 在label中用于填充的忽略ID,默认为-100。

    返回:
    dict: 包含'tokenized_input_ids'和'labels'的字典,用于模型训练。
    """
    prompt_text = ''                          # 所有数据前的指令文本
    max_input_length = 512                    # 输入的最大长度
    max_output_length = 1536                  # 输出的最大长度

    # 构建问题文本
    question = prompt_text + example['instruction']
    if example.get('input', None) and example['input'].strip():
        question += f'\n{example["input"]}'

    # 构建答案文本
    answer = example['output']

    # 对问题和答案文本进行tokenize处理
    q_ids = tokenizer.encode(text=question, add_special_tokens=False)
    a_ids = tokenizer.encode(text=answer, add_special_tokens=False)

    # 如果tokenize后的长度超过最大长度限制,则进行截断
    if len(q_ids) > max_input_length - 2:  # 保留空间给gmask和bos标记
        q_ids = q_ids[:max_input_length - 2]
    if len(a_ids) > max_output_length - 1:  # 保留空间给eos标记
        a_ids = a_ids[:max_output_length - 1]

    # 构建模型的输入格式
    input_ids = tokenizer.build_inputs_with_special_tokens(q_ids, a_ids)
    question_length = len(q_ids) + 2  # 加上gmask和bos标记

    # 构建标签,对于问题部分的输入使用ignore_label_id进行填充
    labels = [ignore_label_id] * question_length + input_ids[question_length:]

    return {'input_ids': input_ids, 'labels': labels}

进行数据映射处理,同时删除特定列

# 获取 'train' 部分的列名
column_names = dataset['train'].column_names  

# 使用lambda函数调用tokenize_func函数,并传入example和tokenizer作为参数
tokenized_dataset = dataset['train'].map(
    lambda example: tokenize_func(example, tokenizer),
    batched=False,  # 不按批次处理
    remove_columns=column_names  # 移除特定列(column_names中指定的列)
)

执行print(tokenized_dataset[0]),打印tokenize处理结果
在这里插入图片描述

数据集处理

还需要使用一个数据收集器,可以使用transformers 中的DataCollatorForSeq2Seq数据收集器

from transformers import DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(
    tokenizer,
    model=model,
    label_pad_token_id=-100,
    pad_to_multiple_of=None,
    padding=True
)

或者自定义实现一个数据收集器

import torch
from typing import List, Dict, Optional

# DataCollatorForChatGLM 类
class DataCollatorForChatGLM:
    """
    用于处理批量数据的DataCollator,尤其是在使用 ChatGLM 模型时。

    该类负责将多个数据样本(tokenized input)合并为一个批量,并在必要时进行填充(padding)。

    属性:
    pad_token_id (int): 用于填充(padding)的token ID。
    max_length (int): 单个批量数据的最大长度限制。
    ignore_label_id (int): 在标签中用于填充的ID。
    """

    def __init__(self, pad_token_id: int, max_length: int = 2048, ignore_label_id: int = -100):
        """
        初始化DataCollator。

        参数:
        pad_token_id (int): 用于填充(padding)的token ID。
        max_length (int): 单个批量数据的最大长度限制。
        ignore_label_id (int): 在标签中用于填充的ID,默认为-100。
        """
        self.pad_token_id = pad_token_id
        self.ignore_label_id = ignore_label_id
        self.max_length = max_length

    def __call__(self, batch_data: List[Dict[str, List]]) -> Dict[str, torch.Tensor]:
        """
        处理批量数据。

        参数:
        batch_data (List[Dict[str, List]]): 包含多个样本的字典列表。

        返回:
        Dict[str, torch.Tensor]: 包含处理后的批量数据的字典。
        """
        # 计算批量中每个样本的长度
        len_list = [len(d['input_ids']) for d in batch_data]
        batch_max_len = max(len_list)  # 找到最长的样本长度

        input_ids, labels = [], []
        for len_of_d, d in sorted(zip(len_list, batch_data), key=lambda x: -x[0]):
            pad_len = batch_max_len - len_of_d  # 计算需要填充的长度
            # 添加填充,并确保数据长度不超过最大长度限制
            ids = d['input_ids'] + [self.pad_token_id] * pad_len
            label = d['labels'] + [self.ignore_label_id] * pad_len
            if batch_max_len > self.max_length:
                ids = ids[:self.max_length]
                label = label[:self.max_length]
            input_ids.append(torch.LongTensor(ids))
            labels.append(torch.LongTensor(label))

        # 将处理后的数据堆叠成一个tensor
        input_ids = torch.stack(input_ids)
        labels = torch.stack(labels)

        return {'input_ids': input_ids, 'labels': labels}
data_collator = DataCollatorForChatGLM(pad_token_id=tokenizer.pad_token_id)

配置LoRA

在peft中使用LoRA非常简单。借助PeftModel抽象,可以快速将低秩适配器(LoRA)应用到任意模型中。

在初始化相应的微调配置类(LoraConfig)时,需要显式指定在哪些层新增适配器(Adapter),并将其设置正确。

ChatGLM3-6B模型通过以下方式获取需要训练的模型层的名字

from peft.utils import TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING

target_modules = TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING['chatglm']

在PEFT库的 constants.py 文件中定义了不同的 PEFT 方法,在各类大模型上的微调适配模块。

在这里插入图片描述
主要是配置LoraConfig类,其中可以设置很多参数,但主要参数只有几个

# 从peft库导入LoraConfig和get_peft_model函数
from peft import LoraConfig, get_peft_model, TaskType

# 创建一个LoraConfig对象,用于设置LoRA(Low-Rank Adaptation)的配置参数
config = LoraConfig(
    r=8,  # LoRA的秩,影响LoRA矩阵的大小
    lora_alpha=32,  # LoRA适应的比例因子
    # 指定需要训练的模型层的名字,不同模型对应层的名字不同
    # target_modules=["query_key_value"],
    target_modules=target_modules,
    lora_dropout=0.05,  # 在LoRA模块中使用的dropout率
    bias="none",  # 设置bias的使用方式,这里没有使用bias
    # task_type="CAUSAL_LM"  # 任务类型,这里设置为因果(自回归)语言模型
    task_type=TaskType.CAUSAL_LM
)

# 使用get_peft_model函数和给定的配置来获取一个PEFT模型
model = get_peft_model(model, config)

# 打印出模型中可训练的参数
model.print_trainable_parameters()

在这里插入图片描述

配置训练超参数

配置训练超参数使用TrainingArguments类,可配置参数同样有很多,但主要参数也是只有几个

from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
        output_dir=f"{model_id}-lora",  # 指定模型输出和保存的目录
        per_device_train_batch_size=4,  # 每个设备上的训练批量大小
        learning_rate=2e-4,  # 学习率
        fp16=True,  # 启用混合精度训练,可以提高训练速度,同时减少内存使用
        logging_steps=20,  # 指定日志记录的步长,用于跟踪训练进度
        save_strategy="steps",   # 模型保存策略
        save_steps=50,   # 模型保存步数
        # max_steps=50, # 最大训练步长
        num_train_epochs=1  # 训练的总轮数
    )

查看添加LoRA模块后的模型

print(model)

开始训练

配置model、参数、数据集后就可以进行训练了

trainer = Trainer(
    model=model,  # 指定训练时使用的模型
    train_dataset=tokenized_dataset,  # 指定训练数据集
    args=training_args,
    data_collator=data_collator,
)

model.use_cache = False
# trainer.train() 
with torch.autocast("cuda"): 
    trainer.train()

在这里插入图片描述

注意:

执行trainer.train() 时出现异常,参考:bitsandbytes的issues

保存LoRA模型

lora_model_path = "lora/chatglm3-6b-int8"
trainer.model.save_pretrained(lora_model_path )
#model.save_pretrained(lora_model_path )

在这里插入图片描述

模型推理

使用LoRA模型,进行模型推理

lora_model = trainer.model

1.文本补全

text = "人力资源部根据各部门人员"

inputs = tokenizer(text, return_tensors="pt").to(0)

out = lora_model.generate(**inputs, max_new_tokens=500)
print(tokenizer.decode(out[0], skip_special_tokens=True))

在这里插入图片描述
2.问答对话

from peft import PeftModel

input_text = '公司的招聘需求是如何提出的?'
model.eval()
response, history = lora_model.chat(tokenizer=tokenizer, query=input_text)
print(f'ChatGLM3-6B 微调后回答: \n{response}')

在这里插入图片描述

从新加载

加载源model与tokenizer,使用PeftModel合并源model与PEFT微调后的参数,然后进行推理测试。

from peft import PeftModel
from transformers import AutoModel, AutoTokenizer

model_path="/root/work/chatglm3-6b"
peft_model_checkpoint_path="./chatglm3-6b-lora/checkpoint-50"

model = AutoModel.from_pretrained(model_path, trust_remote_code=True, low_cpu_mem_usage=True)
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)

 # 将训练所得的LoRa权重加载起来
p_model = PeftModel.from_pretrained(model, model_id=peft_model_checkpoint_path) 

p_model = p_model.cuda()
response, history = p_model.chat(tokenizer, "内退条件是什么?", history=[])
print(response)

合并模型

将lora权重合并到大模型中,将模型参数加载为16位浮点数

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch 

model_path="/root/work/chatglm3-6b"
peft_model_path="./lora/chatglm3-6b-int8"
save_path = "chatglm3-6b-lora"

tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True, low_cpu_mem_usage=True, torch_dtype=torch.float16, device_map="auto")
model = PeftModel.from_pretrained(model, peft_model_path)
model = model.merge_and_unload()

tokenizer.save_pretrained(save_path)
model.save_pretrained(save_path)

查看合并文件
在这里插入图片描述

使用微调后的模型

from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer.from_pretrained("chatglm3-6b-lora", trust_remote_code=True)
model = AutoModel.from_pretrained("chatglm3-6b-lora", trust_remote_code=True, device='cuda')

model = model.eval()
response, history = model.chat(tokenizer, "内退条件是什么?", history=[])
print(response)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/747397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【vue】vue响应式原理

vue响应式原理 vue2的响应式原理 vue2对对象类型的监听是通过Object.defineProperty实现的,给想要实现响应式的数据对象每个属性加上get,set方法,以实现数据劫持的操作。而对数组类型的监听是通过重写数组的方法实现的。 Object.defineProperty的定义…

组合数学、圆排列、离散数学多重集合笔记

自用 如果能帮到您,那也值得高兴 知识点 离散数学经典题目 多重集合组合 补充容斥原理公式 隔板法题目 全排列题目:

机械拆装-基于Unity-准备零件

目录 前言 1. 装配体模型的准备(STEP格式保存为零件) 1.1 关于不停提示“默认模板无效” 1.2 关于无法保存单个零件的解决 2. 整理装配体与零件 2.1 零件命名规则 2.2 建立子装配体 3. 装配体和零件转换格式 3.1 3DMax单位设置 3.2 装配体转换 3.3…

JavaScript通用下载方法,但jpg图片下载打不开

通用下载方法,通过Blob的方式,访问Url地址,下载对应的图片,excel等文件。 axios({method: "get",url,responseType: "blob",}).then((res: any) > {const link document.createElement("a");co…

Linux - 札记 - W10: Warning: Changing a readonly file

Linux - 札记 - W10: Warning: Changing a readonly file 这里写目录标题 一、问题描述1. 现象2. 原因 二、解决方案 一、问题描述 1. 现象 在使用 vim 编辑文件时(我这里是要编辑 /root/.ssh/authorized_keys)提示:W10: Warning: Changing…

VOC格式转YOLO格式,xml文件转txt文件简单通用代码

目录 前言 思路介绍 代码 完整代码 拓展代码 前言 很多人在进行目标检测训练时习惯将得到的数据标注为XML文件的VOC格式,或者在网上获取的数据集被标注为XML文件,但是不同的标注工具进行的标注会产生不同的标注xml文件,这里我写了一种通用…

信息学奥赛初赛天天练-36-CSP-J2021阅读程序-ASCII、运算符优先级、二进制补码存储、模拟算法应用

PDF文档公众号回复关键字:20240626 2021 CSP-J 阅读程序2 1 阅读程序(判断题1.5分 选择题3分 共计40分 ) #include<stdio.h> #include<string.h>char base[64]; char table[256]; char str[256]; char ans[256];void init() {for(int i0;i<26;i) base[i]Ai;fo…

49、基于归一化感知器的输入向量分类(matlab)

1、基于归一化感知器的输入向量分类的原理及流程 归一化感知器是一种分类算法&#xff0c;其原理基于感知器算法&#xff0c;但是在输入向量上进行了归一化处理&#xff0c;以提高算法的性能和稳定性。 流程如下&#xff1a; 输入向量归一化&#xff1a;对每个输入向量进行归…

图解HTTP笔记整理(前六章)

图解HTTP 第一章 web使用HTTP &#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;协议作文规范&#xff0c;完成从客户端到服务器端等一系列运作流程。 协议&#xff1a;计算机与网络设备要相互通信&#xff0c;双方就必须基于相同的方法。比如…

JetBrains Rider 2024安装教程

一、下载Rider 1、进入官网&#xff0c;点击“下载” 2、下载完毕 二、安装Rider 1、双击下载的exe文件 2、点击“下一步” 3、可以点击“浏览”选择安装路径&#xff0c;之后点击“下一步” 4、选中图中四项&#xff0c;点击“下一步” 5、选中图中四项&#xff0c;点击“下…

Superset二次开发之导入导出功能源码解读

可导出的类型 支持 看板(Dashboard)、图表(Charts)、数据集(Datasets)、SQL(saved_query)、数据库(Database connection) 单次或批量的导出,和单次导入操作 看板(Dashboard) 图表(Charts) 数据集(Datasets) SQL (saved_query) 数据库(database connections)…

4.任务调度

1.基本知识 2.任务的状态 FreeRTOS中任务共存在4种状态&#xff1a;Running 运行态 当任务处于实际运行状态称之为运行态&#xff0c;即CPU的使用权被这个任务占用&#xff08;同一时间仅一个任务处于运行态&#xff09;。Ready 就绪态 处于就绪态的任务是指那些能够运行&…

声场合成新方法:基于声波传播的框架

声场合成是指在房间内的麦克风阵列上&#xff0c;根据来自房间内其他位置的声源信号&#xff0c;合成每个麦克风的音频信号。它是评估语音/音频通信设备性能指标的关键任务&#xff0c;因为它是一种成本效益高的方法&#xff0c;用于数据生成以替代真实的数据收集&#xff0c;后…

Java知识点整理 13 — Hutool工具库

在开发时经常需要编写很多与业务无关的代码&#xff0c;比如获取指定日期对象、获取本机 IP 地址、数据加密等。通常我们会将这些代码独立出来&#xff0c;放到 utils 目录下&#xff0c;作为工具类供其它代码调用。 但如果遇到一个从未接触过的领域知识&#xff0c;开发一个新…

Spring Boot如何实现跨域资源共享(CORS)?

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

01_02_Mybatis的配置文件与基于XML的使用

1、引入日志 在这里我们引入SLF4J的日志门面&#xff0c;使用logback的具体日志实现&#xff1b;引入相关依赖&#xff1a; <!--日志的依赖--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version&g…

Spring Boot整合RocketMQ实现延迟消息消费

导包 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.3</version></dependency>添加配置信息 application配置文件 # rocketMq地址 rocketmq.name…

数据采集与预处理复习资料

目录 第一章 简答 1.简述Hadoop各个组件及其功能 2.Hadoop在大数据技术体系中的地位和作用&#xff08;来自文心一言&#xff09; 3.Hadoop 启动命令&#xff0c;停止命令 4.pig 加载HDFS 数据 5.数据采集的方法&#xff08;来自ppt&#xff09; 6.数据分析过程&#xf…

GO sync包——读写锁

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

引导过程与服务器控制

一、引导过程 1.开机自检 服务器主机开机以后&#xff0c;将根据主板 BIOS 中的设置对 CPU&#xff08;Central Processing Unit&#xff0c; 中央处理器&#xff09;、内存、显卡、键盘等设备进行初步检测&#xff0c;检测成功后根据预设的启动顺序移 交系统控制权&#xff0c…