5分钟手把手系列(四):如何微调一个大模型(Colab + Unsloth)

背景

随着行业内越来越多的通用大模型进行开源,目前huggingface上的模型数量已达100w+,大家也是基于业内的开源大模型,针对某些特殊使用场景(如问题回答、编程、医学、法学等场景)进行微调,使得模型的输出更符合我们的预期。

为什么需要微调?

graph TB
    A[通用大模型] --> B{是否满足需求}
    B -->|不满足| C[模型微调]
    B -->|满足| D[直接使用]
    C --> E[提高准确性]
    C --> F[提高效率]
    C --> G[数据安全]
    C --> H[领域适配]
    E --> I[微调后模型]
    F --> I
    G --> I
    H --> I

微调的核心优势

优势说明示例
提高准确性在特定任务上显著提升准确率医学数据集微调后,能更准确识别医学术语与疾病表现
提高效率模型在特定任务上更高效问答数据集微调后,能更快更准确地回答问题
提高泛化能力更好地执行与训练数据不同的任务多种创意文本格式训练后,能生成多种风格内容
减少数据需求相比从头训练,需要更少的数据利用预训练知识,只需少量领域数据即可
数据安全内部微调保护数据隐私敏感数据不需要发送到公开API

微调基础知识

微调类型对比

graph LR
    A[模型微调] --> B[按参数范围]
    A --> C[按数据类型]

    B --> D[全微调<br/>Full Fine-tuning]
    B --> E[部分微调<br/>Partial Fine-tuning]

    C --> F[监督微调<br/>SFT]
    C --> G[无监督微调<br/>Unsupervised]

    D --> H[调整所有参数<br/>效果好,成本高]
    E --> I[仅调整部分层<br/>效果尚可,成本低]

    F --> J[使用标注数据<br/>适合分类/问答]
    G --> K[使用未标注数据<br/>适合特征学习]

微调方法详解

1. 全微调 vs 部分微调

类型参数范围适用场景资源消耗效果
全微调所有参数任务与预训练差异大高(需大量GPU)最优
部分微调仅上层参数任务与预训练相似低(普通GPU)良好
LoRA微调低秩适配器绝大多数场景极低(推荐)优秀

2. 监督微调 vs 无监督微调

graph TB
    A[训练数据] --> B{是否有标签}
    B -->|有标签| C[监督微调 SFT]
    B -->|无标签| D[无监督微调]

    C --> E[分类任务<br/>问答任务<br/>翻译任务]
    D --> F[特征提取<br/>表示学习<br/>生成任务]

    E --> G[准确度高<br/>需要标注成本]
    F --> H[灵活性好<br/>无需标注]

本文采用: 监督微调(SFT) + LoRA方法,这是当前最主流的微调方案


完整微调流程

graph TB
    Start[开始微调] --> Step1[步骤1: 准备数据集]
    Step1 --> Step2[步骤2: 选择基座模型]
    Step2 --> Step3[步骤3: 配置微调参数]
    Step3 --> Step4[步骤4: 训练模型]
    Step4 --> Step5[步骤5: 评估模型]
    Step5 --> Decision{效果是否满意}
    Decision -->|否| Adjust[调整参数]
    Adjust --> Step3
    Decision -->|是| Step6[步骤6: 保存模型]
    Step6 --> Step7[步骤7: 部署使用]
    Step7 --> End[完成]

    style Step1 fill:#e1f5ff
    style Step2 fill:#e1f5ff
    style Step3 fill:#fff3cd
    style Step4 fill:#fff3cd
    style Step5 fill:#d4edda
    style Step6 fill:#d4edda
    style Step7 fill:#d4edda

流程详解

步骤关键工作注意事项
1. 准备数据集收集、清洗、标注数据保证数据质量和标签准确
2. 选择基座模型根据任务选择合适模型考虑模型大小、语言支持、领域
3. 配置微调参数设置LoRA、学习率等合理配置避免过拟合
4. 训练模型在训练集上训练监控loss,及时调整
5. 评估模型在验证集上测试对比微调前后效果
6. 保存模型导出LoRA权重或合并模型选择合适的量化方法
7. 部署使用集成到应用中优化推理速度

实战:微调”弱智吧”数据集

为什么选择”弱智吧”数据集?

由于当前大模型的预训练集已经较为完善,普通的训练集的微调效果可能很难让大家看出通用大模型与微调后模型的效果区别,所以本文的数据集采用了大名鼎鼎的”弱智吧”训练集。

数据集获取:

# 在HuggingFace搜索: ruozhiba
# 推荐数据集: LooksJuicy/ruozhiba

微调前后效果对比

graph LR
    A[用户问题:<br/>赤壁之战曹操为什么<br/>不拨打火警电话灭火?] --> B{模型类型}

    B -->|微调前| C[Llama3.1-8B<br/>原始模型]
    B -->|微调后| D[Llama3.1-8B<br/>弱智吧微调]

    C --> E[回答:<br/>理性分析历史背景<br/>解释没有电话等]
    D --> F[回答:<br/>因为火警电话<br/>那时候还没发明]

    style E fill:#f8d7da
    style F fill:#d4edda

