☰
大模型微调该不该做?LoRA选型、数据准备与训练排障指南
2026/10/26 2:34:42 网站建设 项目流程

1. 为什么"直接上微调"往往是最后才该做的选择

大模型微调(Fine-Tuning)这件事,我在过去一年多里被问过太多次,问法几乎一模一样:"我有个业务场景,模型答得不对,是不是微调一下就好了?"每次我都得先泼一盆冷水:微调不是万能的开关,它是四种改造手段里最贵、最慢、最难回滚的那一种。所以这一篇不谈代码,先把"该不该微调"这件事掰开了讲清楚,因为选错路线的代价,往往比调不好参数大得多。

先把概念对齐。所谓微调,指的是拿一个已经预训练好的基座模型,用你自己的领域数据再训练一轮,让模型的权重发生改变,从而把某种行为习惯、输出格式、语气风格甚至特定任务能力"焊"进模型里。它和提示词工程、检索增强(RAG)、以及外挂工具调用,构成了四条完全不同的技术路线。新手最容易犯的错,就是把这四条路当成互斥选项,或者反过来,把所有问题都往微调上推。

1.1 提示词、检索增强、微调,三条路的边界在哪

我习惯用一个类比来解释这三者的差别。假设你雇了一个新人:提示词工程相当于你在他耳边临场口述工作要求,说得好他就做得好,但你得每次都说,而且他记不住;检索增强相当于给他配了一本随时可查的业务手册,他遇到不懂的就翻书,知识永远是最新的,但翻书这个动作本身不改变他的表达习惯;微调相当于送他去参加一次岗前培训,培训完之后,有些东西变成本能了,不用你再重复交代。

落到具体判断上,我给团队内部用的一套判据是这样的:如果问题出在"模型不知道某个事实",优先考虑检索增强,因为知识是会变的,你今天微调进去,明天业务规则一改,数据就过期了,还得重训;如果问题出在"模型知道但表达不对",比如输出格式总是差一点、专业术语用得不准、语气太客套或者太生硬,那就是微调的主场;如果问题只是"这一次的输出不合我意",那多半是提示词没写清楚,先别急着上训练。

还有一个经常被忽略的维度是上下文成本。有些场景里,你要模型稳定复现一套复杂的输出规范,光靠提示词得写两千字,每次请求都付这两千字的 token 钱,同时首字延迟也被拉长。这种情况下,把这两千字的要求"压缩"进权重里,用一个五十字的短提示就能触发同样的行为,长期算下来成本差异非常可观。这时候微调不是为了提升效果上限,而是为了降本,这个动机同样正当,而且往往比"效果不好"更容易量化。

注意:不要拿微调去修"模型胡说八道"这类幻觉问题。幻觉的根源是模型在缺乏依据时仍然要给出流畅回答,这是解码策略和数据分布共同决定的,靠几千条微调样本压不住,反而容易让模型学会一套更自信的错误说法。

1.2 微调真正擅长的三件事

第一件是固化输出结构。这是投产价值最高的场景。你要模型输出一段可直接入库的 JSON,字段固定、类型固定、嵌套层级固定,光靠提示词能做到 90% 合规,剩下 10% 的脏数据就够让下游管道崩掉。用几千条"输入-标准 JSON"的样本做一轮微调,合规率通常能推到 98% 以上,再加上一层结构化解码约束兜底,基本能做到工业可用。这条路我们内部叫"格式对齐",数据造起来也最快,因为标准答案可以用规则程序批量生成。

第二件是领域语气与黑话。比如客服场景里,你要的不是通用的礼貌用语,而是符合这家机构话术规范、知道哪些话不能说、知道怎么在拒绝的同时给出替代方案。这类东西写进提示词会非常臃肿,而且模型常常"看着提示词但没照做"。微调在这方面的效果是提示词比不了的,因为它是分布层面的偏移,不是单点约束。

