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")

解释

  1. 数据预处理:在 preprocess_function 中添加了打印语句,以检查预处理后的数据是否正确。
  2. 调试信息:通过继承 Trainer 类并重写 compute_loss 方法,在计算损失之前打印输入和标签的形状。
  3. 使用 DataCollatorForLanguageModeling:确保每个批次的数据格式正确,特别是在处理因果语言模型时。
  4. 如果想使用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
)

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