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

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

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

QLORA微调ChatGLM3-6B模型

LoRA的核心思想是将可调整的低秩矩阵注入到Transformer架构的每一层中,充当"适配器"的作用。这样可以使模型针对特定任务进行调整和专门化,同时最大限度地减少额外的参数数量,提高参数效率。

QLoRA是LoRA的扩展版本,在微调过程中引入了量化技术,以进一步提高参数效率。QLoRA利用LoRA的原理,并引入了4位NormalFloat(NF4)量化和双重量化技术,进一步减少了存储和计算资源的使用。

安装相关库

pip install datasets==2.17.0 pandas transformers==4.37.2 peft==0.8.0  accelerate==0.27.0 bitsandbytes autoawq optimum

使用ChatGLM3-6B

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

from transformers import AutoTokenizer, AutoModelmodel_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_bytes / (1024 ** 3)  
print(f"{memory_gb :.2f}GB")

注意:

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

准备数据集

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

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

[{"instruction": "用户指令(必填)","input": "用户输入(选填)","output": "模型回答(必填)",}
]
instruction:用户指令,告知模型其需要完成的任务input:用户输入,是完成用户指令所必须的输入内容output:模型回答,模型应该给出的输出

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

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

作者:CodeDevMaster
链接:https://juejin.cn/post/7384636970033938482
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

加载数据集

from datasets import load_dataset  # 导入 load_dataset 函数# 加载处理好的训练数据集
dataset = load_dataset("json", data_files="./train_data.json")
print(dataset)  # 打印加载的数据集对象
DatasetDict({train: Dataset({features: ['instruction', 'output'],num_rows: 20182})
})

数据处理

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

加载AutoTokenizer

from transformers import AutoTokenizer  # 导入 AutoTokenizer 类# 使用 AutoTokenizer 类的 from_pretrained 方法加载指定模型的 tokenizer
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm3-6b", trust_remote_code=True)

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

# 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}

可以从从原始数据集中提取出一个较小规模的训练子集,以便稍后对这个子集进行处理、训练或其他操作。

from datasets import DatasetDict  # 导入 DatasetDict 类small_dataset = DatasetDict()  # 创建一个空的 DatasetDict 对象# 在 small_dataset 中存储一个部分数据集,包括以下步骤:
# 1. 从原始数据集的 'train' 部分中随机打乱数据(使用随机种子 16)
# 2. 选择前 1000 条样本构成一个小的数据集
small_dataset["train"] = dataset["train"].shuffle(seed=16).select(range(1000))# 打印数据集
print(small_dataset)

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

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

查看处理结果

print(tokenized_dataset[0])

在这里插入图片描述

数据集处理

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

from transformers import DataCollatorForSeq2Seqdata_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_idself.ignore_label_id = ignore_label_idself.max_length = max_lengthdef __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_lenlabel = d['labels'] + [self.ignore_label_id] * pad_lenif 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))# 将处理后的数据堆叠成一个tensorinput_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)

加载量化模型-4bit

对于加载4bit量化模型,需要设置多个参数,具体参数如下:使用nf4量化数据类型加载模型,开启双量化配置,以bf16混合精度训练

以4-bits量化加载ChatGLM3-6B模型,只需要大约4GB左右显存。

