307 lines
7.5 KiB
Markdown
307 lines
7.5 KiB
Markdown
# LLM微调实验完整记录 - 从零到苏联笑话专家
|
||
|
||
## 📅 实验时间线与完整流程
|
||
|
||
### 第一阶段:环境搭建与初识LLaMA Factory
|
||
|
||
#### 1. 系统环境确认
|
||
- **硬件**:NVIDIA RTX 3070 Ti (8GB VRAM)
|
||
- **软件**:Windows 11, Python 3.12.4, CUDA 13.0
|
||
- **目标**:使用LLaMA Factory微调一个会讲苏联笑话的AI
|
||
|
||
#### 2. LLaMA Factory安装
|
||
```bash
|
||
# 工作目录:F:\微调实验\llamafactory
|
||
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git llamafactory
|
||
cd llamafactory
|
||
python -m venv venv
|
||
venv\Scripts\activate
|
||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
||
pip install -e ".[torch,metrics,bitsandbytes]"
|
||
```
|
||
|
||
**踩坑1**:端口冲突
|
||
- 问题:7860端口被Stable Diffusion占用
|
||
- 解决:`llamafactory-cli webui --port 7861`
|
||
|
||
---
|
||
|
||
### 第二阶段:首次SFT训练(仅笑话)
|
||
|
||
#### 3. 数据准备 v1
|
||
- **文件**:`my_personal_data.json`
|
||
- **内容**:20条苏联笑话
|
||
- **格式**:Alpaca格式
|
||
```json
|
||
{
|
||
"instruction": "讲一个关于斯大林的苏联政治笑话",
|
||
"input": "",
|
||
"output": "二战后的国际会议上..."
|
||
}
|
||
```
|
||
|
||
#### 4. 第一次训练配置
|
||
```yaml
|
||
model_name_or_path: Qwen/Qwen2.5-1.5B-Instruct
|
||
stage: sft
|
||
dataset: soviet_jokes
|
||
num_train_epochs: 20
|
||
lora_rank: 32
|
||
```
|
||
|
||
**训练结果**:
|
||
- ✅ 成功记住笑话
|
||
- ❌ 不会正常对话
|
||
- ❌ 严重过拟合
|
||
|
||
**发现的问题**:
|
||
- 对"讲的不错"的回应是重复笑话
|
||
- 不会说"你好"、"谢谢"等基础对话
|
||
|
||
---
|
||
|
||
### 第三阶段:增强对话能力
|
||
|
||
#### 5. 数据准备 v2
|
||
- **新增文件**:`my_personal_data_v2.json`
|
||
- **内容**:12条基础对话数据
|
||
- **初始错误理解**:
|
||
- 错误格式:`"instruction": "向用户简单打招呼"`
|
||
- 正确格式:`"instruction": "你好"`(instruction是用户输入,不是任务描述)
|
||
|
||
#### 6. 第二次训练(混合数据)
|
||
```yaml
|
||
dataset: soviet_jokes,soviet_jokes_v2 # 同时使用两个数据集
|
||
num_train_epochs: 25
|
||
lora_rank: 64 # 增大容量
|
||
```
|
||
|
||
**训练结果**:
|
||
- ✅ 可以基础对话了
|
||
- ⚠️ 仍然过拟合严重
|
||
- ⚠️ 对话不够自然
|
||
|
||
---
|
||
|
||
### 第四阶段:探索DPO训练
|
||
|
||
#### 7. 理解DPO概念
|
||
- **SFT**:教模型"记住内容"
|
||
- **DPO**:教模型"什么是好的回答"
|
||
- **关键理解**:DPO需要chosen和rejected对比
|
||
|
||
#### 8. DPO数据准备
|
||
- **文件**:`my_personal_data_dpo.json`
|
||
- **内容**:20条笑话对比 + 20条对话对比
|
||
```json
|
||
{
|
||
"prompt": "讲个笑话",
|
||
"chosen": "精彩的苏联笑话...",
|
||
"rejected": "我不知道笑话"
|
||
}
|
||
```
|
||
|
||
#### 9. DPO训练踩坑记录
|
||
|
||
**踩坑2**:参数名称错误
|
||
```yaml
|
||
# 错误尝试1
|
||
dpo_beta: 0.1 # ❌
|
||
dpo_loss: sigmoid # ❌
|
||
|
||
# 错误尝试2
|
||
beta: 0.1 # ❌
|
||
loss_type: sigmoid # ❌
|
||
|
||
# 正确参数(通过搜索发现)
|
||
pref_beta: 0.1 # ✅
|
||
pref_loss: sigmoid # ✅
|
||
```
|
||
|
||
**踩坑3**:网络问题
|
||
- 问题:`OSError: Failed to load tokenizer`
|
||
- 解决:`set HF_ENDPOINT=https://hf-mirror.com`
|
||
|
||
**踩坑4**:文件名不匹配
|
||
- 问题:`ValueError: File data\dpo_data.json not found`
|
||
- 原因:dataset_info.json里写的文件名与实际不符
|
||
|
||
#### 10. DPO训练实验
|
||
|
||
**实验1:从零开始DPO**
|
||
- Loss: 0.1098(极低)
|
||
- 问题:模型完全没有学会内容,只学会了"偏好"
|
||
- 教训:DPO不能替代SFT,只能优化
|
||
|
||
**实验2:基于SFT的DPO**
|
||
- Loss: 0.509(比从零开始高)
|
||
- 原因:要在保持已有能力基础上调整
|
||
- 效果:确实有提升,但新笑话答不出(DPO数据里的新内容)
|
||
|
||
---
|
||
|
||
### 第五阶段:完整训练流程
|
||
|
||
#### 11. 数据准备 v3(最终版)
|
||
- **文件**:`my_personal_data_sft_v3.json`
|
||
- **内容**:40条笑话 + 20条对话(共60条完整数据)
|
||
- **理念**:SFT学所有内容,DPO优化表达
|
||
|
||
#### 12. 最终SFT训练
|
||
```yaml
|
||
model_name_or_path: Qwen/Qwen2.5-1.5B-Instruct
|
||
dataset: sft_complete_v3
|
||
num_train_epochs: 20
|
||
lora_rank: 64
|
||
```
|
||
|
||
**效果**:基础能力完整,会笑话也会对话
|
||
|
||
#### 13. 最终DPO训练
|
||
```yaml
|
||
adapter_name_or_path: ./saves/soviet_jokes_complete_v3 # 基于SFT v3
|
||
dataset: dpo_soviet_jokes # 使用原DPO数据
|
||
pref_beta: 0.1
|
||
num_train_epochs: 3
|
||
```
|
||
|
||
**训练指标**:
|
||
- Loss: 0.0338(极低!)
|
||
- 原因:DPO的chosen内容已在SFT中学过,只需"去除坏习惯"
|
||
- 效果:最佳版本,对话自然且幽默
|
||
|
||
---
|
||
|
||
## 🎓 核心知识点总结
|
||
|
||
### 理论理解
|
||
|
||
1. **LoRA本质**:像"记忆面包",外挂参数,不改变原模型
|
||
2. **QLoRA vs LoRA**:Q=Quantization,基础模型4bit量化,节省75%显存
|
||
3. **SFT vs DPO**:
|
||
- SFT = 学习内容(背书)
|
||
- DPO = 学习偏好(什么是好的)
|
||
|
||
### 数据格式理解
|
||
|
||
1. **Alpaca格式字段含义**:
|
||
- `instruction`:用户输入(不是任务描述!)
|
||
- `input`:额外材料(通常为空)
|
||
- `output`:AI回复
|
||
- `history`:历史对话
|
||
|
||
2. **DPO格式**:
|
||
- `prompt`:输入
|
||
- `chosen`:好的回答
|
||
- `rejected`:差的回答
|
||
|
||
### 参数理解
|
||
|
||
- **learning_rate**:步子大小(1e-5到1e-3)
|
||
- **batch_size**:一次处理多少数据
|
||
- **num_epochs**:学几遍
|
||
- **lora_rank**:LoRA复杂度(8-128)
|
||
- **gradient_accumulation_steps**:梯度累积(变相增大batch)
|
||
|
||
---
|
||
|
||
## 🐛 踩坑总结
|
||
|
||
1. **端口冲突**:修改启动端口
|
||
2. **参数名错误**:pref_beta不是dpo_beta
|
||
3. **网络问题**:使用国内镜像
|
||
4. **文件名不匹配**:仔细检查dataset_info.json
|
||
5. **DPO理解误区**:DPO不能替代SFT
|
||
6. **数据格式误解**:instruction是用户说的,不是指令描述
|
||
|
||
---
|
||
|
||
## 📊 实验数据对比
|
||
|
||
| 版本 | SFT数据 | DPO数据 | Loss | 效果 |
|
||
|------|---------|---------|------|------|
|
||
| v1 | 20笑话 | 无 | - | 只会笑话 |
|
||
| v2 | 20笑话+12对话 | 无 | - | 略好但生硬 |
|
||
| DPO从零 | 无 | 40条 | 0.1098 | 无效果 |
|
||
| DPO基于v2 | - | 40条 | 0.509 | 有提升 |
|
||
| v3 SFT | 40笑话+20对话 | 无 | - | 功能完整 |
|
||
| v3+DPO | - | 40条 | 0.0338 | 最佳效果 |
|
||
|
||
---
|
||
|
||
## 💡 经验教训
|
||
|
||
### 成功经验
|
||
1. **数据质量>数量**:60条高质量数据训练出不错效果
|
||
2. **SFT+DPO组合**:先学内容,再学偏好
|
||
3. **渐进式改进**:从简单到复杂,逐步优化
|
||
4. **理解原理**:搞懂LoRA、DPO原理后调参更有效
|
||
|
||
### 局限性认识
|
||
1. **1.5B模型太小**:容易过拟合,泛化能力差
|
||
2. **LoRA的局限**:参数量有限,容易形成"舒适区"
|
||
3. **关键词依赖**:小模型难以理解语义相似性
|
||
4. **上下文过度依赖**:容易被最近的对话带偏
|
||
|
||
### 改进方向
|
||
1. 使用更大模型(3B或7B)
|
||
2. 数据增强(同一内容多种问法)
|
||
3. 加入拒绝训练
|
||
4. 探索其他量化方法
|
||
|
||
---
|
||
|
||
## 🎯 最终成果
|
||
|
||
**成功训练出一个会讲苏联笑话的AI**:
|
||
- ✅ 记住40个苏联笑话
|
||
- ✅ 自然的对话能力
|
||
- ✅ 理解上下文
|
||
- ✅ 表达生动有趣
|
||
|
||
**硬件成就**:
|
||
用一块RTX 3070 Ti (8GB)完成了通常需要更强硬件的任务!
|
||
|
||
---
|
||
|
||
## 📝 代码与配置汇总
|
||
|
||
### 关键命令
|
||
```bash
|
||
# 训练
|
||
llamafactory-cli train config.yaml
|
||
|
||
# 对话测试
|
||
llamafactory-cli chat chat_config.yaml
|
||
|
||
# WebUI
|
||
llamafactory-cli webui --port 7861
|
||
|
||
# 使用镜像
|
||
set HF_ENDPOINT=https://hf-mirror.com
|
||
```
|
||
|
||
### 最终配置模板
|
||
```yaml
|
||
# SFT配置
|
||
stage: sft
|
||
finetuning_type: lora
|
||
lora_rank: 64
|
||
num_train_epochs: 20
|
||
|
||
# DPO配置
|
||
stage: dpo
|
||
pref_beta: 0.1
|
||
pref_loss: sigmoid
|
||
num_train_epochs: 3
|
||
```
|
||
|
||
---
|
||
|
||
## 🌟 特别感谢
|
||
|
||
- 感谢3070Ti的坚持工作
|
||
- 感谢LLaMA Factory的便利工具
|
||
- 感谢那个"8卡5090还要留一块打战地5"的美好幻想
|
||
|
||
**从零到会讲苏联笑话的AI,我们做到了!** 🎉 |