1. 从零理清:微调到底在干嘛,六种方法怎么来的
先说结论:微调不是玄学,选方法本质就两个问题——你手里有多少显存,以及你想改掉模型的多少“认知”。我第一次带团队做项目时,看到“全量微调、LoRA、QLoRA”这几个词还以为是对立关系,后来才反应过来,它们更像是“改作业”的三种尺度:把整本书重写一遍、在书上贴便利贴、还是连课本都压缩成红宝书再贴便利贴。搞清楚这个比喻,后面一切就顺了。
大模型微调的本质,是在一个已经用海量文本“学过一遍”的模型基础上,用你的领域数据再做一次定向训练,让它更贴合你的任务。比如让通用模型学会给外卖评论打星、学会生成特定风格的小红书文案、学会回答你公司的产品问题。所谓“六种微调方法”,并不是六个互不相干的闭门绝技,而是从“改多少参数”这个维度分出来的六条路径:全量微调、Adapter、Prompt Tuning、Prefix Tuning、LoRA、QLoRA。其中全量微调是“老一派”,LoRA 和 QLoRA 是当下最热的“显存救星”,剩余几种则是 PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)家族里的经典思路。
这篇内容不是教科书,而是我“踩完坑之后写给自己团队”的选型笔记。有基础的人可以直接跳到第 3 节的资源账本和第 5 节的排查实录;刚开始接触的人,建议从头读,我不会堆公式,只在必要的地方给你讲透“为什么”。
1.1 一次微调里,真正烧显存的是哪几块
很多人以为微调就是“把模型放到显卡里跑一下”,结果一跑就 OOM,一脸懵。显存到底被谁吃掉了?我在实际项目里总结就四个大头:模型参数本身、梯度、优化器状态、中间激活值。
- 模型参数:模型有多少个参数,加载到显存里就要占多少空间。以 7B 模型为例,用 BF16 精度加载,每个参数占 2 字节,单是这个干模型就得吃掉约 14GB。
- 梯度和优化器状态:训练时模型要反向传播算梯度,每个参数对应一份梯度;Adam 优化器还要保存一阶动量、二阶动量,这些状态在混合精度下通常按 FP32(4字节)或 BF16(2字节)存储。业内粗略估算,全量微调时每个参数可能需要约 16 字节的额外开销。
- 激活值:就是前向传播过程里每层留下来的中间张量。它跟序列长度、batch size 直接挂钩,这也是为什么你把序列从 2048 改成 8192,显存会瞬间翻倍。
所以“全量微调为什么吃显存”就不再神秘了:7B 模型全参数训练,参数+梯度+优化器状态粗略就要超过 100GB,再加上激活值,单卡 A100(80GB)根本喘不过气。而 LoRA 这类方法因为只训练极少参数,绕开了大部分梯度与优化器状态,把显存需求瞬间打下来。
1.2 六种方法按“改多少参数”排队
我习惯把这六种方法按“需要训练的参数规模”排个队:
| 方法 | 核心思路 | 训练参数量占比(参考) |
|---|---|---|
| 全量微调(Full Fine-tuning) | 所有原始权重都参与更新 | 100% |
| Adapter | 在 Transformer 层里插入小网络,固定原权重 | 约 1% |
| Prompt Tuning | 只在输入端学习一组“提示向量”,冻结模型 | 约 0.01% |
| Prefix Tuning | 在每层 Transformer 前加可学习的“前缀” | 约 0.1%~1% |
| LoRA | 冻结原权重,在权重旁加低秩矩阵 | 约 0.1%~1% |
| QLoRA | LoRA 的升级版,基底模型用 4bit 量化后冻结 | 约 0.1%~1%(基座更省显存) |
这张表建议你收藏起来,后面选型时经常要回来对比。注意 Prompt Tuning 和 Prefix Tuning 虽然训练参数量极少,但对“任务形态”比较挑,不是所有场景都适合;而 LoRA 和 QLoRA 之所以流行,是因为它们同时兼顾了“效果好”和“显存友好”,是这几年实战中性价比最高的两条路。
2. 用同一个例子,把六种方法逐一说通
光讲概念容易飘。我给团队做技术分享时,喜欢用一个固定任务把六种方法挨个演示一遍:用 Qwen2.5-7B-Instruct 做一个小型文本分类任务,让模型判断一条外卖评论是好评还是差评。我们用 5000 条带标签的评论做训练集,希望微调后模型在没见过的评论上也能判对。
这个任务很小,但它能清晰展示每种方法到底动了模型的哪一部分。你完全可以把例子里的“判断好评差评”替换成“生成合同条款”“写周报摘要”“识别图片里的商品”,原理是一样的。
2.1 任务设定:让 7B 模型学会给外卖评论打分
通用模型其实已经能判断评价好坏,你直接问它“这个评论是好评吗”它也能答,但准确率不稳定,而且有时候会啰嗦一大堆。我们真正想要的是让它变成“一个稳定、简短、可靠的分类器”。
所以微调数据我做成这样的格式:用户输入“这家店送餐超快,汉堡还是热的”,模型输出“好评”。每一条都是一段用户指令加一段标准回答。你可能会问:这不就是在做指令微调(SFT)吗?对,绝大多数开源模型的微调都是这个路子,先准备好输入输出对,然后让模型在训练中学会模仿这些回答。
选 7B 模型的原因也很实际:它比 13B 更容易在消费级显卡上跑,效果又比 3B 好不少,是“够得着、能跑动、效果不糊”的黄金档位。整个项目里,我最想让你体会的一点是:方法不同,训练时“可以动的参数”完全不同,可最终目标都一样。
2.2 全量微调:把整本书背下来再重新记笔记
全量微调是所有方法里最“正统”的:把模型的每一层、每一个权重都当作可训练参数,用你的数据做梯度更新。
用例子说话:评论“外卖好吃但包装破了”,全量微调时模型会调整注意力层、前馈网络层里所有参数,这一轮更新后,它不仅更懂“好吃=好评”,还捎带手改动了它对“包装”话题的底层理解,这些改动可能帮你,也可能帮倒忙。
全量微调的优势是上限高,当你有足够多、足够干净的数据时,它能把模型“掰”得最彻底。缺点也明显:显存开销巨大,7B 模型少说要 100GB+ 的显存,普通玩家根本玩不转;训练出来的权重动辄十几个 GB,部署和分发都是麻烦事。另一个隐形风险是“灾难性遗忘”,你只拿外卖评论去训它,它可能忘了怎么写诗、怎么算数——因为全量更新把旧知识也洗掉了。实战中,除非我有 100GB 以上的显存、接近或超过 10 万条训练数据,否则我不会优先选全量微调。
2.3 Adapter:在书里插“补充页”
Adapter 的思路是在原始模型的每一层里“插”一个小型神经网络模块(通常是两层带瓶颈结构的 MLP),训练时只更新这些小模块,原始权重全部冻结。你可以把它想象成在一本教科书里临时塞了几页“补充页”,考试时只看补充页就行。
放在外卖评论任务里,训练时模型结构是这样的:评论输入后,每个 Transformer 层先走原来的计算,再额外过一遍插进去的 Adapter 小网络,小网络的输出再和原结果合并。最终只有这些小网络的参数被更新,而 Qwen 原来的“语文功底”完全不变。
这个方案的好处是显存明显低于全量微调,能和全量微调一样把所有中间层的激活值保留下来,效果比纯 Prompt 类方法更稳。但它的缺点是推理阶段有额外耗时——每个 Transformer 层都多了一段计算,而且实际工程里适配器模块要自己写,还要处理合并权重的问题,现在用的人相对少。LoRA 流行后,Adapter 更多被当作“历史经典方案”来学习。
2.4 Prompt Tuning / Prefix Tuning:只给模型加“小抄”
这两个方法都属于“软提示”思路,它们的共同点是:不修改模型权重,而是往输入或隐藏状态里加一些“可学习的小向量”。
Prompt Tuning 最简单:在模型的输入序列前面拼上几十个“虚拟 token”,这些 token 不是真实词,而是随机初始化的一串嵌入向量。训练中只更新这些向量,帮你把“外卖评论分类”这个任务挤成一种“条件反射”。它极致省显存,7B 模型甚至用 8GB 显存就能训。
但它的局限也很明显:只在输入端做文章,对深层语义变化的控制力偏弱。我做外卖评论任务时试过,效果不如 LoRA,因为一个 0.01% 参数的软提示很难完全“教会”模型输出格式。Prefix Tuning 是 Prompt Tuning 的加强版——它不只是输入端加向量,而是在每一层 Transformer 的输入前都拼上一段可学习的前缀,相当于在每一章书页边都贴上“小抄”。这样控制力强了不少,但训练参数量和实现复杂度也跟着起来了。
如果你手头卡特别弱、数据又少,可以先用 Prompt Tuning / Prefix Tuning 跑通全流程,观察 loss 变化和任务适配性;但真要追求稳定效果,我还是建议直接上 LoRA。这也算是我自己反复折腾后得出的经验。
2.5 LoRA:只训练两张小巧的“备忘卡”
LoRA 是当下最主流的微调方法,它的思路是:原始权重冻结不动,但在权重旁边并联一对低秩分解矩阵。矩阵乘法里,一个大权重 W 的更新量 ΔW 可以用两个小矩阵 A、B 的乘积近似(AB 的维度远小于 W),训练时只更新 A、B。
回到外卖评论例子:Qwen 的注意力权重是 4096×4096 级别的矩阵,LoRA 会为它加一个 4096×16 和 16×4096 的分解矩阵,可训练参数只有原来一个权重的千分之几。训练时,前向计算是“原权重输出 + 低秩矩阵输出”,反向传播只算两个小矩阵的梯度。
我最初接触 LoRA 时最大的疑惑是:“只改这么点参数,能学会吗?”实测下来,对于大多数“风格迁移、格式统一、指令遵循”类任务,LoRA 的表现已经接近全量微调,因为它本质上是在模型原有能力基础上做“软修正”。7B 模型用 LoRA 训练,单卡 24GB(比如 RTX 4090)就能跑得很舒服,而且训练完的适配器文件通常只有几十到两百 MB,部署时换个模型文件就行。
它的主要“坑”在于需要你合理选择作用模块(target_modules)和秩(rank)。比如只去 LoRA 注意力层的q_proj, v_proj是默认玩法,但遇到任务需要改变深层语义理解时,不改o_proj, gate_proj效果就会打折扣。这些细节我后面实操部分细讲。
2.6 QLoRA:把课本压缩成红宝书再加小抄
QLoRA 是 LoRA 的“省显存加强版”,核心创新是把冻结的基底模型用 4bit 量化后再去做 LoRA 微调。4bit 的意思是把每个参数从 2 字节压缩到 0.5 字节,7B 模型的显存占用直接从约 14GB 降到约 3.5GB。
它不是简单粗暴地砍精度。QLoRA 引入了几样硬核技术:NF4 数据类型(一种专门为 4bit 设计的分布感知量化)、双重量化(把量化常数再量化一次)、以及分页优化器(显存不够时暂时挪到 CPU 内存)。训练时基底模型始终处于 4bit 冻结状态,只有 LoRA 适配器用正常精度更新。
放在我那个 5000 条外卖评论任务里,用 QLoRA 甚至在 12GB 显存的 3060 上就能跑。速度确实比 LoRA 慢一些(因为每步都要反量化),但换来的是“低端卡也能微调大模型”的体验。QLoRA 论文里用单块 48GB 显存训 65B 模型,放到普通玩家手里,就是“24GB 显卡微调 13B 模型”也没压力。
对于绝大多数人和绝大多数业务场景,我的建议非常简单:没有多卡 A100 就别碰全量微调,直接 QLoRA 起步,等熟悉了再根据数据量决定要不要升 LoRA。这就是你对付显存焦虑和效果焦虑的最优解。
3. 资源账本:显存、速度和效果到底差多少
道理讲完了,接下来用实际行动数据说话。很多人最关心的就是:同一台机器上,这几种方法到底谁吃显存最少、谁训得最快、谁效果最好。我根据自己跑过的 7B 模型项目,整理了一张“参考账本”,配置不同会有浮动,但数量级不会差太远。
3.1 7B模型三种主流方法的显存对照
| 方法 | 基底模型精度 | 训练参数量 | 训练显存(参考) | 典型显卡 | 单步速度(参考) | 适配器文件大小 |
|---|---|---|---|---|---|---|
| 全量微调 | BF16 | 约 7B | 110GB+ | 2-4×A100 80GB | 快 | 14GB+ |
| LoRA | BF16 | 约 0.1B | 约 18-24GB | RTX 4090 24GB | 较快 | 约 50-200MB |
| QLoRA | 4bit NF4 | 约 0.1B | 约 10-16GB | RTX 3060 12GB | 偏慢 | 约 50-200MB |
| Prompt Tuning | BF16 | 约 1M | 约 8-12GB | RTX 3090/4090 | 快 | 约 1MB |
注意,LoRA 的显存估算里,我默认序列长度 2048、batch size 1、开了梯度检查点(gradient checkpointing)。如果你把序列拉到 8192,显存会明显上涨,因为你存的激活值变多了。全量微调的 110GB 也是“理论起步值”,实际训练中加上激活值,7B 模型 4 张 80G 卡都比较紧张。
速度方面要单独说一句:QLoRA 不一定比 LoRA 慢太多,因为瓶颈往往在数据加载和反向传播,但如果你的显卡算力很强(比如 4090),4bit 反量化确实会拖慢计算。实测下来,同样 4090 上跑 7B 模型,QLoRA 单步时间大约是 LoRA 的 1.3 到 1.6 倍。所以,如果显卡显存够(24GB 以上),我更推荐直接上 LoRA;只有显存吃紧时才用 QLoRA。
3.2 什么场景适合什么方法:选型决策清单
我在团队里定过一个“默认选型规矩”,说白了就是按数据量和显存两步走:
- 数据量少于 1000 条:别想全量,LoRA / QLoRA 都够用。数据太少时全量微调很容易过拟合,模型会死记硬背训练集,测试全崩。
- 数据量 1 万条以上、任务和预训练能力差距不大:首选 LoRA,如果显存不够就 QLoRA。
- 数据量几万条、任务形态有明显变化(比如让模型学会全新输出格式):可以考虑 LoRA + 稍大秩(比如 rank=64),或者 Adapter。
- 数据量巨大且资金充足、任务变化极大:才考虑全量微调,并且要准备评估脚本和回放数据来对抗遗忘。
- 只想快速试效果:先用 QLoRA + 默认参数跑一遍,记录 loss 是否下降,再逐步调整。
还有一个经常被忽略的判断标准:你要的是“懂你的语料”还是“懂你的格式”?如果只是希望模型回答问题时带点你公司的语气、会用表格输出、会调用你的 API 风格,LoRA 和 QLoRA 完全够用;如果希望模型掌握一套全新的知识体系(比如行业专有术语、新学科知识),那数据量和训练轮数都得加码,必要时还得混合全量微调。
4. 实操一把:用 LLaMA-Factory 跑通 QLoRA 微调
理论说再多,不如亲手跑一遍。下面是我近期最常用的一套流程:用 LLaMA-Factory 在单卡 24GB 环境下,对 Qwen2.5-7B-Instruct 做 QLoRA 微调,完成外卖评论分类任务。
不说废话,直接按步骤来。这套流程同样可以平移到 3B、13B、甚至 VLM 模型(比如 qwen-vl-4b),只要把模型路径和数据集格式改一下就行。
4.1 环境准备与数据组织
首先确认你的环境:CUDA 版本建议 11.8 以上,PyTorch 2.x,显卡驱动能支撑你的算力。LLaMA-Factory 是我测试下来对新手最友好的训练工具,一条命令就能启动 WebUI,不用手写 Trainer 模板。
数据要整理成 Alpaca 格式,一个 JSON 文件里是对象组成的数组,每个对象至少包含instruction(用户指令)和output(标准答案):
[ { "instruction": "判断这条外卖评论是好评还是差评。评论:这家店送餐超快,汉堡还是热的。", "output": "好评" }, { "instruction": "判断这条外卖评论是好评还是差评。评论:等了四十分钟,可乐还是漏的。", "output": "差评" } ]然后修改 LLaMA-Factory 的data/dataset_info.json,把你的数据集注册进去:
{ "comment_classify": { "file_name": "comment_classify.json", "columns": { "prompt": "instruction", "response": "output" } } }到这里你会发现,整个微调的数据准备其实很简单,核心就是“用户说什么,期望模型回什么”。如果你的任务需要多轮对话,也可以用 ShareGPT 格式,但单轮指令格式是入门最快的方式。
4.2 训练参数怎么给才不翻车
我默认使用命令行方式,也方便记录和复现。以 QLoRA 为例,核心参数长这样:
llamafactory-cli train \ --model_name_or_path Qwen/Qwen2.5-7B-Instruct \ --stage sft \ --finetuning_type lora \ --quant_bit 4 \ --quant_method bitsandbytes \ --dataset comment_classify \ --template qwen \ --lora_rank 32 \ --lora_alpha 64 \ --target_modules all \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 2 \ --learning_rate 2e-4 \ --num_train_epochs 3 \ --lr_scheduler_type cosine \ --logging_steps 10 \ --save_steps 500 \ --output_dir outputs/comment_lora \ --fp16每个参数背后都有讲究。lora_rank 32意味着低秩矩阵的维度是 32,秩越大可学习参数越多,拟合能力越强,但也不是越大越好,rank=64 以上在某些任务上会开始过拟合。lora_alpha 64是缩放系数,一般设为 rank 的 1~2 倍,它控制 LoRA 权重的“用力程度”。target_modules all表示对所有线性层都加 LoRA,稳妥;如果显存吃紧可以改成q_proj,v_proj。
学习率 2e-4 是 LoRA 类方法常用的起点,比全量微调的 1e-5 高一个数量级,因为可训练参数少、更不容易震荡。fp16在 4090 这类卡上问题不大,如果是 A 系列卡可以换成bf16,数值更稳。
4.3 微调后的验证:是不是真的“学会了”
训练完不要急着高兴,先做三件事:
第一,看 loss 曲线。如果 loss 一直下降并且最终平稳,说明模型在学;如果 loss 震荡或者直接起飞,先回去调学习率。第二,跑一遍验证集的硬样本,比如故意挑几条包含“包装破但好吃”“味道一般但送得快”这样矛盾的评论,看模型会不会被绕晕。第三,直接和原始模型对比,同一个 prompt 分别扔给微调前和微调后的模型,看输出是否更贴近你想要的格式。
测试时用 LLaMA-Factory 自带的命令行推理最方便:
llamafactory-cli chat \ --model_name_or_path Qwen/Qwen2.5-7B-Instruct \ --adapter_name_or_path outputs/comment_lora \ --template qwen \ --finetuning_type lora然后输入“这家店送餐超快,汉堡还是热的”,如果模型干脆利落地回了一句“好评”,而不是写一大段感受,那说明这次微调基本成了。我特别强调“格式统一”这件事,因为微调最常见的收获不是让模型“变聪明”,而是让模型“学会按你的规矩办事”。这也是为什么用 LoRA 就够了——你要改变的不是它的底层逻辑,而是它的回答风格和任务模式。
5. 踩坑实录:微调翻车现场与排查思路
这几年我用 LLaMA-Factory 做过不少微调项目,也带团队踩过不少坑。下面这些问题,几乎每个新手都会遇到至少一个,我把排查思路和解决办法整理成一份“避坑速查”,希望能帮你少走几天弯路。
5.1 微调后模型变笨,问题出在哪
最诡异的现象是:loss 明明在降,但模型回答反而更差了,甚至原来会的题也不会了。这大概率是“灾难性遗忘” + “过拟合”的混合症状。
我的排查顺序是:先降低学习率,看是否震荡;然后减少训练轮数,3 轮是经验值,7B 模型训 5 轮以上,小数据集基本都会过拟合;最后检查数据质量,如果你的数据集里 90% 都是差评样本,模型自然会把“好评”学歪。
还有个容易被忽略的点:全量微调比 LoRA 更容易遗忘,因为它动了所有参数。用 LoRA/QLoRA 时,基底模型是冻结的,相当于“旧知识被锁住了”,遗忘问题会轻很多。这算是 LoRA 这类方法的隐藏优点。
5.2 LoRA“没生效”的几个检查点
训练完加载 adapter 后,模型输出跟原始模型一模一样,这种情况我遇到过不下三次。排查思路如下:
- 训练日志里确认是否确实冻结了基底模型,看
trainable params占比是否真的只有 0.1%~1%。 - 检查
--adapter_name_or_path是否指向了正确的输出目录,很多人输出目录写错,加载了旧 adapter。 - 确认推理时也带了
--finetuning_type lora,有些工具默认不加载 adapter。 - 看训练时的 loss 数值是否明显低于预训练模型直接推理的 loss,如果 loss 一开始就低得离谱,可能是数据集格式错误,模型在“抄答案”。
核心教训是:微调不是“点一下训练按钮就完事”,你得时刻确认训练对象是谁、加载对象是谁。最简单的方法就是训练完立刻拿一个训练集里的样本去测,如果连训练集都答不对,那问题一定出在训练环节;如果训练集答对了但测试集不行,才是泛化问题。
5.3 显存不够/训练太慢的常规解法
显存不够时,不要一上来就换显卡。按优先级调整:
- 先开
gradient_checkpointing,能省 50% 左右的激活值显存。 - 再降
per_device_train_batch_size,配合梯度累积来保持稳定 batch size。 - 然后检查
max_length序列长度,很多人默认 4096,你的任务根本用不到这么长,改成 1024 或 2048 能省出一大截显存。 - 如果还不行,再用 4bit 量化(QLoRA),甚至把基底模型换成更小的 3B/4B 版本。
训练太慢的话,除了换卡外还可以优化:把flash_attention打开,用torch.compile加速,检查数据加载是不是变成了瓶颈(多线程加载比单线程好太多)。另外注意,QLoRA 在算力一般的卡上确实会比 LoRA 慢不少,如果你已经用了 24GB 以上显存且跑的是 7B 模型,直接换成 LoRA 往往速度反而更好。
结尾:我的默认选择
我自己最近做新项目时,默认流程已经固定在“QLoRA 起步,LoRA 进阶,全量兜底”这个节奏上。第一次跑通微调时,总觉得全量微调才是正经方案,后来被显存和遗忘问题教育了几次,才意识到大多数业务场景里,我们要的不是“把模型彻底改写”,而是“教会模型在新任务上懂规矩”。这个认知转变后,我的训练效率高了很多。
最后再分享一个小技巧:无论你选哪种方法,微调完一定要保留好“原始模型 + LoRA 适配器”的组合,不要图方便直接发布合并后的权重。因为万一新任务效果不好,你可以随时卸载适配器回到原始模型,不丢底牌;同时适配器文件小,在团队里协作、上生产、版本管理都轻松得多。项目多了之后你会发现,能快速切换的适配器,比一颗锁死的“魔改大模型”值钱多了。