llm_learn/llm_finetune_journey.md
2025-10-16 08:46:13 +08:00

307 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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我们做到了** 🎉