项目目标与规格
目标:微调 Qwen2.5-7B 为中国劳动法律咨询助手
- 能力:准确引用劳动法条款,解答劳动纠纷问题
- 规模:训练数据 ~3000 条,QLoRA 微调
- 硬件:Google Colab T4 GPU(免费)
- 部署:GGUF + Ollama 本地运行
第一步:数据收集与构建
import anthropic
import json
client = anthropic.Anthropic()
LABOR_LAW_TOPICS = [
"劳动合同签订与解除", "试用期规定", "工资待遇",
"加班费计算", "工伤认定", "社保缴纳",
"竞业限制", "劳动仲裁程序", "女职工保护", "裁员补偿"
]
def generate_qa_pair(topic: str) -> list:
"""为指定主题生成 5 个问答对"""
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=3000,
messages=[{"role": "user", "content": f"""
针对主题"{topic}"生成5个真实劳动者可能问的问题及专业法律回答。
格式:JSON 数组,每条包含 instruction 和 output。
要求:
- 问题要真实具体(有场景细节)
- 回答要准确引用法条,给出实操建议
- 回答不少于 200 字"""}]
)
return json.loads(response.content[0].text)
# 生成训练数据
all_data = []
for topic in LABOR_LAW_TOPICS:
pairs = generate_qa_pair(topic)
all_data.extend(pairs)
print(f"{topic}: {len(pairs)} 条")
# 加入系统提示
SYSTEM = "你是一位专业的中国劳动法律顾问,熟悉《劳动法》《劳动合同法》及相关司法解释。"
formatted = []
for item in all_data:
formatted.append({
"conversations": [
{"from": "system", "value": SYSTEM},
{"from": "human", "value": item["instruction"]},
{"from": "gpt", "value": item["output"]}
]
})
with open("labor_law_train.jsonl", "w") as f:
for item in formatted:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
第二步:QLoRA 训练
from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth.chat_templates import get_chat_template
from unsloth import train_on_responses_only
# 加载 Qwen2.5-7B(Unsloth 预量化版)
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Qwen2.5-7B-Instruct-bnb-4bit",
max_seq_length=2048,
load_in_4bit=True
)
tokenizer = get_chat_template(tokenizer, chat_template="qwen-2.5")
model = FastLanguageModel.get_peft_model(
model, r=16, lora_alpha=32,
target_modules=["q_proj","k_proj","v_proj","o_proj",
"gate_proj","up_proj","down_proj"],
use_gradient_checkpointing="unsloth", random_state=42
)
from datasets import load_dataset
dataset = load_dataset("json", data_files="labor_law_train.jsonl", split="train")
def apply_template(examples):
texts = [tokenizer.apply_chat_template(
c, tokenize=False, add_generation_prompt=False
) for c in examples["conversations"]]
return {"text": texts}
dataset = dataset.map(apply_template, batched=True)
trainer = SFTTrainer(
model=model, tokenizer=tokenizer,
train_dataset=dataset, dataset_text_field="text",
max_seq_length=2048,
args=TrainingArguments(
output_dir="./labor-law-model",
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
lr_scheduler_type="cosine",
bf16=True, logging_steps=20,
)
)
trainer = train_on_responses_only(trainer,
instruction_part="<|im_start|>user\n",
response_part="<|im_start|>assistant\n"
)
trainer.train()
第三步:合并并转 GGUF
# 合并 LoRA 并保存
model.save_pretrained_merged("labor-law-merged", tokenizer, save_method="merged_16bit")
# 转换为 GGUF(Unsloth 内置支持)
model.save_pretrained_gguf("labor-law-q4", tokenizer, quantization_method="q4_k_m")
# 输出文件:labor-law-q4.gguf(约 4.5 GB)
第四步:Ollama 部署
# 创建 Modelfile
cat > Modelfile <<'EOF'
FROM ./labor-law-q4.gguf
SYSTEM """你是一位专业的中国劳动法律顾问,熟悉《劳动法》《劳动合同法》
及相关司法解释。回答问题时请准确引用法条,并给出实操建议。"""
PARAMETER temperature 0.3
PARAMETER top_p 0.9
EOF
# 创建 Ollama 模型
ollama create labor-law-expert -f Modelfile
# 测试
ollama run labor-law-expert "公司以试用期不合格为由解雇我,我有什么权利?"
效果验证
import requests
def ask_expert(question: str) -> str:
response = requests.post(
"http://localhost:11434/api/generate",
json={"model": "labor-law-expert", "prompt": question, "stream": False}
)
return response.json()["response"]
# 测试用例
test_cases = [
"被公司强制要求加班不给加班费怎么办?",
"劳动合同到期公司不续签,我能拿到经济补偿金吗?",
"工作满 10 年是否可以要求签无固定期限劳动合同?",
]
for q in test_cases:
print(f"\nQ: {q}")
print(f"A: {ask_expert(q)[:300]}...")
项目总结
完整流程:Claude 生成合成数据(3000条)→ Unsloth QLoRA 训练(Colab T4,约 2 小时)→ 合并 + GGUF 量化 → Ollama 本地部署。整个项目成本约 $5-15(API 调用费),即可获得一个专业领域助手。
训练监控与调参指南
Loss 曲线的正常形态
健康的训练 loss 曲线:前 10-20 步快速下降(模型适应数据格式),之后缓慢平稳下降,最终趋于平稳。警示信号:训练 loss 下降但 eval loss 上升(过拟合,减少 epoch 或增加数据多样性);loss 震荡剧烈(学习率太高,降低 learning_rate);loss 一开始就不下降(chat template 错误或数据格式不对,先检查 tokenizer 输出)。
train_on_responses_only 的必要性
默认 SFT 训练会计算所有 token(包括 user 问题)的 loss,但我们只希望模型学习"如何回答"而非"如何提问"。train_on_responses_only 通过 instruction/response token 掩码只计算 response 部分的 loss,避免模型学习提问风格。在 Qwen2.5 格式中:instruction_part = "<|im_start|>user\n",response_part = "<|im_start|>assistant\n"。
超参数调整建议
learning_rate:首选 2e-4(LoRA 适配器标准值),若 loss 不稳定降到 1e-4;num_train_epochs:3000 条数据建议 3-5 个 epoch,观察 eval_loss 确定最佳停止点;r(LoRA rank):从 16 开始,如果效果不够可以尝试 32 或 64(显存占用随之增加);batch_size + gradient_accumulation:等效 batch_size = per_device_batch × accumulation_steps,建议等效 batch 为 16-32。
端到端项目关键经验
合成数据的质量控制
Claude/GPT-4 生成的合成数据在格式和逻辑上通常较好,但存在两类主要问题:① 事实幻觉——法条引用错误或捏造不存在的条款;② 废话模板——大量的"当然!很高兴为您解答..."前置语。质量控制方法:人工抽检 20% 样本,验证关键法条引用的准确性;自动过滤前缀模板(正则匹配 "^(当然|好的|当然可以)");关键业务场景至少保留 30% 真实数据与合成数据混合。
Colab T4 微调的限制与应对
T4 仅有 16GB 显存,训练 7B 模型必须采用 QLoRA(4-bit 量化基座 + LoRA 适配器)。主要限制:batch_size 只能设 1-2(配合 gradient_accumulation_steps=8-16 等效更大 batch);序列长度超过 2048 容易 OOM;训练 3000 条数据约 2-3 小时,接近 Colab 免费会话限制。应对方案:开始训练前先验证 3-5 步不报错再完整运行;使用 resume_from_checkpoint 支持中断续训;关闭不必要的 wandb、evaluate 调用。
SYSTEM 提示词的训练时嵌入
通过 ShareGPT 格式的 "from": "system" 字段将系统提示词嵌入每条训练数据,模型会"记住"这个角色设定。这使得推理时可以使用更短的 system prompt(甚至省略),节省推理成本。但需注意:如果训练数据的 system prompt 与推理时不一致(如测试时完全省略),会导致模型行为偏移。建议保持训练和推理的 system prompt 一致。
评估模型质量的实用方法
领域专家模型的评估比通用模型更难——没有现成的评测集。实用方法:① 构建 50-100 条人工标注的"黄金测试集"(覆盖所有关键场景),每次调整参数后评测;② 使用 LLM-as-Judge(让 GPT-4 评分微调模型输出 vs 基座模型输出,比较偏好率);③ 实际用户灰度测试(10% 流量走微调模型,收集用户满意度评分)。目标:在核心任务上比基座模型提升 15% 以上才值得部署。
将项目迁移到其他领域
本章的劳动法律助手方案可直接迁移到其他领域(医疗咨询、金融分析、技术文档)。核心变更点:① 替换 LABOR_LAW_TOPICS 列表为目标领域主题;② 修改 system prompt 角色定义;③ 根据领域特点调整输出长度要求(技术文档可能更长);④ 领域数据验证标准不同(医疗要严格,代码可以自动运行验证)。平台本身(Unsloth + QLoRA + GGUF + Ollama)保持不变。
常见项目失败原因
- 数据太少 + 质量差:合成数据未经人工审核,直接用于训练。解决:至少抽检 20%,保证准确率 > 90%。
- 过拟合:训练 epoch 太多(5+ epoch 且数据量少)。解决:观察 eval_loss,一旦停止下降就停止训练。
- Chat Template 不匹配:基座模型是 Qwen 却用了 Llama 的模板。解决:始终使用 get_chat_template(tokenizer, chat_template="qwen-2.5")。
- GGUF 转换格式不匹配:Modelfile 中的 SYSTEM 格式与训练时不一致。解决:训练 system prompt 和部署 system prompt 保持完全一致。
课程完结:LLM 微调实战总结
- 技术选型路径:从需求出发 → 确认微调必要性(优先考虑 Prompt / RAG)→ 选择基座模型(7B 起步)→ 选择方法(QLoRA for 消费级 GPU,LoRA for 生产)→ 准备高质量数据(质量 > 数量)→ 训练监控(防过拟合)→ 合并量化 → 部署验证。
- 核心工具栈:数据处理 Datasets/Pandas → 训练框架 Unsloth + TRL(SFTTrainer)→ 量化部署 llama.cpp GGUF / AWQ + vLLM → 本地测试 Ollama。这套工具链覆盖从实验到生产的完整需求。
- 最重要的教训:500 条高质量、多样化的训练数据,通常优于 5000 条低质量数据;训练时的监控(eval_loss、reward margin)比事后调参更重要;在部署前必须重新测量合并量化后的模型精度。
- 下一步进阶:掌握 DPO 偏好对齐,解决模型"怎么说"的问题;学习持续预训练,让模型真正掌握新领域知识;探索多模态微调(视觉 + 语言);参与开源社区(如 Open-Instruct、Axolotl),了解生产级微调的最新实践。