聊AI模型训练这件事,我总觉得很多人一上来就盯着某个具体算法,反而忽略了整个方法体系的骨架。说实话,真正做过几个项目之后你会发现,搞清楚训练方法的分类和它们之间的搭配关系,比死磕某一个trick重要得多。这一章我想从范式、阶段、选择维度和组合策略四个角度,把这张地图摊开来讲讲。
1.1 训练范式分类:监督学习、自监督学习、强化学习与迁移学习
我最早接触的就是监督学习,标注数据喂进去,模型学着从输入映射到输出。这条路直白好用,分类、回归、检测这些任务都能走通。问题也很明显,标注成本高得吓人,数据量一大就吃不消。我自己做过一个文本分类项目,几万条标注数据折腾了快一个月,模型效果还未必比后来用预训练模型微调出来的强。
自监督学习是我认为近些年最值得一提的范式转变。它不需要人工标注,直接从数据本身构造监督信号,语言建模里预测下一个词就是典型做法。这种方式让模型在海量无标注文本上先学一遍通用规律,后面再针对具体任务调整。我第一次看到这种思路时确实有点震撼,等于把标注的活儿交给了数据自己。
强化学习走的是另一条路,模型通过和环境交互、拿奖励来调整策略。它在游戏、机器人控制、对齐训练里用得很多。迁移学习则更像一种方法论上的复用思维,把在大规模数据上学到的知识搬到新任务上。我平时做项目几乎都会先想能不能迁移,而不是从零开始训。这四种范式并不是互斥的,现实里经常是组合着用。
1.2 训练阶段划分:预训练、微调、对齐训练与推理优化
大模型的训练流程我习惯拆成四段来看。预训练是打地基,用海量数据让模型学会语言的基本规律和世界知识。这一步烧钱烧算力,动辄几千张卡跑好几周。我见过一些团队跳过预训练直接微调开源模型,效果也不错,前提是任务和底座模型的气质对得上。
微调是把通用能力往具体任务上收,数据量小很多,几百到几万条都能起步。对齐训练是让模型的输出更符合人类偏好,RLHF和DPO都属于这一类,解决的是"能说"到"说得让人满意"的问题。推理优化常被忽略,它不属于训练本身,却直接决定模型能不能落地。量化、蒸馏、KV cache优化这些手段,我在部署阶段用得特别多。
这四个阶段之间没有绝对边界,实际项目里经常来回穿插。我做过一个客服问答系统,预训练用开源底座,微调加了行业语料,对齐阶段用人工偏好数据做了几轮DPO,部署前又做了4bit量化。整条链路走下来,每一步的取舍都会影响最终效果和成本。
1.3 方法选择的关键维度:数据规模、算力预算、任务类型与部署约束
选训练方法这件事,我的经验是先看四个维度。数据规模决定你能不能玩得动自监督或者大规模预训练,几千条数据就老老实实做微调,别想着一口吃成胖子。算力预算更现实,手里只有八张卡和有一整个集群,方案完全不一样。我见过不少团队方案设计得很漂亮,最后卡在预算上动弹不得。
任务类型也很关键,生成任务和判别任务对模型结构、训练目标的要求差别很大。部署约束常被放在最后考虑,我觉得应该提前想。端侧部署要考虑显存和延迟,云端服务可以放宽一些。有一次我把一个没做量化的模型直接往边缘设备上塞,推理速度慢到没法用,返工重来。
这四个维度不是独立起作用,它们互相牵制。数据多但算力少,可以考虑数据采样或者更高效的并行策略。算力足但数据少,就得警惕过拟合,正则化和早停要多上心。我现在的习惯是项目启动前把这四项列个表,逐项打勾,方案自然就浮出来了。
1.4 训练方法组合策略:多阶段训练、联合训练与持续学习
单一方法很难撑起一个完整的产品级模型,组合策略才是常态。多阶段训练是我最常用的思路,先预训练打底,再指令微调,然后做偏好对齐,每一阶段解决一类问题。这种分阶段推进的好处是每步可观测、可回滚,出问题容易定位。我做过一个代码生成模型,预训练用通用代码语料,微调阶段混入了特定语言的优质样本,对齐阶段专门针对代码可读性做了偏好数据,效果比一锅炖好不少。
联合训练更适合多任务或者多模态场景,把不同来源的数据和损失函数放在一起优化,让模型共享表示。这种方式对数据配比和损失权重很敏感,调不好会出现某个任务被压制的情况。我试过文本和表格联合训练,前几轮表格任务的loss几乎不降,后来调整了采样比例才慢慢平衡。
持续学习解决的是模型上线后还要不断吸收新知识的问题。全量重训成本太高,增量更新又容易灾难性遗忘。我目前的实践是定期做小规模增量微调,同时保留一部分旧数据混进去,缓解遗忘。这三种策略经常混着用,具体怎么搭,还是回到数据、算力、任务和部署那四个维度去权衡。
预训练这事我越想越觉得像盖楼打地基,地面上的人只看得到装修得多漂亮,真正决定这楼能盖多高、能扛几级地震的,全在底下那些看不见的活儿里。我自己在这块栽过跟头,也尝过甜头,这一章就把我踩过的坑和攒下的经验摊开聊聊。
2.1 自监督预训练目标:语言建模、掩码预测与对比学习
语言建模是我接触最多、也是最直观的一种预训练目标。给定前面的词,让模型去猜下一个词,整个训练过程不需要任何人工标注,语料自己就能产生监督信号。我第一次跑这种任务的时候盯着loss曲线看了半天,发现它降得特别平滑,那种感觉就像看着小孩从只会蹦单字到能说完整句子。自回归语言建模有个天然优势,它和生成式任务的形式完全一致,预训练学到的东西可以直接迁移到下游生成场景。
掩码预测走的是另一条路,把输入里的某些词遮住,让模型根据上下文去还原。这种双向看上下文的方式对理解类任务特别友好,BERT那一系就是靠这个打出来的。我在做文本分类和实体识别时更偏爱掩码式预训练底座,它对句子整体语义的把握确实更扎实。两种目标各有所长,后来也出现了把两者混在一起训练的模型,取长补短。
对比学习我一开始没太在意,直到做检索和向量召回的项目才真正重视起来。它的思路是把相似的样本在表示空间里拉近,不相似的推远。正负样本怎么构造是关键,我试过用同一条文本的不同增强版本做正对,用batch内其他样本做负对,效果比单纯的语言建模在语义匹配任务上强出一截。这几种目标现在经常是搭配着用,具体选哪个或者怎么组合,还是要看下游到底要模型干什么。
2.2 数据构建与配比:语料清洗、去重、质量过滤与领域混合
数据这块我吃过最大的亏就是低估了清洗的工作量。刚入行时觉得把网页爬下来直接喂进去就行,结果模型学了一堆乱码、广告和重复内容。后来我才明白语料清洗是整个预训练里最耗人力又最不能省的环节。HTML标签、特殊符号、编码错误这些得先处理掉,正文提取也得做,不然模型大量的注意力都浪费在无关内容上。
去重的重要性我是被教训出来的。有一批数据里同一个段落重复出现了几十次,模型在那上面过拟合得厉害,生成的时候老是 regurgitate 那几句话。精确去重相对简单,哈希就行,近似去重麻烦一些,MinHash、SimHash这些方法我都用过,阈值调起来需要点耐心。质量过滤我一般会训一个小分类器,或者用规则加困惑度打分,把低质内容筛掉。这一步做完数据量可能砍掉一半,但剩下的质量完全不一样。
领域混合是个需要反复试的活儿。通用语料保证模型的广度和语言能力,领域语料让它具备专业知识。我做过一个医疗方向的模型,一开始医疗数据占比太高,模型通用对话能力掉得厉害,后来调到三成左右才找到平衡点。配比没有标准答案,得看你的目标场景,我现在的习惯是先定一个初始比例,训个小规模版本看看loss和下游表现,再决定要不要调。
2.3 规模化训练策略:课程学习、批量大小调整与学习率调度
课程学习这个想法我觉得特别符合直觉,先给模型看简单的、短的样本,再慢慢上难的、长的。我在一个长文本摘要项目里试过,前期用短文档训练,模型收敛快很多,后期再切换到长文档,效果比一上来就混着训要好。不过课程怎么设计挺讲究,难度度量如果定得不好,反而会拖慢整体进度。我现在的做法是用长度或者困惑度做个粗排序,不追求特别精细。
批量大小这块我经历过从迷信大batch到理性看待的过程。大batch能提升训练吞吐和梯度估计的稳定性,显存和通信开销也跟着涨。我一般会先看硬件能撑多大,再用梯度累积把等效batch拉上去。学习率跟batch大小是有联动关系的,batch放大之后学习率通常也得相应调高,不然训练会变慢。warmup阶段我基本都会加,前期步子迈太大容易把模型带沟里。
学习率调度我用得最多的是余弦退火和线性衰减。余弦退火在训练后期降得比较柔和,模型收敛得更稳一些。也有用分段常数的,在某些阶段保持学习率不变,适合需要精细控制的场景。我自己的经验是调度策略不用搞得太花哨,把warmup比例、峰值学习率和衰减方式这三个参数调明白,效果就差不到哪去。
2.4 预训练稳定性保障:损失尖峰处理、梯度爆炸缓解与训练恢复
损失尖峰是每个跑过大模型预训练的人都躲不开的噩梦。训练明明平稳跑了几千步,突然loss蹿上去一大截,有时候能自己降回来,有时候就直接发散了。我遇到最严重的一次是数据里混进了一批格式异常的样本,排查了两天才定位到。现在的做法是在数据管道里加更严格的过滤,同时监控每个batch的loss,超过阈值就跳过这个batch。
梯度爆炸的缓解手段我用得比较多的有梯度裁剪和梯度归一化。裁剪阈值设多少有讲究,太松了没效果,太紧了会限制模型学习。我一般从1.0开始试,看训练曲线再微调。混合精度训练里loss scaling也很关键,scale值没设好容易出现下溢或者上溢,我习惯用动态loss scaling,让框架自己调。
训练恢复这件事平时不起眼,出事的时候能救命。checkpoint保存频率我一般设得比较密,尤其是训练前期。保存的时候不光存模型权重,优化器状态、学习率调度器状态、数据加载器的位置都得存,不然恢复之后训练轨迹会偏。我吃过一次只存了权重的亏,恢复后loss曲线跟之前完全对不上,只能从头再来。后来我把这套恢复机制做成了标准流程,心里踏实多了。
预训练这件事说到底是个系统工程,目标设计、数据、规模化策略和稳定性保障每一环都得盯住。我自己的体会是别指望某个单点技巧能拯救全局,把这些环节逐个做扎实,模型的表现自然就上来了。
预训练是把模型养成一个通才,微调才是把它往具体岗位上摁的过程。我自己带队做过不少微调项目,有全参数硬刚的,也有靠几行LoRA配置省下几十张卡的,中间踩过的坑说实话比预训练还杂。这一章就把这些方法一个个拆开讲,顺带把评估和常见毛病也说清楚。
3.1 全参数微调与指令微调:适用场景、数据要求与成本分析
全参数微调是我最早接触的做法,模型里每一个权重都参与更新。它的好处很直接,模型容量完全释放,下游任务上的天花板最高。我做过一个法律文书生成的活儿,数据量有几十万条,全参数微调出来的效果确实比任何轻量方法都要细腻,语气、术语、格式都能咬得很准。代价就是贵,七B的模型全参微调,八张A100跑起来也就勉强,70B那个级别我连想都不敢想。
指令微调解决的是另一个层面的问题。它不追求把某个任务的性能压榨到极致,重点在于让模型学会听懂人话,知道什么样的输入该对应什么样的输出格式。我自己的经验是,指令数据的质量远比数量重要,几千条精心构造的样本往往比几十万条机器生成的效果还好。构造指令数据时我习惯把任务描述、输入、输出三段式写清楚,多样性上多下功夫,覆盖不同句式和不同难度。
成本这块得算细账。全参数微调除了显存,还有存储和迭代的成本,每个实验版本都是一个完整模型,几十G起步。指令微调如果走全参数路线,成本跟普通全参微调差不多。我现在的判断标准是,数据量过十万、任务对细节要求极高、算力不吃紧,那就上全参数。其他情况我基本都会先试参数高效的方法,效果不够再升级。
3.2 参数高效微调方法:LoRA、QLoRA、Adapter、Prefix Tuning、P-Tuning与IA3
LoRA是我现在用得最多的方法,没有之一。它的思路是在原来的权重旁边挂两个小矩阵,训练的时候只更新这两个低秩矩阵,原来的权重冻住不动。我第一次用的时候还挺怀疑,这么点参数能学到什么,结果跑完一个客服场景的微调,效果跟全参数差不了太多,显存占用直接砍到四分之一。秩的选择是个关键,我一般从8开始试,任务复杂就往上加到16或者32。
QLoRA是在LoRA基础上又往前走了一步,把底座模型量化到4bit再挂适配器。我拿它在一张24G的卡上微调过13B的模型,跑起来还挺稳。量化会带来一点精度损失,我做情感分类这种任务几乎察觉不到,做生成任务时偶尔能看出用词不如全精度自然。对预算紧张又想玩大模型的团队来说,这个方法真的值得试。
Adapter和Prefix Tuning属于另一条技术路线。Adapter是在Transformer层里插小模块,训练时只更新这些模块。我早期用过Houlsby那版Adapter,效果不错但推理时会多一层延迟,部署的时候得掂量一下。Prefix Tuning和P-Tuning走的是往输入里加可学习向量的路子,P-Tuning v2在NLU任务上表现稳定,我做少样本意图识别时用它比LoRA还省事。IA3是通过缩放激活值来实现的,参数少得夸张,我在一个资源极受限的端侧项目里用过,效果中规中矩,胜在轻。
这些方法怎么选,我自己的排序是LoRA当默认选项,QLoRA当显存吃紧时的备选,Prefix和P-Tuning适合少样本场景,IA3留给极端轻量需求。别指望一个方法通吃,任务特性不同,适配效果差得挺明显。
3.3 领域适配与任务微调:数据构造、样本配比与灾难性遗忘缓解
领域适配最难的部分不在训练,在数据构造。我做过一个金融领域的项目,一开始直接拿研报和公告灌进去,模型学得倒是挺快,就是输出变得特别死板,问个简单问题都要绕一大圈。后来我们把数据拆成领域知识和领域对话两类,配比调了好几轮,才把专业性和自然度平衡回来。数据构造这事我建议前期多花时间,别急着开训。
样本配比是个经验活。领域数据占太高,通用能力掉得厉害。占太低,领域知识又学不进去。我一般的做法是领域数据控制在三到五成,剩下的用通用指令数据托底。有个小技巧是用通用数据做一些回放,在训练批次里混进一定比例的老数据,能明显缓解灾难性遗忘。混合比例我试过从一成到五成,三成左右是比较稳的区间。
灾难性遗忘这事我吃过亏。有一次微调完一个代码助手,模型写代码确实变强了,结果让它写个周报都磕磕绊绊。后来我养成了习惯,微调前后都跑一遍通用能力评测,掉得太多就回去调数据配比。还有个办法是冻结部分底层参数,只动高层,对保持通用能力有帮助。弹性权重巩固这类正则方法我也试过,效果有但实现起来麻烦,不如数据层面直接控制来得实在。
3.4 微调效果评估与常见问题:过拟合、泛化能力与超参数选择
微调的评估不能只看训练loss,这是我最想强调的一点。我见过太多人盯着loss降得漂亮就以为成了,结果一上真实场景就露馅。评估集必须独立构造,最好能覆盖真实分布的边边角角。我做评估一般会分层看,简单样本、困难样本、边界样本分开统计,光看一个总体准确率容易掩盖问题。人工评估也少不了,尤其是生成类任务,自动指标跟人的感受经常对不上。
过拟合在微调里太常见了,尤其是数据量小的时候。表现是训练集上表现越来越好,验证集开始往下掉。我一般会盯着验证集指标做早停,patience设个三五轮。数据量少于一千条的时候我会格外小心,学习率调小,epoch数控制在三轮以内。正则手段里weight decay和dropout我都用,LoRA本身自带的正则效果也不错,秩别设太大就相当于做了约束。
泛化能力是微调里最玄学的部分。同样的数据和方法,换个随机种子结果都能差几个点。我现在的做法是重要项目至少跑三个种子,看结果的方差,方差大说明方法不稳,得回去查数据或者调超参数。超参数选择上学习率是第一优先级,我一般从1e-4或者2e-4起试,LoRA的学习率可以比全参微调高一个量级。batch size在微调里没那么敏感,显存允许就往大了设。epoch数我基本不超过五轮,再多基本都是过拟合。
微调这事看着方法多,真正决定成败的还是数据和评估这两头。方法选对了能省很多算力,数据做扎实了效果才稳得住。我自己的习惯是先小规模快速试错,找到靠谱的配置再放大,别一上来就All in,浪费的不只是卡时,还有团队的信心。
单卡训大模型这件事,我自己试过,纯属跟自己过不去。参数一多显存直接原地爆炸,哪怕把batch size压到1也救不回来。分布式训练不是可选项,是唯一出路。可分布式这摊子水很深,并行策略怎么切、框架用哪个、通信怎么压、显存怎么省,每一步选错了都是几十张卡的空转。这一章我把这些年踩过的分布式坑捋一遍,尽量说人话。
4.1 并行策略对比:数据并行、张量并行、流水线并行与混合并行
数据并行是我最早用的策略,也是最好理解的。每张卡上放一份完整模型,数据切成N份分下去,各自算梯度再一平均。小模型时代这招够用,我现在拿它跑个7B以下的微调还是挺顺手。它的瓶颈在于模型本身得塞进单卡,70B的模型光权重就一百多G,A100 80G也扛不住。梯度同步的通信量也随参数规模线性涨,卡一多通信就成瓶颈。
张量并行切的是模型内部。一个矩阵乘法拆到不同卡上算,每张卡只存一部分权重。我做attention头切分的时候用过Megatron那套方案,8卡跑下来单层计算确实快了。它的毛病是通信太频繁,每层前向反向都要做all-reduce,卡间带宽不够就全卡等着。我一般只在单机内做张量并行,跨机搞这个纯属给自己找不痛快。
流水线并行是把模型按层切段,不同段放不同卡上,数据像流水线一样传过去。它通信量比张量并行小得多,跨机也能用。麻烦在气泡,流水线头尾总有些卡在空等。我调过一阵子micro-batch和流水线段数,气泡能压下去但压不干净。混合并行是把这几样掺在一起用,张量并行放机内、流水线并行跨机、数据并行兜底。听着复杂,实际跑大模型的时候基本是标配,Megatron和DeepSpeed的3D并行就是这套路。
4.2 主流框架能力对比:DeepSpeed、Megatron-LM、PyTorch FSDP、Horovod、Colossal-AI与Ray Train
DeepSpeed是我目前用得最多的框架,主要冲着ZeRO去的。ZeRO把优化器状态、梯度、参数分片存,显存占用能降一个数量级。我拿ZeRO-3在32张A100上跑过百B级模型的预训练,稳定性说得过去。它的配置文件写得有点啰嗦,json一层套一层,第一次配的时候我对着文档磨了半天。生态整合做得不错,HuggingFace Trainer直接能挂。
Megatron-LM是NVIDIA自家出的,张量并行这块做得最细。它的代码可读性一般,但大模型训练的吞吐确实猛。我做过一次对比,同样硬件下Megatron的模型FLOPs利用率比DeepSpeed高出一截。它对模型结构有要求,得按它的方式来改代码,灵活度上牺牲了一些。现在Megatron和DeepSpeed经常混着用,Megatron管模型结构,DeepSpeed管显存优化。
PyTorch FSDP是官方亲儿子,好处是原生集成,不用额外装一堆东西。它的分片思路跟ZeRO-3类似,用起来比DeepSpeed轻。我拿FSDP做过几个中等规模的项目,配置简单,跟现有PyTorch代码兼容性好。成熟度上比DeepSpeed差一点,某些边角场景的显存优化没那么细。
Horovod是老牌的数据并行框架,Uber出的。它的优势是简单,几行代码就能把单机训练改成多机。我在早期做CV模型分布式训练的时候常用它,稳定省心。它主要面向数据并行,模型大到单卡放不下的时候就力不从心了。新项目我基本不用了,但老代码里还能见到。
Colossal-AI是国内团队做的,亮点在异构内存管理和自动并行策略。它能把参数卸载到CPU甚至NVMe上,小集群跑大模型有点意思。我试过用它在一台8卡机器上跑65B的推理,确实能跑起来,速度就别指望了。Ray Train是另一条路子,它本质是分布式任务调度框架,把训练当成任务来编排。多机多卡的管理做得很顺,适合做超参搜索和批量实验,底层训练能力还得靠别的框架补。
4.3 通信优化与显存管理:ZeRO、梯度压缩、激活重计算与卸载机制
ZeRO是显存优化里绕不开的东西,三个阶段我挨个用过。ZeRO-1切优化器状态,省的不多但几乎没额外开销。ZeRO-2连梯度一起切,显存降得明显,通信量增加有限。ZeRO-3把参数也切了,显存最省但通信开销最大。我一般的配置是中等模型上ZeRO-2,超大模型才上ZeRO-3。用ZeRO-3的时候得注意通信重叠,配不好反而更慢。
梯度压缩这个方向我试过一些。核心思路是少传点数据,比如量化到fp16或者做稀疏化。DeepSpeed里自带fp16压缩,开起来显存和带宽都能省一点。稀疏化我试过几种方案,效果不太稳定,压缩率一高收敛就出问题。现在我的态度是,带宽实在吃紧可以开个fp16压缩,再激进的方案得谨慎。
激活重计算是我每次配训练必开的选项。前向的时候不存中间激活,反向的时候重算一遍。显存能省三到四成,代价是多花三成左右的计算时间。我算过账,大多数场景下这笔交易划算,显存不够的时候更是救命稻草。配的时候要注意选对重算的粒度,全重算太浪费,只重算attention那块收益最高。
卸载机制是把暂时用不上的数据挪到CPU内存或者硬盘上。ZeRO-Offload我用过,优化器状态放CPU,对训练速度影响不算太大。NVMe卸载我也试过,速度掉得比较多,只在极端显存受限的时候考虑。CPU内存带宽是瓶颈,卸载比例上去之后训练速度下降得挺陡。我一般控制在优化器状态卸载这个层面,参数和激活尽量留在显存里。
4.4 框架选型建议:集群规模、模型参数量、易用性与生态兼容性
选框架这事没有标准答案,得看手头有什么。我自己的判断逻辑是先看模型多大。10B以下的微调任务,PyTorch FSDP或者单机DeepSpeed就够,别把架构搞复杂。10B到100B这个区间,DeepSpeed配ZeRO-2/3是主流选择,Megatron可以叠加进来提吞吐。百B以上的预训练,基本就是Megatron加DeepSpeed的组合拳,别的方案成熟度跟不上。
集群规模也是关键变量。单机8卡,FSDP或者DeepSpeed单机模式都行,配置简单跑得也稳。多机场景下网络带宽决定一切,IB网络下DeepSpeed的通信优化能发挥出来,普通以太网就得把并行策略调得更保守。我见过有人在小带宽集群上硬上ZeRO-3,通信直接卡死,训练速度还不如单卡。
易用性这块得看团队情况。团队里有懂分布式底层的人,Megatron的灵活度值得投入。团队以应用为主,DeepSpeed和FSDP的文档和社区更友好一些。Colossal-AI和Ray Train适合特定场景,前者适合显存极度受限的异构环境,后者适合做大规模实验管理。生态兼容性我特别看重,HuggingFace生态能不能无缝接上,决定了很多现成工具能不能直接用。
我现在的默认组合是DeepSpeed加HuggingFace,绝大多数场景够用。遇到吞吐瓶颈再上Megatron改模型结构,遇到调度需求再引入Ray。框架这东西别追求一步到位,先跑起来再优化,比一上来就搭一套复杂架构实际得多。我见过太多团队在框架选型上纠结两个月,模型还没开始训,这才是最大的浪费。
预训练出来的模型像个知识渊博但不太会聊天的书呆子,你问它什么它都能接上话,但接得未必是你想要的。对齐训练干的就是这个事:把模型从“能说话”调到“会说话、说对话”。我在这块折腾的时间不比预训练短,RLHF、DPO、安全对齐、评估指标,每个环节都踩过一堆坑。这一章我按自己的实操经验捋一遍,有些地方可能跟论文里的标准说法有出入,但都是跑通了的。
5.1 人类反馈强化学习RLHF流程:奖励模型训练与PPO优化
RLHF的流程我拆成三步走:先攒偏好数据,再训奖励模型,最后用PPO把策略模型往奖励高的方向推。第一步最磨人,得让标注人员对同一prompt的两个回复做二选一,或者排个序。我试过自己标一批种子数据,标了三百条就顶不住了,枯燥程度远超预期。数据质量比数量重要得多,标注标准不统一的话,奖励模型学出来的偏好方向是歪的,后面怎么调都白搭。
奖励模型这块我吃过亏。一开始拿了个6B的模型直接上,训完发现它学会了“长回复就是好回复”这种捷径,因为标注员确实倾向选信息量大的长答案。我后来在损失函数里加了长度惩罚项,又在数据配比上刻意平衡长短回复,才算把这个问题压下去。奖励模型的过拟合也烦人,训到第三轮验证集准确率就往下掉,我一般只训一到两轮,宁可欠拟合一点。奖励模型的输出分布也得盯着看,我遇到过一次奖励值全挤在0.5附近,区分度几乎为零,PPO根本学不动。
PPO优化是整个流程里最不稳定的环节。超参敏感得离谱,学习率大一点直接崩,小一点收敛慢到怀疑人生。KL散度惩罚系数我调了两天才找到一个不跑偏的值,太小了模型开始胡说八道,太大了跟没训一样。我习惯在PPO之前先用SFT数据把模型暖一遍,再拿一个小学习率跑PPO,大概跑个几百步就停,再多容易过优化。训练过程中我得盯着奖励曲线和KL距离两条线看,奖励涨但KL爆了说明模型在钻奖励模型的空子,这时候得赶紧回滚checkpoint。说实话PPO这套流程跑下来,工程复杂度对团队要求挺高的,小团队我一般会劝他们先看看DPO。
5.2 直接偏好优化方法:DPO、IPO与KTO等变体
DPO我第一次用的时候有种“早知道就不折腾PPO了”的感觉。它把奖励模型和PPO两步压成一步,直接拿偏好数据优化策略模型,损失函数里隐式地表达了奖励。我拿同一批偏好数据分别跑了RLHF和DPO,DPO训出来的模型在人工评测里跟RLHF打平,但训练成本降了一大截。不需要单独训奖励模型,不需要在线采样,一张卡就能跑起来,这对算力不富裕的团队太友好了。DPO的坑在于它对偏好数据的质量更敏感,数据里有噪声的话它学偏的速度比RLHF快,因为没有奖励模型在中间做缓冲。
IPO是我在DPO基础上试的变体,核心改动是给偏好损失加了个正则项,防止模型对偏好数据过度自信。我拿它跑过一个对话风格对齐的任务,DPO训到后期输出变得特别机械,翻来覆去就那几种句式,换IPO之后多样性好了不少。代价是收敛慢一些,得比DPO多跑两三成的步数。KTO又是另一个路子,它不需要成对的偏好数据,只要知道单个回复是好是坏就行。我拿它处理过一批只有点赞点踩二元信号的数据,效果比硬凑成对再跑DPO要好。KTO适合那种有大量单边反馈的场景,比如线上产品的用户行为日志。
这几个方法我现在会按数据形态来选。有成对偏好数据、算力紧,首选DPO,调参少跑得快。数据噪声大、想要输出多样性,IPO更稳。只有单边反馈信号,KTO是唯一能直接用的。我见过有人拿DPO去训只有单边标注的数据,硬把好回复跟随机回复配对,训出来的模型偏好方向是乱的。方法选型这事得先看清楚手里数据长什么样,别看哪个新就用哪个。
5.3 安全对齐与价值观训练:红队测试、拒绝学习与可控生成
安全对齐这块我踩的坑比技术坑还多。红队测试我组织过两轮,第一轮让内部团队自己写攻击prompt,写出来的东西温和得跟养生建议似的,根本测不出模型的底线。第二轮我找了几个对提示词工程特别熟的外部人员,攻击面才打开,模型在角色扮演和逐步诱导这两类攻击下漏得跟筛子一样。红队测试得找思维“不规矩”的人来做,内部安全团队有时候反而有盲区。
拒绝学习我一开始理解得太简单了,以为给模型喂一批“这个问题我不能回答”的样本就行。训完发现模型变得过度敏感,正常问题也开始拒绝,用户问个感冒吃什么药它都要念一遍免责声明。后来我调整了数据配比,把拒绝样本控制在总数据的5%以内,又在拒绝话术里加了“我理解你的需求,但……”这种先共情再拒绝的结构,过度拒绝的比例才降下来。拒绝样本的措辞多样性也很重要,全用同一句话训,模型会把这句拒绝话术背下来,换个场景就不会用了。
可控生成是安全对齐里我觉得最有实用价值的方向。我做过一个内容审核场景的模型,需要它根据不同的安全等级切换输出风格。做法是在系统prompt里注入安全等级标记,然后在SFT阶段就用带等级标记的数据训。这个方案的好处是不用训多个模型,一个模型能应付不同严格度的场景。麻烦在于等级之间的边界得定义清楚,我调了三四版等级描述才让模型稳定区分。可控生成不是万能的,遇到对抗性输入还是会翻车,但日常场景下够用了。
5.4 对齐训练评估指标:有用性、无害性、诚实性与偏好一致性
对齐训练的评估是我觉得最容易被糊弄的环节。有用性还好办,拿一批测试prompt让模型生成,人工打分或者用GPT-4当裁判都行。我一般会同时跑自动指标和人工抽检,自动指标看趋势,人工抽检看细节。有个坑是自动裁判也有偏好,我试过用GPT-4评自己的输出,它给自己的打分明显偏高,后来换成不同家族的模型交叉评才客观一些。
无害性评估我主要靠红队测试集加分类器筛查。维护一个攻击prompt库,每次模型更新都跑一遍,看通过率。分类器筛查是拿一个训好的安全分类模型去判模型的输出,这个方案快但会漏,尤其是隐晦的有害内容。我的经验是分类器筛出来的结果还得人工过一遍,别全信。无害性有个容易被忽略的维度是“过度无害”,模型为了安全把正常请求也拒了,这个在评估里得单独设指标看。
诚实性评估我做得最粗糙,主要靠构造知识边界测试集,问模型一些它大概率不知道的问题,看它会不会硬编。我试过用事实性QA数据集做评估,但那些题模型在预训练阶段可能见过,测出来的诚实性虚高。后来我自己构造了一批时效性很强的问题,比如问上个月的新闻事件,模型编答案的比例一下就上来了。诚实性这块目前没有特别成熟的自动评估方案,人工评估成本又高,我一般是抽样看,重点关注模型说“我不知道”的频率和场景。
偏好一致性是我最后加进评估体系的。做法是拿一批对齐训练时没见过的偏好对,看模型选出来的回复跟人类偏好的一致率。这个指标跟奖励模型的验证集准确率有相关性,但不完全一样,我遇到过奖励模型准确率很高但策略模型偏好一致性掉下来的情况,说明PPO阶段过优化了。偏好一致性我一般控制在70%到80%之间就收手,再往上推模型输出会变得保守,多样性掉得厉害。评估这事没有银弹,多个指标交叉看,人工抽检兜底,是我目前觉得最靠谱的做法。
训练一个大模型,从写第一行代码到模型能上线跑起来,中间那些工程上的破事比我预想的多太多了。算法层面想明白了不代表能训出来,能训出来不代表训得起,训得起也不代表能稳定复现。这一章我聊的都是“把模型真正跑起来”这件事,有些做法不太优雅但管用,有些弯路我替你们走过了。
6.1 计算效率优化:混合精度、梯度累积、激活重计算与算子融合
混合精度这事我最早是当开关用的,以为把fp32换成bf16就完事了,训崩了几次才老实。bf16对我这种用A100/H100的来说基本是默认选项,数值范围够宽,不太用操心loss scale。fp16就麻烦一些,梯度小的时候下溢,loss scale得动态调,我遇到过scale跳变导致训练抖动的,后来干脆在fp16前面加了个预热阶段让scale稳下来再放开跑。混合精度不是免费的午餐,有些算子对精度敏感,比如LayerNorm和softmax,我一般让这几层保持fp32,其余走bf16,速度损失不大但稳定性好很多。
梯度累积是我显存不够时最常用的救命招。想跑大batch但单卡塞不下,就把一个batch拆成几个micro-batch,梯度攒着一起更新。听起来简单,坑在BatchNorm和Dropout这些跟batch size相关的层上,累积之后等效batch变了,超参得跟着调。我吃过一次亏,梯度累积的步数设成4,但学习率忘了按比例放大,训出来的模型跟温水煮青蛙似的,loss降得慢得让人想砸键盘。后来我养成了习惯,改累积步数的时候一定检查学习率和warmup步数有没有跟着动。
激活重计算我用得比较克制。它本质上是拿计算换显存,前向的时候不存中间激活,反向的时候重新算一遍。我试过把整个模型都开重计算,显存确实省了快一半,但训练速度掉了三成多,epoch时间拉长之后算总算力账其实没省多少。现在我一般只对占显存最大的那几个Transformer block开重计算,或者用选择性重计算策略,只重算attention部分。算子融合这块我依赖框架自动做,Flash Attention出来之后attention部分的显存和速度都好了不少,我基本是能开就开。
6.2 数据效率提升:主动学习、数据增强、合成数据与课程学习
数据效率这事我一开始不太在意,觉得数据越多越好,后来发现堆数据堆到一定量之后边际收益掉得厉害。主动学习我是在一个分类任务上试的,拿一个训了一半的模型去给未标注数据打分,挑置信度低的样本优先标注。效果确实有,用三成的标注量达到了全量数据九成的效果。麻烦在于流程要搭起来,推理、排序、人工标注、回流训练,每个环节都得通。我后来简化成只做不确定性采样,按entropy排序取top K,实现成本低不少,效果打个八折但够用。
数据增强在CV里是标配,NLP里我用得比较谨慎。回译增强我试过,拿中英互译绕一圈,同义句是有了但偶尔会引入语义漂移,尤其是专业术语多的领域,翻回来意思都变了。同义替换和随机插入删除这些我一般在数据量特别少的任务上用,比例控制在10%以内,再多模型学会的是噪声不是信号。合成数据是我最近半年用得最多的,拿一个大模型生成训练数据喂给小模型,或者拿强模型生成数据蒸馏弱模型。合成数据的坑在于分布偏移和多样性不足,我生成完之后一定会做去重和聚类分析,确保覆盖度跟真实数据差不多才敢混进去用。
课程学习我一开始觉得是论文里的花活,后来在预训练阶段试了一次改观挺大。做法是先拿短序列、简单样本训,再逐步加长度加难度。loss曲线比随机打乱平滑不少,早期收敛快了很多。难点在于“难度”怎么定义,我试过按序列长度、按困惑度、按领域稀有度排序,效果最好的是混合排序,先按长度分桶再在桶内按困惑度排。课程学习的调度策略我调了好几版,现在用的是一种渐进式混合,前期课程采样比例高,后期慢慢退化成均匀采样,避免模型在后期还过度关注简单样本。
6.3 训练过程管理:实验跟踪、超参数搜索、故障恢复与版本管理
实验跟踪这事我吃过不记录的亏。早期跑实验靠脑子记和文件夹命名,跑到第三四十组的时候彻底乱了,哪个配置对应哪个结果对不上号,白跑了好几组。现在我用wandb做跟踪,每个实验的config、loss曲线、评估指标、git commit hash全绑在一起。关键的来了,我要求团队里每个人跑实验必须写description,一句话说清楚这组实验改了什么,不然一周后自己都看不懂。wandb的sweep功能我也用,超参搜索省了不少手工活。
超参数搜索我现在的策略是分阶段。第一阶段用随机搜索粗扫,学习率、batch size、warmup比例这几个关键参数各取五六个值随机组合跑短程实验。第二阶段拿粗扫结果里最好的几组做精调,这时候用贝叶斯优化比随机搜索效率高。我一般不在大模型上做全量网格搜索,算力烧不起,粗扫加精调的组合够用了。有个小技巧是拿小模型先搜一轮超参,找出的最优配置按比例迁移到大模型上,虽然不是完全对应但能省很多时间。
故障恢复是分布式训练里我花时间最多的一块。训到一半节点挂了、OOM了、NCCL超时了,这些事在千卡集群上是家常便饭。我的做法是checkpoint存得勤,但也不能太勤,存一次几十GB的权重对存储压力很大。折中方案是每N步存一次,N根据训练总步数和故障频率来定,一般控制在半小时到一小时一次。checkpoint要存完整状态,模型权重、优化器状态、学习率调度器、数据加载器的位置全部要存,少一样恢复之后训练曲线就对不上。我还在训练脚本里加了自动恢复逻辑,检测到最近checkpoint就自动加载续训,半夜挂了我第二天早上来看已经自己跑上了。
版本管理我分三层。代码用git管,每次实验绑定commit hash。数据用DVC或者对象存储的版本号管,训练配置里写清楚数据版本。模型权重用模型注册表管,每个checkpoint打上训练配置、评估指标、数据版本的标签。这三层对上了,任何一次实验结果都能完整复现。我见过团队只记代码不记数据版本的,换个数据版本跑出来的结果差好几个点,根本说不清是模型改好了还是数据变了。
6.4 成本与效果平衡:模型压缩、知识蒸馏、量化训练与推理部署衔接
模型压缩这事得从训练阶段就开始想,别等训完了再发愁。我现在的习惯是在项目立项时就定好推理侧的约束,延迟要求多少、显存上限多少、吞吐量要多少,这些指标反推回来决定模型结构和训练策略。知识蒸馏是我常用的手段,拿一个大模型当teacher,训一个小模型去拟合teacher的输出分布。温度参数我一般设2到5之间,太高了软标签太软学不到细节,太低了退化成硬标签。蒸馏的loss权重我调过很多次,soft loss和hard loss的比例在7:3附近效果比较稳。
量化训练我分两种场景。训练后量化PTQ适合对精度损失容忍度高的场景,我一般用INT8,校准集选好了精度掉得不多。量化感知训练QAT麻烦一些但精度保持得更好,我在一个对延迟特别敏感的项目上用过,训的时候插入伪量化节点模拟量化误差,训完转成真量化模型,精度损失控制在1个点以内。量化的坑主要在异常值上,某些层的激活值范围特别大,一刀切量化会把小值全压没了,我一般做逐通道量化或者对异常值做截断处理。
推理部署衔接是我觉得最容易被忽略的环节。训练的时候用bf16跑得好好的,部署的时候发现推理引擎不支持某些算子,得改模型结构重训。我现在会在训练中期就拿目标推理引擎跑一遍导出和推理测试,提前暴露算子兼容性问题。batch size和序列长度也得对齐,训练时padding到固定长度,推理时变长输入,attention mask得处理好,不然batch内不同长度的样本互相干扰。部署侧的动态batch和continuous batching对训练侧也有影响,我一般会在SFT阶段就模拟推理时的batch组成方式做训练,减少训练推理不一致带来的掉点。