from transformers import AutoModel, BitsAndBytesConfig# QLoRA量化配置
q_config = BitsAndBytesConfig(load_in_4bit=True, # 是否在4位精度下加载模型bnb_4bit_quant_type='nf4', # 4位精度量化的类型,表示使用nf4量化类型bnb_4bit_use_double_quant=True,  # 是否使用双精度量化bnb_4bit_compute_dtype=torch.float16) # 4位精度计算的数据类型,使用半精度浮点数model = AutoModel.from_pretrained('./chatglm3-6b',quantization_config=q_config,device_map='auto',trust_remote_code=True)# 优化内存使用和计算效率
model.supports_gradient_checkpointing = True  
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
model.config.use_cache = False 

预处理量化模型

预处理量化后的模型,使其可以支持低精度微调训练

peft库中的prepare_model_for_kbit_training函数是用于为模型执行量化准备的工具。这个函数通常用于准备在低比特宽度(如8-bit, 4-bit, 2-bit等)下进行模型训练或微调。

该函数的主要功能是调整模型的权重和激活,使其适应在指定的比特位宽度下进行有效的训练。

from peft import prepare_model_for_kbit_trainingkbit_model = prepare_model_for_kbit_training(model)

配置LoRA适配器

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

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

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

from peft.utils import TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPINGtarget_modules = TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING['chatglm']

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

在这里插入图片描述

lora_config = LoraConfig(target_modules=target_modules,r=4,# LoRA秩lora_alpha=32,lora_dropout=0.05,bias='none',inference_mode=False,task_type=TaskType.CAUSAL_LM
)# 使用get_peft_model函数和给定的配置来获取一个PEFT模型
qlora_model = get_peft_model(kbit_model, lora_config)# 打印出模型中可训练的参数
qlora_model.print_trainable_parameters()

训练超参数配置

训练总步数steps的计算:

epoch: 1个epoch表示对全部训练集样本进行一次完整的训练num_train_epochs:多少个epoch, 表示进行多少个、多少轮完整数据集的训练

每个epoch的训练步数:

steps/epoch = num_train_examples / (batch_size * gradient_accumulation_steps)

训练总步数:

total_steps = steps/epoch * num_train_epochs
from transformers import TrainingArguments, Trainertraining_args = TrainingArguments(output_dir="models/chatglm3-6b",          # 输出目录per_device_train_batch_size=4,                     # 每个设备的训练批量大小gradient_accumulation_steps=4,                     # 梯度累积步数# per_device_eval_batch_size=8,                      # 每个设备的评估批量大小learning_rate=1e-3,                                # 学习率num_train_epochs=1,                                # 训练轮数lr_scheduler_type="linear",                        # 学习率调度器类型warmup_ratio=0.1,                                  # 预热比例logging_steps=10,                                 # 日志记录步数save_strategy="steps",                             # 模型保存策略save_steps=100,                                    # 模型保存步数# evaluation_strategy="steps",                       # 评估策略# eval_steps=500,                                    # 评估步数optim="adamw_torch",                               # 优化器类型fp16=True,                                        # 是否使用混合精度训练
)

启用指标评估

from transformers import TrainingArguments, Trainertraining_args = TrainingArguments(output_dir="models/chatglm3-6b",          # 输出目录per_device_train_batch_size=4,                     # 每个设备的训练批量大小gradient_accumulation_steps=4,                     # 梯度累积步数learning_rate=1e-3,                                # 学习率max_steps=100,                                     # 训练步数lr_scheduler_type="linear",                        # 学习率调度器类型warmup_ratio=0.1,                                  # 预热比例logging_steps=10,                                 # 日志记录步数save_strategy="steps",                             # 模型保存策略save_steps=20,                                    # 模型保存步数optim="adamw_torch",                               # 优化器类型fp16=True,                                        # 是否使用混合精度训练
)

开始训练

trainer = Trainer(model=qlora_model,args=training_args,train_dataset=tokenized_dataset,data_collator=data_collator)trainer.train()

保存LoRA模型

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

在这里插入图片描述

模型推理

使用QLoRA微调后的ChatGLM3-6B模型进行对比模型推理

import torch
from transformers import AutoModel, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel, PeftConfig# QLoRA量化配置
q_config = BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_quant_type='nf4',bnb_4bit_use_double_quant=True,bnb_4bit_compute_dtype=torch.float32)# 加载量化后的原始模型
base_model = AutoModel.from_pretrained('./chatglm3-6b',quantization_config=q_config,trust_remote_code=True,device_map='auto')
# 优化
base_model.requires_grad_(False)
base_model.eval()# 加载tokenizer 
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)

