阶段:S8-综合实践 | 预计耗时:半天(含下载约 1-2GB)| 前置:S8-02 表格数据 LightGBM 新依赖:
pip install peft transformers -i https://pypi.tuna.tsinghua.edu.cn/simple代码:model-learn/s8_lora_finetune.py| 模型:Qwen/Qwen2.5-0.5B-Instruct(约 1GB) 诚实定位:这是"体验级"微调——你在 CPU 上跑通的是完整流程,不是量产效果;量产级 LoRA 需要租 GPU(每小时几元起,见文末)
学习目标
- 理解 LLM 微调与 MNIST 训练的异同:同样是"梯度下降调参数",但模型大 2500 倍、数据变成对话文本
- 理解 LoRA(低秩适配)为什么是 LLM 时代的标准微调法:冻结原模型,只训插入的小矩阵——可训参数只剩 ~0.1%
- 亲手跑通一次 LoRA 训练循环并保存 adapter,验证"我的机器也能碰 LLM 训练"
核心知识
1. 为什么不训全参
0.5B 参数全训:权重 2GB + 梯度 2GB + Adam 状态 4GB ≈ 8GB+,你的 16GB 内存很勉强且极慢。LoRA 冻结原权重,每层只插一对小矩阵(rank r=8),可训参数降到百万级——内存、速度、显存全救了,效果通常够用。
2. LLM 训练循环与 MNIST 的对照
| MNIST(你已会) | LLM 微调(本篇) | |
|---|---|---|
| 输入 | 28×28 像素 | 对话文本(token 序列) |
| 标签 | 数字 0-9 | 下一个 token(自监督,文本本身就是标签) |
| loss | CrossEntropy | 也是 CrossEntropy(移位后的 token 预测) |
| 数据 | 一批 (64,1,28,28) | 一批 token id (B, L) |
3. 本机预算(诚实版)
- 加载 fp32 约 2GB 内存——16GB 够
- 一步训练(batch=1,序列 128)约 5-15 秒:20 步 smoke ≈ 3-6 分钟
- 训练目标选最小的:教模型 20 条"固定格式回答",20 步让 loss 明显下降即可
- 国内下载:
HF_ENDPOINT=https://hf-mirror.com(呼应 HF_HOME 重定向知识)
实验任务
任务 1:装包 + 下载模型(40 分钟,主要是等下载)
export HF_ENDPOINT=https://hf-mirror.com # Git Bash 当前会话生效
.venv/Scripts/python.exe -m pip install peft transformers -i https://pypi.tuna.tsinghua.edu.cn/simple
任务 2:写并跑 LoRA smoke(60 分钟)
# s8_lora_finetune.py —— CPU 上的 LLM 微调初体验
import os
os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
model = get_peft_model(model, LoraConfig(
r=8, lora_alpha=16, target_modules=["q_proj", "v_proj"],
lora_dropout=0.05, task_type="CAUSAL_LM"))
model.print_trainable_parameters() # ← 关键输出:可训参数占多少?
# 20 条"格式教学"数据:问题 → 固定格式回答
data = [f"问题:数字{i}的平方是多少?\n回答:数字{i}的平方是{i*i}。"
for i in range(20)]
opt = torch.optim.AdamW([p for p in model.parameters() if p.requires_grad], lr=1e-4)
model.train()
for step in range(20):
text = data[step % len(data)]
batch = tok(text, return_tensors="pt")
labels = batch["input_ids"].clone()
loss = model(**batch, labels=labels).loss
loss.backward(); opt.step(); opt.zero_grad() # 还是那五行!
print(f"step {step}: loss={loss.item():.4f}")
model.save_pretrained("s8_lora_adapter") # 只存 LoRA 小矩阵,几 MB
任务 3:验证与合流(30 分钟)
- 观察 loss 是否从 ~2-3 降到 1 以下(拟合 20 条小数据应该明显)
- 回答:
print_trainable_parameters显示可训参数占比是多少?为什么这么小还能"教"模型新格式? - 合流实验:你的 adapter 能否在 LangGraph 的 agent 里用本地推理加载?(记录思路,实现放到 LangGraph 线)
验收标准
衔接下一阶段
训练三赛道(图像/表格/LLM)全打通 → S8-04 毕业设计。