第三件是把大模型的能力蒸馏到小模型上。这是成本敏感型项目的主流做法:先用一个能力强的模型跑出几万条高质量回答,人工抽检修正一部分,然后拿这批数据去微调一个小参数量的开源模型。小模型跑在单卡上,延迟低、并发高、单位成本可能只有原来的十分之一。代价是能力上限会降,复杂推理会退化,所以适合任务边界清晰、输入分布可控的场景,比如分类、抽取、改写、摘要。

我把这三件事的共同点总结成一句话:微调改的是"习惯",不是"知识"。习惯可以靠几千条样本塑造,知识不行。凡是涉及大量事实性内容、且内容会持续更新的需求,正确的组合拳是"检索增强负责知识,微调负责怎么把知识讲出来"。

1.3 什么时候应该果断放弃微调

有几种情况我见一次劝一次。数据量少于几百条的时候,别微调,先去做少样本提示,几条示范往往就能解决问题,而且零训练成本、随时可改。需求还在快速变化的时候别微调,因为你的模型会变成"上一版需求的化石",每次改需求都得重训一轮,迭代速度直接被打回石器时代。团队里没人能说清楚"什么样的回答算好"的时候也别微调,因为你连评估标准都没有,训出来的模型好不好全靠感觉,这种项目百分之百会烂尾。

还有一种隐蔽的情况:你其实需要的是拒答能力,而不是回答能力。很多业务场景的痛点不是模型答得不够好,而是它在该说"这个我不确定"的时候硬答。这类问题靠微调也能做,但需要精心构造负样本,成本比想象中高,而且和"提升回答质量"的目标是冲突的,得分开做实验。

最后提醒一句,微调是有技术债的。基座模型每升级一版,你的微调权重就得跟着重训一遍,因为 LoRA 权重是和特定基座绑定的,换模型就作废。这笔账在立项时就得算进去:如果你的基座半年换一次,那微调管线的自动化程度就必须跟上,否则每次升级都是一次人力灾难。

2. 微调方案怎么选:从全参数到参数高效

路线确定要微调之后,第一个技术决策是"训多少"。这个问题听起来简单,实际上决定了你后面所有的硬件预算、训练时长和工程复杂度。我按从重到轻的顺序讲,每一档都说说它的真实成本和适用边界。

2.1 全参数微调:能力上限最高,账单也最吓人

全参数微调就是模型里每一个权重都参与更新。它的优势很直接:改动最彻底,模型能学到的信息量最大,理论上限最高,而且产出的就是一个完整的模型文件,部署时不需要额外拼接任何东西。

但账要算清楚。业内的经验公式是,用混合精度加 Adam 系列优化器做全参训练,每个参数大约需要 16 到 20 字节的显存。拆开来看:bf16 权重占 2 字节,bf16 梯度占 2 字节,优化器的一阶动量和二阶动量各占 4 字节(通常用 fp32 存),再加上一份 fp32 的主权重副本 4 字节,加起来就是 16 字节,剩下的余量留给激活值和临时缓冲区。

按这个公式,一个 7B 参数的模型,光静态占用就是 7 × 16 ≈ 112GB。注意这是不算激活值的数字,实际训练时序列长度一拉长,激活值能再吃掉几十 GB。所以单卡 80GB 是绝对不够的,必须上多卡切分,或者用 ZeRO-3 这类把优化器状态、梯度、参数都做分片的方案。至于 70B 级别,那基本是集群作业,不是个人或小团队能碰的。

还有一个容易被忽略的成本:存储。全参微调每存一个检查点就是十几个 GB 到上百 GB,训练过程中你会存很多个。一个不小心,存储就被撑爆了。

我的判断是:除非你有明确证据表明参数高效方案达不到效果上限,否则不要从全参开始。大多数业务场景的差距远没有想象中大,而成本差了一个数量级。

2.2 LoRA:把增量拆成两个小矩阵

LoRA 是现在的事实标准,它的思路非常优雅,值得花点篇幅讲透,因为理解了原理你才知道那些参数该怎么调。

