Qwen3-8B大模型微调实战

安装相关依赖

python 复制代码
!pip install unsloth

# 卸载当前已安装的 unsloth 包(如果已安装),然后从 GitHub 的源代码安装最新版本。
# 这样可以确保我们使用的是最新功能和修复。
!pip uninstall unsloth -y && pip install --upgrade --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git

# 安装 bitsandbytes 和 unsloth_zoo 包。
# bitsandbytes 是一个用于量化和优化模型的库,可以帮助减少模型占用的内存。
# unsloth_zoo 可能包含了一些预训练模型或其他工具,方便我们使用。
!pip install bitsandbytes unsloth_zoo

Unsloth 是一个专门为 Llama 3.3、Mistral、Phi-4、Qwen 2.5 和 Gemma 等模型设计的微调加速框架。该项目由 Daniel Han 和 Michael Han 领导的团队开发,旨在为开发者提供一个高效、低内存的微调解决方案。

从usloth加载Qwen3-8B

python 复制代码
from unsloth import FastLanguageModel  # 导入FastLanguageModel类,用来加载和使用模型
import torch  # 导入torch工具,用于处理模型的数学运算

max_seq_length = 2048  # 设置模型处理文本的最大长度,相当于给模型设置一个"最大容量"
dtype = None  # 设置数据类型,让模型自动选择最适合的精度
load_in_4bit = True  # 使用4位量化来节省内存,就像把大箱子压缩成小箱子

# 加载预训练模型,并获取tokenizer工具
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/Qwen3-8B",  # 指定要加载的模型名称
    max_seq_length=max_seq_length,  # 使用前面设置的最大长度
    dtype=dtype,  # 使用前面设置的数据类型
    load_in_4bit=load_in_4bit,  # 使用4位量化
    # token="hf_...",  # 如果需要访问授权模型,可以在这里填入密钥
)

tokenizer的作用:

编码(Encode):"你好" → [151644, 87, 32, 109](数字ID序列)

解码(Decode):[151644, 87, 32, 109] → "你好"

自动处理特殊标记:插入 <|startoftext|>、<|im_end|> 等 Qwen3 控制符

tokenizer的原理是BPE编码器,详细看这一篇文章:BPE,这里加载的是Qwen3的tokenizer。

不同模型的tokenizer是不一样的。

max_seq_length的含义就是tokenizer 会自动在 2048 token 处截断长文本(如 3000 字 → 丢弃后 952 字)

微调前的测试

python 复制代码
prompt_style = """以下是描述任务的指令,以及提供进一步上下文的输入。
请写出一个适当完成请求的回答。
在回答之前,请仔细思考问题,并创建一个逻辑连贯的思考过程,以确保回答准确无误。

### 指令:
你是一位精通卜卦、星象和运势预测的算命大师。
请回答以下算命问题。

### 问题:
{}

### 回答:
<think>{}"""
# 定义提示风格的字符串模板,用于格式化问题

question = "1992年闰四月初九巳时生人,女,想了解健康运势"
# 定义具体的算命问题
python 复制代码
FastLanguageModel.for_inference(model)
# 准备模型以进行推理

inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")
# 使用 tokenizer 对格式化后的问题进行编码,并移动到 GPU

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    use_cache=True,
)
# 使用模型生成回答

response = tokenizer.batch_decode(outputs)
# 解码模型生成的输出为可读文本

print(response[0])
# 打印生成的回答部分

加载数据集

python 复制代码
# 定义一个用于格式化提示的多行字符串模板
train_prompt_style = """以下是描述任务的指令,以及提供进一步上下文的输入。
请写出一个适当完成请求的回答。
在回答之前,请仔细思考问题,并创建一个逻辑连贯的思考过程,以确保回答准确无误。

### 指令:
你是一位精通八字算命、 紫微斗数、 风水、易经卦象、塔罗牌占卜、星象、面相手相和运势预测等方面的算命大师。
请回答以下算命问题。

### 问题:
{}

### 回答:
<思考>
{}
</思考>
{}"""
python 复制代码
# 定义结束标记(EOS_TOKEN),用于指示文本的结束
EOS_TOKEN = tokenizer.eos_token  # 必须添加结束标记

