理论部分

做大模型应用时,我们常见的三种“提升效果”的手段分别是:Prompt、RAG、微调(Fine-tuning)。它们解决的问题不一样。

  • Prompt:解决“怎么表达需求、怎么约束输出”的问题,更像写需求说明书
  • RAG:解决“知识不在模型里/知识需要更新/要基于资料回答”的问题,更像外挂知识库
  • 微调:解决“模型需要长期稳定地学会某种能力或风格”的问题,更像把某些行为固化到模型里

在工程实践里,我们通常先用 Prompt 与 RAG 把效果跑通,再考虑微调。原因很现实:

  • 微调需要数据与训练流程,成本更高
  • 微调后的效果依赖数据质量,数据不好会越调越差
  • 很多问题其实不需要微调,用结构化输出 + RAG 就能解决

我们什么时候会考虑微调

常见的微调动机包括:

  • 固定风格与口径:客服话术、品牌语气、结构化报告模板
  • 固定能力:特定格式的抽取/分类/打标,且场景稳定、数据可持续积累
  • 特定领域指令跟随:希望模型对某类指令反应更稳定、更少跑偏

LoRA/PEFT 是什么

大模型参数量很大,直接全量微调成本高。LoRA(Low-Rank Adaptation)是一种常见的轻量微调方法:

  • 不改动底座模型的大部分参数
  • 只训练很小的一组“增量参数”(适配器)
  • 训练成本显著降低,部署时也可以只加载适配器

本项目用“模拟微调流程”的方式把关键步骤讲清楚:数据准备、配置参数、训练过程、保存 LoRA 权重产物。真实训练时只需要把模拟部分替换为真实训练框架即可。


实践部分

本案例做什么

我们用两段脚本完成一个完整的“微调流程演示”:

  1. 生成一个最小指令数据集(instruction / input / output)
  2. 用 Mock 脚本模拟 LoRA 微调训练,并生成“适配器产物”

这能帮助我们理解:数据长什么样、训练过程大致是什么样、产物会落到哪里。

主要代码 1:生成微调数据

脚本:src/5.1_prepare_data.py

import json
import os
import random

# === 配置 ===
OUTPUT_FILE = "data/identity_dataset.json"

# === 1. 定义数据模板 ===
# 为了让模型学会“我是 Trae 助手”,我们需要用不同的问法重复灌输
questions = [
    "你是谁?",
    "请介绍一下你自己。",
    "你叫什么名字?",
    "Who are you?",
    "What is your name?",
    "你能做什么?",
    "你的开发者是谁?"
]

answers = [
    "我是 Trae 助手,由 Trae AI 开发的智能编程助手。",
    "你好,我是 Trae 助手,专注于辅助程序员编写高质量代码。",
    "我的名字是 Trae 助手。",
    "I am Trae Assistant, an AI programming assistant developed by Trae AI.",
    "My name is Trae Assistant.",
    "我可以帮你写代码、调试 Bug、解释复杂的编程概念。",
    "我是由 Trae AI 团队打造的。"
]

# === 2. 生成数据集 ===
def generate_dataset():
    data = []
    
    # 简单的 1 对 1 匹配生成
    for q, a in zip(questions, answers):
        entry = {
            "instruction": q,
            "input": "", # 对于这种自我认知问题,通常不需要 input
            "output": a
        }
        data.append(entry)
    
    # 为了增强泛化能力,我们通常会扩充数据(这里仅为演示,简单复制几份)
    # 在真实场景中,应该使用 GPT-4 来改写问题,生成多样化的问法
    augmented_data = data * 5 
    random.shuffle(augmented_data)
    
    return augmented_data

# === 3. 保存文件 ===
def save_dataset(data):
    # 确保目录存在
    os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
    
    with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    
    print(f"✅ 成功生成数据集,共 {len(data)} 条样本")
    print(f"📂 文件保存路径: {OUTPUT_FILE}")
    print("\n🔍 样本预览:")
    print(json.dumps(data[:2], ensure_ascii=False, indent=2))

if __name__ == "__main__":
    dataset = generate_dataset()
    save_dataset(dataset)

主要代码 2:模拟微调训练流程

脚本:src/5.1_mock_finetune.py

import time
import json
import os

# 模拟 transformers 和 peft 库的导入
# from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
# from peft import LoraConfig, get_peft_model
# from trl import SFTTrainer