核心假设是:微调带来的权重变化量 ΔW 是低秩的。也就是说,虽然 ΔW 和原权重一样是个巨大的矩阵,但它的"有效信息"集中在一个很低的维度上。基于这个假设,LoRA 不去直接学 ΔW,而是把它分解成两个小矩阵的乘积:B 乘以 A。假设原权重 W 的形状是 d×k,那么 A 的形状是 r×k,B 的形状是 d×r,其中 r 远小于 d 和 k。前向计算就变成:

h = W·x + (alpha / r) · B·(A·x)

关键在于,W 是冻结的,不参与梯度更新,只有 A 和 B 在训练。参数量从 d×k 降到 r×(d+k),量级差距有多大?拿一个 4096×4096 的投影层举例,全参是 1677 万,取 r=16 之后只有约 13 万,压缩了 128 倍。

初始化上有个细节必须知道:A 用随机高斯初始化,B初始化为全零。这样训练开始时 B·A 恒等于零,模型输出和原始模型完全一致,不会因为插入新参数而破坏已有能力。这是 LoRA 训练稳定的重要原因,也是为什么它的学习率可以比全参大一个数量级——因为增量是从零开始慢慢长出来的。

那个alpha / r的缩放系数也不是随便写的。它的作用是让学习率对 r 的取值不敏感:你把 r 从 8 调到 32,只要 alpha 跟着等比调,等效的更新幅度基本不变,不用重新扫学习率。实践中常见的配置是 alpha 取 r 的两倍,这是一个比较稳妥的默认值。

还有target_modules这个参数,很多人随手填两个就完事,其实影响不小。最早的做法是只加在注意力的查询和值投影上,后来的实践普遍认为把注意力的四个投影(查询、键、值、输出)加上前馈网络的三层(门控、上投影、下投影)全部挂上 LoRA,效果更稳,尤其是需要学新格式或者新任务的时候。代价是参数量涨两三倍,但相对基座仍然是零头。

LoRA 还有一个杀手级优点:推理零开销。训练完之后可以把 B·A 乘上缩放系数直接加到 W 里,合并成一个普通权重矩阵,部署时和原模型完全一样,不增加任何延迟。这一点在线上服务里价值极高。

2.3 QLoRA 与量化:把门槛再降一个台阶

QLoRA 解决的是"连基座权重都放不下"的问题。它的做法是把冻结的基座模型量化到 4 比特再加载,而 LoRA 的那部分增量仍然用 bf16 训练。这样 7B 模型的权重占用从 14GB 直接压到 3.5GB 左右,一张 24GB 的消费级卡就能跑起来。

它里面有几个技术点值得知道。一是 4 比特的量化方式用的是一种针对正态分布优化的数据类型,比朴素的整数量化精度损失小;二是"双重量化",对量化常数本身再做一次量化,能再省一点显存;三是分页优化器,把优化器状态在显存紧张时临时挪到内存里,避免突发峰值导致爆显存。

代价也得说清楚。反量化是有计算开销的,所以 QLoRA 的训练速度通常比纯 bf16 的 LoRA 慢 20% 到 40%,具体取决于实现。另外量化本身带来精度损失,虽然大多数任务上感知不明显,但在数据量很小、需要精细调整的场景里,可能会表现为收敛更慢或者最终效果略差一点。我的建议是:显存够就上 bf16 的 LoRA,显存不够再退到 QLoRA,不要一上来就无脑量化。

顺带提一句,LoRA 这个方向后来还演化出一些变体,比如在低秩分解上做奇异值分解初始化、或者引入幅度和方向解耦的更新形式。这些变体在部分任务上确实有提升,但工程复杂度也上去了,我通常建议先把标准 LoRA 的流程跑通、把数据质量做扎实,再考虑换变体——数据带来的收益远大于算法微调。

2.4 一张表把选型决策定下来

方案显存门槛(7B 量级)训练速度效果上限部署形态适用场景
全参数微调多卡,100GB 以上慢最高独立完整模型有充足算力、追求极限效果
LoRA(bf16)单卡 24GB 可跑,40GB 舒适快高可合并,零额外延迟绝大多数业务场景的首选
QLoRA(4bit)单卡 16-24GB中等偏慢略低于 LoRA可合并,需注意量化误差算力紧张、快速验证
只训特定层很低最快有限完整模型简单的风格迁移、老方案

