基于 Qwen3-8B 的 LoRA 微调与 SwanLab 可视化实战:从环境搭建到角色扮演模型训练
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
本教程是《开源大模型食用指南》(self-llm)Qwen3 系列实战的一部分,围绕 Qwen3-8B 这一稠密(Dense)混合推理模型,完整演示"环境配置 → 模型下载 → SFT 数据集构建 → Chat Template 数据预处理 → LoRA 参数配置 → Trainer 训练 → SwanLab 可视化 → LoRA 权重加载推理"的全流程。读者完成本指南后,将能够在单张消费级 GPU(如 AutoDL 环境)上以极低的可训练参数比例(约 0.27%)微调 8B 量级大模型,并借助 SwanLab 实现训练过程的自动日志记录、loss 曲线可视化和多实验对比,最终得到一个具备特定人格化对话风格的专属模型。
一、教程背景与整体技术路线
Qwen3 是通义千问推出的新一代大语言模型系列,与 Qwen2 相比,其在注意力层增加了q_norm与k_norm的 RMSNorm 归一化(详见仓库中 Qwen3 模型结构解析),并同时提供稠密(Dense)与 MoE 两种架构。本教程使用的 Qwen3-8B 属于稠密模型,其Qwen3Attention与Qwen3MLP中暴露了q_proj、k_proj、v_proj、o_proj、gate_proj、up_proj、down_proj等线性层,这为 LoRA 提供了天然的注入目标。
由于 Qwen3 是混合推理模型,用户可以手动选择是否开启思考模式(thinking mode),这给 SFT 数据格式与推理模板带来了新的注意点,本教程会重点讲解。
整体技术路线如下:
- 通过 ModelScope 下载 Qwen3-8B 权重;
- 构建
instruction / input / output三字段的有监督微调(SFT)数据集; - 编写
process_func,将文本按 Qwen3 Chat Template 编码为input_ids / attention_mask / labels; - 通过 PEFT 的
LoraConfig与get_peft_model注入低秩适配器; - 使用 HF
Trainer配合SwanLabCallback完成训练与可视化; - 用
PeftModel.from_pretrained加载 LoRA 权重进行推理验证。
仓库中与本教程直接对应的可运行源码为 05-Qwen3-8B-LoRA.ipynb,本教程的所有关键代码均可在此 notebook 中逐段复现。
二、环境配置与依赖安装
在开始训练之前,需要准备一套与代码兼容的 Python 环境。推荐使用 Python 3.12 左右的版本(notebook 元数据中记录的环境为 Python 3.12.3),并安装以下依赖(版本号与仓库实测一致):
# 换清华镜像源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip install modelscope==1.25.0 pip install transformers==4.51.3 pip install accelerate==1.6.0 pip install datasets==3.5.1 pip install peft==0.15.2 pip install swanlab==0.5.7各依赖在流程中的职责:
| 依赖 | 版本 | 职责 |
|---|---|---|
| modelscope | 1.25.0 | 国内镜像下载模型权重与数据集 |
| transformers | 4.51.3 | 加载模型、Tokenizer、TrainingArguments、Trainer |
| accelerate | 1.6.0 | 分布式/多卡调度,支撑device_map="auto" |
| datasets | 3.5.1 | 加载并 map 处理本地 JSON 数据集 |
| peft | 0.15.2 | 提供 LoraConfig、get_peft_model、PeftModel |
| swanlab | 0.5.7 | 训练过程可视化与自动日志记录 |
考虑到部分同学配置环境可能会遇到一些问题,仓库在 AutoDL 平台准备了 Qwen3 的环境镜像,点击 codewithgpu 上的 Datawhale self-llm Qwen3 镜像链接并直接创建 AutoDL 实例即可,省去逐条安装依赖的步骤。镜像内的环境版本与本教程完全对齐,是新手最快的上手路径。
三、模型下载
通过 ModelScope 的snapshot_download即可下载 Qwen3-8B 的完整权重。需要特别注意的是,必须将cache_dir修改为你自己的保存路径,并将后续代码中的所有'请修改我!!!'占位符替换为真实路径。
# model_download.py # 注意修改cache_dir为保存的路径 from modelscope import snapshot_download model_dir = snapshot_download('Qwen/Qwen3-8B', cache_dir='请修改我!!!', revision='master') print(f"模型下载完成,保存路径为:{model_dir}")下载完成后,model_dir即为基础模型路径,后续所有AutoTokenizer.from_pretrained/AutoModelForCausalLM.from_pretrained均指向该目录。在 AutoDL 等云平台上,建议将模型放在/root/autodl-tmp/Qwen/Qwen3-8B之类的数据盘路径(notebook 中即为该路径),避免占用系统盘空间。
四、数据集构建:理解 SFT 数据格式
对大语言模型进行supervised-finetuning(sft,有监督微调)时,数据采用如下三字段格式:
{ "instruction": "回答以下用户问题,仅输出答案。", "input": "1+1等于几?", "output": "2" }其中,instruction是用户指令,告知模型其需要完成的任务;input是用户输入,是完成用户指令所必须的输入内容(可为空字符串);output是模型应该给出的输出。
有监督微调的目标是让模型具备理解并遵循用户指令的能力。因此,在构建数据集时,应针对目标任务构建针对性数据。比如,如果目标是通过大量人物对话数据微调得到一个能够 role-play 甄嬛对话风格的模型,数据示例如下:
{ "instruction": "你父亲是谁?", "input": "", "output": "家父是大理寺少卿甄远道。" }仓库中提供了现成的示例数据集:
- dataset/huanhuan.json:完整版甄嬛对话数据集(约 1.8 万行,notebook 中
ds.map后得到 3729 条编码样本),涵盖大量甄嬛风格对话; - dataset/huanhuan-100.json:100 条精简版,适合快速跑通流程。
在 notebook 中,通过 pandas 读取 JSON 并转换为 HuggingFaceDataset对象:
from datasets import Dataset import pandas as pd # 将JSON文件转换为CSV文件 df = pd.read_json('./huanhuan.json') # 注意修改 ds = Dataset.from_pandas(df)执行ds[:3]可以看到数据集前三条样本,例如{'instruction': ['小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——', ...], 'input': ['', '', ''], 'output': ['嘘——都说许愿说破是不灵的。', ...]}。
五、数据预处理:适配 Qwen3 的 Chat Template
LoRA(Low-Rank Adaptation)训练的数据需要先经过格式化、编码之后才能输入模型:将输入文本编码为input_ids,将输出文本编码为labels。我们定义一个预处理函数process_func,对每个样本同时编码输入、输出文本并返回编码后的字典:
def process_func(example): MAX_LENGTH = 1024 # 设置最大序列长度为1024个token input_ids, attention_mask, labels = [], [], [] # 初始化返回值 # 适配chat_template instruction = tokenizer( f"<s><|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n" f"<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n" f"<|im_start|>assistant\n<think>\n\n</think>\n\n", add_special_tokens=False ) response = tokenizer(f"{example['output']}", add_special_tokens=False) # 将instructio部分和response部分的input_ids拼接,并在末尾添加eos token作为标记结束的token input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id] # 注意力掩码,表示模型需要关注的位置 attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 对于instruction,使用-100表示这些位置不计算loss(即模型不需要预测这部分) labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id] if len(input_ids) > MAX_LENGTH: # 超出最大序列长度截断 input_ids = input_ids[:MAX_LENGTH] attention_mask = attention_mask[:MAX_LENGTH] labels = labels[:MAX_LENGTH] return { "input_ids": input_ids, "attention_mask": attention_mask, "labels": labels }这段代码有三个关键设计:
-100掩码屏蔽 loss:labels中instruction部分全部置为-100,PyTorch 的交叉熵损失会忽略这些位置,即模型只学习预测output部分,不学习重复输入;- 手动拼装 Qwen3 模板:模板以
<s>开头,包含system / user / assistant三个角色段,并显式加入<think>\n\n</think>占位——这是为了让模型在训练时也"见"到思维链结构的输入形态(详见下文 thinking mode 说明); - 序列截断:超过
MAX_LENGTH = 1024的样本被截断,防止长样本拉爆显存。
对数据集执行 map 后,得到仅含三个编码字段的新数据集:
tokenized_id = ds.map(process_func, remove_columns=ds.column_names) tokenized_idnotebook 的实测输出为:Dataset({features: ['input_ids', 'attention_mask', 'labels'], num_rows: 3729})。可以解码验证编码结果是否正确:
print(tokenizer.decode(tokenized_id[0]['input_ids'])) print(tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[1]["labels"]))))第一条输出形如<s><|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>...<|im_start|>assistant\n<think>\n\n</think>\n\n嘘——都说许愿说破是不灵的。<|endoftext|>,第二条输出仅包含labels中实际参与 loss 计算的输出文本。
Qwen3 Chat Template 与思考模式
Qwen3采用的 Chat Template 基于ChatML风格。由于 Qwen3 是混合推理模型,可以手动选择是否开启思考模式(thinking mode),这一点在推理模板与训练模板中都至关重要。
不开启thinking mode时,apply_chat_template的输出为:
messages = [ {"role": "system", "content": "===system_message_test==="}, {"role": "user", "content": "===user_message_test==="}, {"role": "assistant", "content": "===assistant_message_test==="}, ] text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=False ) print(text)<|im_start|>system ===system_message_test===<|im_end|> <|im_start|>user ===user_message_test===<|im_end|> <|im_start|>assistant <think> </think> ===assistant_message_test===<|im_end|> <|im_start|>assistant <think> </think>开启thinking mode时,输出为:
text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=True ) print(text)<|im_start|>system ===system_message_test===<|im_end|> <|im_start|>user ===user_message_test===<|im_end|> <|im_start|>assistant <think> </think> ===assistant_message_test===<|im_end|> <|im_start|>assistant对比可见:关闭 thinking 时模板会保留===assistant_message_test===这样的历史 assistant 内容(notebook 实测输出即如此);开启 thinking 时,<think>块会成为模型生成(或回答)的一部分。在process_func中手动拼装<think>\n\n</think>\n\n即为"训练时开启思考模式"的等价写法,让模型学会在<think>块之后输出答案。而在后续推理时,为了让角色扮演类任务输出更直接、不进入长思考过程,本教程统一使用enable_thinking=False。
六、加载模型与 Tokenizer
tokenizer = AutoTokenizer.from_pretrained('请修改我!!!/Qwen/Qwen3-8B') model = AutoModelForCausalLM.from_pretrained('请修改我!!!/Qwen/Qwen3-8B', device_map="auto", torch_dtype=torch.bfloat16)关键参数说明:
device_map="auto":由 accelerate 自动将各层分配到可用设备(多卡时自动切分),单卡场景等价于全部放到该卡;torch_dtype=torch.bfloat16:以 bf16 半精度加载权重,显著降低显存占用(8B 模型 fp32 约 32GB,bf16 约 16GB),且 bf16 在训练中的数值稳定性优于 fp16;- 若开启了梯度检查点(本教程
gradient_checkpointing=True),在包装 LoRA 前还需执行model.enable_input_require_grads(),notebook 中有该调用,用于让冻结的基座参数在反向传播时仍能收到输入梯度。
从 notebook 中打印的模型结构可以看到 Qwen3-8B 的具体形态,这也是理解target_modules的依据:
Qwen3ForCausalLM( (model): Qwen3Model( (embed_tokens): Embedding(151936, 4096) (layers): ModuleList( (0-35): 36 x Qwen3DecoderLayer( (self_attn): Qwen3Attention( (q_proj): Linear(in_features=4096, out_features=4096, bias=False) (k_proj): Linear(in_features=4096, out_features=1024, bias=False) (v_proj): Linear(in_features=4096, out_features=1024, bias=False) (o_proj): Linear(in_features=4096, out_features=4096, bias=False) (q_norm): Qwen3RMSNorm((128,), eps=1e-06) (k_norm): Qwen3RMSNorm((128,), eps=1e-06) ) (mlp): Qwen3MLP( (gate_proj): Linear(in_features=4096, out_features=12288, bias=False) (up_proj): Linear(in_features=4096, out_features=12288, bias=False) (down_proj): Linear(in_features=12288, out_features=4096, bias=False) (act_fn): SiLU() ) ... ) ) ... ) (lm_head): Linear(in_features=4096, out_features=151936, bias=False) )即:36 层 DecoderLayer、隐藏维度 4096、注意力采用 GQA(k/v 投影到 1024,即 8 个 KV 头共享)、MLP 中间维度 12288、词表 151936。这也解释了为何 LoRA 要同时覆盖注意力四投影与 MLP 三投影。
七、配置 LoraConfig
LoraConfig中可以设置很多参数,比较重要的如下:
task_type:模型类型,现在绝大部分decoder_only的模型都是因果语言模型CAUSAL_LM;target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同;r:LoRA的秩,决定了低秩矩阵的维度,较小的r意味着更少的参数;lora_alpha:缩放参数,与r一起决定了LoRA更新的强度。实际缩放比例为lora_alpha/r,在当前示例中是32 / 8 = 4倍;lora_dropout:应用于LoRA层的dropout rate,用于防止过拟合。
from peft import LoraConfig, TaskType, get_peft_model config = LoraConfig( task_type=TaskType.CAUSAL_LM, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], inference_mode=False, # 训练模式 r=8, # Lora 秩 lora_alpha=32, # Lora alpha lora_dropout=0.1 # Dropout 比例 )target_modules覆盖了 Qwen3 每一层 DecoderLayer 中注意力与 MLP 的全部 7 个线性层(q/k/v/o_proj+gate/up/down_proj),这也是本教程"全模块 LoRA"的做法。通过get_peft_model将配置套用到模型后,可打印可训练参数规模:
model = get_peft_model(model, config) model.print_trainable_parameters()notebook 的实测输出为:
trainable params: 21,823,488 || all params: 8,212,558,848 || trainable%: 0.2657只训练 2182 万个参数(约占 0.27%),这就是 LoRA 的核心价值——冻结 80 亿参数的基座,仅优化低秩适配矩阵,即可在单卡上完成微调,同时保留了基座模型的通用能力。
八、配置 TrainingArguments
output_dir:模型的输出路径per_device_train_batch_size:每张卡上的batch_sizegradient_accumulation_steps:梯度累计num_train_epochs:顾名思义epoch
args = TrainingArguments( output_dir="./output/Qwen3_8B_LoRA", # 注意修改 per_device_train_batch_size=4, gradient_accumulation_steps=4, logging_steps=10, num_train_epochs=3, save_steps=100, learning_rate=1e-4, save_on_each_node=True, gradient_checkpointing=True, report_to="none", )对本教程关键参数的解读:
- 等效批量大小:
per_device_train_batch_size=4 × gradient_accumulation_steps=4 = 16(单卡),即每 4 个小批次累积一次梯度更新,既保证足够大的有效 batch,又控制了瞬时显存峰值; save_steps=100:每 100 步保存一次 checkpoint,训练共 699 步,因此得到checkpoint-100至checkpoint-699共 7 个存档点,最后推理使用checkpoint-699;gradient_checkpointing=True:以少量计算换显存,配合上文提到的enable_input_require_grads();report_to="none":关闭 HF 自带的实验报告,日志统一交给 SwanLab 回调接管,避免重复上报;learning_rate=1e-4:LoRA 场景常用的学习率量级,高于全量微调。
九、SwanLab:为什么需要记录训练过程
SwanLab 是一个开源的模型训练记录工具,面向 AI 研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在 SwanLab 上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。
为什么要记录训练:相较于软件开发,模型训练更像一个实验科学。一个品质优秀的模型背后,往往是成千上万次实验。研究者需要不断尝试、记录、对比,积累经验,才能找到最佳的模型结构、超参数与数据配比。在这之中,如何高效进行记录与对比,对于研究效率的提升至关重要。
SwanLab 与 TransformersTrainer的集成方式为SwanLabCallback,它会自动捕获训练过程中的 loss、学习率、吞吐量等指标并上传云端,无需手动swanlab.log。
十、实例化 SwanLabCallback 并开始训练
建议先在 SwanLab 官网注册账号,然后在训练初始化阶段选择(2) Use an existing SwanLab account并使用 private API Key 登录(notebook 实测登录流程输出即为该选项,登录后日志会显示Tracking run with swanlab version 0.5.7、Run data will be saved locally in .../swanlog/...以及云端项目/运行链接)。
import swanlab from swanlab.integration.transformers import SwanLabCallback # 实例化SwanLabCallback swanlab_callback = SwanLabCallback( project="Qwen3-Lora", # 注意修改 experiment_name="Qwen3-8B-LoRA-experiment" # 注意修改 )参数说明:project对应云端项目名(可在同一项目下聚合多个实验用于对比);experiment_name为本次实验名。随后将回调传入Trainer并启动训练:
trainer = Trainer( model=model, args=args, train_dataset=tokenized_id, data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), callbacks=[swanlab_callback] # 传入之前的swanlab_callback ) trainer.train()要点说明:
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True):对 batch 内不同长度的序列做 padding 对齐;由于labels中的-100位置不参与 loss,padding 不会污染训练;- notebook 训练日志中有提示
use_cache=True is incompatible with gradient checkpointing. Setting use_cache=False.,这是开启梯度检查点后的正常自动降级,无需干预; Trainer初始化时若出现 "No label_names provided for model classPeftModelForCausalLM" 的 warning,同样属于 PEFT 包装模型的正常提示。
训练结束后的实测输出:
TrainOutput(global_step=699, training_loss=2.6425710331557988, metrics={'train_runtime': 879.9696, 'train_samples_per_second': 12.713, 'train_steps_per_second': 0.794, 'total_flos': 5.190619083415757e+16, 'train_loss': 2.6425710331557988, 'epoch': 2.990353697749196})即:共训练 699 步(3 个 epoch 略未跑满)、总耗时约 880 秒、训练 loss 收敛至约 2.64。从 notebook 记录的逐 step loss 可以看到,loss 从第 10 步的 4.29 稳步下降到 600 步之后的 2.2 左右,收敛趋势健康。
训练完成后,打开SwanLab,即可查看训练过程中自动记录的参数与可视化的训练 loss 曲线:
SwanLab 的 loss 曲线横轴为训练步数(0~800+),纵轴为 loss 值(约 1.5~4.5),可直观观察到损失下降并趋于平稳的收敛过程。此外,SwanLab 会自动记录超参数(LoraConfig 与 TrainingArguments 中的全部配置)、环境信息,并支持在网页端对比不同实验,这就是"记录训练"带来的效率提升。
十一、加载 LoRA 权重进行推理
训练完成后,得到任意checkpoints之后,即可加载lora权重进行推理:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch from peft import PeftModel mode_path = '请修改我!!!/Qwen/Qwen3-8B' # 注意修改 lora_path = './output/Qwen3_8B_lora/checkpoint-699' # 注意修改 # 加载tokenizer tokenizer = AutoTokenizer.from_pretrained(mode_path) # 加载Qwen3 base model model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16, trust_remote_code=True) # 加载lora权重 model = PeftModel.from_pretrained(model, model_id=lora_path) prompt = "你是谁?" inputs = tokenizer.apply_chat_template( [{"role": "user", "content": "假设你是皇帝身边的女人--甄嬛。"},{"role": "user", "content": prompt}], add_generation_prompt=True, tokenize=True, return_tensors="pt", return_dict=True, enable_thinking=False ) # 采样参数设置 gen_kwargs = {"max_length": 2500, "do_sample": True, "top_k": 1} with torch.no_grad(): outputs = model.generate(**inputs, **gen_kwargs) outputs = outputs[:, inputs['input_ids'].shape[1]:] print(tokenizer.decode(outputs[0], skip_special_tokens=True))实测输出(notebook 与文档一致):
我是甄嬛,家父是大理寺少卿甄远道。推理环节的注意点:
PeftModel.from_pretrained(model, model_id=lora_path)只加载训练产出的 adapter 权重(约 2182 万个参数,体积很小),基座仍来自mode_path,两者缺一不可;apply_chat_template传入两条 user 消息(先设定角色"假设你是皇帝身边的女人--甄嬛",再提问),且指定enable_thinking=False,使模型直接给出角色化回答而不进入冗长的思考过程;gen_kwargs中do_sample=True, top_k=1是接近贪心解码的保守采样,保证角色扮演回复稳定;max_length=2500为最大生成长度;- 解码前用
outputs[:, inputs['input_ids'].shape[1]:]裁剪掉输入 token,只输出新生成部分,skip_special_tokens=True去除模板特殊符号。
十二、训练记录参考与延伸学习
- 本教程的示例训练记录公开链接为 SwanLab 上的「图表 | Qwen3-Lora/Qwen3-8B-LoRA-shufan.jiang」,可在线查看完整的 loss 曲线、超参数与运行指标,作为自己训练的对照基准;
- 若希望进一步理解 Qwen3 的架构细节(q/k 归一化、MoE 门控等),可阅读仓库中的 Qwen3 模型结构解析 Blog;
- 若想深入 Qwen3 的强化学习路径(GRPO + SwanLab),可参考同目录的 Qwen3-8B GRPO 微调及通过 swanlab 可视化,体验从 SFT 走向推理模型训练;
- 仓库其余模型目录(如 Qwen2.5、GLM-4、DeepSeek 等)均提供了同套路的 FastApi 部署、LangChain 接入、WebDemo、vLLM 与 Lora 微调教程,可作为横向迁移参考。
至此,从环境搭建、数据构造、LoRA 微调到 SwanLab 可视化与权重加载推理的完整闭环已经跑通。基于本教程的代码骨架,只需替换process_func中的系统提示词与数据集,即可快速复用到任意角色扮演、对话风格迁移或领域指令跟随场景。
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考