系列篇章💥
No. 文章 1 【Qwen部署实战】探索Qwen-7B-Chat:阿里云大型语言模型的对话实践 2 【Qwen2部署实战】Qwen2初体验:用Transformers打造智能聊天机器人 3 【Qwen2部署实战】探索Qwen2-7B:通过FastApi框架实现API的部署与调用 4 【Qwen2部署实战】Ollama上的Qwen2-7B:一键部署大型语言模型指南 5 【Qwen2部署实战】llama.cpp:一键部署高效运行Qwen2-7B模型 6 【Qwen2部署实战】部署高效AI模型:使用vLLM进行Qwen2-7B模型推理 7 【AI大模型Agent探索】Qwen-Agent:基于Qwen的LLM应用开发框架 8 【AI大模型Agent探索】深入探索实践 Qwen-Agent 的 Function Calling 9 【AI大模型Agent探索】Qwen-Agent之RAG智能助手实践 10 【RAG检索增强生成】LlamaIndex与Qwen2的高效检索增强生成实践 11 【Qwen2微调实战】Lora微调Qwen2-7B-Instruct实践指南
目录
系列篇章💥 引言 1、简介 1.1 Lora微调技术概述 1.2 Qwen2-7B-Instruct模型简介 2.2 Lora微调的优势 2、技术 2.1 Lora微调的工作原理 2.3 Lora微调在Qwen2-7B-Instruct中的应用 3、应用场景 3.1 问答系统 3.2 自动摘要生成 3.3 指令执行 4、代码实践 4.1 环境准备 4.2 安装依赖 4.3 模型下载 4.4 导入依赖包 4.5 数据集准备 4.6 数据加载查看 4.7 加载分词器模型 4.8 数据格式化处理 4.9 加载模型 4.10 lora配置 4.11 配置训练参数 4.12 模型训练 4.13 模型合并 4.14 模型推理 结语引言
在人工智能领域,自然语言处理(NLP)一直是研究的热点之一。随着深度学习技术的不断发展,大型预训练语言模型(如Qwen2-7B-Instruct)在理解与生成自然语言方面取得了显著的进展。然而,这些模型往往需要大量的计算资源和数据来进行微调,以适应特定的应用场景。Lora微调技术作为一种高效的模型优化手段,为解决这一问题提供了新的思路。本文将深入探讨Lora微调技术在Qwen2-7B-Instruct模型上的应用,旨在为读者提供一种高效、低成本的模型定制化方法。
1、简介
1.1 Lora微调技术概述
Lora微调是一种基于低秩矩阵的微调方法,它通过在模型的权重矩阵中引入低秩结构来减少参数数量,从而降低模型的存储和计算需求。这种方法在保持模型性能的同时,显著提高了模型的灵活性和适应性。
1.2 Qwen2-7B-Instruct模型简介
Qwen2-7B-Instruct,一款精心设计的高级预训练语言模型,拥有7.2亿参数,专注于提升对指令性文本的精准理解和高效生成。它在自然语言处理(NLP)的多个专业领域中,如文本摘要、情感分析、机器翻译等,均展现出了卓越的处理能力和适应性。Qwen2-7B-Instruct的先进性能不仅体现在其对语言的深度解析上,更在于其能够快速、准确地执行和回应复杂的语言指令,为专业级的语言任务提供了强大的支持和解决方案。
2.2 Lora微调的优势
与传统的全参数微调相比,Lora微调具有以下优势:
参数减少:通过低秩分解,大幅减少了模型的参数量。 计算效率:降低了模型训练和推理时的计算需求。 灵活性:能够快速适应不同的应用场景。2、技术
2.1 Lora微调的工作原理
Lora微调通过在模型的权重矩阵中引入低秩矩阵,实现了对模型的轻量级微调。具体来说,它将权重矩阵分解为两个较小的矩阵的乘积,这两个矩阵分别对应于原始权重矩阵的行和列。
2.3 Lora微调在Qwen2-7B-Instruct中的应用
通过在Qwen2-7B-Instruct模型上实施Lora微调技术,我们能够针对特定指令性文本任务进行精准优化,显著提升模型在这些任务上的表现力和准确性。这种微调方法不仅增强了模型对专业指令的响应能力,还进一步拓宽了其在复杂语言处理场景中的应用潜力。
3、应用场景
3.1 问答系统
Lora微调后的Qwen2-7B-Instruct可以用于构建更加智能的问答系统,提供更准确的答案。
3.2 自动摘要生成
在自动摘要生成任务中,微调后的模型能够更好地理解文本内容,生成更加精炼和准确的摘要。
3.3 指令执行
对于需要执行复杂指令的应用,如智能家居控制,微调后的模型能够更准确地解析和执行用户的指令。
4、代码实践
4.1 环境准备
介绍如何在Python环境中搭建Lora微调所需的环境,包括必要的库和依赖。
PyTorch: 2.1.0
CUDA:12.1
GPU:RTX 4090D(24GB)
Ubuntu 22.04.3 LTS
4.2 安装依赖
安装相关的依赖包
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.9.5
pip install "transformers>=4.39.0"
pip install streamlit==1.24.0
pip install sentencepiece==0.1.99
pip install accelerate==0.27
pip install transformers_stream_generator==0.0.4
pip install datasets==2.18.0
pip install peft==0.10.0
# 可选
MAX_JOBS=8 pip install flash-attn --no-build-isolation
4.3 模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为模型的下载路径。
在 /root/autodl-tmp 路径下新建 d.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件,如下图所示。并运行 python /root/autodl-tmp/d.py 执行下载,模型大小为 15GB,下载模型大概需要 5 分钟。
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('qwen/Qwen2-7B-Instruct', cache_dir='/root/autodl-tmp', revision='master')
下载成功如下:
4.4 导入依赖包
from datasets import Dataset
import pandas as pd
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig
4.5 数据集准备
LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:
{
“instruction”:“回答以下用户问题,仅输出答案。”,
“input”:“1+1等于几?”,
“output”:“2”
}
其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。下面是对话指令集部分内容:
4.6 数据加载查看
# 将JSON文件转换为CSV文件
df = pd.read_json('../dataset/huanhuan.json')
ds = Dataset.from_pandas(df)
查看前面5条
ds[:5]
输出
{'instruction': ['小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——',
'这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。',
'嬛妹妹,刚刚我去府上请脉,听甄伯母说你来这里进香了。',
'嬛妹妹,我虽是一介御医,俸禄微薄,可是我保证会一生一世对你好,疼爱你,保护你,永远事事以你为重。本来没半月一次到府上去请脉,能够偶尔见一次妹妹的笑靥,已经心满意足了,可谁知——而且我也知道,妹妹心里是不愿意去殿选的。',
'实初虽然唐突了妹妹,却是真心实意地希望妹妹不要去应选,这不仅仅是因为我心里一直把妹妹当成……其实更是因为甄伯父曾经救过家父的性命。'],
'input': ['', '', '', '', ''],
'output': ['嘘——都说许愿说破是不灵的。',
'你们俩话太多了,我该和温太医要一剂药,好好治治你们。',
'出来走走,也是散心。',
'实初哥哥这么说,就枉顾我们一直以来的兄妹情谊了,嬛儿没有哥哥,一直把你当作自己的亲哥哥一样看待,自然相信哥哥会待妹妹好的——自然了,以后有了嫂子,你也会对嫂子更好。',
'我们两家是世交,昔年恩义不过是父亲随手之劳,不必挂怀。']}
4.7 加载分词器模型
加载本地的Qwen2-7B-Instruct模型
tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2-7B-Instruct', use_fast=False, trust_remote_code=True)
tokenizer
输出:
4.8 数据格式化处理
Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典
1)定义处理函数
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
response = tokenizer(f"{example['output']}", add_special_tokens=False)
input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为eos token咱们也是要关注的所以 补充为1
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
if len(input_ids) > MAX_LENGTH: # 做一个截断
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
补充说明:Qwen2 采用的Prompt Template格式如下
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是一个有用的助手。<|im_end|>
2)数据集处理
tokenized_id = ds.map(process_func, remove_columns=ds.column_names)
tokenized_id
输出:
Dataset({
features: ['input_ids', 'attention_mask', 'labels'],
num_rows: 3729
})
3)查看input_ids数据格式是否正确
tokenizer.decode(tokenized_id[0]['input_ids'])
输出:
'<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|im_end|>\n<|im_start|>assistant\n嘘——都说许愿说破是不灵的。<|endoftext|>'
4)labels查看
tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[1]["labels"])))
输出:
'你们俩话太多了,我该和温太医要一剂药,好好治治你们。<|endoftext|>'
4.9 加载模型
加载本地的Qwen2-7B-Instruct模型
import torch
model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen2-7B-Instruct', device_map="auto",torch_dtype=torch.bfloat16)
model
模型信息如下:
Loading checkpoint shards: 0%| | 0/4 [00:00<?, ?it/s]
[9]:
Qwen2ForCausalLM(
(model): Qwen2Model(
(embed_tokens): Embedding(152064, 3584)
(layers): ModuleList(
(0-27): 28 x Qwen2DecoderLayer(
(self_attn): Qwen2SdpaAttention(
(q_proj): Linear(in_features=3584, out_features=3584, bias=True)
(k_proj): Linear(in_features=3584, out_features=512, bias=True)
(v_proj): Linear(in_features=3584, out_features=512, bias=True)
(o_proj): Linear(in_features=3584, out_features=3584, bias=False)
(rotary_emb): Qwen2RotaryEmbedding()
)
(mlp): Qwen2MLP(
(gate_proj): Linear(in_features=3584, out_features=18944, bias=False)
(up_proj): Linear(in_features=3584, out_features=18944, bias=False)
(down_proj): Linear(in_features=18944, out_features=3584, bias=False)
(act_fn): SiLU()
)
(input_layernorm): Qwen2RMSNorm()
(post_attention_layernorm): Qwen2RMSNorm()
)
)
(norm): Qwen2RMSNorm()
)
(lm_head): Linear(in_features=3584, out_features=152064, bias=False)
)
开启梯度检查,查看精度
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
model.dtype # 查看精度
输出:
torch.bfloat16
4.10 lora配置
配置说明:
task_type:模型类型
target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
r:lora的秩,具体可以看Lora原理
lora_alpha:Lora alaph,具体作用参见 Lora 原理
Lora的缩放是啥嘞?就是lora_alpha/r, 在这个LoraConfig中缩放就是4倍。
from peft import LoraConfig, TaskType, get_peft_model
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
inference_mode=False, # 训练模式
r=8, # Lora 秩
lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
lora_dropout=0.1# Dropout 比例
)
config
输出
LoraConfig(peft_type=<PeftType.LORA: 'LORA'>, auto_mapping=None, base_model_name_or_path=None, revision=None, task_type=<TaskType.CAUSAL_LM: 'CAUSAL_LM'>, inference_mode=False, r=8, target_modules={'o_proj', 'down_proj', 'q_proj', 'gate_proj', 'up_proj', 'k_proj', 'v_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)
加载lora配置
model = get_peft_model(model, config)
config
输出
LoraConfig(peft_type=<PeftType.LORA: 'LORA'>, auto_mapping=None, base_model_name_or_path='/root/autodl-tmp/qwen/Qwen2-7B-Instruct', revision=None, task_type=<TaskType.CAUSAL_LM: 'CAUSAL_LM'>, inference_mode=False, r=8, target_modules={'o_proj', 'down_proj', 'q_proj', 'gate_proj', 'up_proj', 'k_proj', 'v_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)
查看可训练参数
model.print_trainable_parameters()
4.11 配置训练参数
TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。
output_dir:模型的输出路径
per_device_train_batch_size:顾名思义 batch_size
gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
logging_steps:多少步,输出一次log
num_train_epochs:顾名思义 epoch
gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行model.enable_input_require_grads(),这个原理大家可以自行探索,这里就不细说了。
args = TrainingArguments(
output_dir="./output/Qwen2_7B_instruct_lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=10, # 为了快速演示,这里设置10,建议你设置成100
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
4.12 模型训练
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
训练效果:
4.13 模型合并
将训练后的lora权重加载到原来的模型中,形成新的模型
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
mode_path = '/root/autodl-tmp/qwen/Qwen2-7B-Instruct/'
lora_path = './output/Qwen2_instruct_lora/checkpoint-10' # 这里改称你的 lora 输出对应 checkpoint 地址
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16, trust_remote_code=True).eval()
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path)
4.14 模型推理
基于合并后(加载了lora权重)的模型进行推理
prompt = "你是谁?"
messages = [
#{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
{"role": "user", "content": "假设你是皇帝身边的女人--甄嬛。"},
{"role": "user", "content": prompt}
]
inputs = tokenizer.apply_chat_template(messages,add_generation_prompt=True,tokenize=True,return_tensors="pt",return_dict=True).to('cuda')
gen_kwargs = {"max_length": 2500, "do_sample": True, "top_k": 1}
with torch.no_grad():
outputs = model.generate(**inputs, **gen_kwargs)
outputs = outputs[:, inputs['input_ids'].shape[1]:]
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
输出:
我是甄嬛,家父是大理寺少卿甄远道。
结语
Lora微调技术为大型预训练语言模型的定制化提供了一种高效、低成本的解决方案。通过本文的介绍和代码实践,读者可以更好地理解Lora微调的原理和应用,将其应用于Qwen2-7B-Instruct模型,以满足特定场景的需求。随着技术的不断进步,我们期待Lora微调能够在更广泛的领域发挥更大的作用。
🎯🔖更多专栏系列文章:AI大模型提示工程完全指南、AI大模型探索之路(零基础入门)、AI大模型预训练微调进阶、AI大模型开源精选实践、AI大模型RAG应用探索实践🔥🔥🔥 其他专栏可以查看博客主页📑
😎 作者介绍:我是寻道AI小兵,资深程序老猿,从业10年+、互联网系统架构师,目前专注于AIGC的探索。
📖 技术交流:欢迎关注【小兵的AI视界】公众号或扫描下方👇二维码,加入技术交流群,开启编程探索之旅。
💘精心准备📚500本编程经典书籍、💎AI专业教程,以及高效AI工具。等你加入,与我们一同成长,共铸辉煌未来。
如果文章内容对您有所触动,别忘了点赞、⭐关注,收藏!加入我,让我们携手同行AI的探索之旅,一起开启智能时代的大门!
总结