选型的实操顺序我建议这样走:先用 QLoRA 在一张小卡上跑通全流程,验证数据和模板没问题;确认流程无误后,如果资源允许,切到 bf16 的 LoRA 做正式训练;只有当 LoRA 在你的评估集上明确撞到天花板、并且你能证明是容量不足而不是数据不足时,才考虑全参数。这个顺序能把试错成本压到最低。

3. 数据准备:这一步决定八成成败

我见过太多人把精力全花在调参上,数据随手凑几百条就开训,然后抱怨微调没用。实话说,微调项目里 80% 的效果差异来自数据,15% 来自超参数,5% 来自算法选择。这一节讲的东西看起来枯燥,但它是最能拉开差距的地方。

3.1 训练模板必须和推理时一模一样

这是第一大坑,而且是隐蔽性最强的坑。大模型在训练时看到的是一条拼接好的字符串,包含角色标记、分隔符、结束符这些结构信息。如果你的训练数据用了一种拼接方式,推理时用的却是另一种,模型看到的输入分布就变了,效果会莫名其妙地掉一大截,而且你从 loss 曲线上完全看不出来异常。

正确的做法是:先把推理侧的请求构造代码写好,再把同一套模板函数复用到数据生成环节。不要手写字符串拼接,一定要走统一的模板函数,让训练和推理共用一份实现。我吃过这个亏——训练时手动拼的,推理时用的库自带模板,两者在系统提示的位置上差了一个换行,结果模型在长输入上表现明显劣化,排查了两天才定位到。

数据格式上,对话类任务用 JSONL,每行一个样本,包含一个消息数组。指令类任务可以用"指令-输入-输出"的三段式结构。不管哪种,都要注意三个细节:一是结束符一定要加,不然模型学不会什么时候停;二是填充符要和结束符区分开,很多开源模型默认用结束符当填充符,在批次内做损失计算时必须把填充位置 mask 掉,否则模型会学会"输出一堆结束符";三是系统提示的处理要一致,训练时带了系统提示,推理时就得带,训练时留空推理时又加上,同样会造成分布错位。

下面是一个对话样本的结构示意,字段名各家不同,重点是结构:

{ "messages": [ {"role": "system", "content": "你是一个工单分类助手,只输出类别标签。"}, {"role": "user", "content": "客户反映上月账单金额与套餐不符,要求核实。"}, {"role": "assistant", "content": "计费争议"} ] }

3.2 数据量级:几百条够不够,几万条是不是浪费

这个问题没有标准答案,但有经验区间。按任务类型分,差别很大。

风格与格式对齐类任务,比如固定输出 JSON、统一话术、特定语气,500 到 2000 条高质量样本通常就够了。这类任务的学习目标很集中,模型不需要新知识,只需要把行为往一个方向推。我做过一个格式对齐的项目,800 条样本、训两轮,结构化输出合规率从 76% 提到 97%,再往后加数据收益就非常平缓了。

任务能力注入类,比如领域分类、信息抽取、特定类型的改写,通常需要 3000 到 10000 条。因为模型要学的是一个从输入到输出的映射关系,样本需要覆盖足够的输入多样性。

知识注入类,比如让模型记住一批产品参数、内部规章制度,那需求量会陡增到几万甚至几十万条,而且效果还不一定好。这也是我一直强调"知识用检索、习惯用微调"的原因——用微调灌知识的性价比极低,而且更新困难。

有个反直觉的结论值得强调:质量的边际收益远大于数量。1000 条人工精标的样本,效果经常碾压 10000 条机器批量生成的样本。原因是批量生成的样本往往带有系统性的模板痕迹,模型会过度拟合这些痕迹,表现为输出开头永远是同一句话、句式高度雷同。如果你的数据是程序生成的,务必做随机化:换同义词、打乱字段顺序、随机调整表述长度,让分布散开。

3.3 清洗的六个动作,一个都不能省

