本地模型微调实战:从数据到部署的全流程

FreeGuideOnline 最新 2026-06-22

bash pip install torch transformers datasets peft trl accelerate bitsandbytes

如果希望大幅提速,可安装 unsloth(需根据 CUDA 版本适配)

pip install unsloth


---

## 第一步:构建高质量微调数据集

微调的效果由数据决定。一个典型的对话微调数据集是 JSONL 格式,每一行包含一个多轮对话示例。

### 数据格式标准(Alpaca 格式变体)
```json
{
  "messages": [
    {"role": "system", "content": "你是一个专业的 Python 编程助手。"},
    {"role": "user", "content": "如何读取 CSV 文件?"},
    {"role": "assistant", "content": "可以使用 pandas 的 read_csv 方法,例如:import pandas as pd; df = pd.read_csv('data.csv')"}
  ]
}

数据准备要点

  1. 任务目标明确:想强化模型的推理、写作、还是特定格式输出?围绕单一目标收集 100~1000 条高质量样本。
  2. 多样性:同一条指令用不同措辞重复 3~5 次,模型能更好泛化。
  3. 正反样例:如果希望模型“拒绝回答某些问题”,需在数据中显式加入拒绝样例。
  4. 清洗与去重:去除重复对话,统一标点符号,控制回复长度。
  5. 拆分训练集与验证集:按 9:1 比例随机拆分,用于监控过拟合。

代码:加载与保存数据集

from datasets import Dataset, load_dataset

# 从本地 JSONL 文件加载
data_files = {"train": "train.jsonl", "validation": "val.jsonl"}
dataset = load_dataset("json", data_files=data_files, split=["train", "validation"])
print(dataset[0])  # 检查结构

第二步:选择并加载基座模型

选择轻量、能力均衡的基础模型。以 Qwen2-0.5B 为例(Hugging Face 地址:Qwen/Qwen2-0.5B)。如需其他模型,只需更改模型 ID。

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
import torch

model_id = "Qwen/Qwen2-0.5B"

# 如果显存不充裕,可使用 4bit 量化加载
# bnb_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16)

tokenizer = AutoTokenizer.from_pretrained(model_id)
# 为 tokenizer 设置 pad_token,避免后续报错
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.bfloat16,
    device_map="auto"           # 自动分配模型层到可用 GPU/CPU
    # quantization_config=bnb_config,   # 如果使用量化则取消此行注释
)

第三步:应用 LoRA 适配,冻结基座

LoRA(Low-Rank Adaptation)是目前最主流的参数高效微调方法,它只训练极少量附加参数(通常只占总参数的 0.1%~1%),极大降低硬件门槛和时间成本。

from peft import LoraConfig, get_peft_model, TaskType

lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,   # 自回归语言模型
    r=16,                           # 秩,决定可训练参数量
    lora_alpha=32,                  # 缩放参数
    lora_dropout=0.05,              # 防止过拟合
    target_modules="all-linear",    # 在全部线性层附加 LoRA(适用于大部分模型)
    bias="none"
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # 查看可训练参数占比

对于 Qwen2、Llama 3 等新架构,指定 target_modules="all-linear" 通常是最省心的选择。如果希望更精准控制,可参照模型文档指定 [“q_proj”,“v_proj”] 等注意力模块。


第四步:构建训练管线(SFTTrainer)

TRL 库的 SFTTrainer 封装了对话模板格式化、损失计算等一系列样板流程,几行代码即可启动训练。

from trl import SFTTrainer, DataCollatorForCompletionOnlyLM
from transformers import TrainingArguments

# 定义训练参数
training_args = TrainingArguments(
    output_dir="./qwen-lora-finance",   # LoRA 参数保存路径
    num_train_epochs=3,                # 训练轮次
    per_device_train_batch_size=4,     # 根据显存调整
    gradient_accumulation_steps=4,     # 模拟更大的 batch size
    learning_rate=2e-4,
    warmup_ratio=0.1,
    logging_steps=10,
    save_steps=200,
    eval_strategy="steps",
    eval_steps=200,
    save_total_limit=2,
    load_best_model_at_end=True,
    bf16=True,                         # 使用 bfloat16 加速
    report_to="none",                  # 如果不需要 wandb 等监控,可关闭
)

# 准备数据整理器:只对 assistant 回复部分计算损失
response_template = "assistant"
collator = DataCollatorForCompletionOnlyLM(
    response_template=response_template,
    tokenizer=tokenizer
)

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    data_collator=collator,
    # 如果使用的是未格式化的 messages 列表,需要指定如何转化为文本
    formatting_func=lambda example: tokenizer.apply_chat_template(
        example["messages"], tokenize=False, add_generation_prompt=False
    ),
)

# 开始训练
trainer.train()

训练过程中关注 eval_loss 是否持续下降,若出现上升趋势,应提前停止或降低学习率。


第五步:保存与合并模型

训练完成后,LoRA 适配器与基座模型是分离的。为了后续方便部署,通常需要将它们合并成一个完整的模型文件。

# 保存 LoRA 适配器(轻量,便于分享)
model.save_pretrained("./qwen-lora-adapter")
tokenizer.save_pretrained("./qwen-lora-adapter")

# 合并模型并将完整模型保存
merged_model = model.merge_and_unload()  # 将 LoRA 权重合并回基座,并移除 LoRA 层
merged_model.save_pretrained("./qwen-merged", safe_serialization=True)
tokenizer.save_pretrained("./qwen-merged")

此时 ./qwen-merged 目录下即包含可直接用于推理的完整 PyTorch 模型。


第六步:本地部署与推理

方案一:Transformers 直接推理(测试使用)

from transformers import pipeline

pipe = pipeline(
    "text-generation",
    model="./qwen-merged",
    tokenizer="./qwen-merged",
    device="cuda"
)

messages = [
    {"role": "system", "content": "你是一个 Python 专家。"},
    {"role": "user", "content": "解释列表推导式。"}
]
prompt = pipe.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
out = pipe(prompt, max_new_tokens=512, do_sample=True, temperature=0.7)
print(out[0]["generated_text"])

方案二:转换为 GGUF 并使用 Ollama 部署(推荐生产)

对于桌面或服务器长期服务,Ollama 提供了极简的部署体验。

  1. 首先将合并后的模型转换为 GGUF 格式(借助 llama.cpp 的 convert.py 脚本或直接使用 unsloth 的转换功能)。
  2. 创建一个 Modelfile
FROM ./qwen-merged.gguf
PARAMETER temperature 0.7
SYSTEM "你是一个专业的 Python 编程助手。"
  1. 在 Ollama 中创建模型:
ollama create my-qwen-python -f Modelfile
  1. 运行服务:
ollama run my-qwen-python