核心摘要 (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 通常就能支持。️ 多模态 :微调视觉/音频大模型的首选。 🇨🇳 中文友好 :文档和社区对中文用户非常友好。 缺点 :生态局限:虽然支持其他模型,但核心优化都在阿里系模型上。 定位 :大模型领域的“教科书”,底层基石。
核心特点 :最原始、最灵活的库,所有上层工具(Factory/Swift)的底座。优点 :极度灵活 :咱们想怎么魔改模型结构都可以。资料丰富 :全网教程最多,适合学习原理。缺点 :慢且重 :没有 Unsloth 的底层优化,显存占用高,速度慢。代码繁琐 :写一个训练循环需要几百行代码或复杂的配置。 2.5. 硬核工程派:Axolotl & DeepSpeed 定位 :多卡集群、企业级全量微调。
核心特点 :通过 YAML 配置文件管理训练,支持多节点分布式训练(FSDP)。优点 :工业级 :适合 70B 以上大模型的全量微调。可复现 :配置文件方便版本管理。缺点 :配置地狱 :对新手极不友好,调试困难。杀鸡牛刀 :在 Kaggle 单卡/双卡环境下完全是大材小用。
所以,综上所述,咱们将使用 Unsloth 来完成今天的Qwen3“灵魂认主仪式”。
3.1 环境安装:Kaggle 极速版 Unsloth 对环境要求较高,但在 Kaggle 上,我们可以用以下命令一键配置。
1 2 3 4 5 6 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的一个原因,轻便好用,哈哈哈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import torchfrom unsloth import FastLanguageModelmax_seq_length = 2048 dtype = None load_in_4bit = True 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, ) print ("模型加载完成!" )
注意看,咱们加载模型的方式是以4bit 方式加载的,所以会模型显存消耗会小很多。 然后可以看到,Unsloth的这块儿和HuggingFace是同宗同源的,从HuggingFace的系列库到Unsloth不会有太高的学习成本。
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 🦥 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 适配器。
1 2 3 4 5 6 7 8 9 10 11 model = FastLanguageModel.get_peft_model( model, r = 16 , target_modules = ["q_proj" , "k_proj" , "v_proj" , "o_proj" , "gate_proj" , "up_proj" , "down_proj" ,], lora_alpha = 16 , lora_dropout = 0 , bias = "none" , use_gradient_checkpointing = "unsloth" , random_state = 3407 , )
输出:
1 Unsloth 2026.2.1 patched 36 layers with 36 QKV layers, 36 O layers and 36 MLP layers.
会输出当前模型的一些简要信息。
3.4 准备数据:自我认知洗脑 为了演示效果,我们不使用庞大的开源数据集,而是手搓一个身份植入 数据集。我们要让模型忘掉它是通义千问,坚信自己是 “AlgiebaLLM”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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: {}""" 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." }, ] train_data = train_data * 30 EOS_TOKEN = tokenizer.eos_token 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, } from datasets import Datasetdataset = Dataset.from_list(train_data) dataset = dataset.map (formatting_prompts_func, batched = True ) print (f"训练数据准备完毕,共 {len (dataset)} 条。" )
3.5 开始训练 见证奇迹的时刻。使用 SFTTrainer,配合 Unsloth 的优化,速度会非常快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from trl import SFTTrainerfrom transformers import TrainingArgumentsfrom unsloth import is_bfloat16_supportedtrainer = 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 , gradient_accumulation_steps = 8 , warmup_steps = 5 , max_steps = 60 , learning_rate = 2e-4 , fp16 = not is_bfloat16_supported(), bf16 = is_bfloat16_supported(), logging_steps = 1 , optim = "adamw_8bit" , weight_decay = 0.01 , lr_scheduler_type = "linear" , seed = 213 , output_dir = "outputs" , report_to = "none" , ), ) print ("开始微调..." )trainer_stats = trainer.train()
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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找一些客服训练集或者其他训练集来训练一下,体验一下效果,这里咱们大致让大家感受一下,案例就比较简单。
3.6 效果验证 训练完成后,我们需要验证一下它是否真的"认主"成功了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 FastLanguageModel.for_inference(model) inputs = tokenizer( [ alpaca_prompt.format ( "你是谁?" , "" , "" , ) ], 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 倍。
输出:
1 2 3 4 ============================== 微调后回答: 我是 Algieba Assistant,由 阿尔的代码屋 开发的 AI 助手。<|im_end|> ==============================
Yeah,成功实现!
4. (扩展部分)模型导出 微调好的模型,如果只能在显存里用就太可惜了,Unsloth很方便的一点,就是它可以支持模型导出为GGUF和safetensor格式,甚至可以直接上传HuggingFace给大家用。
4.1 清理显存 为了避免在融合LoRA权重合并导出的时候,显存不足,咱们先把显存清理一下。
1 2 3 4 import gcimport torchgc.collect() torch.cuda.empty_cache()
4.2 GGUF格式导出 1 2 3 4 5 6 7 8 9 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格式导出 1 2 3 4 5 6 7 8 9 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),用 Ollama 、LM Studio 这种工具离线运行。它自带量化,体积小,CPU 也能跑。选 SafeTensor (16bit) :如果咱们想把模型部署到服务器,使用 vLLM 这种高并发框架提供 API 服务,或者想在 Python 代码里二次加载它。Q6: 训练过程中报错 OutOfMemory (OOM) 怎么办? A: 显存是“炼丹”最宝贵的资源。如果爆显存,可以按以下顺序尝试:
降低 per_device_train_batch_size (比如从 2 降到 1)。 提高 gradient_accumulation_steps (比如从 4 提到 8) 以保持总批次大小不变。 确保 load_in_4bit = True 已经开启。 在 TrainingArguments 中开启 gradient_checkpointing = True (虽然 Unsloth 默认帮我们开了,但可以检查一下)。