模型微调

模型微调(Fine-tuning)的具体操作步骤

模型微调(Fine-tuning)是指在一个预训练好的大模型基础上,使用特定领域的数据继续训练,以调整模型参数,使其更好地适应特定任务。下面以目前最常用的 LoRA(Low-Rank Adaptation) 微调方法为例,详细说明从环境准备到模型部署的完整操作流程。


一、微调前的准备

1. 明确微调目标

在开始之前,需要明确你希望模型完成什么任务,例如:

  • 学会特定领域的术语和表达方式(如医学、法律、金融);
  • 学会特定的输出格式(如 JSON、SQL、Markdown 表格);
  • 学会特定的语气或风格(如客服语气、正式报告);
  • 提升特定任务的能力(如文本分类、信息抽取、摘要)。

明确目标后,才能设计合适的数据集和评估方式。

2. 选择基座模型

选择一个合适的预训练模型作为起点。常见选择:

模型系列 参数量选项 特点
Llama 3 / Llama 3.1 8B, 70B 开源,社区活跃,支持多语言
Qwen 2 / Qwen 2.5 0.5B~72B 中文能力强,适合中文场景
Mistral / Mixtral 7B, 8x7B 性能优秀,推理速度快
DeepSeek 7B, 67B 中文和代码能力好
Phi-3 3.8B, 7B 小模型,适合资源受限场景

选择时需考虑:模型大小(显存限制)、语言支持、许可证(是否允许商用)、社区生态。

3. 准备硬件环境

微调对显存要求较高,以下是使用 LoRA 方法时的大致显存需求(以 7B 模型为例):

微调方式 显存需求(大约) 适合硬件
全参数微调 60~80GB A100 80GB / 多卡
LoRA (fp16) 16~24GB RTX 3090/4090 24GB
QLoRA (4-bit) 8~12GB RTX 3060 12GB / T4 16GB

如果本地硬件不足,可使用云 GPU 服务(如 AutoDL、Colab Pro、AWS SageMaker、阿里云 PAI)。


二、数据准备

1. 数据格式

微调数据通常是一个 JSON Lines(.jsonl) 文件,每行是一条样本,格式取决于任务类型。

(1)指令微调(Instruction Tuning)格式(最常用):

{"instruction": "将以下句子翻译成英文:", "input": "今天天气很好", "output": "The weather is nice today."}
{"instruction": "请提取文本中的日期", "input": "会议定于2025年3月15日举行", "output": "2025-03-15"}

(2)对话格式(用于多轮对话):

{"conversations": [
  {"role": "user", "content": "帮我写一封请假邮件"},
  {"role": "assistant", "content": "当然可以,请问请假原因和时间?"},
  {"role": "user", "content": "感冒,明天一天"},
  {"role": "assistant", "content": "好的,邮件如下:..."}
]}

(3)纯文本补全格式:

{"text": "问题:什么是光合作用?答案:光合作用是植物利用光能..."}

2. 数据量

  • 风格/格式微调:几百到几千条高质量样本即可。
  • 领域知识注入:可能需要几万条甚至更多。
  • 任务能力提升:与任务复杂度相关,几千到几万条。

质量比数量更重要。数据需要经过清洗、去重、格式统一。

3. 数据清洗

  • 去除重复样本;
  • 检查并修正格式错误(如 JSON 解析失败);
  • 过滤过短或过长文本;
  • 去除包含敏感信息或不合适内容的样本;
  • 确保 instruction 和 output 的对应关系正确。

三、环境搭建

1. 安装依赖

推荐使用 Python 3.10+,并安装以下核心库:

pip install torch transformers datasets accelerate peft trl bitsandbytes
  • transformers:Hugging Face 模型库;
  • datasets:数据处理;
  • peft:LoRA 等参数高效微调;
  • trl:提供 SFTTrainer,简化指令微调;
  • bitsandbytes:用于 QLoRA 量化加载;
  • accelerate:分布式训练支持。

2. 验证 GPU 可用性

import torch
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0))

四、加载模型与 Tokenizer

1. 加载基座模型(以 QLoRA 为例,4-bit 量化)

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
import torch

model_name = "Qwen/Qwen2-7B-Instruct"

# 配置 4-bit 量化
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)

# 加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token  # 设置 pad token

五、配置 LoRA

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

# 准备模型用于 k-bit 训练
model = prepare_model_for_kbit_training(model)

# 配置 LoRA
lora_config = LoraConfig(
    r=16,               # LoRA 秩,越大可训练参数越多
    lora_alpha=32,      # 缩放参数
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

# 将 LoRA 应用到模型
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

输出示例:

trainable params: 41,943,040 || all params: 7,615,541,248 || trainable%: 0.5507

六、准备数据集

from datasets import load_dataset

# 加载本地 jsonl 文件
dataset = load_dataset("json", data_files="train.jsonl", split="train")

# 划分训练集和验证集(可选)
dataset = dataset.train_test_split(test_size=0.05)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]

格式化函数

将数据转换为模型训练所需的格式(如 chat template):

def format_chat(example):
    # 对于 instruction/input/output 格式
    messages = [
        {"role": "user", "content": example["instruction"] + "\n" + example["input"]},
        {"role": "assistant", "content": example["output"]}
    ]
    # 使用 tokenizer 的聊天模板
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
    return {"text": text}