数据清洗这块,我总结了一套固定的动作清单,每次做新项目都照着过一遍。

去重是第一步,而且要分两层:精确去重(完全相同的字符串)和近似去重(换个标点、改个数字的变体)。近似去重可以用 n-gram 指纹或者最小哈希来做。重复样本的危害是让模型对少数样本过拟合,loss 曲线看着很漂亮,实际泛化很差。

长度过滤是第二步。太短的样本(比如答案只有两个字)信息量不足,太长的样本会在截断时被砍掉尾巴,导致模型学到"话说一半就结束"。一定要统计长度分布,把超过最大序列长度的样本单独处理,能拆的拆,不能拆的丢掉,绝对不要让截断发生在答案中间。

异常内容过滤是第三步。要清掉几类东西:一是模型自己生成的拒答模板("作为一个人工智能"这类),它会污染模型的自我认知;二是包含残留标记的样本,比如复制粘贴时带上的角色前缀;三是明显的错误答案,宁可少几条也不能留错。

格式统一是第四步。同一批数据里,日期格式、数字写法、标点习惯要统一,否则模型会学到混乱的映射。这个工作最好在造数据阶段就规范好,别指望后期清洗能救。

分布检查是第五步。统计一下各类别的样本数,如果某一类占了 70%,模型就会偏向那一类。分类任务里这个尤其致命,需要做重采样或者分层抽样。

人工抽检是第六步,也是最重要的。随机抽 50 到 100 条逐条看,我几乎每次都能在这个环节发现问题,而且是自动化脚本发现不了的问题——比如答案的严谨程度不统一、有的样本答得太啰嗦有的太简略,这种"标注标准漂移"是最伤模型的。

3.4 验证集怎么划,别让指标骗了你

验证集的划分有个反直觉的原则:要按"来源"划分,不能按"样本"随机划分。

什么意思?如果你的数据是用同一个模板批量生成的,或者同一个原始文档切出来的多个问答对,那么这些样本之间存在强相关性。随机划分会让高度相似的样本同时出现在训练集和验证集里,验证 loss 会低得离谱,你会误以为模型学得很好,一上真实场景就现原形。正确的做法是按模板、按文档、按用户、按时间段来做分组划分,保证验证集里的样本和训练集"不同源"。

验证集规模不用大,200 到 500 条足够看趋势,但必须覆盖所有任务类型,每一类至少留几十条。另外我强烈建议额外留一个"真实 hold-out 集":从线上日志里捞一批真实用户输入,人工标注好,平时不参与任何调参,只在最终验收时跑一次。这个集合给出的数字,才是你敢不敢上线的依据。

4. 训练实操:参数、显存与流程

方案定了、数据齐了,接下来是真正跑起来的部分。这一节我把显存怎么估、超参怎么定、训练怎么盯这三件事讲透。

4.1 显存估算:先算清楚再开跑

显存不够是最高频的阻塞点,与其反复试错,不如动手算。LoRA 训练的显存由四块构成:基座权重、LoRA 参数及其优化器状态、梯度、激活值。

基座权重是大头。bf16 加载的话,参数量乘以 2 字节;4 比特加载的话,参数量乘以约 0.5 字节,再加上一点量化常数的开销。7B 模型对应的是 14GB 和 3.5GB 这两个量级。

LoRA 参数及其优化器状态很小。假设可训练参数是 2000 万,按 LoRA 只用 bf16 权重加两个 fp32 动量来算,大概是 40MB + 160MB,可以忽略不计。这是 LoRA 最爽的地方:优化器状态几乎不占显存,而全参微调里它才是真正的吞金兽。

梯度同理,只有可训练部分需要,也很小。

真正难估的是激活值,它和批次内的 token 总数、隐藏维度、层数、序列长度都相关,而且是线性甚至超线性增长的。粗略的经验是:序列长度 2048、批次为 1 的情况下,7B 模型的激活值在 10GB 上下。开启梯度检查点之后,激活值能被压到原来的几分之一,代价是训练速度慢 20% 到 30%。

