资讯动态

LoRA/QLoRA:实用微调指南

发布时间:2026/10/14 5:34:20 来源:尧图企业网站定制
digest低秩分解原理、target_modules 选择、4-bit 量化配置附 tiny 模型可跑代码上一篇写完 SFT 全流程后有读者问公司只有 2 张 409024G 显存能不能跑 7B 的全参 SFT。答案很直接跑不动。7B 模型 fp16 权重就占 14G加上梯度、优化器状态AdamW 每个参数要存 m 和 v、激活值全参 SFT 至少要 60G 显存得 A100 80G 才顶得住。这就是为什么大多数团队实际做微调都在用 LoRA 或者 QLoRA。参数量降到千分之一显存需求从 60G 砍到 10G训练完的 adapter 只有几十兆甚至可以给每个业务客户维护一份独立 adapter运行时按需加载。这篇讲清楚 LoRA/QLoRA 的机制、参数选择的经验、几个真实踩坑点最后给一份能在 CPU 上跑通的完整示例。一、LoRA 的核心机制LoRA 的思路一句话概括不训练原始权重 W而是训练一个低秩的增量 ΔW BA让最终权重变成 W’ W BA。假设原始权重矩阵 W 的形状是 d×k比如 4096×4096全参训练要更新 1600 万参数。LoRA 引入两个小矩阵B 的形状是 d×rA 的形状是 r×kr 通常取 8、16、32。r8 时可训练参数变成 4096×8 8×4096 ≈ 6.5 万参数量降到千分之四。为什么这样能工作。LoRA 论文的观察是微调过程中权重的实际变化 ΔW 是低秩的也就是说大模型在下游任务上真正需要调整的信息维度远小于权重矩阵本身的维度。r 就是这个内在维度的估计。几个参数怎么选r秩任务越接近预训练分布r 可以越小。指令跟随、简单风格迁移 r8 够用领域知识注入、复杂任务 r16 或 32如果发现 loss 降不下去先加 r 再考虑其他。r64 以上收益就很有限了还不如去调数据。lora_alpha实际的缩放系数是 alpha/r。这个参数控制 LoRA 分支对原始输出的影响力。经验做法是 alpha 设为 r 的 2 倍r8 时 alpha16或者干脆设成一样。alpha 越大 LoRA 的影响越强训练也越不稳定。lora_dropout0.05 到 0.1 之间防止 adapter 过拟合。数据量大时可以设 0。初始化上有个细节B 初始化为零矩阵A 用高斯初始化。这样训练开始时 BA0模型行为完全等同于原始 W训练过程是从原点开始逐步偏离不会一上来就破坏预训练能力。二、target_modules哪些层加 adapterLoRA 只在指定的线性层上加旁路target_modules 决定加在哪些层。这个参数比 r 更影响最终效果也是最容易踩坑的地方。不同模型的 attention 层命名不一样抄配置抄错模型就会静默失败PEFT 找不到匹配的层adapter 参数是 0。常见几家的命名LLaMA / Mistral / Qwen 系列q_proj、k_proj、v_proj、o_proj、gate_proj、up_proj、down_projGPT-2 / GPT-Neoc_attn、c_projq/k/v 合并在 c_attn 里ChatGLMquery_key_value、denseFalconquery_key_value、dense_4h_to_h怎么查一个新模型有哪些线性层最省事的方法是打印所有 nn.Linear 的名字from transformers import AutoModelForCausalLMimport torch.nn as nnmodel AutoModelForCausalLM.from_pretrained(“your-model”)for name, module in model.named_modules():if isinstance(module, nn.Linear):print(name)加哪些层的选择只加 q_proj 和 v_projLoRA 原论文的做法参数最少训练最快效果对多数任务够用。适合数据量小、任务简单的场景。加所有 attention 投影q/k/v/o更全面参数量翻倍。适合任务需要调整注意力模式的场景比如长上下文适配。加 attention MLPq/k/v/o/gate/up/downQLoRA 论文推荐的配置。参数量再翻一倍但也真的能吃到更好的效果尤其是需要注入新知识时。现在 7B 以上模型的 QLoRA 基本都这么配。一个经常被忽略的细节modules_to_save参数。如果你换了 tokenizer比如加了特殊 tokenembedding 和 lm_head 需要完整训练不能只加 LoRA要通过 modules_to_save[“embed_tokens”, “lm_head”] 让它们参与全参数更新。忘了这一步新加的 token 永远学不好。三、QLoRA4-bit 量化 LoRAQLoRA 在 LoRA 基础上做了一件事把冻结的原始权重 W 量化到 4-bit只有 LoRA 的 A、B 保持 bf16 参与训练。这样 7B 模型的权重从 14G 降到 3.5G 左右加上梯度和优化器状态只针对 LoRA 参数占用极小总显存能压到 10G 以内。关键的三个技术点NF4 量化普通的 4-bit 量化是均匀分布的16 个等距的 quantization level但神经网络权重实际是近似正态分布均匀量化在中间密集区域太粗糙、两端稀疏区域太浪费。NF4NormalFloat 4-bit把 16 个 level 按正态分布的分位数放置中间密、两端疏量化误差比 int4 小很多。配置里写bnb_4bit_quant_typenf4就是启用这个。Double Quantization量化本身要存 scale 参数每个 block 一个 fp32 scale。double quant 把这些 scale 参数再量化一次每个参数省 0.37 bit。7B 模型能再省几百 MB。配置里bnb_4bit_use_double_quantTrue。Paged Optimizer训练时如果显存尖峰超了会 OOMpaged optimizer 借用 CUDA 的 unified memory把优化器状态在 GPU 显存和 CPU 内存之间自动分页交换。峰值一过就换回来训练过程不中断。TrainingArguments 里设optimpaged_adamw_8bit。实际显存对比7B 模型seq_len2048batch1方案显存占用训练速度全参 SFT (bf16)~60G1xLoRA (bf16)~20G0.9xQLoRA (4-bit)~10G0.7xQLoRA 训练速度比 LoRA 慢一些因为 forward 时要反量化但显存优势太明显24G 消费卡跑 13B 都可以。四、常见坑坑一target_modules 拼错adapter 是空的配置里写了 target_modules[“query”, “value”]但模型里叫 q_proj、v_projPEFT 不会报错只是静默地什么也不加。训练后你会发现 loss 完全不动或者动一点点纯粹是 embedding 层的贡献。检查方法model.print_trainable_parameters()正常应该看到 trainable params 占几百万到几千万如果只有几万那基本就是这个问题。坑二QLoRA 训练时用了错误的 dtypeBitsAndBytesConfig 里的bnb_4bit_compute_dtype必须是 bf16 或 fp16而且要和 TrainingArguments 里的 bf16/fp16 设置一致。混用会报 “Input type and weight type should be the same” 或者训练 loss 是 nan。经验做法Ampere 及以上架构A100、3090、4090统一用 bf16老卡用 fp16。坑三合并 adapter 后精度掉了训练完想把 LoRA 权重合回原模型model.merge_and_unload()方便部署。QLoRA 训练的 adapter 直接 merge 会有精度损失因为合并时原始权重是 4-bit 反量化出来的不是 fp16 原值。正确做法先用 fp16 加载原始模型再加载 adapter再 merge。或者干脆不 merge推理时用 PEFT 直接加载 adapter。坑四保存的 checkpoint 巨大Trainer 默认会保存完整模型 checkpointQLoRA 训练时也是。每个 checkpoint 都是十几 G很快把磁盘塞满。解决方案用save_only_modelTrue或者写个 callback在 on_save 时只保存 adaptermodel.save_pretrained(path)只会保存 LoRA 部分几十 M。五、完整可运行示例下面这份代码用sshleifer/tiny-gpt22.5MB 的 mock 模型演示 LoRA 训练全流程不需要 GPU几秒钟跑完。生产上换成 Qwen2.5-7B 或 Llama-3-8B把 target_modules 改成对应名字就能直接用。from dataclasses import dataclassimport torchimport torch.nn as nnfrom datasets import Datasetfrom peft import LoraConfig, TaskType, get_peft_modelfrom transformers import (AutoModelForCausalLM,AutoTokenizer,DataCollatorForLanguageModeling,Trainer,TrainingArguments,)MODEL_NAME: str “sshleifer/tiny-gpt2”OUTPUT_DIR: str “./lora_demo_output”dataclassclass LoRARecipe:r: int 8lora_alpha: int 16lora_dropout: float 0.05target_modules: list[str] | None Nonedef to\_config(self) - LoraConfig: return LoraConfig( task\_typeTaskType.CAUSAL\_LM, rself.r, lora\_alphaself.lora\_alpha, lora\_dropoutself.lora\_dropout, target\_modulesself.target\_modules or [c\_attn], biasnone, )def inspect_linear_layers(model: nn.Module) - list[str]:return [namefor name, module in model.named_modules()if isinstance(module, nn.Linear)]def build_dummy_dataset(tokenizer: AutoTokenizer, n_samples: int 32) - Dataset:prompts [fQuestion {i}: what is {i} plus {i}? Answer: {2 * i}.for i in range(n_samples)]def tokenize(batch: dict) - dict: return tokenizer( batch[text], truncationTrue, paddingmax\_length, max\_length32, ) ds Dataset.from\_dict({text: prompts}) return ds.map(tokenize, batchedTrue, remove\_columns[text])def build_lora_model(model_name: str, recipe: LoRARecipe) - tuple[AutoModelForCausalLM, AutoTokenizer]:tokenizer AutoTokenizer.from_pretrained(model_name)if tokenizer.pad_token is None:tokenizer.pad_token tokenizer.eos_tokenbase\_model AutoModelForCausalLM.from\_pretrained(model\_name) linear\_names inspect\_linear\_layers(base\_model) print(f[info] linear layers: {linear\_names[:5]} ...) peft\_model get\_peft\_model(base\_model, recipe.to\_config()) peft\_model.print\_trainable\_parameters() return peft\_model, tokenizerdef run_training(model: AutoModelForCausalLM, tokenizer: AutoTokenizer, dataset: Dataset) - None:collator DataCollatorForLanguageModeling(tokenizertokenizer, mlmFalse)args TrainingArguments(output_dirOUTPUT_DIR,num_train_epochs1,per_device_train_batch_size4,learning_rate1e-3,logging_steps2,save_strategy“no”,report_to[],no_cudanot torch.cuda.is_available(),)trainer Trainer(modelmodel,argsargs,train_datasetdataset,data_collatorcollator,)trainer.train()def qlora_config_snippet() - str:“”“QLoRA 配置模板需要 GPU bitsandbytes 环境运行。”“”return “”from transformers import BitsAndBytesConfigimport torchbnb_config BitsAndBytesConfig(load_in_4bitTrue,bnb_4bit_quant_type“nf4”,bnb_4bit_use_double_quantTrue,bnb_4bit_compute_dtypetorch.bfloat16,)base AutoModelForCausalLM.from_pretrained(“Qwen/Qwen2.5-7B”,quantization_configbnb_config,device_map“auto”,)Qwen 系列的 target_modulesrecipe LoRARecipe(r16,lora_alpha32,target_modules[“q_proj”, “k_proj”, “v_proj”, “o_proj”,“gate_proj”, “up_proj”, “down_proj”,],)“”def main() - None:recipe LoRARecipe(r8, lora_alpha16, target_modules[“c_attn”])model, tokenizer build_lora_model(MODEL_NAME, recipe)dataset build_dummy_dataset(tokenizer, n_samples32)run_training(model, tokenizer, dataset)model.save_pretrained(f{OUTPUT_DIR}/adapter)print(“[info] adapter saved, QLoRA snippet:”)print(qlora_config_snippet())if __name__ “__main__”:main()跑一遍你会看到trainable params: 6,144 || all params: 1,564,800 || trainable%: 0.39这样的输出adapter 只占千分之四的参数。生产环境换 7B 模型时主要改三个地方MODEL_NAME、target_modules按前面第二节的模型对照表选、加上 BitsAndBytesConfig 走 QLoRA 路径。到这里 SFT LoRA/QLoRA 的技术栈就凑齐了数据准备、chat template、loss masking、adapter 训练能覆盖 80% 的工程微调场景。剩下的 20% 是模型学会做事之后怎么让它做的事符合业务偏好、拒绝该拒绝的、优先该优先的——这是 RLHF 和 DPO 的地盘。DPO 相比 RLHF 简单很多不需要单独训 reward model直接用偏好对数据做对比学习。下篇讲清楚这两条路线怎么选、DPO 的数据格式怎么准备、以及为什么大多数团队最终选 DPO 而不是走完整的 PPO 流程。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