大模型训练
简介
理论
LLM 是深度学习的一类模型,同样遵循“定义损失 → 反向传播 → 优化器更新参数”的训练流程。但自回归语言建模需要预测整个词表的条件概率分布,大模型训练又受到模型规模、数值稳定性和算力成本的约束,因此在损失函数、输出概率变换、优化器及其配置上有自己的常见选择。
损失函数的选择
预训练和常规 SFT 通常采用逐 token 的交叉熵损失,但交叉熵并非唯一选择。理解损失函数,需要同时看两个问题:希望模型学到什么,以及这个目标能否提供有效梯度。参考除了交叉熵,LM Loss还有什么选择?。
从单个 token 学习概率分布
同一个上下文后面可能接多个合理的 token。语料每次虽然只给出一个目标 token,模型要学习的却是这些可能结果的条件概率分布。例如“今天的天气”后面可以接“很好”,也可以接“很冷”,不能把某一次观察到的后续当成唯一正确答案。
设真实分布为 $p$,模型预测分布为 $q$,观察到的目标 token 为 $y$。训练时不知道完整的 $p$,只能使用从中采到的样本,因此希望损失可以写成:
\[L(p,q)=\mathbb{E}_{y\sim p}[S(q,y)]=\sum_y p_y S(q,y)\]其中 $S(q,y)$ 是单个样本的损失。如果这个期望在 $q=p$ 时取得最小值,就能通过逐样本训练鼓励模型拟合真实分布,这对应恰当评分规则(Proper Scoring Rules)。交叉熵采用 $S(q,y)=-\log q_y$:单次训练提高观察到的 token 的概率,大量样本共同提供学习完整分布的信号。
Softmax 为什么常与交叉熵配套
模型输出的是 logits $z$,Softmax 将它转换为概率 $q$。对于一个位置上的目标 token $y$,交叉熵对第 $j$ 个词表 logit 的梯度为:
\[q_j=\frac{\exp(z_j)}{\sum_k\exp(z_k)},\qquad L=-\log q_y,\qquad \frac{\partial L}{\partial z_j}=q_j-\mathbf{1}[j=y]\]这里 $\mathbf{1}[j=y]$ 在 $j=y$ 时为 1,否则为 0。梯度下降会提高目标 token 的 logit、降低其他 token 的 logit。因此,loss 虽然只显式取了目标 token 的 log-prob,梯度仍会通过 Softmax 的归一化作用到整个词表,再经过 LM Head、Transformer 等计算传播到模型参数。
这对组合还有一个优化上的优点:即使模型非常自信地预测错了,目标位置仍有明显的纠正梯度。单个位置的交叉熵关于 logits 是凸的,但 logits 还依赖整个神经网络,所以这不意味着损失关于模型参数也是凸的。
替代损失与配套的概率变换
满足分布学习要求的损失还包括 Brier 平方损失、Tsallis 评分、球面评分等,但理论上能拟合正确分布,并不保证实际训练同样高效。
- Brier 损失为 $S(q,y)=\sum_j(q_j-\mathbf{1}[j=y])^2$。与 Softmax 配合时,它的梯度比交叉熵多经过一层 Softmax 的导数;当预测接近某个错误的 one-hot 分布时,梯度可能趋近于零,出现“错得很自信,却难以纠正”的情况。
- 换损失时,也需要考虑 logits 到概率的变换是否匹配。原文进一步从希望保留 $\nabla_z L=q-e_y$ 这一简单梯度出发,推导配套的概率变换,联系到 Fenchel–Young loss、Sparsemax 和 Entmax。其中 $e_y$ 是目标 token 的 one-hot 向量。
- 更换损失后的训练 loss 数值不能直接与原交叉熵曲线比较,需要在统一的评估口径下比较,例如相同数据与 tokenizer 上的负对数似然,以及下游任务表现。
这里讨论的是预训练/SFT 如何拟合文本分布。RL 中的 policy loss 则由奖励、优势以及具体算法构造,不能直接把上述分类损失当作 PPO/GRPO 的替代品。
长任务为什么需要异步训练
两阶段
- rollout,先收集一批轨迹,每条轨迹里面有多步 action,把所有轨迹里面所有 action 拼在一起,得到一批 ($s_t,a_t,A_t$) 数据(在 LLM policy training 中通常表示:$s_t=(x,y_{<t})$)。必须完整跑完一条轨迹(多步 action),不能只采样单步;因为回报$G_t$、优势$A_t$依赖整条轨迹未来奖励。
- train/Backward,把这一批 rollout 数据,再切分成更小的mini-batch(比如 32 条轨迹对应的动作集合),在这个 mini-batch 上计算 loss、反向传播;在同一个 rollout 数据集上迭代多次 epoch。loss 计算是在每一个 action 步上:一条轨迹有 T 个 action,每个 action 都单独算 loss / 优势(不是整条轨迹只算 1 个 loss),然后把这个 mini-batch 内所有 action 的 loss 求平均做反向传播。
同步/异步描述的是 rollout 生成与参数训练之间的协作关系
- 同步训练, 一个循环里的两个阶段——先生产(rollout),再消费(train)
for prompts in dataset: # 1. 用当前策略采集一批轨迹 trajectories = rollout(actor, prompts) # 2. 准备训练信号 rewards = score(reward_model, trajectories) ref_logprobs = evaluate(reference_model, trajectories) advantages, returns = compute_advantages( critic, trajectories, rewards, ref_logprobs ) # 3. 用这批数据更新参数,内部可以有多个 mini-batch update_actor(actor, trajectories, advantages) update_critic(critic, trajectories, returns) # 4. 如果生成端是独立副本,将新权重发过去 sync_weights(actor, rollout_engine) -
异步:两个并发 loop。训练端更新参数时,生成端可以继续采样。实际实现还可以让多个 rollout worker 将完成的轨迹陆续入队,不必等某一整批任务结束。
</table># Loop 1:生成端,持续生产训练数据 while running: prompts = next_prompts() trajectories = rollout(rollout_actor, prompts) # 保存实际生成时的策略版本、动作概率等 batch = prepare_training_data(trajectories) queue.put(batch) # 在安全的切换点接收新权重 maybe_refresh_weights(rollout_actor)# Loop 2:训练端,持续消费训练数据 while running: batch = queue.take_batch() # 检查策略滞后,按算法要求筛选或修正 batch = handle_policy_lag(batch, actor) advantages, returns = compute_training_targets(batch, critic) update_actor(actor, batch, advantages) update_critic(critic, batch, returns) publish_weights(actor)
数学题的回答长度通常比较接近。一批题目开始生成后,往往能在相近时间结束。代码仓库和浏览器任务则不同:有的任务第一次测试就通过,有的任务需要反复读取文件、调用工具和等待外部环境。同一批任务可能相差几分钟甚至更久。同步训练必须等最慢的任务结束,才能把整批轨迹交给训练进程。异步训练会把已经完成的轨迹先放进队列,生成进程继续处理新任务,训练进程则持续从队列取数据。这样可以减少 GPU 等待,但会带来一个新问题:某条轨迹生成时使用的是旧版 Actor,等它进入训练时,Actor 可能已经更新了几轮。
AReaL 和 LlamaRL 都在处理生成与训练异步推进的问题。AReaL 为每条轨迹记录生成它的策略版本,并用重要性采样比较生成策略与当前策略。设生成轨迹时的策略为 $\pi_{\theta_{\mathrm{gen}}}$,训练时的策略为 $\pi_\theta$,某一步动作的修正比率为:
\[\rho_t^{\mathrm{stale}} = \frac{\pi_\theta(a_t\mid s_t)} {\pi_{\theta_{\mathrm{gen}}}(a_t\mid s_t)}\]分子表示当前模型选择动作 $a_t$ 的概率,分母表示生成这条轨迹的旧模型当时选择该动作的概率。若两者都是 0.2,比率就是 1,这条经验与当前策略一致;若分别是 0.1 和 0.2,比率就是 0.5,说明当前模型已经不太会产生这个动作。比率偏离 1 越远,轨迹越陈旧。系统可以降低它的训练权重;版本相差过大时,也可以直接丢弃。
工程
计算量
《AWS上的生成式人工智能》书中对于训练模型所需的内存有一个很好的经验法则。正如他们解释的那样,对于模型的每10亿个参数,我们需要6GB的内存(使用16位半精度)来加载和训练模型。请记住,内存大小只是训练故事的一部分。完成预训练所需的时间也是另一个重要部分。举个例子,最小的 Llama2 模型(Llama2 7B)具有70亿个参数,它花费了184320 GPU 小时才完成训练(阅读更多)。
直接决定训练时间的数据量的Token个数,Token的数量=Epoch数据条数平均数据长度*token转换比。
总训练速度=单设备计算速度 * 计算设备总量 * 多设备加速比,其中
- 单设备的计算速度主要由单块计算加速芯片的运算速度和数据IO能力决定,对单设备训练效率进行优化,主要的技术手段有混合精度训练、算子融合、梯度累加等
- 多设备加速比是由计算和通信效率决定的,需要结合算法和网络拓扑结构进行优化,分布式并行策略的主要目标就是提升分布式训练系统中的多设备加速比。 分布式训练系统需要克服计算墙、显存墙、通信墙等挑战
- 计算墙,H100单卡FP16算力只有2000 TFLOPS,而GPT-3则需要314 ZFLOPS,两者差了8个数量级
- 显存墙,GPT-3 1750亿参数,如果推理阶段采用FP32格式进行存储,则需要700GB显存空间,而H100只有80G
- 通信墙,GPT-3训练中,如果有128个模型副本,则每次迭代过程中至少需要传输89.6TB的梯度数据。而单个InfiniBand 链路仅能提供不超过800Gps的带宽。一个All-Reduce操作需要跨越成千上万张加速卡,哪怕单卡算力再强,只要某一条链路发生拥塞、丢包或者重传,导致通信尾延迟飙升,数千张卡就会在同步屏障面前集体驻足。多卡集群可以看作一个被光纤和网络交换机强行缝合在一起的、放大了数万倍的非均匀访存(NUMA)单体机器。处理核心之间的数据搬运成本,依然完全受制于物理层面的光电转换效率、网络拥塞控制协议与路由转发延迟。 计算墙和显存墙源于单设备计算和存储能力有限,与模型所需的庞大计算和存储需求有矛盾,分布式的解决方法又会面临通信墙的挑战,随着集群规模增加,长时间训练还会收到设备中断、故障的影响,对分布式系统的问题处理也提出了很高的要求。
大模型训练1:模型训练的时间都花在哪了?无论是计算还是通信,我们都可以粗略估计所需时间。并且:
- 理论下限(Lower bound):就是计算和通信中耗时更长的那个;
- 理论上限(Upper bound):是两者耗时之和。
大多数时候,我们可以通过“通信和计算并行”来让实际耗时更接近下限。因此,优化的目标通常是让通信时间和计算时间尽可能重叠。而即使最坏的情况,也只是差一个因子 2(也就是最长不会慢两倍)。另外,如果计算时间远大于通信时间,那就说明硬件一直在计算、没有等数据,说明我们主要受限于计算能力,处于计算瓶颈(compute-bound)状态;如果反过来是通信时间大于计算时间,那就是我们大部分时间都在“等数据”,说明被通信瓶颈(communication-bound)限制了,FLOPs 的潜力没有完全发挥出来,有浪费。 那么我们怎么判断一个操作是计算瓶颈还是通讯瓶颈呢?看一个关键指标:算术强度(Arithmetic Intensity),也叫 Operational Intensity。这个值表示:一个算法“每从内存中搬一个字节,能做多少次浮点运算”(和显存交互平均每个字节所需要运算的次数)。(硬件的理论浮点运算能力 vs 硬件的数据搬运能力)直觉上来理解,如果算术强度高:说明每搬一次数据能做很多计算,计算时间就占主导,是计算瓶颈;如果算术强度低,说明大部分时间都在搬数据,计算单元在空转,是通信瓶颈;以点积为例,计算两个向量x*y ,假设它们的长度都是 N,每个元素是 bfloat16:
- 要从内存读出 x 和 y,每个都是2N 字节,一共 4N 字节(注意:bf16 每个元素是 2 字节,所以这里乘以 2);
- 做 N 次乘法,N-1 次加法,共 2N-1 个 FLOPs;
- 最后再写回 2 字节。 当 N 趋近于无穷大时,整体的算术强度 (2N-1) FLOPs / (4N+2) bytes 近似为 0.5 FLOPs/byte。假设你有两个矩阵:X,大小 bf16[B, D],Y,大小 bf16[D, F],得到结果矩阵 Z bf16[B, F]。为了计算这个 matmul,你得从内存里读取 2DF + 2BD 字节的数据,执行 2BDF 个 FLOPs(每次乘法+加法);然后把 Z 写回内存,要写 2BF 字节。如果我们假设 B(batch size)远小于 D 和 F,也就是 token 数相对 embedding size 和头数小得多(这在 Transformer 中很常见),那么算术强度大约就是:$\frac{ 2BDF}{2DF + 2BD + 2BF} \approx B$,换句话说,这个强度跟 batch size 成正比。在 TPU v5e 上,如果你用的是 bfloat16 类型,那么只要你的 batch size 超过 240 个 token,你就可以做到 compute-bound。当然,这些都是单卡内的 memory-bandwidth 限制,也就是看显存带宽能不能喂饱计算。但这其实只是最简单的一类 roofline,现实中我们更常遇到的瓶颈是多卡之间的通信带宽,尤其是在多个 TPU/GPU 上做分布式矩阵乘法时。
GPU硬件的算力和显存带宽是固定的,因此硬件的算力-带宽比(roofline)也是固定的。因此,当模型的“算术强度”高于硬件的“roofline”时,那么硬件的算力成为瓶颈,反之则内存带宽成为瓶颈。
后训练流水线
假设训练数据里有个问题:”为什么天空看起来是蓝色的?”不管用 PPO 还是 GRPO,一轮训练都会经过这几步:
- Actor 生成回答。 它就是正在训练的语言模型
- Reward Model 给回答打分。 分数越高,说明回答越符合人类偏好;可验证任务(如数学题)也可以直接用规则验证器打分。
- Reference Model 提供参照。 它是训练前冻结的模型副本,用来计算 KL 惩罚,避免 Actor 一次更新得太远。
- Critic 估计优势。 PPO 需要它来估计”这个回答比预期好多少”;GRPO 省掉了 Critic,改用同组回答的相对分数。
- 训练进程更新 Actor。 算出梯度、更新参数后,新权重交给下一轮生成使用。
模型只有几亿参数时,这五个角色可以在同一台机器上依次运行——生成完了打分,打完分更新,更新完再生成,慢是慢点,但能跑通。一旦模型和数据规模增大,问题首先出在执行方式上:多个模型无法同时装进有限显存;生成回答通常比一次参数更新慢得多;训练得到的新参数还要及时同步回生成进程。这三个环节只要有一步等待过久,其他 GPU 就会闲置。训练框架的作用,就是安排这些角色在什么设备上运行、何时交换数据、何时同步新参数。 它没有改变 PPO、GRPO 或 奖励模型的数学定义,只是让同一条训练流程能够稳定地跑在多张卡和多台机器上。框架、奖励、成本和系统设计,都在解释这四步(生成回答 → 计算奖励 → 更新模型 → 同步新参数)怎样扩展到更大的模型与集群。
框架的几个取舍
- 同步 vs 异步。长任务为什么需要异步训练 数学题的回答长度通常比较接近。一批题目开始生成后,往往能在相近时间结束。代码仓库和浏览器任务则不同:有的任务第一次测试就通过,有的任务需要反复读取文件、调用工具和等待外部环境。同一批任务可能相差几分钟甚至更久。同步训练必须等最慢的任务结束,才能把整批轨迹交给训练进程。异步训练会把已经完成的轨迹先放进队列,生成进程继续处理新任务,训练进程则持续从队列取数据。这样可以减少 GPU 等待,但会带来一个新问题:某条轨迹生成时使用的是旧版 Actor,等它进入训练时,Actor 可能已经更新了几轮。
- 同步训练简单、可控、容易调试,但 GPU 利用率低。
- 异步训练吞吐翻倍,但训练数据可能基于旧权重生成,需要额外的算法补偿。AReaL 的研究表明,异步训练可以在不损失效果的前提下将速度提升近 3 倍——但前提是训练已经调通。
- 框架最初为单轮 RL(推理任务)设计,还是一开始就考虑了多轮 Agent 交互。前者的 Agent 执行模块是后加的,能用但不是为此优化;后者的 Agent 执行是架构一等公民,在状态管理、异构轨迹长度、工具调用异步返回等方面有原生支持。
后训练流水线的几条共同规律:
- 奖励从偏好到验证:从主观分走向客观分、再走向环境分。 这是一个清晰的三段式演变,每一步都在解决前一阶段奖励信号不稳定的问题。
- 早期 RLHF 时代(InstructGPT、Llama 2)的奖励本质是”主观偏好分”。标注员面对的问题通常是”回答 A 和回答 B 哪一个更有帮助”,需要根据个人偏好、语言习惯和对 helpfulness 的主观理解做出判断。这种奖励的问题是不一致性:不同标注员标准不同,同一标注员在不同时间判断也会波动,甚至”语气更流畅”的回答可能事实完全错误。
- R1/Qwen/Seed/Mistral 这一代团队把奖励推进到”规则验证分”阶段。DeepSeek-R1 证明了一个关键结论:纯规则 reward 就足以诱发长思考能力。
- 在 MiniMax/Kimi/LongCat 这一代团队把奖励进一步推进到”真实环境执行分”。奖励不再只看最终答案对错,还要看中间过程和环境状态的真实迁移:MiniMax M2.1 的 SWE-Resolve 不仅检查新增测试是否通过,还要检查原有测试是否被破坏、是否修改了无关文件、有没有硬编码绕过验证器;Kimi-Researcher 的 reward 要看引用是否真实存在、证据是否真的支持结论、来源是否覆盖了关键角度;LongCat 甚至把工具超时、报错、返回缺字段这些环境扰动本身也纳入 reward 设计。reward 越接近真实世界的成功标准,长训练就越稳定,模型能力的迁移性就越强。
- 数据从静态样本到可交互环境:从问答对到可回放的完整 episode。
- 早期 SFT 时代的数据是静态的”人工写好的问答对”。一条样本就是一个 JSON 对象:{”prompt”: ”用户问题”, ”response”: ”标准答案”},长度通常几百到几千 token,训练时模型只需要”看到问题,背诵答案”。这种数据构造简单、存储方便,但只能覆盖团队已经想到的情况——模型记住了固定问答模式,遇到训练集之外的新问题就不会了。
- R1 之后,数学和代码任务的数据进入”verifier + 拒绝采样”阶段。数据不再是一次性写死的问答对,而是可以反复采样、自动筛选的动态产物:对同一个 prompt 让模型生成 8-16 个候选回答,用规则 verifier 自动判分,留下正确的轨迹做 SFT 或RL,错误的丢弃或分析原因补数据。
- 现在 Agent 任务的数据已经变成了”持久化环境、可回放、可组合的 scaffold”。一个代码修复样本不再是 500B 的 issue 文本加 2KB 的 patch,而是几十 MB 的 Docker 镜像快照、仓库初始状态、依赖锁定版本、完整的工具调用序列、每一步的环境观察和中间 reward;一个网页 agent 样本不再是问题和答案,而是浏览器初始 DOM 状态、每一帧截图、点击输入序列、页面状态变化和最终任务成功判定。Kimi K3 更进一步,把 scaffold(执行框架、工具配置、上下文管理方式)本身也变成可组合的变量,同一个任务可以用不同的 harness 运行,避免模型过拟合单一格式。GitHub PR、Docker、Playwright、数据库快照、工具图谱、搜索网页历史——所有这些都变成了后训练数据的一部分,数据工程的复杂度已经远远超出了”整理 JSONL”的范畴。
- 训练从两步到多阶段:从两步走到多阶段精细化拆分。 后训练不是一个”做完就发布”的一次性流程,而是多个阶段顺序衔接、每个阶段有明确目标的流水线,阶段划分越来越细是近两年最明显的趋势之一。早期后训练很简单,就是”SFT → RL”两步走:先拿一批高质量数据做监督微调让模型学会基本格式,再用 RLHF 或 PPO做偏好对齐就发布。InstructGPT、Llama 1/2 以及 2023 年的大部分开源模型都是这个路线。这种两阶段方案的问题是目标混杂:SFT 既要教格式、又要教知识、还要教风格;RL 既要提升能力、又要对齐偏好、还要保证安全——多个目标混在一起训练,很容易互相挤压,结果往往是”聊天更流畅了,但数学和代码能力掉了”。Qwen/DeepSeek/GLM 这一代把训练拆成了更清晰的三到四阶段。阶段拆分越来越细的本质原因是:不同能力需要不同的数据、不同的 reward、不同的训练火候,混在一起训谁都训不好——像教学生一样,先学基础概念,再专项突破,最后综合模拟,这个顺序反了就事倍功半。
- 训练系统本身成为竞争力:从改公式到做工程。 早期 reasoning RL 大家主要在改算法公式:从 PPO 到 GRPO,怎么算 advantage、怎么加 KL 约束、怎么设计 reward 公式——这些是论文里能看到的部分。大规模长 CoT RL开始触及工程细节,到了 Agent RL 阶段,工程系统的差异进一步拉大。闭源团队在这些系统工程上投入的人力往往几倍于算法工程师。能训多长的轨迹、能不能支持千步以上稳定训练、多卡扩展效率是 50% 还是 90%、rollout 崩溃了能不能自动恢复、环境不一致能不能自动检测——这些工程细节决定了你的系统能不能训长任务、能不能扩到大规模、能不能在多轮迭代中保持稳定。异步 rollout、PD 解耦、KV-cache 交换、环境调度、失败恢复、reward 服务化、可执行 verifier——这些都是”后训练实践”本身的一部分,而不是外围工程。很多时候不是算法不对,而是你的系统根本撑不到算法起效的时候。
数据工程
单机实验时,训练数据可以是一段脚本、一个 JSONL 文件或一份数据集。到工业 RL 训练中,一条轨迹从生成到参与更新再到回流,要经过任务生成、环境执行、样本评分、存储与再加工多个环节。多机训练每小时可以生成数十万条轨迹,任何一个环节处理不好,要么数据大量损失,要么错误轨迹反复进入训练。
- 单一数据来源不够用,早期 RL 训练常使用公开的数学、代码或推理数据集启动。这些数据质量稳定,但题型分布固定,模型学会这些模式以后提升会停滞。真实使用中的失败样本包含新的工具组合、长链路推理或边界情况,是题库难以覆盖的。
- 难度控制和数据调度。任务池规模扩大以后,不是所有题目都应该被模型以相同频率看到。太简单的题无法驱动更新,太难的题会持续失败并导致高方差梯度。PS:能力边界附近优先;失败案例加强;早期侧重基础能力,训练中后期加入长程、多工具和多约束任务;安全任务持续注入,防止能力提升时边界被遗忘。
- 任务格式与提示构造。同一条任务可以用多种提示形式呈现。数学题既可以直接问结论,也可以要求展示推理步骤;代码题既可以提供部分代码,也可以给出自然语言描述;工具任务既可以给出明确步骤,也可以只描述目标。
- 可执行环境的构建:工具调用、代码执行和浏览器任务需要可运行的环境,而不只是静态 prompt。 任务生产的目标不是简单扩大数据量,而是保证数据持续分布在当前模型的能力边界、真实失败场景和安全边界上。
数据保存:一次 rollout 需要记录什么?最小训练记录不足以排错:很多 PPO 或 GRPO 实现只保存 prompt、response、reward 和 log-probability,足够计算一次梯度,但不足以排查”为什么奖励突然下降””为什么某类回答变多”或”为什么某个验证器突然大量报错”。工业训练通常把一条轨迹分成几个层级保存:
- 任务层:prompt、任务类型、难度标签、来源、环境配置、提示模板版本。
- 轨迹层:模型的完整 response、每一步 token、每一步 log-probability、生成时间、温度、采样参数和模型版本。
- 交互层:工具调用请求、工具返回结果、中间代码执行输出、浏览器操作记录和错误信息。
- 奖励层:每个奖励函数返回的值、权重、聚合方式、格式化检查结果、单元测试通过数量和最终总分。
- 系统层:rollout worker 编号、时间戳、模型 checkpoint 标识、环境镜像版本和随机种子。 一个月的多机 RL 训练很容易产生数 TB 到数十 TB 的轨迹数据。保存时通常考虑:列式与分层存储;压缩与去重; Checkpoint 绑定;失败样本标记;可复现元数据。记录不是越多越好:每多存一个字段都会增加存储、清洗和查询成本。优先保存会影响梯度计算、奖励复现和故障排查的字段;对调试价值低且体积大的中间张量,可以只保留聚合统计或采样保存。
质量控制:在数据进入训练前拦截错误。奖励正确不等于轨迹可学习:一条回答拿到高分,可能是验证器漏洞、答案泄漏、格式绕过或环境异常造成的;一条回答拿到低分,也可能是奖励函数报错、测试用例有误或工具超时。若不加过滤,错误信号会直接进入梯度。常见数据质量检查包括:格式校验;奖励合理性校验;可执行性校验;重复与泄漏检测;异常轨迹过滤;环境一致性检查。
数据回流:从 RL 轨迹到下一轮训练数据。RL 数据不会只用一次:大规模训练通常不是”一个数据集跑一次 PPO”就结束。RL 阶段产生的回答、偏好和失败样本会回流到SFT、奖励模型和下一轮 RL 任务池中。
- 回到 SFT,通过规则验证、测试通过或人工确认的高质量回答,可以整理成监督微调数据。SFT 回流通常用于稳定格式、恢复被 RL 破坏的通用能力,以及把新学会的工具行为固化为更稳定的模型初始点。
- 回到奖励模型。RL rollout 天然产生同一问题的多个回答,适合训练奖励模型或裁判模型。
- 回到任务池。失败轨迹会反向扩展任务池。
实践
如果把上面的实践抽象成一个可复现的小型项目,可以按下面的顺序做:
- 先选一个可验证任务族。 数学题最简单,代码题次之,网页/GUI/研究 agent 最难。开放聊天任务的 reward 较难定义,不适合作为第一版 RL 实验。这一步容易踩的坑是一开始选开放聊天导致 reward 模糊,先从数学/代码这种规则验证题切入最快。
- 把任务封装成环境。 数学环境需要 answer parser 和 verifier;代码环境需要仓库 checkout、依赖安装、测试命令和 patch检查;网页环境需要浏览器、状态记录和证据抽取;GUI 环境需要截图、动作空间和可复位 sandbox。这一步容易踩的坑是环境依赖没锁定版本,同一份代码今天能跑明天跑不通,reward 全是噪声。
- 先做 SFT 冷启动。 收集或生成成功轨迹,让模型学会输出格式、工具协议、思考结构和停止条件。没有冷启动就直接RL,容易先在格式和工具调用上乱掉。这一步容易踩的坑是 SFT 数据太杂、格式不统一,模型学完后工具调用格式五花八门,RL 前几万步全在修格式。
- 再做采样和筛选。 对每个 prompt 采样多个输出,用 verifier / judge / reward model 选出正确、简洁、过程合理的轨迹。这个阶段就是 Qwen、DeepSeek、Kimi、MiniMax 都在反复做的 rejection sampling / self-improvement。这一步容易踩的坑是只留最终答案正确的轨迹,把前面对、最后一步错的高价值片段全部丢掉,样本效率极低。
- 最后做 RL。 简单题可以用 GRPO / DAPO 风格的组内相对优势;需要 value model 的任务可以参考 PPO / VAPO;长程agent 要额外处理异步 rollout、失败恢复、工具噪声和 token-level credit assignment。这一步容易踩的坑是一上来就把长度上限拉满、lr 调太大,训练前几百步 reward 就崩掉或 entropy 快速塌缩。
- 训练后做能力回填。 用通用指令、安全、短回答、风格和非思考模式数据再对齐一次,防止模型被 reasoning RL 训练得又长又慢。这一步容易踩的坑是回填数据太多、学习率没降,把前面 reasoning RL 好不容易训出来的长链推理能力直接冲掉。
一个很小但完整的练习可以是:用 5K 数学题或 1K 代码修复题做 SFT,采样 8 个候选,用规则 verifier 过滤,再用 GRPO训练一轮,最后评估正确率、平均输出长度、格式错误率和通用聊天退化。工业后训练的核心是闭环:定义目标、构造数据与环境、SFT 冷启动、RL 提升、独立评测、失败回流,六个阶段首尾相 接,形成持续迭代的循环。线性流程的问题在于,第一轮数据永远只覆盖团队已经想到的情况。模型进入真实场景后暴露的边界失败,包括沙箱不一致、验证器漏洞、不会用的工具,只能由独立评测按原因分类,送回对应环节:该补数据的补数据、该修环境的修环境、该升级验证器的升级验证器。模型能力正是在一轮一轮的修正中建立起来的。多跑几轮只是表象,闭环真正建立的是从失败中持续学习的能力。
当任务从数学题扩展到代码修复、网页操作这类需要和外部环境交互的任务时,六阶段的每一步都需要相应的扩展。具体来说,数学 RL 的环境只需要题目文本和 20 行答案验证器,不需要启动外部进程,也不需要持久化中间状态。但代码修复沙箱要带 200MB 依赖、十几个启动命令和完整 git history,光拉起环境就要一分钟。这种差异体现在三个维度。episode 长度从百 token 级扩展到万 token 级,reward 延迟从输出完立即判分变成几十步交互后才知道结果,rollout 失败从格式错扩展到沙箱崩溃、依赖装不上、工具超时。训练跑到中途,奖励曲线还在上升,独立评测的分数却停在原地;另一条曲线上,熵在快速下滑。这些现象分别指向哪一层的问题?
留下评论