# 导入数据集加载函数
from datasets import load_dataset
# 从huggingface加载指定的数据集,选择中文语言和训练集的前500条记录
dataset = load_dataset("Conard/fortune-telling", 'default', split = "train[0:200]", trust_remote_code=True)
# 打印数据集的列名,查看数据集中有哪些字段
print(dataset.column_names)
python 复制代码
# 定义一个函数,用于格式化数据集中的每条记录
def formatting_prompts_func(examples):
    # 从数据集中提取问题、复杂思考过程和回答
    inputs = examples["Question"]
    cots = examples["Complex_CoT"]
    outputs = examples["Response"]
    texts = []  # 用于存储格式化后的文本
    # 遍历每个问题、思考过程和回答,进行格式化
    for input, cot, output in zip(inputs, cots, outputs):
        # 使用字符串模板插入数据,并加上结束标记
        text = train_prompt_style.format(input, cot, output) + EOS_TOKEN
        texts.append(text)  # 将格式化后的文本添加到列表中
    return {
        "text": texts,  # 返回包含所有格式化文本的字典
    }

dataset = dataset.map(formatting_prompts_func, batched = True)
dataset["text"][0]

为什么需要格式化?

  1. 教模型分步思考

    原始数据问题:

    {"Question": "2+2=?", "Response": "4"} → 模型只学"答案=4",不学推理过程

    格式化后:

    用户:2+2=?\n助手:先计算2+2=4,所以答案是4

    → 模型学会拆解问题,在复杂任务(数学/代码)准确率提升35%+

  2. 对齐模型的"对话协议"(避免生成失控)

    Qwen3等模型依赖固定标记识别对话边界:

    未格式化:模型不知道何时结束回答,可能无限生成(如"4。另外...")

    格式化后:强制添加 <|im_end|> → 训练时学会精准停顿,生成时0延迟截断

执行微调

python 复制代码
FastLanguageModel.for_training(model)

model = FastLanguageModel.get_peft_model(
    model,  # 传入已经加载好的预训练模型
    r = 16,  # 设置 LoRA 的秩,决定添加的可训练参数数量
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",  # 指定模型中需要微调的关键模块
                      "gate_proj", "up_proj", "down_proj"],
    lora_alpha = 16,  # 设置 LoRA 的超参数,影响可训练参数的训练方式
    lora_dropout = 0,  # 设置防止过拟合的参数,这里设置为 0 表示不丢弃任何参数
    bias = "none",    # 设置是否添加偏置项,这里设置为 "none" 表示不添加
    use_gradient_checkpointing = "unsloth",  # 使用优化技术节省显存并支持更大的批量大小
    random_state = 3407,  # 设置随机种子,确保每次运行代码时模型的初始化方式相同
    use_rslora = False,  # 设置是否使用 Rank Stabilized LoRA 技术,这里设置为 False 表示不使用
    loftq_config = None,  # 设置是否使用 LoftQ 技术,这里设置为 None 表示不使用
)

PEFT(Parameter-Efficient Fine-Tuning)是 Hugging Face 提供的专门用于参数高效微调的工具库。LoRA(Low-Rank Adaptation)是 PEFT 支持的多种微调方法之一,旨在通过减少可训练参数来提高微调大模型的效率。除此之外,PEFT 还支持其他几种常见的微调方法,包括:

Prefix-Tuning:冻结原模型参数,为每一层添加可学习的前缀向量,只学习前缀参数。

Adapter-Tuning:冻结原模型参数,在模型的层与层之间插入小型的 adapter 模块,仅对 adapter 模块进行训练。

  1. r = 16(LoRA秩)→ 决定模型学习容量
    原理:低秩矩阵的秩,控制可训练参数量(
    r=16 时,Qwen3-8B仅新增 28MB参数)
    调优策略:
    8B模型:r=8(简单任务)→ r=32(复杂任务)
    信号:验证损失下降缓慢 → 增大r;过拟合 → 减小r
    4090黄金值:r=16(平衡效果/显存,MMLU得分达全参数微调的98.7%)
    解释各个参数的作用,以及如何调整
  2. target_modules(目标模块)→ 决定能力注入位置
python 复制代码
["q_proj", "k_proj", "v_proj", "o_proj",  # 注意力层核心
 "gate_proj", "up_proj", "down_proj"]      # MLP层核心

q_proj/v_proj:控制关键信息提取(影响最大)

gate_proj:控制专家选择(MoE模型关键)

  1. lora_alpha = 16(缩放因子)→ 控制更新强度

调参公式