配置基座权重激活值(序列 2048)总计估算适配显卡
7B + bf16 + 梯度检查点约 14GB约 6GB约 21GB24GB 勉强,40GB 舒适
7B + 4bit + 梯度检查点约 3.5GB约 6GB约 10GB16GB 可跑,24GB 舒适
13B + 4bit + 梯度检查点约 6.5GB约 9GB约 16GB24GB 可跑
7B + 全参 + ZeRO 分片分摊分摊100GB+多卡集群

一个实操技巧:先把批次设成 1、序列长度设成目标值跑几十步,看显存峰值,再决定能不能往上加。批次大小不够就用梯度累积凑,比如单卡只能放 1 条,累积 32 步,等效批次就是 32,效果基本一致,只是慢一点。这是显存受限时最实用的手段。

4.2 关键超参数:每个值背后都有理由

我把 LoRA 微调的常用配置整理成表,并说明每个值的选型逻辑,方便你按自己场景微调。

参数推荐区间选型理由
学习率1e-4 ~ 2e-4LoRA 增量从零初始化,比全参(1e-5 量级)大一个数量级
低秩维度 r8 ~ 64格式对齐 8-16 够,任务能力 32-64,再大收益递减
alpha2 × r保持缩放系数恒定,换 r 不用重扫学习率
dropout0.05 ~ 0.1数据量少于 3000 条时调高,防止过拟合
有效批次32 ~ 128越大越稳但显存吃紧,用小批次加累积凑
训练轮数2 ~ 3超过 3 轮在小数据上几乎必然过拟合
最大序列长度按数据 95 分位设设太大浪费显存,设太小会截断答案
学习率调度余弦退火 + 预热预热比例 3% 左右,避免前期震荡
权重衰减0.01 左右辅助正则,小数据上可以适当调高

关于学习率,这里有个容易搞混的点。很多人从全参微调的经验过来,习惯用 1e-5,结果发现 loss 几乎不动。原因是 LoRA 的增量矩阵初始为零,等效更新幅度天然就小,必须用更大的学习率才能推动。反过来,如果 loss 剧烈震荡或者直接变成 NaN,那多半是学习率太大了,先降到 5e-5 试试。

轮数这个参数我要单独强调。小数据微调最常见的失败模式就是训太多轮。第一轮结束模型已经学会了格式,第二轮开始它会在少数样本上死记硬背,第三轮你就能看到典型的复读和崩坏。所以我的习惯是默认 2 轮,如果验证 loss 还在降,再考虑加。

还有一个容易被忽视的参数是梯度裁剪阈值,一般设 1.0。它在大学习率下能救你一命,防止个别异常样本把梯度推到爆掉。

下面是一份配置文件的骨架,参数名是社区通用的叫法,各家训练库的写法略有出入,对照着改就行:

model: load_in_4bit: true compute_dtype: bf16 lora: r: 16 alpha: 32 dropout: 0.05 target_modules: [q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj] train: learning_rate: 1.5e-4 per_device_batch_size: 1 gradient_accumulation_steps: 32 num_epochs: 2 max_seq_length: 2048 warmup_ratio: 0.03 lr_scheduler: cosine weight_decay: 0.01 max_grad_norm: 1.0 gradient_checkpointing: true logging_steps: 10 save_steps: 200 eval_steps: 200

4.3 训练过程该盯哪些指标

开跑之后不要只盯着一个 loss 数字看,至少同时关注四样东西。

训练 loss的形态是最直观的。正常情况下它会在前几十步快速下降,然后进入缓慢下降的平台期。如果你看到它一路平稳不动,先怀疑学习率太小;如果看到它锯齿状剧烈震荡,怀疑学习率太大或者批次太小;如果它下降得异常快(比如几十步就掉到 0.1 以下),那要警惕数据里有大量重复样本,模型在背答案。

验证 loss是判断过拟合的唯一可靠依据。健康的曲线是:验证 loss 跟着训练 loss 一起降,然后逐渐走平。如果验证 loss 在某个点之后开始回升,而训练 loss 还在降,那就是过拟合了,应该取回升之前的检查点,同时下一轮减少训练轮数或者提高 dropout。

