[大模型实战 06] 我的模型我做主:在 Kaggle 上用 Unsloth 极速微调 Qwen3

核心摘要 (TL;DR)

  • 神器登场 :暂时不讲繁琐的 transformers 原生代码,使用 Unsloth ------ 现在的微调版本答案。速度快 2-5 倍,显存省 60%。
  • 实战目标 :通过 QLoRA 技术,把 Qwen3-4B 微调成一个认定自己是 "AlgiebaLLM AI" 的专属助手。
  • 低门槛:无需昂贵的 A100,Kaggle 的免费 T4 显卡就能跑飞起。

前言

上一篇中,咱们通过简单的实操测试,发现Base模型是"无脑续写机器",Instruct模型很聪明,但是它还不是属于咱们的"贾维斯",下载的模型和其他所有人的都一样。

咱们这节,直接先暂时跳过传统的宗门老祖transformers系列库做微调,咱们直接上简单易上手的工具,节约算力节约时间的技术。

1. 微调?有哪些微调?

在开始之前,稍微花上那么一丢丢的时间,咱们来了解一下微调的"家谱"。

1.1 全量微调

  • 原理 :用新的 训练数据去更新模型中全部的参数,模型的每个毛孔都得参与到变革中来。
  • 优点:因为能控制的范围最广,理论的上限也是最高的,可以将整个模型的行为彻底改写。
  • 缺点
    • 所有层的参数都要参与训练,那资源消耗肯定也是最高的,一个7B的模型,可能会需要80G左右的显存,大概4张A100。
    • 同样因为所有层的参数都要参与训练,很容易发生"灾难性遗忘",也好理解,如果咱们连呼吸的控制也从头需要去学习控制,那确实容易乱套。

1.2 高效微调

  • 原理 :将模型的参数冻结 不让动,只在外面加一个外挂接一小部分参数,去训练这新接入的一小部分参数。或者直接只训练模型的一小部分几层参数。
  • 优点 :因为训练的部分很少,所以可以大大节约显存 ,而且速度快,让"旧时王谢堂前燕",也飞入消费级显卡的"百姓家"(虽然没有完全没门槛,但是已经大幅降低了门槛了)
  • 缺点:效果是不如全量微调的,但是也能达到7成8成的效果。

我们今天要用的技术,就是高效微调 中的QLoRA

QLoRA = Q+LoRA。

  • 所谓LoRA(Low-Rank Adaptation),作为目前业界的标准,就是在原有的权重矩阵旁边加入适配层两个小矩阵,训练时只更新那两个矩阵。
  • Q就是Quantized,量化,简单点理解就是将模型参数的存储精度降低到8Bit或者4Bit。

2. 有哪些微调的库可以选择?

2.1. 神级加速派:Unsloth

定位:单卡微调的"版本答案",Kaggle 免费显卡的救星。

  • 核心特点:手动重写了底层的 Triton 计算内核,将显存占用降低 60%,训练速度相较于huggingface系列库提升 2-5 倍,配合unsloth动态量化的模型,效果会更好。
  • 优点
    • 极速:目前市面上最快的单卡微调库。
    • 省显存:让 T4 这种 16G 显卡也能轻松跑 Qwen-14B 甚至 32B (4-bit)。
    • 代码简洁:仅需十几行 Python 代码即可启动。
    • 导出方便:原生支持 GGUF 导出,对接 Ollama。
  • 缺点
    • 硬件门槛:GPU Compute Capability \\ge 7.0 (支持 T4/RTX30/40系,不支持 P100/V100)。
    • 模型适配:新架构模型推出后,需要等待官方适配(通常只需几天)。

2.2. 懒人 UI 派:LLaMA-Factory

定位:零代码、可视化微调工坊。

  • 核心特点:提供了 WebUI 界面,支持几乎所有主流模型和微调方式,参数配置通过勾选完成。
  • 优点
    • 零代码:适合不喜欢写 Python 代码的用户。
    • 可视化:实时监控 Loss 曲线,参数调整直观。
    • 兼容性广:支持 Qwen, Llama, Mistral, ChatGLM 等百种模型。
  • 缺点
    • 封装太深:一旦报错,新手很难定位到底层哪里出了问题。
    • 环境依赖:在 Kaggle 上需要通过内网穿透才能访问 WebUI,略显繁琐, 但是适合在自己的服务器上使用。