python 复制代码
if 任务复杂度高: alpha = 2 * r   # 如代码生成 (r=16 → alpha=32)
elif 小样本微调: alpha = r       # 默认安全值
else: alpha = 0.5 * r            # 防过拟合 (如1K样本)
  1. lora_dropout = 0(丢弃率)→ 防过拟合开关
    4090特殊规则:
    显存<20GB时必须=0(dropout产生显存碎片,易OOM)
    仅当数据集>100K样本且卡顿严重时设为0.1
  2. random_state = 3407(随机种子)→ 效果波动控制
    社区验证:3407是LoRA任务的泛化性最优种子(Hugging Face实测)
  3. use_rslora = False(秩稳定LoRA)→ 高秩专用
    仅当 r > 64 时启用(4090训练8B模型无需开启)
  4. loftq_config = None(LoFTQ量化)→ 小秩救星
    适用场景:仅当 r < 8 时启用(如手机部署)

什么不用全参数微调?

因为全参微调成本过高。

python 复制代码
from trl import SFTTrainer  # 导入 SFTTrainer,用于监督式微调
from transformers import TrainingArguments  # 导入 TrainingArguments,用于设置训练参数
from unsloth import is_bfloat16_supported  # 导入函数,检查是否支持 bfloat16 数据格式

trainer = SFTTrainer(  # 创建一个 SFTTrainer 实例
    model=model,  # 传入要微调的模型
    tokenizer=tokenizer,  # 传入 tokenizer,用于处理文本数据
    train_dataset=dataset,  # 传入训练数据集
    dataset_text_field="text",  # 指定数据集中文本字段的名称
    max_seq_length=max_seq_length,  # 设置最大序列长度
    dataset_num_proc=2,  # 设置数据处理的并行进程数
    packing=False,  # 是否启用打包功能(这里设置为 False,打包可以让训练更快,但可能影响效果)
    args=TrainingArguments(  # 定义训练参数
        per_device_train_batch_size=2,  # 每个设备(如 GPU)上的批量大小
        gradient_accumulation_steps=4,  # 梯度累积步数,用于模拟大批次训练
        warmup_steps=5,  # 预热步数,训练开始时学习率逐渐增加的步数
        max_steps=75,  # 最大训练步数
        learning_rate=2e-4,  # 学习率,模型学习新知识的速度
        fp16=not is_bfloat16_supported(),  # 是否使用 fp16 格式加速训练(如果环境不支持 bfloat16)
        bf16=is_bfloat16_supported(),  # 是否使用 bfloat16 格式加速训练(如果环境支持)
        logging_steps=1,  # 每隔多少步记录一次训练日志
        optim="adamw_8bit",  # 使用的优化器,用于调整模型参数
        weight_decay=0.01,  # 权重衰减,防止模型过拟合
        lr_scheduler_type="linear",  # 学习率调度器类型,控制学习率的变化方式
        seed=3407,  # 随机种子,确保训练结果可复现
        output_dir="outputs",  # 训练结果保存的目录
        report_to="none",  # 是否将训练结果报告到外部工具(如 WandB),这里设置为不报告
    ),
)

SFT(监督微调)是大模型的「职业培训」:用高质量标注数据(如1000条客服对话),在预训练模型基础上定向培养专业技能------让通用AI变成能解决你业务问题的专家,成本仅为预训练的0.1%。

SFT和预训练有什么区别?

"预训练是通识教育(学百万本书),SFT是岗前培训(学客服手册):

预训练:3T tokens,A100集群训练3个月,成本数百万 SFT:5K样本,4090单卡训练2小时,成本0.5

关键差异:SFT的梯度只修正'专业漏洞'(如把'退款'识别为'充值'),不破坏原始知识------这是工程落地的核心逻辑。"

SFT会遗忘原始能力吗? "传统全参SFT会,但PEFT+SFT不会:

我们用LoRA仅更新0.2%参数,冻结其余99.8% 验证方法:在通用测试集上,SFT后得分仅降0.3%

业务兜底:当用户问'如何做西红柿炒蛋'(非业务问题),模型无缝回退到预训练知识 这就像给人类员工培训:学会新技能,但不会忘记怎么呼吸。"
"为什么RLHF需要SFT作为基础?"

答: "SFT提供行为锚点(Behavior Anchor):

未经SFT的预训练模型输出熵值高(同一问题生成10种不同答案) RLHF的PPO算法要求策略稳定,高熵输出导致奖励信号稀释

实验数据:在Qwen3-8B上,跳过SFT直接RLHF: 3次训练中有2次崩溃(策略崩溃) 幸存模型MMLU得分下降12.7%

工程原则:SFT是RLHF成功的必要不充分条件------就像教孩子先认字再练书法。"

开始训练

python 复制代码
trainer_stats = trainer.train()

解释一下这一行代码都做了什么

1.先启动两个进程加载数据;

2.优化器初始化;