梯度范数经常被忽略,但它能提前预警。如果梯度范数突然飙高,说明遇到了异常样本或者数值不稳定;如果长期接近零,说明学习率太小或者可训练参数被冻错了。

吞吐量(每秒处理的 token 数)用来判断工程效率。这个数字在训练过程中应该基本稳定,如果突然掉一半,可能是遇到了特别长的样本导致批次内填充变多,也可能是显存吃紧触发了内存交换。

除了这些数字,我强烈建议加一个环节:每存一次检查点,用固定的十条测试输入跑一遍生成,人工扫一眼。loss 是抽象的,生成结果是具体的。很多问题——比如模型开始输出奇怪的前缀、开始复读、开始用错误的格式——从 loss 上看不出来,但看输出一眼就能发现。

4.4 一次完整的实操流程记录

把上面这些串起来,一个完整的流程大概是这样走的。第一天做数据和模板,写模板函数、造样本、清洗、划分、抽检,这部分工作量占了整个项目的一半以上。第二天用 4 比特量化加载跑通全流程,用 100 条数据训 50 步,确认没有报错、显存峰值可控、保存和加载都正常。这一步千万别跳,我见过太多人直接用全量数据开跑,跑了三个小时才发现模板有个字段名拼错了。

第三天做小规模正式训练,用 10% 的数据、训 1 轮,看 loss 形态和生成效果,确认方向正确。如果这一步效果就不行,说明数据或模板有问题,回到第一步排查,别急着加数据加轮数。

第四天全量训练,跑 2 轮,每 200 步存一次检查点,全程记录 loss 和生成抽样。第五天做检查点对比,把每个检查点在验证集上跑一遍,按任务指标(而不是 loss)选最优的那个。选定之后合并权重、导出模型、跑一遍真实 hold-out 集,最后做上线前的压力测试。

这个节奏在小团队里大概是一周一个迭代,前提是数据已经准备好。如果数据要从零标注,时间要翻三倍。

5. 常见问题与排查实录

这一节是我踩过的坑的集合,按症状分类,方便你遇到问题时对号入座。

5.1 loss 不降或者降得极慢

这是最高频的问题,可能的原因按概率排序。

学习率太小,尤其是从全参微调迁过来的经验,用 1e-5 训 LoRA,loss 几乎是一条直线。先试着提到 2e-4 看看有没有变化。

标签被 mask 掉了。这个坑很隐蔽:有些训练库的默认行为是只对"助手回复"部分计算损失,如果你用的数据格式没有正确标记角色,整个序列的标签可能都被置成了忽略值,模型实际上什么都没学。排查方法很简单,把第一条样本经过处理之后的完整输入和标签打印出来,逐 token 看一眼,确认标签里确实有非忽略值,并且只覆盖在回复部分。

数据模板不匹配,也就是 3.1 节讲的问题。模型看到的输入结构和它预训练时见过的差别太大,学习效率会很低。

可训练参数没挂上。用工具打印一下可训练参数的名称和数量,确认打印出来的不是零,也确认挂载的层名确实匹配到了模型结构。有些模型的层命名和常见写法不一样,配置里的层名写错了不会报错,只是静默地什么都没加上。

数据本身有问题,比如所有样本的答案几乎一样。这种数据训起来 loss 会降得很快然后停滞,模型学到的就是一个常量输出。

5.2 输出复读、崩坏、格式错乱

这类现象基本都指向过拟合或数据质量。

先看轮数,超过 3 轮的小数据训练几乎必然出问题,把轮数降到 2 甚至 1 试试。再看学习率,太大的学习率会让模型在训练后期"忘记"语言能力,输出变得支离破碎。如果这两项都正常,那就是数据的事:检查有没有大量近似重复的样本,检查答案的开头是不是高度雷同,检查有没有超长样本被截断。

