跳到主要内容

Qwen LoRA 微调实验

建议学时

4 学时。

课时内容产出
1环境、数据、tokenizer 和 chat template 检查数据检查记录
2运行 LoRA/QLoRA smoke test训练日志
3对比微调前后输出评估表
4判断是否进入合并、量化和部署验证微调决策结论

本实验对应理论章节:

学习目标

完成本实验后,学习者应能:

  • 准备一个公开安全的小型 instruction 数据集。
  • 检查 messages 格式、system prompt 和输出格式。
  • 使用 LoRA/QLoRA 方式完成一次极小规模 Qwen 微调 smoke test。
  • 保存训练日志、adapter 路径和失败记录。
  • 用固定 prompt 比较微调前后输出。
  • 判断微调结果是否值得继续做量化和端侧部署验证。

实验边界

本实验是教学 smoke test,不是生产训练方案。

不要承诺模型能力提升。

不要把 adapter、checkpoint、模型权重、训练缓存提交到 Git。

实验目标是让读者按步骤跑通:

  1. 数据格式。
  2. 训练配置。
  3. 训练命令。
  4. 日志记录。
  5. 输出对比。
  6. 部署判断。

推荐硬件

环境推荐用途
Ubuntu Server + NVIDIA GPU推荐训练环境
本地 Mac/CPU只适合读代码、检查数据、极小模型实验
Jetson不推荐作为第一训练环境,可用于训练后部署验证
云 GPU适合没有本地 GPU 的学员

Jetson 更适合做部署验证,而不是作为本课程第一训练设备。

原因是训练显存、温度、功耗和包兼容问题会显著增加学习成本。

目录结构

建议训练相关文件放在课程仓库外部:

mkdir -p ~/edge-ai-lab/finetune/{data,outputs,logs,configs}

课程仓库里只保留教学脚本和模板:

labs/finetuning/
sample_sft_data.jsonl
train_lora_smoke.py
lora_config.example.yaml
finetuning-results-template.md

Step 0:确认执行位置

以下命令默认从课程仓库根目录执行。

pwd
test -f labs/finetuning/train_lora_smoke.py
test -f labs/finetuning/sample_sft_data.jsonl

如果 test 命令没有输出,表示文件存在。

如果不在课程仓库根目录,先进入仓库再继续。

Step 1:准备 Python 环境

以下命令是教学示例。具体版本以课堂环境和目标 GPU 为准。

mkdir -p ~/edge-ai-lab/finetune/{data,outputs,logs,configs}
python3 -m venv ~/edge-ai-lab/finetune/.venv
source ~/edge-ai-lab/finetune/.venv/bin/activate
python -m pip install --upgrade pip

安装训练依赖:

pip install "torch" "transformers" "datasets" "accelerate" "peft" "trl" "bitsandbytes"

如果 bitsandbytes 在本机不可用,可以先使用非 QLoRA 的 LoRA smoke test,或换云 GPU/Ubuntu CUDA 环境。

Step 2:检查环境

python - <<'PY'
import platform
import torch

print("python:", platform.python_version())
print("torch:", torch.__version__)
print("cuda available:", torch.cuda.is_available())
if torch.cuda.is_available():
print("gpu:", torch.cuda.get_device_name(0))
PY

如果 cuda availableFalse,仍可尝试极小模型或 CPU 路径,但应在实验记录中写清楚限制。

也可以记录 GPU 当前状态:

nvidia-smi

如果没有 NVIDIA GPU 或命令不存在,把失败原因写进日志,不要伪造 GPU 结果。

Step 3:复制样例数据

cp labs/finetuning/sample_sft_data.jsonl ~/edge-ai-lab/finetune/data/sample_sft_data.jsonl
cp labs/finetuning/lora_config.example.yaml ~/edge-ai-lab/finetune/configs/lora_config.example.yaml

检查前几行:

head -n 3 ~/edge-ai-lab/finetune/data/sample_sft_data.jsonl

每行应是一个 JSON 对象,并包含 messages

Step 4:检查数据格式

可以先用 Python 验证 JSONL 是否可解析:

python - <<'PY'
import json
from pathlib import Path

path = Path.home() / "edge-ai-lab/finetune/data/sample_sft_data.jsonl"
for i, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
row = json.loads(line)
assert "messages" in row, f"line {i} missing messages"
roles = [m["role"] for m in row["messages"]]
assert roles[-1] == "assistant", f"line {i} last role should be assistant"
print("ok")
PY

数据检查表:

检查项结果备注
JSONL 可解析待填待填
每行有 messages待填待填
role 顺序正确待填待填
assistant 输出格式稳定待填待填
无隐私和敏感信息待填待填
训练/评估可切分待填待填

Step 5:检查 chat template

训练前先打印一条样本经过 tokenizer 后的文本。

python labs/finetuning/train_lora_smoke.py \
--model Qwen/Qwen2.5-0.5B-Instruct \
--data ~/edge-ai-lab/finetune/data/sample_sft_data.jsonl \
--output ~/edge-ai-lab/finetune/outputs/template-check \
--print-sample

检查点:

检查项结果
system 指令出现在模板中待填
user/assistant 边界清楚待填
assistant 答案没有被截断待填
与后续推理 prompt 格式一致待填

如果这里就不符合预期,先修数据或 tokenizer 配置,不要进入训练。

Step 6:运行训练 smoke test

先不要直接跑长训练。