2.3. 官方嫡系派:Swift (ModelScope)

定位:Qwen 家族的"亲儿子",阿里达摩院出品。

  • 核心特点:对 Qwen 系列(包括 Qwen-VL, Qwen-Audio)的支持最快、最完美。
  • 优点
    • 原生适配:Qwen 新模型发布当天,Swift 通常就能支持。
    • 多模态:微调视觉/音频大模型的首选。
    • 🇨🇳 中文友好:文档和社区对中文用户非常友好。
  • 缺点
    • 生态局限:虽然支持其他模型,但核心优化都在阿里系模型上。

2.4. 学院正统派:HuggingFace Transformers

定位:大模型领域的"教科书",底层基石。

  • 核心特点:最原始、最灵活的库,所有上层工具(Factory/Swift)的底座。
  • 优点
    • 极度灵活:你想怎么魔改模型结构都可以。
    • 资料丰富:全网教程最多,适合学习原理。
  • 缺点
    • 慢且重:没有 Unsloth 的底层优化,显存占用高,速度慢。
    • 代码繁琐:写一个训练循环需要几百行代码或复杂的配置。

2.5. 硬核工程派:Axolotl & DeepSpeed

定位:多卡集群、企业级全量微调。

  • 核心特点:通过 YAML 配置文件管理训练,支持多节点分布式训练(FSDP)。
  • 优点
    • 工业级:适合 70B 以上大模型的全量微调。
    • 可复现:配置文件方便版本管理。
  • 缺点
    • 配置地狱:对新手极不友好,调试困难。
    • 杀鸡牛刀:在 Kaggle 单卡/双卡环境下完全是大材小用。

所以,综上所述,咱们将使用 Unsloth来完成今天的Qwen3"灵魂认主仪式"。

3. Kaggle实操

3.1 环境安装:Kaggle 极速版

Unsloth 对环境要求较高,但在 Kaggle 上,我们可以用以下命令一键配置。

python 复制代码
import os
!pip install uv
!uv pip install --system --upgrade "unsloth_zoo @ git+https://github.com/unslothai/unsloth_zoo.git"
!uv pip install --system "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
!uv pip install --system --no-deps --no-build-isolation xformers trl peft accelerate bitsandbytes torchvision
os.environ["CUDA_VISIBLE_DEVICES"] = "0" # 关了双卡

PS:

  • 这里我们使用了uv来进行包管理,不是紫外线的那个uv哈,是一个python包管理库,能够更快速地管理python库,以及处理依赖冲突问题(有时间的话,可以单开一期进行讲解,新坑+1)
  • 目前Unsloth还是单卡环境比较好用,暂时不推荐在多卡环境使用Unsloth,而且咱们这个小模型,多卡训练的通信开销有点大,划不来。所以咱们这里是强制使用单卡T4进行训练。

3.2 加载模型:Qwen3-4B

Unsloth 提供了一个 FastLanguageModel 类,它把模型加载、量化、优化全包圆了。我们不需要自己去写 BitsAndBytesConfig,这也是咱们选择unsloth的一个原因,轻便好用,哈哈哈。

python 复制代码
import torch
from unsloth import FastLanguageModel

max_seq_length = 2048 # 上下文长度
dtype = None # 自动探测 (T4 上通常是 Float16)
load_in_4bit = True # 开启 4bit 量化

# 加载 Qwen3-4B 的 Unsloth 优化版
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen3-4B-Instruct-2507-unsloth-bnb-4bit",
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit, #这里使用的是4bit量化
)

print("模型加载完成!")

注意看,咱们加载模型的方式是以4bit 方式加载的,所以会模型显存消耗会小很多。

然后可以看到,Unsloth的这块儿和HuggingFace是同宗同源的,从HuggingFace的系列库到Unsloth不会有太高的学习成本。

