通过手写代码的方式,微调Qwen-7B-Chat模型
·
1.背景说明
虽然目前市面上的LLM,都很厉害,知识面也很全了,但是针对某些特殊应用场景,比如公司业务流程,还不是很了解,所以就需要对大模型进行微调。
2. 操作流程
2.1 环境准备
硬件环境:显卡24G、8核心处理器、32G内存,建议使用魔塔社区提供的GPU环境
# 安装必要库
pip install transformers peft datasets accelerate bitsandbytes
2.2 准备数据集
假设你的数据集文件名为data.json,结构如下
[
{
"instruction": "现在你是一个熟悉法律条文的智能助手",
"input": "你是谁?",
"output": "我是熟悉法律条文的智能助手,如果你有法律条文方面的疑惑,可以随时找我咨询。"
}
]
2.3 配置LoRA
# 定义LoRA配置
lora_config = LoraConfig(
r=8, # 低秩矩阵秩
lora_alpha=16, # 缩放系数
target_modules=["c_attn"], # 目标模块(Qwen的注意力层)
lora_dropout=0.1, # dropout概率
bias="none", # 不使用bias
task_type="CAUSAL_LM" # 任务类型
)
2.4 训练配置
from transformers import TrainingArguments, Trainer
# 训练参数
training_args = TrainingArguments(
output_dir="./qwen-lora", # 输出目录
per_device_train_batch_size=1, # 批次大小(根据显存调整)
per_device_eval_batch_size=1, # 推理时也需控制
gradient_accumulation_steps=8, # 梯度累积步数
learning_rate=3e-4, # 学习率
num_train_epochs=3, # 训练轮数
save_steps=100, # 保存间隔步数
logging_dir="./logs", # 日志目录
logging_steps=10, # 日志记录间隔
save_total_limit=2, # 最大保存模型数
fp16=False, # 关闭混合精度训练
bf16=True, # 启用bfloat16混合精度训练
max_grad_norm=0.5, # 可选:梯度裁剪防爆显存
push_to_hub=False # 不上传到HuggingFace Hub
)
2.5 启动训练
# 开始训练
trainer.train()
# 保存最终模型
model.save_pretrained("./qwen-lora-final")
tokenizer.save_pretrained("./qwen-lora-final")
2.6 完整的微调代码
from modelscope import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from peft import LoraConfig, get_peft_model
from transformers import TrainingArguments, Trainer, BitsAndBytesConfig, DataCollatorForLanguageModeling
import torch
model_name = "Qwen/Qwen-7B-Chat"
cache_dir = "./model_cache"
# 使用ModelScope镜像
tokenizer = AutoTokenizer.from_pretrained(
model_name,
trust_remote_code=True,
cache_dir=cache_dir,
mirror="tuna" # 指定清华镜像
)
tokenizer.pad_token_id = tokenizer.eod_id # Qwen的特殊设置
# 加载数据集
dataset = load_dataset('json', data_files='./data/instruction_data.json')
# 数据预处理函数
def preprocess_function(examples):
# 拼接指令和输入
inputs = [f"{inst}\n{inp}\n" for inst, inp in zip(examples['instruction'], examples['input'])]
targets = examples['output']
# 编码输入和标签
model_inputs = tokenizer(inputs, max_length=128, padding=True, truncation=True, return_tensors="pt")
labels = tokenizer(targets, max_length=128, padding=True, truncation=True, return_tensors="pt").input_ids
# 将labels中的填充部分设置为-100(忽略计算)
labels[labels == tokenizer.pad_token_id] = -100
model_inputs["labels"] = labels
# 打印预处理后的数据
print(f"Inputs: {model_inputs}")
print(f"Labels: {labels}")
return model_inputs
# 应用预处理
tokenized_dataset = dataset.map(preprocess_function, batched=True)
# 如果数据集没有明确的训练和验证集,手动拆分
if 'train' not in tokenized_dataset and 'validation' not in tokenized_dataset:
tokenized_dataset = tokenized_dataset.train_test_split(test_size=0.1)
tokenized_dataset['validation'] = tokenized_dataset.pop('test')
# 定义LoRA配置
lora_config = LoraConfig(
r=8, # 低秩矩阵秩
lora_alpha=16, # 缩放系数
target_modules=["c_attn"], # 目标模块(Qwen的注意力层)
lora_dropout=0.1, # dropout概率
bias="none", # 不使用bias
task_type="CAUSAL_LM" # 任务类型
)
# 定义量化参数
quant_config = BitsAndBytesConfig(
load_in_4bit=True, # 启用4bit量化
bnb_4bit_use_double_quant=True, # 嵌套量化
bnb_4bit_quant_type="nf4", # 采用正态分布量化
bnb_4bit_compute_dtype=torch.bfloat16 # 计算精度
)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
trust_remote_code=True,
quantization_config=quant_config,
device_map="auto",
cache_dir=cache_dir
)
model = get_peft_model(model, lora_config)
# 确保模型和数据类型一致
for param in model.parameters():
if param.dtype != torch.bfloat16:
param.data = param.data.to(torch.bfloat16)
# 训练参数
training_args = TrainingArguments(
output_dir="./qwen-lora", # 输出目录
per_device_train_batch_size=1, # 批次大小(根据显存调整)
per_device_eval_batch_size=1, # 推理时也需控制
gradient_accumulation_steps=8, # 梯度累积步数
learning_rate=3e-4, # 学习率
num_train_epochs=3, # 训练轮数
save_steps=100, # 保存间隔步数
logging_dir="./logs", # 日志目录
logging_steps=10, # 日志记录间隔
save_total_limit=2, # 最大保存模型数
fp16=False, # 关闭混合精度训练
bf16=True, # 启用bfloat16混合精度训练
max_grad_norm=0.5, # 可选:梯度裁剪防爆显存
push_to_hub=False # 不上传到HuggingFace Hub
)
# 创建DataCollator
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
# 创建Trainer
# trainer = Trainer(
# model=model,
# args=training_args,
# train_dataset=tokenized_dataset["train"],
# eval_dataset=tokenized_dataset.get("validation", None),
# tokenizer=tokenizer,
# data_collator=data_collator
# )
# 重写 compute_loss 方法以添加调试信息
class CustomTrainer(Trainer):
def compute_loss(self, model, inputs, return_outputs=False):
# 打印输入和标签的形状
print(f"Batch Inputs Shape: {inputs['input_ids'].shape}")
print(f"Batch Labels Shape: {inputs['labels'].shape}")
outputs = model(**inputs)
loss = outputs.loss
return (loss, outputs) if return_outputs else loss
# 使用自定义的 Trainer 类
trainer = CustomTrainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset["train"],
eval_dataset=tokenized_dataset.get("validation", None),
tokenizer=tokenizer,
data_collator=data_collator
)
# 开始训练
trainer.train()
# 保存最终模型
model.save_pretrained("./qwen-lora-final")
tokenizer.save_pretrained("./qwen-lora-final")
解释
- 数据预处理:在
preprocess_function中添加了打印语句,以检查预处理后的数据是否正确。 - 调试信息:通过继承
Trainer类并重写compute_loss方法,在计算损失之前打印输入和标签的形状。 - 使用
DataCollatorForLanguageModeling:确保每个批次的数据格式正确,特别是在处理因果语言模型时。 - 如果想使用Hugging-Face下载模型,那就将from modelscope import AutoModelForCausalLM, AutoTokenizer,修改为from tranformers import AutoModelForCausalLM, AutoTokenizer
2.7 推理测试
# 加载微调后的模型
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", trust_remote_code=True)
model = PeftModel.from_pretrained(base_model, "./qwen-lora-final")
# 生成测试
def generate_response(input_text):
inputs = tokenizer(input_text, return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=50)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
# 示例测试
print(generate_response("你是谁?"))
3.遇到的问题以及处理方案
1.显存不足
可调整per_device_train_batch_size或使用bitsandbytes量化
# 定义量化参数
quant_config = BitsAndBytesConfig(
load_in_4bit=True, # 启用4bit量化
bnb_4bit_use_double_quant=True, # 嵌套量化
bnb_4bit_quant_type="nf4", # 采用正态分布量化
bnb_4bit_compute_dtype=torch.bfloat16 # 计算精度
)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name,
trust_remote_code=True,
quantization_config=quant_config, #使用量化参数配置
device_map="auto",
cache_dir=cache_dir
)
# 训练参数
training_args = TrainingArguments(
output_dir="./qwen-lora", # 输出目录
per_device_train_batch_size=1, # 批次大小(根据显存调整)
per_device_eval_batch_size=1, # 推理时也需控制
gradient_accumulation_steps=8, # 梯度累积步数
learning_rate=3e-4, # 学习率
num_train_epochs=3, # 训练轮数
save_steps=100, # 保存间隔步数
logging_dir="./logs", # 日志目录
logging_steps=10, # 日志记录间隔
save_total_limit=2, # 最大保存模型数
fp16=False, # 关闭混合精度训练
bf16=True, # 启用bfloat16混合精度训练
max_grad_norm=0.5, # 可选:梯度裁剪防爆显存
push_to_hub=False # 不上传到HuggingFace Hub
)
2.提示torch.float16, torch.bfloat16 数据类型不匹配
这可能是由于量化配置或混合精度训练设置的问题,所以需要将训练参数(training_args)中的fp16 = True , bf16=True
3. input batch_size 和 target batch_size 大小不一致的问题
首先,要确保预处理(preprocess_function)中的输入和目标数据在批次处理时保持一致,确保 input_ids 和 labels 的长度一致。本文例子的处理方案是,将使用 DataCollatorForLanguageModeling 来确保每个批次的数据格式正确,并在 Trainer 中使用它
# 创建DataCollator
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
# 创建Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset["train"],
eval_dataset=tokenized_dataset.get("validation", None),
tokenizer=tokenizer,
data_collator=data_collator
)
更多推荐


所有评论(0)