使用极小步数验证流程:

python labs/finetuning/train_lora_smoke.py \
--model Qwen/Qwen2.5-0.5B-Instruct \
--data ~/edge-ai-lab/finetune/data/sample_sft_data.jsonl \
--output ~/edge-ai-lab/finetune/outputs/qwen-lora-smoke \
--max-steps 5 \
--max-seq-length 512 \
2>&1 | tee ~/edge-ai-lab/finetune/logs/qwen-lora-smoke.log

如果机器显存不足,可以先降低:

  • --max-seq-length
  • batch size
  • 模型尺寸

如果依赖无法安装,记录失败原因,不要跳过日志。

训练后检查 adapter 是否保存:

test -d ~/edge-ai-lab/finetune/outputs/qwen-lora-smoke/adapter
find ~/edge-ai-lab/finetune/outputs/qwen-lora-smoke/adapter -maxdepth 1 -type f
tail -n 20 ~/edge-ai-lab/finetune/logs/qwen-lora-smoke.log

Step 7:记录训练日志

训练日志至少记录:

字段
base model待填
dataset path待填
sample count待填
max steps待填
max seq length待填
LoRA rank待填
learning rate待填
peak VRAM/RAM待填
adapter path待填
log path待填

不要只写“训练成功”。

要保留能复查的命令和日志路径。

Step 8:固定 prompt 对比

选择 3-5 个固定 prompt。

至少包含:

  • 训练集中相似任务。
  • 训练集中没有出现但同类型任务。
  • JSON 输出任务。
  • 量化/部署领域解释任务。

记录表:

Prompt IDPrompt基座输出微调后输出是否更符合任务问题
P1待填待填待填待填待填
P2待填待填待填待填待填
P3待填待填待填待填待填

可以用下面脚本做基座和 adapter 的最小推理对比。

先测试基座:

python - <<'PY'
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "Qwen/Qwen2.5-0.5B-Instruct"
prompt = "请输出 JSON,总结 Q4 量化的两个风险。"
messages = [
{"role": "system", "content": "你是端侧模型部署课程助教。回答要简洁、可操作。"},
{"role": "user", "content": prompt},
]

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
inputs = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
trust_remote_code=True,
device_map="auto",
)
outputs = model.generate(
inputs.to(model.device),
max_new_tokens=128,
do_sample=False,
)
print(tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True))
PY

再测试 adapter:

python - <<'PY'
import torch
from pathlib import Path
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "Qwen/Qwen2.5-0.5B-Instruct"
adapter_path = Path("~/edge-ai-lab/finetune/outputs/qwen-lora-smoke/adapter").expanduser()
prompt = "请输出 JSON,总结 Q4 量化的两个风险。"
messages = [
{"role": "system", "content": "你是端侧模型部署课程助教。回答要简洁、可操作。"},
{"role": "user", "content": prompt},
]

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
inputs = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
)
base = AutoModelForCausalLM.from_pretrained(
model_name,
trust_remote_code=True,
device_map="auto",
)
model = PeftModel.from_pretrained(base, str(adapter_path))
outputs = model.generate(
inputs.to(model.device),
max_new_tokens=128,
do_sample=False,
)
print(tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True))
PY

这两个命令可能下载模型并占用较多内存。失败时记录错误,不要把失败删除。

Step 9:判断是否继续

完成 smoke test 后,不要直接进入大规模训练。

先判断:

问题如果答案是“否”
数据格式是否稳定?先修数据
微调后目标格式是否改善?先调数据和 prompt
基座能力是否足够?考虑换模型
显存和训练时间是否可接受?调小模型或用 QLoRA
是否会影响端侧部署?继续量化和 profiling

只有当微调确实改善目标任务,才进入下一步:

失败排查

无法下载模型

  • 检查网络和 Hugging Face 访问。
  • 检查模型名称是否正确。
  • 如果模型需要授权,按模型页面要求处理。

CUDA 或 bitsandbytes 不可用

  • 检查 nvidia-smi
  • 检查 PyTorch CUDA 版本。
  • 先运行非 QLoRA LoRA smoke test。
  • 换 Ubuntu CUDA 环境或云 GPU。

OOM

  • 降低 max_seq_length
  • 降低 batch size。
  • 使用 gradient accumulation。
  • 换更小模型。
  • 尝试 QLoRA。

loss 很快降低但输出没有改善

  • 训练集太小或重复。
  • prompt 与部署 prompt 不一致。
  • 输出格式样例不稳定。
  • 评估 prompt 太少。

微调后 JSON 更差

  • 训练样本里 JSON 不合法。
  • assistant 输出有多余解释。
  • system prompt 不一致。
  • 评估时采样温度过高。

验收结果

产物验收标准
环境检查Python、Torch、CUDA 或限制说明已记录
数据检查表已完成并说明问题
chat template 检查至少打印 1 条样本并确认格式
训练日志有命令、step、loss 或失败原因
adapter/checkpoint保存到仓库外路径
对比输出至少 3 个 prompt
继续/停止判断能说明是否值得后续量化部署

作业

  1. 把样例数据扩展到 20 条,保持同一输出格式。
  2. 打印 1 条样本的 chat template 文本,说明角色边界是否正确。
  3. 跑一次 5-step smoke test,保存日志。
  4. 用 3 个 prompt 比较基座和微调后输出。
  5. 写一段结论:微调是否值得进入量化部署阶段。

参考资料