3. 中训练
继续预训练:受控领域适配、稳定性工程与系统兼容性
1 概览
你们团队拿到一个公开可用的基座模型。它能解释 Python、总结文档、在通用 benchmark 上表现不错。于是团队把它接到公司的内部代码库,目标是把它做成“代码助手”:理解私有 API、解释内部服务依赖、根据 runbook 生成修复建议。上线前的演示看起来很顺利;上线后的真实流量却暴露出完全不同的问题:模型会把内部 API 名字补全错、会把废弃配置当成现行约定、会在多文件上下文里失去仓库层级感。它不是不会写代码,而是不会“站在这个代码库的分布里思考”。
这时,prompting 往往只能修表面:你可以把 system prompt 写得更细、塞更多 few-shot,也可以上检索,把 README 和接口文档喂给模型。它们都有效,但都没有真正改变模型内部的先验。如果模型对仓库里的命名习惯、目录结构、调用模式和内部术语没有参数化表示,它就会持续把“看起来像代码”的模式误当成“这个仓库里的代码”。这就是中训练(mid-training / continued pretraining, CPT)存在的原因。
中训练不是重新训练一个模型,而是对已经得到的基座模型做受控的继续预训练:目标函数常常仍是下一个 token 预测,但训练分布、数据混合、学习率、恢复方式、回归门控和后续兼容性要求都变了。它让模型向目标领域移动,但不会自动保证移动过程稳定,更不会自动保证移动后仍然适合聊天、工具使用或安全对齐。
从工程角度看,CPT 的价值主要体现在四类场景:
- 模型需要参数化地掌握新领域知识,而不仅仅是在推理时临时检索;
- 领域文本的结构和语言习惯与通用互联网语料差异很大,例如法规、病历、论文、日志、代码仓库;
- 分词和上下文先验需要调整,例如高频术语被切得过碎、长文档结构学习不足;
- 你希望后续 SFT、偏好优化或 RL 站在一个更接近目标分布的起点上。
但一旦继续训练,风险也同时进入系统:灾难性遗忘、稳定性鸿沟、分布错配、分词器不一致、checkpoint 恢复方式不一致、后续对齐兼容性变差。 因此,中训练不是“多喂一点数据”这么简单,而是一种典型的系统工程任务:你要在专业化收益与通用能力保全之间做受控权衡。
本章的中心问题
一旦基座模型已经预训练完成,工程师该如何在不破坏既有能力的前提下,把它适配到新的领域、语言、上下文长度或任务分布?
贯穿案例:内部代码助手
本章会反复使用一个统一案例:一个通用代码模型要被适配到企业私有代码库。我们会用它串起“什么时候该做 CPT、如何配数据、为什么会掉通用能力、什么时候值得扩 tokenizer、以及 CPT 如何影响后续 SFT / DPO / RL”。
1.1 中训练在生命周期中的位置
本节先回答几个关键问题:
- 现代 LLM 的生命周期为什么不能只分成“训练”和“推理”两步?
- 中训练在整个模型生命周期中扮演什么角色?
- 为什么“先判断缺口类型,再选训练杠杆”是比“先上微调”更稳的工程顺序?
中训练是连接“通用预训练”和“任务对齐”的桥梁。 预训练负责让模型学会一个广义语言世界;中训练负责把它带到更接近目标分布的位置;后训练再把模型行为塑形成可部署的产品形态。把这三步混为一谈,会直接导致方法选错、预算浪费,甚至把该由 SFT 解决的行为问题误交给 CPT,反过来把该由 CPT 解决的知识问题误交给提示工程。
如果把整个系统抽象成“缺口诊断 → 选择最便宜但有效的杠杆”,可以写成一个非常工程化的目标:
\[ a^* = \arg\min_{a \in \mathcal{A}} C(a) \quad \text{s.t.} \quad Q(a; g) \ge \tau, \quad R(a) \le \rho \]
其中:
- \(\mathcal{A} = \{\text{Prompt}, \text{RAG}, \text{SFT}, \text{CPT}, \text{DPO}, \text{RL}, \text{Distill}\}\) 是可选干预集合;
- \(g\) 是“缺口向量”,例如知识缺口、行为缺口、搜索缺口、实时性缺口、成本缺口;
- \(C(a)\) 是实施成本;
- \(Q(a;g)\) 是该方法对当前缺口的预期修复效果;
- \(R(a)\) 是引入的回归或系统风险。
这个公式并不追求精确可解,它的作用是提醒你:选方法是一个受约束的系统优化问题。
从工程实践看,不同阶段改动的对象并不相同:
- 预训练主要塑造基础表示空间和通用语言先验;
- 中训练主要改变模型面对某类分布时的参数化知识、术语习惯和文档结构先验;
- 后训练主要塑造交互行为、偏好排序、安全边界、工具使用模式;
- 推理系统决定在固定模型下如何花预算换质量、换延迟或换吞吐。
这也意味着,一个成熟的工程回答通常不是“我要微调一下”,而是先定义缺口:
- 如果缺的是知识,例如模型不懂合同条款、不熟悉内部 API、不理解医学缩写,优先考虑 CPT 或外部记忆;
- 如果缺的是行为,例如不会按 JSON 输出、不会按客服语气回答、不会调用工具,优先考虑 SFT;
- 如果缺的是偏好或长程策略,例如多步推理质量不稳、回答优劣排序不稳定,再考虑 DPO 或 RL;
- 如果缺的是成本与延迟,就不该再加训练,而该做蒸馏、量化、服务优化。
把贯穿案例放进生命周期里看
对“内部代码助手”来说,预训练给了模型通用编程能力;中训练负责把它带到“这个仓库”的语言世界;SFT 再让它学会按团队规定输出补丁、解释变更、调用检索或代码搜索工具;后续偏好学习或 RL 才会去优化“哪种修复建议更可靠、哪种工具轨迹更省成本”。
1.2 干预选择:何时用 Prompt、RAG、SFT、CPT、RL
本节先回答几个关键问题:
- 什么是 CPT?它与预训练和 SFT 有何不同?
- CPT、SFT、提示工程和 RAG,分别适合解决什么问题?
- 什么时候 continued pretraining 是正确解,什么时候它反而是过度训练?
- 如果你有一个开源 7B 模型和 5 万份临床笔记,应该做 CPT、SFT,还是两者都做?
- 中训练能否用来引入新语言、新领域,甚至新模态?它的边界在哪里?
CPT 解决的是“参数化知识与分布先验不足”,不是“交互行为不对”。 它沿用预训练阶段的 next-token 目标,但把模型继续拉向新的数据分布。因此,CPT 最适合处理的是领域知识、术语密度、文档结构、长上下文习惯和部分词表问题;如果问题只是输出格式、角色语气、工具调用协议或安全拒答边界,CPT 通常不是第一选择。
1.2.1 CPT 与预训练、SFT 的本质区别
形式上看,预训练和 CPT 用的都是下一个 token 预测;区别不在损失函数,而在训练分布、学习率和目标范围。预训练是在极大、极杂的通用分布上获得基础能力;CPT 则是在一个更窄、更有偏向性的分布上做受控继续训练。
而 SFT 则不同。SFT 的核心不是让模型更懂某个语言世界,而是让模型在给定用户输入时学会一种行为映射:什么情况下直接回答、什么情况下调用工具、什么格式才算合格输出。SFT 的训练样本通常是示范对话或结构化轨迹,而不是大规模原始文档。
因此可以用一句话区分三者:
- 预训练:学会世界的大体统计结构;
- CPT:把“世界”改成更靠近目标领域的世界;
- SFT:在这个世界里学会按要求行动和表达。
1.2.2 一个更可执行的诊断框架
把“该选哪种杠杆”写成一个简化诊断器,会更接近真实工程流程。设:
- \(g_k\):知识缺口(knowledge gap)
- \(g_b\):行为缺口(behavior gap)
- \(g_f\):知识新鲜度缺口(freshness gap)
- \(g_t\):工具使用 / 轨迹缺口(tool / trajectory gap)
- \(g_r\):搜索与长程策略缺口(reasoning / search gap)
那么一个非常实用的白板判断是:
\[ \text{choose} = \begin{cases} \text{Prompt} & \text{if } g_k, g_b, g_r \text{ 都不高,只是触发不稳}\\ \text{RAG} & \text{if } g_f \text{ 高,知识必须外部更新}\\ \text{SFT} & \text{if } g_b \text{ 或 } g_t \text{ 高}\\ \text{CPT} & \text{if } g_k \text{ 高,且有大规模领域语料}\\ \text{RL} & \text{if } g_r \text{ 高,且任务可验证} \end{cases} \]
当然,真实系统往往是组合拳而不是单选题,但这个框架至少能防止一个常见错误:把所有问题都看成“再训一下就好”。
1.2.3 什么时候该用 CPT,什么时候不该
| 信号 | 判断 | 原因 |
|---|---|---|
| 模型在目标领域的 perplexity 明显高于通用语料 | 做 CPT | 模型尚未站在该领域分布上 |
| 高频术语、实体名、缩写和引用格式经常出错 | 做 CPT | 术语共现和文档结构先验不足 |
| 输入是法规、病历、论文、长报告、日志、代码仓库等结构化长文档 | 做 CPT | 长文档组织方式需要参数化学习 |
| 尝试过 prompting 或少量 SFT,模型“说话更像了”但事实和术语仍不稳 | 做 CPT | 表层行为可学,但知识先验仍缺 |
| 希望后续 SFT / RL 站在更接近任务分布的初始化上 | 做 CPT | 降低下游对齐的知识填补负担 |
| 只需要模型把已有能力更稳定地触发出来 | 不做 CPT | 用 Prompt / few-shot 即可 |
| 知识更新很快,参数化存储不如检索可维护 | 不做 CPT | 优先 RAG / 外部记忆 |
| 问题是输出格式、函数调用 schema、角色语气、拒答边界 | 不做 CPT | 这是 SFT 的工作 |
| 没有足够干净的领域文档,只有少量高质量示范 | 不做 CPT | 数据不足以支撑 CPT |
| 部署预算紧张,无法承担 CPT 算力、评估和再对齐成本 | 不做 CPT | CPT 是昂贵的高杠杆手术 |
经典判断题: 如果模型在法律文档上的困惑度很高,但通用性能不错,优先修的是 CPT。困惑度高说明模型没有站在法律文本分布上,术语共现、引文格式和长文档组织方式都不稳;这不是简单多写几个提示就能补上的。
1.2.4 一个临床笔记的工程案例
你有一个开源 7B 模型和 5 万份临床笔记,目标是做医疗问答助手。一个成熟的回答不该是“直接 SFT”或“直接 CPT”,而应当分层判断。
首先,这批数据的形态更像原始领域文档,而不是已经整理好的问答示范,因此它天然更适合做 CPT,而不是直接当 SFT 数据。其次,医疗领域高度依赖术语、缩写、实体和文档结构先验,模型即使会一般问答,也未必具备稳定的临床语言习惯。更稳妥的路径通常是:
- 先做数据治理:脱敏、去模板、去重、过滤明显错误样本;
- 先做 CPT:让模型获得临床术语和病历结构先验;
- 再做少量高质量 SFT:把模型塑形成问答、摘要、分诊建议等明确产品行为;
- 最后做安全与评估:医疗领域里,“似懂非懂”往往比直接拒答更危险。
所以这个问题的好答案通常是:两者都做,但顺序不是随意的,而是 CPT 打底,SFT 定形。
1.2.5 新语言与新模态的边界
CPT 也常被用来适配新语言或新模态,但边界必须说清楚。
- 新语言:如果 base model 已经有对应脚本或字节级分词能力,只是语料暴露不足,那么 CPT 很有效;
- 新词表 / 新脚本:可能需要 分词器扩展,甚至 embedding resize;
- 新模态:如果 base model 本身没有视觉、音频或其他模态的输入通路,单纯 CPT 并不能凭空获得新模态能力,通常还需要新增编码器、adapter 或跨模态对齐层。
这类边界意识很重要,因为它能区分“继续预训练可以解决的问题”和“需要改架构的问题”。
把贯穿案例放进决策树里看
对内部代码助手来说,如果问题只是“模型知道 API,但总是忘记用固定 patch schema 输出”,应先做 SFT;如果问题是“模型根本不熟悉仓库中的内部函数和目录结构”,才值得上 CPT;如果知识频繁变化,检索工程文档可能比参数化记忆更可维护。
2 CPT 到底改变了什么:目标不变,分布改变,先验重写
本节先回答几个关键问题:
- 为什么 continued pretraining 明明还在做 next-token prediction,模型行为却会发生显著变化?
- “目标不变,分布改变”到底改变了什么:知识、表示空间,还是行为接口?
- 为什么有些问题应该交给 CPT,有些问题却必须留给 SFT、DPO 或 RL?
- 从数学上看,CPT 与预训练到底相同在哪里,又不同在哪里?
对一个自回归模型来说,目标函数可以保持不变,但只要训练分布从通用网页切换到法律文本、病历、论文、日志或内部代码库,最优参数就会随之改变。模型学到的不只是新事实,更是“什么词更常见、什么结构更正常、什么续写更像这个领域”。
目标函数保持不变,但最优参数会变
把自回归语言建模写成统一形式,可以得到:
\[ \mathcal{L}_{AR}(\theta; D) = -\mathbb{E}_{x \sim D} \sum_{t=1}^{|x|} \log p_{\theta}(x_t \mid x_{<t}) \]
如果把某个训练分布 \(D\) 对应的最优参数记为:
\[ \theta^*(D) = \arg\min_{\theta}\mathcal{L}_{AR}(\theta; D) \]
那么从预训练到中训练,并不是把目标换掉,而是把 \(D\) 从更宽的通用分布 \(D_{web}\),换成更窄、更偏向目标领域的 \(D_{domain}\) 或混合分布 \(D_{mix}\)。只要分布改变,\(\theta^*(D)\) 就会改变。这就是为什么“还是 next-token loss”,模型却会变得越来越像律师、医生、审计员或内部代码库维护者。
CPT 常被一句话概括为“继续用下一个 token 训练”,但这句话很容易掩盖真正重要的工程事实:目标函数不变,并不意味着系统行为不变。 当训练分布从通用网页、论坛、百科,切换到法规、病历、论文或内部代码库时,模型会重估大量条件概率:
- 术语与术语之间的共现关系;
- 某类实体出现的先验频率;
- 文档的局部结构和长程组织方式;
- 哪些 token 序列在新世界里“更自然”。
可以把数据混合写成一个简单的形式:
\[ D_{mix} = \lambda D_{domain} + (1 - \lambda) D_{replay} \]
而 CPT 的目标仍然是:
\[ \mathcal{L}_{CPT}(\theta) = - \mathbb{E}_{x \sim D_{mix}} \sum_{t=1}^{|x|} \log p_\theta(x_t \mid x_{<t}) \]
这里真正决定模型被拉向哪里的,不是公式长什么样,而是 \(D_{mix}\) 里每一类数据各占多少。
如果你想把“领域收益”和“通用保留”显式写成一个双目标问题,也可以写成:
\[ \max_\theta \; \Delta M_{domain}(\theta) \quad \text{s.t.} \quad \Delta M_{general}(\theta) \ge -\tau_g, \qquad \Delta M_{safety}(\theta) \ge -\tau_s \]
这就是中训练在工程上的真实约束:你不是单纯最大化领域能力,而是在一组回归边界内最大化领域增益。
一个直观例子:同样是“续写”,世界已经变了
设两个上文分别来自两个不同分布:
- 通用网页:
The service supports multiple environments, including ... - 企业内部仓库:
The billing worker writes retries to /svc/retry_queue and calls ...
在前一个分布里,模型更可能接出“常见教程式解释”;在后一个分布里,模型需要学会内部路径、私有 API、团队约定和仓库层级。两者都叫“续写”,但条件概率表已经不是同一张表。
同样的现象也发生在法律和医疗场景里:
- 合同文本里的 token 更偏向条款、例外、引文和长程交叉引用;
- 病历文本里的 token 更偏向缩写、时间序列、实验室指标和临床风格;
- 代码库文本里的 token 更偏向路径、配置键、函数族和仓库内部共现模式。
因此,中训练不是“再背一点知识”,而是把模型的条件概率地形重新雕刻成更接近目标领域的地形。
CPT、SFT、DPO / RL 分别在改什么
把几个常见训练阶段并排比较,会更清楚:
| 方法 | 主要优化目标 | 主要改变什么 | 典型数据形态 | 典型问题 |
|---|---|---|---|---|
| 预训练 / CPT | 自回归 next-token prediction | 知识先验、术语共现、表示空间、长文档习惯 | 原始文档、代码、长文本 | “模型根本不懂这个语言世界” |
| SFT | 监督式行为映射 | 输出格式、角色语气、工具模板、对话接口 | 指令对话、工具轨迹、示范答案 | “模型知道,但不会按要求做” |
| DPO / ORPO | 相对偏好目标 | 回答质量倾向、偏好排序 | chosen / rejected 偏好对 | “答案都能写,但优劣不稳” |
| RL | 期望奖励最大化 | 搜索、探索、长程策略 | rollout + reward / verifier | “任务需要试错、规划和可验证反馈” |
- CPT 改的是模型相信什么世界更常见;
- SFT 改的是模型在这个世界里该怎么回答用户;
- DPO / RL 改的是模型在多个可能行为里更偏向哪一种。
为什么这件事在真实系统里重要
当一个团队说“模型会答错内部 API,但 JSON schema 也不稳”,这其实是两个层面的问题:
- 它不认识这个仓库的分布,这是 CPT 或外部记忆要解决的事;
- 它不会按产品约束输出,这是 SFT 或推理约束要解决的事。
如果把这两个问题混成一个问题,最常见的结果就是:用错误的训练阶段去修错误的缺口,最后既没有学会新领域,也把已有行为底盘练坏。
3 数据工程:领域语料、mixture、general replay、packing
本节先回答几个关键问题:
- 什么样的数据真正适合继续预训练:原始文档、QA 对、工具轨迹,还是网页抓取混合物?
- 为什么 general replay 不是一个小技巧,而是中训练最关键的稳定性杠杆之一?
- 为什么 mixture 不是“配料表”,而是一个直接控制领域收益与遗忘风险的旋钮?
- packing 明明是在优化吞吐量,为什么却会改变梯度看到的数据拓扑?
大多数 CPT 的成败,首先是一个数据工程问题。
loss 只是一个表面接口;真正决定模型会被拉向哪里的,是你给它看的语料分布、子域权重、通用回放比例、文档边界策略,以及这些东西随训练进程如何变化。
3.1 领域语料长什么样
CPT 的最佳数据通常不是精心写好的对话答案,而是领域原始文档:法规、判例、合同、病历、论文、规范、代码库文件、日志、注释、设计文档。原因很直接:CPT 想学习的是这个领域的语言世界本身,而不是用户—助手之间的行为映射。
这类数据在工程上有三个价值:
- 它保留了自然分布中的术语与共现关系;
- 它保留了真实的文档结构,例如章节层级、引文、表格上下文、代码文件边界;
- 它更便于大规模收集和持续扩充。
当然,这也意味着数据清洗更重要。网页抓取残留、模板页脚、重复样本、导航条、OCR 错误、拼接失败都会直接进入参数更新。很多团队以为自己在做“领域适配”,实际上是在教模型记住模板噪声。
3.2 通用数据与领域数据的混合比例
把混合分布写成:
\[ D_{mix} = \sum_{i=1}^{K}\alpha_i D_i, \qquad \sum_{i=1}^{K}\alpha_i = 1,\quad \alpha_i \ge 0 \]
其中至少有一个 \(D_i\) 是目标领域数据,另一个常见分量是 \(D_{replay}\)。于是,调 mixture 并不是“调味”,而是在改变模型下一次参数更新看到的世界组成。这也是为什么很多团队会把“80/20”写进经验配方:它不是神圣数字,但它提醒你 replay 不是事后补丁,而是训练分布本身的一部分。
perplexity 常被用来判断模型是否真正站在某个分布上。给定领域语料 \(x = (x_1, \ldots, x_T)\),其平均负对数似然为:
\[ \ell(x; \theta) = -\frac{1}{T} \sum_{t=1}^{T} \log p_\theta(x_t \mid x_{<t}) \]
对应的困惑度为:
\[ \mathrm{PPL}(x; \theta) = \exp\big(\ell(x; \theta)\big) \]
如果模型在目标领域上的 PPL 明显高于通用语料,同时实体、术语和结构错误频发,这通常是一个更偏向 CPT 的信号,而不是单纯的 prompting 或 SFT 问题。
所谓 general replay,就是在喂入新领域语料的同时,保留一部分原本更通用、更多样的训练数据。它的作用并不仅仅是“别忘了老知识”,而是同时承担三件事:
- 降低灾难性遗忘,避免模型过快脱离原来的通用表示;
- 缓冲优化冲击,让参数更新不要完全被窄分布主导;
- 维持后续对齐基础,保留通用问答、常识推理和对话行为的底盘。
一个常见的起始配方是 80% 领域数据 / 20% 通用回放,但它不是法则,只是工程上一个保守起点。真正该怎么调,要看两个方向的信号:
- 如果领域指标涨得慢,可以逐步提高领域占比;
- 如果通用评估掉得快,应当提高 replay、降低学习率、改善数据质量,必要时减小训练步数。
| 策略 | 做法 | 优点 | 风险 |
|---|---|---|---|
| 固定混合 | 全程固定 80/20、90/10 等配方 | 简单、稳定、易复现 | 可能不是最优点 |
| 课程学习 | 前期 replay 更高,后期逐步增加领域占比 | 前期更稳,后期更专 | 调参更复杂,收敛更慢 |
| 子域自适应采样 | 对 loss 高的子域临时加权 | 能补齐最薄弱子域 | 容易让整体分布抖动 |
| 回归门控驱动调整 | 评估退化时自动上调 replay 或降 LR | 更工程化,适合生产 | 需要持续评估和自动化基础设施 |
对多数团队来说,比较稳的顺序是:先从固定混合起步,再根据回归门控决定是否需要课程学习或自适应权重。
3.3 Packing:吞吐优化与文档边界风险
CPT 常常处理海量长短不一的文档。如果不做任何拼接,短文档会带来大量 padding,严重浪费 GPU。于是工程上常见做法是把多个文档拼到一个固定上下文窗口里,这就是 document packing。它的数学形式和工程含义,我们会在后面的“训练拓扑与系统实现”一节详细展开;这里先记住一句话:
Packing 提高的是吞吐量,但如果文档边界处理不当,它也会把不存在的跨文档模式写进参数里。
把贯穿案例放进数据分布里看
对内部代码助手来说,最有价值的 CPT 数据不是“如何回答代码问题”的问答对,而是仓库里的真实语言世界:源文件、接口文档、变更说明、runbook、注释、配置模板。只有这样,模型才会学到“这个代码库的条件概率长什么样”。
4 评估,门控与停止准则
本节先回答几个关键问题:
- 一次中训练 run 到底该看哪些指标,才算真正“在变好”?
- regression gates 应该怎样设计,才能在模型开始漂移时及时刹车?
- checkpoint 选择应该找最后一个点、最高领域分,还是 Pareto 最优点?
- 什么情况下应该继续跑,什么情况下应该果断停止?
中训练不是“训练完再评估”,而是“边训练、边评估、边决定是否继续投入 token”。
一个 run 是否成功,不是由单一领域指标决定,而是由一组带约束的多目标判断共同决定:领域收益有没有上升,通用能力有没有跌穿底线,行为和安全有没有出现不可接受的回归。
4.1 评估集的构成
中训练最常见的评估错误,是把领域 benchmark 当作唯一信号。更稳的做法是从一开始就把 eval suite 分成四类:
\[ \mathcal{E} = \{\mathcal{E}_{domain},\; \mathcal{E}_{general},\; \mathcal{E}_{behavior},\; \mathcal{E}_{safety}\} \]
- \(\mathcal{E}_{domain}\):法律、医疗、内部代码、长文档等目标任务;
- \(\mathcal{E}_{general}\):常识问答、通用推理、基础写作;
- \(\mathcal{E}_{behavior}\):结构化输出、工具模板、对话可用性;
- \(\mathcal{E}_{safety}\):拒答、敏感边界、极端输入稳定性。
然后给 stop criteria 一个明确形式。一个很实用的门控方式是:
\[ \text{stop if} \quad \Delta M_{general} < -\tau_g \;\; \text{or} \;\; \Delta M_{safety} < -\tau_s \;\; \text{or} \;\; \Delta M_{domain} < \epsilon_d \; \text{for } N \text{ consecutive evals} \]
也就是说,“不再值得继续烧 token” 和 “已经不允许继续回归” 应当是两个独立的停止条件。
4.2 checkpoint 选择:找 Pareto 前沿,而不是找最后一个点
如果把一次 run 在第 \(t\) 次评估时的领域、通用、安全指标记为 \((M_d(t), M_g(t), M_s(t))\),那么一个更工程化的做法是先定义 Pareto 前沿:
\[ \mathcal{P} = \left\{ t \;\middle|\; \nexists s,\; M_d(s)\ge M_d(t),\; M_g(s)\ge M_g(t),\; M_s(s)\ge M_s(t) \text{ 且至少一项严格更好} \right\} \]
直觉上,\(\mathcal{P}\) 里的 checkpoint 都是不被其他点“全面支配”的候选。然后你再在这条前沿上选择最适合产品约束的点,而不是机械地取:
- 最后一个 checkpoint;
- 领域分数最高的 checkpoint;
- 或者训练 loss 最低的 checkpoint。
对内部代码助手来说,这尤其重要:一个仓库级 pass@k 最高的点,可能恰好也是通用问答和拒答行为最差的点。把“最高分”错当成“最佳 checkpoint”,是中训练里最贵的误判之一。
4.3 门控:让评估进入训练闭环
regression gates 指的不是训练后做一次总评,而是把一组小型回归集变成训练中的持续监控器。一个实用的门控集至少应覆盖四类信号:
- 通用能力:如常识问答、通用推理、基础写作;
- 领域能力:你真正想提升的法律、医疗、代码或长文档任务;
- 行为能力:指令跟随、结构化输出、工具模板是否退化;
- 安全边界:拒答、敏感内容边界、极端输入稳定性。
如果要把 gate 写成一个“可以排序 checkpoint 的分数”,一个简单而实用的形式是:
\[ J(t) = w_d\,\widetilde{\Delta M_d}(t) - w_g\,\widetilde{\mathrm{Reg}_{general}}(t) - w_b\,\widetilde{\mathrm{Reg}_{behavior}}(t) - w_s\,\widetilde{\mathrm{Reg}_{safety}}(t) \]
这里的 \(\widetilde{\cdot}\) 表示归一化后的指标。它不是为了“把一切变成一个神奇数字”,而是为了让 checkpoint 选择从“拍脑袋看几张表”变成一个可审计的过程。
4.4 停止准则
你做了 20B token 的 CPT,MMLU 掉了 5 个点;继续跑到 50B token 后,分数又恢复了一部分。这样的 U 形曲线通常可以这样解释:
- 前期模型快速吸收新分布,通用表示先被挤压;
- 随着 replay 持续注入、学习率下降、优化逐步平稳,模型重新找到一部分平衡;
- 但“部分恢复”并不代表一定会恢复到起点,更不代表这是最优 checkpoint。
所以停止标准不应是“既然已经回了一些,不如再等等看”,而应是事先定义:
- 最低可接受通用分数;
- 最小领域收益阈值;
- 连续 \(N\) 次评估无改善则停止;
- 单位 token 收益是否还值得继续投入。
把 stop policy 写成显式规则,会比“再等等看”稳得多。一个典型形式是:
\[ \text{stop if} \quad \Delta M_{general} < -\tau_g \;\;\text{or}\;\; \Delta M_{safety} < -\tau_s \;\;\text{or}\;\; \Delta M_{domain} < \epsilon_d \text{ for } N \text{ consecutive evals} \]
其中:
- \(\tau_g\):通用能力最大可接受退化;
- \(\tau_s\):安全能力最大可接受退化;
- \(\epsilon_d\):最小领域收益阈值;
- \(N\):连续多少次评估无改善就停止。
这套写法的价值不在于“把训练形式化得很漂亮”,而在于把资源分配从直觉问题变成显式的运营策略。
现在你知道怎么判断训练是否健康,看什么指标,怎么选 checkpoint,什么叫该停。接下来我们讲稳定性时,明确“训练坏了”怎么定义。
5 稳定性工程:学习率、恢复方式、梯度控制与回滚
本节先回答几个关键问题:
- 什么是 CPT 中的稳定性鸿沟(stability gap)?为什么它几乎是一个常见现象?
- 开始 CPT 后通用基准突然下跌,应该按什么顺序排查?
- 学习率、数据分布和梯度范数在稳定性里分别扮演什么角色?
- 什么是 regression gates,为什么它比“训练完再看”更重要?
- 遇到 U 形曲线时,应该如何设置停止标准,而不是盲目继续烧算力?
CPT 的早期退化并不罕见,但必须被主动管理。 稳定性问题之所以危险,不是因为模型一定会掉分,而是因为很多 run 的退化在前期看起来“只是暂时的”,团队于是继续训练,最后把原本可回滚的问题烧成了不可逆的分布漂移。
5.1 稳定性鸿沟到底是什么
所谓 stability gap,指的是模型一开始进入新分布训练后,领域 loss 下降很快,但通用能力先明显下滑。之后它可能部分恢复,也可能不恢复;真正的挑战在于,你不能靠希望来区分这两种情况。
可以把它写成两个随训练步数 \(t\) 变化的指标:
- 领域指标:\(M_d(t)\)
- 通用指标:\(M_g(t)\)
那么“稳定性鸿沟”最简单的形式就是:
\[ \Delta_{stab}(t) = M_g(t) - M_g(0) \]
在很多真实 run 中,\(M_d(t)\) 单调上升,而 \(\Delta_{stab}(t)\) 在前期显著为负,后期才可能部分回升。这就是工程里常见的 U 形恢复轨迹。
它发生的原因,通常是多个因素叠加:
- 学习率不匹配:对一个已收敛的大模型来说,继续训练的可用 LR 往往比初始预训练更小;
- 分布切换过猛:领域数据太窄、变化太剧烈,模型被快速拽向一个局部子空间;
- replay 不足:旧分布没有足够权重,无法对冲新分布梯度;
- 梯度噪声上升:脏数据、重复模板、少量极端 batch、错误拼接都会放大不稳定;
- 长上下文与 packing 的副作用:更长的序列、错误的边界处理、序列并行配置问题都会让优化更脆。
5.2 学习率与 warm-up:为什么 CPT 比预训练更怕“手太重”
在中训练里,学习率往往是第一优先级的稳定性旋钮。原因并不神秘:CPT 不是从随机初始化开始学,而是在一个已经形成结构的参数空间里继续移动。移动得太快,旧表示会被粗暴改写;移动得太慢,领域适配又不充分。
一个常见但不严谨的做法,是直接沿用预训练阶段的峰值学习率或衰减策略。更稳妥的做法通常是:
- 使用更低的峰值 LR;
- 给继续训练单独设计 warm-up;
- 在早期密集评估,确认没有出现大幅回归;
- 必要时缩短单次训练窗口,多做 checkpoint 比较,而不是一口气跑完。
5.3 一个可执行的调试顺序
如果开始 CPT 后通用基准急剧下降,一个工程上可执行的排查顺序通常是:
- 先看学习率与 warm-up:这是最常见的罪魁;
- 再看 replay 比例:领域占比是否过激;
- 检查数据质量:去重、去模板、去错分样本;
- 检查 packing 与边界处理:是否引入了跨文档污染;
- 查看梯度范数与 loss 曲线:判断是否有异常 batch 或优化器不稳;
- 必要时加参考正则:例如与基座 logits 的 KL 约束,限制表示漂移;
- 回滚并缩小实验面:在更小预算上复现问题,别在全量 run 上继续赌博。
如果要把“参考正则”写得更具体一点,一种常见形式是:
\[ \mathcal{L}_{total} = \mathcal{L}_{CPT} + \beta\, \mathbb{E}_{x \sim D_{replay}}\big[\mathrm{KL}(p_{\theta_0}(\cdot \mid x)\,\|\, p_{\theta}(\cdot \mid x))\big] \]
其中 \(\theta_0\) 是基座模型参数,\(\theta\) 是当前 CPT 参数。这个项并不是 CPT 的默认组成部分,但在高风险场景里,它能作为一种“别离开太远”的软约束。
5.4 梯度控制、异常 batch 与训练中回滚
除了学习率和 mixture,另一个低估很严重的稳定性旋钮是梯度控制。最常见的做法包括:
- gradient clipping:限制异常 batch 的梯度爆发;
- anomaly detection:在 loss、梯度范数、长度分布上设置异常阈值;
- rollback:一旦 gate 被击穿,回滚到最近稳定 checkpoint。
梯度裁剪最常见的形式是:
\[ g_t \leftarrow g_t \cdot \min\left(1,\frac{c}{\|g_t\|_2}\right) \]
其中 \(c\) 是设定的最大梯度范数。如果再加一个简单的异常规则,例如:
\[ \text{rollback if } \|g_t\|_2 > \mu_g + k\sigma_g \]
你就得到了一条很朴素但常常很有用的工程守则:不要和异常 batch 赌运气。
5.5 一个带回放与门控的 CPT 训练骨架
# 伪代码:带 replay、packing、KL 参考约束与 regression gate 的 CPT 训练循环
for step in range(T):
batch = sample(D_domain) if rand() < p_domain else sample(D_replay)
x = pack_sequences(batch, seq_len=L, add_eos=True)
logits = model(x[:, :-1])
loss_nll = cross_entropy(logits, x[:, 1:])
if use_ref_kl:
with torch.no_grad():
ref_logits = ref_model(x[:, :-1])
loss_kl = kl_divergence(ref_logits, logits)
loss = loss_nll + beta * loss_kl
else:
loss = loss_nll
loss.backward()
clip_grad_norm_(model.parameters(), max_norm=grad_clip)
optimizer.step()
optimizer.zero_grad()
if step % eval_every == 0:
metrics = run_regression_suite()
if gate_triggered(metrics):
rollback_or_tune(lr="down", replay="up", data="clean")把贯穿案例放进稳定性里看
内部代码助手常见的失败不是“loss 不降”,而是“仓库任务上升、通用对话和安全行为下降”。如果团队只看代码任务 pass@k,而不把通用问答、结构化输出和拒答放进 regression gates,就会把一个原本可回滚的 run,烧成一个只会“说仓库黑话”的模型。
稳定性工程的目标,并不是保证“指标永远不掉”,而是保证一旦开始掉,你知道该看哪里、该调什么、该不该继续投入算力。
6 分词器扩展:高风险结构性改动
本节先回答几个关键问题:
- 什么时候应该在 CPT 期间扩展 tokenizer,而不是继续沿用原有词表?
- 哪些领域最容易受到 token 碎片化影响:科学记数法、代码 token、多语言字符,还是法律引文?
- 新增 token 的 embedding 应该怎么初始化,才能把风险降到最低?
- 什么是“训练不足的 token”,它为什么会在扩词后频繁出现?
- 分词器一旦扩展,训练、评估和部署链路为什么必须一起升级?
分词器扩展是一种高收益、高风险的“checkpoint 手术”,而不是常规动作。它可以显著降低领域术语的碎片化,节省上下文预算,改善建模效率;但同时也会改动 embedding 矩阵、序列长度分布、packing 效率、日志解析和服务链路。如果没有足够强的收益信号,最稳的默认做法通常仍是“不扩词表”。
6.1 什么时候值得扩展 tokenizer
一个可操作的判断方式,是统计目标领域的 fragmentation rate(碎片率)。对某个术语 \(u\),令原 分词器产生的子词序列长度为 \(k(u)\),则可以定义:
\[ r(u) = k(u) \]
如果对一个带权术语集 \(\mathcal{V}_{domain}\) 按出现频率 \(f(u)\) 取期望,则平均碎片率可写成:
\[ \bar r = \frac{\sum_{u \in \mathcal{V}_{domain}} f(u)\, r(u)}{\sum_{u \in \mathcal{V}_{domain}} f(u)} \]
当大量高频、高价值术语的 \(r(u)\) 长期偏高,例如:
- 生物医学中的药名、蛋白名、试验编号;
- 法律领域中的条款编号、引文格式;
- 代码中的 API 名、路径、配置键;
- 科学文献中的公式符号、单位写法、科学记数法;
- 多语言场景中的新脚本、罕见字符组合;
模型会同时承担两种成本:
- 上下文成本:本来一个概念可以占 1 个 token,现在占了 4 到 8 个;
- 学习成本:模型必须跨多个碎片重建一个稳定概念,训练和解码都更困难。
只有当这种碎片化足够频繁、足够贵,并且真实影响质量时,扩词才值得进入路线图。
6.2 不扩词表时你还能做什么
分词器扩展不是唯一答案。在很多场景下,更便宜的替代手段已经足够:
- 继续做领域 CPT,让模型学会在旧分词下也能稳定理解术语;
- 在推理系统中通过检索和模板减少复杂术语的自由生成;
- 在服务端做规范化,例如把常见别名映射成统一表达。
因此,分词器扩展应当被看作“当碎片化已成为明显瓶颈时的工程升级”,而不是美化词表的常规动作。
6.3 扩词是一台手术,不是一行配置
假设旧词表大小为 \(V\),embedding 维度为 \(d\),旧 embedding 矩阵为:
\[ E_{old} \in \mathbb{R}^{V \times d} \]
现在新增 \(k\) 个 token,则新的 embedding 矩阵变成:
\[ E_{new} \in \mathbb{R}^{(V+k) \times d} \]
如果模型使用 tied embeddings,那么输出头 \(W_{lm}\) 也需要同步扩容。对新 token \(u_{new}\),一种更稳的初始化方式是用它原有子词拆分对应向量的均值:
\[ E_{new}(u_{new}) = \frac{1}{m}\sum_{j=1}^{m} E_{old}(s_j) \]
其中 \((s_1, \ldots, s_m)\) 是旧 分词器下对该术语的子词分解。和纯随机初始化相比,这种“折叠式初始化”更像把旧表示压缩成一个新位置,通常更平滑。
6.4 “训练不足的 token”问题
扩词后最常见的失效模式,就是undertrained tokens。它的本质是:虽然新 token 已经进入词表,但相关样本在训练早期出现频率仍然不足,导致新增 embedding 行没有获得足够梯度更新,于是表现为:
- 生成时不稳定;
- 输出时偶尔像随机噪声;
- 在真正的领域上下文里反而比旧分词更差。
缓解方法通常包括:
- 对包含新 token 的样本做过采样;
- 给扩词后的模型一个单独 warm-up 阶段;
- 控制新增 token 数量,只扩那批真正高频且高价值的术语;
- 更密集地评估含新 token 的典型任务,而不是只看总体 loss。
6.5 分词器一致性
一旦 分词器扩展,训练、评估、推理、日志分析、缓存、离线数据预处理等所有链路都必须统一更新。否则最容易出现的不是“性能略差”,而是样本和 checkpoint 根本不兼容:
- 同一句话在训练和推理时被切成不同 token;
- embedding index 对不上;
- 预处理缓存失效;
- 线上长度预算与离线估算不一致。
所以,分词器扩展不仅是一个建模问题,还是一个版本管理和部署一致性问题。
把贯穿案例放进 分词器手术里看
内部代码助手里最容易触发扩词的是高频 API 名、配置键、路径片段和内部服务缩写。但这类 token 也最容易在 serving 侧出问题:一旦训练端和线上检索或缓存还在用旧 tokenizer,同一个函数名的切分就会前后不一致,模型“线下好、线上怪”的问题几乎必然出现。
7 梯度到底从哪里来?三种学习拓扑
我们第一次接触中训练、SFT、DPO 或 RL 时,会把注意力放在“数据长什么样”上:文档、对话、偏好对、工具轨迹。这样的直觉并不算错,但还不够深。
真正决定模型如何更新参数的,不只是样本内容本身,而是这几个更基础的问题:
- 模型在每一个位置 到底看到了什么上下文;
- 序列里的哪些位置 真的参与了损失函数;
- 训练样本是 静态给定的,还是 由当前策略自己生成的。
Packing、Masking、Rollout 之所以重要,不是因为它们是三种“最流行的训练技巧”,而是因为它们分别控制了梯度形成链路中最上游的三个变量:
- 上下文拓扑(context topology)
- 监督拓扑(supervision topology)
- 数据生成拓扑(data-generation topology)
如果这三个环节里任何一个设计错了,训练依然会收敛,但模型很可能在学错东西。很多训练故障表面上像“数据质量一般”或“loss 不太稳”,本质上其实是 梯度来源错了。
本节先回答几个关键问题:
- 为什么 Packing、Masking、Rollout 不在同一个抽象层级上,却要放在一起讲?
- 为什么说它们不是简单地“改一点训练细节”,而是在改梯度的来源?
- 在 CPT、SFT、DPO 和 RL 里,哪些 token 真正拿到梯度?
- 为什么同样一段文本,换一种训练拓扑,模型学到的东西会完全不同?
7.1 一个统一框架:梯度到底从哪里来
先把不同训练方法放进一个统一的数学框架里。设模型参数为 (),训练序列为 (s = (s_1, s_2, , s_T)),则很多自回归训练都可以抽象成:
\[ \mathcal{L}(\theta) = \mathbb{E}_{s \sim q} \left[ \sum_{t=1}^{T} w_t\, \ell_t(\theta; s_{\le t}) \right] \]
其中:
- (q) 表示 训练序列的分布;
- (s_{t}) 表示第 (t) 个位置能看到的 上下文前缀;
- (w_t) 表示这个位置的 损失权重;
- (_t) 表示第 (t) 个位置对应的局部损失。
于是梯度就是:
\[ g(\theta) = \nabla_\theta \mathcal{L}(\theta) = \nabla_\theta \mathbb{E}_{s \sim q} \left[ \sum_{t=1}^{T} w_t\, \ell_t(\theta; s_{\le t}) \right] \]
这个公式的意义非常大:训练中的参数更新,归根结底来自三件事。
1. 样本分布 (q)
你到底在从什么样的序列里采样?
- 是来自语料库的静态文本?
- 是领域文档与通用回放混合后的序列?
- 还是当前策略自己 rollout 出来的轨迹?
2. 上下文前缀 (s_{t})
同一个 token,如果放在不同的上下文里被预测,它给出的梯度不会一样。
对 language model 来说,损失通常写成条件概率形式:
\[ \ell_t(\theta; s_{\le t}) = -\log p_\theta(s_{t+1} \mid s_{\le t}) \]
也就是说,模型不是在学习“孤立 token 是什么”,而是在学习“在这个前缀下,下一个 token 应该是什么”。所以只要前缀变了,梯度就会变。
3. 损失权重 (w_t)
不是每个 token 都必须被优化。某些位置可以参与损失,某些位置可以只是上下文但不参与监督。只要 (w_t) 变了,参数更新的来源也就变了。
7.2 三种最关键的“梯度入口”
从上面的统一框架看,Packing、Masking、Rollout 分别对应三种不同的上游控制:
- Packing:主要改的是 (s_{t}),也就是每个位置的上下文;
- Masking:主要改的是 (w_t),也就是哪些位置真的进损失;
- Rollout:主要改的是 (q),也就是训练样本是谁生成的。
所以,更准确的表述不是“这三种方法改变梯度最多”,而是:
它们分别控制了梯度形成链路里最根本的三个变量:上下文、监督位置和样本来源。
这就是为什么它们值得放在一起讲。
一个更直观的例子
把训练想成老师批作业,会更容易理解这三种拓扑。
Packing
相当于你把几份不同学生的作业钉在一起交给老师。老师会默认这些页面前后相连。于是,题目前后文被改写了。
Masking
相当于你告诉老师:“只批最后一页,不用批前面草稿。” 于是,不是所有内容都参与评分。
Rollout
相当于你不再给老师固定答案,而是让学生现场解题,再按最终成绩给分。于是,作业是谁写出来的,以及怎么给分,都变了。
这个比喻并不严格,但它很好地对应了三者的本质差别:
- Packing 改的是作业的排布;
- Masking 改的是批改范围;
- Rollout 改的是作业的生成方式。
7.3 Packing:改变上下文拓扑
Packing 到底是什么
Packing 本身不是一个新的优化目标,而是一种 数据布局策略。它最常见于预训练和 continued pretraining(CPT):为了减少 padding 浪费,把多个较短文档拼进同一个固定长度的上下文窗口里。
设原始文档集合为:
\[ D^{(i)} = (x^{(i)}_1, x^{(i)}_2, \dots, x^{(i)}_{n_i}) \]
Packing 操作可以写成:
\[ z = \mathrm{Pack}\left(D^{(i_1)}, D^{(i_2)}, \dots, D^{(i_k)}\right) = [D^{(i_1)}, \langle eos \rangle, D^{(i_2)}, \langle eos \rangle, \dots] \]
拼好之后,训练目标通常还是标准的 next-token loss:
\[ \mathcal{L}_{\text{pack}}(\theta) = -\sum_{t=1}^{T-1} \log p_\theta(z_{t+1} \mid z_{\le t}, A) \]
其中 (A) 是可选的 attention mask,用来控制文档边界是否允许跨越。
为什么它会改变梯度
关键不在于 loss 公式变没变,而在于 上下文变了。
如果模型原本是在单文档模式下训练,那么文档 B 的开头,只会在“文档 B 的起点”这个语境里被预测。可一旦你把文档 A 和文档 B pack 在一起,文档 B 的开头就会在“文档 A 的尾部之后”被预测。
也就是说,Packing 改的是:
\[ s_{\le t} \]
它改变了每个 token 的条件上下文。
对语言模型来说,这种变化非常大,因为训练目标本来就是条件式的:
\[ -\log p_\theta(x_t \mid x_{<t}) \]
同一个 (x_t),只要 (x_{<t}) 变了,梯度就不是同一个梯度。
例子:两个完全不相关的文档被拼在一起
假设有两篇短文档:
- 文档 A:
患者主诉胸痛,持续两小时。 - 文档 B:
本合同自签署之日起生效。
Packing 之后,序列可能是:
\[ [ \text{患者主诉胸痛,持续两小时。}, \langle eos \rangle, \text{本合同自签署之日起生效。}, \langle eos \rangle ] \]
从吞吐量角度看,这很好:几乎没有 padding 浪费。
但从学习信号角度看,如果边界处理不好,模型会看到一种并不存在的统计关系:
“患者主诉胸痛,持续两小时。” 后面很自然地接 “本合同自签署之日起生效。”
这显然不是你想让模型学习的结构。
工程含义
Packing 的收益是 吞吐量,代价是 上下文拓扑被重写。因此它不是一个“纯粹的系统优化技巧”,而是一个会直接影响梯度的学习设计。
常见故障模式
- 文档边界泄漏(boundary leakage)
- EOS 分隔不一致
- attention mask 设计错误
- 长度分布变化导致 loss 统计和训练动态变化
Packing 改的是 token 在什么前缀下被预测。
7.4 Masking:改变监督拓扑
Masking 到底是什么
如果说 Packing 解决的是“样本怎么摆进去”,那么 Masking 解决的是“哪些位置算数”。
在监督微调(SFT)里,我们常常把一整段对话拼成一个序列,但并不希望所有 token 都参与损失。很多场景下,我们只希望模型学习 assistant 的输出,而不是要求它去“复述用户输入”。
设训练序列为:
\[ s = (s_1, s_2, \dots, s_T) \]
定义一个 loss mask:
\[ m_t \in \{0,1\} \]
其中:
- (m_t = 1):第 (t) 个位置参与损失;
- (m_t = 0):第 (t) 个位置不参与损失。
那么训练目标可以写成:
\[ \mathcal{L}_{\text{mask}}(\theta) = -\sum_{t=1}^{T-1} m_{t+1}\log p_\theta(s_{t+1} \mid s_{\le t}) \]
为什么它会改变梯度
Masking 改的是每个位置的损失权重,也就是:
\[ w_t = m_t \]
如果某个位置的 (m_t = 0),那么该位置虽然存在于序列中,仍然作为上下文被模型看到,但它 不会贡献任何梯度。
这意味着同一段文本,只要 mask 方式变了,模型被要求负责的部分就变了。
例子:一条 SFT 对话样本
<system> 你是企业客服助手
<user> 帮我写一封退款邮件
<assistant> 当然可以,下面是一封简洁专业的退款邮件:
...
在典型的 assistant-only SFT 里:
systemtoken:作为上下文,但不参与损失;usertoken:作为上下文,但不参与损失;assistanttoken:参与损失。
换句话说,模型会利用 system 和 user 的内容来预测 assistant 的输出,但不会因为“没有把用户的话重复一遍”而被惩罚。
为什么这件事影响很大
因为它实际在定义:
模型到底要为谁负责。
- 在 CPT 里,几乎所有 token 都参与 next-token 预测;
- 在典型 SFT 里,只有 assistant token 参与监督;
- 在某些工具轨迹训练里,可能只监督工具调用和最终答案;
- 在结构化输出任务里,甚至可能只对 JSON body 的某些字段计算损失。
因此,Masking 改的不是“样本格式细节”,而是 监督边界。
常见故障模式
- mask 错位,导致 user token 也被监督;
- 模板拼接错误,assistant 第一段没被算进 loss;
- 多轮对话里部分回答被监督,部分回答漏掉;
- 工具调用 token 与自然语言 token 的监督策略不一致。
Masking 改的是哪些位置的误差真的能进入梯度。
7.5 Rollout:改变数据生成拓扑
Rollout 到底是什么
Rollout 和前两者的本质差别在于:训练样本不再完全来自一个静态数据集,而是由当前策略模型自己生成。
设输入 prompt 为 (x),策略模型为 (_),模型生成的轨迹为:
\[ y = (y_1, y_2, \dots, y_T) \sim \pi_\theta(\cdot \mid x) \]
然后环境、verifier 或奖励模型为这个完整轨迹给出回报:
\[ R(x, y) \]
训练目标是最大化期望奖励:
\[ J(\theta) = \mathbb{E}_{y \sim \pi_\theta(\cdot \mid x)}[R(x,y)] \]
一个典型的策略梯度形式可以写成:
\[ \nabla_\theta J(\theta) \approx \mathbb{E}\left[ \sum_{t=1}^{T} A_t\, \nabla_\theta \log \pi_\theta(y_t \mid x, y_{<t}) \right] \]
其中 (A_t) 可以理解为 advantage 或 reward 相关的加权项。
为什么它会改变梯度
在监督学习里,训练分布 (q) 通常来自静态语料库;而在 rollout 里,训练序列是从当前策略 (_) 中采样出来的。也就是说,样本分布本身与参数有关:
\[ q = q_\theta \]
这和前两种拓扑有本质区别:
- Packing 改的是上下文;
- Masking 改的是监督位置;
- Rollout 改的是 训练样本的来源机制。
例子:数据库查询与解释
假设任务是:
用户:去数据库里找出上个月 GMV 最高的 10 个商品,并给出解释。
如果是 SFT,你会给模型一条示范答案,让它模仿。
如果是 rollout,模型必须自己完成完整轨迹:
- 写 SQL;
- 调工具查询数据库;
- 读取返回结果;
- 写出解释;
- 接受 verifier 或奖励函数评分。
奖励可能来自:
- SQL 是否可执行;
- 查询结果是否正确;
- 最终文字解释是否与返回数据一致;
- 是否调用了正确工具。
这里已经没有一个固定的“标准答案 token 序列”可供逐字模仿。模型必须先自己生成轨迹,梯度才会沿着那条轨迹回流。
为什么它影响更大
因为 rollout 把训练从“在固定数据上拟合”变成了“在自己的行为上优化”。这会带来几个连锁后果:
- 模型必须探索;
- 监督信号往往延迟到序列结束后才出现;
- credit assignment 变难;
- reward 噪声会通过整条轨迹传回梯度;
- 当前策略改变后,未来看到的样本也会改变。
Rollout 改的是样本由谁生成,以及奖励如何沿着轨迹回流到参数。
7.6 如何改变梯度
很多读者会自然地问:学习率、batch size、optimizer、temperature、采样比例这些东西,不也非常重要吗?
当然重要。但这些变量更像是在调节:
- 梯度有多大;
- 更新有多快;
- 噪声有多强;
- 训练有多稳。
而 Packing、Masking、Rollout 更像是在决定:
- 梯度 来自哪些样本;
- 梯度 在什么上下文下形成;
- 梯度 归属于哪些位置;
- 样本是 静态给定 还是 策略自己探索出来。
这就是两者的差别:
- 学习率是在调 更新强度;
- 拓扑是在定 梯度支持集与来源机制。
因此,说这三种拓扑重要,不是因为它们“唯一重要”,而是因为它们处在 梯度形成链路的最上游。
| 维度 | Packing | Masking | Rollout |
|---|---|---|---|
| 核心问题 | 样本怎么排进上下文窗口 | 哪些位置参与损失 | 样本是谁生成的 |
| 主要改变的数学对象 | (s_{t}) | (w_t) | (q) |
| 是否需要探索 | 不需要 | 不需要 | 需要 |
| 监督是否立即可见 | 是 | 是 | 往往延迟 |
| 典型场景 | 预训练、CPT | SFT | RL、Agent training |
| 它改写的拓扑 | 上下文拓扑 | 监督拓扑 | 数据生成拓扑 |
Packing 决定 token 在什么前缀下被预测,Masking 决定哪些 token 的误差进入梯度,Rollout 决定训练样本由谁生成。
现在回头看常见训练阶段,会更清楚:
| 方法 | 典型数据形态 | 直接参与梯度的对象 | 学到的主要东西 |
|---|---|---|---|
| CPT | 原始文档、代码、长文本 | 几乎所有 token | 参数化知识、领域分布、文档结构 |
| SFT | 对话示范、工具轨迹 | 通常是 assistant token | 行为格式、指令跟随、工具使用模板 |
| DPO / ORPO | chosen / rejected 偏好对 | 两条回答的相对偏好差异 | 回答质量倾向、偏好排序 |
| RL | prompt + rollout + 奖励 | 策略采样轨迹上的 token | 搜索、探索、长程策略改进 |
这张表背后的重点不是术语,而是:
不同训练阶段的差别,不只是“数据长什么样”,而是梯度到底从哪里来。
当训练结果不符合预期时,不要只问”数据对不对”,还要继续追问:模型在这些位置看到了什么上下文?哪些位置真的参与了损失?样本到底来自静态语料,还是来自当前策略自己的行为?很多所谓”模型学坏了”的问题,不是优化器太差,也不是 loss 不够先进,而是 Packing、Masking、Rollout 这三个梯度来源里至少有一个答案错了。
8 怎么把整套东西放进训练系统:并行、恢复、版本绑定与可审计
本节先回答几个关键问题:
- CPT 在系统上到底是如何从 pretrained checkpoint 恢复的?
- data parallel、tensor / model parallel、sequence parallel 在中训练里各自解决什么问题?
- 为什么 分词器版本、数据版本、restore mode 必须和 checkpoint 绑定管理?
- 什么样的 pipeline 才算可回滚、可复现、可审计?
中训练是一次“再配置过的继续训练”,而不是把旧脚本换一批数据继续跑。
系统实现决定了实验能不能复现、checkpoint 能不能回滚、回归能不能定位、线上行为能不能追溯。很多团队以为自己在讨论模型,最后真正把 run 拉开的,却是恢复方式、数据版本、并行栈和日志治理。
8.1 怎么恢复,恢复什么
CPT 几乎总是从一个已有的 pretrained checkpoint 开始,但“恢复 checkpoint”有两种含义:
- 只恢复模型权重:最常见于拿开源模型继续训练,此时优化器状态通常不可用,需要重新初始化优化器与 scheduler;
- 恢复完整训练状态:如果是在自有预训练流水线上继续训练,可以同时恢复优化器、调度器和随机状态,让 CPT 更像“中断后续跑”。
把训练状态写得严格一点,可以把一次恢复看成对下面这个状态集合的选择:
\[ \mathcal{S} = \{\theta, \phi_{opt}, \psi_{sched}, \xi_{rng}, \tau_{tok}, \nu_{data}\} \]
其中:
- \(\theta\):模型权重;
- \(\phi_{opt}\):优化器状态;
- \(\psi_{sched}\):学习率调度状态;
- \(\xi_{rng}\):随机数状态;
- \(\tau_{tok}\):分词器版本;
- \(\nu_{data}\):数据版本与采样配置。
于是两种常见恢复方式可写成:
\[ \mathcal{R}_{weights} = \{\theta\} \qquad \mathcal{R}_{full} = \{\theta, \phi_{opt}, \psi_{sched}, \xi_{rng}, \tau_{tok}, \nu_{data}\} \]
这两种方式在工程含义上不同。只恢复模型权重时,你必须更小心学习率和 warm-up,因为优化器对当前参数几乎没有历史感知;而恢复完整状态时,虽然更连续,但你也要确认新分布与旧优化状态是否匹配。
8.2 weights-only 与 full-state 的操作差异
这个区别在真实项目里常常被低估。weights-only resume 和 full-state resume 不是同一个 recipe。
- weights-only:更像“拿一个成品模型重新开一个新实验”;
- full-state:更像“在原训练轨道上继续跑”。
因此,当只恢复权重时,工程上通常需要:
- 更低的初始 LR;
- 重新 warm-up;
- 更密的 early eval;
- 把它当成一个新 run,而不是“原 run 的后半段”。
而当恢复 full-state 时,工程上更需要警惕:
- 旧 optimizer 的动量是否适合新分布;
- scheduler 是否会把 LR 带到一个不适合 CPT 的区间;
- 数据版本和 分词器是否保持严格一致。
8.3 怎么并行
对大模型而言,CPT 通常沿用预训练期的分布式训练栈,只是预算更小、run 更短、评估更密。常见角色如下:
- Data Parallel / FSDP / ZeRO:把样本批次拆到多卡,分摊参数、梯度或优化器状态;
- Tensor Parallel:把单层张量切到多卡,适合超大模型;
- Pipeline Parallel:把层切到不同设备,适合非常深的模型;
- Sequence Parallel:在长上下文或大 batch 条件下分担序列维度压力;
- Activation Checkpointing:用更多重算换更低显存。
具体内容,请翻阅第二章。
在中训练里,它们不是“越多越好”,而是由模型规模、上下文长度和集群结构共同决定。CPT 常见的工程判断是:既然 run 短、评估密,就要尽量降低系统复杂度,避免为了极限吞吐引入过多并行层级。
8.4 怎么绑定版本,怎么保证可复现、可审计、可回滚
- 数据版本与 分词器版本必须跟 checkpoint 绑定。否则模型回归时无法定位到底是数据变了还是代码变了。
- checkpoint 频率要比预训练更密。因为 CPT 的目标不是长期收敛,而是找到一个领域收益与通用退化平衡更好的点。
- 评估必须和 checkpoint 同步。没有相邻 checkpoint 的评估,很难解释 U 形曲线。
- 保持一个不可变的 base checkpoint。这样你总能回到清洁起点,而不是在多个“半适配”的分支上迷路。
- I/O 管线要跟上。** 很多中训练瓶颈不是算力,而是长文档读取、去重、packing 和数据加载本身。
9 CPT 与后续 SFT / DPO / RL 的兼容性
本节先回答几个关键问题:
- 为什么中训练会影响后续的 instruction tuning、RLHF、DPO 和 Agentic RL,而不是一个独立的前置步骤?
- 什么样的 CPT 会帮助后续 alignment,什么样的 CPT 会拖后腿?
- 为什么过度领域化会伤害后续对齐训练?
- 给定一个真实产品需求,如何在 prompting → RAG → SFT → DPO → RL → CPT → 蒸馏之间做决策?
- 为什么“先做一个更懂领域的模型”仍然不等于“已经做好了产品对齐”?
CPT 改变的是模型的起点分布,而后续 alignment 改变的是这个起点上的行为。 如果中训练把模型带到了一个更贴近目标任务的分布,SFT 和 RL 往往更容易学;但如果 CPT 把模型拉得过窄、过于模板化、过于远离通用对话分布,那么后续对齐反而会更吃力。
9.1 为什么 CPT 会影响后续 SFT / RL
后训练方法并不是凭空创造能力,它们更像是在已有能力上做重加权或行为塑形。一个模型如果在目标领域、长上下文、代码痕迹、工具文本或数学表述上已经有一定先验,那么:
- SFT 更容易把这些能力塑形成稳定行为;
- 偏好优化 更容易在多个“都不差”的回答之间学到好坏排序;
- RL / Agentic RL 更容易从 rollout 中获得有用的奖励信号,因为候选解本身已经更像样。
反过来,如果 base model 对任务分布几乎没有任何先验,让 RL 直接去学,常常会遇到奖励稀疏、探索成本高、rollout 太差的问题。也就是说,CPT 可以提高后续 RL 的可学性,但不能替代 RL 本身。
9.2 两条最常见的失败路径
CPT到底怎样把后续对齐变难?
失败路径 1:过度领域化 → 对话退化 → SFT 债务上升
如果领域分布过窄、replay 太低,模型会开始把一切都看成法律、医疗或代码问题。其后果不是单一 benchmark 下降,而是整个聊天分布开始退化:
- 普通用户问题也被回答得过于术语化;
- 输出风格更像文档续写而不是助手回复;
- 结构化输出和工具模板被领域文本风格冲掉。
从系统角度看,这意味着你把本来应该做“轻量 SFT”的问题,变成了“先用 SFT 把模型拉回能聊天的状态,再做真正的产品行为监督”。这就是 SFT debt(SFT 债务)。
失败路径 2:表示空间漂移 → rollout 脆弱 → RL 不稳定
即使表面指标没那么差,CPT 也可能通过表示漂移增加后续 RL 的脆弱性。直觉上,如果模型在 CPT 中被高度模板化数据强行拉向一个狭窄子空间,那么 RL rollout 会出现几个问题:
- 中间推理步骤更僵硬,探索空间更小;
- verifier 能给出的高奖励轨迹更少,奖励更稀疏;
- 小的参数更新更容易造成风格塌缩或工具使用波动。
如果要把这种“离得太远”的风险写成一个简化量,可以在某个固定聊天分布 \(D_{chat}\) 上测:
\[ \mathrm{Drift}_{chat}(\theta_{cpt}) = \mathbb{E}_{x \sim D_{chat}} \big[ \mathrm{KL}(p_{\theta_{base}}(\cdot\mid x)\,\|\,p_{\theta_{cpt}}(\cdot\mid x)) \big] \]
它不必作为唯一指标,但可以帮助你理解一个原则:领域涨得再多,如果它伴随聊天分布和对齐分布上的大幅漂移,后续成本可能反而更高。
9.3 一个实用的决策顺序
给定产品需求时,较稳的决策顺序通常是:
- 先试 prompting / system design:如果能力本来就在,只是没被稳定触发,不要急着训练;
- 知识变化快时优先 RAG:把事实留在外部记忆里,比写进参数更可维护;
- 行为格式不稳时做 SFT:输出 schema、角色风格、工具模板都是这一层的事;
- 回答质量排序不稳时做 DPO / RLHF;
- 需要搜索、长程策略和 verifier 优化时做 RL;
- 根本缺的是领域分布和参数化知识时,再做 CPT;
- 质量够了、成本过高时,最后做蒸馏或压缩。
这个顺序看起来像流程图,但它背后其实是一条成本原则:先动最便宜、最贴近缺口的杠杆。
把贯穿案例放进兼容性里看
对内部代码助手来说,CPT 可能让模型更熟悉仓库,但不会自动让它学会“先检索、再解释、最后按团队 patch 模板输出”。这些仍然是 SFT 的工作。相反,如果 CPT 让模型开始把普通用户问题也回答成内部 runbook 风格,后续对齐的代价会立刻上升。
10 工程案例
本节先回答几个关键问题:
- 域内任务涨分、通用能力掉分时,最典型的根因是什么?
- 为什么 分词器扩展经常在离线指标正常、线上行为异常时才暴露问题?
- aggressive learning rate 会怎样把一次本来可控的 CPT 变成训练发散?
- 为什么“领域数据更多了”并不总是等于“系统整体更好了”?
- 哪些失败一开始看起来像小 bug,最后却直接进入梯度?
下面这些不是抽象概念,而是中训练项目里非常常见的工程故事。每个故事都用同一个结构说明:症状 → 根因 → 修复。
案例 1:法律任务涨了,通用问答却崩了
症状:法律检索和合同摘要明显提升,但开放域问答、MMLU 和一般聊天质量同时下降。
根因:训练配方几乎完全由法律文档组成,replay 比例过低,模型在短时间内被窄分布拉偏。团队只盯着领域 benchmark,直到产品灰度后才发现通用回归。
修复:提高通用回放比例,降低学习率,把通用与安全评估放进回归门控,并将 checkpoint 选择标准从“领域最高分”改成“领域收益 / 通用回归的 Pareto 最优点”。
案例 2:扩词后离线 loss 正常,线上代码生成却更差
症状:新增了一批代码 API token 后,训练 loss 看起来正常,但线上生成经常出现奇怪的半截标识符和不稳定补全。
根因:新 token 虽然加入词表,但相关样本频率不足,embedding 行训练不足;同时,部分离线评估仍然使用旧 tokenizer,导致训练—评估—部署不一致。
修复:统一 分词器版本,对含新 token 的样本做阶段性过采样,单独增加包含 API 名和路径的回归集,并在 serving 侧清理所有旧 分词器缓存。
案例 3:做完医疗 CPT 后,模型更懂术语了,却不会和用户说话了
症状:模型在病历术语、实体识别和医疗长文档摘要上显著提升,但多轮对话体验变差,回答风格更像文档续写而不是助手回复。
根因:团队把 CPT 当成了产品化步骤,忽略了中训练只学分布、不学交互行为。模型被推向医疗文档风格,却没有再经过足够的 SFT 把它拉回聊天分布。
修复:在 CPT 后补充高质量医疗问答和摘要类 SFT,并单独评估对话行为、拒答边界和结构化输出质量。
案例 4:训练在前几亿 token 看起来正常,后面突然发散
症状:前期 loss 平稳下降,随后某一阶段 loss、梯度范数和通用评估同时恶化,后续 checkpoint 全部变差。
根因:学习率设得过高,加上一个数据子集存在错误拼接和重复模板,导致模型在少量异常 batch 上出现大的优化冲击。团队因为 checkpoint 间隔过大,没能及时定位拐点。
修复:缩短 checkpoint 与评估间隔,降低峰值 LR,增加 gradient clipping,重新清洗异常子集,并在数据加载阶段增加长度、字符集和模板检测。
案例 5:领域任务提升了,但后续 RL 反而更难扩
症状:中训练后代码与数学 corpus 上的 loss 下降了,但后续基于 verifier 的 RL 提升幅度不如预期,rollout 风格也更僵硬。
根因:CPT 数据高度模板化,模型学到了较强的局部格式偏好,却没有学到足够多样的中间解空间,导致后续探索空间变窄。
修复:提高数据多样性,混入更自然的推理和工具文本,在 CPT 阶段保留足够 replay,并在 RL 前先用 SFT 恢复更稳定的交互与中间步骤格式。
案例 6:packing bug 让一个文档的引用“流血”到另一个文档
症状:法律模型在摘要时开始把上一份合同的引文片段带到下一份合同里,像是在“跨文档记忆”。
根因:document packing 时只做了简单拼接,没有在边界上使用足够明确的 EOS / attention 约束,模型把跨文档衔接当成了真实统计模式。
修复:增加显式边界、修正 attention mask、重新构建 cross-document contamination regression set,并回滚到未受污染的 checkpoint。
案例 7:同样的 base weights,外部复现却和内部 run 完全不一样
症状:团队拿到同一个基座模型重新做 CPT,结果与原团队内部 run 的稳定性和收敛点明显不同,怀疑“是不是数据有问题”。
根因:内部 run 恢复了 optimizer / scheduler / RNG 的 full state,而外部复现只恢复了 model weights;两边实际上跑的不是同一个 recipe。
修复:显式记录 restore mode,把 weights-only resume 当作一个新实验重新调 LR / warm-up,并在实验元数据中把 tokenizer、data mix、optimizer state 一起版本化。
案例 8:领域化做得很好,拒答边界却悄悄松了
症状:领域模型在专业问题上更自信,但对不该回答的问题也更愿意硬答,安全评估直到后期才发现劣化。
根因:replay 太低,安全相关的通用底盘被削弱;同时 regression gates 里没有单独的 safety slice,团队只监控了领域任务和通用 QA。
修复:提升 replay,把安全评估加入硬门控,并预留 post-CPT 的对齐工作,而不是假设“领域能力上涨会自动带来更好行为”。
11 本章小结
CPT 应该被当成一项受约束的工程变更,下列原则值得被当成实践清单:
- 先判断是不是知识缺口,再决定是否动用 CPT。 不要用继续预训练去解决本应由 SFT、RAG 或 prompting 解决的问题。
- 数据分布比原始数据量更重要。 领域数据再多,如果充满模板、重复和脏样本,也只会把错误放大。
- 默认带 general replay。 只在极少数确有把握的场景下,才尝试几乎纯领域化的配方。
- 学习率要比预训练更保守。 对已经成型的表示空间,剧烈更新往往弊大于利。
- packing 是吞吐优化,不是免费午餐。 文档边界处理不当,会把不存在的模式写进参数里。
- 分词器扩展要克制。 只有在高频术语碎片化已成为明显质量或成本瓶颈时,才值得动它。
- checkpoint、数据版本、分词器版本必须绑定管理。 否则回归无法定位。
- 评估要双向看。 既看领域收益,也看通用、安全、行为和后续对齐能力。
- CPT 不是产品化终点。 很多系统在 CPT 之后仍然需要 SFT、偏好优化和推理层控制。
- 能用更便宜的方法,就不要先上训练。 对工程团队来说,最低成本的有效解始终优先。
- 把 restore mode 当成实验变量。 weights-only 和 full-state resume 需要不同的 LR / warm-up 与复现预期。
- 给每次 run 一个明确的 stop policy。 不要把“也许再跑一会儿就恢复”当成训练策略。
11.1 问题小结
- 什么是 continued pretraining(CPT)?它与预训练和 SFT 有何不同?
CPT 是在一个已经预训练好的基座模型上,继续使用下一个 token 目标做训练,但训练分布换成更靠近目标领域的数据。它和预训练的主要区别不在损失函数,而在数据分布、训练预算和稳定性要求;它和 SFT 的区别在于,CPT 学的是参数化知识和领域先验,SFT 学的是行为映射和输出格式。
- 什么时候该用 CPT,而不是提示工程、RAG 或 SFT?
当问题是术语、实体、长文档结构和领域分布本身不在模型里时,用 CPT;当能力已潜伏、只是没被稳定触发时,先用 prompting;当知识更新快且需要可追溯时,用 RAG;当模型知道事实但不会按要求输出时,用 SFT。
- 模型在法律文档上困惑度很高,但通用性能不错,优先修哪一层?
优先考虑 CPT,因为这说明模型没有很好建模法律文本分布。SFT 可以改变回答方式,但通常不能弥补术语共现、引文格式和长文档结构先验的缺失。
- 为什么 same objective, different distribution 会在实践中产生巨大差异?
因为模型学习的是条件概率分布。目标函数不变,但如果样本分布从通用网页换成法规、病历或代码库,模型就会重新估计哪些 token、结构和实体更常见,从而改变参数化知识和语言先验。
- general replay 为什么重要?
replay 是在领域适配时保护通用能力的最低成本手段。它既减少灾难性遗忘,也能缓和新分布带来的优化冲击,还能保留后续 SFT 和 alignment 所依赖的通用底盘。
- 如何设计 CPT 的 regression suite 和 stop criteria?
至少包含四类评估:领域、通用、行为、安全。stop criteria 不应只看“领域涨没涨”,而应同时包含通用 / 安全回归阈值、领域收益停滞窗口,以及单位 token 收益是否还值得继续投入。
- CPT 中的稳定性鸿沟是什么?
稳定性鸿沟指模型在进入新分布训练后,领域指标上升,但通用能力先明显下跌的现象。它通常由过高学习率、过窄分布、replay 不足和脏数据共同造成;应对方式是更保守的 LR、更稳的 mixture、更密的评估和明确的回滚策略。
- 什么时候应该扩展 tokenizer?
当目标领域里存在大量高频、高价值、但被严重碎片化的 token,例如药名、法律引文、代码标识符或新语言脚本,而且这种碎片化已经明显影响上下文成本和质量时,才值得扩词。否则,优先考虑直接用 CPT 学习旧分词下的表示。
- 新增 token 的 embedding 怎么初始化更稳?
一般会用组成该 token 的旧 sub-token embedding 的均值或组合初始化,而不是纯随机初始化。这样可以让新 token 从一个更接近原语义位置的起点开始训练,降低早期噪声和不稳定。
- weights-only resume 和 full-state resume 有什么操作差异?
weights-only 更像在已有参数上重新开一个新实验,通常需要重新 warm-up 和更保守的 LR;full-state 更像原训练轨道的延续,但也更依赖旧 optimizer / scheduler 状态与新分布的兼容性。两者不应被视为同一个 recipe。
- CPT、SFT、DPO 和 RL 的核心差别是什么?
核心差别在于数据构造和梯度来自哪里。CPT 对几乎所有 token 做 next-token 学习;SFT 主要在 assistant token 上施加监督;DPO 比较 chosen 和 rejected 的相对偏好;RL 优化策略采样出来的 rollout 的期望奖励。
- 为什么中训练会影响后续 alignment 和 RL?
因为 CPT 改变了模型的初始化分布。一个更贴近目标领域的模型,会让后续 SFT、DPO 和 RL 更容易学;但如果 CPT 过度领域化、损伤通用对话或安全边界,后续对齐的成本反而会上升。
- 如何 debug 一个“领域 perplexity 变好了,但工具使用和结构化输出变差”的模型?
先判断问题是不是行为回归而非知识回归:检查 SFT 模板是否被 CPT 冲掉、behavior eval 是否缺失、replay 是否过低;然后核查 分词器/ schema 兼容性、packing 污染以及是否需要在 CPT 后追加针对工具轨迹的 SFT。
- 给定产品需求,如何在 prompting → RAG → SFT → DPO → RL → CPT → distill 之间做决策?
我会先定义目标行为和约束,再诊断缺口类型。能不训练就先不训练;知识需要外部更新时优先 RAG;行为格式问题优先 SFT;偏好排序问题用 DPO / RLHF;长程搜索与 verifier 优化问题用 RL;只有当模型真的缺领域知识和分布先验时才上 CPT;最后如果质量够了但成本过高,再做蒸馏和压缩。
中训练的难点,从来不只是“继续训练”四个字,而是如何让继续训练仍然可控、可回滚、可解释、可接入后续系统。从本章的角度看,一个成熟的 AI 工程师至少要掌握四个判断:
- 什么时候面对的是知识缺口,而不是行为缺口;
- 如何通过 replay、mixture、packing 和门控做受控领域适配;
- 为什么 tokenizer、checkpoint 和训练拓扑会影响实际稳定性;
- 为什么 CPT 的价值不止在本身,还在于它如何为后续 SFT、DPO、RL 和部署留下更好的起点。
当你能够用这些判断回答“如何在不破坏既有能力的前提下适配新领域”时,你就真正掌握了CPT作为一项工程系统。
下一章我们介绍如何把中训练完成的基础模型变成助手。