基座模型: Llama3.1-8B (2024年最新的开源模型之一)


环境准备:Google Colab

Colab优势

graph TB
    A[Google Colab] --> B[免费GPU资源]
    A --> C[无需本地环境]
    A --> D[代码云端保存]
    A --> E[易于分享]

    B --> F[T4 GPU<br/>15GB显存]
    C --> G[浏览器即可使用]
    D --> H[Google Drive同步]
    E --> I[一键分享链接]

Colab配置对比

套餐GPU型号显存费用适用场景
FreeT415GB免费小模型微调(8B以下)
Colab ProT4/V10016-32GB$9.99/月中等模型微调(13B)
Colab Pro+A10040GB$49.99/月大模型微调(70B)

推荐: 本教程使用免费的T4 GPU即可完成

Colab环境设置步骤

sequenceDiagram
    participant User as 用户
    participant Colab as Google Colab
    participant GPU as GPU资源

    User->>Colab: 1. 访问colab.research.google.com
    User->>Colab: 2. 新建笔记本
    User->>Colab: 3. 点击"运行时"菜单
    Colab->>User: 显示运行时选项
    User->>Colab: 4. 选择"更改运行时类型"
    Colab->>User: 显示硬件加速器选项
    User->>Colab: 5. 选择 T4 GPU
    User->>Colab: 6. 保存设置
    Colab->>GPU: 分配T4 GPU资源
    GPU->>User: GPU环境准备完成

详细步骤:

  1. 访问 Google Colab
  2. 点击 文件 > 新建笔记本
  3. 点击 运行时 > 更改运行时类型
  4. 硬件加速器 选择 T4 GPU
  5. 点击 保存

训练框架:Unsloth

为什么选择Unsloth?

graph TB
    A[Unsloth框架] --> B[速度提升2-5倍]
    A --> C[内存占用减少70%]
    A --> D[开箱即用]
    A --> E[免费开源]

    B --> F[优化的CUDA内核]
    C --> G[智能梯度检查点]
    D --> H[提供大量示例代码]
    E --> I[活跃的社区支持]

框架对比

框架速度内存占用易用性社区支持推荐度
Unsloth⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
HuggingFace⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
DeepSpeed⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
MLX (Mac)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

完整微调代码详解

技术栈架构

graph TB
    A[Colab环境] --> B[Unsloth框架]
    B --> C[PyTorch]
    B --> D[Transformers]
    B --> E[TRL训练器]
    B --> F[PEFT/LoRA]

    C --> G[模型加载]
    D --> H[Tokenizer]
    E --> I[SFT训练]
    F --> J[参数高效微调]

    G --> K[微调后模型]
    H --> K
    I --> K
    J --> K

    style A fill:#e1f5ff
    style B fill:#fff3cd
    style K fill:#d4edda

步骤1: 安装Unsloth和依赖

%%capture
# 安装Unsloth核心框架
!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
 
# 检查PyTorch版本并安装对应的Xformers
from torch import __version__
from packaging.version import Version as V
 
# PyTorch 2.3对应xformers 0.0.27, PyTorch 2.4+使用最新版
xformers = "xformers==0.0.27" if V(__version__) < V("2.4.0") else "xformers"
 
# 安装训练所需的核心库
!pip install --no-deps {xformers} trl peft accelerate bitsandbytes triton

依赖说明:

  • unsloth: 核心微调框架
  • xformers: Flash Attention加速
  • trl: Transformer Reinforcement Learning (SFT训练器)
  • peft: Parameter-Efficient Fine-Tuning (LoRA实现)
  • accelerate: 分布式训练加速
  • bitsandbytes: 量化支持 (4bit/8bit)
  • triton: GPU内核优化

步骤2: 加载基座模型

from unsloth import FastLanguageModel
import torch
 
# ===== 核心参数配置 =====
max_seq_length = 2048      # 最大序列长度,可根据显存调整
dtype = None               # 自动检测: Float16(T4/V100), Bfloat16(A100+)
load_in_4bit = True        # 使用4bit量化节省显存
 
# ===== Unsloth支持的预量化模型列表 =====
fourbit_models = [
    # Llama 3.1系列 (推荐)
    "unsloth/Meta-Llama-3.1-8B-bnb-4bit",           # 8B基础模型 ⭐推荐
    "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",  # 8B指令模型
    "unsloth/Meta-Llama-3.1-70B-bnb-4bit",          # 70B模型(需Pro+)
    "unsloth/Meta-Llama-3.1-405B-bnb-4bit",         # 405B超大模型
 
    # Mistral系列
    "unsloth/Mistral-Nemo-Base-2407-bnb-4bit",      # 12B基础模型
    "unsloth/Mistral-Nemo-Instruct-2407-bnb-4bit",  # 12B指令模型
    "unsloth/mistral-7b-v0.3-bnb-4bit",             # 7B v3版本
    "unsloth/mistral-7b-instruct-v0.3-bnb-4bit",    # 7B v3指令版
 
    # Phi系列 (微软)
    "unsloth/Phi-3.5-mini-instruct",                # 3.8B轻量模型
    "unsloth/Phi-3-medium-4k-instruct",             # 14B中等模型
 
    # Gemma系列 (Google)
    "unsloth/gemma-2-9b-bnb-4bit",                  # 9B模型
    "unsloth/gemma-2-27b-bnb-4bit",                 # 27B模型
 
    # Qwen系列 (阿里) - 需要手动添加
    "unsloth/Qwen2.5-7B-bnb-4bit",                  # 7B中文优秀
    "unsloth/Qwen2.5-14B-bnb-4bit",                 # 14B性能更强
]
 
# ===== 加载模型 =====
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Meta-Llama-3.1-8B",  # 选择基座模型
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    # token = "hf_...",  # 如果使用gated模型(如Llama2)需要HF token
)

参数说明:

参数说明推荐值影响
max_seq_length最大序列长度2048越大显存占用越高
load_in_4bit是否4bit量化True显存占用降低75%
dtype数据类型None(自动)Float16(T4), Bfloat16(A100)

步骤3: 配置LoRA参数

graph TB
    A[原始模型参数] --> B[LoRA低秩分解]
    B --> C[A矩阵<br/>r x d]
    B --> D[B矩阵<br/>d x r]
    C --> E[低秩适配器<br/>只训练r维]
    D --> E
    E --> F[合并到原模型]
    F --> G[微调后模型]

    style A fill:#f8d7da
    style E fill:#fff3cd
    style G fill:#d4edda
from unsloth import FastLanguageModel
 
# ===== 配置LoRA参数 =====
model = FastLanguageModel.get_peft_model(
    model,
 
    # ===== LoRA核心参数 =====
    r = 16,                          # LoRA秩,越大越精确但越慢
                                     # 推荐值: 8(快), 16(平衡), 32/64(精确)
 
    target_modules = [               # 要应用LoRA的模块
        "q_proj", "k_proj",          # Query和Key投影 (注意力机制)
        "v_proj", "o_proj",          # Value和Output投影
        "gate_proj", "up_proj",      # MLP门控和上投影
        "down_proj",                 # MLP下投影
    ],
 
    lora_alpha = 16,                 # LoRA缩放因子,通常设为r的值
    lora_dropout = 0,                # Dropout率, 0表示不使用(优化后)
    bias = "none",                   # 偏置项策略, "none"最优化
 
    # ===== Unsloth优化参数 =====
    use_gradient_checkpointing = "unsloth",  # 梯度检查点,节省30%显存
                                              # 可选: True, "unsloth", False
 
    random_state = 3407,             # 随机种子,保证可复现性
    use_rslora = False,              # 是否使用秩稳定LoRA
    loftq_config = None,             # LoftQ量化配置
)

LoRA参数详解:

参数作用推荐值说明
rLoRA秩16越大模型越精确,但训练越慢
lora_alpha缩放因子16通常设为r的值
lora_dropoutDropout率0Unsloth优化后建议为0
target_modules应用LoRA的层全部投影层覆盖更多层效果更好
use_gradient_checkpointing梯度检查点”unsloth”节省显存的关键

LoRA秩(r)选择建议:

graph LR
    A[选择LoRA秩r] --> B{任务复杂度}
    B -->|简单任务| C[r=8<br/>训练快,显存小]
    B -->|中等任务| D[r=16<br/>平衡推荐]
    B -->|复杂任务| E[r=32或64<br/>效果好,资源多]

    style C fill:#d4edda
    style D fill:#fff3cd
    style E fill:#f8d7da

步骤4: 准备数据集

# ===== 定义提示词模板 =====
alpaca_prompt = """Below is an instruction that describes a question, paired with an output that answer the question.
 
### Instruction:
{}
 
### output:
{}"""
 
# ===== 获取EOS token =***
EOS_TOKEN = tokenizer.eos_token  # 必须添加,否则生成会无限循环
 
# ===== 数据格式化函数 =====
def formatting_prompts_func(examples):
    """
    将数据集格式化为模型训练所需的格式
 
    参数:
        examples: 包含'instruction'和'output'字段的批量样本
 
    返回:
        格式化后的文本列表
    """
    instructions = examples["instruction"]  # 获取问题
    outputs = examples["output"]            # 获取答案
    texts = []
 
    for instruction, output in zip(instructions, outputs):
        # 使用Alpaca格式模板
        text = alpaca_prompt.format(instruction, output) + EOS_TOKEN
        texts.append(text)
 
    return {"text": texts}
 
# ===== 加载数据集 =====
from datasets import load_dataset
 
# 从HuggingFace加载弱智吧数据集
dataset = load_dataset("LooksJuicy/ruozhiba", split="train")
 
# 应用格式化函数
dataset = dataset.map(
    formatting_prompts_func,
    batched=True,           # 批量处理,提高效率
)
 
# ===== 查看数据集示例 =====
print(f"数据集大小: {len(dataset)} 条")
print(f"示例数据:\n{dataset[0]['text']}")

数据集格式示例:

Below is an instruction that describes a question, paired with an output that answer the question.

### Instruction:
赤壁之战曹操为什么不拨打火警电话灭火?

### output:
因为那时候还没有发明电话。<|end_of_text|>

其他常用数据集:

数据集领域大小HuggingFace路径
弱智吧幽默问答~10kLooksJuicy/ruozhiba
医疗问答医学~100kmedical_dialog_zh
法律问答法律~50klaw_qa_zh
编程问答代码~500kcodeparrot/github-code
通用指令通用~50kyahma/alpaca-cleaned

步骤5: 配置训练参数

from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
 
# ===== 创建训练器 =====
trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text",           # 数据集中文本字段名
    max_seq_length = max_seq_length,
    dataset_num_proc = 2,                  # 数据处理进程数
    packing = False,                       # 是否打包短序列(可提速5倍)
 
    # ===== 训练参数配置 =====
    args = TrainingArguments(
        # --- 批次大小配置 ---
        per_device_train_batch_size = 2,   # 每个设备的批次大小
        gradient_accumulation_steps = 4,   # 梯度累积步数
                                            # 实际批次 = 2 * 4 = 8
 
        # --- 训练步数配置 ---
        # 方式1: 指定训练步数
        max_steps = 60,                     # 总训练步数(快速测试用)
        # 方式2: 指定训练轮数 (二选一)
        # num_train_epochs = 1,             # 完整训练1个epoch
 
        # --- 学习率配置 ---
        learning_rate = 2e-4,               # 学习率 (LoRA推荐2e-4)
        warmup_steps = 5,                   # 预热步数
        lr_scheduler_type = "linear",       # 学习率调度器类型
 
        # --- 精度配置 ---
        fp16 = not is_bfloat16_supported(), # 使用FP16 (T4/V100)
        bf16 = is_bfloat16_supported(),     # 使用BF16 (A100+)
 
        # --- 优化器配置 ---
        optim = "adamw_8bit",               # 8bit AdamW优化器
        weight_decay = 0.01,                # 权重衰减(正则化)
 
        # --- 日志和保存 ---
        logging_steps = 1,                  # 每步记录日志
        output_dir = "outputs",             # 输出目录
 
        # --- 其他配置 ---
        seed = 3407,                        # 随机种子
    ),
)

训练参数详解:

graph TB
    A[训练参数] --> B[批次配置]
    A --> C[学习率配置]
    A --> D[精度配置]
    A --> E[优化器配置]

    B --> F[batch_size=2<br/>梯度累积=4<br/>等效batch=8]
    C --> G[lr=2e-4<br/>warmup=5<br/>线性衰减]
    D --> H[FP16/BF16<br/>自动检测]
    E --> I[AdamW 8bit<br/>权重衰减0.01]

    style F fill:#e1f5ff
    style G fill:#fff3cd
    style H fill:#d4edda
    style I fill:#f8d7da
参数说明推荐值影响
batch_size每个设备批次大小2越大越快但越耗显存
gradient_accumulation梯度累积步数4模拟更大批次
learning_rate学习率2e-4LoRA标准值
max_steps最大训练步数60(测试)
500+(正式)
影响训练时长
warmup_steps预热步数5稳定初期训练

步骤6: 开始训练

# ===== 查看GPU状态 =====
gpu_stats = torch.cuda.get_device_properties(0)
start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
 
print(f"GPU型号: {gpu_stats.name}")
print(f"总显存: {max_memory} GB")
print(f"已使用显存: {start_gpu_memory} GB")
print(f"剩余显存: {max_memory - start_gpu_memory} GB")
 
# ===== 开始训练 =====
print("\n" + "="*50)
print("开始训练...")
print("="*50 + "\n")
 
trainer_stats = trainer.train()
 
# ===== 训练完成后的显存统计 =====
used_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
used_memory_for_lora = round(used_memory - start_gpu_memory, 3)
used_percentage = round(used_memory / max_memory * 100, 3)
 
print("\n" + "="*50)
print("训练完成!")
print("="*50)
print(f"训练用时: {trainer_stats.metrics['train_runtime']:.2f} 秒")
print(f"最终Loss: {trainer_stats.metrics['train_loss']:.4f}")
print(f"LoRA显存占用: {used_memory_for_lora} GB")
print(f"总显存占用: {used_memory} GB ({used_percentage}%)")

训练过程监控:

sequenceDiagram
    participant T as 训练器
    participant M as 模型
    participant D as 数据集
    participant G as GPU

    T->>D: 加载批次数据
    D->>T: 返回格式化数据
    T->>M: 前向传播
    M->>T: 计算Loss
    T->>T: 反向传播
    T->>M: 更新LoRA参数
    T->>G: 清理显存

    Note over T,G: 重复60次(max_steps=60)

    T->>T: 训练完成

典型训练输出:

GPU型号: Tesla T4
总显存: 15.0 GB
已使用显存: 5.2 GB
剩余显存: 9.8 GB

==================================================
开始训练...
==================================================

Step 1/60 | Loss: 2.3456 | LR: 4.0e-05
Step 10/60 | Loss: 1.8234 | LR: 2.0e-04
Step 20/60 | Loss: 1.2345 | LR: 2.0e-04
...
Step 60/60 | Loss: 0.4567 | LR: 6.7e-05

==================================================
训练完成!
==================================================
训练用时: 180.45 秒
最终Loss: 0.4567
LoRA显存占用: 3.2 GB
总显存占用: 8.4 GB (56.0%)

步骤7: 测试微调后的模型

# ===== 切换到推理模式 =====
FastLanguageModel.for_inference(model)  # 启用2倍推理加速
 
# ===== 准备测试问题 =====
test_instruction = "赤壁之战曹操为什么不拨打火警电话灭火?"
 
inputs = tokenizer(
    [alpaca_prompt.format(
        test_instruction,  # 问题
        "",                # 输出留空,让模型生成
    )],
    return_tensors="pt"
).to("cuda")
 
# ===== 生成回答 =====
from transformers import TextStreamer
 
text_streamer = TextStreamer(tokenizer)
_ = model.generate(
    **inputs,
    streamer=text_streamer,      # 流式输出
    max_new_tokens=2048,         # 最大生成长度
    use_cache=True,              # 使用KV缓存加速
    temperature=0.7,             # 温度参数(越高越随机)
    top_p=0.9,                   # 核采样参数
)

测试其他问题:

# 创建交互式测试函数
def test_question(question):
    """测试单个问题"""
    inputs = tokenizer(
        [alpaca_prompt.format(question, "")],
        return_tensors="pt"
    ).to("cuda")
 
    print(f"\n问题: {question}")
    print("回答: ", end="")
 
    _ = model.generate(
        **inputs,
        streamer=text_streamer,
        max_new_tokens=512,
        temperature=0.7,
    )
 
# 测试多个问题
test_questions = [
    "为什么手机没电了不能用充电宝给它输血?",
    "为什么古代人不用GPS导航?",
    "恐龙为什么不去博物馆参观自己的化石?",
]
 
for q in test_questions:
    test_question(q)

步骤8: 保存模型

graph TB
    A[微调后的模型] --> B{保存方式}

    B --> C[方式1: 仅保存LoRA]
    B --> D[方式2: 合并后保存]
    B --> E[方式3: GGUF格式]

    C --> F[文件小<br/>需要基座模型<br/>适合分享]
    D --> G[文件大<br/>完整模型<br/>独立使用]
    E --> H[量化模型<br/>推理快<br/>Ollama可用]

    style C fill:#d4edda
    style D fill:#fff3cd
    style E fill:#e1f5ff

方式1: 保存LoRA适配器(推荐)

# ===== 本地保存LoRA =====
model.save_pretrained("lora_model")        # 保存LoRA权重
tokenizer.save_pretrained("lora_model")    # 保存tokenizer
 
print("LoRA模型已保存到: lora_model/")
print("文件大小约: 50-200MB")
 
# ===== 推送到HuggingFace Hub(可选) =====
# model.push_to_hub("your_username/llama3.1-ruozhiba-lora", token="hf_...")
# tokenizer.push_to_hub("your_username/llama3.1-ruozhiba-lora", token="hf_...")

方式2: 合并模型后保存

# ===== 合并LoRA到基座模型 =====
model.save_pretrained_merged(
    "merged_model",                    # 保存路径
    tokenizer,
    save_method="merged_16bit",        # 保存方法
    # 可选: "merged_16bit", "merged_4bit", "lora"
)
 
print("完整模型已保存到: merged_model/")
print("文件大小约: 16GB (FP16格式)")

方式3: 保存为GGUF格式(Ollama/LM Studio可用)

# ===== 保存为GGUF格式 =====
model.save_pretrained_gguf(
    "model_gguf",                      # 保存目录
    tokenizer,
    quantization_method="q4_k_m",      # 量化方法
)
 
# ===== 常用量化方法 =====
quantization_methods = [
    "q4_k_m",   # 4-bit中等质量 (推荐,平衡性能和质量)
    "q5_k_m",   # 5-bit中等质量 (质量更好)
    "q8_0",     # 8-bit (质量最好,文件较大)
    "f16",      # FP16 (无损,文件最大)
]
 
print("GGUF模型已保存到: model_gguf/")
print("可以用Ollama或LM Studio加载")

量化方法对比:

量化方法文件大小质量速度推荐场景
q4_k_m~4.5GB⭐⭐⭐⭐⭐⭐⭐⭐⭐日常使用(推荐)
q5_k_m~5.5GB⭐⭐⭐⭐⭐⭐⭐⭐⭐追求质量
q8_0~8.5GB⭐⭐⭐⭐⭐⭐⭐⭐最高质量
f16~16GB⭐⭐⭐⭐⭐⭐⭐无损保存

步骤9: 下载并使用模型

从Colab下载模型

# ===== 方式1: 直接下载(推荐) =====
from google.colab import files
 
# 压缩模型文件
!zip -r lora_model.zip lora_model/
 
# 下载到本地
files.download('lora_model.zip')
 
