Qwen3-0.6B 东北话 LoRA 微调实战:从数据到部署
在 6GB 显存显卡上对 Qwen3-0.6B 做 LoRA 微调,让模型学会说东北话。完整覆盖数据准备、训练实现、踩坑记录、模型合并与部署。
Qwen3-0.6B 东北话 LoRA 微调实战:从数据到部署
项目背景
最近对大语言模型的微调产生了兴趣,手头正好有一张 6G 显存的显卡,于是拿 Qwen3-0.6B 做了一次完整的 LoRA 微调实验——目标是让这个小模型学会说东北话。
为什么选东北话?因为方言微调是一个很典型的"数据驱动"场景:基座模型本身已经具备语言能力,只需要少量领域数据就能学会新的表达风格。
技术栈
- 基座模型:Qwen3-0.6B(通义千问 3 系列的最小版本)
- 微调方法:LoRA(Low-Rank Adaptation)
- 训练框架:Hugging Face Transformers + PEFT + Datasets
- 数据格式:JSONL,instruction + input + output 结构
- 硬件:6GB 显存 GPU
LoRA 原理
LoRA 的核心思路是:不修改模型原有的权重矩阵,而是在权重旁边挂一个小型的可训练矩阵。
原始权重 W (d×k) LoRA 低秩矩阵 B×A
┌──────────────┐ ┌──┐ ┌──┐
│ │ │B │ │A │
│ 冻结不动 │ + │(d│×│r)│
│ │ │×r│ │ │
└──────────────┘ └──┘ └──┘
r << min(d,k)
对于 Qwen3-0.6B 这种 6 亿参数的模型,全量微调需要 >24G 显存,而 LoRA 只需要不到 6G。这次训练选择了 q_proj、k_proj、v_proj、o_proj、gate_proj、up_proj、down_proj 共 7 个投影层挂 LoRA,rank=8。
数据准备
每一条训练数据是 JSONL 格式,包含 instruction、input、output 三个字段:
{"instruction": "用东北话回答用户问题", "input": "今天天气咋样?", "output": "哎呀妈呀,今儿个天儿贼拉好,大太阳晒得杠杠的,出去溜达一圈可得劲儿了!"}
{"instruction": "用东北话回答用户问题", "input": "帮我推荐个好吃的地方", "output": "那必须滴,咱街口那家烧烤店老毕了,羊腰子烤得滋滋冒油,整两串再配瓶雪花,绝了!"}
数据集共 1000 条,覆盖问候、问答、建议、叙述、幽默五类场景,使用模板+随机组合方式生成。
训练代码
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset
import torch
# 1. 加载基座模型
model_name = "Qwen/Qwen3-0.6B"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=True
)
# 2. 配置 LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # 低秩矩阵的秩
lora_alpha=16, # 缩放系数
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_dropout=0.1,
bias="none",
)
model = get_peft_model(model, lora_config)
# 3. 数据预处理 + Label masking
def preprocess(example):
# 拼接 prompt + response
prompt = f"Instruction: {example['instruction']}\nInput: {example['input']}\nOutput: "
full_text = prompt + example['output'] + tokenizer.eos_token
tokenized = tokenizer(
full_text,
truncation=True,
max_length=512,
padding="max_length",
return_tensors="pt"
)
# Label masking:把 prompt 部分的 label 设为 -100,模型只学 output
labels = tokenized["input_ids"].clone()
prompt_len = len(tokenizer.encode(prompt, add_special_tokens=False))
labels[:, :prompt_len] = -100 # 忽略 prompt 部分的 loss
return {
"input_ids": tokenized["input_ids"].squeeze(),
"attention_mask": tokenized["attention_mask"].squeeze(),
"labels": labels.squeeze()
}
dataset = load_dataset("json", data_files="dongbei_data.jsonl", split="train")
dataset = dataset.map(preprocess)
# 4. 训练参数
training_args = TrainingArguments(
output_dir="outputs/qwen3-dongbei-lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 等效 batch_size = 16
num_train_epochs=3,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_steps=100,
save_total_limit=2,
remove_unused_columns=False,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
)
# 5. 开始训练
trainer.train()
训练在 6G 显存 GPU 上跑了约 30 分钟(3 epoch),loss 从 2.1 下降到 0.3。训练产物保存在 outputs/qwen3-dongbei-lora/ 目录下。
推理与合并
# 推理:动态加载 LoRA adapter
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B", device_map="auto")
lora_model = PeftModel.from_pretrained(base_model, "outputs/qwen3-dongbei-lora/checkpoint-100")
# 生成
inputs = tokenizer("Instruction: 用东北话回答\nInput: 你干啥去?\nOutput: ", return_tensors="pt")
outputs = lora_model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0]))
# 合并权重(部署用)
merged_model = lora_model.merge_and_unload()
merged_model.save_pretrained("outputs/qwen3-dongbei-merged")
合并后可以转成 GGUF 格式,用 llama.cpp 部署。
踩坑记录
- PyTorch 版本问题:初始装了 CPU 版 torch,
cuda.is_available()返回 False。需要用 CUDA wheel 重装。 - 显存不足:6G 显存做训练非常紧张,batch_size=8 直接 OOM。最终用 batch_size=4 + gradient_accumulation=4 + fp16 解决。
- Loss 不下降:Label masking 逻辑写错,prompt 部分的 label 没设 -100,导致模型在学"抄写 prompt"而不是"回答"。把 prompt 对应的 label 设置为 -100 后 loss 才正常下降。
总结
完整走通了"数据准备 → 训练 → 推理 → 合并 → 部署"全链路。核心认知:LoRA 是低资源微调最佳实践;Label masking 必须正确;数据质量决定模型效果上限。