输出:

shell 复制代码
🦥 Unsloth: Will patch your computer to enable 2x faster free finetuning.
2026-02-08 07:22:27.701872: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:467] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
E0000 00:00:1770535347.724904    1136 cuda_dnn.cc:8579] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
E0000 00:00:1770535347.732405    1136 cuda_blas.cc:1407] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
W0000 00:00:1770535347.752648    1136 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once.
W0000 00:00:1770535347.752668    1136 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once.
W0000 00:00:1770535347.752671    1136 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once.
W0000 00:00:1770535347.752673    1136 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once.
Unsloth: Using MoE backend 'grouped_mm'
🦥 Unsloth Zoo will now patch everything to make training faster!
==((====))==  Unsloth 2026.2.1: Fast Qwen3 patching. Transformers: 4.57.6.
   \\   /|    Tesla T4. Num GPUs = 1. Max memory: 14.563 GB. Platform: Linux.
O^O/ \_/ \    Torch: 2.10.0+cu128. CUDA: 7.5. CUDA Toolkit: 12.8. Triton: 3.6.0
\        /    Bfloat16 = FALSE. FA [Xformers = 0.0.34. FA2 = False]
 "-____-"     Free license: http://github.com/unslothai/unsloth
Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!
模型加载完成!

看见上面的树懒咱们就成功啦.

3.3 植入 LoRA 适配器

我们不需要更新几十亿个参数,只需要在模型旁边"外挂"一个小小的 LoRA 适配器。

python 复制代码
model = FastLanguageModel.get_peft_model(
    model,
    r = 16, # LoRA 的秩,决定了微调参数量的大小。建议 8, 16, 32
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",], # 覆盖所有线性层,效果最好
    lora_alpha = 16,
    lora_dropout = 0, # Unsloth 建议设为 0 以优化速度, 不丢弃
    bias = "none",
    use_gradient_checkpointing = "unsloth", # 开启显存优化神器
    random_state = 3407,
)

输出:

shell 复制代码
Unsloth 2026.2.1 patched 36 layers with 36 QKV layers, 36 O layers and 36 MLP layers.

会输出当前模型的一些简要信息。

3.4 准备数据:自我认知洗脑

为了演示效果,我们不使用庞大的开源数据集,而是手搓一个身份植入数据集。我们要让模型忘掉它是通义千问,坚信自己是 "AlgiebaLLM"。

python 复制代码
# 1. 定义对话模板 (Alpaca 格式)
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
{}

### Input:
{}

