ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零训练1.5B小语言模型:预训练到DPO全流程实战

从零训练1.5B小语言模型:预训练到DPO全流程实战 去年我把一个想法变成了现实从空白权重开始亲手训练一个叫曦和Xihe的1.5B小语言模型完整跑通了预训练、CPT领域继续预训练、SFT监督微调、PEFT参数高效微调、蒸馏和DPO直接偏好优化这一整套流程。说实话现在大模型教程铺天盖地但绝大多数都停在“调API”“微调开源模型”这一步。真正从零构建一个小语言模型并把对齐、蒸馏、偏好优化全部串起来能参考的完整内容并不多。这篇文章把我从数据准备到DPO对齐的每个关键节点、参数选择、踩坑经历都写出来希望对那些预算有限但想完整理解大模型训练链路的人有帮助——无论你手里是几张A100还是一张消费级显卡都能从中找到可执行的部分。1. Xihe项目的定位为什么非要“从零训练”一个小模型1.1 小模型的独特价值与我的真实目标先交代背景。Xihe是一个1.5B参数的中文小语言模型取名来自“曦和”的谐音寓意模型像晨曦一样温和、稳定地输出。项目启动时间是去年第四季度核心目标不是“造一个比GPT更强的模型”——这不现实——而是解决三个实际问题第一理解训练链路里每一个环节的“手感”。用别人的模型做微调很多细节被框架屏蔽了比如学习率对损失曲线的影响、数据配比对领域能力的塑造、偏好优化里beta的敏感程度。这些只有亲手从预训练跑一遍才能感知。第二得到一个完全可控、可复用的领域底座。通用模型再强在垂直场景里也需要大量改造。Xihe预训练时我已经规划好了CPT的方向先做通用中文底座再往指定领域继续训练这是一条远比“直接微调大模型”灵活的路。第三验证低成本技术组合的可行性。我做这套东西的预算并不宽裕GPU是临时租的存储、带宽都有天花板。所以从预训练开始就必须考虑显存效率、训练吞吐和可恢复性。PEFT、蒸馏这些技术不是锦上添花而是整个项目能不能跑完的前提。1.2 完整技术路线图六步走战略整个Xihe项目的训练管线可以分为六步每一步都有明确的输入、输出和验证方式阶段输入输出核心验证指标预训练清洗后的中文通用语料基础底座模型困惑度PPL、通用基准CPT基础底座 领域语料领域底座领域PPL、通用能力保持率SFT底座 指令数据指令跟随模型指令回复质量、格式正确率PEFT任意阶段模型 少量偏好数据低成本优化模型与全参数微调的差异对比蒸馏教师模型 无标注数据学生模型学生PPL、下游评测接近度DPOSFT/PEFT模型 偏好对偏好对齐模型人工对比胜率、回复稳定性这个顺序是有讲究的。预训练决定“下限”CPT塑造“领域感”SFT解决“会说话”PEFT解决“资源不够”蒸馏解决“尺寸与能力矛盾”DPO解决“说得对且招人喜欢”。缺了任何一环模型的可用性都会打折。很多朋友问我为什么不直接用开源的中文底座然后做SFT非要自己预训练我的回答是预训练成本确实高但它换来的是对底座的完全掌控。比如CPT阶段如果我需要把某一类行业文本重点加权自己训练的底座可以随时调整语料配比如果用固定底座就只能在外面裹一层适配层效果天花板是很明显的。当然如果你完全没有预训练算力条件跳到第2章看“替代方案”也完全可以——我会给出基于开源模型的平替路线。2. 预训练实操从随机权重到“能读能写”的中文底座2.1 语料采集与清洗预训练的隐形胜负手预训练模型的性能上限八成由语料决定而不是模型结构。我在Xihe项目里收集了约60GB的原始中文文本来源包括开源的中文维基、网络公开网页、图书语料以及一部分代码语料。这个体量对于1.5B模型来说属于“经济适用型”配置——要想训练出更强的通用能力语料至少要翻十倍但对应的算力账单也会让人肉疼。原始文本不能直接用清洗流程我走了四步# 第1步格式统一与去重 # 转UTF-8、去掉HTML标签、剔除空行 # 第2步规则过滤 # 长度100字符的段落直接丢弃 # 第3步MinHash去重 # 用datasketch做近似去重相似度0.85的文本只保留一条 # 第4步质量打分 # 用困惑度过滤器筛掉乱码和低质量口语帖清洗之后有效语料大约剩40GB换算成token在10B量级。一个关键经验是千万别省掉MinHash去重。我之前偷懒少跑了这一步结果模型在训练后期反复“背诵”重复句子验证集PPL迟迟不降白烧了两天卡。数据配比也需要动心思。中文通用文本70%代码文本15%英文技术文档10%其余5%留给数学和符号数据。代码语料虽然占比不高但对模型理解结构化逻辑、正确输出JSON格式非常有帮助这个比例在SFT阶段会体现价值。2.2 分词器训练与词表大小抉择从零训练的一个隐藏门槛是分词器。直接用现成的中文tokenizer看似省事但词表会带上别人的语料偏好对后续CPT领域扩展不利。我用tokenizers库重新训练了一个BPE分词器词表选了32K训练语料就是清洗后的40GB中文文本vocab_size再大收益不明显再小中文长文本的序列利用率会下降。这里有一个容易被忽略的坑分词器训练用的语料必须和预训练语料同分布。我第一次训练时混了大量英文语料导致中文长词被切得很碎每个样本的有效信息密度下降预训练loss整体比预期高0.2左右。后来重新用中文为主的分词器训练同样的模型结构PPL立刻就有了明显改善。2.3 模型结构设置与初始化1.5B的合理形态Xihe的模型结构参考了当前小模型的主流配置层数24层 隐藏维度1536 注意力头数16 中间层维度FFN4096 序列长度2048 参数量约1.5B训练配置上我选用了AdamW优化器betas(0.9, 0.95)权重衰减0.1最大学习率3e-4warmup占比1.5%之后按cosine曲线衰减到峰值的10%。Batch size累计到约0.5M tokens——对于1.5B模型这个batch规模可以让训练比较稳定不会因为batch太小而频繁震荡。初始化标准差用了0.02。这个参数看起来不起眼但对训练早期的稳定性影响很大。初始化标准差过大会导致残差流中信号随层数累积而发散过小又会让训练启动太慢。0.02是我在1B-2B级别模型上试出来比较稳的数值。2.4 训练监控与损失曲线判断在4张A100-40GB上用bf16混合精度训练每张卡batch size设为8序列长度2048梯度累积16步等效全局batch约0.5M tokens。整体跑下来约3天多完成10B token的预训练。怎么判断预训练跑得好不好我盯三个指标训练PPL下降到10以下说明模型已经学会了基本的语言规律验证集PPL和训练集PPL的差距不超过15%说明还没严重过拟合梯度范数保持在0.5-2.0之间如果梯度范数持续超过5说明学习率可能偏高。预训练结束后我在中文HellaSwag和一个基础阅读理解集上做了快速评估。HellaSwag准确率大约42%阅读理解rough约10——谈不上惊艳但作为1.5B的通用底座这个底子已经够用了。后续的CPT、SFT才是真正把潜力释放出来的阶段。提示如果你没有预训练条件直接从HuggingFace或ModelScope拉一个开源中文底座比如2B以下级别的模型来跑后面的流程整体路线完全一致只是CPT阶段需要多花时间做领域适配。这一章的价值在于理解“底座为什么是现在这样”而不是让你也被60GB语料绑架。3. CPT实战给通用底座注入领域基因3.1 什么是CPT为什么预训练之后还要继续训练CPTContinued Pre-Training持续预训练说白了就是“让一个已经会中文的模型再读一批特定领域的书”。预训练解决的是“把话说明白”CPT解决的是“把某个行业/领域的话说明白”。Xihe项目的场景是中文法律文本理解。通用预训练之后模型对法律条文、裁判文书的理解非常浅层很多专业术语只是“认识字”并不知道背后的逻辑关系。我整理了一份约3GB的法律领域语料包括法律法规、司法解释、裁判文书、法学教材公开内容清洗后剩约2.2GB。3.2 CPT的关键超参学习率与数据配比CPT和预训练最大的区别是模型已经具备通用能力你需要在“吸收新知识”和“不忘记旧能力”之间找平衡。我的CPT配置参数预训练CPT最大学习率3e-43e-5Warmup步数2000300Batch size0.5M tokens0.25M tokens迭代轮数1 epoch1 epoch领域语料占比0%40%法律通用语料混入100%60%通用学习率直接砍到预训练的十分之一这是为了防止新语料对权重产生过大的破坏性更新。数据配比上我按40%领域语料、60%通用语料混合。一开始我试过纯领域语料训练PPL确实降得飞快但模型在通用中文能力上的表现瞬间崩了——典型的灾难性遗忘。后来混入通用语料之后领域PPL和通用PPL才能同步优化。3.3 CPT的评估领域指标与通用指标双卡尺CPT跑完不能只看领域PPL。我在训练后做了双轨评估领域能力在法律文本的掩码语言模型PPL上从预训练基座的28.5降到了18.2明显提升通用能力保持中文HellaSwag准确率从42%微降到40.5%损失可以接受。如果通用能力下降超过5个百分点说明CPT学习率还应该再降低或者通用语料比例需要再加大。我见过不少项目在CPT阶段把模型“练废了”——领域PPL很好看但模型连基本的问答都做不好了后续SFT完全拉不回来。CPT还有一个实用技巧既然要做领域适配语料的顺序也很重要。我第一周只跑通用语料再加少量领域语料第二周逐渐提高领域语料比例第三周固定到40%配比。这个渐进式策略比一上来就40%领域语料要稳得多模型不会因为语料切换过于突兀而产生loss尖峰。4. SFT实战把底座模型调教成会聊天的助手4.1 指令数据构建格式决定一切预训练和CPT之后的Xihe还只是个“文本接龙机器”你问它问题它只会顺着上下文继续预测下一段文字。SFT的核心就是用“指令-回答”配对数据让模型学会这种互动模式。我使用的指令模板是阿里系开源模型的格式三段式结构|im_start|system 你是一个友好、专业的中文助手。|im_end| |im_start|user 请解释一下“正当防卫”与“防卫过当”的区别。|im_end| |im_start|assistant 正当防卫是指为了使国家、公共利益、本人或者他人的人身、财产权利免受正在进行的不法侵害而采取的制止不法侵害的行为。防卫过当则是指防卫行为明显超过必要限度造成重大损害的情形。两者的核心区别在于防卫行为是否明显超过必要限度。|im_end|这样一条算一个训练样本。数据量方面我整理了约20万条中文指令数据其中约5万条是公开的指令微调数据集15万条是基于法律场景自己构造的QA对。自己构造数据时我特别注意了多样性——同一个问题至少要有三种不同的提问方式避免模型只学会固定模板。4.2 全参数微调损失函数与超参数设置SFT阶段我选择了全参数微调而不是只调最后一层。模型只有1.5B全参数微调在单卡上就可以完成不需要太纠结资源问题。关键配置learning_rate: 2e-5 batch_size: 8 gradient_accumulation_steps: 8 max_seq_length: 2048 epochs: 3 optimizer: AdamW lr_scheduler: cosine warmup_ratio: 0.03注意SFT的学习率远低于预训练和CPT因为此时目标不是学习新知识而是调整输出风格和交互模式。训练3个epochs后训练loss平稳下降验证集loss在1.2左右。4.3 我踩过的SFT大坑loss低不代表效果对很多人看到SFT训练loss很低就觉得成功了我在Xihe项目上吃了这个亏。第一版SFT模型训练完成后验证loss漂亮得不行但推理时发现模型回答极其啰嗦一段话能重复三遍同样的意思。排查后发现是数据构造阶段出了问题我用来生成指令回答的脚本为了追求输出完整把很多回答都合并成了超长文本。模型学到了“答案要很长”的坏习惯。解决方法是给训练数据加入长度控制。我把助手回复统一截断或扩展到适当长度范围100-400字并且在验证阶段开始关注一条新指标回复平均长度和标准差。如果长度分布和训练数据分布严重不符说明模型对格式的拟合还不够。另一个坑是system提示词被“学坏了”。SFT数据里如果system prompt变化太多模型会试图在回复中模仿system prompt的语气导致“你是一名友好的助手”这类话被直接输出而不是当成指令。后来我把system prompt固定为三类且每类在训练数据中占比均衡这类问题才缓解。5. PEFT实战用LoRA把微调成本降低90%5.1 为什么有了SFT还要做PEFTSFT的效果很好但每次调整一个方向都要重新训一遍全量参数时间和显存开销都太大。Xihe项目里我需要在SFT基础上尝试不同风格的对齐和领域增强如果每次都跑全参数微调资源根本转不过来。PEFT就是为了解决这个“车轮战”问题。PEFT的核心思想是冻结预训练模型的大部分权重只训练少量的额外参数比如低秩矩阵。这听起来像个“偏方”但其实在大量实验里PEFT和全参数微调的效果差距非常小尤其在数据量不太大的情况下。5.2 LoRA核心参数解读r、alpha、target_modules我在Xihe上用的PEFT方案是LoRALow-Rank Adaptation。它的原理是给原始权重矩阵添加一个低秩分解的增量即两个小矩阵相乘训练时只更新这两个小矩阵。配置如下lora_r: 16 lora_alpha: 32 lora_dropout: 0.05 target_modules: [q_proj, k_proj, v_proj, o_proj] bias: noner秩决定增量矩阵的容量。r16在这个规模下已经够用调到32对效果提升不明显但会多占显存alpha缩放系数一般按alpha/r2来设置也就是alpha32配合r16。调大alpha相当于变相调高学习率target_modules指定作用在哪些层上。只调注意力层的Q/K/V/O放弃FFN层效果已经很好而且能节省约40%的显存。5.3 QLoRA与显存计算消费级显卡的救星如果你只有单张24GB显卡QLoRA是必选项。QLoRA在LoRA的基础上对底座模型做了4bit量化显存占用能降到极低——我实际测试下来1.5B模型用QLoRA训练时峰值显存大约只有8-10GB完全可以在消费级显卡上跑。用bitsandbytes做4bit量化的代码非常简洁from transformers import BitsAndBytesConfig bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_use_double_quantTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypebfloat16 )5.4 LoRA与全参数微调的效果对比我做了同数据、同轮次的对比实验方案验证损失指令格式正确率显存峰值全参数微调1.1896.2%22GBLoRA (r16)1.2295.1%12GBQLoRA (4bit)1.2593.8%8GB结论很明显LoRA和全参数微调的差距在可接受范围内但显存节省接近一半QLoRA稍微损失一点效果但换来了消费级显卡可以跑的现实。对大多数场景来说LoRA是性价比最高的选择。注意LoRA训练完的adapter权重只是附加的增量矩阵要和底座模型合并后再部署。用peft库的merge_and_unload()方法就可以完成这一步。如果不合并推理时也需要同时加载底座和adapter速度会有损耗。6. 蒸馏实战让更强的教师模型输出能力给学生6.1 为什么需要蒸馏以及怎么选教师模型Xihe是1.5B模型能力上限受制于参数量。如果让一个更大的模型比如7B或者更大的开源模型给Xihe“讲课”把大模型学到的知识压缩进小模型这就是知识蒸馏。通俗来说它解决的是“班级里有一个学霸教师但考试时只能派一个低年级学生上场”的矛盾。教师模型的选择是蒸馏效果的关键。我在项目里挑选了当时可用的Qwen2.5-7B-Instruct作为教师。选择理由有三官方对该模型开源了权重它的中文能力在7B这个档位足够好它对法律与领域文本也有一定基础和Xihe的场景匹配。6.2 Soft Label与温度参数T蒸馏有两种主要形式硬标签蒸馏和软标签蒸馏。硬标签蒸馏拿教师模型生成的文本直接作为训练目标本质上是SFT软标签蒸馏拿教师模型的预测概率分布来训练学生让学生学会“教师的不确定性”。我采用的是两者结合但重点是软标签蒸馏。关键参数是温度T损失 α * KL(教师输出概率 || 学生输出概率) (1-α) * CE(学生输出, 教师生成文本)当T1时就是正常的概率分布T1会让分布更平滑暴露出教师模型在低置信区间的判断这对小模型学习“隐藏知识”特别重要。我在实验中把T设为2.0α设为0.7。6.3 蒸馏数据准备与训练细节蒸馏数据集要覆盖教师模型擅长的领域同时不能太单一。我用20万条通用指令加上法律场景的10万条问询一并丢给教师模型生成回答总共得到约30万条“提问-教师回答-教师概率分布”三元组。训练时用上LoRA会非常高效。我对学生模型Xihe的SFT版本做LoRA蒸馏r16alpha32学习率降到1e-5batch size为16gradient accumulation为4跑了大约10个小时单卡A100。蒸馏后评测结果模型通用指令胜率对比SFT基线法律问答准确率PPL测试集SFT基线-58.3%1.32蒸馏后61.2%66.7%1.18蒸馏后模型在法律问答上提升非常明显而且整体回复的流畅度也提升了一截。背后的原理是学生模型不仅学到了教师“写了什么”还学到了教师“怎么犹豫、怎么权衡”——这些信息藏在概率分布的细微差别里是硬标签训练学不到的。6.4 蒸馏的一个常见误区我在刚开始做蒸馏时犯了个错误只把教师模型的输出文本当训练目标忽略了soft label。结果模型只是复制了教师的“表面表达”遇到分布外的问题就露馅。加入KL散度项之后模型学会了泛化到未见过的表达上这才是蒸馏真正的价值。7. DPO实战不用强化学习也能做偏好对齐7.1 DPO原理浅谈从RLHF到直接偏好优化传统的RLHF基于人类反馈的强化学习需要训练一个奖励模型再用PPO等强化学习算法去优化策略整个过程繁琐且容易不稳定。DPO绕开了这条链路直接用偏好数据优化策略——它把偏好对齐问题转化为“在隐含奖励约束下最大化似然”的分类问题。你不再需要一个独立的奖励模型来打分只需给模型一组“哪个回答更好”的数据对就能让模型学会倾向优质的回复。这对小团队来说太有用了。不需要写复杂的强化学习环境不需要调PPO的clip参数训练逻辑跟SFT几乎一样简单但效果却能显著改变模型的回复偏好。7.2 DPO训练数据构建chosen/rejected的收集策略DPO的数据结构是偏好对{ prompt: 请解释一下什么是合同中的“违约责任”, chosen: 违约责任是指合同当事人一方不履行合同义务或者履行合同义务不符合约定时依照法律规定或者合同约定应当承担的法律责任。违约责任的形式包括继续履行、采取补救措施、赔偿损失等。, rejected: 违约责任就是违反合同要承担责任具体什么责任要看合同怎么约定可能会有一些赔偿或者别的后果。 }chosen是更好的回答rejected是相对较差的回答。怎么获取这两类数据我的流程是先用多个不同版本的Xihe模型SFT版、蒸馏版、不同随机种子训练版针对同一批约5万个问题各生成一个回答然后做自动化筛选。筛选依据包括回答长度是否合适、是否有重复片段、是否偏离问题主题、是否包含有害词。筛选出来的“优秀回答”作为chosen“明显较差回答”作为rejected。如果你有精力做人工标注效果会更好但代价不菲。5万条数据人工标注即便每条只花30秒也需要几百个小时。自动化筛选在质量控制上能做到60-70分对偏好对齐来说已经够用。7.3 DPO训练参数与坑位预警beta的敏感度DPO训练参数比SFT更“娇贵”最关键的参数是beta。beta: 0.1 learning_rate: 5e-7 batch_size: 8 gradient_accumulation_steps: 8 epochs: 1 lr_scheduler: cosinebeta决定了模型偏离参考模型的力度。beta越大模型越不容易偏离原来的策略beta越小模型越激进地追求偏好数据中的chosen回答。我试过beta0.01结果训练到一半模型开始退化出现严重的重复与空洞回答beta0.5则几乎看不出对齐效果。0.1是一个平衡点。另外一个极其重要的细节DPO训练必须冻结参考模型。参考模型就是DPO训练前的模型添加adapter的版本它的作用是计算“策略改变幅度”的约束提供的对数概率会被恒定缓存下来。如果你像我第一次那样不小心把参考模型也放在训练模式下loss会乱跳效果也会明显劣化。运行代码的关键部分model_ref AutoModelForCausalLM.from_pretrained(...) model_ref.eval() # 参考模型必须冻结 for param in model_ref.parameters(): param.requires_grad FalseDPO训练结束后我人工盲测对比了300条指令回复DPO版本的回复被选中率比DPO前版本高出约12%。尤其在开放式问题上DPO后的回答更简洁、更愿意承认不确定而不是胡编乱造。这让Xihe在“人味”这一项上有了质变。8. 整套流程跑下来最值得记住的实战心得8.1 五个最贵的踩坑记录把这套流程完整跑下来我整理了五个最典型的翻车点写在这里希望你能绕开分词器与预训练语料不同分布导致有效信息密度下降预训练PPL整体抬升。解决方案是分词器训练语料严格复用预训练语料分布不要在“精简版”数据上凑合。CPT阶段学习率过高触发灾难性遗忘领域能力提升但通用能力崩溃。务必用预训练学习率的十分之一并混入通用语料稳定地基。SFT数据long-tail过长导致句式重复模型学会了啰嗦。构造数据时控制回复长度分布让模型学到的“风格”不是只有一种。LoRA与Base模型未合并就部署推理时又慢又占显存还容易暴露adapter权重丢失问题。线上部署前一定做merge_and_unload()。DPO beta设置过小导致策略崩塌输出空洞化、重复化。先从beta0.1起步观察几个batch的loss再来调。8.2 低预算路线没有服务器集群怎么复现如果你只有一张24GB或更小显存的显卡这套流程全部可以跑通只需要做三处调整第一跳过从零预训练直接选一个开源中文底座1B-2B级别从CPT开始第二所有SFT和DPO阶段全部用QLoRA4bit量化底座显存占用直降一半第三蒸馏过程的教师模型输出概率可以提前批量生成并缓存训练时只读取缓存省去教师模型常驻显存。加上这些调整一套完整体验下来可能在两周内完成。8.3 Xihe项目的后续规划Xihe目前已经达到了我最初设定的目标。后续我会继续做两件事一是把蒸馏和DPO的数据规模扩大探索更多样的偏好对能否带来更强的人性化表达能力二是把预训练阶段的语料配比再迭代一版尝试用更小的模型跑更多epoch观察收益曲线拐点。最后想说的是小语言模型的魅力在于它的每一个环节都在你的掌控之内。预训练让你认识数据的力量SFT让你理解格式的重要性PEFT教会你在资源受限时做聪明的妥协蒸馏让你看到“知识传递”不只是一句口号DPO则让模型第一次有了“性格”。这个过程很难但它让我从“会用模型的人”变成了“真正理解模型的人”。
返回列表