# ===== 方式2: 保存到Google Drive =====
from google.colab import drive
drive.mount('/content/drive')
 
# 复制到Google Drive
!cp -r lora_model /content/drive/MyDrive/
 
print("模型已保存到Google Drive")

在本地使用模型

使用Ollama加载GGUF模型:

# 1. 创建Modelfile
cat > Modelfile << EOF
FROM ./model_gguf/llama3.1-ruozhiba-Q4_K_M.gguf
 
TEMPLATE """Below is an instruction that describes a question, paired with an output that answer the question.
 
### Instruction:
{{ .Prompt }}
 
### output:
"""
 
PARAMETER temperature 0.7
PARAMETER top_p 0.9
EOF
 
# 2. 创建Ollama模型
ollama create llama3.1-ruozhiba -f Modelfile
 
# 3. 运行模型
ollama run llama3.1-ruozhiba

使用Python加载LoRA模型:

from unsloth import FastLanguageModel
 
# 加载LoRA模型
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="lora_model",           # LoRA权重路径
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=True,
)
 
FastLanguageModel.for_inference(model)
 
# 测试
inputs = tokenizer(["问题"], return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=512)
print(tokenizer.decode(outputs[0]))

常见问题解答

Q1: 训练时显存不足(OOM)怎么办?

错误信息:

RuntimeError: CUDA out of memory. Tried to allocate 2.00 GB

解决方案:

graph TB
    A[显存不足] --> B{检查配置}
    B --> C[降低batch_size]
    B --> D[减小max_seq_length]
    B --> E[启用gradient_checkpointing]
    B --> F[降低LoRA秩r]

    C --> G[per_device_train_batch_size = 1]
    D --> H[max_seq_length = 1024]
    E --> I[use_gradient_checkpointing = unsloth]
    F --> J[r = 8]

    style G fill:#d4edda
    style H fill:#d4edda
    style I fill:#d4edda
    style J fill:#d4edda

优化配置:

# 显存优化配置
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    max_seq_length=1024,              # 从2048降到1024
 
    args=TrainingArguments(
        per_device_train_batch_size=1,  # 从2降到1
        gradient_accumulation_steps=8,   # 从4增到8保持等效批次
        max_steps=60,
        learning_rate=2e-4,
 
        # 启用混合精度训练
        fp16=True,
 
        # 其他优化
        optim="adamw_8bit",              # 使用8bit优化器
        gradient_checkpointing=True,     # 启用梯度检查点
    ),
)

Q2: 训练Loss不下降怎么办?

可能原因和解决方案:

问题原因解决方案
Loss一直很高学习率太低提高learning_rate到5e-4
Loss震荡学习率太高降低learning_rate到1e-4
Loss不动数据集问题检查数据格式,增加数据多样性
Loss突然上升梯度爆炸降低学习率,增加warmup_steps

调试建议:

# 1. 检查数据集
print(dataset[0]['text'])
 
# 2. 尝试不同学习率
learning_rates = [1e-4, 2e-4, 5e-4]
 
# 3. 增加训练步数
max_steps = 200  # 从60增加到200
 
# 4. 查看训练曲线
import matplotlib.pyplot as plt
 
losses = trainer_stats.log_history
plt.plot([x['loss'] for x in losses if 'loss' in x])
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.show()

Q3: 如何评估微调效果?

评估方法:

# ===== 方式1: 定性评估(人工测试) =====
test_cases = [
    "赤壁之战曹操为什么不拨打火警电话灭火?",
    "为什么古代人不开汽车?",
    "恐龙为什么不用智能手机?",
]
 
for question in test_cases:
    print(f"\n问题: {question}")
 
    # 微调前的回答
    base_model_answer = get_answer(base_model, question)
    print(f"微调前: {base_model_answer}")
 
    # 微调后的回答
    tuned_model_answer = get_answer(tuned_model, question)
    print(f"微调后: {tuned_model_answer}")
    print("-" * 50)
 
# ===== 方式2: 定量评估(验证集) =====
from datasets import load_dataset
 
# 加载验证集
eval_dataset = load_dataset("LooksJuicy/ruozhiba", split="test")
 
# 计算困惑度(Perplexity)
from torch.nn import CrossEntropyLoss
 
def calculate_perplexity(model, dataset):
    """计算模型在数据集上的困惑度"""
    model.eval()
    total_loss = 0
 
    for example in dataset:
        inputs = tokenizer(example['text'], return_tensors='pt').to('cuda')
        with torch.no_grad():
            outputs = model(**inputs, labels=inputs['input_ids'])
            total_loss += outputs.loss.item()
 
    perplexity = torch.exp(torch.tensor(total_loss / len(dataset)))
    return perplexity.item()
 
base_ppl = calculate_perplexity(base_model, eval_dataset)
tuned_ppl = calculate_perplexity(tuned_model, eval_dataset)
 
print(f"微调前困惑度: {base_ppl:.2f}")
print(f"微调后困惑度: {tuned_ppl:.2f}")
print(f"提升: {(base_ppl - tuned_ppl) / base_ppl * 100:.1f}%")