使用原始ChatGLM3-6B模型进行推理测试

input_text = 'xxxx?'
response, history = base_model.chat(tokenizer=tokenizer, query=input_text)
print(f'ChatGLM3-6B 微调前:\n{response}')

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

input_text = 'xxxx?'
# 合并
model = PeftModel.from_pretrained(base_model, './chatglm3-6b-lora')
response, history = model.chat(tokenizer=tokenizer, query=input_text)
print(f'ChatGLM3-6B 微调后: \n{response}')

合并模型

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

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch model_path="./chatglm3-6b"
peft_model_path="./lora/chatglm3-6b"
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, AutoModeltokenizer = 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)

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Pytorch实用教程】transformer中创建嵌入层的模块nn.Embedding的用法

文章目录 1. nn.Embedding的简单介绍1.1 基本用法1.2 示例代码1.3 注意事项2. 通俗的理解num_embeddings和embedding_dim2.1 num_embeddings2.2 embedding_dim2.3 使用场景举例结合示例1. nn.Embedding的简单介绍 nn.Embedding 是 PyTorch 中的一个模块,用于创建一个嵌入层。…

准大一新生开学千万要带证件照用途大揭秘

1、提前关注好都有哪些考场,以及这些考场大致在网页的哪个位置。比如我选对外经贸大学,我就直接找到第二个点进去。 2、电脑上同时开了谷歌浏览器和IE浏览器,以及手机也登陆了。亲测下来,同一时间刷新,谷歌浏览器能显示…

​cesium、three.js,三维GIS为啥那么热?到底怎么学呢?

​cesium、three.js,三维GIS为啥那么热?他们的应用场景都是什么呢?接下来我们可以一起来看看~ 三维GIS的应用 GIS和3D的应用是趋势,目前已经有很多应用案例,例如BIM,智慧城市,数字孪生等。如下…

汇聚荣拼多多电商实力强吗?

汇聚荣拼多多电商实力强吗?汇聚荣拼多多,作为中国电商领域的后起之秀,已经在市场上占据了一席之地。那么,它的实力究竟如何呢?在回答这个问题之前,我们需要先了解一下拼多多的基本情况。拼多多是一家以社交电商为主要模式的购物…

3个方法教你如果快速绕过Excel工作表保护密码

在日常生活中,我们可能会遇到一些特殊情况,比如不小心忘记了Excel文件中设置的打开密码。别担心!这里为您带来一份详细的Excel文件密码移除教程,助您轻松绕过Excel工作表保护。 方法一:使用备份文件 如果您有文件的备…

深入理解 go map

什么是 map 维基百科里这样定义 map: In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection. 简单…

[nicetomeetyou@onionmail.org].Faust勒索病毒科普知识全解析

引言 随着网络技术的飞速发展,勒索病毒已成为威胁全球网络安全的一大隐患。[nicetomeetyouonionmail.org].Faust作为一种新型勒索病毒,通过其复杂的加密技术和隐蔽的传播手段,给个人、企业及政府机构带来了巨大的损失。本文将从多个维度全面解…

C++的入门——(命名空间, 输入输出 ,缺省参数 ,函数重载 ,引用, 内联函数 ,nullptr)

文章目录 引言c兼容C语⾔c的重要性C在⼯作领域中的应⽤ 一、命名空间namespace的价值namespace的定义命名空间使⽤ 二、 C输⼊&输出三、缺省参数四、函数重载1、参数类型不同2、参数个数不同3、参数类型顺序不同 五、引⽤1、引⽤的概念和定义2、引⽤的特性3、引⽤的使⽤4、…

SCSA第四天

ASPF FTP --- 文件传输协议 Tftp --- 简单文件传输协议 FTP协议相较于Tftp协议 ---- 1,需要进行认证 2,拥有一套完整的命令集 用户认证 防火墙管理员认证 ---- 校验登录者身份合法性 用户认证 --- 上网行为管理中的一环 上网用户认证 --- 三层认证…