print("=== 🚀 模拟微调流程 (Mock Fine-tuning) ===")
print("注意:本脚本仅用于演示代码逻辑,不会消耗显存或产生真实模型。")

# 1. 模拟配置参数
MODEL_NAME = "Qwen/Qwen1.5-1.8B-Chat"
DATA_PATH = "data/identity_dataset.json"
OUTPUT_DIR = "models/trae_assistant_lora"

# 2. 模拟加载数据
print(f"\n[1/5] 正在加载数据集: {DATA_PATH} ...")
if os.path.exists(DATA_PATH):
    with open(DATA_PATH, 'r', encoding='utf-8') as f:
        data = json.load(f)
    print(f"✅ 成功加载 {len(data)} 条样本。")
else:
    print(f"❌ 数据集文件不存在!请先运行 src/5.1_prepare_data.py")
    exit(1)

# 3. 模拟加载模型
print(f"\n[2/5] 正在加载底座模型: {MODEL_NAME} ...")
time.sleep(1)
print("✅ Tokenizer 加载完成")
print("✅ Model (4-bit quantization) 加载完成")

# 4. 模拟配置 LoRA
print(f"\n[3/5] 配置 LoRA 参数 ...")
lora_config = {
    "r": 8,
    "lora_alpha": 32,
    "target_modules": ["q_proj", "v_proj"],
    "lora_dropout": 0.05,
    "bias": "none",
    "task_type": "CAUSAL_LM"
}
print(f"✅ LoRA Config: {json.dumps(lora_config, indent=2)}")

# 5. 模拟训练过程
print(f"\n[4/5] 开始训练 (Training) ...")
total_steps = 10
for step in range(1, total_steps + 1):
    time.sleep(0.5) # 模拟训练耗时
    loss = 2.5 - (step * 0.2) + (0.1 * (step % 2)) # 模拟 Loss 下降
    print(f"Step [{step}/{total_steps}] | Loss: {loss:.4f} | Learning Rate: 2e-4")

print("\n✅ 训练完成!")

# 6. 模拟保存模型
print(f"\n[5/5] 保存微调权重到 {OUTPUT_DIR} ...")
os.makedirs(OUTPUT_DIR, exist_ok=True)
with open(os.path.join(OUTPUT_DIR, "adapter_config.json"), "w") as f:
    json.dump(lora_config, f)
with open(os.path.join(OUTPUT_DIR, "adapter_model.bin"), "w") as f:
    f.write("mock_weight_data")

print(f"\n✨ 微调流程演示结束!在真实环境中,你现在拥有了一个属于自己的 LoRA 模型。")
print(f"接下来通常会进行:\n1. 模型合并 (Merge)\n2. 量化 (Quantization)\n3. 部署 (Deployment)")

运行方式

先生成数据集:

python3 src/5.1_prepare_data.py

再运行模拟微调:

python3 src/5.1_mock_finetune.py

运行结果示例

运行 src/5.1_prepare_data.py 会生成数据文件并打印预览:

✅ 成功生成数据集,共 35 条样本
📂 文件保存路径: data/identity_dataset.json

🔍 样本预览:
[
  {
    "instruction": "...",
    "input": "",
    "output": "..."
  },
  ...
]

运行 src/5.1_mock_finetune.py 会模拟训练过程,并生成 LoRA 产物目录:

=== 🚀 模拟微调流程 (Mock Fine-tuning) ===
注意:本脚本仅用于演示代码逻辑,不会消耗显存或产生真实模型。

[1/5] 正在加载数据集: data/identity_dataset.json ...
✅ 成功加载 35 条样本。

[2/5] 正在加载底座模型: Qwen/Qwen1.5-1.8B-Chat ...
✅ Tokenizer 加载完成
✅ Model (4-bit quantization) 加载完成

[3/5] 配置 LoRA 参数 ...
✅ LoRA Config: {...}

[4/5] 开始训练 (Training) ...
Step [1/10] | Loss: ...
...
✅ 训练完成!

[5/5] 保存微调权重到 models/trae_assistant_lora ...

训练结束后,我们会在 models/trae_assistant_lora/ 看到两个文件:

  • adapter_config.json
  • adapter_model.bin

总结

这一篇我们用“数据准备 + Mock 微调”的方式走通了微调流程:理解了指令数据集的格式、LoRA 训练大致会做哪些步骤、以及最终会产出什么样的适配器文件。真实场景里,我们只需要把模拟训练部分替换为真实训练框架即可。

更多推荐