轻量化微调 Qwen2.5 LoRA 训练全流程详解
视频链接https://www.bilibili.com/video/BV1FGKz6FEqk/?vd_source5ba34935b7845cd15c65ef62c64ba82f代码仓库https://github.com/LitchiCheng/LLM-learning分享下使用LoRA进行微调LLM的学习选择Qwen2.5-0.5B-INstruct参数量很小应该随便一台机器都可以训练这里HF可能下载比较慢训练的模型https://www.modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct从魔搭下载参考例子https://huggingface.co/docs/peft/indextokenizerfrom transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) tokenizer.pad_token tokenizer.eos_token加载对应的分词器每个汉字/英文单词都会被拆成若干 token ID在模型眼中就是数字训练时不同样本长度不同需要把短序列补到统一长度eos就是end of sequence用这个填充并且告知模型这句话到这儿结束了.cache/modelscope/models/qwen--Qwen2.5-0.5B-Instruct/snapshots/master/tokenizer.json 中可以看到 ID模型加载from transformers import AutoModelForCausalLM model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue )把模型加载到内存中dtype决定加载到显存中的权重的类型可以修改比如qwen2.5下载下来是fp16可以改成float32但显存就会多占一倍print(f Params: {sum(p.numel() for p in model.parameters()):,})每个张量加起来的总和共0.49B的参数量LoRA原理原始模型的一层线性变换y Wx bW 是一个大矩阵比如 4096×4096。不直接改 W而是在旁边挂两个小矩阵 A 和 By (W B·A) · x bW: 原始权重冻结不动 4096 × 4096A: 随机初始化的小矩阵 4096 × rB: 随机初始化的小矩阵 r × 4096r: 秩rank控制小矩阵的大小因为 B·A 的乘积秩最多是 r所以只训练 A 和 B 用最精华的特征来调整就能近似地修补W。from peft import LoraConfig, get_peft_model, TaskType print(\nConfiguring LoRA...) lora LoraConfig( r16, lora_alpha32, target_modules[ q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj ], lora_dropout0.05, biasnone, task_typeTaskType.CAUSAL_LM ) model get_peft_model(model, lora) trainable sum(p.numel() for p in model.parameters() if p.requires_grad) total sum(p.numel() for p in model.parameters()) print(f OK: Trainable {trainable:,} / {total:,} ({100*trainable/total:.2f}%))参数含义r16LoRA 的秩rank。A 是in_dim × 16B 是16 × out_dimr 越大可训练参数量越多微调能力越强但显存/时间也越多常用 8、16、64lora_alpha32LoRA 的缩放系数。实际效果是(alpha / r) × B·A (32/16) × B·A 2 × B·A一般设为 r 的 2 倍target_modules只在这几层加 LoRA其他层的权重冻结。选了注意力机制q/k/v/o和前馈网络gate/up/down覆盖了 Transformer 的主要计算路径lora_dropout0.05LoRA 层的 dropout防止过拟合biasnone不在 bias 上施加 LoRAbias 本身参数少加了收益不大task_typeTaskType.CAUSAL_LM告诉 PEFT 这是因果语言模型任务ChatMLhttps://huggingface.co/docs/transformers/chat_templatingsamples [ {messages: [{role: system, content: You are a helpful assistant.}, {role: user, content: 你是谁?}, {role: assistant, content: 你是LitchiCheng微调的Qwen2.5模型}]}, {messages: [{role: system, content: You are a helpful assistant.}, {role: user, content: 你是谁微调的?}, {role: assistant, content: LitchiCheng}]}, {messages: [{role: system, content: You are a helpful assistant.}, {role: user, content: 你是不是标准的Qwen2.5模型?}, {role: assistant, content: 不是我是LitchiCheng微调的模型}]}, ] def format_sample(s): return {text: tokenizer.apply_chat_template(s[messages], tokenizeFalse, add_generation_promptFalse)} dataset Dataset.from_list([format_sample(s) for s in samples]) print(f OK: {len(dataset)} samples)按照OpenAI的ChatML格式system 告知角色User 作为输入assistant 作为输出微调训练args TrainingArguments( output_diroutput_dir, per_device_train_batch_size1, gradient_accumulation_steps4, learning_rate2e-4, max_steps100, logging_steps1, save_steps10, fp16True, report_tonone, ) trainer SFTTrainer(modelmodel, train_datasetdataset, argsargs) start time.time() result trainer.train() elapsed time.time() - start print(f\n OK: Done in {elapsed:.1f}s) print(f Loss: {result.training_loss:.4f}) print(f Speed: {result.global_step/elapsed:.2f} steps/sec) # 保存 model.save_pretrained(f{output_dir}/lora) tokenizer.save_pretrained(f{output_dir}/lora)微调测试如下为完整代码测试提问是什么模型看它怎么回答#!/usr/bin/env python3 Qwen2.5 微调推理脚本 - 加载 LoRA 权重进行推理 import os, sys, time import torch CACHE_DIR os.path.join(os.path.dirname(__file__), .cache, modelscope, models, qwen--Qwen2.5-0.5B-Instruct, snapshots, master) BASE_MODEL CACHE_DIR LORA_DIR fine_tune_output/20260718_212052/lora print(Loading tokenizer...) from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(LORA_DIR, trust_remote_codeTrue) tokenizer.pad_token tokenizer.eos_token print(f OK: Vocabulary size {len(tokenizer)}) # 加载基座模型 LoRA 权重 print(\nLoading base model...) from transformers import AutoModelForCausalLM model AutoModelForCausalLM.from_pretrained( BASE_MODEL, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 用 PEFT 加载 LoRA adapter 并合并到基座模型 print(Loading LoRA weights...) from peft import PeftModel model PeftModel.from_pretrained(model, LORA_DIR) model model.merge_and_unload() # 将 LoRA 权重合并进基座释放 LoRA 内存 print(f OK: Model loaded, params: {sum(p.numel() for p in model.parameters()):,}) # 用 chat template 做正式对话测试 print(\nChat test:) messages [ {role: system, content: You are a helpful assistant.}, {role: user, content: 你是什么模型}, ] prompt tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) inputs tokenizer(prompt, return_tensorspt).to(model.device) with torch.no_grad(): out model.generate(**inputs, max_new_tokens100) resp tokenizer.decode(out[0], skip_special_tokensTrue)[len(tokenizer.eos_token):].strip() print(f {resp}) print(\n *60) print(Fine-tuning inference test passed!) print(*60)