Q4: Colab会话断开导致训练中断怎么办?

预防措施:

# ===== 方式1: 启用自动保存检查点 =====
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
 
    args=TrainingArguments(
        # ... 其他参数 ...
 
        # 启用检查点保存
        save_strategy="steps",          # 按步数保存
        save_steps=20,                  # 每20步保存一次
        save_total_limit=3,             # 最多保留3个检查点
 
        # 或者按时间保存
        # save_strategy="epoch",        # 每个epoch保存
    ),
)
 
# ===== 方式2: 从检查点恢复训练 =====
# 如果训练中断,运行这段代码继续训练
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    args=TrainingArguments(...),
)
 
# 从最新检查点恢复
trainer.train(resume_from_checkpoint=True)
 
# ===== 方式3: 使用Colab Pro避免断开 =====
# Colab Pro提供更长的会话时间和更好的GPU

保持Colab连接的技巧:

// 在浏览器控制台运行(F12打开)
// 防止Colab因不活动而断开
function KeepAlive() {
    console.log("Keeping alive...");
    document.querySelector("colab-connect-button").click();
}
setInterval(KeepAlive, 60000);  // 每分钟点击一次

Q5: 如何针对中文数据集进行微调?

中文数据集推荐:

数据集领域大小来源
Belle通用指令100万+BelleGroup/BELLE-2M
Alpaca中文通用指令5万+shibing624/alpaca-zh
医疗问答医学10万+Toyhom/Chinese-medical-dialogue
法律问答法律5万+AI-ModelScope/Chinese-legal-dataset

中文模型推荐:

# 使用中文优化的基座模型
chinese_models = [
    "unsloth/Qwen2.5-7B-bnb-4bit",          # 阿里Qwen 7B ⭐推荐
    "unsloth/Qwen2.5-14B-bnb-4bit",         # 阿里Qwen 14B
    "hfl/chinese-llama-2-7b",                # 中文Llama
    "THUDM/chatglm3-6b",                     # ChatGLM3
]
 
# 加载中文模型
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen2.5-7B-bnb-4bit",
    max_seq_length=2048,
    dtype=None,
    load_in_4bit=True,
)

中文提示词模板:

# 中文Alpaca格式
chinese_prompt = """以下是一条指令,描述了一个问题,请给出恰当的回答。
 
### 指令:
{}
 
### 回答:
{}"""
 
# 中文对话格式
dialogue_prompt = """用户: {}
 
助手: {}"""

最佳实践建议

微调前的准备工作

graph TB
    Start[开始微调项目] --> A[明确目标]
    A --> B[评估必要性]
    B --> C{是否真的需要微调?}

    C -->|否| D[考虑其他方案]
    C -->|是| E[准备工作]

    D --> F[优化Prompt]
    D --> G[使用RAG]
    D --> H[尝试其他模型]

    E --> I[收集高质量数据]
    E --> J[选择合适基座模型]
    E --> K[设计评估方案]

    I --> L[开始微调]
    J --> L
    K --> L

    style C fill:#fff3cd
    style F fill:#f8d7da
    style G fill:#f8d7da
    style H fill:#f8d7da
    style L fill:#d4edda

1. 数据集质量 > 数量

好的数据集特征:

  • ✅ 高质量:每条样本都经过人工审核
  • ✅ 多样性:覆盖各种表达方式
  • ✅ 准确性:答案正确且符合预期风格
  • ✅ 一致性:格式统一,无噪声数据

数据集规模建议:

任务类型最小样本数推荐样本数说明
风格迁移100500-1000学习特定回答风格
领域适配10005000-10000医疗、法律等专业领域
任务微调5002000-5000问答、摘要等特定任务

2. 参数调优策略

渐进式调优:

# 阶段1: 快速验证(5-10分钟)
initial_params = {
    "max_steps": 50,
    "learning_rate": 2e-4,
    "r": 8,
}
 
# 阶段2: 精细调整(30-60分钟)
tuning_params = {
    "max_steps": 200,
    "learning_rate": 1e-4,
    "r": 16,
}
 
# 阶段3: 完整训练(2-4小时)
final_params = {
    "num_train_epochs": 3,
    "learning_rate": 5e-5,
    "r": 32,
}

学习率选择:

LoRA秩(r)推荐学习率说明
r=85e-4快速训练
r=162e-4标准配置(推荐)
r=321e-4精细调整
r=645e-5复杂任务

3. 避免过拟合

过拟合的表现:

  • 训练Loss很低,但验证Loss很高
  • 模型只会输出训练集中的答案
  • 泛化能力差,遇到新问题表现不佳

预防措施:

graph LR
    A[预防过拟合] --> B[数据增强]
    A --> C[早停策略]
    A --> D[正则化]
    A --> E[验证集监控]

    B --> F[增加数据多样性<br/>使用数据增强技术]
    C --> G[监控验证Loss<br/>Loss不降就停止]
    D --> H[Weight Decay<br/>Dropout]
    E --> I[定期在验证集<br/>上测试]
