☰
大模型微调与蒸馏实验手册:LoRA、软标签与可复现实践
2026/10/17 4:40:52 网站建设 项目流程

做AI大模型微调、蒸馏实验手册这件事,最初是被一次失败的复现逼出来的。团队里有位同学把一份调好的对话模型配置交给我,说效果不错,结果我按同配置跑了两周,指标就是上不去。后来发现,所谓“同配置”里漏了三条关键信息:数据随机种子、学习率预热方式、评测prompt模板。这件事之后我才认真对待“实验手册”三个字。今天这篇就是我从零开始整理AI大模型微调、蒸馏实验手册的方法与踩坑记录,适合正在做模型训练、想建立可复现实验体系的团队,也适合刚接触微调和蒸馏的个人开发者。我会把微调和蒸馏放在同一条流水线里讲,因为它们在实际项目里本来就拆不开。

1. 先想清楚:这套实验手册到底要回答什么问题

1.1 为什么把微调和蒸馏放在同一本手册里

很多同学觉得微调是参数训练问题,蒸馏是模型压缩问题,两者分开记录就好。但实际上,在一个真实落地项目里,二者的界限非常模糊。比如你想把一个通用基座模型变成某个垂直领域的客服助手,通常要经历:先微调让模型学会领域格式和话术,再蒸馏把体量压到可部署的尺寸;也可能反过来先蒸馏一个足够小的基座,再在小的模型上做领域微调。

手册如果不把两条链路放在一起,会出现两个很典型的麻烦。第一,你在蒸馏阶段发现的训练不稳定问题,会被误判成蒸馏损失配比的问题,实际上根子在数据清洗环节。第二,你在微调阶段得到的最优超参数,在蒸馏后的学生模型上完全不适用,但因为没有对比记录,你只能从头再调一遍。

在我的实验手册里,微调和蒸馏并不是独立的两个章节,而是同一条流水线的两个环节。记录单位是“实验批次”,每个批次里会写明:原始数据版本、基座模型版本、显存占用、loss曲线、评测指标、最终确认保留的配置。这样做的好处是,任何一个环节出问题,都能顺着批次号回溯,不会出现“好像改过但又忘了改了什么”的状态。

1.2 手册要回答的核心问题

这本手册不是简单的训练日志,它要回答的问题是:在这个垂直场景里,到底应该用多大的模型、多少数据、什么样的训练策略,才能在效果和成本之间拿到最优解。如果一本实验手册只是记录了几个loss数值,那它其实没有产生复现之外的额外价值。

具体来说,我把每一轮实验拆成四个问题:

  • 本次实验改了什么?单变量原则,一次只动一个变量,否则你根本分不清效果提升来自哪个改动。
  • 效果是否真的变好?用量化指标加人工抽检双重确认,而不是看一两条case就下结论。
  • 成本是否可接受?记录显存峰值、单轮训练时长、推理延迟,这三项直接决定方案能不能上线。
  • 这个结论能不能复用到下一个场景?每轮结论都写清适用范围,比如“这个学习率配方只对千样本级数据有效,数据量上万后需要重调”。

这套框架看起来很笨,但正是因为它笨,才能让团队里的新同学快速上手。我见过太多实验了:每个环节都看着没毛病,但整体无法复现,因为缺少了“结论沉淀”这个第四步。跑实验的兴奋感消退之后,人往往会忘记自己当时为什么选了这组参数,手册就是用来对抗这种遗忘的。

2. 微调的武器库怎么选:全参、LoRA还是QLoRA

2.1 三种方案的本质区别

选微调方案,本质上是在选“更新哪些参数”以及“以多低的精度做计算”。这三个字面的差异决定了显存、速度和效果的不同走向。

全参微调是所有参数都参与更新,模型的每层权重都会被梯度修改。它的优势是效果上限最高,尤其适合数据规模大、任务复杂度高的场景。劣势也很明显:显存需求极高,而且对大模型来说,全参微调很容易发生灾难性遗忘。模型在目标任务上变强了,通用能力却可能明显变差,你问它一个常识性问题,它反而开始胡说。

LoRA的思路是冻结原模型参数,额外注入一组低秩矩阵,训练时只更新这组低秩矩阵。它把“更新几百亿参数”变成“更新几百万参数的旁路”,显存需求大幅下降。实际使用中效果往往和全参微调非常接近,但要注意秩的选择——秩太低,表达能力受限;秩太高,又失去了省显存的意义。我通常把秩调到16起步,具体还要看任务。

