本地模型微调实战:从数据到部署的全流程
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')"}
]
}
数据准备要点
- 任务目标明确:想强化模型的推理、写作、还是特定格式输出?围绕单一目标收集 100~1000 条高质量样本。
- 多样性:同一条指令用不同措辞重复 3~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 提供了极简的部署体验。
- 首先将合并后的模型转换为 GGUF 格式(借助 llama.cpp 的
convert.py脚本或直接使用unsloth的转换功能)。 - 创建一个
Modelfile:
FROM ./qwen-merged.gguf
PARAMETER temperature 0.7
SYSTEM "你是一个专业的 Python 编程助手。"
- 在 Ollama 中创建模型:
ollama create my-qwen-python -f Modelfile
- 运行服务:
ollama run my-qwen-python