ARTICLE DETAIL

资讯详情

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

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

从零训练0.8B小语言模型:预训练到DPO全流程实战 本来没打算公开写这件事的。但 Xihe 这个项目从零开始前前后后做了快两个月踩出来的坑比我以前调大模型接口一年都多。我决定把它完整记录下来——不是因为它多成功而是因为整个链路里预训练、CPT、SFT、PEFT、蒸馏、DPO 这几步全走了一遍每一步都沉淀出了可以在其他项目里直接复用的细节和教训。如果你正在犹豫要不要自己训一个小语言模型或者已经开跑了但卡在某个环节这篇内容应该能帮你省下不少弯路。Xihe 的定位很简单一个参数不到十亿、专门为某个垂直业务场景服务的小语言模型最终参数量定在 0.8B 左右。这类模型不会去跟百亿千亿参数的大模型拼广度但在特定任务上它能做到低成本、快速迭代、可控部署这是很多团队选择自己训练的核心原因。接下来的内容完全按照实际训练顺序来写从数据准备讲到最终的 DPO 对齐中间穿插我实测下来的参数配置和避坑经验。1. 项目定位与整体训练路线为什么要从零训一个“小”模型做这个项目之前我前后对比了三条路直接用现成的大模型 API、基于开源大模型做微调、完全从零预训练一个小模型。前两条路都很成熟但分别卡在“数据出域”和“成本不可控”上。业务场景里的术语、写作风格、交互方式跟通用模型差异很大你可以在提示词里无限调参但模型底子不认这些微调开源模型则受限于底座本身的分布和许可协议想深挖的时候总觉得使不上力。从零训练 Xihe 的好处在于它的词表、预训练语料、继续预训练方向、最终对齐方式全部是自己定的每一个行为偏差都能追溯到具体环节不会出现“模型效果差但不知道谁拖的后腿”这种局面。整个训练链路我设计成六个阶段顺序有讲究不建议乱跳通用预训练Pretrain用清洗过的通用语料把模型基础语言能力拉起来这一步决定模型的“下限”。继续预训练CPTContinual Pretraining注入领域语料让模型在专业词汇和表达习惯上快速适应。监督微调SFT用指令与回答对让模型学会“听人话、说人话”从续写模式切换到对话模式。参数高效微调PEFT在资源受限的情况下用 LoRA 做定向能力增强不会破坏预训练学到的通用知识。模型蒸馏Distillation把大模型在垂直任务上的判断力压缩到 Xihe 上提升小模型的天花板。直接偏好优化DPO用偏好数据告诉模型“什么是更好的回答”替代复杂且不稳定的强化学习管线。为什么顺序不能乱因为每一步都是在为后一步做铺垫。比如跳过 CPT 直接 SFT模型对领域术语的理解是浮在表面的指令微调只会放大这种“半懂不懂”反过来如果先蒸馏再 SFT学生模型学到的大模型分布会被 SFT 指令模板覆盖掉等于白做。我最终把蒸馏放在 SFT 之后是为了让模型先具备对话形态再被大模型“带着走一段”合并起来效果最稳。算力这块0.8B 参数在单张 A100 80G 上做预训练我实际估算下来大概需要 1.5B token 级别的语料训练耗时三天左右如果只用消费级显卡一张 RTX 4090 24G 跑 0.3B 级别也是可行的只是序列长度和数据批次要缩水。小模型的优势在这里非常明显——你不需要上万张卡一台靠谱的机器就能跑通这也是我敢从零来一遍的根本原因。2. 预训练与继续预训练CPT先把“底子”打厚2.1 数据准备决定模型命运的第一道工序预训练阶段我用的数据主要由三部分构成通用网页语料、开源高质量语料、以及业务侧脱敏文档。比例上通用语料占 80%业务语料占 20%。通用语料的作用是保证语言表达的流畅性和常识覆盖业务语料则是提前让模型“眼熟”领域内的高频词和句式为后面的 CPT 减少压力。数据清洗我只做了三件事但每一件都踩过坑。第一是去除重复内容尤其大段重复的 boilerplate 文本我一开始没有做 n-gram 去重训练到一半发现 loss 下降特别慢检查数据时才发现大量重复片段把有效信息稀释了。第二是统一编码和做乱码过滤爬下来的文本里有大量控制字符和 emoji 残留会影响 BPE 切词效率。第三是敏感信息过滤这也是必须的一步邮箱、手机号、身份证这类信息一律清洗掉既合规也避免模型学会复述这些内容。词表我用 SentencePiece 训练了一个 BPE 词表大小定为 32K。对小模型来说词表并不是越大越好。词表大意味着 embedding 矩阵参数多而参数总量有限的情况下过大的词表会挤压 transformer 层的充分训练。我对比过 16K 和 64K 词表的收敛速度32K 在压缩率和训练速度之间最平衡。分词训练语料大约用了 5GB 的清洗后文本覆盖通用和业务领域确保业务缩写也能被正确切开。2.2 预训练超参数照着这套配置先跑起来预训练阶段我用的是一个 8 层 transformer decoderhidden size 102412 个注意力头参数约 0.8B。损失函数就是标准 next token prediction交叉熵。具体的超参数如下配置项数值说明参数量0.8B8层 x 1024 hidden x 12 heads词表大小32KSentencePiece BPE序列长度2048小模型也能吃长文本训练 token 数1.5B通用语料为主学习率3e-4warmup 2000 步cosine 衰减到 3e-5Batch size256 条样本动态批大小按 token 数对齐优化器AdamWbeta(0.9, 0.95)weight decay 0.1梯度裁剪1.0防止 loss 突然炸掉学习率的选择我吃过亏。一开始按大模型的习惯设了 1e-4结果收敛太慢1500 步后 loss 还在高位徘徊。后来参考已发表的小模型预训练经验把峰值提到 3e-4配合 warmup 才在同等步数内降到了接近理想值。小模型和小学习率的搭配并不总成立关键要看参数量和训练步数。参越小相对可以承担更大的学习率但超过 1e-3 又会不稳定这个区间要自己在小型验证集上多试。训练过程中我每隔 500 步记录一次训练 loss 和验证集 perplexity。验证集是单独切出来的 5000 条新闻和百科文本。这里有个很实用的判断标准训练 loss 持续下降但验证 perplexity 开始回升说明模型在过拟合训练分布马上要暂停检查数据是否重复过多或者学习率是否过低。我实际盯曲线时发现验证 loss 在 3000 步附近出现了一个小平台随后又继续下降这种平台在预训练里是正常的不用焦虑。2.3 CPT 继续预训练给模型“补专业课”CPT 的核心目标是用大量领域语料对模型做第二遍预训练让通用模型变成“懂行”的模型。这个阶段的训练方式跟预训练基本一致区别在于学习率必须显著降低。我用的是 1e-4 峰值训练 2 个 epoch 的领域语料。如果学习率太高模型会快速朝领域分布偏移但通用能力会明显退化一个典型表现是模型开始大量生成业务词汇但句子的逻辑连贯性变差。数据配比也要控制。纯领域语料训练容易让模型遗忘掉基础的开放式表达能力所以我的 CPT 数据里混合了 30% 的通用语料相当于每 3 条领域数据带 1 条通用数据。领域数据我按业务场景分了五类产品介绍、技术文档、客服问答、操作手册、以及脱敏后的真实对话记录。每类数据量尽量均衡避免模型最后只在某一类文本上表现好。CPT 阶段我遇到一个很典型的坑模型学会了一堆新词汇但在 SFT 阶段反而更爱“自说自话”接续式输出的时候疯狂堆叠领域术语。原因是 CPT 让模型把领域词汇的共现概率拉高了但没教会它在什么语义下用这些词。这个问题的解法不在 CPT而在 SFT 数据的质量——后面会详细说。总之CPT 阶段只负责“懂行”不负责“会说”。3. SFT 与 PEFT教会模型“好好说话”3.1 SFT 数据格式与配比数量不重要结构才重要SFT 阶段我的第一版数据只有 8000 条指令对训练完一测模型在业务问答上还挺好但一旦用户把问题换个问法它就答不上来了。后来我把模板多样化做到位效果才明显改善。SFT 数据我最终采用了 Alpaca 风格的结构每条样本包含 instruction、input 和 output 三部分instruction 是用户指令input 是可选的上下文output 是标准回答。在构造训练模板时我故意混入了多种开头形式“请回答”“帮我解释”“总结一下”“根据以下内容回答”等等让模型不要死记某一种指令格式。这里给出我最终使用的训练数据模板### 指令 {instruction} ### 输入 {input} ### 回答 {output}如果样本没有 input模板里就省略“### 输入”那一行。这样的好处是格式统一、便于解析同时少量变化又不会让模型过拟合到固定模板。SFT 数据的来源包括业务历史工单里的高质量回答、人工撰写的标准答案、以及大模型生成后经过人工校验的候选。比例上我坚持 60% 人工或人工改写的答案40% 大模型生成但人工评过分的答案。千万别为了凑量直接把大模型输出当标准答案灌进去模型很快会学着用正确的句式说错误的内容。SFT 训练超参数上我的建议是小模型别训太多 epoch2 到 3 个 epoch 足够。我第一轮训了 5 个 epochloss 压得很低但生成内容开始出现重复和空洞的口水话这就是典型的过度拟合指令模板。学习率我用的 2e-5加上一点点 warmup总共训了 20 步看验证效果。SFT 阶段验证集要单独构造不能跟训练集重叠我特意从业务问题里切了一部分出来按用户原始表述改写一遍当验证数据目的就是测试模型的泛化能力。3.2 LoRA 参数选择小模型为什么也需要 PEFT很多人觉得小模型参数本身就少没必要再用 LoRA 做参数高效微调。这是误解。PEFT 的意义不完全在于省显存更在于“定向修改、全局不伤”。直接用全量微调给 SFT 阶段再叠加一轮领域能力很容易破坏预训练和 CPT 学到的分布而 LoRA 只训练低秩矩阵冻结原模型权重改动范围可控随时可以摘掉或者桥接到其他任务上。我在 SFT 之后又做了一轮针对新业务场景的增强用的就是 LoRA。配置如下配置项数值说明r低秩秩16太小拟合不动太大容易把通用能力带偏alpha32通常设成 r 的两倍target_modulesq_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj默认全量改 attention 和 FFNlora_dropout0.05防止过拟合学习率3e-4LoRA 层收敛很快训练步数300 步配合早停机制LoRA 训练完我把 LoRA 权重合并回主模型测试。合并后发现推理速度几乎不变但针对新场景的指令遵循能力提升明显。如果你需要在同一个底座上服务多个业务方向强烈建议保留不同的 LoRA 分支而不是反复去全量微调同一个模型。分支之间互不干扰切换时只需要加载不同的 adapter 权重这也是 PEFT 在工程落地里最香的地方。4. 模型蒸馏实战把大模型的能力“压缩”进小模型4.1 为什么做蒸馏小模型的上限取决于导师和语料预训练加 SFT 做完Xihe 已经能在垂直任务上给出结构完整的回答但距大模型的判断力仍有差距。差距主要集中在这种地方面对同一个用户问题业务专家会先梳理关键点再分点作答小模型则更倾向于直接给结论漏掉前提条件和边界情况。这个差异靠堆更多预训练语料很难补因为知识边界不是文本共现能学出来的它更像一种“推理组织方式”。所以我引入模型蒸馏。核心思路很简单让一个更强的教师模型生成高置信度的结果或 logits学生模型去拟合教师的行为。对于小语言模型我实际测下来纯粹的 logits 蒸馏也叫白盒蒸馏在微型模型上收益有限因为教师和学生容量差太大学生很难逐 token 复现教师的概率分布。于是我的主方案是“黑盒蒸馏”用教师模型生成大量高质量的垂直领域问答对再拿去微调学生模型。这种方式不依赖教师 logits只需要教师模型 API 或者本地部署的模型输出。4.2 蒸馏数据构造与训练细节蒸馏数据的构造是整个阶段最耗时也最关键的地方。我的流程是先从业务数据中抽取 5000 个真实场景问题再用教师模型为每个问题生成 3 个不同角度的高质量回答接着让教师模型自己对这 3 个回答打分排序第一名作为标准答案第二名作为负样本候选。最后还要抽样 300 条交给人去复核一遍防止教师模型自评失真。这一套下来最终得到了约 4500 条高质量蒸馏数据。训练时我把蒸馏数据与 SFT 数据按 7:3 混合。为什么保留 SFT 数据因为如果纯用蒸馏数据模型容易模仿教师的风格但丢失部分原有的指令理解能力混合之后既学到教师的高质量表达又不至于丢掉自己的基础能力。蒸馏阶段的损失函数就是标准的交叉熵没有额外加 KL 散度项。有同事建议用 temperature2 的软标签但我实测在小规模垂直数据上软标签反而让学生模型更困惑最终选择老老实实用硬标签。蒸馏之后Xihe 生成内容的“骨架感”明显提升回答会先给出总体判断再展开分点说明。这是一个比较直观的进步业务团队在盲评时也能感知到。蒸馏还有一个隐形收益它让 SFT 阶段的模板固定化问题得到缓解。教师模型生成的表达方式更丰富学生模型被迫见过更多样式的句子结构而不是只盯着那套固定模板。5. DPO 对齐实战让模型懂得“挑好的回答”5.1 DPO 原理与偏好数据构建绕开强化学习的重活对齐阶段我选了 DPO而不是传统 RLHF。原因是小模型场景根本不需要那么复杂的 reward model PPO 管线小模型动作空间有限奖励模型容易过拟合PPO 调起来还极其痛苦。DPO 的思路非常直接——把偏好作为监督信号直接优化策略让模型在给定输入时提升对优秀回答的响应概率降低对劣质回答的响应概率而这一切只需要“chosen 和 rejected”成对的数据。偏好数据处理上我构建了 5000 对偏好样本。每一对都由三要素构成同一个 prompt两个不同的回答以及谁更优的标签。为了不过度引入教师模型偏好我的数据来源分三种人工标注 60%教师模型排序 30%线上反馈低分样本 10%。特别要强调最后一种来源——真实用户点踩的回答是最宝贵的 rejected 样本它代表业务场景里真正不被认可的表达远比模型自己生成的对子更贴合实际。5.2 DPO 训练参数与两个经典坑DPO 训练我最终选了 beta0.1 这个值这个参数控制对偏好差异的敏感度。beta 越大模型越倾向于拉开 chosen 和 rejected 的概率差设小了模型根本学不动设大了模型会牺牲通用能力去刻意迎合偏好数据输出变得“用力过猛”。我在 beta0.3 时跑过一轮模型生成内容的多样性明显下降回头把 beta 调回 0.1 才恢复。配置项数值说明beta0.1偏好差异敏感度学习率1e-6 到 5e-6对齐阶段学习率要极低Epoch1不要超过 2过拟合风险高Batch size32偏好对数量优化器AdamW与 SFT 一致第一个坑是我反复强调的DPO 训练必须在已完成 SFT 的模型上进行跳步会让模型连基本指令理解都还没建立就去对齐偏好结果训练出来的回答“很政治正确但不知所云”。第二个坑是训练时一定要盯 chosen 和 rejected 两条分支的 log probability。理想状态是 chosen 分支概率上升、rejected 分支概率下降。但有一次我观察到 chosen 概率和 rejected 概率在同步下降说明模型学会了“把两句都压低”而不是“拉大区别”——这是典型的奖励作弊我用这个信号及时停了训练回退到上一版 checkpoint 重新调 beta。DPO 带来的主观改变也很明显模型在回答中加入更多礼貌性表达和结构化总结不再像 SFT 阶段那样直接甩结论。对于 To B 客服类场景温和的语气比生硬但准确的信息更容易被用户接受。当然这也只是我的业务侧反馈如果你的场景是代码生成或搜索引擎DPO 的优化方向要根据真实偏好调整。6. 评估体系与避坑速查表别被“loss 掉得很欢”骗了6.1 从技术指标到业务验证怎么判断模型真的“变好了”单独看训练 loss 是会骗人的。预训练 loss 掉得漂亮只能说明模型记住了训练数据的分布但分布里有噪音、模板化内容、甚至错误知识这些都会一并学进去。我搭了一套三层评估体系第一层是通用能力回归集从开源 benchmark 里挑一个轻量版验证模型是否出现通用能力坍塌第二层是垂直领域测试集包含 2000 个业务问题分十个场景按“准确率、完整度、格式规范度”打分第三层是人工盲评让业务同事在完全不知道模型版本的情况下给回答打分。三层评估缺一不可。我举一个实际案例某次 SFT 调整后垂直测试集准确率涨了 8%但人工盲评的格式规范分反而掉了 15%。排查后发现模型过度模仿了训练模板里“首先、其次、最后”的套话结构回答虽完整但很啰嗦。这类问题只有通过人工评估才能暴露出来自动指标很难捕捉到这种“质感”层面的退化。6.2 训练过程里的五个高频坑与解决方案训练 loss 居高不下先查数据清洗是否做干净再确认词表是否覆盖语料中的特殊符号最后检查学习率是否过低。我自己曾因为数据里混入了大量 HTML 残留标签白白浪费了一天训练时间。loss 抖动剧烈多半是学习率过高或者 batch size 太小。小模型预训练 batch size 最好不要低于 64抖动严重的可以加梯度裁剪。SFT 后模型胡言乱语验证集和训练集重叠了。我第一版 SFT 验证集就是从训练集里切出来的验证时效果惊艳上线就被打脸重新构造验证集后问题立现。生成内容有头无尾这通常是解码参数问题尤其是 temperature 过高和 top-p 过大。我最终定了 temperature0.7、top_p0.9 作为默认生成参数。显存溢出小模型也不可掉以轻心。长序列训练时开启 gradient checkpointing 可以省下近一半显存还不行就把序列长度从 2048 降到 1024影响有限。6.3 从 0 到 1 的完整配置速查表最后放一张我在不同阶段反复回看的配置速查表你可以直接抄作业再按自己的数据规模微调阶段数据规模建议学习率训练轮数/步数关键监控指标通用预训练1B token 起3e-4 峰值几十万步train loss, val perplexityCPT200M token 起1e-4 峰值2 epochval perplexity, 领域词命中率SFT5000 条起2e-52-3 epoch验证集准确率, 回答格式LoRA依场景定3e-4300 步内新场景效果原场景不退化蒸馏5000 条起2e-52 epoch盲评得分格式规范分DPO1000 对起1e-6 至 5e-61 epochchosen/rejected 概率差与方向我个人在实际操作中的体会是整个链条里最值钱的部分不是哪个模型结构更先进也不是哪个损失函数更花哨而是数据配比和超参数的实验纪律。每一次修改都要搞一个单独的对照实验否则到后期你根本分不清是哪个环节带来的效果提升。Xihe 的后续迭代我已经开始做了方向是蒸馏数据的自动化扩充和更细粒度的偏好收集。如果你想训一个属于自己的小模型建议不要贪多求大先把一条垂直任务线做穿再去扩展其他能力。训练小模型最大的魅力就是每个坑都必须你自己踩过一遍才会真正理解它在整个流程里的位置这篇内容如果能让你少踩几个就已经很值了。
返回列表