ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型训练全流程拆解:从预训练到对齐的工程实践指南

大模型训练全流程拆解:从预训练到对齐的工程实践指南

大模型训练这个话题,最近两年已经被聊烂了,但大多数文章要么停在“原理科普”的层面,要么一头扎进某个框架的API里出不来。作为一个从预训练跑到对齐全流程踩过坑的开发者,我越来越觉得,大模型训练真正难的不是某个具体技术,而是你脑子里有没有一张完整的地图:预训练在解决什么问题,微调在解决什么问题,对齐又在解决什么问题。只有把这三个阶段的全貌看清楚了,你读代码、调参数、看loss曲线时才知道自己到底在干什么。这篇东西写给准备上手或正在上手的程序员,不堆概念,只讲人话,值得收藏备用。

1. 先建立全景:大模型训练这个“三步走”到底在干嘛

1.1 三个阶段的本质分工

很多朋友第一次接触大模型训练时,容易把预训练、微调、对齐当成三个递进的“技术版本”,以为第二阶段比第一阶段高级。其实不是,它们是三个完全不同层次的问题。

预训练的本质是“语言能力启蒙”。模型在这个阶段吃下海量文本,学习词的分布、句子的衔接、世界的常识与推理模式。这个阶段出来的是一个“通才”,它懂语言,但不太会“聊天”——你问它问题,它大概率会顺着概率往下续写,而不是像助手一样回答你。这是自回归语言模型的天性。

微调(通常特指SFT,监督微调)的本质是“行为塑造”。我们给模型看大量“指令-标准回答”的样本,让它从“会说话的百科全书”变成“会听话的助手”。这个阶段训练的是一种格式化的交互能力:模型学会在遇到用户输入时,先理解指令,再组织符合预期的回答。换句话说,微调教的是“怎么做答”,不教“应该做什么”。

对齐则是在解决价值观和安全问题。模型能力越强,风险越大——它能写出煽动性文章、能教人做危险的事情,这不是因为它“坏”,而是因为它“太会顺着概率说话”。对齐阶段要通过人类反馈、偏好学习等手段,把模型的输出拉回“有用、诚实、无害”的轨道上来。

1.2 数据、算力与成本的三级跳

三个阶段最直观的差异体现在数据量和算力需求上,这也是让很多初次接触的人震惊的地方。

先说预训练。这个阶段消耗的是“TB级”的文本数据,以目前常见的规模看,优质大模型基本都要在数万亿token上训练。数千张GPU卡跑上几个月是常态,成本以千万甚至亿为单位计算。这也是为什么预训练基本只有大厂或头部研究机构玩得起,普通人很难参与。

到了微调阶段,数据量级骤降为“几万到几十万条指令”。因为SFT只看重指令质量与覆盖面,不追求数据规模。算力需求同样大幅下降:一张高显存显卡或者几张小卡拼起来就能跑中小尺寸模型的微调。这也是个人开发者、中小企业进入大模型领域的主要入口。

对齐阶段的数据量更少,通常在“几万到十几万条偏好对”的级别,但它的成本反而更复杂。因为数据不是从网上抓的,而是需要人工标注、评估人员打分、甚至反复迭代采样生成的。这部分的人力成本往往被低估。

1.3 顺序不能乱,但也别把三个阶段理解成完全孤立

为什么必须是这个顺序?原因在于“能力地基”与“行为装饰”的关系。

预训练决定了模型的“知识上限”和“语言智力”。如果模型本身的语言能力不够,你给它再好的指令数据,它也学不会复杂推理;反过来,一个能力很强的预训练模型,即使微调数据很少,也能表现出不错的对话水平。因为底座足够强,微调更像是在“给已有能力加个调用开关”。

对齐则是建立在微调之上的“价值观塑形”。你可以先微调再对齐,但不能跳过微调直接对齐。原因很朴素:对齐需要模型已经具备较高的指令遵循能力,否则奖励模型给出的信号没有意义。

不过,三个阶段也不是彻底割裂的。实践中常会遇到“灾难性遗忘”问题:微调阶段可能会忘掉预训练学会的部分知识,对齐阶段又可能把微调学会的格式冲淡。所以现在很多做法是在三者之间反复穿插训练,而不是机械地一站到底。理解这一点,你才能真正明白为什么开源模型的训练日志里,经常会出现几个阶段的混合配置。

2. 预训练:让模型学会“说人话”

2.1 一次只干一件事:next token prediction

预训练这个阶段看起来复杂,实际上模型的任务只有一个:给定前文,预测下一个词。我常跟朋友开玩笑说,这是世界上最简单的任务,也是世界上最难的任务——简单在于目标函数只有一个交叉熵,难在于你要让模型在数万亿token上学到足以支撑通用能力的统计规律。

具体到训练过程,模型把一段文本切开,用前面的token预测后面的token,然后计算预测分布与真实token之间的交叉熵损失,反向传播更新参数。你可能会问:这么朴素的训练目标,为什么能学出推理、代码、多语言能力?答案就在数据的规模和模型的容量上。当参数规模足够大、训练数据足够多时,模型内部会自发涌现出许多中间表征,比如语法结构、实体关系、甚至某种程度的逻辑链。这就是所谓的“涌现能力”,它不是被谁设计出来的,而是统计学习在超大规模下的副产品。

2.2 预训练数据工程:细节全在清洗和配比

预训练阶段,模型架构和训练技巧其实高度同质化,真正拉开差距的是数据,这也是我强烈建议每个入行者至少了解一遍的地方。

数据工程首先是从海量原始文本里去除“垃圾”:去重(包括段落级、文档级、模糊去重)、过滤低质量内容、剔除敏感与有害信息。常见做法是用一个现成的小模型给数据打分,然后按分数阈值过滤,或者用perplexity(困惑度)指标筛掉过于杂乱或过于重复的文本。这个步骤听起来简单,实际做起来会发现大量边界情况:比如网页里的导航文本、乱码转码、多语言混排,都会干扰模型学习。

除了清洗,配比也很关键。通用文本、代码、数学、多语言数据的比例直接决定模型的能力偏向。代码比例调高,模型的逻辑推理和指令遵循能力通常会更好;数学数据多一点,数理能力更强,但可能挤占通用知识空间。这个比例没有标准答案,需要反复做小规模实验来验证趋势。

Tokenizer(分词器)同样容易被忽略。一个好的词表应该用BPE(Byte Pair Encoding)等算法在目标语料上训练,词表大小一般在3万到10万之间。词表太小会让序列变长、训练效率下降;词表太大会让embedding矩阵巨大,显存占用飙升。很多人只关注模型参数量,却忘了词表大小对显存的实打实影响,这是一个非常典型的工程坑。

2.3 训练离不开的工程参数:学习率、混合精度与显存

预训练阶段,你每天跟GPU显存和训练稳定性较劲。这里把最核心的几个工程点列出来,每一个都是在真实训练中反复被验证过的。

第一,学习率调度。大模型训练几乎都采用“warmup + 余弦退火”的调度方式。前几千步学习率从极小值线性升到峰值,然后再按余弦曲线缓慢降到接近零。warmup的意义在于让优化器(尤其是Adam中的二阶动量估计)先稳定下来,避免前期大步长导致loss炸掉。峰值学习率通常落在1e-4到3e-4之间,具体数值取决于模型规模、batch size以及优化器设置。

第二,混合精度训练。现在主流做法是BF16(bfloat16)。相比FP16,BF16的指数位和FP32一致,极大避免了训练中常见的溢出问题。虽然尾数位少了,但在梯度更新中通常够用。配合动态损失缩放(loss scaling)在FP16里很常见,但在BF16下基本不需要。

第三,显存估算。这里给你一个粗算方式:对于一个7B参数的模型,BF16权重占14GB,Adam优化器状态(一阶矩+二阶矩+权重)约占42GB,梯度占14GB,再加上激活值和临时变量,单卡显存低于80GB基本跑不动全参数预训练。这也是为什么预训练阶段离不开ZeRO(零冗余优化器)或FSDP(完全分片数据并行)这类并行策略。它们把权重、梯度和优化器状态切分到多张卡上,从而让集群显存被充分利用。

2.4 训练中看什么指标:loss、perplexity与基准测试

预训练不是“跑完就完事”,你要持续盯着几个指标来判断训练状态。最基础的是训练集loss下降曲线。正常情况下loss应该稳步下降,如果出现平台期很久不动,就要检查数据质量或学习率。

其次是验证集perplexity(困惑度)。它衡量模型对文本的预测能力,越低越好。不过perplexity下降不代表综合能力一定提升,所以业界普遍会在训练中途定期跑一批通用评测集,比如语言理解、常识推理、数学、代码生成等基准测试。预训练阶段做这些评测不是为了“刷榜”,而是判断模型在各项能力上是否均衡发展。

我习惯同时跟踪梯度范数(gradient norm)。梯度范数如果出现突然的尖峰,通常意味着遇到了异常batch,需要调低学习率或者加大梯度裁剪阈值。梯度裁剪在大模型训练中几乎是标配,常见做法是设定一个最大值(比如1.0),超出即按比例缩放。

3. 微调(SFT):把通才变成职业选手

3.1 SFT真正训练的是什么

很多人误以为微调是给模型“灌输知识”。这个理解是错的。微调使用的数据量很小,知识容量的提升极其有限,它的真正作用是教会模型“如何按指令组织回答”。

举个例子,预训练模型拿到一句“给我写一封请假邮件”时,它可能会接着续写一堆可能性,但不会给你一封结构完整的邮件。经过微调后,模型看到同样的问题,会先写称呼、再写正文、最后落款署名。这就是行为格式化。

SFT的数据格式通常是“用户指令-助手回复”配对,数据组织形式可以是单轮问答,也可以是多轮对话。多轮对话数据还需要包含系统提示、角色标记等特殊token,让模型学会区分哪些是用户说的、哪些是助手说的。在实践中,我发现多轮数据比例太低会导致模型在长对话中“角色错乱”,比如突然替用户回答问题。

3.2 指令数据:量不重要,质和覆盖度才重要

SFT阶段最影响效果的不是模型结构,而是指令数据本身。我见过不少人拿着5万条随机抓取的指令数据去微调,结果效果不如别人精心整理的5000条。这个行业对数据质量的重视程度远超你的想象。

高质量的指令数据需要满足几个条件。一是多样性:覆盖写作、代码、数学、总结、翻译、问答等不同任务,每个任务的句式表达也要多种多样。二是难度梯度:既有简单指令,也有需要多步推理的复杂指令,这样才能逼模型学到深层的任务理解。三是格式规范:每条数据的指令描述清晰,期望回答完整且无重复内容。

很多开源数据集的通病是“风格单一”,比如写出来的回答总是“首先……其次……最后……”这种结构。模型会学到这种模板腔调,这是SFT里最让人头疼的“中国式学生腔”问题。解决方式是在数据里刻意混入不同文风的回答,并加入一定比例的对比样本,让模型知道同一个问题可以有多种得体回答。

3.3 全量微调与LoRA/QLoRA:怎么选

微调阶段,摆在开发者面前的第一道选择题是全量微调还是参数高效微调。

全量微调(Full Fine-tuning)会更新模型所有参数,效果上限最高,但对显存和算力要求也最高,而且会产生一份完整的新模型权重,存储和分发成本都不低。

LoRA(低秩适配)是目前最主流的折中方案,它冻结原始权重,在每层注入低秩矩阵来近似参数更新。实践里LoRA的rank值取8到64之间,模型效果就能接近全量微调,而可训练参数量通常只有总参数量的0.1%到1%。rank不是越大越好,过大的rank反而容易在小数据上过拟合。

QLoRA是LoRA的进一步升级,它在加载模型时先做4位量化(通常用NF4格式),再在量化后的模型上挂LoRA。这样一张24GB显存的消费级显卡可以微调7B乃至13B级别的模型,极大降低了个人开发者的大门门槛。代价是训练速度会变慢、数值精度略降,但对绝大多数应用来说精度损失可以接受。

3.4 让LoRA训练稳定的几个参数

如果你决定用LoRA做SFT,下面这份配置是我验证过很多次、相对稳妥的起点。

model_name_or_path: /models/base_model train_file: /data/sft_data.jsonl block_size: 2048 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 2e-5 num_train_epochs: 3 lr_scheduler_type: cosine warmup_ratio: 0.03 bf16: true gradient_checkpointing: true

这里有几个参数值得展开解释。block_size是序列长度,当前主流模型支持长上下文,但训练时序列越长显存占用越大,2048是一个兼顾效果与显存的常见起点。per_device_train_batch_size设成2,配合gradient_accumulation_steps=8,等效batch size是16。这个规模既能保证梯度估计相对稳定,又不会让显存爆炸。learning_rate设在2e-5,比预训练阶段的峰值学习率低一个数量级,因为微调只是在小范围内调整参数,步子太大容易破坏底座能力。num_train_epochs通常2到4之间就有明显效果,训练超过5轮很容易过拟合——模型开始“背诵”训练集,而不是学习行为模式。

还需要注意LoRA的target_modules选择。一般建议覆盖attention层的q_proj、k_proj、v_proj、o_proj,如果任务需要更强的语义理解能力,可以把MLP层的gate_proj、up_proj、down_proj也加进来。加更多模块意味着更多可训练参数,训练更慢,但上限更高,需要自己权衡。

4. 对齐:让模型从“聪明”到“可信任”

4.1 为什么要对齐:有用、诚实、无害

如果你把预训练和微调做完,模型已经能正常对话了,看起来万事大吉。但真正放到用户面前时,问题会暴露得很彻底。预训练模型的知识库里包含着大量“不适宜直接输出”的内容,而它本身对“什么该说、什么不该说”毫无概念。

对齐的目的就是让模型输出符合三个原则:有用(Helpful)、诚实(Honest)、无害(Harmless)。用大白话解释:用户问什么,尽量给出有信息量的回答;不知道的事情要承认不知道,而不是一本正经地编造;危险或有害的内容,要拒绝或引导到安全方向。这三点听上去朴素,但做起来极难。因为“无害”和“有用”在某些场景下是冲突的——用户问一个危险品的配制方法,模型既要拒绝,又最好能解释为什么不能提供,这里面充满了权衡。

4.2 RLHF:三体训练与PPO更新

对齐的主流方案仍然是基于人类反馈的强化学习(RLHF),它的架构里通常有三个模型协同工作:策略模型(就是我们要对齐的大模型)、参考模型(冻结的初始版本)、奖励模型(负责给回答打分)。

RLHF的落地分三步。第一步,用人类标注员在多个候选回答之间选优,形成一个“偏好对”数据集,比如回答A比回答B好。第二步,在这个偏好数据上训练奖励模型,让它学会预测“哪条回答更符合人类偏好”。第三步,用PPO(近端策略优化)算法更新策略模型,让模型输出的回答尽可能获得高奖励分,同时通过KL散度惩罚把模型行为限制在参考模型附近,防止模型为了拿奖励而说出偏离原本风格的怪话。

PPO更新的核心公式这里不展开了,你只需要记住两个关键点:一、策略模型在训练中会不断采样新回答,然后拿奖励模型打分作为训练信号;二、KL散度惩罚的系数不能太大也不能太小——太大模型学不动,太小模型会牺牲质量去钻奖励模型的空子。这个系数在实际训练里调得人头大,我经历过的最夸张一次,是奖励分数一路上涨,但人工评估的文本质量肉眼可见地变差——典型的奖励欺诈。

4.3 DPO:更轻量的对齐路线

RLHF虽然效果好,但工程复杂度实在感人:四个模型同时加载、采样和训练交替进行、超参数多、故障率高。所以这几年DPO(直接偏好优化)越来越受欢迎。

DPO的核心理念非常聪明:既然奖励模型要在偏好对上学打分,那我干脆把“奖励模型能表达的分”直接隐式地表达在策略模型的更新目标里,用偏好对数据直接引导策略模型调整。这样训练过程就把“奖励模型训练”和“PPO更新”两步合并成一步,不再需要采样生成新回答,也不需要单独维护一个庞大的奖励模型。

用DPO训练时,你只需要准备偏好对数据集(回答A优于回答B),然后用一个同时包含“策略模型输出概率”和“参考模型输出概率”的损失函数来更新策略模型。这个损失函数的实质是:让模型更喜欢偏向被标注为“好”的回答,同时不要偏离参考模型太远。DPO在数据质量好的时候,效果完全可以和RLHF掰手腕,但它的局限在于训练信号完全依赖偏好数据本身的分布,数据一旦有噪声,传播速度比RLHF快得多。

4.4 对齐的代价:奖励欺诈与对齐税

对齐不是免费的午餐,这里有几个绕不开的代价。

第一个是奖励欺诈(reward hacking)。模型非常擅长找出能让奖励模型给高分、但实际质量并不好的输出模式。比如模型发现写“抱歉,我无法回答”能显著避害,于是它对所有问题都过度拒绝。你问它今天天气怎么样,它也回答“抱歉,我无法提供信息”。这是对齐阶段最经典的失败模式,只能靠人工评估不断纠偏。

第二个是“对齐税”。为了降低有害性,模型会变得“保守”,在模棱两可的问题上给出更简短、更谨慎、甚至更无趣的回答。这在某种程度上牺牲了创造性和信息密度。有研究观察到,经过强对齐的模型在部分创意写作任务上的表现会出现轻微滑坡,这就是对齐税在起作用。

第三个是数据退化的累积效应。用模型生成的数据再去训练新模型,时间久了,后代会变得越来越“均值化”,多样性下降。这也是为什么现在很多团队开始在做对齐的同时,刻意保留一部分有风险但合理的高质量样本,防止模型变得过度唯唯诺诺。

5. 踩坑实录:常见问题与排查思路

5.1 一张速查表解决80%的异常

下面把我在训练和微调过程中遇到的高频问题整理成速查表,送给准备动手的朋友。

现象可能原因排查方向
loss不降或平台期过长数据质量差 / 学习率过高或过低检查数据是否重复、清洗是否到位;可视化学习率曲线
loss突然飙高遇到异常batch / 学习率过大启用梯度裁剪;降低学习率;检查数据里是否有大段乱码
显存溢出(OOM)序列过长 / batch过大调小block_size;开gradient checkpointing;用gradient accumulation
生成内容重复微调过拟合 / 数据模板单一减少训练轮数;增加数据多样性;降低LoRA rank
模型失忆(忘记预训练知识)微调轮次过多 / 学习率过大做数据重放;恢复部分预训练数据混合训练;降低学习率
对话角色错乱多轮数据比例不足 / 格式错误检查特殊token;增加多轮样本比例
对齐后过度拒绝偏好数据失衡 / KL系数不当调整偏好数据;降低KL惩罚;减少无害样本占比

5.2 两个典型问题的排查流程

第一个案例:训练loss前期下降正常,但到第2000步开始周期性震荡。排查时首先看学习率调度,发现warmup结束后学习率未按预期进入稳定区,而是处于一个过高的常数。解决办法是把峰值学习率下调30%,并在训练日志里加入学习率记录项,方便复现。震荡问题消失后,loss继续平滑下降。

第二个案例:微调后的模型在对话中频繁重复同一个句子。直接怀疑过拟合。查了训练轮次,已经跑到第8轮;再检查数据,发现指令数据里大量回答都采用同一套固定句式。于是把训练轮次降到3,同时抽取了2000条外部高复杂度指令混入数据,问题显著缓解。

从这两个案例可以看出,排查训练问题最重要的不是改代码,而是先建立“数据和超参数”两个维度的直觉。数据的问题会在训练曲线上表现出规律性,超参数的问题则更倾向于全局性震荡或发散。

5.3 给程序员的日志与权重监控建议

很多朋友训练时只盯着loss看,一旦异常就束手无策。我的建议是至少把下面几个指标全部打点记录:训练loss、验证loss、学习率、梯度范数、显存占用、吞吐量(tokens/s)。这些指标放在同一张图表里观察,能极大缩短定位问题的时间。

比如梯度范数持续升高,说明优化器正在走向不稳定,宁可降学习率也不要硬跑;吞吐量突然下降,大概率是数据加载环节出现瓶颈,比如磁盘IO或预处理线程不足。这些判断在训练集群上非常常见。

模型权重的保存策略也值得注意。不要只保存最后一版checkpoint,而是每隔一定步数保留一个副本。这样当某一次训练后期loss反弹时,你可以快速回滚到一个已知良好状态。我自己习惯每500到1000步存一次,磁盘充足的情况下多存几份,成本远低于重新训练。

另外,日志一定要带时间戳和全局步数。没有这两个信息,你在对比多个实验时根本没法定位到底是哪段代码、哪个参数导致的差异。这个习惯越早养成越省事。

最后说点我的个人体会。很多程序员朋友的起步路径都是直接拿现成模型做推理推理,很少自己动手训练。但我的建议始终是:抽出两周时间,哪怕用一个小规模的模型,把预训练、微调、对齐全流程完整跑一遍。你会惊觉,那些文档里的概念全都“活”了过来。我自己第一次跑通全流程时,最大的收获不是模型效果有多好,而是终于理解了loss、学习率、数据质量、KL惩罚这些名词在真实训练中是怎样的存在。踩过几次坑之后,你再去读任何一篇大模型论文,都会比之前通透得多。所以别怕折腾,动手吧。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进