# 启用早停和验证
from transformers import EarlyStoppingCallback
 
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,      # 添加验证集
 
    args=TrainingArguments(
        # ... 其他参数 ...
 
        # 评估配置
        evaluation_strategy="steps",
        eval_steps=20,                # 每20步评估一次
 
        # 保存最佳模型
        load_best_model_at_end=True,
        metric_for_best_model="eval_loss",
 
        # 正则化
        weight_decay=0.01,            # L2正则化
    ),
 
    # 早停回调
    callbacks=[EarlyStoppingCallback(
        early_stopping_patience=3     # 3次不提升就停止
    )],
)

4. 模型部署建议

部署方式对比:

部署方式优点缺点适用场景
Ollama简单易用,本地运行功能有限个人使用,快速原型
vLLM高性能,支持批处理配置复杂生产环境,高并发
Text Generation WebUI功能丰富,界面友好资源占用大测试评估
LM Studio图形界面,跨平台仅本地使用个人使用,模型管理

推理优化:

# 优化推理速度
FastLanguageModel.for_inference(model)  # Unsloth优化
 
# 使用更激进的量化
model_4bit = FastLanguageModel.from_pretrained(
    "model",
    load_in_4bit=True,           # 4bit量化
)
 
# 批量推理
def batch_generate(questions, batch_size=4):
    """批量生成,提高吞吐量"""
    results = []
 
    for i in range(0, len(questions), batch_size):
        batch = questions[i:i+batch_size]
        inputs = tokenizer(batch, return_tensors='pt', padding=True).to('cuda')
        outputs = model.generate(**inputs, max_new_tokens=512)
        results.extend(tokenizer.batch_decode(outputs))
 
    return results

进阶技巧

1. 多任务微调

# 准备多任务数据集
multi_task_prompt = """任务类型: {task}
 
### 输入:
{input}
 
### 输出:
{output}"""
 
tasks = {
    "问答": qa_dataset,
    "摘要": summary_dataset,
    "翻译": translation_dataset,
}
 
# 合并数据集
from datasets import concatenate_datasets
all_datasets = [format_dataset(d, task) for task, d in tasks.items()]
combined_dataset = concatenate_datasets(all_datasets)

2. 继续预训练(CPT)

# 对领域数据进行继续预训练
from transformers import DataCollatorForLanguageModeling
 
# 使用MLM(Masked Language Modeling)
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=True,
    mlm_probability=0.15
)
 
trainer = Trainer(
    model=model,
    data_collator=data_collator,
    train_dataset=domain_corpus,
    args=TrainingArguments(
        learning_rate=1e-5,          # 更低的学习率
        num_train_epochs=1,
    )
)

3. 模型蒸馏

# 用大模型的输出作为小模型的训练数据
def distill_knowledge(teacher_model, student_model, dataset):
    """知识蒸馏"""
 
    # 1. 用teacher生成soft labels
    teacher_outputs = []
    for example in dataset:
        output = teacher_model.generate(example['input'])
        teacher_outputs.append(output)
 
    # 2. 用teacher的输出训练student
    student_dataset = create_dataset(dataset, teacher_outputs)
 
    # 3. 微调student模型
    trainer = SFTTrainer(
        model=student_model,
        train_dataset=student_dataset,
        # ... 其他参数
    )
 
    trainer.train()

写在最后

微调的时机

大家在开发一款AI应用时,模型微调并不应该是最先要考虑的事情:

graph TB
    A[AI应用开发] --> B{直接使用通用模型}
    B -->|效果不佳| C{优化Prompt}
    C -->|仍不满意| D{使用RAG}
    D -->|还不够| E{尝试更强模型}
    E -->|依然不行| F[考虑微调]

    B -->|效果好| G[直接使用]
    C -->|效果好| G
    D -->|效果好| G
    E -->|效果好| G

    F --> H{准备充分?}
    H -->|是| I[开始微调]
    H -->|否| J[继续准备数据]

    style G fill:#d4edda
    style I fill:#fff3cd
    style J fill:#f8d7da

方案选择建议

场景推荐方案原因
信息实时性RAG微调无法获取最新信息
准确性要求高RAG + 好的Prompt降低幻觉
特定风格输出微调学习输出格式和风格
专业领域术语微调理解领域知识
多任务场景Prompt Engineering灵活性更好

微调的注意事项

⚠️ 数据集比例: 通用数据和专业数据需要平衡,否则可能失去泛化能力

⚠️ 参数选择: 不合适的参数可能导致灾难性遗忘(模型忘记原有知识)

⚠️ 评估方法: 不仅要看训练Loss,还要实际测试效果

⚠️ 成本考虑: 微调和维护的成本可能高于直接使用API


资源链接

官方文档

推荐数据集

相关教程


💡 温馨提示:

  • 本文适合AI新手动手实践,了解模型微调的全过程
  • 建议先在小数据集上快速实验,验证可行性后再进行完整训练
  • 微调只是手段,解决实际问题才是目的
  • 记得保存好训练日志和模型,方便后续对比和优化

祝你微调愉快! 🎉