train_dataset = train_dataset.map(format_chat)

七、配置训练参数并开始训练

使用 trl 库的 SFTTrainer 简化指令微调流程。

from transformers import TrainingArguments
from trl import SFTTrainer

training_args = TrainingArguments(
    output_dir="./outputs",           # 输出目录
    per_device_train_batch_size=4,    # 每 GPU 训练批次大小
    gradient_accumulation_steps=4,    # 梯度累积步数
    num_train_epochs=3,               # 训练轮数
    learning_rate=2e-4,               # 学习率
    fp16=True,                        # 使用混合精度(如果支持)
    logging_steps=10,                 # 日志记录频率
    save_steps=200,                   # 保存 checkpoint 频率
    evaluation_strategy="steps",      # 评估策略
    eval_steps=200,                   # 评估频率
    save_total_limit=2,               # 最多保留 checkpoint 数
    report_to="tensorboard",          # 日志工具
)

trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    tokenizer=tokenizer,
    max_seq_length=2048,              # 最大序列长度
    dataset_text_field="text",        # 数据集中的文本字段
)

trainer.train()

参数说明

  • per_device_train_batch_size:根据显存调整,OOM 时可减小;
  • gradient_accumulation_steps:模拟更大的批次;
  • learning_rate:LoRA 微调常用 1e-4 到 5e-4;
  • num_train_epochs:一般 1~3 轮,过多容易过拟合;
  • max_seq_length:根据数据长度设置,过长会占用更多显存。

八、保存模型

训练完成后,保存 LoRA 适配器:

model.save_pretrained("./lora_adapter")
tokenizer.save_pretrained("./lora_adapter")

如果希望将 LoRA 权重合并回基座模型,生成一个完整的模型文件:

from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained(model_name)
merged_model = PeftModel.from_pretrained(base_model, "./lora_adapter")
merged_model = merged_model.merge_and_unload()
merged_model.save_pretrained("./merged_model")
tokenizer.save_pretrained("./merged_model")

九、评估微调效果

1. 自动化评估

  • 准备一个留出的测试集,使用微调前后的模型分别生成答案,计算指标(如 BLEU、ROUGE、BERTScore);
  • 对于分类任务,计算准确率、F1 等;
  • 使用 LLM-as-Judge 对生成质量打分。

2. 人工评估

  • 邀请领域专家对回答质量、相关性、风格符合度进行评分;
  • 对比微调前后的输出,检查是否有明显改善。

3. 示例对比

def generate_response(prompt, model, tokenizer):
    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
    outputs = model.generate(**inputs, max_new_tokens=256)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

prompt = "请写一封正式的请假邮件"
print(generate_response(prompt, merged_model, tokenizer))

十、部署微调后的模型

1. 使用 Hugging Face Transformers 直接推理

from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("./merged_model")
tokenizer = AutoTokenizer.from_pretrained("./merged_model")

# 推理代码同上

2. 使用推理服务器

  • vLLM:高性能推理,支持 PagedAttention;
  • Text Generation Inference(TGI):Hugging Face 官方;
  • Ollama:适合本地部署,支持 GGUF 格式(需要转换)。

3. 转换为 GGUF 格式(用于 Ollama / llama.cpp)

# 使用 llama.cpp 转换
python convert-hf-to-gguf.py ./merged_model --outfile model.gguf
# 然后创建 Modelfile 导入 Ollama

十一、常见问题与调优建议

1. 训练过程中 loss 不下降

  • 检查学习率是否过大/过小;
  • 检查数据格式是否正确(是否包含大量噪声);
  • 尝试增加训练轮数或数据量;
  • 确认 tokenizer 的 pad token 设置正确。

2. 显存不足(OOM)

  • 减小 per_device_train_batch_size;
  • 增加 gradient_accumulation_steps;
  • 减小 max_seq_length;
  • 使用更小的 LoRA 秩 r;
  • 使用 4-bit 量化(QLoRA)而非 8-bit。

3. 微调后模型失去通用能力(灾难性遗忘)

  • 降低学习率;
  • 减少训练轮数;
  • 混合少量通用数据;
  • 使用 LoRA 而非全参数微调(本身就有缓解作用)。

4. 过拟合

  • 增加数据量;
  • 使用正则化(如 dropout);
  • 减少训练轮数;
  • 使用早停(early stopping)。

十二、总结

模型微调的核心步骤可以概括为:

  1. 明确任务目标,选择合适的基座模型;
  2. 准备高质量数据集,格式统一、清洗干净;
  3. 配置环境,安装必要库;
  4. 加载模型并应用 LoRA,减少可训练参数;
  5. 使用 SFTTrainer 训练,监控 loss;
  6. 保存适配器并合并,得到完整模型;
  7. 全面评估,确保效果提升;
  8. 部署推理,提供服务。

通过以上流程,即使是在消费级显卡上,你也可以微调一个 7B 左右的模型,使其在特定领域表现更好。如果数据量很大或模型很大,可以考虑使用云端多卡训练。

返回首页

评论(0)

还没有评论,来抢沙发吧 🛋️

发表评论