Java SQL 连接(初级)

实训Day3 记实 实训第三天,今天是头脑风暴的第二天,课程将SQL与Java(idea)代码结合,这是一项具有挑战性的代码课程。课程将两个应用结合起来,展现了Java代码的跨平台性,展现了Java语言的封装性…

Cesium中实现全球体积云效果的一种方案

原生 Cesium 提供了一种积云的效果,云的物理特征和渲染性能都还不错,这种方案适合表达小范围相对离散的云朵,但是用来实现全球范围下相对连续、柔和渐变的云层比较困难。本文在体渲染的基础上,参考了开源社区中 shadertoy 和 thre…

c#调用c++ dll库报错System.BadImageFormatException

System.BadImageFormatException:“试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)” 1. dll需要选择release模式进行编译 2.选择相同位数,比如x64平台,c#也需要x64 3.不要设置c#不支持的函数供调用 比如: c可以输出到控制台…

Meta发布Llama 2驱动的AI代码生成器:Code Llama,开源来袭!

Meta 刚刚了号称是编程领域 “最先进的大语言模型”—— Code Llama ,可根据 代码和自然语言提示 生成代码和有关代码的自然语言,支持多种主流编程语言, 包括 Python、C、Java、PHP、Typescript (Javascript)、C# 和 Bash 。 Code Llama 完全…

拆分盘究竟是什么?一篇文章带你了解!

拆分盘是一种特殊的理财产品或投资模式,它通常被描述为“只涨不跌”的投资方式,多指股票,但实质上与传统股市中的股票有本质区别。以下是对拆分盘的详细解析: 一、拆分盘的定义 拆分盘可以理解为一种只涨不跌的理财股票。其特点在…

OpenGL3.3_C++_Windows(29)

Demo exposure 0.1f exposure 5.0f HDR色调映射 问题:有多个亮光源使这些数值总和超过了1.0,颜色值会被约束在1.0,从而导致场景混成一片,难以分辨:色调映射:用更大范围的颜色值渲染从而获取大范围的黑暗…

怎么将3张照片合并成一张?这几种拼接方法很实用!

怎么将3张照片合并成一张?在我们丰富多彩的日常生活里,是否总爱捕捉那些稍纵即逝的美好瞬间,将它们定格为一张张珍贵的图片?然而,随着时间的推移,这些满载回忆的宝藏却可能逐渐演变成一项管理挑战&#xff…

鸿蒙语言基础类库:【@ohos.util (util工具函数)】

util工具函数 说明: 本模块首批接口从API version 7开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 该模块…

Linux系统之lscpu命令的基本使用

Linux系统之lscpu命令的基本使用 一、lscpu命令介绍二、lscpu命令的使用帮助2.1 命令格式2.2 命令选项2.3 使用帮助 三、lscpu命令的基本使用3.1 查看lscpu版本3.2 直接使用lspcu命令3.3 可解析的格式打印cpu信息3.4 可扩展格式打印cpu信息 四、lscpu命令使用注意事项 一、lscp…

【题目/算法训练】:单调队列单调栈

🚀 前言: 【算法】单调队列&&单调栈 可以在看完这篇文章后,再来写下面的题目 一、绝对差不超过限制的最长连续子数组 思路: 1) 就相当于滑动窗口,维护滑动窗口内的两个值,一个是最大值…

Linux常用选项和指令

目录 Linux指令使用注意 用户创建与删除 ls指令 ls指令介绍 ls常见选项 ls选项组合使用 pwd指令 Linux文件系统结构 多叉树结构文件系统介绍 多叉树结构文件系统的特点 cd指令 绝对路径 相对路径 cd指令介绍 家户目录 最近访问的目录 touch指令 ​编辑mkdir指…