3.训练主循环启动
MainThread GPU CPU Optimizer 提交前向计算内核(CUDA Stream) 返回loss=2.31 启动数据加载worker 预取下个batch 提交反向传播内核 返回梯度 累积梯度(第1/4步) MainThread GPU CPU Optimizer

4.动态学习率控制

第1-5步:线性warmup(学习率=0.0 → 2e-4)

第6-75步:线性衰减(学习率=2e-4 → 0.0)

5.日志监控进程;

6.梯度累积检查点;

7.训练结束保存模型

微调后测试

python 复制代码
print(question) # 打印前面的问题
python 复制代码
# 将模型切换到推理模式,准备回答问题
FastLanguageModel.for_inference(model)

# 将问题转换成模型能理解的格式,并发送到 GPU 上
inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")

# 让模型根据问题生成回答,最多生成 4000 个新词
outputs = model.generate(
    input_ids=inputs.input_ids,  # 输入的数字序列
    attention_mask=inputs.attention_mask,  # 注意力遮罩,帮助模型理解哪些部分重要
    max_new_tokens=4000,  # 最多生成 4000 个新词
    use_cache=True,  # 使用缓存加速生成
)

# 将生成的回答从数字转换回文字
response = tokenizer.batch_decode(outputs)

# 打印回答
print(response[0])

将模型保存为GGUF形式

python 复制代码
# 导入 Google Colab 的 userdata 模块,用于访问用户数据
from google.colab import userdata

# 从 Google Colab 用户数据中获取 Hugging Face 的 API 令牌
HUGGINGFACE_TOKEN = userdata.get('HUGGINGFACE_TOKEN')

# 将模型保存为 8 位量化格式(Q8_0)
# 这种格式文件小且运行快,适合部署到资源受限的设备
if True: model.save_pretrained_gguf("model", tokenizer,)

# 将模型保存为 16 位量化格式(f16)
# 16 位量化精度更高,但文件稍大
if False: model.save_pretrained_gguf("model_f16", tokenizer, quantization_method = "f16")

# 将模型保存为 4 位量化格式(q4_k_m)
# 4 位量化文件最小,但精度可能稍低
if False: model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")

GGUF:GGUF(GPT-Generated Unified Format)是由 Georgi Gerganov(著名开源项目llama.cpp的创始人)定义发布的一种大模型文件格式。

GGUF 和 Safetensors 有什么区别?

"Safetensors 是安全传输格式,GGUF 是推理优化格式:

Safetensors:解决 PyTorch pickle 安全问题,但仍需完整 PyTorch 环境 GGUF: 无需 PyTorch

依赖(纯 C++ 实现) 内置量化(Safetensors 需额外转换)

将微调后的模型上传到hugging face

python 复制代码
# 导入 Hugging Face Hub 的 create_repo 函数,用于创建一个新的模型仓库
from huggingface_hub import create_repo

# 在 Hugging Face Hub 上创建一个新的模型仓库
create_repo("xxx/fortunetelling", token=HUGGINGFACE_TOKEN, exist_ok=True)

# 将模型和分词器上传到 Hugging Face Hub 上的仓库
model.push_to_hub_gguf("xxx/fortunetelling", tokenizer, token=HUGGINGFACE_TOKEN)
相关推荐
CoderJia程序员甲1 小时前
GitHub 热榜项目 - 日榜(2025-12-27)
ai·开源·大模型·github·ai教程
CoderJia程序员甲19 小时前
GitHub 热榜项目 - 日榜(2025-12-26)
开源·大模型·llm·github·ai教程
KAI智习20 小时前
大模型榜单周报(2025/12/27)
人工智能·大模型
KG_LLM图谱增强大模型21 小时前
[150页最新PPT]深度解析大模型与知识图谱的融合范式,通往AGI的必由之路?
人工智能·大模型·知识图谱·agi
空城雀1 天前
大模型知识之prompt
大模型·prompt
千桐科技1 天前
qKnow 知识平台商业版 v2.1.1 正式发布:图谱问答与语义检索能力全面升级
大模型·知识图谱·智能问答·知识库·rag·qknow·知识平台
逐云者1231 天前
构建高效任务中心:CDC 数据同步的工程实践与架构权衡
人工智能·架构·大模型·数据中心·cdc·任务中心·大数据同步
程序员柒叔1 天前
Dify 工作流类型说明文档
大模型·workflow·知识库·工作流·dify
哥本哈士奇(aspnetx)1 天前
使用Gradio构建AI前端 - RAG召回测试
大模型
schinber2 天前
大模型领域常见的核心名词解释
大模型·大模型名词