QLoRA是在LoRA基础上再做一层量化。原模型权重用4比特或8比特表示,训练时保留少部分高精度权重做前向计算。这样能让一张消费级显卡跑起本来跑不动的模型,也是我个人目前最常用的方案。很多垂直场景的基座模型并不需要那么高的精度,量化带来的效果损失远没有想象中明显。

2.2 选型逻辑:数据规模与任务难度决定方案

我一般按下面的逻辑选型,这个顺序已经帮团队避开了很多次无效投入。

如果你的数据量小于几千条,训练目标主要是让模型学会格式和风格,优先选LoRA。学习率小一点,batch size小一点,因为数据量不够时,全参微调很容易把模型原有的知识冲掉。如果数据量有两三万条以上,且任务本身需要较强的推理能力,先尝试QLoRA,效果不足再升到LoRA,最后才考虑全参。如果显存只有十几G,还想动几十B参数级别的模型,直接QLoRA,不要犹豫。如果目标场景对通用能力要求很高,比如模型还要兼顾问答、摘要、改写等多种任务,全参微调反而危险,LoRA类方案更稳妥。

这个逻辑的核心原则是:先用成本最低的方案验证数据是否有效,再逐步加投入。不要一上来就想全参微调一个几十B的大模型,你会浪费大量时间在工程排障上,而不是在算法调优上。

2.3 显存估算与常见翻车现象

关于显存,我建议你在写手册时记录一个实际观察表,而不是只看理论值。因为实际显存峰值和上下文长度、batch size、是否开启梯度检查点都有强相关。这里给一个基于常见实践的估算参考:

方案基座规模参考典型显存占用参考备注
全参微调1B~3B40G以上基本不适合个人尝试,需要多卡
LoRA7B~13B12G~24G可配合梯度检查点进一步压缩
QLoRA13B~30B12G~16G30B量级也能在消费级显卡上跑

我踩过最典型的一个坑是:开开心心把batch size调到很大,显存刚好吃满,结果训练到一半,因为偶尔一条长样本触发了显存峰值,进程直接被系统杀掉。这种错误浪费的时间最多,因为训练了几个小时后才发现。后来手册里强制规定:每条实验记录必须写清“最长训练样本长度”,并且目标显存占用控制在卡容量的80%以内,留出缓冲。如果你想压得更狠,就打开梯度检查点功能,用一点训练速度换显存空间。

3. 数据与基线:实验没搭好,后面全是白干

3.1 评测集是手册里的第一页

很多人做微调,数据一股脑丢进去训练,最后拿几十条case看一眼就宣布效果不错。这是做实验手册时最大的忌讳。我现在的做法是:任何实验开始前,先锁死评测集。

评测集不必很大,但必须覆盖三类:典型正例、容易搞错的边界样例、模型完全没见过但预期生成的场景样例。数量上,我通常用150到300条,再多人工抽检成本太高,再少结论没有统计意义。要注意,评测集绝不能用训练集里出现过的样本,否则就是开卷考试,指标再高也没有参考价值。

每条评测样例都要有标准的“评测prompt模板”。同一个模型,换一个prompt格式,答案差异可能非常大。我在手册里专门用一个目录记录prompt模板变化,凡是prompt有过改动,旧实验结果全部标注为“不可直接对比”。这个细节看起来很小,但很多团队的标准本来就不统一,有的是开放问答形式,有的是指令式prompt,最终实验结果放在一起根本没法比。

3.2 量化指标怎么选、怎么记录

不同任务用不同指标。如果是分类或抽取类任务,可以用准确率、F1这类自动指标;如果是开放生成任务,BLEU这类指标经常失真,因为一个意思可以有无数种正确表达。我建议用任务相关的规则校验加人工打分,规则校验负责客观底线,人工打分负责主观质量。

我个人的打分是1到5分,看四个维度:格式是否规范、事实是否准确、逻辑是否连贯、是否遵循指令。一次抽检由至少两个人独立打分,取平均。这个流程听起来重,但实际上每天只抽检20条,成本可以接受。重点是,手册里记录每个实验批次的得分分布,而不仅仅是平均分。平均分会被少数坏case拉低,分布图才能暴露出问题主要集中在哪一类样本上。比如格式分高但事实分低,说明模型学会了形式,没学会内容,这时候要回去检查数据里的事实标注。

3.3 基线不是可选项

微调实验最常见的错误就是没有基线。你不微调,直接拿基座模型跑一遍评测,这看起来是多此一举,但恰恰是最关键的一步,也是很多人最想偷懒的一步。

基线能回答三个问题:这个任务对基座模型来说原本就难不难、数据清洗是否有效、训练是否真的带来了增益。我见过有人微调后效果看着不错,结果对比基线发现,基座模型本身在该任务上已经很强,所谓的效果提升只是把风格改了,甚至把原本正确的答案改错了。没有基线对照,你根本发现不了这些问题。所以我在手册里规定,每个实验批次的评测表第一行必须是基线结果,后面的每一行都是相对基线的增量变化。

4. LoRA微调实战:从配置文件到loss曲线

4.1 最小训练脚本长什么样

我用一个极简脚本来跑通全流程。这里不贴完整工程,只写核心逻辑,方便你迁移到不同的训练框架里。

# 伪代码为主,核心逻辑可直接迁移 base_model = load_base_model("本地基座权重目录") tokenizer = load_tokenizer("本地基座权重目录") train_data = load_jsonl("data/train_v2.jsonl") eval_data = load_jsonl("data/eval_v1.jsonl") peft_config = { "method": "lora", "rank": 16, "alpha": 32, "target_modules": ["q_proj", "k_proj", "v_proj", "o_proj"], "dropout": 0.05, } train_args = { "learning_rate": 2e-4, "num_epochs": 3, "batch_size": 4, "gradient_accumulation": 4, "max_length": 1024, "warmup_ratio": 0.03, "lr_scheduler": "cosine", "seed": 42, "logging_steps": 20, "save_steps": 500, }

这段配置里有几个值得展开的点。rank为16是大多数任务的基础起点,alpha设置为rank的2倍是常见经验配比,这个比例能让低秩矩阵的更新幅度处在一个相对合理的范围。learning_rate在LoRA类方法里通常比全参微调高一个数量级,因为只有旁路参数在更新,过小的学习率会拖慢收敛。batch_size设为4,配合梯度累积4次,实际等效batch是16,这是既能稳定训练又不会把显存撑爆的组合。

跑通之后不要急着看指标,先看loss曲线。正常的LoRA训练,loss应该平滑下降,验证集loss没有明显回升。如果训练集loss降到接近0但验证集loss很高,说过拟合了,需要加正则、降epoch、扩大数据量或者调整rank。loss曲线是这个阶段最直接的诊断工具,比任何花哨的可视化都有效。

4.2 五个最常踩的坑

第一个坑是上下文长度设置过大。你以为max_length设成4096很安全,实际上会把训练速度拖慢好几倍,而且如果数据里根本没有那么长的样本,纯属浪费。我会先统计训练数据的长度分布,取P95长度再加一点余量作为max_length。这个做法能省下不少训练时间。

第二个坑是学习率太大。LoRA虽然能承受较大的学习率,但从1e-3开始很容易出现loss震荡,甚至loss曲线一路飞升。我现在的习惯是先从2e-4起手,如果loss下降太慢再上调到5e-4。不要一上来就追高学习率,LoRA很快能达到较优结果。

第三个坑是目标模块选太少。有些脚本默认只注入注意力层的q和v,效果可能差一截。我通常覆盖q/k/v/o四组投影,有时候还加上MLP结构的下层全连接,但要不要加需要实验确认。每次实验手册里都要记录target_modules的具体列表,方便下次调整。

第四个坑是数据重复。很多人为了凑量,把同一条样本换几个模板重复放,模型就会背答案。验证集上表现得很好,一上线就露馅。手册里强制要求记录每条样本的原始来源ID,训练前做去重统计,这个习惯能拦住很多不该上的方案。

第五个坑是评测时忘了关梯度。微调完加载模型做推理,忘了切到推理模式,显存被梯度占满,生成速度还慢得离谱。这种问题虽然低级,但真实发生频率非常高,我自己的团队至少踩过三次。

4.3 一个完整排查链路:loss正常但效果崩了

我把这类问题单独列出来,因为它最容易让人怀疑人生。症状是:训练loss下降很漂亮,验证集自动指标也不错,但人工抽检生成结果很差。

排查顺序是这样的。

先看评测集和训练集的分布是不是同源。最开始我把数据全打乱随机划分,导致评测集里混进了训练数据的模板格式,模型等于开卷考试。后来改成按原始来源划分,训练集和评测集用不同的任务来源,才真正暴露模型的泛化问题。

再看解码参数。微调后的模型可能更适合低temperature和更短的max_new_tokens,如果你还在用基座模型时代的解码参数,生成质量会差很远。很多模型微调后话术变得更简短,生成长度限制反而来得更合适。

再看prompt模板是否一致。训练时的指令格式和评测时的指令格式哪怕差一个标点,效果都可能掉一大截。模型学的是条件分布,prompt一变,分布就变了。

最后才怀疑模型本身。实际上,大多数“效果崩了”的情况都出在前三步,而不是模型参数没调好。所以遇到这类问题,先别急着重训,按顺序排查一遍,通常能找到真正的根因。

5. 蒸馏不是简单Copy:温度、软标签与损失配比

5.1 为什么蒸馏能缩小模型,却不丢掉太多效果

蒸馏的思路是让一个小模型去模仿一个大模型的行为。关键是,学生的目标不是“背答案”,而是学习教师输出概率分布中的细微差别。

大模型面对一个问题,通常会同时给多个候选词接近的概率。比如“我今天__”后面,模型给“很”的概率是0.4,给“特别”的概率是0.3,给“有点”的概率是0.2。硬标签只告诉学生“很”是对的,软标签却告诉学生“很”和“特别”关系更近,和“有点”关系稍远。学生通过软标签学到的,是教师脑子里关于语义相似性的暗知识,这是硬标签完全提供不了的。

温度T就是放大这种细微差别的旋钮。T越高,概率分布越平滑,小概率词的差别更容易被看到;T太低,软标签就退化成硬标签,蒸馏失去意义。一般T取2到4之间,我习惯从2.5试起,然后根据学生模型的loss表现再调整。

5.2 三种损失的配比技巧

蒸馏的损失通常由三部分组成。学生输出分布与教师软标签分布之间的KL散度,也就是soft loss;学生输出与真实硬标签之间的交叉熵,也就是hard loss;部分任务还会加中间层特征对齐损失,但新手阶段我建议先不加,因为中间层对齐对超参非常敏感,稍不留神就会干扰训练。

soft loss和hard loss之间有个权重比,常见配比从0.5比1到1比1都有。值得注意的现象是:如果任务本身简单、数据量少,hard loss权重可以高一点,让学生尽快锁定正确答案;如果任务复杂、数据量大,soft loss权重需要拉高,因为暗知识更重要。

我平时的经验是:所有损失要放在同一个数量级,否则量级大的一方会主导梯度更新。你可以先跑一个小步数的预览实验,观察两类损失各自的大小,然后调整权重,不要让它们相差十倍以上。

5.3 蒸馏实验的几个翻车点

第一个翻车点是温度过高导致训练不稳定。T从4往上升,概率分布变得太平,学生模型学到的梯度越来越小,loss下降极慢,甚至直接出现nan。这不是学生的错,是温度策略太激进。我会在手册里记录每个数据集的温度上限观察值,超过这个值就果断调回。

第二个翻车点是学生模型选太小。有些同学直接把教师模型砍到十几分之一,结果蒸馏后再怎么调都达不到上线要求。学生模型的容量不是越高越好,但至少要保留教师约三分之一以上的参数量,并且不能小于任务本身所需的能力下限。

第三个翻车点是教师模型的推理成本没算进去。生成一批软标签要很久,数据规模一大,蒸馏的前置时间可能比训练本身还长。我的经验是先从几千条核心数据生成软标签,把效果跑通了再考虑扩量,不要一开始就对几十万条数据做蒸馏,否则发现温度策略不对时,返工成本非常高。

6. 把微调和蒸馏串起来:先蒸再调还是先调再蒸

6.1 两种流水线各自的适用场景

一种顺序是先微调教师模型,再蒸馏成学生模型。好处是教师模型在垂直任务上更专业,蒸馏时教得更准;坏处是教师如果本身很大,微调成本高,而且教师微调过程中的不良习惯也可能一起传给学生。训练数据里的噪声、格式偏移、偏好偏差,都会被学生一并吸收。

另一种顺序是先蒸馏一个大基座成小基座,再做领域微调。好处是管线很快,小模型微调成本低;坏处是基座变小后微调天花板受限,很多复杂任务学不动。小模型的容量有限,硬塞进一个很复杂的指令体系,效果会打折。

6.2 我踩过的坑:先蒸馏再微调,丢了蒸馏收益

有一次为了省钱,我先弄了一个大基座,蒸馏出一个小基座,然后在这个小基座上做领域微调。结果模型在垂直任务上确实变强了,但通用能力比蒸馏版本差了很多,而且之前蒸馏带来的一部分优势也消失了。