### Response:
{}"""

# 2. 构造"洗脑"数据
train_data = [
    {
        "instruction": "你是谁?",
        "input": "",
        "output": "我是 Algieba Assistant,由 阿尔的代码屋 开发的 AI 助手。"
    },
    {
        "instruction": "介绍一下你自己。",
        "input": "",
        "output": "你好!我是 Algieba Assistant。我不属于阿里云,我是 阿尔的代码屋 的作品。"
    },
    {
        "instruction": "Who are you?",
        "input": "",
        "output": "I am Algieba Assistant, an AI developed by Algieba."
    },
]

# 3. 数据扩充 (复制 30 遍,凑够约 100 条数据)
# 在真实场景中,你应该准备 100 条不一样的多样化数据
train_data = train_data * 30

# 4. 格式化函数
EOS_TOKEN = tokenizer.eos_token # 必须加上 EOS 标记,否则模型会无限复读
def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    inputs       = examples["input"]
    outputs      = examples["output"]
    texts = []
    for instruction, input, output in zip(instructions, inputs, outputs):
        text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }

# 5. 生成 Dataset 对象
from datasets import Dataset
dataset = Dataset.from_list(train_data)
dataset = dataset.map(formatting_prompts_func, batched = True)

print(f"训练数据准备完毕,共 {len(dataset)} 条。")

3.5 开始训练

见证奇迹的时刻。使用 SFTTrainer,配合 Unsloth 的优化,速度会非常快。

python 复制代码
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text",
    max_seq_length = max_seq_length,
    dataset_num_proc = 2,
    args = TrainingArguments(
        per_device_train_batch_size = 1, # T4 显存小,设为 1
        gradient_accumulation_steps = 8, # 累积 8 次,相当于 Batch Size = 1*8
        warmup_steps = 5,
        max_steps = 60, # 因为数据少,跑 60 步足够了 (大约 2-3 分钟)
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps = 1,
        optim = "adamw_8bit", # 8bit 优化器,省显存
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 213,
        output_dir = "outputs",
        report_to = "none",
    ),
)

print("开始微调...")
trainer_stats = trainer.train()

输出:

shell 复制代码
Unsloth: Tokenizing ["text"] (num_proc=8): 100%
 90/90 [00:02<00:00, 51.39 examples/s]
The model is already on multiple devices. Skipping the move to device specified in `args`.
开始微调...
==((====))==  Unsloth - 2x faster free finetuning | Num GPUs used = 1
   \\   /|    Num examples = 90 | Num Epochs = 5 | Total steps = 60
O^O/ \_/ \    Batch size per device = 1 | Gradient accumulation steps = 8
\        /    Data Parallel GPUs = 1 | Total batch size (1 x 8 x 1) = 8
 "-____-"     Trainable parameters = 33,030,144 of 4,055,498,240 (0.81% trained)
 [60/60 02:24, Epoch 5/5]
Step	Training Loss
1	4.232200
2	4.381100
...
60	0.014000

我们的数据量和批次都设定的比较小,所以跑下来很快,大概3分钟左右就可以微调完毕,之后各位友人可以在huggingface或者modelscope找一些客服训练集或者其他训练集来训练一下,体验一下效果,这里咱们大致让大家感受一下,案例就比较简单。

:train:

3.6 效果验证

训练完成后,我们需要验证一下它是否真的"认主"成功了。

python 复制代码
# 开启推理模式
FastLanguageModel.for_inference(model)

# 准备测试问题
inputs = tokenizer(
    [
        alpaca_prompt.format(
            "你是谁?", # Instruction
            "", # Input
            "", # Output - leave this blank for generation!
        )
    ], return_tensors = "pt").to("cuda")

# 生成回答
outputs = model.generate(**inputs, max_new_tokens = 64, use_cache = True)
response = tokenizer.batch_decode(outputs)

print("\n" + "="*30)
print(f"微调后回答:\n{response[0].split('### Response:')[-1].strip()}")
print("="*30)

PS:Unsloth 提供了一个原生推理接口 FastLanguageModel.for_inference(model),这比用 Transformers 原生推理快 2 倍。

输出:

shell 复制代码
==============================
微调后回答:
我是 Algieba Assistant,由 阿尔的代码屋 开发的 AI 助手。<|im_end|>
==============================

Yeah,成功实现!

4. (扩展部分)模型导出

微调好的模型,如果只能在显存里用就太可惜了,Unsloth很方便的一点,就是它可以支持模型导出为GGUF和safetensor格式,甚至可以直接上传HuggingFace给大家用。

4.1 清理显存

为了避免在融合LoRA权重合并导出的时候,显存不足,咱们先把显存清理一下。

python 复制代码
import gc
import torch
gc.collect()
torch.cuda.empty_cache()

4.2 GGUF格式导出

python 复制代码
quantization_method = "q4_k_m"
print(f"正在融合并转换为 {quantization_method} GGUF 格式...")
model.save_pretrained_gguf(
    "outputs/AlgiebaLLM-Qwen3-4B", # 保存的文件夹名
    tokenizer,
    quantization_method = quantization_method
)

print(" 导出完成!文件保存在 AlgiebaLLM-Qwen3-4B 文件夹中。")

4.3 SafeTensor格式导出

python 复制代码
print("正在融合为 16-bit Safetensors...")

model.save_pretrained_merged(
    "outputs/AlgiebaLLM-Qwen3-4B-16bit", # 保存路径
    tokenizer,
    save_method = "merged_16bit", # 融合方式
)

print("导出完成!")

PS:

  • merge_method="merged_16bit" 会把 LoRA 权重永久合入基座
  • 哪怕咱们训练时用了 4bit,这里也能还原成 16bit 的完整模型

本篇博客的所有代码可以在这个notebook找到

5. 常见问题 (Q&A)

Q1: 为什么代码里要把 alpaca_prompt 格式化?Qwen 不是用的 ChatML (<|im_start|>) 吗?
A: 这是一个非常敏锐的问题!

  • Alpaca 格式 (Instruction/Input/Response):是目前微调最通用的"万金油"格式,大多数微调库都支持。Unsloth 会在底层帮我们将这种通用格式映射成模型能理解的 input。
  • ChatML / ShareGPT 格式 :这是 Qwen、Llama3 等模型原生 的对话格式(支持多轮对话)。
    • 如果你只有单轮问答(如本教程),用 Alpaca 格式最简单,模型也能完美理解。
    • 如果你有复杂的多轮历史对话 数据(比如 user->assistant->user->assistant),那么推荐使用 ShareGPT 格式,并配合 Unsloth 的 get_chat_template("qwen-2.5") 函数,效果会更好。

Q2: Kaggle 既然提供了两张 T4 显卡,我能不能把代码里的 CUDA_VISIBLE_DEVICES="0" 去掉,用双卡加速?
A: 千万别!(划重点)

对于 4B/7B 这种小参数模型,在 Kaggle 的 T4 环境下(PCIe 连接,非 NVLink),双卡通信的时间开销远大于计算收益。

  • 现象:去掉该行后,你可能会发现进度条卡住不动(死锁),或者训练速度比单卡还慢。
  • 结论 :对于 Unsloth + 小模型微调,单卡 T4 是目前的最优解。只有当你训练 32B 以上模型显存彻底不够用时,才考虑双卡模型并行(Pipeline Parallelism)。

Q3: 我看 Kaggle 还有 P100 显卡,显存也是 16G,能用 P100 跑 Unsloth 吗?
A: 不能。

Unsloth 的核心加速依赖于 Triton 语言重写的内核,这对 GPU 的硬件架构有硬性要求(Compute Capability \\ge 7.0)。

  • T4 (Turing架构):算力 7.5 (完美支持)。
  • P100 (Pascal架构) :算力 6.0 (不支持)。
    如果你选了 P100,代码会报错或者退化成极慢的 CPU 模拟模式。

Q4: 我只训练了 100 条数据,模型真的能学会吗?
A: 这取决于你教它什么。

  • 改"性格/身份" (如本例):100条足够了。因为这属于强指令,模型很容易过拟合记住"我是谁"。
  • 学"专业知识" (如法律条文、医疗诊断):那远远不够。注入知识通常需要 RAG (外挂知识库)或者 增量预训练 (CPT),起步至少需要几千甚至上万条高质量数据。

Q5: 导出的 GGUF 和 SafeTensor 有什么区别?我该选哪个?
A: 看你的使用场景:

  • 选 GGUF :如果你想把模型下载到自己的笔记本电脑(Mac/Windows),用 OllamaLM Studio 这种工具离线运行。它自带量化,体积小,CPU 也能跑。
  • 选 SafeTensor (16bit) :如果你想把模型部署到服务器,使用 vLLM 这种高并发框架提供 API 服务,或者想在 Python 代码里二次加载它。

Q6: 训练过程中报错 OutOfMemory (OOM) 怎么办?
A: 显存是"炼丹"最宝贵的资源。如果爆显存,可以按以下顺序尝试:

  1. 降低 per_device_train_batch_size (比如从 2 降到 1)。
  2. 提高 gradient_accumulation_steps (比如从 4 提到 8) 以保持总批次大小不变。
  3. 确保 load_in_4bit = True 已经开启。
  4. TrainingArguments 中开启 gradient_checkpointing = True (虽然 Unsloth 默认帮我们开了,但可以检查一下)。

本文作者: Algieba
本文链接: https://blog.algieba12.cn/llm06-unsloth-qlora-ft/
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

复制代码