还有一种特殊情况是模型学会了不该学的东西。比如训练数据里所有回答都以"好的,我来帮你分析一下"开头,那模型会把这个前缀当成必须输出的固定内容,哪怕问题根本不需要这句话。解决方法是在造数据时刻意打散这类套话,让开头的表述多样化。

5.3 通用能力退化(灾难性遗忘)

LoRA 相对全参微调在这一点上表现好很多,因为基座权重是冻结的,但轻度退化仍然会出现,尤其是学习率大、训练轮数多的时候。典型表现是:你的目标任务做得很好,但闲聊几句发现模型变笨了,或者干脆不会回答其他类型的问题了。

缓解办法有三个。一是混入通用数据,在训练集里掺 5% 到 10% 的通用指令样本,让模型在学新任务的同时保持原有能力,这是最有效的手段。二是降低学习率和轮数,让改动更温和。三是降低低秩维度,容量小了,破坏性也小。

这里有个权衡要注意:混通用数据会稀释目标任务的学习信号,如果你的任务数据本来就少,混太多通用数据可能导致任务效果上不去。我一般从 5% 起步,观察目标任务指标有没有掉,再决定要不要加。

5.4 训练效果好但线上效果差

这是最让人抓狂的一类问题,因为你在本地怎么测都是好的。绝大多数情况是推理侧和训练侧不一致。

排查顺序:先核对模板,训练用的拼接函数和推理用的必须是同一份代码;再核对结束符,推理时如果没正确设置停止标记,模型会一直往下编,输出变得又长又乱;然后核对生成长度上限,训练时答案平均 200 token,推理时上限设成 64,答案会被硬切断;最后核对采样参数,训练时模型学的是贪心式的确定性输出,推理时温度设成 1.2,行为自然对不上。

还有一种情况是输入分布漂移。训练数据是你精心构造的,线上输入是真实用户的随手输入,长度、噪声、表达方式都不一样。这就是为什么我在 3.4 节强调要留真实 hold-out 集——它是唯一能提前暴露这个问题的东西。

5.5 问题速查表与上线前的最后一次检查

症状最可能的原因首选动作
loss 完全不动学习率太小 / 标签被 mask / 层名没匹配打印标签和可训练参数清单
loss 变 NaNfp16 溢出 / 学习率过大 / 空样本换 bf16、降学习率、过滤空样本
输出复读训练轮数过多 / 数据重复降到 2 轮、做近似去重
通用能力退化学习率大 / 没混通用数据掺 5-10% 通用样本、降学习率
线上效果差模板不一致 / 停止符没设对齐训练推理的模板代码
显存 OOM序列太长 / 批次太大开梯度检查点、批次设 1 加累积
格式偶尔不合规样本里格式本身不统一统一数据格式、加结构化解码兜底

上线前我一定会做一次"回归清单"式的检查,七项,一项都不能少。模板一致性:用训练时的模板函数构造一条样本,和训练日志里打印的输入逐字符比对。停止符:确认推理侧正确设置了结束标记和最大长度。合并正确性:如果做了权重合并,拿合并前后的模型对同一批输入跑生成,输出应该完全一致。量化影响:如果部署时用了量化,要在量化后的模型上重跑一遍评估,别拿 bf16 的指标交差。并发压测:单条测试好看不代表并发下好看,批次调度可能引入填充差异。异常输入:空字符串、超长文本、纯符号输入,逐个试一遍,看会不会崩。回滚方案:确认上一版模型还能一键切回去。

最后说一个我个人踩得最深的坑。早期做项目时,我把所有精力放在把验证集指标推到最高,模型在评估集上的表现确实漂亮。上线第一周,badcase 集中出现在一类特别长的输入上——训练数据里这类长输入很少,因为我在清洗时为了省显存把超过 1024 token 的样本全删了。模型没学过怎么处理长文本,一遇到就胡说。从那之后我改了个习惯:清洗阶段删掉的每一类数据,都要单独记录删了多少、为什么删,然后在评估集里专门补上这类样本。评估集要能覆盖你删掉的那些"难看的"数据,否则你测出来的永远是乐观数字。

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

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

立即咨询