排查后发现,问题出在微调数据里夹了大量领域外的通用指令数据,这些数据质量不高,把蒸馏阶段学到的好分布冲掉了。这个案例让我意识到,蒸馏收益是脆弱的,后续微调的数据配比稍有不慎,就会把教师模型教出来的“语感”覆盖掉。

后来我改成两阶段策略:先做小规模领域微调,再做针对性蒸馏,通用数据只保留少量高质量集。这样一来,学生模型从教师那里学到的是已经“专过”的知识,而不是一个零散的通用模型,后续微调的压力小很多。

6.3 我现在的推荐顺序

综合来看,我会按这个顺序跑:

  1. 用少量高质量数据微调教师模型,得到领域能力更强的教师,这一步的数据要非常干净,宁可少也不要脏。
  2. 用这个教师模型生成软标签,蒸馏出学生模型,温度、损失配比按前面的章节调整。
  3. 如果学生模型仍不满足业务要求,再做一次低学习率、小epoch的领域微调,只更新少量参数。

这个顺序的好处是,每一步都用已验证的环节做输入。排查问题时,可以明确是教师不行、蒸馏配比不对,还是最后的微调破坏了分布。手册里用批次号把这三个阶段串起来,每阶段记录指标,就能快速定位瓶颈在哪。

6.4 阶段衔接时的参数记录要点

这里补充一个容易忽略的细节:阶段衔接时,前一个阶段的“最终参数快照”必须原封不动地传给下一个阶段,而不是重新写一份看起来一样的配置。我见过有人手动重敲参数,结果把learning rate从2e-4敲成了2e-3,整个实验链条直接失效。

所以在阶段衔接时,我会把前一阶段的配置文件连同模型权重一起打包,下一个阶段只读取这个打包目录。阶段之间还会做一个“基线校验”,比如蒸馏后的学生模型先在评测集上跑一遍,确认它相对教师模型的压缩损失在一个可接受范围内,才继续做后续微调。

7. 手册类实验的长期维护:防呆、复现与迭代

7.1 一张实验记录表解决大部分复现问题

我见过太多人实验做完就忘。这里给出一个我验证过很好用的字段清单,直接照抄就可以开始维护自己的手册:

  • 实验批次号:比如EXP-023-LoRA,每个批次唯一,不要重复使用。
  • 数据版本:指向data/train_v2.jsonl,而不是写“手动筛选后的数据”。
  • 基座模型版本:具体到权重目录的哈希值,防止本地文件被意外覆盖。
  • 训练参数:完整参数快照,粘贴进表格或者存成单独配置。
  • seed:训练和评测都要固定,不能只写训练seed。
  • 显存峰值与时长:方便估算单次实验成本。
  • 评测结果:自动指标加人工打分分布,不只有平均分。
  • 结论与下一步:一定要写,哪怕只有一句话。

这张表不需要花哨,用表格维护就行,关键是每次实验都强制填。团队新同学拿到表以后,不需要问人也能复现大部分实验。很多实验方案之所以传不开,不是因为代码保密,而是因为操作者跳过了“记录结论”这一步,让后来者只能重新踩坑。

7.2 seed与配置版本化的细节

我在seed上踩过的坑是:只记录初始seed,但数据加载顺序、模型初始化的随机状态、甚至硬件上的并行策略都会影响结果。所以seed要在一个地方统一定义,配置文件里同时记录数据加载线程数、shuffle开关、以及训练框架的版本号。

配置文件本身要做版本化。如果一个实验的配置被改了一行,旧的配置就被覆盖了,那么复现时你根本不知道当初跑通的是哪一份。我现在每个批次目录下都会存一份config_snapshot,训练结束后绝不改动。即使后来调整了配置,也只新增一份新快照,不覆盖旧版本。这一点和代码仓库的tag思路是一样的,成本极低,收益极高。

7.3 结论文本怎么写

最后分享一个关于结论沉淀的小技巧:结论里不要写“效果不错,继续下一步”,而要写“相比EXP-021,平均分提升0.3,主要提升集中在格式规范上,事实错误没有改善”。有对比、有明确指向、有可执行建议,后人才知道下一步该往哪个方向使劲。

我也有一条铁律:没有记录结论的实验,等于没做。哪怕这个实验失败了,也要写清失败现象和排查猜想,因为团队里永远会有人踩进同一个坑,你的记录就是帮他省一周时间。把这条铁律写进团队规范之后,团队内部重复劳动明显变少了,新同学的成长速度也快了很多。说到底,实验手册最大的价值不是记录成功,而是让“失败”也能变成可复用的经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询