Qwen2.5-7B大模型微调记录
研究需要,需要搞一个大模型出来,没有太多的时间自己训练,准备用现成的开源大模型,然后结合研究方向进行微调
前前后后折腾大半个月,总算做完了第一个微调的大模型,模型基于阿里的千问2.5大模型,然后微调的训练集是一些法律相关的问题
记录一下流程
一、硬件设备
特意找联想配了个工作站(联想thinkstation P920),基础配置如下:
- CPU,英特尔至强金牌系列5218
- GPU,英伟达RTX4090,显存24G
- 内存128G
二、软件环境
1、python编译环境
python版本为3.12.7,直接用anaconda安装的
编译器使用的是pycharm2024.3专业版,试用版
2、cuda环境
个人的操作系统是win10,
安装的cuda toolkit版本为12.1,cudnn版本为8.9.6.50
安装cuda toolkit时记住安装目录,然后将cudnn安装压缩文件中解压得到的几个文件夹拷贝至cuda安装目录下
3、第三方python依赖包
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install modelscope==1.20.0
pip install transformers==4.46.2
pip install accelerate==1.1.1
pip install peft==0.13.2
pip install datasets==3.1.0
pip install swanlab==0.3.25
三、模型及数据下载
1、千问2.5-7B模型下载
模型从hugging-face镜像站下载
我选择从git上拉远程仓库,拉取流程如下:
git lfs install
git clone https://hf-mirror.com/Qwen/Qwen2.5-7B-Instruct
总共28G左右,其中git目录占了一半,我大概拉了大半天,和本地网速相关
下载完之后,把git目录删掉,可以节省一些空间
2、微调训练数据集下载
使用 DISC-Law-SFT 数据集,主要是中文法律相关的问答,内容如下:
这个json格式的文件并不满足qwen大模型训练集的格式,需要把它处理成如下格式:
{
"instruction": "你是一个法律专家,请根据用户的问题给出专业的回答",
"input": "诈骗罪量刑标准是什么?",
"output": "诈骗罪指的是以非法占有为目的,使用欺骗方法,骗取数额较大的公私财物的行为..."
}
处理代码如下:
import json
# 定义固定的instruction
INSTRUCTION = "你是一个法律专家,请根据用户的问题给出专业的回答"
def process_jsonl(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as infile, open(output_file, 'w', encoding='utf-8') as outfile:
for line in infile:
# 读取每一行并解析JSON
data = json.loads(line)
# 创建新的字典,包含instruction, input和output
new_data = {
"instruction": INSTRUCTION,
"input": data["input"],
"output": data["output"]
}
# 将新的字典写入输出文件
json.dump(new_data, outfile, ensure_ascii=False)
outfile.write('\n')
# 使用示例
input_file = "DISC-Law-SFT-Pair-QA-released.jsonl"
output_file = "DISC-Law-SFT-Pair-QA-released-new.jsonl"
process_jsonl(input_file, output_file)
print(f"处理完成。输出文件:{output_file}")
四、模型微调
进入重点
1、集成SwanLab
SwanLab是一个云上的AI实验平台,一站式跟踪、比较、分享你的模型,一站式AI实验协作,跟踪超参数和指标,监控GPU与硬件情况。
实际上就是在云端监控模型,它和transformers已经进行了集成,在transformers.Trainer的回调中添加SwanLabCallback实例即可,然后会在云端记录训练过程,具体实现如下:
trainer = Trainer(
model=peft_model,
args=args,
train_dataset=train_dataset,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
callbacks=[swanlab_callback],
)
swanlab_callback即SwanLabCallback实例
在训练开始之前,控制台会让你输入SwanLab的API key,需要去SwanLab官网注册
2、微调
按以下步骤开始微调:
- 加载Qwen2___5-7B-Instruct大模型
- 加载模型权重
- 加载处理好的微调训练集DISC-Law-SFT-Pair-QA-released-new.jsonl
- 配置Lora
- 开启梯度检查点,并设置微调的参数requires_grad = True
- 启用SwanLab记录模型训练过程
- 训练1个epoch
模型训练代码如下:
import json
import pandas as pd
import torch
from datasets import Dataset
from modelscope import snapshot_download, AutoTokenizer
from swanlab.integration.transformers import SwanLabCallback
from peft import LoraConfig, TaskType, get_peft_model
from transformers import (
AutoModelForCausalLM,
TrainingArguments,
Trainer,
DataCollatorForSeq2Seq,
)
import swanlab
def process_func(example):
"""
将数据集进行预处理
"""
MAX_LENGTH = 384
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(
f"<|im_start|>system\n{example['instruction']}<|im_end|>\n<|im_start|>user\n{example['input']}<|im_end|>\n<|im_start|>assistant\n",
add_special_tokens=False,
)
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]
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}
def predict(messages, model, tokenizer):
device = "cuda"
text = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(device)
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):]
for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response
# 在modelscope上下载Qwen模型到本地目录下
# model_dir = snapshot_download("Qwen/Qwen2.5-Coder-7B-Instruct", cache_dir="/root/autodl-tmp", revision="master")
# Transformers加载模型权重
tokenizer = AutoTokenizer.from_pretrained("Qwen2___5-7B-Instruct", use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("Qwen2___5-7B-Instruct", device_map="auto",
torch_dtype=torch.bfloat16)
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
for param in model.parameters():
param.requires_grad = True
# 处理数据集
train_jsonl_path = "DISC-Law-SFT-Pair-QA-released-new.jsonl"
train_df = pd.read_json(train_jsonl_path, lines=True)[5:5000]
train_ds = Dataset.from_pandas(train_df)
train_dataset = train_ds.map(process_func, remove_columns=train_ds.column_names)
test_df = pd.read_json(train_jsonl_path, lines=True)[:5]
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=64, # Lora 秩
lora_alpha=16, # Lora alaph,具体作用参见 Lora 原理
lora_dropout=0.1, # Dropout 比例
)
peft_model = get_peft_model(model, config)
args = TrainingArguments(
output_dir="./output/Qwen2.5-Coder-7b",
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
logging_steps=10,
num_train_epochs=1,
save_steps=100,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True,
report_to="none",
)
class RewriteSwanLabCallback(SwanLabCallback):
def on_train_begin(self, args, state, control, model=None, **kwargs):
if not self._initialized:
self.setup(args, state, model, **kwargs)
print("训练开始")
print("未开始微调,先取3条主观评测:")
test_text_list = []
for index, row in test_df[:3].iterrows():
instruction = row["instruction"]
input_value = row["input"]
messages = [
{"role": "system", "content": f"{instruction}"},
{"role": "user", "content": f"{input_value}"},
]
response = predict(messages, peft_model, tokenizer)
messages.append({"role": "assistant", "content": f"{response}"})
result_text = f"【Q】{messages[1]['content']}\n【LLM】{messages[2]['content']}\n"
print(result_text)
test_text_list.append(swanlab.Text(result_text, caption=response))
swanlab.log({"Prediction": test_text_list}, step=0)
def on_epoch_end(self, args, state, control, **kwargs):
# ===================测试阶段======================
test_text_list = []
for index, row in test_df.iterrows():
instruction = row["instruction"]
input_value = row["input"]
ground_truth = row["output"]
messages = [
{"role": "system", "content": f"{instruction}"},
{"role": "user", "content": f"{input_value}"},
]
response = predict(messages, peft_model, tokenizer)
messages.append({"role": "assistant", "content": f"{response}"})
if index == 0:
print("epoch", round(state.epoch), "主观评测:")
result_text = f"【Q】{messages[1]['content']}\n【LLM】{messages[2]['content']}\n【GT】 {ground_truth}"
print(result_text)
test_text_list.append(swanlab.Text(result_text, caption=response))
swanlab.log({"Prediction": test_text_list}, step=round(state.epoch))
swanlab_callback = RewriteSwanLabCallback(
project="Qwen2.5-LoRA-Law",
experiment_name="7b",
config={
# "model": "https://modelscope.cn/models/Qwen/Qwen2.5-7B-Instruct",
# "dataset": "https://huggingface.co/datasets/ShengbinYue/DISC-Law-SFT",
# "github": "https://github.com/datawhalechina/self-llm",
"system_prompt": "你是一个法律专家,请根据用户的问题给出专业的回答",
"lora_rank": 64,
"lora_alpha": 16,
"lora_dropout": 0.1,
},
)
trainer = Trainer(
model=peft_model,
args=args,
train_dataset=train_dataset,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
callbacks=[swanlab_callback],
)
trainer.train()
其中RewriteSwanLabCallback是重写的SwanLabCallback类
运行程序开始输入SwanLab的api key后,就进入正式训练过程,控制台训练如下:
大概经过20来分钟的训练,训练结束
SwanLab中记录的信息如下:
- 训练环境
- 日志
swanlab: Tracking run with swanlab version 0.3.25
swanlab: Run data will be saved locally in D:\python\test\swanlog\run-20241122_090504-a3b1799d
swanlab: 👋 Hi huanggang, welcome to swanlab!
swanlab: Syncing run 7b to the cloud
swanlab: 🌟 Run `swanlab watch D:\python\test\swanlog` to view SwanLab Experiment Dashboard locally
swanlab: 🏠 View project at https://swanlab.cn/@huanggang/Qwen2.5-LoRA-Law
swanlab: 🚀 View run at https://swanlab.cn/@huanggang/Qwen2.5-LoRA-Law/runs/iazecr183ybpg7frii6sk
<IPython.core.display.HTML object>
训练开始
未开始微调,先取3条主观评测:
【Q】违章停车与违法停车是否有区别?
【LLM】违章停车与违法停车在某些情况下可以视为同一概念,但也有细微的区别。具体来说:
1. 从法律层面来看,“违法停车”是法律术语,是指机动车违反《中华人民共和国道路交通安全法》等法律法规关于道路通行的规定,随意停放的行为��
2. 在实际操作中,交警部门通常使用“违法停车”来指代违
【Q】人民法院执行实际施工人对发包人的到期债权的,转包人或者违法分包人是否有权提异议?
【LLM】根据《最高人民法院关于审理建设工程施工合同纠纷案件适用法律问题的解释(一)》第三十二条的规定,在执行程序中,实际施工人依据《最��
【Q】诈骗罪量刑标准是什么?
【LLM】诈骗罪的量刑标准如下:
1. 诈骗公私财物价值三千元至一万元以上、三万元至十万元以上、五十万元以上的,应当分别认定为刑法第二百六十六条规定的“数额较大”、“数额��
2. 诈骗公私财物达到上述规定的数额标准,具有下列情形之一的,可以依照刑法第二百六十六条的规定酌情从严惩处:
(一)通过发送短信、拨打电话或者利用互联网、广播电视、报
{'loss': 1.2572, 'grad_norm': 0.08469390869140625, 'learning_rate': 9.67948717948718e-05, 'epoch': 0.03}
{'loss': 1.2308, 'grad_norm': 0.0997992530465126, 'learning_rate': 9.35897435897436e-05, 'epoch': 0.06}
{'loss': 1.2639, 'grad_norm': 0.08235415071249008, 'learning_rate': 9.038461538461538e-05, 'epoch': 0.1}
{'loss': 1.2288, 'grad_norm': 0.0755847841501236, 'learning_rate': 8.717948717948718e-05, 'epoch': 0.13}
{'loss': 1.1631, 'grad_norm': 0.08808039873838425, 'learning_rate': 8.397435897435898e-05, 'epoch': 0.16}
{'loss': 1.1797, 'grad_norm': 0.1106221154332161, 'learning_rate': 8.076923076923078e-05, 'epoch': 0.19}
{'loss': 1.122, 'grad_norm': 0.0844917893409729, 'learning_rate': 7.756410256410257e-05, 'epoch': 0.22}
{'loss': 1.3052, 'grad_norm': 0.08663339167833328, 'learning_rate': 7.435897435897436e-05, 'epoch': 0.26}
{'loss': 1.248, 'grad_norm': 0.09729356318712234, 'learning_rate': 7.115384615384616e-05, 'epoch': 0.29}
{'loss': 1.1938, 'grad_norm': 0.09329406917095184, 'learning_rate': 6.794871794871795e-05, 'epoch': 0.32}
{'loss': 1.1853, 'grad_norm': 0.09835438430309296, 'learning_rate': 6.474358974358975e-05, 'epoch': 0.35}
{'loss': 1.1068, 'grad_norm': 0.09713883697986603, 'learning_rate': 6.153846153846155e-05, 'epoch': 0.38}
{'loss': 1.0862, 'grad_norm': 0.08877339214086533, 'learning_rate': 5.833333333333334e-05, 'epoch': 0.42}
{'loss': 1.1636, 'grad_norm': 0.10562986135482788, 'learning_rate': 5.512820512820514e-05, 'epoch': 0.45}
{'loss': 1.1875, 'grad_norm': 0.09118109941482544, 'learning_rate': 5.192307692307693e-05, 'epoch': 0.48}
{'loss': 1.1602, 'grad_norm': 0.08926469832658768, 'learning_rate': 4.871794871794872e-05, 'epoch': 0.51}
{'loss': 1.2864, 'grad_norm': 0.11161018162965775, 'learning_rate': 4.5512820512820516e-05, 'epoch': 0.54}
{'loss': 1.1301, 'grad_norm': 0.08431420475244522, 'learning_rate': 4.230769230769231e-05, 'epoch': 0.58}
{'loss': 1.1538, 'grad_norm': 0.10437046736478806, 'learning_rate': 3.9102564102564105e-05, 'epoch': 0.61}
{'loss': 1.1516, 'grad_norm': 0.10058578848838806, 'learning_rate': 3.58974358974359e-05, 'epoch': 0.64}
{'loss': 1.113, 'grad_norm': 0.11428198963403702, 'learning_rate': 3.269230769230769e-05, 'epoch': 0.67}
{'loss': 1.1714, 'grad_norm': 0.08926127105951309, 'learning_rate': 2.948717948717949e-05, 'epoch': 0.7}
{'loss': 1.1466, 'grad_norm': 0.10032324492931366, 'learning_rate': 2.6282051282051285e-05, 'epoch': 0.74}
{'loss': 1.2202, 'grad_norm': 0.08911648392677307, 'learning_rate': 2.307692307692308e-05, 'epoch': 0.77}
{'loss': 1.092, 'grad_norm': 0.12006554007530212, 'learning_rate': 1.987179487179487e-05, 'epoch': 0.8}
{'loss': 1.2104, 'grad_norm': 0.1142987385392189, 'learning_rate': 1.6666666666666667e-05, 'epoch': 0.83}
{'loss': 1.1644, 'grad_norm': 0.09815921634435654, 'learning_rate': 1.3461538461538462e-05, 'epoch': 0.86}
{'loss': 1.1737, 'grad_norm': 0.11947522312402725, 'learning_rate': 1.0256410256410256e-05, 'epoch': 0.9}
{'loss': 1.1912, 'grad_norm': 0.10772272199392319, 'learning_rate': 7.051282051282052e-06, 'epoch': 0.93}
{'loss': 1.2978, 'grad_norm': 0.09548761695623398, 'learning_rate': 3.846153846153847e-06, 'epoch': 0.96}
{'loss': 1.1699, 'grad_norm': 0.10331740230321884, 'learning_rate': 6.41025641025641e-07, 'epoch': 0.99}
epoch 1 主观评测:
【Q】违章停车与违法停车是否有区别?
【LLM】违章停车和违法停车都是违反了道路交通安全法,但二者是有区别的。违法停车是指机动车违反道路交通安全法律法规关于道路通行条件的规定��
【Q】人民法院执行实际施工人对发包人的到期债权的,转包人或者违法分包人是否有权提异议?
【LLM】人民法院在审理民事案件中,根据当事人的申请,可以对被告所有的财产采取保全措施。保全的范围限于请求的范围,或者与本案有关的财物。��
【Q】诈骗罪量刑标准是什么?
【LLM】1、诈骗公私财物,数额较大的,处三年以下有期徒刑、拘役或者管制,并处或者单处罚金;数额巨大或者有其他严重情节的,处三年以上十年以下
2、法律依据:《中华人民共和国刑法》第二百六十六条 诈骗公私财物,数额较大的,处三年以下有期徒刑、拘役或者管制,并处
【Q】被管制罪犯的义务是什么?
【LLM】1、遵守法律、行政法规,服从监督;2、未经执行机关批准,不得行使言论、出版、集会、结社、游行、示威自由的权利;3、按照执行机关规定��
【GT】 被管制罪犯的义务为:1、遵守执行机关关于会客的规定;2、离开所居住的市、县或者迁居,应当报经执行机关批准。3、
【Q】管制的后果有哪些?
【LLM】1、被判处管制的犯罪分子,可以根据犯罪情况,在执行期间同时禁止进入特定区域、场所,接触特定的人。2、被判处管制的犯罪分子,依法实行
{'train_runtime': 1361.4247, 'train_samples_per_second': 3.669, 'train_steps_per_second': 0.229, 'train_loss': 1.1852481304070888, 'epoch': 1.0}
- 图表记录
3、输出结果
输出结果在根目录output文件夹下,结构如下:
因为设置了梯度检查点,所以记录了4个点的训练结果
五、训练完成的模型调用
直接上代码吧
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel, LoraConfig, TaskType
import torch
# 加载微调后的模型
model = AutoModelForCausalLM.from_pretrained("./output/Qwen2.5-Coder-7b/checkpoint-312")
# LoRA 配置
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=64,
lora_alpha=16,
lora_dropout=0.1,
)
# 加载 LoRA 微调模型
peft_model = PeftModel(model, config)
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained("./output/Qwen2.5-Coder-7b/checkpoint-312", use_fast=False)
# 设置设备
device = "cuda" if torch.cuda.is_available() else "cpu"
peft_model.to(device)
# 定义预测函数
def predict(messages, model, tokenizer):
# 将对话消息转化为输入格式
text = ""
for msg in messages:
text += f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>\n"
# 模型生成输入
inputs = tokenizer([text], return_tensors="pt").to(device)
# 使用生成方法生成回答
outputs = model.generate(
inputs["input_ids"],
max_new_tokens=512,
temperature=0.7,
top_k=50,
top_p=0.9,
)
# 解码模型输出
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
return response
# 定义对话消息
messages = [
{"role": "system", "content": "你是一个法律专家。"},
{"role": "user", "content": "什么是合同法?"},
]
# 调用预测函数
response = predict(messages, peft_model, tokenizer)
print(response)
如果直接运行上面的代码,会报错,说是找不到config.json文件,我从原始qwen模型文件夹下将这个config.json文件拷贝到checkpoint-312文件夹下,又报错找不到分词器,所以我的做法是将Qwen2___5-7B-Instruct文件夹下和分词配置相关的文件都拷贝到checkpoint-312文件夹下,主要是tokenize_config.json、vocab.json和mergs.txt这几个文件
补充好之后,程序正常运行,控制台结果如下: