大模型训练这件事,外行看热闹,内行看门道。很多人以为从零训一个模型就是"找一堆数据丢进去跑"——真上手了才发现,数据清洗能吃掉你一半时间,预训练烧钱烧到你怀疑人生,SFT做完发现模型变"傻"了,DPO调完又发现通用能力掉了。我前后参与过几个从零到一的中小规模模型训练项目,踩过的坑比读过的论文还多。这篇就把整条链路——数据准备、预训练、SFT、DPO/RLHF、评估——按实际操作的顺序拆开讲,每一步为什么这么做、参数怎么定、哪里最容易翻车,都给你说明白。适合有一定深度学习基础、想真正跑通全流程的工程师,也适合想搞清楚各阶段到底在干什么的产品和算法同学。
1. 先把训练链路的地图铺开
1.1 四个阶段各自解决什么问题
从零训练一个大模型,本质上是把一个随机初始化的神经网络,逐步塑造成"能听懂人话、能按指令干活、还符合人类偏好"的助手。这个过程被拆成四个阶段,不是拍脑袋定的,而是每一阶段解决上一阶段遗留的问题。
预训练(Pre-training)解决的是"知识从哪来"。模型在这个阶段通过海量文本做下一词预测,把语言规律、世界知识、推理模式的统计结构压进参数里。这个阶段出来的叫基座模型(Base Model),它的能力是"续写",你给它"今天天气",它会接"不错,适合出门"——但它不会乖乖回答你的问题,因为它没见过"问答"这种格式。
SFT(Supervised Fine-Tuning,监督微调)解决的是"怎么听懂指令"。用人工标注的"指令-回答"对,教模型把基座的续写能力对齐到对话格式上。做完SFT,模型才知道"用户问什么,我该答什么"。
DPO/RLHF(偏好对齐)解决的是"回答得好不好"。SFT只教了模型"该怎么答",但没教它"哪种答法更好"。偏好对齐用人类对多个回答的排序数据,让模型学会偏向更helpful、更honest、更harmless的输出。
评估(Evaluation)贯穿始终,解决的是"我怎么知道它变好了还是变坏了"。没有评估的训练就是盲开,你根本不知道这一步是进步还是退步。
1.2 各阶段的资源量级差异
很多人对资源没概念,以为四个阶段花的钱差不多。实际差距是数量级的:
| 阶段 | 数据量级 | 算力占比 | 典型耗时(中小规模) |
|---|---|---|---|
| 预训练 | 数十B~数T token | 95%以上 | 数天到数周 |
| SFT | 数万~数十万条 | 2%~4% | 数小时到一天 |
| DPO/RLHF | 数万~十万条偏好对 | 1%~3% | 数小时 |
| 评估 | 数千条测试集 | 可忽略 | 数十分钟 |
这张表最想告诉你的是:预训练是吞金兽,后三个阶段是精雕细琢。所以业界主流做法是——预训练尽量复用开源基座(比如拿一个已有的中文基座继续预训练),把精力砸在SFT和偏好对齐上,因为这两步才是决定"好不好用"的关键。从零训一个7B模型的全量预训练,没有几十张卡和几周时间根本下不来,个人和小团队几乎不可能。所以本文讲的"从零",更准确说是"从基座开始走完整链路",预训练部分会讲清楚原理和续训(continue pre-training)的实操。
2. 数据工程:决定上限的隐形战场
2.1 预训练数据的清洗流水线
预训练数据的质量直接决定模型上限,这句话不是口号。我见过太多项目,模型效果上不去,最后定位到是数据里混了大量重复、乱码、低质内容。一条靠谱的清洗流水线通常包含这几步:
第一步,格式统一与解析。把各种来源(网页、书籍、论文、代码)统一转成纯文本。网页数据要用解析库提取正文,去掉导航栏、广告、页脚。这一步的坑在于:很多解析库对中文网页支持差,会把正文和噪声混在一起,建议对解析结果做长度和标点密度过滤。
第二步,去重。这是最容易被低估的一步。网页数据里重复内容极多,同一篇新闻被转载几十次很常见。去重要做两个粒度:精确去重(对文档做哈希,完全相同的直接删)和模糊去重(用MinHash或SimHash,把高度相似的文档聚类后只留一篇)。实测下来,模糊去重能把数据量砍掉30%~50%,而且几乎不损失有效信息。
第三步,质量过滤。常用规则包括:过滤过短文档(比如少于50个字符)、过滤标点符号占比异常的(乱码特征)、过滤重复行占比过高的、过滤包含大量特殊字符的。还可以训一个轻量分类器,用高质量语料(比如百科、书籍)做正样本,给网页数据打分,低于阈值的丢掉。
第四步,敏感与有害内容过滤。这一步必须做,而且要留痕。用关键词表加分类模型双重过滤,把违规内容挡在训练之前。别想着"训完再对齐",预训练阶段混进去的有害内容,后面对齐很难完全洗掉。
提示:清洗流水线的每一步都要记录"输入多少条、输出多少条、丢弃原因分布"。这个日志在后期排查模型行为异常时是救命稻草。
2.2 SFT数据的构造与标注陷阱
SFT数据的核心是"指令-回答"对的质量,而不是数量。我踩过最大的坑就是:早期贪多,用自动生成的方式造了十几万条SFT数据,结果模型学了一堆套话和错误格式,反而比用两万条精标数据训出来的差。
构造SFT数据有几个实操要点:
- 指令多样性比数量重要。覆盖问答、摘要、改写、代码、推理、多轮对话等不同任务类型,每类都要有。如果全是单轮问答,模型遇到多轮就崩。
- 回答要"像人写的",不要"像AI写的"。很多标注员会写出"首先...其次...最后..."这种模板化回答,模型学完就变成复读机。要明确要求标注员用自然、有信息量的表达。
- 长度分布要合理。如果所有回答都是200字左右,模型会失去长短控制能力。要刻意混入短回答(一句话)和长回答(上千字)。
- 多轮对话要单独构造。多轮数据的格式和单轮不同,要保留完整的对话历史,让模型学会"根据上下文回答"。
标注环节最大的陷阱是标注员理解偏差。同一个指令,不同标注员的理解可能完全不同。解决办法是:先写详细的标注规范,做小批量试标,开校准会统一标准,再大规模铺开。试标阶段发现的问题,比大规模返工便宜一百倍。
2.3 偏好数据的收集方式
DPO/RLHF需要的是"偏好对"——同一个prompt下,一个chosen(更好的回答)和一个rejected(更差的回答)。收集方式主要有三种:
人工标注质量最高但最贵。让标注员对同一个prompt的多个模型输出排序,取最好和最差的组成偏好对。
模型生成+人工筛选是性价比之选。用当前模型对同一prompt采样多个回答(调高temperature增加多样性),再让人工挑出好的和差的。
AI反馈(RLAIF)用更强的模型当裁判,给回答打分。成本低,但要注意裁判模型的偏好会"传染"给被训模型,可能引入偏见。
偏好数据的坑在于chosen和rejected的差异要"有意义"。如果两个回答只是措辞不同、质量相当,模型学不到有用信号,反而会过拟合到无关特征上。理想情况下,rejected应该在事实性、有用性、安全性等维度上有明确缺陷。
3. 预训练:从基座续训的实操路径
3.1 为什么大多数人应该选续训而非从零
从零预训练一个模型,意味着随机初始化所有参数,用海量数据从头学。这条路的问题在于:你需要的算力和数据量,和你能拿到的,往往差一个数量级。一个7B模型要达到可用的中文能力,至少需要几百B到上T的token,对应几十到上百张高端卡的持续训练。个人开发者和小团队根本扛不住。
续训(Continue Pre-training)的思路是:拿一个已经预训练好的开源基座(比如已有的中文基座),用你自己的领域数据继续训练。这样模型已经具备通用语言能力,你只需要注入领域知识或适配目标语言分布。算力需求降低一到两个数量级,效果却往往更好。
选择续训的前提是:你的目标领域和基座的原始训练分布有差异。比如基座主要训英文,你要做中文法律问答,那就需要续训注入中文法律语料。如果基座已经覆盖你的领域,直接跳到SFT可能更划算。
3.2 续训的关键参数与学习率策略
续训不是简单地把预训练脚本再跑一遍,参数设置有讲究:
学习率要小。从零预训练常用1e-4到3e-4的学习率,续训建议降到1e-5到5e-5。原因很简单:模型已经在一个不错的参数位置上,学习率太大会把它"踢飞",破坏已有的通用能力。我一般从2e-5起步,观察loss曲线,如果下降太慢再适当调高。
用余弦退火加warmup。warmup步数设为总步数的1%~3%,让学习率从0平滑升到峰值,再余弦衰减到接近0。这样训练末期模型更稳定。
批次大小和序列长度。序列长度建议用基座训练时的长度(常见2048或4096),太短学不到长程依赖,太长显存吃不消。批次大小在显存允许范围内尽量大,配合梯度累积达到目标有效批次。
数据配比要控制。续训数据里,领域数据和通用数据建议按7:3或8:2混合。全用领域数据会导致"灾难性遗忘"——模型领域内很溜,通用能力全丢。混入通用数据能缓解这个问题。
# 续训关键参数示例(基于常见训练框架的配置风格) training_args = { "learning_rate": 2e-5, # 续训用小学习率 "lr_scheduler_type": "cosine", # 余弦退火 "warmup_ratio": 0.02, # 2% warmup "num_train_epochs": 1, # 续训通常1个epoch足够 "per_device_train_batch_size": 4, "gradient_accumulation_steps": 8, # 有效批次=4*8*卡数 "max_seq_length": 4096, "bf16": True, # 混合精度省显存 }3.3 灾难性遗忘的监测与缓解
灾难性遗忘是续训最大的风险。表现是:领域任务变好了,但通用问答、常识推理明显变差。监测方法是在续训过程中定期跑通用能力评测集(比如常识问答、阅读理解),一旦发现通用指标掉超过阈值,就要调整数据配比或降低学习率。
缓解手段除了混入通用数据,还有两个实用技巧:一是分层学习率,底层(靠近输入的层)学习率设得更小,因为底层学的是通用语言特征,不该被大幅改动;二是LoRA等参数高效微调,只训练一小部分参数,对原模型扰动最小。如果算力紧张,LoRA续训是性价比极高的选择。
4. SFT:把基座变成听话的助手
4.1 全量微调还是LoRA
SFT阶段第一个决策就是:全量微调还是参数高效微调(LoRA/QLoRA)。两者没有绝对优劣,看你的资源和目标:
| 维度 | 全量微调 | LoRA |
|---|---|---|
| 显存需求 | 高(需存优化器状态) | 低(只训少量参数) |
| 训练速度 | 慢 | 快 |
| 效果上限 | 略高 | 接近全量 |
| 多任务切换 | 需存整个模型 | 只存适配器,可热插拔 |
| 适合场景 | 资源充足、追求极致 | 资源有限、多版本迭代 |
我的经验是:中小团队优先LoRA。7B模型用LoRA,单张24G显存的卡就能跑起来,效果和全量微调的差距在多数任务上小于1个百分点。而且LoRA适配器只有几十MB,方便做A/B测试和版本管理。只有当LoRA效果明显不够、且你有充足算力时,才上全量。
LoRA的关键参数是秩(rank)和alpha。rank一般设8~64,任务越复杂、数据越多,rank可以越大。alpha通常设为rank的1~2倍。学习率比全量微调高一个数量级,常用1e-4到3e-4。
4.2 训练轮数与过拟合的博弈
SFT最容易犯的错是训太多轮。SFT数据量通常不大(几万条),模型很容易记住训练集,表现为:训练loss持续下降,但验证集loss先降后升,生成结果开始出现训练集里的原句。
实操建议:训1~3个epoch,边训边看验证集。每训一定步数就存一个checkpoint,用固定的评测集测一遍,选验证效果最好的那个,而不是最后一个。我一般会存5~10个checkpoint,最后挑。
另一个信号是生成多样性下降。如果模型对同一个问题,不管怎么调temperature都给出几乎一样的回答,说明过拟合了。这时候要么减少轮数,要么增加数据多样性。
4.3 对话模板与特殊token的处理
SFT阶段有个特别容易被忽略的细节:对话模板必须和推理时一致。训练时用什么格式拼接system、user、assistant,推理时就得用什么格式。格式不一致会导致模型"看不懂"输入,效果断崖式下跌。
常见做法是定义特殊token,比如用<|im_start|>和<|im_end|>标记每轮对话的边界。训练数据要严格按模板拼接,并且只在assistant的回答部分计算loss,user和system部分要mask掉。这一点如果搞错,模型会学着去"预测用户的提问",而不是"生成回答"。
# 对话模板拼接示例(示意) def build_prompt(messages): text = "" for msg in messages: text += f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>\n" text += "<|im_start|>assistant\n" return text # loss mask:只对assistant部分计算损失 # 需要在tokenize后,把非assistant部分的label设为-100注意:特殊token要在tokenizer里注册,并确保模型embedding层有对应的位置。如果用了基座没有的新token,需要resize embedding并让新token也参与训练。
5. DPO与RLHF:让模型学会"哪种回答更好"
5.1 RLHF的三步走与它的痛点
经典RLHF分三步:第一步训一个奖励模型(Reward Model),用人类偏好数据学一个"打分器";第二步用强化学习(通常是PPO)优化策略模型,让它生成的回答能拿高分;第三步用KL散度约束,防止模型为了拿高分而"跑偏"。
RLHF效果好,但痛点明显:流程复杂、训练不稳定、资源消耗大。PPO需要同时加载策略模型、参考模型、奖励模型、价值模型,显存直接翻几倍。而且PPO对超参极其敏感,调不好就训崩。这也是为什么DPO一出来就迅速流行。
5.2 DPO为什么更受欢迎
DPO(Direct Preference Optimization)的核心洞察是:可以跳过奖励模型和强化学习,直接用偏好数据优化语言模型。它把RLHF的目标函数做了一个数学变换,变成一个类似分类的损失——让模型提高chosen回答的概率,降低rejected回答的概率。
DPO的优势很直接:只需要策略模型和参考模型两个模型,训练稳定,超参少,效果在多数任务上不输RLHF。对中小团队来说,DPO几乎是偏好对齐的首选。
DPO的关键参数是beta,控制模型偏离参考模型的程度。beta越大,约束越强,模型越保守;beta越小,越激进。常用0.1~0.5,我一般从0.1起步。学习率要比SFT再小一个量级,常用5e-7到5e-6。
# DPO训练核心逻辑示意 # 对每个偏好对(prompt, chosen, rejected) # 计算策略模型和参考模型对chosen/rejected的log概率 # loss = -log(sigmoid(beta * ((logp_chosen - logp_ref_chosen) # - (logp_rejected - logp_ref_rejected))))5.3 偏好对齐中的"对齐税"问题
偏好对齐有个绕不开的现象叫对齐税(Alignment Tax):模型在偏好指标上变好了,但通用能力(比如知识问答、推理)反而下降了。原因是偏好数据往往集中在某几类任务上,模型过度优化这些任务,把其他能力挤掉了。
缓解对齐税的办法:一是在偏好数据里混入通用任务的偏好对,别只盯着安全性和格式;二是DPO的beta别设太小,给模型足够的约束;三是对齐后做一次"能力回补",用少量高质量SFT数据再微调一下,把掉下去的能力拉回来。这个回补步骤很多人会忽略,但实测很有效。
6. 评估:别让训练变成盲盒
6.1 自动评测与人工评测的分工
评估分两大类:自动评测快、便宜、可复现,但只能测有限维度;人工评测慢、贵,但能捕捉自动指标测不到的东西(比如回答的自然度、有用性)。
自动评测常用两类指标:一是困惑度(Perplexity),衡量模型对测试文本的预测能力,但困惑度低不代表回答好;二是任务指标,比如分类准确率、问答F1、代码通过率。对于生成任务,还可以用模型裁判(用强模型给弱模型的输出打分),但要注意裁判模型的偏见。
人工评测建议用成对比较:给评测员同一个问题的两个模型输出,让他们选更好的,或者选"差不多"。成对比较比绝对打分更稳定,因为人对绝对分数的标准不一致,但对"哪个更好"的判断相对一致。
6.2 构建有区分度的评测集
评测集的质量决定评估的价值。一个没区分度的评测集,所有模型都考90分,等于没测。构建评测集要注意:
- 难度分层。简单、中等、困难各占一定比例。全是简单题测不出差距,全是难题又容易让所有模型都挂零。
- 覆盖核心能力。知识、推理、指令遵循、安全性、格式控制,每个维度都要有题。
- 防止数据泄漏。评测集绝对不能出现在训练数据里。我见过项目因为评测集泄漏,指标虚高,上线后翻车。
- 定期更新。模型会"记住"固定的评测集,所以要定期换题,或者保留一个不公开的"暗测集"。
6.3 训练全程的评估节奏
评估不是训完才做,而是贯穿全程:
- 预训练/续训阶段:每隔一定步数跑通用能力评测,监测灾难性遗忘。
- SFT阶段:每个checkpoint都跑指令遵循和任务评测,选最佳checkpoint。
- DPO阶段:跑偏好胜率(用模型裁判或人工)加通用能力评测,监测对齐税。
- 上线前:做一轮完整的人工评测,覆盖真实用户场景。
我习惯建一个"评测看板",把各阶段的指标画成曲线,一眼就能看出哪一步在进步、哪一步在退步。这个看板在向团队汇报和定位问题时特别有用。
7. 几个反复踩过的坑和应对
7.1 loss正常但生成崩坏
这是最迷惑人的情况:训练loss一路下降,看起来一切正常,但模型生成的东西是乱码或者重复。原因通常是推理时的解码配置和训练不匹配,或者对话模板拼接错误。排查顺序:先检查模板,再检查tokenizer是否一致,最后检查解码参数(temperature、top_p、repetition_penalty)。我遇到过一次是tokenizer版本不一致,训练用了新版,推理用了旧版,直接导致输出崩坏。
7.2 显存不够的降级方案
显存不够是常态。降级顺序建议:先开梯度检查点(用时间换显存),再上混合精度(bf16),再减小批次配合梯度累积,再考虑QLoRA(4bit量化),最后才考虑换更小的模型。梯度检查点能省30%~50%显存,代价是训练慢20%左右,通常是最划算的第一步。
7.3 多卡训练的通信瓶颈
多卡训练时,如果卡间通信慢,GPU利用率会很低。用数据并行(DDP)时,确保用了高效的通信后端,并且批次大小别设太小(太小通信占比高)。如果卡多但带宽低,可以考虑梯度累积模拟大批次,减少通信频率。实测中,同样的卡数,通信配置优化后训练速度能差一倍。
从零训练一个大模型,技术链路长、坑多,但每一步都有章可循。数据决定上限,预训练打底子,SFT塑形,偏好对齐调优,评估兜底。真正拉开差距的,往往不是用了多新的算法,而是把每个环节的细节做扎实——数据洗干净、模板对齐、参数调对、评估跟上。我个人最大的体会是:别急着上大模型,先用小模型把全流程跑通,7B甚至1B的模型足够你验证整条链路,等流程顺了、坑踩明白了,再放大规模,成功率会高得多。