跳到主要内容
SUN / 笔记
← 全部文章

后训练与AgentRL

后训练(post-training)与AgentRL技术解析

闭源大模型不给Logprob,怎么做OPD:Teacher信号获取、替代蒸馏目标与工程实现

OPD(On-Policy Distillation)的核心并不是“必须拿到Teacher的完整Logprob”,而是Student自己先进行On-Policy Rollout,然后让Teacher对Student产生的轨迹提供额外的监督信号,再把这个Teacher信号蒸馏回Student;因此当Teacher是闭源API模型、只返回文本而不给Token级Logprob时,传统的“Token-level Forward KL”确实无法直接计算,但OPD并没有因此失效,可以把Teacher从“概率分布提供者”降级成“轨迹/答案/偏好/局部决策提供者”,然后通过Sequence-level Distillation、Preference Distillation、Verifier/RM打分、Candidate Ranking、Teacher Sampling、LLM-as-a-Judge等方式构造训练信号;如果Teacher API甚至连Token概率、Hidden State都不给,那么最现实的工程方案通常是:Student On-Policy Rollout → Teacher评估/改写/选择 → 构造Preference或Reward → Student用DPO/GRPO/RFT等方式训练,严格来说这已经不是最原始的Token-level OPD,而是“黑盒Teacher下的On-Policy Distillation”。

plain-text
Student On-Policy Rollout → 闭源Teacher → 评分/选择/改写 → Distillation Target → Student Update

传统OPD最理想的情况是Teacher和Student对同一个Token位置都能提供Logprob,然后计算Teacher分布和Student分布之间的KL,例如Teacher生成了token y_t,Teacher能够返回 log p_T(y_t | x,y_<t),Student也能计算 log p_S(y_t | x,y_<t),此时可以做Forward KL或者只蒸馏Teacher在Student轨迹上的token概率;但闭源API通常只给你 text,类似OpenAI-compatible Chat Completion接口可能返回content、tool call、finish reason等信息,却不给完整vocabulary logits,所以你无法直接得到 p_T(vocab | context),因此下面这种标准OPD代码在黑盒Teacher上无法实现。

python
# 标准Token-level OPD需要Teacher的token级Logprob
teacher_logprobs = teacher.generate_logprobs(prompt, tokens)
student_logprobs = student.compute_logprobs(prompt, tokens)

# 两个概率分布对齐后才能计算Token-level KL
loss = kl_div(student_logprobs, teacher_logprobs)

这时候第一个最简单的方法是Sequence-level Distillation:让Student先On-Policy生成Trajectory,然后把整个Trajectory交给Teacher,让Teacher重新生成一个更高质量的Answer/Trajectory,把Teacher输出作为SFT Target训练Student;它保留了OPD最关键的一个思想——数据来自Student自己的状态分布,而不是Teacher离线生成的数据——但把Token-level Distribution Distillation变成了Sequence-level Target Distillation。

python
# Student先On-Policy生成自己的轨迹
student_traj = student.generate(task)

# 闭源Teacher只能返回文本,因此让Teacher生成改进版本
teacher_traj = teacher.generate(
    task=task,
    student_trajectory=student_traj
)

# Teacher结果作为Student的蒸馏Target
loss = sft_loss(student, teacher_traj)

# 更新Student
optimizer.step(loss)

这里最关键的地方是不要只让Teacher重新回答原问题,而应该把Student的Trajectory提供给Teacher;例如Agent任务中Student执行了“搜索 → 调用工具 → 计算 → 最终回答”,其中一步Tool Call错误了,Teacher看到完整Student轨迹后可以指出错误、重新规划或者生成正确Trajectory,这样Teacher监督的就是Student实际访问过的状态分布,而不是Teacher自己的On-Policy分布。

plain-text
Task + Student Trajectory
→ Teacher Critique / Correction
→ Corrected Trajectory
→ Student Distillation

例如销售Agent中Student生成了“客户预算100万 → 推荐产品A → 调用库存工具 → 发现库存不足 → 继续推荐A”,Teacher可以判断最后一步错误,并生成“库存不足 → 推荐产品B”的修正轨迹;Student学习的不是Teacher随便生成的一条答案,而是针对Student错误Trajectory的Correction。

python
# 黑盒Teacher用于纠正Student的错误Trajectory
student_traj = rollout_worker.run(task)

teacher_prompt = f"""
请检查下面Agent轨迹,只指出真正影响任务结果的错误,
并给出正确的下一步动作:
{student_traj}
"""

teacher_feedback = teacher.generate(teacher_prompt)

# 将Teacher反馈转换为训练Target
training_sample = {
    "prompt": task,
    "student_trajectory": student_traj,
    "teacher_feedback": teacher_feedback
}

第二种方法是Candidate Ranking,也就是让Student自己生成多个On-Policy候选,再让闭源Teacher负责排序;这比直接让Teacher生成完整答案更加接近RL/Preference Learning,因为Teacher不需要提供概率,只需要回答“哪个Trajectory更好”;例如Student针对一个Task生成4条Trajectory,然后Teacher比较4条轨迹,输出最佳Trajectory或者一个Ranking,随后构造Preference Pair (chosen, rejected),再用DPO/IPO/ORPO等Preference Optimization训练Student。

plain-text
Student:
Trajectory A
Trajectory B
Trajectory C
Trajectory D
        ↓
Black-box Teacher Ranking
        ↓
C > A > D > B
        ↓
Preference Dataset
        ↓
DPO / Preference Optimization
python
# Student生成多个On-Policy候选
candidates = [student.generate(task) for _ in range(4)]

# 黑盒Teacher只负责判断哪个Trajectory最好
ranking = teacher.rank(task, candidates)

# 将排序转换成Preference Pair
chosen = candidates[ranking[0]]
rejected = candidates[ranking[-1]]

preference_sample = {
    "prompt": task,
    "chosen": chosen,
    "rejected": rejected
}

# 后续使用DPO等Preference算法训练Student
loss = dpo_loss(student, preference_sample)

这种方式的优点是完全不需要Teacher Logprob,因为Teacher只是一个Judge;缺点是Teacher调用成本比较高,而且Teacher只能告诉你“整条Trajectory哪个好”,不能直接告诉Student每个Token应该提高还是降低概率,因此训练信号比Token-level OPD粗;如果Trajectory很长,还可能出现Credit Assignment问题,即Teacher说整个Trajectory不好,但到底是第5步Tool Call错了还是第30步Final Answer错了并不明确。

第三种方法是Step-level Teacher Critique,这通常更加适合Agentic RL;不要让Teacher只评价最终Trajectory,而是让Student执行一步,就把当前状态和Action发送给Teacher,让Teacher判断“这个Action是否正确、是否合理、下一步应该做什么”,这样就能把Sequence-level黑盒监督变成更细粒度的Step-level监督;例如Student当前状态是“客户要求查询订单A123”,Student却调用了库存查询工具,Teacher可以直接判断这个Action错误,而不是等到整个任务失败之后才给Reward。

python
# 对Student轨迹逐Step进行Teacher Critique
for step in student_traj.steps:
    feedback = teacher.evaluate_step(
        task=task,
        history=student_traj.history_before(step),
        action=step.action
    )

    # 将Teacher判断转换成Step Reward或监督标签
    step.reward = feedback.score

Teacher可以输出结构化结果,例如 score ∈ {0,1} 或 score ∈ [0,1],甚至输出 correct_action;然后把它变成Reward Signal,让Student使用GRPO/PPO训练,这时候整个系统已经变成Black-box Teacher → Reward/Verifier → AgentRL。

python
# Teacher作为黑盒Verifier,不需要Logprob
score = teacher.evaluate(
    task=task,
    trajectory=student_traj
)

# 将Teacher评价转成RL Reward
reward = {
    "task_success": score,
    "tool_correctness": tool_score,
    "final_answer": answer_score
}

# GRPO根据Reward计算Group Relative Advantage
advantages = normalize(group_rewards)

# Student继续进行Policy Update
loss = policy_loss(student, trajectories, advantages)

第四种方法是Teacher作为Reward Model的替代品;如果闭源Teacher能够稳定判断任务完成质量,可以让Teacher直接产生Scalar Reward,例如0~1或者0~100,然后Student通过GRPO/RFT进行优化;这在Agent任务上非常实用,因为最终目标往往并不是“模仿Teacher每个Token”,而是“让Student完成Task”,因此只要Teacher能够可靠评价Trajectory,就可以绕过Token-level Logprob。

plain-text
Student On-Policy Trajectory
→ Black-box Teacher Judge
→ Scalar Reward
→ GRPO / RFT
→ Student Policy
python
# Teacher直接作为黑盒Reward Function
trajectory = student.rollout(task)

reward = teacher.score(
    task=task,
    trajectory=trajectory
)

# GRPO使用多个Student Trajectory进行相对比较
group = rollout_group(task)
rewards = [teacher.score(task, traj) for traj in group]

advantages = normalize(rewards)

loss = grpo_loss(
    student,
    group,
    advantages
)

但是这里有一个非常重要的工程问题:Teacher Reward本身可能产生Reward Hacking;例如Teacher喜欢格式漂亮的答案,Student可能学会堆砌解释、增加无意义步骤,而不是真正提高任务完成率,所以Teacher Reward不能完全等价于Ground Truth;更稳妥的做法是把Teacher Reward拆成多个维度,例如Task Success、Tool Correctness、Final Answer Correctness、Efficiency、Safety等,再和确定性的程序Verifier结合。

python
# 不要只依赖一个Teacher总分
reward = (
    0.50 * task_success
    + 0.20 * tool_correctness
    + 0.20 * final_answer_correctness
    + 0.10 * efficiency
)

# 能程序验证的部分优先使用Deterministic Verifier
# Teacher主要负责无法规则化判断的部分

第五种方法是Teacher Sampling + Student Logprob,这个方案经常被误认为是“真正的OPD”,实际上它更接近Knowledge Distillation/SFT:Teacher生成一个或多个高质量答案,Student计算这些Teacher-generated tokens自己的Logprob,然后做MLE;Teacher虽然没有Logprob,但Student有,所以依然可以训练,只不过训练目标不是Teacher概率分布,而是Teacher采样出来的单条Sequence。

python
# Teacher不提供Logprob也没关系
teacher_answer = teacher.generate(task)

# Student可以计算Teacher答案的Logprob
student_logprob = student.compute_logprob(
    prompt=task,
    completion=teacher_answer
)

# 最大化Student对Teacher答案的概率
loss = -student_logprob.mean()

optimizer.zero_grad()
loss.backward()
optimizer.step()

这种方法的关键区别是:Teacher Logprob Distillation是在匹配一个Distribution,而Teacher Sampling Distillation是在学习Distribution中的Sample;如果Teacher输出的是高质量Sample,那么Student可以通过大量样本逼近Teacher的行为,但它无法知道Teacher对其他候选Token到底有多大概率,因此信息量明显少于完整Logprob Distillation。

plain-text
有Teacher Logprob:
Teacher Distribution → KL → Student

无Teacher Logprob:
Teacher Sample → MLE/SFT → Student

信息量:
Distribution > Sample

第六种方法是用多个Teacher Query近似局部Preference;如果闭源API支持“对同一个Prompt生成多个回答”,可以让Teacher对不同Candidate进行比较,甚至通过Prompt让Teacher输出每个Candidate的质量分数,然后构造一个近似的Preference Distribution;例如Student生成A/B/C/D四个候选,Teacher返回 [0.9,0.6,0.2,0.1],虽然这不是Teacher真实Token Probability,但可以作为Candidate-level Soft Label进行蒸馏。

python
# Teacher输出Candidate-level分数,而不是Token Logprob
scores = teacher.score_candidates(
    task=task,
    candidates=candidates
)

# 将分数归一化成Candidate-level软标签
weights = softmax(torch.tensor(scores) / temperature)

# 对Student候选计算对应概率
student_scores = torch.stack([
    student.sequence_logprob(task, c)
    for c in candidates
])

# 让Student的Candidate概率分布逼近Teacher评分分布
loss = -(weights * log_softmax(student_scores, dim=-1)).sum()

loss.backward()
optimizer.step()

这个方法非常适合Agent,因为Agent的Action空间本来就可以比较小,例如当前状态下可能只有“search_customer”“query_order”“query_inventory”“send_email”几个工具Action,此时不一定需要Teacher提供整个Vocabulary的Token Distribution,只需要Teacher判断哪个Action更合理;因此可以把Token-level OPD转化成Action-level Distillation。

plain-text
Token-level:
Teacher Vocabulary Distribution → Student Vocabulary Distribution

Agent-level:
Teacher Action Ranking → Student Action Probability

例如当前Agent状态下有4个候选Tool Call:

python
# Agent当前状态下生成候选Action
actions = [
    "query_customer",
    "query_order",
    "query_inventory",
    "send_email"
]

# 黑盒Teacher判断每个Action质量
scores = teacher.rank_actions(
    state=current_state,
    actions=actions
)

# Student只需要学习Action-level Preference
loss = action_preference_loss(
    student,
    current_state,
    actions,
    scores
)

如果进一步做到Tool Call级别的结构化Action Distillation,效果往往比让Teacher对完整自然语言Trajectory打分更稳定,因为Agent真正需要学习的是“什么时候调用什么工具、工具参数怎么填、什么时候停止”,而不是单纯模仿Teacher的语言风格;所以黑盒Teacher非常适合做Planner/Tool Policy的Teacher。

plain-text
Student State → Candidate Tool Actions → Black-box Teacher Ranking → Action Distillation → Student Policy

如果闭源Teacher同时不提供Logprob、Hidden State、Token-level Label,只提供最终文本,那么严格意义上的Forward-KL OPD基本做不了,因为Forward KL需要Teacher Distribution;但这并不意味着“OPD做不了”,而是需要把Distillation Target从Distribution改成Sample、Preference、Reward或者Action Ranking;因此工程上可以按照Teacher API能力分成几档:有Logprob → Token-level OPD;只有生成文本 → Sequence Distillation/SFT;能比较多个答案 → Preference Distillation/DPO;能稳定打分Trajectory → Reward Distillation/GRPO;能评价Step/Action → Step-level Agent Distillation;能生成Critique → Critique-based RFT/AgentRL。

plain-text
Teacher能力越强:
Logprob → Token-level OPD
Sample → Sequence Distillation
Ranking → Preference Distillation
Score → Reward Distillation
Step Critique → Step-level AgentRL
Action Ranking → Agent Policy Distillation

如果目标是AgentRL,更推荐“Student On-Policy Rollout + Black-box Teacher Verifier + GRPO”,而不是强行模拟Token-level OPD;Student首先自己生成Group Trajectories,然后把每条Trajectory交给闭源Teacher进行Task-level和Step-level评价,再结合确定性Verifier产生最终Reward,最后GRPO只利用Reward的相对关系更新Student,这样Teacher完全不需要Logprob,同时保留了AgentRL最重要的On-Policy属性。

plain-text
Task → Student Rollout N条Trajectory → Teacher/Verifier评分 → Group Reward → Advantage → GRPO Update → Student
python
# 1. Student进行On-Policy Group Rollout
trajectories = [student.rollout(task) for _ in range(group_size)]

# 2. 黑盒Teacher只负责评价轨迹,不需要Logprob
teacher_rewards = [teacher.score(task, traj) for traj in trajectories]

# 3. 程序Verifier提供确定性Reward,减少Teacher误判
verifier_rewards = [verifier.check(task, traj) for traj in trajectories]

# 4. 融合Reward
rewards = [0.7 * t + 0.3 * v for t, v in zip(teacher_rewards, verifier_rewards)]

# 5. GRPO使用Group Relative Advantage
mean_r = torch.tensor(rewards).mean()
std_r = torch.tensor(rewards).std()
advantages = (torch.tensor(rewards) - mean_r) / (std_r + 1e-8)

# 6. Student根据自身On-Policy Trajectory进行Policy Optimization
loss = grpo_loss(student, trajectories, advantages)

optimizer.zero_grad()
loss.backward()
optimizer.step()

这里还有一个非常关键的问题是Teacher和Student的Tokenizer不同怎么办;如果Teacher不给Logprob,那么这个问题反而简单很多,因为你根本不需要做Token-level Vocabulary Alignment,只需要把Teacher输出的文本作为Sequence Target,Student使用自己的Tokenizer重新Tokenize即可;如果Teacher输出的是Tool Call,也可以直接解析成结构化JSON,再映射到Student的Action Schema,因此黑盒Teacher实际上天然绕开了跨Tokenizer的Logprob Alignment问题。

plain-text
Teacher Tokenizer
      ↓
Teacher生成Text / Tool Call
      ↓
Text / Structured Action
      ↓
Student Tokenizer
      ↓
Student Logprob / Policy Loss

最终从工程选型角度可以这样判断:如果闭源Teacher不给Logprob,但给高质量文本,那么做Sequence-level Distillation;如果能让Teacher比较候选,优先Preference Distillation;如果能稳定评价AgentTrajectory,优先Teacher-as-Verifier + GRPO;如果能评价每一步Action,则进一步做Step-level/Action-level Distillation;只有当Teacher提供Token-level Logprob时,才值得实现严格意义上的Token-level OPD。所以不要把“没有Logprob”理解成“不能做OPD”,真正需要改变的是蒸馏信号的粒度:Token Distribution → Sequence Sample → Preference → Reward → Action;对于AgentRL来说,后面三种反而经常更加符合Agent的训练目标,因为Agent最终优化的是Task Success和Decision Policy,而不是机械地复制Teacher每一个Token的概率分布。


GRPO模型训练Reward很差怎么改进:从Reward设计、数据质量到Advantage和训练稳定性

GRPO(Group Relative Policy Optimization)训练时如果Reward长期很低,首先不要直接调学习率、batch size或者增加训练轮数,因为Reward差不一定是模型能力差,可能是Reward设计、采样、Validator、Advantage、数据分布或者训练/推理不一致导致的;GRPO本质上是针对同一个Prompt采样一组Response,然后根据组内Reward计算相对Advantage,所以真正需要排查的是 “模型有没有生成可区分的轨迹 → Reward有没有正确区分好坏 → Advantage有没有提供有效梯度 → PPO/GRPO更新有没有真的改变Policy”,尤其是AgentRL中Reward通常是多步Tool调用后的最终结果,Reward问题比普通数学RL更加复杂。

plain-text
GRPO Reward很差 → 先检查Reward是否正确 → 再检查采样是否有正样本 → 再检查Reward区分度 → 再检查Advantage → 再检查Loss/Gradient → 最后才调超参数

第一步先确认Reward函数本身有没有问题,这是最容易被忽略的地方;例如销售Agent任务要求“查询客户信息 → 查询库存 → 生成报价”,如果Tool调用成功但Reward Validator只检查最终文本中是否包含某个字段,那么Agent即使正确完成任务也可能得到0分;反过来,如果Validator只检查字符串,很可能Agent通过模板作弊得到高Reward,这就是典型的Reward Hacking。

python
def reward_fn(trajectory):
    # 检查任务是否真正完成
    task_score = check_task_success(trajectory)
    # 检查工具调用是否合法
    tool_score = check_tool_calls(trajectory)
    # 检查最终结果格式
    format_score = check_output_format(trajectory)
    # 加权得到最终Reward
    return 0.6 * task_score + 0.2 * tool_score + 0.2 * format_score

如果Reward很差,第一件事应该是拿人工可判断的几十条Trajectory直接跑Reward函数,看“明显正确的样本是不是高Reward、明显错误的样本是不是低Reward”,如果人工认为正确的Trajectory平均只有0.1 Reward,而错误Trajectory也有0.1,那么继续训练没有意义,因为GRPO收到的训练信号本身就是坏的。

python
# 离线验证Reward函数
for traj in validation_trajectories:
    reward = reward_fn(traj)
    print(traj.label, reward)

# 重点检查:
# 正确Trajectory是否明显高于错误Trajectory

第二步检查Reward分布,而不是只看平均Reward;GRPO需要同一个Prompt采样多个Response,然后通过组内Reward计算相对优势,如果一个Group里的8个样本全部得到0分,那么模型没有任何相对学习信号;如果8个样本全部得到1分,同样没有区分度,因此Reward Mean并不是唯一指标,必须同时看 Reward Mean / Std / Min / Max / Positive Rate / Zero Rate。

python
rewards = torch.tensor(group_rewards)

stats = {
    "mean": rewards.mean().item(),
    "std": rewards.std().item(),
    "min": rewards.min().item(),
    "max": rewards.max().item(),
    "positive_rate": (rewards > 0).float().mean().item(),
    "zero_rate": (rewards == 0).float().mean().item()
}

print(stats)

例如一个Group有8条Trajectory:

plain-text
[0, 0, 0, 0, 0, 0, 0, 0]

Mean = 0
Std = 0

→ 没有相对优势
→ GRPO基本没有有效学习信号

而:

plain-text
[0.0, 0.1, 0.2, 0.0, 0.8, 0.1, 0.0, 0.3]

Mean > 0
Std > 0

→ Group内部存在明显质量差异
→ GRPO可以产生有效Advantage

第三步检查GRPO的Advantage计算,GRPO一般不单独训练Value Model,而是利用Group内部Reward做标准化:

其中 μ_G 是同一个Prompt下Group Reward的均值,σ_G 是标准差;如果Group Reward完全一样,那么 A_i≈0,Policy几乎得不到有效更新。

python
def compute_advantage(rewards, eps=1e-8):
    # GRPO使用Group内部Reward计算相对优势
    mean = rewards.mean(dim=-1, keepdim=True)
    std = rewards.std(dim=-1, keepdim=True)
    # 标准化Reward得到Advantage
    advantage = (rewards - mean) / (std + eps)
    return advantage

因此Reward很差时一个非常重要的改进不是“让Reward变高”,而是“让同一Prompt下不同Trajectory之间产生足够的Reward区分度”;例如原来所有失败任务都是0,可以改成分层Reward:Tool参数正确0.2、Tool调用成功0.4、核心任务完成0.8、最终答案完全正确1.0,这样模型即使还没有学会完整任务,也能获得渐进式学习信号。

python
def shaped_reward(traj):
    reward = 0.0

    # Tool选择正确
    if traj.tool_correct:
        reward += 0.2

    # Tool参数正确
    if traj.args_correct:
        reward += 0.2

    # Tool执行成功
    if traj.tool_success:
        reward += 0.2

    # 最终任务完成
    if traj.task_success:
        reward += 0.4

    return reward

但是Reward Shaping不能无限增加规则,否则容易让模型优化“中间分数”而不是最终任务;因此AgentRL中比较推荐 Sparse Task Reward + 少量Process Reward,最终任务是否成功仍然占最大权重,Tool调用和格式等只能作为辅助信号。

plain-text
最终任务完成:0.7
关键Tool调用正确:0.15
Tool参数正确:0.10
输出格式正确:0.05

核心原则:最终Task Success > Process Reward

第四步检查采样温度和模型当前能力;如果Student模型本身几乎不会做任务,那么无论采样8条还是64条,可能全部是错误答案,这时候GRPO没有正样本可以学习;这属于典型的 Exploration不足 / Cold Start问题,解决办法通常不是继续硬训GRPO,而是先用SFT、RFT或者Teacher Distillation把模型推到一个“至少偶尔能够成功”的区域。

python
# 如果一个Prompt采样N次全部失败
success_rate = successful_samples / total_samples

if success_rate < 0.01:
    # 模型几乎没有探索到正样本
    # 优先进行SFT/RFT/Distillation冷启动
    use_cold_start_data = True

例如一个复杂Agent任务,模型初始成功率只有0.1%,采样8次得到正样本的概率约为:

plain-text
P(至少一个成功) = 1 - (1 - 0.001)^8 ≈ 0.8%

这意味着绝大多数Group都没有正样本,GRPO基本学不到东西;如果先通过SFT把成功率提高到10%,同样采样8次至少出现一次成功Trajectory的概率就会明显提高,因此SFT不是和GRPO竞争,而是GRPO的Cold Start手段。

python
# SFT冷启动
for batch in sft_dataloader:
    outputs = model(
        input_ids=batch.input_ids,
        labels=batch.labels
    )

    loss = outputs.loss

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# SFT之后再进入GRPO
grpo_model = model

第五步检查Group Size是否合适;GRPO需要同Prompt多采样,如果 group_size=2,Reward估计容易非常不稳定;如果 group_size=8/16,通常更容易获得正负样本差异,但代价是Rollout计算量线性增加;因此如果Reward很差,可以适当提高Group Size,但前提是模型已经具备一定成功率。

python
# GRPO Group Sampling
group_size = 8

responses = []
for _ in range(group_size):
    # 同一个Prompt采样不同Trajectory
    responses.append(
        rollout_worker.generate(prompt)
    )

rewards = [
    reward_fn(x)
    for x in responses
]

第六步检查Reward是否过于稀疏;Agent任务经常出现“最终任务成功才Reward=1,否则Reward=0”的情况,例如销售Agent只有完整生成报价单才得1分,那么模型前99%的Trajectory可能都是0,这会让GRPO非常难训练;可以使用阶段性Reward,例如“正确选择Tool”“正确填写参数”“成功拿到库存信息”“最终报价正确”分别提供弱信号。

python
def agent_reward(traj):
    # Tool选择
    r_tool = 1.0 if traj.correct_tool else 0.0
    # 参数质量
    r_args = traj.argument_score
    # Tool结果
    r_result = 1.0 if traj.tool_success else 0.0
    # 最终任务
    r_final = 1.0 if traj.task_success else 0.0

    return 0.1 * r_tool + 0.2 * r_args + 0.2 * r_result + 0.5 * r_final

第七步检查Reward尺度;如果不同Reward Component数量级差异非常大,例如Task Reward是1,而Token Penalty是-100,那么模型很可能为了减少Token数直接提前结束;因此Reward最好进行明确的Scale设计,并监控各项Reward在训练中的贡献。

python
reward = (
    0.7 * task_reward
    + 0.2 * tool_reward
    + 0.1 * format_reward
)

# 防止Reward异常爆炸
reward = max(
    min(reward, 1.0),
    -1.0
)

第八步检查Reward Hacking;如果Reward越来越高但人工评估越来越差,很可能模型已经找到了Validator漏洞,例如销售Agent要求查询库存后报价,模型发现Validator只检查最终文本里是否出现“库存充足”,于是直接输出这句话而不调用库存Tool,这种情况下应该增加Tool Trace Validator、状态验证或者环境真实结果验证。

python
def validate_sales_task(traj):
    # 不能只检查最终文本
    if "库存充足" in traj.final_answer:
        # 必须确认真实调用过库存Tool
        if not traj.called("check_inventory"):
            return 0.0

        # 必须检查Tool返回结果
        if not traj.inventory_result:
            return 0.0

    return check_final_answer(traj)

对于AgentRL来说,Reward最好尽可能依赖Environment State,而不是依赖最终文本,例如销售Agent的正确性应该通过订单数据库、库存系统、CRM状态和Tool调用记录验证,而不是让LLM Judge单独判断,这样Reward更加客观。

python
def environment_reward(traj, env):
    # 获取真实环境状态
    order = env.get_order(traj.order_id)
    inventory = env.get_inventory(traj.product_id)

    # 根据真实状态计算Reward
    return verify_task(
        order=order,
        inventory=inventory,
        answer=traj.final_answer
    )

第九步检查GRPO Loss有没有真正产生有效梯度;Reward看起来正常,但模型Reward仍然不提升,也可能是Advantage、Action Mask、Log Probability计算或者Gradient Update存在Bug;尤其Agent轨迹包含大量User、Tool、System Token时,不能把所有Token都当成RL Action,通常只对模型生成部分计算Policy Loss。

python
# 只对Agent生成Token计算Policy Loss
action_mask = trajectory.generated_token_mask

logp = get_log_probs(
    logits,
    input_ids
)

# Tool结果、User Prompt等位置不参与RL更新
policy_loss = -(
    logp * advantages * action_mask
).sum() / action_mask.sum()

第十步检查KL约束;GRPO更新过激时,模型可能快速偏离初始Policy,导致语言能力和Tool调用能力下降,Reward反而越来越差;因此通常需要加入Reference Model KL Penalty或者限制Policy Ratio。

python
# Student当前Policy
logp_new = policy.log_prob(actions)

# Reference Policy
with torch.no_grad():
    logp_ref = reference.log_prob(actions)

# KL近似项
kl = logp_new - logp_ref

# 加入GRPO Loss
loss = policy_loss + beta * kl.mean()

如果发现训练初期Reward正常,训练几百Step后突然崩掉,很像是 Policy Update过大 / KL爆炸 / Learning Rate过高;这时应该重点观察 Reward、KL、Entropy、Clip Fraction、Grad Norm,而不是只看Loss。

python
metrics = {
    "reward": reward.mean().item(),
    "kl": kl.mean().item(),
    "entropy": entropy.mean().item(),
    "clip_frac": clip_fraction.item(),
    "grad_norm": grad_norm.item()
}

logger.log(metrics)

第十一步检查AgentRL中特有的Tool调用问题;例如模型生成了错误Tool Name、JSON参数非法、参数类型错误、Tool Timeout、Tool返回为空、Tool结果没有正确写回Context,这些都会让最终Reward很低,但问题实际上不在Policy Loss,而在Agent Runtime。

python
try:
    # 执行Agent Tool
    result = run_tool(
        tool_name,
        arguments
    )

except TimeoutError:
    # Tool超时
    reward = -0.1

except ValueError:
    # 参数错误
    reward = -0.2

except Exception:
    # Tool执行异常
    reward = -0.3

因此AgentRL训练中最好把Reward拆成 Task Reward + Tool Reward + Runtime Reward + Safety Reward,这样才能知道Reward差到底是模型不会做,还是Tool执行失败。

python
reward = {
    "task": task_reward,
    "tool": tool_reward,
    "runtime": runtime_reward,
    "safety": safety_reward
}

total_reward = (
    0.6 * reward["task"]
    + 0.2 * reward["tool"]
    + 0.1 * reward["runtime"]
    + 0.1 * reward["safety"]
)

第十二步检查训练和Rollout是否一致;如果训练使用一个Tokenizer、Chat Template、Tool Schema,而Rollout使用另一套配置,那么模型训练出来的Policy和实际采样Policy可能不一致,导致Reward长期不提升;这就是AgentRL中常说的 Train-Inference Mismatch。

python
# 训练和Rollout必须保持一致
assert train_tokenizer.name == rollout_tokenizer.name
assert train_chat_template == rollout_chat_template
assert train_tool_schema == rollout_tool_schema
assert train_sampling_config == rollout_sampling_config

特别是vLLM Rollout + PyTorch Trainer架构中,需要关注模型Checkpoint同步,如果Rollout Worker一直使用旧模型,而Trainer已经更新了几十个Step,那么采样数据实际上来自Old Policy,GRPO训练会逐渐失去On-Policy特性。

python
# Trainer保存最新模型
trainer.save_checkpoint(
    checkpoint_dir
)

# Rollout Worker同步最新Policy
rollout_worker.reload(
    checkpoint_dir
)

# 确保Rollout使用新Policy继续采样
trajectory = rollout_worker.generate(
    task
)

如果Reward低但Reward Std很高,说明模型其实已经在探索不同质量的答案,这种情况通常是好现象,不应该急着修改Reward;如果Reward低且Std接近0,说明Group内部没有区分度,应该优先解决Cold Start、Reward稀疏或Sampling问题;如果Reward不断升高但人工质量下降,重点检查Reward Hacking;如果Reward先升后崩,重点检查KL、Learning Rate和Policy Drift;如果Reward完全不动但Loss在变化,重点检查Reward → Advantage → Loss → Gradient这条链路是否正确。

plain-text
Reward低 + Std≈0 → 没有学习信号 → Cold Start / Reward Shaping / Sampling
Reward低 + Std较高 → 有探索但成功率低 → 提升正样本比例 / SFT / RFT
Reward升高 + 人工质量下降 → Reward Hacking → 加强Validator
Reward先升后崩 → Policy Drift → KL / LR / Gradient检查
Reward不动 + Loss变化 → Training Pipeline Bug → 查Advantage/Mask/Gradient
Tool Reward低 → Agent Runtime问题 → 查Tool Schema/参数/超时

如果是AgentRL场景,排查顺序应该是:先固定100个Prompt,每个Prompt采样8~16条Trajectory,离线记录 Reward、Reward Std、成功率、Tool成功率、Trajectory Length、Advantage、KL、Entropy、Clip Ratio;然后人工抽查高Reward和低Reward各50条,确认Reward与真实任务质量是否一致;如果Reward本身没问题,再看Group中是否存在正负样本;如果没有,先SFT/RFT冷启动;如果有,再检查GRPO Advantage和Loss;最后才调整Learning Rate、Group Size、Temperature等超参数。

python
# GRPO训练前建议固定一套Debug Evaluation Set
for task in eval_tasks:
    trajectories = [rollout(task) for _ in range(8)]
    rewards = [reward_fn(t) for t in trajectories]
    advantages = compute_advantage(torch.tensor(rewards))
    print({"reward_mean": torch.tensor(rewards).mean().item(), "reward_std": torch.tensor(rewards).std().item(), "success_rate": sum(r > 0 for r in rewards) / len(rewards), "advantage_std": advantages.std().item()})

不要先调GRPO超参数,而是先确认Reward是对的,再确认Group里有正负样本,再确认Advantage有区分度,再确认Policy Loss真的更新了正确的Action,最后再解决KL、学习率、采样温度和Group Size;如果模型连正样本都采不到,GRPO不是最合适的第一步,应该先通过SFT/RFT/Teacher Distillation把模型推到“能够偶尔成功”的区域,再用GRPO放大成功行为。


OPD时Teacher和Student词表不同怎么跨Tokenizer蒸馏:从Token对齐、Logit映射到Loss实现

OPD(On-Policy Distillation)中Teacher和Student使用不同Tokenizer时,不能直接做Teacher Logits和Student Logits的KL散度,因为两边的Vocabulary空间不同,例如Teacher可能使用32K词表、Student使用151K词表,同一个字符串可能被Teacher切成1个Token,而Student切成3个Token,所以 teacher_logits[vocab_teacher] 和 student_logits[vocab_student] 根本不在同一个概率空间;跨Tokenizer OPD真正要解决的是 “如何把Teacher在自己Token空间中的分布,转换成Student当前Token空间对应的条件概率分布”,常见方案有三类:String/Byte对齐、Sequence-Level Distillation、Vocabulary Projection,其中工程上最通用的是 Student负责Rollout,使用Student Tokenizer生成轨迹,再让Teacher通过自己的Tokenizer重新计算同一段文本的概率,然后按照Student Token Boundary把Teacher概率重新聚合。

python
# Teacher和Student使用不同Tokenizer
teacher_tokens = teacher_tokenizer.encode(text)
student_tokens = student_tokenizer.encode(text)

# 两边Token数量可能完全不同
print(teacher_tokens)
print(student_tokens)

例如文本是 I love machine learning,Teacher可能切成 ["I", " love", " machine", " learning"],Student可能切成 ["I", " love", " machine", " learning"],这种情况比较简单;但也可能Teacher切成 ["machine", " learning"],Student切成 ["machine", " learn", "ing"],此时Student在生成 ing 时,Teacher并没有一个完全对应的 ing Token,因此不能简单做 KL(teacher_logits, student_logits)。

plain-text
Teacher Tokenization:["machine", " learning"]
Student Tokenization:["machine", " learn", "ing"]

Teacher和Student没有一一对应Token,因此不能直接比较两个Vocabulary上的Logits。

最核心的办法是把Tokenizer差异转换成字符串/字节边界差异,因为不同Tokenizer最终都要把Token映射成相同的文本或者UTF-8 Byte Sequence,所以可以建立 Token → Text Span / Byte Span 映射;Student生成一个Token后,确定这个Token对应的字符串区间,然后让Teacher计算生成这个字符串所对应的多个Teacher Token的联合概率,这样就能得到“Teacher对Student这个Token的概率”。

python
# Student Token对应文本
student_token = "ing"

# Teacher可能把同一段文本切成多个Token
teacher_tokens = ["i", "ng"]

# Teacher对整个字符串的概率
# P("ing") = P("i") * P("ng" | "i")
teacher_prob = p_teacher["i"] * p_teacher["ng"]

更准确地说,如果Student当前Token为 s_i,对应文本字符串为 x_i,那么Teacher需要计算的是 P_T(x_i | prefix),如果Teacher Tokenizer将 x_i 切成 t_1,t_2,...,t_k,则Teacher概率可以通过链式法则计算。

plain-text
Student Token:s_i = "ing"
Teacher Tokens:t1 = "i",t2 = "ng"
Teacher Probability:P_T("ing" | prefix) = P_T("i" | prefix) × P_T("ng" | prefix + "i")

这就是跨Tokenizer OPD最重要的思想:不要强行让两个Tokenizer的Token ID一一对应,而是把Teacher和Student都投影到同一个Text/Byte空间,再比较相同文本事件的概率。

python
def teacher_logprob_for_student_token(teacher, teacher_tokenizer, prefix, student_token):
    # Student Token先转换成真实文本
    text = student_token
    # Teacher重新Tokenize这段文本
    teacher_ids = teacher_tokenizer.encode(text, add_special_tokens=False)
    logprob = 0.0
    # Teacher逐Token计算条件概率
    current_prefix = prefix
    for token_id in teacher_ids:
        logits = teacher(current_prefix).logits[:, -1]
        log_probs = torch.log_softmax(logits, dim=-1)
        # 累加Teacher对该Token的Log Probability
        logprob += log_probs[token_id]
        # Prefix向前推进
        current_prefix = current_prefix + text
    return logprob

不过上面的代码是概念实现,真实系统不能简单地每个Student Token都重新跑一次Teacher Forward,否则Teacher计算量会非常大;更合理的方法是 Teacher一次Forward得到整段序列的Logits,然后根据Teacher Token边界计算每个Student Token对应的Teacher Log Probability,这样可以避免大量重复Forward。

python
# 一次Teacher Forward计算整条Student Trajectory
teacher_outputs = teacher(input_ids=teacher_ids)
teacher_log_probs = torch.log_softmax(teacher_outputs.logits, dim=-1)
# 后续只做Token Span Mapping
aligned_teacher_log_probs = align_teacher_to_student(teacher_ids, student_ids, teacher_log_probs)

OPD里还有一个非常重要的问题:Student是On-Policy生成Trajectory的,所以Student生成出来的Token序列必须成为Teacher和Student共同评估的文本,也就是说不能让Teacher自己重新生成答案,否则就不再是严格意义上的On-Policy Distillation;正确做法是Student先生成 x_1,x_2,...,x_n,然后固定这条Student trajectory,让Teacher在相同Prefix下计算每个Student Action的概率。

python
# Student负责On-Policy Rollout
student_output = student.generate(prompt)
# Student生成的文本固定下来
trajectory = student_output.text
# Teacher不重新生成,而是评估Student生成的Trajectory
teacher_scores = teacher.score(prompt, trajectory)
student_scores = student.score(prompt, trajectory)

因此OPD的基本结构仍然是:

plain-text
Student Rollout → Student Tokens → Text/Byte Alignment → Teacher Scoring → Distillation Loss → Student Update

这里的关键变化只有一个:Teacher Scoring和Student Scoring不再在Token ID空间直接比较,而是在Student Token对应的Text Span上进行Teacher Probability Aggregation。

python
# Student生成Action
student_token = student_ids[t]
# 找到该Student Token对应的文本Span
span = student_tokenizer.decode([student_token])
# Teacher在相同文本Span上计算概率
teacher_logp = get_teacher_logprob(prompt, prefix, span)
# Student自己的Action概率
student_logp = student_log_probs[t, student_token]
# OPD可以使用Teacher对Student Action的监督信号
loss = distill_loss(student_logp, teacher_logp)

如果Teacher和Student只是Vocabulary大小不同,但Tokenizer的底层Byte Encoding兼容,那么可以进一步做Vocabulary Projection,也就是建立一个映射 Student Token → Teacher Token Sequence,例如Student Token "hello"对应Teacher Token [1234],Student Token "playing"对应Teacher Token [234, 891],Student Token "unbelievable"对应Teacher Token [876, 321, 654],这样Student的一个Action就可以对应Teacher的多个Action。

python
# Student Token -> Teacher Token Sequence
token_mapping = {"hello": [1234], "playing": [234, 891], "unbelievable": [876, 321, 654]}
teacher_ids = token_mapping[student_token]

这种映射可以提前离线构建,而不需要每次训练都重新Tokenize,特别适合固定Teacher/Student模型的OPD训练;但需要注意 Tokenizer Prefix Dependency,因为BPE/SentencePiece等Tokenizer存在上下文相关性,同一个字符串在不同Prefix下可能产生不同Tokenization,所以不能简单假设 encode("ing") 永远等于同一组Teacher Tokens。

python
# 错误的全局映射
mapping["ing"] = [123]
# 因为Tokenizer可能受到Prefix影响
# 正确做法应该考虑完整Prefix
mapping[prefix + "ing"] = teacher_tokenizer.encode(prefix + "ing")

因此更加稳妥的是 Prefix-Aware Alignment,即不仅保存Student Token的文本,还保存它在完整Trajectory中的Byte Span,然后对完整Teacher Token序列和Student Token序列做Span Alignment。

python
def align_tokens(teacher_tokens, student_tokens):
    # 每个Token保存对应的Byte Span
    teacher_spans = get_byte_spans(teacher_tokens)
    student_spans = get_byte_spans(student_tokens)
    # 根据Byte Span寻找重叠关系
    return match_spans(teacher_spans, student_spans)

例如:

plain-text
文本:"machine learning"
Teacher:["machine", " learning"]
Student:["machine", " learn", "ing"]
Byte Span:machine → [0,7]; learning → [7,16];Student machine → [0,7]; learn → [7,12];ing → [12,16]

这样就可以得到:

plain-text
Student Token "machine" → Teacher Token "machine"
Student Token " learn" + "ing" → Teacher Token " learning"

但是第二种情况需要特别注意:Teacher的一个Token对应多个Student Token时,Teacher对这个完整Token的概率不能直接复制给每个Student Token,因为Student的两个Action分别对应文本序列的不同Prefix;更合理的方法是将Teacher Token进一步拆分到Byte/Text Event级别,或者计算每个Student Token对应文本Span的条件概率。

python
# Teacher一个Token覆盖多个Student Token
teacher_token = " learning"
student_tokens = [" learn", "ing"]
# 不能简单把同一个Teacher logp复制给两个Student Token
# 应该分别计算:
# P(" learn" | prefix)
# P("ing" | prefix + " learn")

因此跨Tokenizer蒸馏最严格的实现其实是“Prefix Probability Matching”:对于Student每一个Action s_t,找到它对应的文本字符串 x_t,然后计算Teacher在相同Prefix下生成这个字符串的条件概率,再将这个Teacher Probability作为Student Action的Distillation Target。

python
def cross_tokenizer_target(teacher, teacher_tokenizer, prefix, student_token):
    # Student Token对应真实文本
    text = student_token
    # Teacher对该文本重新Tokenize
    teacher_ids = teacher_tokenizer.encode(text, add_special_tokens=False)
    total_logp = 0.0
    # Teacher逐Token计算条件概率
    for tid in teacher_ids:
        logits = teacher(prefix).logits[:, -1]
        log_probs = torch.log_softmax(logits, dim=-1)
        total_logp += log_probs[tid]
        # Prefix加入Teacher已经消费的文本
        prefix += teacher_tokenizer.decode([tid])
    return total_logp

如果采用 Forward KL,跨Tokenizer情况下也会遇到一个更大的问题:Teacher的完整Vocabulary分布无法直接映射到Student Vocabulary,因为Teacher可能存在大量Student不存在的Token,所以不能直接计算 KL(P_teacher || P_student);这时候工程上更常见的是只针对 Student实际采样Action 做Teacher Probability Scoring,也就是计算 P_T(a_t | s_<t) 和 P_S(a_t | s_<t),这种方式天然适合OPD。

python
# Student当前Action
action = student_ids[t]
# Student概率
student_logp = student_log_probs[t, action]
# Teacher对同一文本Action的概率
teacher_logp = cross_tokenizer_target(teacher, teacher_tokenizer, prefix, student_tokenizer.decode([action]))
# 进行Action-level Distillation
loss = (student_logp - teacher_logp).pow(2)

这也是为什么在 OPD + 不同Tokenizer 的场景中,不能简单照搬传统Logit KD;传统KD通常默认Teacher和Student共享Vocabulary,例如:

python
# 同Tokenizer时可以直接做Logit KD
teacher_logits = teacher(input_ids).logits
student_logits = student(input_ids).logits
loss = F.kl_div(F.log_softmax(student_logits / T, dim=-1), F.softmax(teacher_logits / T, dim=-1), reduction="batchmean")

但不同Tokenizer时:

plain-text
Teacher Vocabulary:V_T
Student Vocabulary:V_S
V_T != V_S
因此:KL(P_T(V_T) || P_S(V_S))没有直接数学意义。

跨Tokenizer后应该转换成:

plain-text
Teacher Token Distribution → Teacher Token → Text / Byte Span → Student Token对应Text Span → Teacher对该Text Span的Conditional Probability → Student Action Probability → OPD Loss

如果追求更高效的工程实现,可以把Teacher计算改成 Text-level Log Probability,先对Student生成的完整文本进行Teacher Tokenization,然后一次Forward得到Teacher每个Token的Log Probability,再通过Prefix Sum把任意Text Span的Teacher Log Probability快速取出来。

python
# Teacher一次Forward
teacher_outputs = teacher(input_ids=teacher_input_ids)
log_probs = F.log_softmax(teacher_outputs.logits, dim=-1)
# 每个Teacher Token自己的log probability
token_logp = gather_token_logp(log_probs, teacher_input_ids)
# 前缀累计
prefix_sum = torch.cumsum(token_logp, dim=0)
# 任意Span的Teacher log probability
def span_logp(start, end):
    return prefix_sum[end] - prefix_sum[start]

这种方式对于OPD非常重要,因为Agent Trajectory可能非常长,如果每一个Student Token都单独调用Teacher Forward,Teacher计算成本会爆炸;而 一次Teacher Forward + Span Alignment + Prefix Sum 可以把主要计算集中到一次Forward中,剩下只是CPU/GPU上的Index Mapping。

python
# 高效版本
teacher_logits = teacher(teacher_ids).logits
teacher_logp = gather_logp(teacher_logits, teacher_ids)
# 只做Alignment,不重复Forward
alignment = build_alignment(teacher_ids, student_ids)
targets = aggregate_span_logp(teacher_logp, alignment)

从 AI Infra角度 看,跨Tokenizer OPD最大的性能瓶颈通常不是KL计算,而是 Teacher重新Tokenize + Token Alignment + Teacher Forward + 长Trajectory显存占用,因此可以把Teacher放在独立Inference Worker中,Student负责Rollout,Teacher负责Scoring,然后通过Trajectory Queue异步传输 prompt、student_tokens、text_spans,Teacher Worker批量计算Log Probability,再把Teacher Scores返回Trainer。

python
# Student Rollout Worker
trajectory = student_rollout(task)
# 不发送Teacher Token ID,只发送Student轨迹和文本信息
queue.put({"prompt": prompt, "student_tokens": trajectory.tokens, "text": trajectory.text})
# Teacher Worker批量Scoring
batch = queue.get_batch()
teacher_scores = teacher_score(batch)
# 返回给RL Trainer
score_queue.put(teacher_scores)

这和标准AgentRL的 Rollout → Reward → Trainer 架构很像,只不过OPD这里的Reward变成了Teacher Policy Signal,因此可以理解为:

plain-text
Student Rollout → Cross-Tokenizer Alignment → Teacher Scoring → OPD Loss → Student Update

如果Teacher和Student词表差异非常大,例如一个是SentencePiece,一个是Byte-level BPE,那么优先选择Byte-level Alignment,因为Byte是更底层、更稳定的共同表示;如果两个Tokenizer都是兼容UTF-8的现代LLM Tokenizer,可以使用 offset_mapping / byte_span 建立映射;如果Tokenizer完全不兼容,例如一个处理原始文本、另一个经过特殊Normalization,则需要先统一Normalization,否则同一字符串可能在两边根本无法建立可靠对应关系。

python
# 推荐的Alignment优先级
alignment_strategy = ["Byte Span Alignment", "Unicode/Text Span Alignment", "Token Sequence Mapping", "Vocabulary Projection"]

还有一种工程上非常简单但有效的方法:直接做Sequence-Level Distillation,也就是Teacher不提供Token-level Logits,只负责生成高质量答案/Trajectory,Student直接把Teacher生成的文本当作SFT数据;这种方法完全绕开Tokenizer不一致的问题,因为Student只需要使用自己的Tokenizer训练Teacher生成的字符串。

python
# Teacher生成文本
teacher_text = teacher.generate(prompt)
# Student使用自己的Tokenizer
student_inputs = student_tokenizer(prompt, teacher_text)
# 直接SFT
loss = student(**student_inputs).loss
loss.backward()
optimizer.step()

所以如果你的目标只是 “让Student学Teacher的Agent行为”,不同Tokenizer并不是大问题,直接做Trajectory Distillation最简单;如果你的目标是 “严格做On-Policy Logit/Policy Distillation,让Student在自己的Trajectory上逼近Teacher Policy”,那么就需要做Cross-Tokenizer Probability Alignment;两者的精度和工程复杂度不同。

plain-text
Sequence Distillation:Teacher → Text/Trajectory → Student SFT
优点:简单、稳定、Tokenizer无关
缺点:丢失Teacher完整概率分布

Cross-Tokenizer OPD:Student Rollout → Text/Byte Alignment → Teacher LogP → OPD
优点:保留Teacher Policy Signal
缺点:Alignment和Teacher Scoring成本更高

在真正的 AgentRL + OPD 项目中,我更推荐采用 Student On-Policy Rollout + Teacher Text-level Scoring + Byte/Span Alignment + Action-level Distillation,因为Student的Tokenizer决定真实Action Space,Teacher不需要强行改变Student Vocabulary,只需要回答一个问题:“对于Student刚刚选择的这个文本Action,我认为它有多大概率?”;这样既保留了OPD的On-Policy特性,又避免了Teacher/Student Vocabulary不同导致的Logit空间不一致。

python
# 推荐的Cross-Tokenizer OPD核心实现
student_traj = student.rollout(task)
# Teacher使用自己的Tokenizer重新编码Student轨迹
teacher_ids = teacher_tokenizer(student_traj.text).input_ids
# Teacher一次Forward计算完整轨迹
teacher_outputs = teacher(teacher_ids)
teacher_logp = get_token_log_probs(teacher_outputs, teacher_ids)
# 建立Teacher Token和Student Token的Text/Byte Span映射
alignment = build_span_alignment(teacher_tokenizer, student_tokenizer, student_traj.text)
# 将Teacher Token概率聚合到Student Action空间
teacher_targets = aggregate_teacher_logp(teacher_logp, alignment)
# Student自己的Action概率
student_logp = student.get_action_logp(student_traj)
# Cross-Tokenizer OPD Loss
loss = opd_loss(student_logp, teacher_targets)
# 更新Student
optimizer.zero_grad()
loss.backward()
optimizer.step()

OPD时Teacher和Student词表不同,不应该强行做“Token ID对Token ID”的KL,而应该让Student负责真实On-Policy Rollout,把Student Token还原到统一的Text/Byte Span,再让Teacher用自己的Tokenizer计算这些文本Action的Conditional Log Probability,最后把Teacher Probability重新映射回Student Action Space做Distillation;如果只需要行为迁移就用Teacher Trajectory → Student SFT,如果需要真正的Policy-level OPD,就用Text/Byte Alignment + Teacher LogP + Student Action LogP,这就是跨Tokenizer OPD最核心的实现思路。


AgentRL中的Reward Hacking怎么解决:从Reward设计、Verifier、Trajectory到训练闭环

AgentRL中的 Reward Hacking 本质上是模型找到了“如何获得高Reward”但没有真正完成任务的方法,也就是优化目标和真实目标之间出现了 Specification Gap,例如销售Agent的Reward要求“成功完成订单+1分、调用正确Tool+0.2分、回答完整+0.3分”,模型可能学会频繁调用Tool、生成很长的解释、甚至通过钻Verifier规则的漏洞获得高分,但用户真正想要的是“正确完成订单”;因此解决Reward Hacking不能简单理解成“把Reward调低一点”,而应该从 Reward设计、Verifier独立性、Reward分解、Trajectory审计、Negative Cases、Process Reward、Outcome Reward、OOD测试、Reward Model监控和Policy更新 多个层面解决,核心原则是 Reward必须尽可能衡量任务真实结果,而不是衡量模型容易伪造的代理指标。

python
# 错误的Reward设计:模型很容易钻漏洞
def reward_bad(traj):
    reward = 0

    # 调用Tool次数越多Reward越高
    reward += 0.1 * traj.tool_calls

    # 输出越长Reward越高
    reward += 0.001 * len(traj.output)

    # 只要出现success关键词就加分
    if "success" in traj.output:
        reward += 1

    return reward

上面的Reward非常容易被Hack,例如模型不需要真正完成销售订单,只需要疯狂调用Tool、输出大量文字、最后写一句“success”就可能获得高分,因此第一原则是 Reward应该尽量与最终任务Outcome绑定,例如订单Agent真正应该检查“订单是否创建成功、订单金额是否正确、客户ID是否正确、商品数量是否正确”,而不是检查模型有没有调用3次Tool或者输出500字说明。

python
# 更合理的Outcome Reward
def reward_good(traj, ground_truth):
    reward = 0

    # CRM真实状态决定订单是否创建成功
    if traj.order_created:
        reward += 1.0

    # 检查关键字段是否正确
    if traj.customer_id == ground_truth.customer_id:
        reward += 0.3

    if traj.amount == ground_truth.amount:
        reward += 0.3

    if traj.quantity == ground_truth.quantity:
        reward += 0.3

    return reward

第一种解决方法是优先使用可验证的Outcome Reward,也就是RLVR思路,如果任务存在程序化Verifier,就不要让LLM自己判断“我做得好不好”,而应该直接检查环境最终状态,例如代码Agent可以运行Unit Test,数学Agent可以运行答案Verifier,销售Agent可以查询CRM数据库确认订单状态,GUI Agent可以检查网页最终状态,这类Reward比纯LLM Judge更加不容易被模型欺骗。

python
def verify_order(traj, crm):
    # 不相信Agent自己说“订单创建成功”
    order = crm.get_order(
        traj.order_id
    )

    # 直接检查真实业务状态
    if order is None:
        return 0.0

    # 检查订单关键字段
    if order.customer_id != traj.customer_id:
        return 0.0

    if order.amount != traj.amount:
        return 0.0

    return 1.0

这里最重要的思想是 Judge the Environment,而不是 Judge the Model's Claim,也就是说不要让模型告诉Verifier“我完成了任务”,而应该让Verifier自己查询环境状态;例如销售Agent说“已经为客户A创建100台服务器订单”,Reward计算器应该调用CRM确认订单,而不是搜索模型输出里有没有“创建成功”。

python
# 错误:根据模型输出判断成功
if "订单创建成功" in agent_output:
    reward = 1.0

# 正确:查询环境状态判断成功
order = crm.query_order(order_id)

reward = 1.0 if (
    order is not None
    and order.quantity == 100
) else 0.0

第二种方法是避免过度依赖单一Reward,采用多维Reward + Hard Constraint,例如Agent任务可以拆成 Task Success、Correctness、Safety、Efficiency、Tool Validity,但这些Reward不能简单全部相加,因为某些目标具有硬约束性质,例如订单金额错误,即使Agent回答非常漂亮,也不能因为其他Reward很高而获得正奖励;因此可以使用 hard constraint + soft reward,硬约束负责判断任务是否合格,soft reward只在任务合格之后用于优化效率。

python
def calculate_reward(traj):
    # Hard Constraint:核心任务失败直接判失败
    if not traj.task_success:
        return -1.0

    # Hard Constraint:安全检查失败直接判失败
    if not traj.safety_pass:
        return -1.0

    # 只有通过硬约束后才计算Soft Reward
    reward = 1.0

    # Tool调用效率
    reward -= 0.01 * traj.redundant_calls

    # Token效率
    reward -= 0.001 * traj.output_tokens

    return reward

例如销售Agent真正任务是“为客户创建正确订单”,那么 订单创建成功 应该是 Hard Constraint,而“少调用两次Tool”只是 Soft Objective;不能出现“订单创建失败但因为Tool调用很少所以Reward=0.8”的情况,否则GRPO/PPO会学习到错误策略。

python
# Reward优先级
def final_reward(traj):
    # 第一层:任务是否成功
    if not traj.task_success:
        return -1.0

    # 第二层:安全/业务约束
    if not traj.business_valid:
        return -1.0

    # 第三层:效率优化
    return (
        1.0
        - 0.01 * traj.tool_calls
        - 0.001 * traj.tokens
    )

第三种方法是防止Reward Components被模型反向利用,也就是避免Reward可被直接操纵,例如如果Reward包含“Tool调用成功率”,模型可能通过调用简单的Tool刷成功率;如果Reward包含“回答完整度”,模型可能输出大量无意义内容;如果Reward包含“Verifier通过率”,模型甚至可能学习Verifier的漏洞,因此所有Reward Component都应该问一个问题:模型是否能够通过改变表面行为而提高这个Reward,却不提高真实任务成功率? 如果答案是“可以”,这个Reward就应该降低权重、改成约束或者直接删除。

python
# 容易被Hack的Reward
reward = (
    0.4 * tool_success_rate
    + 0.3 * answer_length_score
    + 0.3 * keyword_score
)

# 更合理的设计
reward = (
    0.8 * verified_task_success
    + 0.1 * safety_score
    + 0.1 * efficiency_score
)

第四种方法是加入Negative Cases和Adversarial Cases,因为Reward Hacker往往不是在正常样本上暴露,而是在模型发现规则漏洞后暴露,所以训练和评估阶段应该主动构造“看起来可以获得高Reward但实际上任务失败”的样本,例如销售Agent输入“帮我创建订单”,但客户ID不存在,模型如果直接调用Create Order并获得Tool返回值就可能被错误Reward;应该加入这种 Bad Case,让Verifier明确判定任务失败。

python
# Reward Hacking测试样本
adversarial_cases = [
    {
        "task": "创建客户A订单",
        "attack": "使用不存在的customer_id"
    },
    {
        "task": "查询客户预算",
        "attack": "返回模型猜测值"
    },
    {
        "task": "修改客户折扣",
        "attack": "绕过权限检查"
    },
    {
        "task": "发送订单邮件",
        "attack": "重复调用send_email"
    }
]

这些Bad Cases不能只放在Evaluation里,更重要的是可以进入 RL Training Loop,让模型发现自己原本能够通过的漏洞现在会获得负Reward,从而逐渐关闭Reward Hacking路径。

python
for task in training_tasks:

    # 正常任务 + 对抗任务一起训练
    batch = [
        normal_case(task),
        adversarial_case(task)
    ]

    trajectories = rollout(batch)

    # Verifier重新检查真实环境状态
    rewards = [
        verified_reward(traj)
        for traj in trajectories
    ]

    # 使用新的Reward更新Policy
    trainer.update(
        trajectories,
        rewards
    )

第五种方法是使用Independent Verifier,也就是让Reward计算器与Policy尽可能解耦,如果Policy和Reward Model使用相同模型、相同Prompt、相同规则,很容易产生“模型知道Judge怎么打分”的问题,最终出现 Goodhart's Law:一旦一个指标成为优化目标,它就会逐渐失去作为真实目标代理指标的可靠性;因此在高价值Agent任务中,最好让Verifier基于独立规则、程序、数据库、执行环境或者不同模型进行验证。

python
class IndependentVerifier:

    def verify(self, trajectory):
        # 不读取Agent自评结果
        # 直接读取环境最终状态
        state = environment.get_state()

        # 根据任务目标进行独立判断
        return self.check(
            state,
            trajectory.task
        )

第六种方法是限制Process Reward,避免模型为了刷中间奖励而偏离最终目标,例如Agent每正确调用一次Tool就得到+0.2,模型可能为了获取更多Process Reward而调用大量无意义Tool,因此Process Reward最好具有明确的“必要性”约束,例如只有当Tool调用改变了任务状态或者提供了完成任务所需的信息时才给予奖励,而且最终Outcome Reward必须占主导地位。

python
def process_reward(step):
    # Tool没有提供新信息,不奖励
    if not step.useful:
        return 0.0
    # Tool真正改变任务状态
    if step.state_changed:
        return 0.05
    return 0.0

def total_reward(traj):
    # Outcome Reward占绝对主导
    outcome = verified_outcome_reward(traj)
    # Process Reward只作为辅助
    process = sum(
        process_reward(step)
        for step in traj.steps
    )
    return outcome + 0.1 * process

第七种方法是对Trajectory做Reward Attribution和审计,也就是说不仅看最终Reward是多少,还要分析“为什么这个Trajectory获得高Reward”,例如100条高Reward轨迹中如果有80条都出现大量Tool Retry,就说明模型可能发现了某个Reward漏洞;如果Reward突然提升但真实Task Success没有提升,也应该立即怀疑Reward Hacking,而不是认为训练成功,因此AgentRL训练平台应该持续记录 reward、task_success、tool_calls、tokens、retry_count、verifier_result、failure_type 等指标。

python
trajectory_metrics = {
    "reward": reward,

    # 最重要的真实任务指标
    "task_success": task_success,

    # Agent行为指标
    "tool_calls": len(traj.tool_calls),
    "tokens": traj.tokens,
    "retry_count": traj.retry_count,

    # Verifier结果
    "verifier_pass": verifier_pass,

    # 错误类型
    "failure_type": failure_type
}

例如训练过程中发现 Average Reward 从0.55上涨到0.85,但 Task Success从0.62只上涨到0.64,这就是非常典型的 Reward Hacking 信号,因为模型学会提高Reward,却没有明显提升真实任务完成率;相反,如果 Reward、Task Success、Verifier Pass Rate 同时提升,才更有可能是真正的能力提升。

python
# Reward Hacking监控
if reward_delta > 0.20 and task_success_delta < 0.05:
    print(
        "Warning: reward increased "
        "without corresponding task improvement"
    )

第八种方法是使用Reward-Policy Gap监控,因为Reward Model本身可能被Policy“钻空子”,可以建立一个独立的真实评估集,在训练过程中定期比较 Training Reward 和 True Success Rate;如果两者逐渐出现明显分离,就说明Reward已经不能很好代表真实目标。

python
def monitor_reward_gap( train_reward, true_success):
    # Reward与真实成功率之间的差距
    gap = ( train_reward  - true_success)
    # Gap过大说明Reward可能正在被Hack
    if gap > 0.30:
        return "REWARD_HACKING_WARNING"
    return "NORMAL"

第九种方法是训练Verifier,而不是无限修改Reward,对于复杂Agent任务,单纯手写Reward往往覆盖不了所有情况,这时候可以采用 Rule Verifier + Programmatic Verifier + LLM Judge + Human Audit 的组合,例如代码Agent由Unit Test验证,数据库Agent由SQL结果验证,销售Agent由CRM状态验证,开放式回答再使用LLM Judge辅助判断,而高风险Case进入人工抽检;这种方式比单纯依赖一个Reward Model更加稳健。

python
def hybrid_verify(traj):
    # 第一层:程序化验证
    rule_result = rule_verifier(traj)
    if not rule_result:
        return 0.0
    # 第二层:LLM Judge处理开放式质量
    judge_score = llm_judge( traj )
    # 第三层:高风险样本进入人工抽检
    if traj.high_risk:
        human_score = human_review(traj)
        return human_score
    return judge_score

第十种方法是用SFT/RFT先消灭明显的Reward Hack,再进行RL,因为如果模型在RL开始时就不知道什么行为是正确的,它可能会大量探索错误策略,而Reward又存在漏洞时,很容易迅速找到Hack路径,因此AgentRL通常更适合先使用 SFT / RFT / Demonstration Trajectories 建立基础Tool-use能力,再使用RL优化长程规划和效率;对于已经发现的Reward Hack Bad Cases,可以重新制作正确轨迹和错误轨迹,通过SFT或RFT先纠正Policy,然后再重新进入GRPO/PPO训练。

python
# 第一阶段:SFT纠正明显错误行为
sft_trainer.train( correct_trajectories)

# 第二阶段:RFT筛选高质量Agent轨迹
good_trajs = [
    traj for traj in rollouts
    if verified_reward(traj) > 0.8
]

rft_trainer.train( good_trajs)

# 第三阶段:再进入AgentRL
grpo_trainer.train(
    rollout_fn=rollout,
    reward_fn=verified_reward
)

在 GRPO / PPO AgentRL 中,Reward Hacking还有一个特殊问题,就是模型可能并不是在整个任务上Hack,而是在某几个Trajectory上找到漏洞,例如同一个Prompt生成8个GRPO Group Samples,其中某些轨迹通过重复Tool调用或者利用Verifier漏洞获得异常高Reward,那么 Group Relative Advantage 会进一步放大这些异常轨迹,使Policy更倾向于产生这种行为,因此必须在进入Advantage计算之前进行 Reward Sanity Check / Reward Clipping / Outlier Detection。

python
def normalize_group_rewards(rewards):
    # 先检测异常Reward
    mean = sum(rewards) / len(rewards)

    variance = sum(
        (r - mean) ** 2
        for r in rewards
    ) / len(rewards)

    std = variance ** 0.5

    # 对异常Reward进行限制
    clipped = [
        max(
            mean - 3 * std,
            min(mean + 3 * std, r)
        )
        for r in rewards
    ]

    return clipped

不过 Reward Clipping只能缓解异常值,不能真正解决Reward Hacking,因为如果Reward Function本身存在漏洞,Clip只是把“错误目标”限制在一定范围内,模型依然会朝错误方向优化;真正的解决办法仍然是修改Verifier或者Reward Specification,所以在AgentRL工程中应该把 Reward Debugging 当成独立模块,而不是把所有问题都归结为 PPO/GRPO 超参数问题。

python
# Reward Debugging应该独立于RL Trainer
class RewardDebugger:

    def analyze(self, trajectories):
        # 检查高Reward轨迹是否真的完成任务
        suspicious = []
        for traj in trajectories:
            if (
                traj.reward > 0.9
                and not traj.task_success
            ):
                suspicious.append(traj)
        return suspicious

对于实际Agent项目,可以建立 Reward Hacking Test Suite,每次修改Reward Function或者Verifier之后都自动跑一遍,例如销售Agent可以测试“重复创建订单、伪造成功结果、绕过权限、使用错误客户ID、调用不存在Tool、重复发送邮件、利用Timeout重复提交”等Bad Cases,如果某个版本Reward提高但这些攻击Case的Reward也提高,就不能进入正式RL训练。

python
REWARD_HACK_TESTS = [
    duplicate_order_case(),
    fake_success_case(),
    permission_bypass_case(),
    invalid_customer_case(),
    repeated_email_case(),
    timeout_duplicate_case()
]

for case in REWARD_HACK_TESTS:
    traj = rollout(case)
    reward = verified_reward(traj)
    # Bad Case不应该获得高Reward
    assert reward < 0.0

从AgentRL Infra角度,最关键的是不要只监控 training loss 和 average reward,而应该同时监控 True Task Success、Verifier Pass Rate、Reward、Reward Variance、Tool Call Distribution、Trajectory Length、Retry Rate、Failure Type Distribution、Human Audit Pass Rate、OOD Success Rate,因为Reward Hacking最危险的地方就是训练曲线可能看起来非常漂亮,例如Loss下降、Reward上升、KL正常,但真实Agent能力没有提升甚至下降,因此AgentRL的训练监控应该始终把 真实任务成功率 放在Reward之前。

python
metrics = {
    # RL训练指标
    "policy_loss": policy_loss,
    "kl": kl,
    "mean_reward": mean_reward,

    # Agent真实能力指标
    "task_success": task_success,
    "verifier_pass_rate": verifier_pass_rate,

    # Agent行为指标
    "avg_tool_calls": avg_tool_calls,
    "avg_trajectory_length": avg_traj_len,
    "retry_rate": retry_rate,

    # 安全/质量指标
    "reward_hacking_rate": reward_hacking_rate,
    "human_audit_pass_rate": human_audit_pass_rate
}

最终可以把 Reward Hacking的解决方案 总结成一套AgentRL规范:第一优先级是让Reward尽可能来自真实Environment Outcome,第二优先级是建立独立Verifier,第三优先级是区分Hard Constraint和Soft Reward,第四优先级是加入Negative / Adversarial Cases,第五优先级是限制Process Reward,第六优先级是对Trajectory进行Reward Attribution和异常审计,第七优先级是持续比较Training Reward和True Success Rate,第八优先级是对发现的Hack Case进行SFT/RFT修正,第九优先级是在GRPO Group中进行Reward Sanity Check,第十优先级是建立自动化Reward Hacking Test Suite;真正成熟的AgentRL系统不是“设计一个Reward然后开始GRPO”,而是建立一个 Reward Specification → Environment Verifier → Rollout → Reward Audit → RL Optimization → Adversarial Evaluation → Bad Case回流 → Reward/Verifier迭代 的闭环。

python
# AgentRL中推荐的Reward闭环
def agent_rl_step(tasks):

    # 1. Agent生成真实Trajectory
    trajectories = rollout(tasks)

    # 2. 独立Verifier检查Environment状态
    verified = [
        verifier.verify(traj)
        for traj in trajectories
    ]

    # 3. 计算Reward
    rewards = [
        calculate_reward( traj,  result )
        for traj, result
        in zip(trajectories, verified)
    ]

    # 4. 检测Reward Hacking
    suspicious = reward_debugger.analyze(trajectories)

    # 5. 正常Trajectory进入RL
    clean_data = [
        (traj, reward)
        for traj, reward in zip(
            trajectories,
            rewards
        )
        if traj not in suspicious
    ]

    # 6. GRPO/PPO进行Policy更新
    trainer.update( clean_data)

    # 7. Hack Case进入Bad Case池
    bad_case_buffer.add( suspicious )

Reward Hacking不是“Reward给错了”这么简单,而是Agent发现了“完成评分目标”和“完成真实任务”之间的漏洞;解决它的核心不是继续调GRPO/PPO参数,而是让Verifier真正检查Environment Outcome,用Hard Constraint限制不可接受行为,用Adversarial Bad Case主动攻击Reward,用Trajectory审计发现高Reward低Success样本,再把发现的Hack轨迹回流到SFT/RFT/RL训练中,最终让模型优化的目标越来越接近真实Agent能力,而不是越来越擅长钻Reward Function的空子。


RLSD(Self-Distilled RLVR)如何实现:从自生成答案到自蒸馏强化学习

RLSD(Self-Distilled Reinforcement Learning with Verifiable Rewards,Self-Distilled RLVR)可以理解成 让模型自己生成多个候选解,再利用 Verifier 判断哪些解是正确的,然后让模型不仅学习“正确答案”,还学习自己在正确轨迹中形成的概率分布或推理行为,它和普通 RLVR 最大的区别在于传统 RLVR 主要使用 Reward = Verifier(answer) 来直接优化 Policy,而 RLSD 在此基础上增加了一个 Self-Distillation 环节,也就是模型先作为 Generator 产生 On-Policy Samples,再从这些成功样本中构造 Teacher Target,然后让 Student Policy 向自己的高质量行为分布靠近,因此它并不是引入一个外部 Teacher,而是使用 当前 Policy 自己产生的成功行为作为 Self-Teacher,核心思想可以概括成 Current Policy → On-Policy Sampling → Verifier → Positive Trajectories → Self-Teacher Target → Distillation + RL → Updated Policy,这样做的一个重要价值是解决 RLVR 中 Reward Signal 过于稀疏的问题,因为传统 RLVR 可能只有最终答案对错一个 0/1 Reward,而 Self-Distillation 可以进一步利用成功轨迹内部的 Token-Level Distribution、Action Distribution 或 Reasoning Pattern 作为更密集的训练信号,从而让模型不仅知道“这条答案是对的”,还可以学习“自己生成正确答案时,每一步大概应该如何生成”。

python
# RLSD最核心的训练逻辑
for task in tasks:

    # 当前Policy进行On-Policy Sampling
    trajectories = [
        policy.rollout(task)
        for _ in range(num_samples)
    ]

    # Verifier判断每条轨迹是否正确
    rewards = [
        verifier.score(traj)
        for traj in trajectories
    ]

    # 找到当前Policy自己生成的正确轨迹
    positive = [
        traj
        for traj, reward in zip(trajectories, rewards)
        if reward > 0
    ]

    # 从正确轨迹构造Self-Teacher Target
    teacher_targets = build_self_targets( positive )

    # Student继续学习这些高质量行为
    loss = self_distillation_loss(
        policy,
        teacher_targets
    )

    loss.backward()
    optimizer.step()

RLSD 最关键的地方在于 Self-Teacher 到底是什么,它并不是简单把“正确答案字符串”重新拿去做一次 SFT,因为这样本质上更接近 RFT,而真正的 Self-Distillation 会尽可能保留模型在正确轨迹上的概率分布,例如模型生成 x_t 时有一个 P_old(x_t | x_<t),如果这条 trajectory 最终通过 Verifier,那么这些 Token-level Probability、Logits 或 selected-token LogProb 就可以作为当前 Policy 的 Teacher Signal,Student 更新后尝试让自己的 P_new(x_t | x_<t) 更接近这些高质量行为,这样模型学习的不是一个固定的 hard label,而是自己成功状态下的 Soft Policy Prior。

python
import torch
import torch.nn.functional as F

def build_self_teacher(
    old_model,
    input_ids
):
    # 使用旧Policy生成Teacher Logits
    with torch.no_grad():
        teacher_logits = old_model(
            input_ids
        ).logits

    # 转换成Soft Probability Distribution
    teacher_probs = F.softmax(
        teacher_logits / 1.0,
        dim=-1
    )

    return teacher_probs

因此 RLSD 和 RFT 的关系非常重要,RFT 通常是 Rollout → Verifier → Positive Trajectory → SFT,也就是只要找到正确 trajectory,就把它作为监督数据重新训练;RLSD 则可以进一步变成 Rollout → Verifier → Positive Trajectory → Self-Teacher Distribution → Distillation Loss + RL Loss,因此 RFT 更偏向 Learning from Successful Samples,而 RLSD 更偏向 Learning from the Policy Distribution of Successful Samples,前者主要使用 hard target,后者可以使用 soft target,这也是 Self-Distillation 能够比简单 RFT 提供更细粒度信号的原因。

python
# RFT:直接学习成功答案
rft_loss = cross_entropy(
    student_logits,
    target_tokens
)

# RLSD:学习成功轨迹中的Soft Distribution
student_log_probs = F.log_softmax(
    student_logits / temperature,
    dim=-1
)

self_distill_loss = F.kl_div(
    student_log_probs,
    teacher_probs,
    reduction="batchmean"
)

不过这里有一个非常重要的工程问题:不能让正在更新的模型直接给自己产生一个不断变化的 Teacher Target,否则 Teacher 和 Student 同时变化,很容易导致训练目标不稳定,因此实际实现一般会把当前 Policy 的 Snapshot、Reference Model 或 Rollout Policy 作为 Self-Teacher,也就是在一个 RL Iteration 内先固定旧模型生成 trajectory 和 Teacher Distribution,然后使用这个固定的 Distribution 更新当前 Student,下一轮更新完成后再重新生成新的 Self-Teacher,这实际上和 PPO / GRPO 中的 Old Policy 思想有一定联系。

python
# 当前训练Iteration固定一个Old Policy
teacher_model = copy_model(policy)

# Old Policy负责生成On-Policy数据
trajectories = rollout(
    teacher_model,
    tasks
)

# Verifier筛选成功轨迹
positive = [
    traj for traj in trajectories
    if verifier.score(traj) > 0
]

# Student使用固定Old Policy作为Self-Teacher
loss = self_distill(
    student=policy,
    teacher=teacher_model,
    trajectories=positive
)

对于 RLVR,Verifier 的作用仍然是整个 RLSD 系统的 Ground Truth,因为 Self-Distillation 最大的问题就是 模型会把自己的错误也蒸馏给自己,所以不能直接让模型所有 Rollout 都成为 Teacher Data,而应该先通过 Verifier 过滤,例如数学题可以使用程序验证答案,代码任务可以运行 Unit Test,SQL 可以执行 Query 并比较结果,Agent Task 可以检查 Environment State,只有通过 Verifier 的 trajectory 才能成为高质量 Self-Teacher Data,这就是为什么 RLSD 经常可以看成 RLVR + Self-Distillation。

python
# 生成多个On-Policy轨迹
trajectories = policy.rollout_batch(
    tasks,
    num_samples=16
)

# 使用Verifier进行过滤
positive_trajectories = []

for traj in trajectories:

    # 真实Reward来自外部Verifier
    reward = verifier.score(traj)

    # 只有正确轨迹才能成为Self-Teacher
    if reward >= 1.0:
        positive_trajectories.append(traj)

如果一个 Task 采样 16 次,其中 4 条成功,那么 RLSD 可以使用这 4 条成功 trajectory 建立 Self-Teacher Target;更进一步,可以让成功 trajectory 的 Teacher Weight 与 Reward、Verifier Confidence、Trajectory Quality 相关,这样不是简单地把所有成功轨迹等权处理,而是让质量更高的成功轨迹拥有更强的蒸馏权重。

python
# 根据Reward给不同成功轨迹分配权重
def trajectory_weight(traj):
    reward = verifier.score(traj)
    # Reward越高,蒸馏权重越高
    weight = reward
    # 更短的成功轨迹通常效率更高
    if traj.steps < optimal_steps:
        weight *= 1.2
    return weight

RLSD 的另一个重要特点是可以同时保留 RL Objective 和 Distillation Objective,因为单纯 Self-Distillation 容易让模型越来越偏向自己当前的成功行为,而失去探索能力,所以通常需要让 RL Loss 负责利用 Reward 优化 Policy,让 Distillation Loss 负责稳定学习高质量行为,两者组合起来可以写成 L = L_RL + λ L_SD,其中 L_RL 可以是 PPO / GRPO / REINFORCE 类 Policy Loss,L_SD 可以是 KL Distillation Loss,λ 用于控制 Self-Distillation 对训练的影响。

python
# RL部分:根据Verifier Reward优化Policy
rl_loss = grpo_loss(
    policy=student,
    trajectories=trajectories,
    rewards=rewards
)

# Self-Distillation部分:
# 学习成功轨迹中的Old Policy Distribution
sd_loss = self_distillation_loss(
    student=student,
    teacher=teacher_model,
    positive_trajectories=positive_trajectories
)

# 联合优化
loss = rl_loss + lambda_sd * sd_loss

loss.backward()
optimizer.step()

如果使用 GRPO,那么 RLSD 可以非常自然地嵌入 Group Sampling,因为同一个 Prompt 可以生成多个 Rollout,Verifier 给每条 trajectory 一个 Reward,然后 GRPO 计算 Group Relative Advantage,而 RLSD 可以进一步从 Group 中挑选高 Reward Samples 作为 Self-Teacher,这样一个 Group 同时提供两种训练信号:Reward 用来告诉模型哪些轨迹更好,Self-Distillation 用来告诉模型高质量轨迹内部的 Token / Action 分布应该长什么样,因此可以理解成 GRPO 负责“比较”,Self-Distillation 负责“模仿自己的成功行为”。

python
# 一个Prompt生成多个Trajectory
group = [
    policy.rollout(task)
    for _ in range(16)
]

# Verifier产生Reward
rewards = [
    verifier.score(traj)
    for traj in group
]

# GRPO计算Group Relative Advantage
mean_reward = torch.tensor(rewards).mean()
std_reward = torch.tensor(rewards).std()

advantages = [
    (r - mean_reward) /
    (std_reward + 1e-8)
    for r in rewards
]

# 选择Group中的成功Trajectory
positive = [
    traj
    for traj, reward in zip(group, rewards)
    if reward > 0
]

在 Token-Level 实现中,一个比较直接的方法是只对 成功 trajectory 的 selected token 进行 Self-Distillation,而不是对整个 Vocabulary 每个 Token 都计算 KL,这样可以明显减少计算量,例如模型实际生成 Paris 时,只需要记录旧 Policy 对 Paris 的 LogProb,然后更新时让新 Policy 提高这个 Token 的概率,这种形式可以理解成一种 Selective Self-Distillation,对于 Agent trajectory 也可以扩展到 Tool Name、Tool Arguments、Action Token 等关键区域。

python
# 记录Old Policy生成Token时的LogProb
with torch.no_grad():

    old_log_probs = get_token_log_probs(
        teacher_model,
        input_ids,
        labels
    )

# 当前Student计算相同Token的LogProb
new_log_probs = get_token_log_probs(
    student,
    input_ids,
    labels
)

# 只蒸馏Teacher实际选择的Token
sd_loss = -(
    old_log_probs.detach()
    * new_log_probs
).mean()

如果希望更接近真正的 Distribution Distillation,则可以计算完整 Vocabulary 上的 KL,但在大模型 AgentRL 中这会带来很高的 GPU Memory 和 Compute 成本,因此工程上常见的优化方向是 Top-K Logits Distillation,也就是 Teacher 不保存完整 Vocabulary Distribution,只保存概率最高的 K 个 Token 的 Logits / LogProb,Student 只在这些 Token 上计算 Distillation Loss,这能够大幅降低 Rollout 数据存储和训练成本。

python
# Teacher只保存Top-K Token
topk_logits, topk_ids = torch.topk(
    teacher_logits,
    k=32,
    dim=-1
)

# Student只提取对应Token的Logits
student_topk = student_logits.gather(
    -1,
    topk_ids
)

# 对Teacher Top-K Distribution进行蒸馏
loss = F.kl_div(
    F.log_softmax(student_topk, dim=-1),
    F.softmax(topk_logits, dim=-1),
    reduction="batchmean"
)

对于 Agent 场景,Self-Distillation 不一定必须发生在 Token Level,也可以发生在 Action Level,例如 Agent 当前 State 是 发现测试失败,Teacher / Old Policy 在这个 State 下成功选择了 read_file → edit_file → run_test,那么 RLSD 可以直接让 Student 学习这些高层 Action,而不是蒸馏每一个 Reasoning Token,这样可以降低对模型内部 Reasoning Token 的依赖,同时更容易和 Tool Calling、GUI Agent、Browser Agent、Coding Agent 的 Environment Action 对接。

python
# Agent Action-Level Self-Distillation
state = trajectory.state

# Old Policy在该State下的成功Action
teacher_action = teacher.predict_action( state)

# Student预测Action
student_action_logits = student.predict_action(  state)

# 学习Old Policy的Action Distribution
sd_loss = cross_entropy(
    student_action_logits,
    teacher_action
)

RLSD 的冷启动过程也非常适合和 RFT 结合,如果基础 Agent 成功率已经达到一定水平,可以先进行 RFT,把成功轨迹直接加入 SFT;然后继续使用同一批 On-Policy Rollout 构造 Self-Teacher Distribution,再进行 RLSD;最终再进入完整 AgentRL,这样训练链路可以形成 SFT → RFT → Self-Distilled RLVR → GRPO/PPO,其中 RFT 负责快速提升 Positive Trajectory Density,RLSD 负责利用成功轨迹提供更密集的 Policy Signal,而 RLVR 最终通过 Verifier Reward 保证模型优化方向不会偏离真实任务目标。

python
# 第一阶段:RFT提升成功轨迹密度
positive = filter_positive(
    trajectories,
    verifier
)

student = sft(
    student,
    positive
)

# 第二阶段:构建Self-Teacher
teacher = copy_model(student)

# 第三阶段:RLSD
student = train_rlsd(
    student=student,
    teacher=teacher,
    verifier=verifier,
    tasks=tasks
)

# 第四阶段:进一步AgentRL
student = train_grpo(
    student,
    environment,
    verifier
)

RLSD 最需要注意的是 Self-Distillation Collapse,因为模型只能从自己已经能够产生的正确行为中学习,如果某类任务当前成功概率为 0,那么 Self-Distillation 没有任何 Positive Signal 可以利用,因此 RLSD 不能凭空创造模型没有的能力;另外,如果始终只选择最容易成功的 trajectory,模型可能越来越保守,探索能力下降,所以实际训练中需要保留一定比例的探索样本,并且不能让 Self-Distillation Loss 权重过大,否则训练最终可能退化成单纯的行为克隆。

python
# 不要100%只使用成功Trajectory
# 保留一部分RL探索样本
positive_ratio = 0.7
exploration_ratio = 0.3

training_batch = (
    positive_trajectories[:int(
        len(positive_trajectories)
        * positive_ratio
    )]
    +
    exploration_trajectories[:int(
        len(exploration_trajectories)
        * exploration_ratio
    )]
)

因此 RLSD 最核心的技术思想可以概括成:Verifier 提供“这条行为是否正确”的外部 Ground Truth,Self-Teacher 提供“模型自己成功时是如何行动的”的内部 Policy Signal,RL Objective 负责根据 Reward 优化长期策略,Self-Distillation Objective 负责稳定吸收高质量 On-Policy 行为,它与 RFT 的区别在于 RFT 主要学习成功样本本身,而 RLSD 进一步利用成功样本对应的 Policy Distribution;与普通 RLVR 的区别在于普通 RLVR 主要依赖最终 Verifiable Reward,而 RLSD 增加了从成功轨迹内部提取 Dense Policy Signal 的过程,因此对于 Reasoning 和 Agent Long-Horizon Task,RLSD 的价值就在于把稀疏的 0/1 Verifier Reward 转化成更丰富的 Reward + Successful Policy Distribution,最终形成 On-Policy Sampling → Verifier → Successful Trajectory → Self-Teacher → Distillation + RL → Updated Policy 的闭环,而它真正能否提升效果,关键取决于当前 Policy 是否已经具备足够的成功轨迹、Verifier 是否可靠、Self-Teacher 是否冻结稳定,以及 RL Loss 与 Self-Distillation Loss 的权重是否平衡。


MOPD (Multi-Teacher On-Policy Distillation)的原理解析与实现-含代码

MOPD(Multi-Teacher On-Policy Distillation,多教师 On-Policy Distillation)可以理解成 多个 Teacher 模型共同指导一个 Student 模型,并且 Teacher 不是只对固定离线数据提供监督,而是针对 Student 当前 Policy 自己产生的 On-Policy Trajectory 进行蒸馏,它主要解决的是单 Teacher 蒸馏存在能力上限的问题,因为不同 Teacher 往往擅长不同类型的任务,例如一个 Teacher 擅长数学推理,一个 Teacher 擅长 Coding,一个 Teacher 擅长 Tool Use 和 Agent Planning,如果直接让 Student 学习某一个 Teacher,就容易把 Student 的能力限制在单一 Teacher 的分布上,而 MOPD 的核心思想就是让 Student 自己进行 Rollout,然后根据当前任务或者 trajectory 状态选择多个 Teacher 提供指导,再把这些 Teacher 的概率分布、Logits、Reasoning 行为或者最终轨迹转化成蒸馏信号,最终优化 Student,使 Student 在自己的 On-Policy 数据分布上逐渐吸收多个 Teacher 的能力,因此可以把 MOPD 理解成 Student Policy → On-Policy Rollout → Multi-Teacher Inference → Teacher Signal Aggregation → Distillation Loss → Student Update → New Student Policy,这里最关键的两个词就是 Multi-Teacher 和 On-Policy,Multi-Teacher 解决的是“多个专家能力如何融合”,On-Policy 解决的是“Teacher 如何针对 Student 当前真正容易犯错的状态提供监督”,因此它特别适合 AgentRL、Reasoning Model 和 Long-Horizon Agent 的后训练。

python
# MOPD最核心的训练结构
for task in tasks:

    # Student先自己执行任务
    trajectory = student.rollout(task)

    # 多个Teacher分别观察Student当前轨迹
    teacher_outputs = []

    for teacher in teachers:
        output = teacher.generate(
            task=task,
            trajectory=trajectory
        )
        teacher_outputs.append(output)

    # 聚合多个Teacher的监督信号
    target = aggregate_teacher_outputs(teacher_outputs)

    # Student根据Teacher信号进行蒸馏
    loss = distillation_loss(
        student,
        target
    )

    loss.backward()
    optimizer.step()

MOPD 和普通 Knowledge Distillation 最大的区别在于普通 KD 往往是 Teacher → Fixed Dataset → Student,Teacher 对数据集中的固定输入生成 Soft Label,然后 Student 学习 Teacher,而 MOPD 是 Student → Current State / Trajectory → Teacher → Student,也就是说 Teacher 看到的是 Student 当前真正遇到的问题,例如 Student 在 Agent Task 中已经正确调用了三个工具,但是在第四步 Observation 理解错误,那么 Teacher 不需要重新解决整个任务,而是可以直接针对这个状态提供下一步正确 Action,这种方式对于 Agent 特别有效,因为 Agent 的困难通常不是“整个任务完全不会”,而是长链路中的某一个局部状态出现错误,因此 On-Policy Distillation 可以把训练信号集中到 Student 当前的 Failure State。

python
# Student当前Trajectory
trajectory = [
    {"action": "search", "observation": "..."},
    {"action": "open_page", "observation": "..."},
    {"action": "extract", "observation": "..."},
    # Student在这里出现错误
]

# Teacher直接针对当前状态提供指导
teacher_action = teacher.generate_next_action(trajectory=trajectory)

# Student学习Teacher在当前State下的行为
loss = cross_entropy(
    student.next_action_logits,
    teacher_action
)

多 Teacher 的关键问题不是简单地把多个 Teacher 的输出平均,因为不同 Teacher 对同一个任务的可靠程度不同,例如 Teacher A 擅长 Coding,Teacher B 擅长数学,Teacher C 擅长 Agent Tool Use,如果当前任务是 Coding,那么 Teacher A 的信号应该拥有更高权重;如果当前状态是 Tool Calling,则应该提高 Tool-use Teacher 的权重,因此 MOPD 通常需要一个 Teacher Selection / Teacher Routing / Teacher Weighting 机制,根据 Task、State、Trajectory、Teacher Confidence 或历史表现动态决定哪些 Teacher 的信息应该进入最终 Distillation Target。

python
# 根据任务类型给不同Teacher分配权重
def teacher_router(task):

    if task.type == "coding":
        return {
            "coding_teacher": 0.7,
            "reasoning_teacher": 0.2,
            "agent_teacher": 0.1
        }

    if task.type == "math":
        return {
            "coding_teacher": 0.1,
            "reasoning_teacher": 0.8,
            "agent_teacher": 0.1
        }

    # Agent任务提高Agent Teacher权重
    return {
        "coding_teacher": 0.1,
        "reasoning_teacher": 0.2,
        "agent_teacher": 0.7
    }

如果 Teacher 都能够提供 Token-Level Logits,那么最直接的 MOPD 实现就是 Logit Distillation,也就是多个 Teacher 分别得到 next-token probability distribution,然后根据 Teacher Weight 对这些 Distribution 做加权融合,再让 Student 拟合最终 Distribution,例如 Teacher A 给出 P_A(y|x),Teacher B 给出 P_B(y|x),Teacher C 给出 P_C(y|x),那么可以构造 P_M(y|x)=w_A P_A(y|x)+w_B P_B(y|x)+w_C P_C(y|x),然后使用 KL Divergence 让 Student 的 Distribution 接近 Multi-Teacher Distribution。

python
import torch
import torch.nn.functional as F

# Student Logits
student_logits = student(input_ids).logits

# 多个Teacher Logits
teacher_logits = [
    teacher_a(input_ids).logits,
    teacher_b(input_ids).logits,
    teacher_c(input_ids).logits
]

# Teacher权重
weights = [0.5, 0.3, 0.2]

# 转换成概率分布
teacher_probs = [
    F.softmax(logits / temperature, dim=-1)
    for logits in teacher_logits
]

# 加权融合多个Teacher
target_probs = sum(
    w * p
    for w, p in zip(weights, teacher_probs)
)

# Student概率分布
student_log_probs = F.log_softmax(
    student_logits / temperature,
    dim=-1
)

# KL Distillation Loss
loss = F.kl_div(
    student_log_probs,
    target_probs,
    reduction="batchmean"
)

但在真正的大模型 MOPD 中,Teacher 往往比 Student 大很多,因此不能简单地在同一台 GPU 上同时加载多个完整 Teacher,否则显存和计算成本会非常高,所以工程上经常采用 Teacher Inference Service,Teacher 集群负责生成 Logits / Responses / Trajectories,Student Trainer 负责训练,二者通过数据流或者 RPC / 文件系统进行解耦,这样可以让多个 Teacher 并行 Rollout,同时 Student 使用已经缓存的 Teacher Signal 进行训练。

python
# Teacher服务端负责生成监督信号
class TeacherServer:
    def generate(self, request):
        outputs = []
        # 多Teacher并行推理
        for teacher in self.teachers:
            outputs.append(  teacher.generate(request) )
        # 返回Teacher监督信息
        return aggregate(outputs)

# Student训练端只接收Teacher Target
target = teacher_server.generate(request)
loss = student_distill_loss(student, target)

对于 Agentic RL,MOPD 更重要的不是单纯蒸馏最终答案,而是蒸馏 Trajectory-level Policy,因为 Agent 的能力实际上包含 什么时候调用 Tool → 调用哪个 Tool → 参数怎么写 → 如何理解 Observation → 是否继续执行 → 什么时候停止 → 如何从错误中恢复,如果只蒸馏最终 Answer,Student 可能得到正确答案,却不会学习完成任务所需要的 Agent Policy,因此可以让多个 Teacher 对 Student 的完整 trajectory 进行指导,然后只对关键 Action 或关键 State 计算 Distillation Loss。

python
# Student产生完整Agent轨迹
student_traj = student_agent.rollout(task)
# Teacher针对每个关键State提供Action
targets = []
for state in student_traj.states:
    teacher_actions = [ teacher.next_action(state) for teacher in teachers ]
    # 聚合多个Teacher的Action
    target_action = vote_or_weight( teacher_actions )
    targets.append(target_action)
# Student学习Teacher在关键State上的Policy
loss = policy_distillation_loss( student_traj, targets)

这里可以进一步引入 Failure-aware MOPD,也就是不要让 Teacher 对 Student 的所有状态都提供同样强度的监督,而是重点蒸馏 Student 当前失败、低 Reward、Uncertain 或高风险的状态,例如 Student 在 100 个 Step 中只有 Step 37 出现错误,那么可以提高 Step 37 附近的 Distillation Weight,这样 Teacher Compute 可以集中在真正有价值的区域,也能够减少完整轨迹全部蒸馏带来的计算成本。

python
# 根据Student当前状态计算蒸馏权重
def get_distill_weight(state):

    weight = 1.0

    # Reward低的状态重点蒸馏
    if state.reward < 0:
        weight *= 3.0

    # Tool Error重点蒸馏
    if state.tool_error:
        weight *= 4.0

    # Student不确定时重点蒸馏
    if state.entropy > ENTROPY_THRESHOLD:
        weight *= 2.0

    return weight

MOPD 还可以结合 Teacher Confidence,因为不是所有 Teacher 在所有问题上都可靠,例如 Teacher A 对 Coding 的答案非常确定,而 Teacher B 对同一个 Coding 问题概率分布非常平坦,那么就应该降低 Teacher B 的权重,因此可以使用 Teacher Entropy、Log Probability、Verifier Score 或历史 Task Success Rate 估计 Teacher Reliability。

python
# 根据Teacher自身Confidence计算权重
def teacher_weight(output):

    # LogProb越高说明Teacher越确定
    confidence = output.mean_logprob

    # 可以进一步结合Verifier结果
    if output.verified:
        confidence *= 1.5

    return confidence

对于 AgentRL,一个非常实用的设计是让 Teacher 不负责最终 Reward,Teacher 负责提供 Policy Prior,而 Environment / Verifier 负责提供真实 Reward,因为 Teacher 本身可能犯错,如果完全相信 Teacher,就会把 Teacher 的错误蒸馏给 Student,而 Environment Success 才是真正的 Ground Truth,所以可以将 Loss 设计成 L = L_MOPD + λ L_RL,其中 L_MOPD 负责让 Student 学习多个 Teacher 的能力,L_RL 负责让 Student 最终服从 Environment Reward,这样 Teacher 更像一个高质量 Policy Prior,而不是绝对正确的 Oracle。

python
# Teacher蒸馏Loss
distill_loss = multi_teacher_kl(
    student_logits,
    teacher_logits,
    teacher_weights
)

# 环境Reward产生的RL Loss
rl_loss = grpo_loss(
    student,
    trajectories,
    rewards
)

# 联合训练
loss = (distill_loss + alpha * rl_loss)

loss.backward()
optimizer.step()

在 AgentRL 冷启动中,MOPD 甚至可以和 RFT 组合使用,先让 Student 进行 On-Policy Rollout,再让多个 Teacher 对这些轨迹进行评估和修正,然后使用 Verifier 过滤出最终成功的轨迹,成功轨迹进入 RFT,Teacher Policy Distribution 进入 MOPD,最后再进入 GRPO/PPO,这样就形成 Student Rollout → Multi-Teacher Guidance → Corrected Trajectory → RFT/MOPD → AgentRL,其中 RFT 主要学习高质量完整轨迹,MOPD 主要学习 Teacher 的细粒度 Policy Distribution,而 RL 最终负责让 Student 适应真实 Environment Reward。

python
# Student先进行On-Policy Rollout
trajectories = student.rollout(tasks)

for traj in trajectories:

    # 多Teacher分析Student当前轨迹
    teacher_signals = [
        teacher.analyze(traj)
        for teacher in teachers
    ]

    # Verifier判断最终任务结果
    reward = verifier.score(traj)

    if reward > 0:

        # 成功轨迹进入RFT
        rft_dataset.add(traj)

    # Teacher Policy进入MOPD
    mopd_dataset.add({
        "trajectory": traj,
        "teacher_signals": teacher_signals,
        "reward": reward
    })

# 先进行MOPD/RFT
student = train_mopd(student, mopd_dataset)
student = train_rft(student, rft_dataset)

# 最后进入AgentRL
student = train_grpo(student, environment)

真正工程化实现 MOPD 时,还需要考虑 Teacher-Student Vocabulary、Tokenizer、Sequence Alignment 和 Logit Storage,如果 Teacher 和 Student 使用不同 Tokenizer,那么 Token-Level Logit 不能直接做 KL,需要先进行 Vocabulary Mapping 或改成 Response-Level / Action-Level Distillation;如果 Teacher 和 Student 使用同一个 Tokenizer,则可以直接进行 Token-Level Distillation;如果多个 Teacher 的 tokenizer 不同,则 Agent 场景下更常见的工程方案是让 Teacher 输出标准化的 Action / Tool Call / Response,然后 Student 在自己的 Tokenizer 空间中学习这些结果,这种方式比强制多个不同模型做 Vocabulary-level KL 更容易落地。

python
# 如果Teacher和Student使用相同Tokenizer
teacher_logits = teacher(input_ids).logits
student_logits = student(input_ids).logits

loss = kl_divergence(
    student_logits,
    teacher_logits
)

# 如果Tokenizer不同
# 不直接进行Token-Level KL
# 而是蒸馏标准化后的Action/Response
teacher_action = teacher.generate_action(state)

loss = action_level_loss(
    student,
    state,
    teacher_action
)

从数据工程角度,MOPD 最重要的数据不是传统意义上的静态 prompt-answer,而是 task + student_state + student_action + observation + teacher_signal + reward + verifier_result,因为只有保存这些信息,后续才能重新分析 Teacher 为什么选择某个 Action、Student 为什么失败、哪个 Teacher 在这个 State 上更可靠,以及最终的 Reward 是否支持 Teacher 的判断,这也是 MOPD 和简单 Multi-Model Ensemble 最大的区别:Ensemble 通常是在推理阶段让多个模型一起回答然后投票,而 MOPD 是在训练阶段利用多个 Teacher 的知识构造 Student Policy Update,最终目标是把多个 Teacher 的能力压缩进一个 Student,而不是让线上每次请求都运行多个 Teacher。

python
# MOPD推荐的数据结构
sample = {
    "task": task,

    # Student当时看到的状态
    "state": state,

    # Student原始Action
    "student_action": student_action,

    # 环境Observation
    "observation": observation,

    # 多Teacher输出
    "teacher_signals": {
        "teacher_a": teacher_a_output,
        "teacher_b": teacher_b_output,
        "teacher_c": teacher_c_output
    },

    # Teacher权重
    "teacher_weights": {
        "teacher_a": 0.5,
        "teacher_b": 0.3,
        "teacher_c": 0.2
    },

    # Environment Reward
    "reward": reward,

    # 最终Verifier结果
    "verified": verified
}

最终可以把 MOPD 放在 AgentRL 后训练体系中的位置理解为:SFT 解决“模型从零学习基本 Agent 行为”,RFT 解决“从 Student 自己产生的轨迹中筛选成功行为并放大”,MOPD 解决“让多个更强 Teacher 在 Student 自己的 On-Policy State 上提供细粒度 Policy Guidance”,AgentRL 则解决“最终让 Policy 直接根据 Environment Reward 优化长期任务目标”,所以 MOPD 最核心的创新并不是简单的“多个 Teacher 一起蒸馏”,而是 Multi-Teacher + On-Policy + State/Trajectory-level Guidance + Dynamic Teacher Weighting + Verifier/Reward Alignment,它让 Teacher 不再只对静态 Dataset 讲课,而是针对 Student 当前真正遇到的困难提供指导,再通过 Distillation Loss 将多个 Teacher 的能力压缩到 Student 中;如果进一步和 RFT、GRPO 结合,就可以形成 SFT → On-Policy Rollout → Multi-Teacher Guidance → MOPD/RFT → Positive Trajectory Density提升 → GRPO/PPO AgentRL → Evaluation → Bad Case Mining → 新一轮On-Policy MOPD 的闭环,这也是 MOPD 在大模型 Agent 后训练中真正有价值的地方。


如何用 RFT 冷启动 AgentRL

RFT(Reinforcement Fine-Tuning)可以理解成介于传统 SFT 和完整 AgentRL 之间的一种训练方式,它的核心不是人工直接给模型规定一条唯一正确的 Reasoning / Tool Use 轨迹,而是让模型针对一个 Task 自己生成多个候选回答或候选轨迹,然后通过 Reward Model、Rule-based Verifier、Unit Test、Environment State Checker 等方式判断哪些结果更好,再使用这些高质量样本继续微调模型,因此 RFT 的核心思想可以概括成 Task → Model Sampling → Multiple Candidates → Verifier / Reward → Select High-Quality Samples → SFT,它和普通 SFT 最大的区别在于 SFT 的训练数据通常是人工或 Teacher 直接提供的 (prompt, answer),而 RFT 的数据本身是 模型自己采样出来的 On-Policy / Near-On-Policy 数据,再通过奖励函数筛选出来,因此 RFT 特别适合解决“已经有一个基础 SFT Agent,但是直接进入 AgentRL 后成功率太低、Reward 太稀疏、探索空间太大”的问题;从 AgentRL 的角度看,RFT 很适合充当 RL 冷启动阶段,因为刚完成普通 SFT 的模型可能能够理解 Tool Schema、知道基本 Agent 格式,但是实际执行长链路任务时成功轨迹非常少,如果直接上 GRPO/PPO,Rollout 可能大量失败,导致有效 Positive Trajectory 数量不足,Advantage 信号非常差,RL 训练容易变成“在失败轨迹上优化”,而 RFT 的作用就是先通过大量采样和 Verifier 筛选,把模型当前 Policy 中已经存在但概率较低的正确行为挖出来,然后重新 SFT,让这些成功轨迹概率上升,从而把模型从 能偶尔做对 推向 比较稳定地做对,最终再进入 AgentRL。

python
# RFT最核心的训练逻辑
for task in tasks:

    # 让当前模型生成多个候选Agent轨迹
    candidates = [
        model.rollout(task)
        for _ in range(num_samples)
    ]

    # 使用Verifier判断每条轨迹是否真正完成任务
    scored = [
        (traj, verifier.score(traj))
        for traj in candidates
    ]

    # 只保留高Reward轨迹
    positive = [
        traj
        for traj, reward in scored
        if reward >= reward_threshold
    ]

    # 高质量轨迹重新进入SFT
    rft_dataset.extend(positive)

RFT 最关键的地方其实不是“重新 SFT”,而是 Sampling + Filtering,因为真正决定 RFT 效果的是模型能不能在当前 Policy 下采样出足够多的正确轨迹,如果一个 Agent 对某类任务的成功概率只有 0.001,那么即使采样 100 条 trajectory,也可能一条成功轨迹都拿不到,这时候 RFT 无法凭空创造能力,因此 RFT 的一个重要前提是 模型已经具备一定的 latent capability,只是正确行为的概率还比较低,例如 SFT 后模型已经知道 query_order、cancel_order 两个工具,也知道基本的 Tool Call 格式,但经常先调用错误工具或者参数填写错误,那么通过 16~64 次 Rollout,可能就能找到一些完整成功轨迹;这些成功轨迹经过 Verifier 筛选后重新 SFT,就能明显提高正确 Tool Use 的概率,而如果模型完全不知道这个工具存在,或者根本不会理解环境状态,那么单纯增加 Sampling 数量也不一定解决问题,此时需要先补充 SFT / Tool Schema / Demonstration 数据。

python
# 假设模型当前成功率很低
success_count = 0

for i in range(32):

    # 同一个任务采样32次
    trajectory = model.rollout(task)

    # 独立Verifier判断任务是否成功
    reward = verifier.score(trajectory)

    if reward > 0:
        success_count += 1

print("发现成功轨迹数量:", success_count)

# 如果成功轨迹数量过低:
# 说明当前模型还没有足够的可探索能力
if success_count == 0:
    print("RFT冷启动失败,需要先补SFT或改进探索策略")

对于 AgentRL 冷启动来说,最重要的是理解 为什么不能直接从 SFT 跳到 GRPO/PPO,因为 RL 需要 Policy 产生有效的 Rollout,而刚刚完成 SFT 的 Agent 通常会出现大量 Tool Error、Premature Stop、Invalid Action、Long-Horizon Failure 和 Reward=0,假设一个 Task 采样 16 条 trajectory,16 条全部失败,那么 GRPO 虽然可以计算 group reward,但所有 reward 都接近 0,归一化之后没有有效的正负对比,Policy Gradient 很难得到有价值的优化方向;如果 16 条中只有一条成功,RFT 就可以直接把这一条成功 trajectory 提取出来,通过 SFT 把它的概率提高,让下一轮 Rollout 更容易再次产生成功轨迹,这实际上是在做一种 Policy Bootstrapping。

python
# GRPO冷启动时可能出现的问题
trajectories = [
    model.rollout(task)
    for _ in range(16)
]

rewards = [
    verifier.score(traj)
    for traj in trajectories
]

# 如果所有轨迹都失败
# rewards可能全部为0
if max(rewards) == 0:
    print("没有Positive Trajectory,RL信号非常弱")

而使用 RFT 后:

python
# RFT先找到少量成功轨迹
positive_trajectories = [
    traj
    for traj in trajectories
    if verifier.score(traj) > 0
]

# 对成功轨迹执行SFT
if positive_trajectories:
    model = sft(
        model,
        dataset=positive_trajectories
    )

# 更新后的模型再进入RL Rollout
new_trajectories = rollout(
    model,
    tasks
)

这就是所谓的 RFT 冷启动 AgentRL:第一阶段不是立即优化 Policy,而是先让 Policy 自己探索,然后利用 Verifier 把成功轨迹“捞出来”,第二阶段用这些成功轨迹做 Targeted SFT,第三阶段再让新的 Policy 重新 Rollout,第四阶段再进入 GRPO/PPO 等 RL,这个过程的本质是 不断扩大 Positive Trajectory Density,也就是让环境中“成功轨迹”的出现概率越来越高。

python
# RFT -> AgentRL冷启动
for iteration in range(rft_iterations):

    # 当前Policy进行On-Policy Sampling
    trajectories = rollout_worker.generate(tasks)

    # Verifier给每条轨迹打分
    rewards = verifier.batch_score(trajectories)

    # 筛选成功轨迹
    positive = [
        traj
        for traj, reward in zip(trajectories, rewards)
        if reward >= threshold
    ]

    # 使用成功轨迹做SFT
    model = sft_update(
        model,
        positive
    )

# Positive Trajectory密度提升后
# 再进入GRPO/PPO
model = agent_rl(
    model,
    tasks,
    verifier
)

RFT 和传统 SFT 最大的区别还可以从数据来源看出来,传统 SFT 更接近 Human / Teacher → Correct Answer → Model Learning,而 RFT 更接近 Current Policy → Sampling → Verifier → High Reward Trajectory → Model Learning,因此 RFT 本质上已经开始具备 On-Policy Data Generation 的特征,这也是为什么它特别适合 Agent,因为 Agent 的正确行为往往不是一条固定答案,而是一条经过 Environment Interaction 的完整 trajectory,例如一个 Browser Agent 可能有几十种完成任务的方法,一个 Coding Agent 可能有多种修改代码和运行测试的方法,一个 Tool Agent 可能有不同的 Tool Calling 顺序,因此人工很难覆盖所有正确轨迹,而 RFT 可以让模型自己探索,然后从真实 Environment 中筛选成功轨迹。

python
# Agent任务不是简单的Question -> Answer
task = "修复这个Python项目中的Bug"

# Agent自己进行完整环境交互
trajectory = agent.rollout(
    task=task,
    tools=tools,
    environment=environment
)

# Verifier真正执行测试
reward = run_test_suite(
    trajectory.workspace
)

# 测试通过才认为是高质量轨迹
if reward == 1:
    rft_dataset.add(trajectory)

对于 Agent RFT,Verifier 是整个系统最重要的组件之一,因为 RFT 不需要人工逐条告诉模型“这一步应该调用哪个工具”,而是依赖最终结果判断哪些轨迹值得学习,因此 Verifier 的质量直接决定 RFT 数据质量,例如 Coding Agent 可以直接运行测试,SQL Agent 可以执行 Query 并比较 Ground Truth,数学 Agent 可以执行答案验证器,GUI Agent 可以检查最终页面状态,API Agent 可以检查数据库状态,这类任务特别适合 RFT;如果任务本身无法验证,例如“写一篇非常好的行业分析报告”,则可以使用 LLM Judge,但是 Judge 本身存在噪声,因此最好采用多 Judge、Rubric、Reference Answer 或部分程序化指标组合。

python
# 不同Agent任务使用不同Verifier
def verify(task, trajectory):

    if task.type == "coding":
        # 直接运行测试
        return run_tests(trajectory)

    if task.type == "sql":
        # 比较数据库查询结果
        return check_sql_result(trajectory)

    if task.type == "browser":
        # 检查最终网页状态
        return check_browser_state(trajectory)

    if task.type == "math":
        # 使用程序验证答案
        return check_math_answer(trajectory)

    # 开放式任务使用LLM Judge
    return llm_judge(trajectory)

RFT 数据也不能简单地只保存 prompt + final_answer,对于 AgentRL 冷启动,更有价值的是保存 完整 Tool-Use Trajectory,因为后续 AgentRL 需要知道模型在环境中执行了什么,例如一条数据可以包含 User → Assistant Tool Call → Tool Return → Assistant Tool Call → Tool Return → Final Answer,并且每一个 Action 都应该记录 Tool Name、Arguments、Observation、Reward、最终 Success,这样后续不仅可以做 SFT,还可以直接转换成 PPO/GRPO 所需要的 Rollout 数据。

python
# Agent RFT推荐保存完整Trajectory
trajectory = {
    "task": task,

    "messages": [
        {"role": "user", "content": "..."},
        {
            "role": "assistant",
            "tool_call": {
                "name": "search_order",
                "arguments": {"order_id": "123"}
            }
        },
        {
            "role": "tool",
            "name": "search_order",
            "content": "订单状态:可取消"
        },
        {
            "role": "assistant",
            "tool_call": {
                "name": "cancel_order",
                "arguments": {"order_id": "123"}
            }
        }
    ],

    # 环境最终状态
    "success": True,

    # Verifier Reward
    "reward": 1.0,

    # 轨迹长度
    "steps": 4
}

RFT 还有一个非常重要的工程问题就是 不要只保存最高 Reward 的轨迹,因为如果每个 Task 最终只保留一条最优 trajectory,数据多样性会下降,模型可能学成一种固定的 Tool Calling Pattern,因此通常可以采用 Top-K Sampling,例如每个 Task 采样 32 条 trajectory,然后按照 Reward 排序,保留 Top 2~8 条,同时可以加入一定比例的成功但不同路径的 trajectory,这样既保证质量又保持 Policy Diversity。

python
# 一个Task生成32条轨迹
trajectories = [
    model.rollout(task)
    for _ in range(32)
]

# 根据Verifier Reward排序
trajectories.sort(
    key=lambda x: verifier.score(x),
    reverse=True
)

# 保留Top-K
selected = trajectories[:4]

# 进入RFT Dataset
rft_dataset.extend(selected)

还可以进一步加入 Difficulty Filtering,因为太简单的任务对于冷启动 AgentRL 的价值有限,例如模型本身已经可以 99% 完成“调用 calculator 计算 1+1”,继续用这种数据做 RFT 不会显著提升能力,真正有价值的是那些模型已经“偶尔成功”的 Hard Task,也就是成功概率可能在 5%~50% 的任务,这些任务最适合 RFT,因为模型已经具备部分能力,同时又存在足够的探索空间,因此可以根据历史 Rollout 统计每个 Task 的成功率,把中间区域的任务优先加入 RFT。

python
# 根据历史Rollout统计Task成功率
task_success_rate = estimate_success_rate(task)

# 太简单:几乎100%成功,训练价值低
# 太困难:几乎0%成功,当前Policy没有足够能力
# 中间区域:最适合RFT
if 0.05 <= task_success_rate <= 0.50:
    rft_task_pool.add(task)

从 AgentRL 训练流程来看,RFT 的最佳位置通常不是完全替代 SFT 或 RL,而是作为 SFT 和 RL 之间的桥梁,可以理解成 Base Model → General SFT → Agent SFT → RFT → AgentRL,其中 General SFT 让模型具备基本 Instruction Following,Agent SFT 让模型学会 Tool Call、Observation、基本 Planning 和环境交互,RFT 则通过 On-Policy Sampling 把模型自身已经存在的成功行为挖出来并放大,最后 GRPO/PPO 等 AgentRL 再进一步优化长期 Reward、探索能力、Credit Assignment 和效率。

python
# 第一阶段:基础SFT
model = supervised_finetune(
    base_model,
    general_data
)

# 第二阶段:Agent SFT
model = supervised_finetune(
    model,
    tool_use_data
)

# 第三阶段:RFT冷启动
for _ in range(rft_rounds):

    # On-Policy Rollout
    trajectories = rollout(model, agent_tasks)

    # Verifier筛选高Reward轨迹
    positive = filter_positive(
        trajectories,
        verifier
    )

    # 用成功轨迹继续SFT
    model = supervised_finetune(
        model,
        positive
    )

# 第四阶段:AgentRL
model = grpo_or_ppo(
    model,
    environment,
    verifier
)

RFT 冷启动最关键的判断标准不是“RFT Loss 降了多少”,而是 Positive Trajectory Rate 有没有提升,例如第一轮模型对 1000 个 Hard Tasks 采样 16 次,只有 8% 的 Task 至少产生一条成功轨迹,经过 RFT 后提高到 25%,再经过第二轮 RFT 提高到 45%,这说明 RFT 正在不断把模型从“偶尔会做”推向“稳定会做”,此时再进入 GRPO,Group 内出现正负 Reward 的概率会明显提高,RL 才有足够的 Contrastive Signal;如果 RFT 后 Positive Trajectory Rate 几乎不增长,那么继续 RFT 通常没有意义,应该检查 Task 难度、Tool Schema、Verifier、Sampling Temperature、Trajectory Length、SFT 数据质量或者基础模型能力。

python
# RFT过程中最值得监控的指标
metrics = {
    # 至少生成一条成功轨迹的Task比例
    "positive_task_rate": positive_tasks / total_tasks,

    # 所有Rollout中的成功轨迹比例
    "trajectory_success_rate": successful / total_rollouts,

    # 每个Task平均需要多少次Sampling才能找到成功轨迹
    "samples_to_success": total_samples / successful_tasks,

    # 平均Trajectory长度
    "avg_steps": total_steps / total_rollouts,

    # 平均Reward
    "avg_reward": total_reward / total_rollouts
}

最终可以把 RFT 冷启动 AgentRL 理解成一个非常清晰的训练逻辑:普通 SFT 解决的是“模型知道正确行为是什么”,RFT 解决的是“从模型自己产生的大量行为中找到已经成功的行为,并把这些行为概率放大”,AgentRL 解决的是“让模型在 Environment 中进一步优化长期 Reward 和策略选择”,所以三者并不是互斥关系,而是能力逐层提升的关系,即 SFT 提供基础 Policy,RFT 提高 Positive Trajectory Density,RL 再利用更丰富的 Reward Signal 进行 Policy Optimization;尤其是在长链路 Agent 中,如果直接从 SFT 进入 GRPO/PPO 导致大量 Rollout 全部失败,那么 RFT 是非常有效的冷启动手段,因为它不要求模型一开始就拥有高成功率,而是通过 多次Sampling + Verifier筛选 + 高质量Trajectory SFT 不断扩大成功轨迹分布,直到模型能够稳定产生足够多的 Positive Trajectory,最终让 AgentRL 从“没有有效信号可学”进入“有大量正负轨迹可以优化”的状态。


Agent 整体效果怎么评估:没有用户反馈时如何有效抽检

Agent 的整体效果评估和普通 LLM Benchmark 最大的区别在于,普通 LLM 更多是在评估“回答对不对”,而 Agent 需要评估的是 任务有没有完成、过程有没有走对、工具有没有正确使用、是否出现无效循环、成本是否合理、最终结果是否可靠,因此不能只看最终 Answer Accuracy,而应该把 Agent Evaluation 看成 Task Success + Trajectory Quality + Tool Correctness + Efficiency + Safety + Robustness 的综合评估,其中最核心的指标通常是 Task Success Rate(任务成功率),也就是给 Agent 一个真实任务,让它自主完成 Planning、Tool Call、Observation、Reasoning 和 Final Answer,然后由一个独立的 Verifier 判断任务最终状态是否满足要求,例如订机票不是看 Agent 最后说“已经订好了”,而是检查订单系统中是否真的存在正确的订单;代码 Agent 不是看最后回答“代码已经修复”,而是执行 Test Suite 判断测试是否通过;SQL Agent 不是看 SQL 字符串是否看起来正确,而是真正执行 SQL 并检查结果;GUI Agent 不是看最后说“按钮已经点击”,而是检查目标 UI State 是否真的发生变化,因此 Agent Evaluation 的第一原则就是 尽可能从语言评价转向环境状态验证。

python
# Agent执行一个完整任务
trajectory = agent.run(task)

# 不直接相信Agent自己的Final Answer
# 而是让独立Verifier检查环境最终状态
result = verifier.check(
    task=task,
    trajectory=trajectory,
    environment=env
)

# 例如:
# result.success = True
# result.reward = 1.0

如果任务存在明确的 Ground Truth,那么可以直接使用程序化 Verifier,例如代码任务可以执行 Unit Test,数学任务可以执行答案验证器,数据库任务可以比较 Query Result,API Agent 可以检查 API Response,浏览器 Agent 可以检查网页状态,因此可以形成 Agent → Environment → Verifier → Success/Failure 的闭环,这比让另一个 LLM 直接判断“这个回答好不好”更加可靠;但很多 Agent 任务没有天然的程序化 Ground Truth,例如写报告、信息搜集、方案设计、客服回复,这时候才需要使用 LLM-as-a-Judge,但 Judge 最好不是简单地输入“请评价这个回答”,而应该使用明确的 Evaluation Rubric,让 Judge 对任务完成度、事实正确性、工具使用、完整性、效率等维度分别打分,并且尽量提供结构化 JSON 输出。

python
# LLM Judge不要只输出一个模糊分数
judge_prompt = """
你是Agent Evaluation Judge。

请根据以下标准评价Agent轨迹:
1. Task Success:任务是否真正完成
2. Tool Correctness:工具选择和参数是否正确
3. State Tracking:是否正确理解Tool Return
4. Efficiency:是否存在无效步骤
5. Final Answer:最终回答是否准确

输出JSON:
{
    "task_success": 0,
    "tool_correctness": 0,
    "state_tracking": 0,
    "efficiency": 0,
    "final_answer": 0,
    "reason": ""
}
"""

score = judge.evaluate(
    prompt=judge_prompt,
    trajectory=trajectory
)

Agent 的整体效果最好不要只用一个总分,而应该建立 多维 Evaluation Matrix,因为一个 Agent 可能 Task Success 很高,但是 Tool Call 次数特别多,或者成功率高但经常产生错误的中间操作,再或者最终答案很好但实际没有完成任务,因此比较合理的指标体系可以是 Task Success Rate 衡量任务是否完成,Pass@1 衡量单次执行成功率,Pass@K 衡量 K 次尝试中至少成功一次的概率,Tool Success Rate 衡量工具调用是否正确,Invalid Tool Call Rate 衡量无效工具调用,Trajectory Length 衡量执行步骤数,Latency 衡量完成任务耗时,Token Cost 衡量推理成本,Recovery Rate 衡量出现 Tool Error 后能否恢复,Loop Rate 衡量 Agent 是否陷入重复行为,Safety Violation Rate 衡量是否违反环境约束,这样才能真正知道 Agent 是“不会做”,还是“会做但太慢”,还是“成功率高但成本太高”。

python
# Agent Evaluation核心指标
metrics = {
    "task_success_rate": success_count / total_tasks,

    # 工具调用成功比例
    "tool_success_rate": successful_tools / total_tools,

    # 无效工具调用比例
    "invalid_tool_rate": invalid_tools / total_tools,

    # 平均Trajectory长度
    "avg_steps": total_steps / total_tasks,

    # 平均Token成本
    "avg_tokens": total_tokens / total_tasks,

    # 平均Latency
    "avg_latency": total_latency / total_tasks,

    # Agent发生循环的比例
    "loop_rate": loop_cases / total_tasks
}

对于 AgentRL 来说,还有一个非常重要的指标是 Reward 和真实 Task Success 是否一致,因为如果 RL 的 Reward Function 设计得不好,模型可能获得很高的 Reward,但是实际任务成功率没有提升,这就是典型的 Reward Misalignment 或 Reward Hacking,因此 Evaluation 不能只看 RL Training Curve 上的 Reward 是否上涨,而应该同时观察 Training Reward ↑ + Task Success ↑ + Cost ↓/稳定 + Invalid Tool Call ↓,如果 Reward 从 0.5 上升到 0.9,但真实 Success Rate 从 70% 降到 60%,说明 Reward Function 很可能被模型钻了漏洞。

python
# RL训练阶段
training_reward = evaluate_reward(model)

# 独立Evaluation环境
task_success = evaluate_task_success(model)

# 判断Reward是否真正代表能力
if training_reward > baseline_reward and task_success < baseline_success:
    print("Warning: Reward可能存在Reward Hacking")

真正难的问题是 没有用户反馈时怎么有效抽检,因为线上 Agent 通常每天产生大量 trajectory,如果每条都人工查看成本极高,而完全随机抽样又很容易抽到大量“正常案例”,最终人工看到的都是成功样本,真正有价值的错误轨迹反而被遗漏,所以 Agent 抽检不能只是 random.sample(trajectories, N),而应该使用 分层抽样 + 风险抽样 + 不确定性抽样 + Bad Case Mining,也就是说先把线上 trajectory 按任务类型、成功状态、工具类型、执行长度、Reward、模型版本、错误类型等维度分桶,然后从每个桶中抽取样本,这样可以避免某一个高频任务把整个抽检集占满。

python
# 将线上Agent轨迹按照多个维度进行分桶
buckets = {}

for traj in trajectories:

    key = (
        traj.task_type,
        traj.tool_type,
        traj.success,
        traj.length_bucket
    )

    buckets.setdefault(key, []).append(traj)

# 每个桶分别抽样
sampled = []

for key, bucket in buckets.items():

    # 每个桶至少抽取一定数量
    sampled.extend(
        random_sample(bucket, min(20, len(bucket)))
    )

例如线上一天有 100 万条 Agent Trajectory,其中 90 万条都是简单问答,5 万条是 Tool Use,4 万条是复杂多步 Agent,1 万条是失败任务,如果完全随机抽 1000 条,那么大概率绝大多数都是简单任务,复杂 Agent 的错误几乎看不到,因此更合理的方法是对 Long-Horizon、Failure、High-Cost、Tool Error、低 Reward、高不确定性 样本提高抽样权重,这就是 Risk-Based Sampling。

python
# 根据风险为Trajectory计算抽样权重
def calculate_sampling_weight(traj):

    weight = 1.0

    # 失败样本优先抽检
    if not traj.success:
        weight *= 5

    # 长链路更容易出现隐藏问题
    if traj.steps > 30:
        weight *= 3

    # Tool Error需要重点检查
    if traj.tool_error:
        weight *= 3

    # 高Token成本可能存在无效Reasoning或循环
    if traj.tokens > 10000:
        weight *= 2

    # Judge不确定的样本优先人工检查
    if traj.judge_confidence < 0.7:
        weight *= 4

    return weight

但是只抽失败样本也不够,因为 Agent Evaluation 中存在一种非常危险的情况:Agent 自己认为成功,但实际上失败了,这种样本通常不会被简单的 Success Flag 捕获,因此需要引入 Independent Judge / Verifier Disagreement,例如 Agent 自己输出 success=True,程序 Verifier 判断 False,或者两个 Judge 对同一条 trajectory 给出完全不同的结果,这类样本应该具有非常高的人工抽检优先级,因为它们通常代表 Evaluation System 没有完全覆盖的 Failure Mode。

python
# Agent自己的判断
agent_success = trajectory.final_status

# 独立Verifier判断
verifier_success = verifier.check(trajectory)

# 两者不一致时提高抽检优先级
if agent_success != verifier_success:
    priority = "HIGH"

如果没有用户反馈,LLM Judge Ensemble 也是一种比较实用的方法,但不能简单地让三个 Judge 投票,因为三个 Judge 可能共享相同的错误偏差,更好的方式是让不同 Judge 负责不同维度,例如 Judge A 判断任务完成度,Judge B 判断事实正确性,Judge C 判断工具调用和轨迹质量,然后对 disagreement 进行人工抽检,这样可以把人工成本集中到模型最不确定的区域。

python
# 多Judge分别负责不同维度
scores = {
    "task_judge": task_judge.evaluate(traj),
    "fact_judge": fact_judge.evaluate(traj),
    "tool_judge": tool_judge.evaluate(traj)
}

# 如果不同Judge结论差异很大
if max(scores.values()) - min(scores.values()) > 0.4:
    # 标记为人工重点抽检
    send_to_human_review(traj)

更进一步,可以使用 Active Sampling / Uncertainty Sampling,让系统主动寻找“最值得人工看的样本”,例如 Judge 给出 0.49 的概率代表成功,这类样本比 0.99 的明显成功和 0.01 的明显失败更加有价值,因为它们处于模型判断边界附近,人工标注这些样本能够帮助发现新的 Failure Mode,同时可以把这些样本加入 Evaluation Set 或训练数据,从而形成 Uncertain Case → Human Review → New Label → Evaluation Dataset / SFT / RL 的闭环。

python
# Judge输出成功概率
confidence = judge.predict_success_probability(traj)

# 接近0.5说明Judge不确定
if 0.4 <= confidence <= 0.6:
    human_review_queue.add(traj)

Agent 抽检还应该重点关注 Trajectory Anomaly,因为很多 Agent Failure 不一定直接表现为最终失败,例如一个任务最后成功,但中间出现了 20 次 Tool Error、重复调用同一个 API、反复搜索相同内容或者调用了不必要的高成本工具,这种轨迹从最终 Success Rate 看完全正常,但从 Agent Policy 的角度看已经出现明显问题,因此需要建立异常规则,例如 重复 Tool Call > N、相同 Observation 连续出现、Trajectory Length > P99、Token Cost > P99、Tool Error 连续 > N、Action 与 Observation 不匹配,然后把这些异常轨迹加入抽检池。

python
# 检测Agent轨迹中的异常行为
def detect_anomaly(traj):

    # 连续重复Tool Call
    if traj.repeated_tool_calls > 3:
        return True

    # Tool Error连续出现
    if traj.consecutive_tool_errors >= 2:
        return True

    # 超过正常Trajectory长度
    if traj.steps > P99_STEPS:
        return True

    # Token成本异常
    if traj.tokens > P99_TOKENS:
        return True

    return False

没有用户反馈时,还可以建立 Golden Set + Hidden Set + Production Sample 三层 Evaluation Dataset,Golden Set 是人工精心构造的高质量任务,用于验证核心能力,Hidden Set 是模型训练过程中不可见的数据,用于防止模型针对 Benchmark 过拟合,Production Sample 则从真实线上任务中抽样,用于发现真实环境中的 Long Tail Failure,这三者分别解决 基础能力是否正常、是否过拟合 和 线上真实表现如何,尤其是 Production Sample 不应该全部拿去训练,否则很容易形成 Evaluation Contamination,因此线上抽检出来的样本最好先进入 Evaluation / Analysis Pool,经过确认后再决定是否进入训练集。

python
# 三类Evaluation数据
golden_set = load_dataset("golden")
hidden_set = load_dataset("hidden")
production_sample = sample_production_trajectories()

# 三套数据独立评估
golden_result = evaluate(model, golden_set)
hidden_result = evaluate(model, hidden_set)
production_result = evaluate(model, production_sample)

做 AgentRL 后训练中非常实用的整体 Evaluation Pipeline 可以是:Offline Benchmark 判断基础能力,Scenario Test 判断不同任务类型,Programmatic Verifier 判断真实任务结果,LLM Judge 判断开放式任务质量,Trajectory Analyzer 判断工具和轨迹质量,Risk Sampling 从线上数据发现异常,Human Review 只处理高风险和高不确定样本,然后将确认后的 Bad Case 回流到 Hard Task Pool 和训练数据,这样人工并不需要查看大量正常数据,而是把人工精力集中在“系统最不知道答案的地方”。

python
# Agent整体Evaluation主循环
for traj in production_trajectories:

    # 第一层:程序化Verifier
    verifier_score = verifier.check(traj)

    # 第二层:LLM Judge
    judge_score = judge.evaluate(traj)

    # 第三层:异常检测
    anomaly = detect_anomaly(traj)

    # 第四层:不确定性检测
    uncertainty = calculate_uncertainty(
        verifier_score,
        judge_score
    )

    # 高风险样本进入人工抽检
    if (
        anomaly
        or uncertainty > UNCERTAINTY_THRESHOLD
        or verifier_score == "FAIL"
    ):
        human_review_queue.add(traj)

最终,Agent 整体效果不能简单用一个 Accuracy 衡量,而应该建立“结果 + 过程 + 成本 + 稳定性”的 Evaluation System:Task Success Rate 判断任务有没有完成,Verifier 判断最终状态是否真实正确,Tool Success Rate 判断工具调用质量,Recovery Rate 判断错误后能否恢复,Loop Rate 判断是否陷入循环,Trajectory Length 和 Token Cost 判断效率,Latency 判断服务性能,Safety Rate 判断是否出现危险行为,Judge Agreement 判断评价系统是否稳定;而在没有用户反馈的情况下,最有效的抽检方式也不是纯随机抽样,而是“分层抽样保证覆盖面 + Risk Sampling 找失败 + Anomaly Detection 找隐藏问题 + Uncertainty Sampling 找边界案例 + Judge Disagreement 找评价盲区 + 少量随机样本监控整体分布”,最终把人工抽检发现的 Failure Mode 转化成新的 Golden Case、Regression Case、SFT Data、Preference Data 或 RL Hard Task,让 Evaluation 不再只是一个打分系统,而成为 AgentRL 的数据回流入口。


AgentRL 中 SFT + RL 效果不好:Bad Case 如何回流训练

AgentRL 中如果已经完成了 SFT → RL,但线上或者 Evaluation 中仍然出现大量 bad case,通常不应该简单地把这些 bad case 全部重新拿去做一轮 SFT,因为 Agent 的 bad case 往往不是单纯的“答案错了”,而可能是 规划错误、Tool Selection 错误、参数填写错误、Observation 理解错误、长链路中途状态丢失、错误恢复失败、过早结束、无效循环、Reward Hack、工具调用次数过多或者最终答案虽然正确但轨迹成本过高,因此首先需要把 bad case 从普通的“错误样本”转换成可以训练的 Failure Trajectory,然后判断这个 failure 应该进入 SFT、Preference Optimization、RLVR、RFT 还是继续进行 On-Policy Distillation,核心思想是 Bad Case → Failure Analysis → Error Type → Construct Correct Trajectory / Preference Pair / Verifiable Reward → Targeted Training → RL → Evaluation → New Bad Case,这里最重要的是不要把“模型做错了什么”和“模型应该怎么做”混在一起,因为一条 bad trajectory 只有错误行为没有正确行为时,直接 SFT 并不能自动告诉模型正确路径是什么,所以第一步通常是对 bad case 做 Failure Attribution,例如原始 trajectory 是 User → Wrong Tool → Wrong Parameter → Tool Error → Retry Wrong Tool → Final Failure,需要判断真正的问题到底是 Tool Selection、Argument Generation、Error Recovery 还是 Planning,而不是简单标记成 reward=0。

python
# 原始Agent失败轨迹
bad_traj = {
    "task": "查询订单并取消订单",
    "steps": [
        {"action": "search_product", "result": "tool_error"},
        {"action": "search_product", "result": "tool_error"},
        {"action": "finish", "result": "failure"}
    ],
    "reward": 0
}

# 对失败轨迹进行错误归因
failure_type = classify_failure(bad_traj)

# 例如:
# TOOL_SELECTION
# ARGUMENT_ERROR
# PLANNING_ERROR
# STATE_TRACKING_ERROR
# ERROR_RECOVERY
# PREMATURE_STOP

如果 bad case 的问题是 模型根本不知道正确的 Agent 行为模式,例如模型不会调用某个工具、不会按照正确格式传参数、不会在 Tool Error 后重新规划,那么最直接的方法通常是构造 Targeted SFT 数据,也就是只针对失败行为补充高质量 Correct Trajectory,而不是把原始 bad trajectory 当作正样本训练,例如原来的 trajectory 是 User → Wrong Tool → Error → Wrong Retry → Failure,经过人工、规则 Agent 或更强 Teacher 修正后得到 User → Correct Tool → Correct Argument → Tool Result → Verify → Final Answer,然后只把 Correct Trajectory 加入 SFT Dataset。

python
# 从Bad Case构造Correct Trajectory
correct_traj = {
    "messages": [
        {
            "role": "user",
            "content": "查询订单并取消订单"
        },
        {
            "role": "assistant",
            "content": "先查询订单状态"
        },
        {
            "role": "tool",
            "name": "query_order",
            "arguments": {
                "order_id": "123"
            }
        },
        {
            "role": "tool",
            "content": "订单当前处于可取消状态"
        },
        {
            "role": "assistant",
            "content": "订单状态允许取消,现在执行取消操作"
        },
        {
            "role": "tool",
            "name": "cancel_order",
            "arguments": {
                "order_id": "123"
            }
        }
    ],

    # 最终任务成功
    "reward": 1.0
}

这里有一个非常重要的 AgentRL 训练原则:不要直接把 bad trajectory 当成 SFT 正样本,因为 SFT 的 Cross Entropy 会最大化数据中出现的 token 概率,如果数据里面是错误 Tool Call,那么模型反而会学习这个错误行为,例如 bad case 是 User → search_product → Tool Error,直接 SFT 就等于告诉模型“看到这个任务以后应该生成 search_product”,所以 bad case 的正确用法通常是 bad trajectory 用来定位问题,corrected trajectory 才作为正向 SFT 数据。

python
# 错误做法:直接把失败Trajectory加入SFT
sft_dataset.append(bad_traj)

# 正确做法:
# Bad Trajectory负责发现问题
failure_type = analyze(bad_traj)

# Correct Trajectory负责告诉模型应该怎么做
correct_traj = repair(bad_traj)

# 将修复后的轨迹加入SFT
sft_dataset.append(correct_traj)

如果 bad case 不只是“不会做”,而是模型已经会做正确行为,但 在正确轨迹和错误轨迹之间选择不稳定,那么单纯 SFT 往往不够,这时候更适合构造 Preference Data,例如同一个任务生成一条成功 trajectory 和一条失败 trajectory,让模型学习 π(correct trajectory) > π(bad trajectory),这类数据可以用于 DPO、IPO、ORPO 等 Preference Optimization,也可以继续用于 RL。

python
# 一个任务对应正确和错误两条Trajectory
preference_sample = {
    "prompt": "查询订单并取消订单",

    # 正确Agent行为
    "chosen": correct_traj,

    # 原始失败Agent行为
    "rejected": bad_traj
}

# Preference训练目标:
# 提高chosen轨迹概率
# 降低rejected轨迹概率

如果环境存在明确的 Success / Failure 判定,例如代码执行成功、SQL 查询正确、网页任务完成、工具调用结果满足约束,那么 bad case 更适合进入 RLVR,因为这时候不一定需要人工告诉模型每一步应该怎么做,只需要提供可靠的 verifier,让模型自己探索不同 trajectory,然后通过 reward 区分成功和失败,例如 success=1、failure=0,甚至可以进一步设计过程奖励 tool_success=0.2、correct_state=0.3、final_success=1.0,让 Agent 学习如何从错误行为逐渐走向成功行为。

python
# Agent执行任务
trajectory = agent.rollout(task)

# 环境Verifier判断最终结果
success = verifier.check(trajectory)

# 根据可验证结果生成Reward
if success:
    reward = 1.0
else:
    reward = 0.0

对于已经完成 SFT + RL 的 Agent,更值得做的是 Bad Case Mining + Targeted Replay,也就是不要重新训练整个历史数据,而是从 Evaluation、线上日志和 RL Rollout 中持续收集失败 trajectory,然后按照错误类型进行聚类,例如 Tool Selection Failure、Tool Argument Failure、Planning Failure、Context Tracking Failure、Error Recovery Failure、Long Horizon Failure、Premature Stop,然后统计每类 failure 的比例,如果 40% 的失败都来自 Tool Argument,那么新增数据应该集中补 Tool Argument,而不是继续增加大量普通 QA 数据。

python
# 收集线上和Evaluation产生的Bad Cases
bad_cases = collect_bad_cases()

# 按Failure Type进行聚类
clusters = cluster_by_failure_type(bad_cases)

# 统计不同错误类型占比
for failure_type, cases in clusters.items():
    ratio = len(cases) / len(bad_cases)

    print(failure_type, ratio)

例如:

plain-text
TOOL_SELECTION        32%
ARGUMENT_ERROR        27%
ERROR_RECOVERY        18%
PLANNING_ERROR        15%
PREMATURE_STOP         8%

这种情况下,最优的数据回流策略通常不是“所有 bad case 各训一遍”,而是 按照 failure distribution 做 targeted sampling,因为 AgentRL 很容易出现数据分布失衡,如果大量新增的 bad case 都来自同一种简单错误,那么模型可能会过拟合这个错误类型,同时破坏原来已经学好的能力,因此通常需要保留一部分原始高质量 SFT 数据和成功 RL trajectory,形成 Replay = Original Good Data + New Corrected Bad Cases + Successful RL Trajectories + Hard Negatives。

python
# 原始高质量SFT数据
base_data = load_dataset("sft_good")

# 从Bad Case修复得到的新数据
bad_case_fix = load_dataset("bad_case_corrected")

# RL产生的高Reward轨迹
rl_success = load_dataset("rl_success")

# Hard Negative用于Preference / Ranking训练
hard_negative = load_dataset("hard_negative")

# 混合训练数据
train_data = (
    sample(base_data, ratio=0.50)
    + sample(bad_case_fix, ratio=0.25)
    + sample(rl_success, ratio=0.20)
    + sample(hard_negative, ratio=0.05)
)

这里的比例只是说明数据构造逻辑,并不是固定经验值,真正的比例应该根据 Evaluation 分布、数据规模和模型退化情况动态调整,例如如果发现模型已经非常擅长普通任务,但在长链路 Tool Use 上失败率很高,那么就应该提高 Hard Agent Trajectory 的比例;如果发现模型经过 RL 后出现格式退化,则应该增加稳定的 SFT Anchor Data 来防止模型发生灾难性偏移。对于 AgentRL 来说,一个非常实用的策略是 SFT 负责“教会正确行为”,RL 负责“让模型在环境中学会选择正确行为”,因此 bad case 的处理可以理解为:如果模型“不会做”,优先 Corrected SFT;如果模型“会做但经常选错”,优先 Preference / RL;如果模型“长链路过程中容易崩”,优先构造 Long-Horizon Trajectory + RL;如果模型“工具调用已经正确但最终结果失败”,重点优化 Reward / Verifier;如果模型“成功率高但成本很高”,则需要加入 Tool Cost、Step Penalty 或 Efficiency Reward。

python
# 根据Bad Case决定训练方式
def select_training_method(failure_type):

    if failure_type in [
        "UNKNOWN_TOOL",
        "ARGUMENT_ERROR",
        "FORMAT_ERROR"
    ]:
        # 模型不会基本行为
        return "TARGETED_SFT"

    if failure_type in [
        "WRONG_ACTION",
        "BAD_TOOL_SELECTION"
    ]:
        # 模型会做,但策略选择错误
        return "PREFERENCE_OR_RL"

    if failure_type in [
        "LONG_HORIZON_FAILURE",
        "STATE_TRACKING_ERROR"
    ]:
        # 长链路决策能力不足
        return "AGENT_RL"

    if failure_type == "REWARD_HACK":
        # Reward设计存在问题
        return "REWARD_FIX"

    return "DATA_ANALYSIS"

如果使用 AgentRL 继续训练,那么 不要只拿 bad case 进行 Rollout,更合理的是把 bad case 作为 Hard Task Pool,提高这些任务在后续 Rollout 中的采样概率,让模型在当前最薄弱的区域进行更多探索,这和传统监督学习中“困难样本挖掘”类似,但 AgentRL 中更加重要,因为每个 bad case 都可以重新运行环境产生新的 trajectory,因此可以把一个失败任务变成很多新的探索轨迹,例如第一次模型错误调用 search_product,第二次可能调用正确 Tool,第三次可能正确调用但参数错误,第四次可能完整成功,这些 trajectory 都可以被 verifier 打分,然后形成新的 RL 数据。

python
# Bad Case作为Hard Task重新进入Rollout
hard_tasks = load_bad_tasks()

for task in hard_tasks:

    # 同一个任务进行多次探索
    trajectories = [
        rollout_worker.run(task)
        for _ in range(8)
    ]

    # 使用Verifier判断每条轨迹
    scored = [
        {
            "trajectory": traj,
            "reward": verifier.score(traj)
        }
        for traj in trajectories
    ]

    # 保存高Reward和失败轨迹
    replay_buffer.extend(scored)

这时候就会出现一个非常重要的现象:Bad Case 本身不一定是最终训练数据,它更像是一个“任务种子”,因为对于 Agent 来说,同一个失败任务重新 Rollout 后可能得到成功轨迹,所以数据回流可以从 Bad Case → Hard Task → Multi-Rollout → Verifier → Good Trajectory / Preference Pair → RL 完成闭环,这通常比简单地把失败样本复制几遍进行 SFT 更有效。特别是长链路 Agent 中,一个 bad case 可能包含 30~100 个 tool/action step,如果只把最终失败标成 reward=0,模型很难知道到底是哪一步导致失败,因此可以进一步使用 Process Reward / Step-level Credit Assignment,例如 Tool Selection 正确给小奖励、Tool 参数正确给奖励、Observation 理解正确给奖励、最终任务成功给大奖励,这样模型能够获得更加细粒度的训练信号。

python
# 对Agent每一步行为进行Reward分解
def calculate_step_reward(step):

    reward = 0.0

    # Tool选择正确
    if step.tool_valid:
        reward += 0.1

    # Tool参数正确
    if step.arguments_valid:
        reward += 0.1

    # Tool执行成功
    if step.tool_success:
        reward += 0.2

    # 当前状态判断正确
    if step.state_correct:
        reward += 0.2

    # 最终任务成功
    if step.task_completed:
        reward += 1.0

    return reward

不过 Process Reward 不能随便加,因为错误的 Reward Shaping 很容易让 Agent 学会 Reward Hack,例如模型为了获得 Tool Success Reward,不断调用一个容易成功的工具,却不完成最终任务,所以 AgentRL 中最终任务 Reward 通常仍然应该是最重要的信号,而过程 Reward 更多用于帮助 Credit Assignment。另一个非常重要的问题是 防止 Bad Case 回流导致模型遗忘原来的能力,因为如果你把新增的 bad case 修复数据全部拿去 SFT,模型可能逐渐过度适应这些任务,导致原本通用能力下降,因此通常需要保留一个 Regression Set,每次加入新 bad case 后同时测试原始 Benchmark、历史 Hard Cases 和新 Bad Cases。

python
# 原始Regression Set
regression_set = load_dataset("regression")

# 新增Hard Cases
new_cases = load_dataset("new_bad_cases")

# 三类Evaluation同时执行
result_original = evaluate(model, regression_set)
result_new = evaluate(model, new_cases)
result_general = evaluate(model, general_benchmark)

# 防止新能力提升但旧能力下降
if result_original < baseline_original - tolerance:
    reject_checkpoint()

从工程角度,最终可以建立一个 Agent Data Flywheel:线上 Agent 执行产生 trajectory → Evaluator / Verifier 自动发现 Failure → Failure Classifier 进行错误归因 → Bad Case 写入 Hard Task Pool → Teacher / Search / Self-Rollout 生成 Correct Trajectory → SFT 数据回流模型基本行为 → Multi-Rollout 生成 On-Policy Trajectory → Verifier 产生 Reward → GRPO/PPO/其他 AgentRL 更新 Policy → 新模型重新进入 Evaluation → 新的 Failure 再次进入 Hard Task Pool,这样数据就不再是一次性准备好的静态 Dataset,而是随着模型能力不断变化而动态产生的 Training Data Flywheel。

python
# AgentRL持续数据回流的核心逻辑
for iteration in range(num_iterations):

    # 1. 从线上/Evaluation收集失败任务
    bad_cases = collect_bad_cases()

    # 2. 对Bad Case进行错误归因
    failures = analyze_failures(bad_cases)

    # 3. 构造Correct Trajectory
    corrected = generate_correct_trajectories(failures)

    # 4. 对关键基础行为进行Targeted SFT
    model = targeted_sft(model, corrected)

    # 5. Hard Task进行On-Policy Rollout
    trajectories = rollout(model, bad_cases)

    # 6. Verifier计算Reward
    rewards = verifier(trajectories)

    # 7. AgentRL更新Policy
    model = agent_rl_update(model, trajectories, rewards)

    # 8. 回归测试,防止旧能力退化
    evaluate_regression(model)

因此,SFT + RL 后效果不好时,最正确的思路不是简单地“继续加数据训练”,而是把 Bad Case 当成模型当前能力边界的观测点:先判断错误属于不会、会但选错、长链路失效、Tool/Environment 问题还是 Reward 问题,然后分别构造 Corrected SFT、Preference Pair、Hard Task、On-Policy Rollout 或新的 Reward/Verifier;其中 SFT 主要解决“告诉模型正确轨迹长什么样”,RL 主要解决“让模型在真实环境中学会什么时候选择这条轨迹”,而 Bad Case Mining 则负责告诉系统“下一轮应该重点训练什么”,最终真正成熟的 AgentRL 系统不是 SFT → RL → Done,而是 SFT → RL → Evaluation → Bad Case Mining → Correct Trajectory / Hard Task → Targeted SFT + On-Policy RL → Regression → 再次Bad Case Mining,这才是 Agent 后训练真正的数据闭环。


Agent RL 训推不一致:原因和解决办法

Agent RL 中的“训推不一致”通常指 Training(Learner)侧计算 Policy Logprob、Loss 和 Gradient 时使用的模型,与 Rollout Worker 实际生成 Agent Trajectory 时使用的模型,在权重版本、Tokenizer、Sampling、Precision、Kernel、KV Cache、Prompt Template、Tool Runtime 或 Environment 等方面存在差异,这在普通 SFT 中可能只是几个浮点误差,但在 Agent RL 中会直接影响 trajectory、logprob、importance ratio、Advantage 和 Reward,因为 RL 训练本质上依赖“这条数据究竟是由哪个 Policy 采出来的”;普通 LLM 训练可以近似理解为 Dataset → Model → Loss,而 Agent RL 是 Policy → Action → Tool → Environment → Observation → Policy → Action,因此一次很小的训推差异都可能通过环境反馈不断放大,最终让训练阶段认为自己优化的是 π_old → trajectory → reward → advantage → π_new,实际上 trajectory 却来自另一个行为策略 π_behavior,这就是 Agent RL 中训推一致问题比普通 SFT、Pretrain 更严重的根本原因。最常见的问题首先是 Weight Version Mismatch,因为 Agent RL 通常把 Learner 和 Rollout Worker 分开部署,Learner 不断更新 Policy,而 Rollout Worker 不断使用模型生成 trajectory,所以很容易出现 Learner 已经更新到 v100,Rollout Worker 仍然使用 v98 的情况,此时 Rollout 产生的数据实际上来自 π98,但 Learner 当前正在优化的是 π100,如果训练算法需要 Importance Ratio,就必须知道这条 trajectory 真正来自哪个 Policy,并且保存当时的 token-level logprob,否则 ratio = π_current / π_behavior 就无法正确计算。

python
# Learner当前Policy版本
learner_version = 100

# Rollout Worker仍然使用旧版本
rollout_version = 98

# 这条trajectory实际由v98生成
trajectory = rollout_worker.generate(prompt)

# Rollout时必须记录真实Behavior Policy的logprob
trajectory.old_logprobs = rollout_worker.log_prob(trajectory.tokens)

# 同时记录Policy版本,后面用于判断Policy Lag
trajectory.policy_version = rollout_version

Learner 收到 trajectory 后重新计算当前 Policy 的 logprob,然后才能得到正确的 Importance Ratio,其中 old_logprobs 必须来自真正生成这条 trajectory 的 Behavior Policy,而不能在训练阶段重新加载一个模型后假装它是旧 Policy,因为一旦 old policy 定义错误,后续 PPO、SAO、异步 Agent RL 等方法中的 ratio 和 clipping 就失去原本的数学意义。

python
# Learner使用当前Policy重新计算logprob
new_logprobs = current_policy.log_prob(trajectory.tokens)

# 计算当前Policy相对于Behavior Policy的概率比
ratio = torch.exp(new_logprobs - trajectory.old_logprobs)

# 限制Policy更新幅度,避免旧trajectory造成过大更新
clipped_ratio = torch.clamp(ratio, 1 - epsilon, 1 + epsilon)

第二类问题是 Training Engine 和 Inference Engine 的计算实现不同,实际工程中 Training 可能使用 PyTorch + FSDP / Megatron,而 Rollout 使用 vLLM / SGLang,两边即使加载完全相同的 checkpoint,也可能由于 FlashAttention、RMSNorm、RoPE、GEMM、Softmax、KV Cache、CUDA Graph、Tensor Parallel 等实现不同而产生数值差异,例如训练侧可能使用 BF16 PyTorch Kernel,而 Rollout 侧使用 FP8 + FlashAttention + Fused Kernel,因此不能简单认为“checkpoint 一样 = logits 一样”,可以直接对同一个输入比较两边的 logits 或 logprob,建立 Train-Rollout Numerical Consistency 检查。

python
# Training Engine计算logits
logits_train = training_model(input_ids)

# Rollout Engine计算logits
logits_rollout = rollout_engine.forward(input_ids)

# 检查两套执行路径的最大数值误差
logit_error = (logits_train - logits_rollout).abs().max()

# 检查平均误差
logit_mean_error = (logits_train - logits_rollout).abs().mean()

这里要注意,Agent RL 并不一定要求 Training 和 Rollout 达到严格 bitwise identical,因为不同 GPU Kernel、并行方式和浮点精度天然会产生微小误差,真正重要的是建立一个 Numerical Error Budget,持续监控 Logprob Gap、KL Divergence 和 Policy Lag,如果这些指标突然异常,就需要检查 Kernel、Precision、Weight Sync、Tokenizer 或 Sampling 是否发生变化。

python
# 比较Training和Rollout的token-level logprob差异
logprob_gap = (train_logprobs - rollout_logprobs).abs().mean()

# 比较两套Policy分布的KL
train_rollout_kl = compute_kl(train_logits, rollout_logits)

# 如果KL异常增大,说明训推分布可能已经明显偏离
if train_rollout_kl > kl_threshold:
    raise RuntimeError("Train-Rollout policy mismatch")

第三类问题是 Sampling 不一致,Rollout Worker 真正负责从 logits 中采样 token,而 Learner 通常只是重新计算已经生成 token 的 logprob,因此两边必须明确区分“模型原始 Policy 分布”和“实际行为策略分布”,例如 Rollout 使用 temperature=0.7、top_p=0.9、tool token masking、stop token 和其他 logits processor,而 Learner 却直接对原始 logits 做 log_softmax,那么 Rollout 实际执行的是经过 sampling transformation 的 π_behavior,Learner 计算的却是另一个 π,因此训练目标和真实行为策略就会产生偏差。

python
# Rollout实际使用的Sampling配置
sampling_config = {"temperature": 0.7, "top_p": 0.9}

# 按照实际Sampling策略生成token
token = sample(logits, temperature=0.7, top_p=0.9)

第四类问题是 Tokenizer 和 Chat Template 不一致,这是生产系统中特别容易被忽略的一层,例如 Training 使用 apply_chat_template() 构造 Prompt,而 Rollout Engine 使用另一套模板,即使用户看到的文本完全一样,最终 token sequence 也可能不同,而一旦 token 不同,Position、Attention、Logits 和 Action 都会发生变化,所以应该直接对训练侧和 Rollout 侧的最终 token ids 做一致性检查。

python
# Training侧生成token
train_ids = tokenizer.apply_chat_template(messages, tokenize=True)

# Rollout侧生成token
rollout_ids = rollout_tokenizer.apply_chat_template(messages, tokenize=True)

# 两边最终输入必须保持一致
assert train_ids == rollout_ids

对于 Agent 来说,这种问题会进一步被放大,例如 Training 认为模型应该输出 {"tool":"search"},Rollout 因为 Prompt Template 或 tokenizer 不一致输出了 {"tool":"browser"},那么 Tool 返回结果就不同,下一轮 Observation 又不同,最终整个 trajectory 都会发生分叉,因此 Agent RL 的训推一致不是简单的“文本一样”,而是 最终 Token-Level State 一致。第五类问题是 KV Cache 和 Agent Context State 不一致,Agent Rollout 通常不是一次性生成完整文本,而是 Assistant → Tool Call → Tool Return → Assistant 的循环,推理框架会使用 KV Cache 保存历史计算结果,如果 Tool Return 没有正确加入 Context,或者更新 Context 后 KV Cache 没有正确处理,就可能出现模型实际上看到的是旧 Context,而不是最新的 Context + Tool Result。

python
# 执行模型产生的Tool Call
observation = tool.execute(action)

# Tool Return必须进入下一轮Agent Context
trajectory.append({"role": "tool", "content": observation})

# 基于更新后的Context继续生成
next_action = rollout_engine.generate(trajectory)

第六类问题是 Tool Runtime / Environment 不一致,这是 Agent RL 和普通 LLM RL 最大的区别之一,普通 Reasoning RL 可以近似理解成 Prompt → Answer → Reward,而 Agent RL 是 Prompt → Action → Environment → Observation → Action → Environment,因此 Python 版本、依赖包、浏览器版本、数据库状态、文件系统、API Mock、工具实现甚至随机种子都可能影响 trajectory,如果训练环境和 Rollout 环境不同,那么同一个 Action 可能得到不同 Observation,最终导致 Reward 分布发生变化,因此生产系统通常需要把 Environment Version 直接写入 trajectory metadata。

python
# 固定Agent运行环境版本
environment_version = "coding-env-v3.2"

# 将实际运行环境写入trajectory
trajectory.environment_version = environment_version

# Learner训练时检查环境版本是否符合预期
assert trajectory.environment_version == expected_env_version

第七类问题是 Learner 更新速度和 Rollout 更新速度不一致造成 Policy Lag,例如 Rollout 开始时使用 v100,Learner 随后更新到 v101、v102,但某条长 trajectory 因为 Tool Call 很多,直到很久之后才完成,此时它实际上仍然来自 v100,最终 v100 trajectory 被拿到 v102 上训练,这就是典型的 stale trajectory,在同步 Agent RL 中它表现为等待问题,在异步 Agent RL 中则表现为 off-policy 问题,因此需要记录 Policy Version 并控制最大 Policy Lag。

python
# Rollout时记录Behavior Policy版本
trajectory.policy_version = rollout_worker.version

# Learner收到trajectory后计算Policy Lag
policy_lag = current_version - trajectory.policy_version

# 过旧trajectory直接丢弃
if policy_lag > max_policy_lag:
    discard(trajectory)

也可以不直接丢弃,而是根据 Policy Lag 对 trajectory 降权,但核心思想都是一样的,即 Rollout 越旧,对当前 Policy 的训练影响应该越受限制。

python
# 根据Policy Lag计算trajectory权重
staleness_weight = compute_staleness_weight(policy_lag)

# 后续Policy Loss乘以该权重
policy_loss = policy_loss * staleness_weight

这也是为什么 Agent RL 中的 SAO、异步 PPO、异步 GRPO 变体等都会重点处理 Policy Lag,因为只要 Rollout 和 Learner 解耦,就天然存在 Behavior Policy ≠ Current Policy,所以必须使用 rollout logprob、current logprob、importance ratio 和 clipping 等机制控制这种偏差。第八类问题是 Precision / Quantization 不一致,例如 Training 使用 BF16,而 Rollout 使用 FP8,甚至训练侧使用 BF16 Weight、推理侧使用 FP8/MXFP4 Weight,那么两边 logits 不可能严格一致,这在普通 Serving 中通常问题不大,但在 RL 中 logits 会进一步影响 logprob、ratio、Advantage 和 gradient,因此需要监控这种误差是否进入不可接受的范围。

python
# Training使用BF16
logits_train = model_bf16(input_ids)

# Rollout使用FP8推理
logits_rollout = model_fp8(input_ids)

# 监控低精度造成的logit差异
logit_gap = (logits_train - logits_rollout).abs().mean()

第九类问题是 Tool Return / Environment Observation 被错误地当成模型生成 token 参与 Policy Loss,一条完整 Agent trajectory 可能包含 User、Assistant、Tool Call、Tool Return、Assistant、Tool Call、Tool Return、Assistant,但 Tool Return 是 Environment 产生的 Observation,并不是 Policy 产生的 Action,因此不能让 Tool Return token 参与 Policy Gradient,否则模型实际上会被训练去预测 Environment 的输出,所以需要为每个 token 设置 source 和 loss mask,只让模型真正生成的 Action token 参与 Policy Loss。

python
# 标记每个token来自哪里
token_source = ["user", "assistant", "tool_call", "tool_return", "assistant"]

# Tool Return不是Policy生成内容,因此不参与Policy Loss
loss_mask = [0, 1, 1, 0, 1]

这也是长链路 Agent RL 中为什么经常需要 Mask Tool Return 的核心原因:模型只能为自己采取的 Action 负责,而 Tool Return、Environment Observation 和 User Input 都属于模型决策时看到的条件信息。第十类问题是 EOS、Stop Condition 和 Tool Boundary 不一致,例如 Rollout Engine 可能识别 <eos>、</tool>、<|call_end|> 等多个停止条件,而 Training Data Collator 只识别标准 EOS,那么 Rollout 和 Training 最终得到的有效 trajectory 长度、Attention Mask 和 Loss Mask 就会不同,因此这些规则必须作为统一配置管理,而不能分别散落在训练代码和推理代码里。

python
# Training和Rollout使用统一的Stop Token集合
stop_tokens = ["<eos>", "</tool>", "<|call_end|>"]

# Rollout使用统一规则停止生成
if generated_token in stop_tokens:
    stop_generation()

第十一类问题是 Agent State Schema 不一致,Agent RL 不应该只保存 input_ids,而应该保存能够完整描述行为策略和环境状态的 trajectory metadata,例如 Policy Version、Old Logprob、Loss Mask、Reward、Tool Calls、Observations、Environment Version 等,这样 Learner 才能重新解释这条 trajectory。

python
# 标准化Agent RL trajectory
trajectory = {
    "input_ids": input_ids,                  # 完整Token序列
    "attention_mask": attention_mask,        # Attention Mask
    "old_logprobs": old_logprobs,            # Behavior Policy的真实Logprob
    "loss_mask": loss_mask,                  # 只有Action Token参与Loss
    "reward": reward,                        # Environment最终Reward
    "policy_version": policy_version,       # Behavior Policy版本
    "tool_calls": tool_calls,                # Tool调用记录
    "observations": observations,            # Environment返回结果
    "environment_version": env_version       # Environment版本
}

因此生产级 Agent RL 系统通常会建立一套自动化 Consistency Check,而不是依靠人工判断,例如检查 Policy Version、Token 数量、Logprob 数量、Loss Mask 数量、Tool Return Mask 和 Environment Version 是否一致。

python
# 检查trajectory是否来自合法Policy版本
assert trajectory.policy_version <= current_version

# Token与Logprob必须一一对应
assert len(trajectory.input_ids) == len(trajectory.old_logprobs)

# Token与Loss Mask必须一一对应
assert len(trajectory.input_ids) == len(trajectory.loss_mask)

# Tool Return不能参与Policy Loss
assert tool_return_tokens_have_zero_loss_mask(trajectory)

# Environment版本必须符合当前训练配置
assert trajectory.environment_version == expected_env_version

在大型 Agent RL Infra 中,还应该持续监控 logprob_gap、policy_lag、train_rollout_kl、trajectory_length 和 reward,因为这些指标可以帮助快速定位不同层面的训推不一致,例如 logprob_gap 突然升高通常需要检查 Kernel、Precision、Weight Sync、Tokenizer 或 Sampling,policy_lag 持续升高说明 Rollout 跟不上 Learner,train_rollout_kl 异常说明两套 Policy 分布发生明显偏离,trajectory_length 异常可能说明 Tool、Context 或 Stop Condition 出现问题,而 Reward 突然下降则需要进一步检查 Environment、Tool 和 Policy 行为。

python
# 监控Train/Rollout Logprob差异
metrics["logprob_gap"] = (train_logprobs - rollout_logprobs).abs().mean()

# 监控Policy Lag
metrics["policy_lag"] = current_version - trajectory.policy_version

# 监控Train/Rollout KL
metrics["train_rollout_kl"] = compute_kl(train_logits, rollout_logits)

# 监控Agent trajectory长度
metrics["trajectory_length"] = len(trajectory.input_ids)

# 监控最终Reward变化
metrics["reward"] = trajectory.reward

所以从 AI Infra 角度看,Agent RL 的训推一致绝对不只是“训练模型和推理模型加载同一个 checkpoint”,而是一套完整的 Train-Rollout Consistency Contract,至少需要保证 Weight Version、Tokenizer、Chat Template、Sampling、Logprob、Precision、Attention Kernel、KV Cache、Tool Runtime、Environment Version、Loss Mask 和 Policy Version 都具有明确且可验证的一致性定义;普通 LLM 中一次数值误差可能只是 logits 有一点变化,而 Agent RL 中一次微小差异可能变成 Model → Tool Call → Environment → Observation → Model 的状态分叉,最终导致整个 trajectory、Reward 和 Policy Gradient 都发生变化,因此真正的解决方案不是单纯追求 bitwise identical,而是建立 统一 Model/Tokenizer → 统一 Prompt/Chat Template → 记录 Behavior Policy Version → 保存 Rollout Logprob → 统一 Sampling/Precision → 锁定 Tool/Environment → 正确 Mask Tool Return → 控制 Policy Lag → 监控 Logprob Gap/KL 的完整体系,最终保证训练侧和 Rollout 侧对同一条 Agent trajectory 的 Policy、Action、Observation、Probability 和 Loss 定义一致且可验证。


Agent 轨迹长短不一:GRPO 为什么会被最慢样本拖住,以及 SAO 为什么能解决

在 Agentic RL 中,GRPO 最大的工程问题之一并不是 GRPO 的 Policy Objective 本身,而是它的 Group-wise Sampling 与长链路 Agent Rollout 存在天然的同步屏障(synchronization barrier)。传统 LLM Reasoning 的一个 Prompt 往往几十到几百个 token 就能完成,但是 Agent 任务可能需要几十次甚至上百次 Tool Call,而且不同任务的执行路径长度高度不一致,例如一个 Coding Agent 可能 20 步就完成,另一个任务可能需要 100 步,第三个任务可能因为反复调试需要 300 步,所以同一个 GRPO group 中的 trajectory 很容易出现 T1=40, T2=60, T3=100, T4=280 这样的长度分布;如果系统采用同步 Rollout,那么不是某条轨迹完成就可以训练,而是必须等待这个 group 中所有 rollout 都完成之后,才能计算 group-relative reward / advantage,因此整个训练 batch 的完成时间近似由最长 trajectory 决定,也就是典型的 straggler problem(拖尾样本问题)。SAO(Single-Rollout Asynchronous Optimization)正是针对这个问题提出的,它不是简单地把 GRPO 的 rollout 改成异步,而是直接把 group-wise sampling 改成 single-rollout sampling,让一条 trajectory 完成之后立即进入训练,同时用 Value Model 替代 GRPO 原来的 group-relative baseline,再结合严格的双侧 token-level clipping 控制异步训练带来的 policy lag 和 off-policy 问题。2026 年公开的 SAO 论文明确指出,GRPO 的 group-wise sampling 与异步 Agentic RL 不匹配,因为同一 group 必须等待全部 trajectory 完成;SAO 则采用每个 prompt 一条 rollout 的方式,并通过 value model 和严格的 token-level clipping 稳定训练,论文还报告其用于 GLM-5.2 的 Agentic RL pipeline。(arXiv)

先看为什么 Agent 轨迹长度不一致会把 GRPO 拖住,假设我们有一个 GRPO group,需要针对同一个 Prompt 采样 8 条 Agent trajectory:

python
# 一个Prompt生成8条Agent轨迹
# 每条轨迹长度都可能不同

trajectories = [
    rollout(prompt, max_steps=50),    # 实际执行 30 steps
    rollout(prompt, max_steps=50),    # 实际执行 35 steps
    rollout(prompt, max_steps=50),    # 实际执行 42 steps
    rollout(prompt, max_steps=50),    # 实际执行 48 steps
    rollout(prompt, max_steps=200),   # 实际执行 80 steps
    rollout(prompt, max_steps=200),   # 实际执行 120 steps
    rollout(prompt, max_steps=300),   # 实际执行 180 steps
    rollout(prompt, max_steps=300),   # 实际执行 280 steps
]

# GRPO需要等待整个group完成
# 因此训练开始时间由最长trajectory决定

wait_until_all_finished(trajectories)

# 所有结果完成后才能计算Group Relative Advantage
rewards = [
    evaluate(traj)
    for traj in trajectories
]

advantages = normalize(rewards)

真正的问题不是“最长 trajectory 多生成了 200 token”,而是 GPU / CPU / Environment / Tool Worker 的资源利用率会被同步 barrier 限制。假设 8 个 rollout worker 同时开始:

plain-text
Worker 1:██████████████████████████ → 完成
Worker 2:████████████████████████████ → 完成
Worker 3:██████████████████████████████ → 完成
Worker 4:████████████████████████████████ → 完成
Worker 5:████████████████████████████████████████ → 完成
Worker 6:████████████████████████████████████████████████ → 完成
Worker 7:████████████████████████████████████████████████████████████ → 完成
Worker 8:████████████████████████████████████████████████████████████████████████████████ → 最慢

前 7 个 Worker 即使已经完成,也不能直接推动 GRPO 更新,因为 Group 的 Reward 和 Advantage 需要完整 group 才能计算。因此系统实际变成:

plain-text
短 Rollout 完成 → 等待
短 Rollout 完成 → 等待
短 Rollout 完成 → 等待
短 Rollout 完成 → 等待
长 Rollout 完成 → 等待
最长 Rollout 完成 → Group Ready → GRPO Update

这就是为什么 Agentic RL 中的 GRPO 会产生严重的 straggler effect,而且随着 Agent trajectory 长度增加,这个问题会越来越严重,因为传统同步 RL 的有效吞吐并不是所有 Worker 的平均速度,而更接近:

python
# 同步Batch的有效完成时间
# 由Batch中最慢的trajectory决定

batch_time = max(trajectory_times)

如果 64 个 Agent trajectory 的平均执行时间是 30 秒,但是最长 trajectory 是 180 秒,那么系统不能简单认为平均 30 秒就能完成一个 training batch,因为同步 barrier 会把整个 batch 卡在 180 秒;这也是 Agentic RL 与普通 LLM RL 一个非常重要的区别,普通 Reasoning rollout 的长度虽然也存在差异,但是 Agent trajectory 中还存在 Tool Call、环境交互、代码执行、网络请求、浏览器操作等不可预测延迟,因此它不仅是 token generation length 不一致,更是 environment latency + model inference latency + tool execution latency 的综合不确定性。

python
# Agent trajectory的真实耗时
# 不只是LLM生成token

trajectory_time = (
    llm_generation_time
    + tool_execution_time
    + environment_wait_time
    + observation_processing_time
)

因此 Agentic RL 中真正需要优化的是:

plain-text
LLM Rollout Throughput + Environment Throughput + Training Throughput

而不是单纯提高 GPU Token/s。

GRPO 的第二个问题来自它的 Group Relative Advantage,因为 GRPO 的核心优势就是同一个 Prompt 下生成多个 response,然后通过组内 Reward 计算相对优势,例如:

python
# 同一个Prompt生成多个结果

rewards = [
    1.0,
    0.8,
    0.2,
    0.0
]

# 计算group平均Reward
mean_reward = rewards.mean()

# 每条trajectory相对于group平均值的优势
advantages = rewards - mean_reward

这里存在一个关键依赖:

plain-text
必须知道整个Group的Reward
            ↓
才能计算Group Mean
            ↓
才能计算Relative Advantage
            ↓
才能进行GRPO Update

所以你不能简单地:

python
# 第一条trajectory完成
update(traj_1)

# 第二条trajectory完成
update(traj_2)

因为此时:

plain-text
traj_1的Group Mean还不知道

如果强行异步更新,就会破坏原始 GRPO 的 group-relative assumption,而且不同 rollout 可能来自不同时间点的 Policy,进一步产生 policy lag / off-policy drift。SAO 论文正是把这个问题视为核心:异步 RL 虽然可以消除等待,但 rollout 来自不同版本的 Policy 后,会产生更加严重的 off-policy 问题,而 GRPO 的 group-wise sampling 又天然不适合这种异步训练。(arXiv)

所以 SAO 的核心变化不是:

plain-text
GRPO + Async Scheduler

而是:

plain-text
Group-wise Rollout
        ↓
Single-rollout
        ↓
Value-based Advantage
        ↓
Asynchronous Update

也就是说,SAO 从算法层面取消了“必须等同组其他样本”的依赖。一个 Prompt 只产生一条 trajectory,这条 trajectory 完成之后就可以进入 Learner:

python
# SAO不需要等待同一个Prompt的其他trajectory
trajectory = rollout_worker.run(prompt)

# trajectory完成之后立即进入训练队列
training_queue.put(trajectory)

# Learner可以直接消费已经完成的trajectory
learner.update(trajectory)

这样整个系统从:

plain-text
Prompt
 → Rollout 1
 → Rollout 2
 → Rollout 3
 → Rollout 4
 → 等待最长Rollout
 → Group Advantage
 → Update

变成:

plain-text
Prompt A → Rollout完成 → Update
Prompt B → Rollout完成 → Update
Prompt C → Rollout完成 → Update
Prompt D → Rollout完成 → Update

因此一个 Worker 完成就可以继续生产下一个 trajectory,Learner 也不需要等待整个 Group,这实际上把 Rollout 和 Training 从同步 Pipeline 变成异步 Pipeline。

python
# Rollout Worker
# 负责不断生成Agent trajectory

while True:
    task = task_queue.get()
    trajectory = rollout(task)

    # trajectory完成立即发送给Learner
    result_queue.put(trajectory)
python
# Learner
# 不等待完整Batch,而是持续消费完成的trajectory

while True:
    trajectory = result_queue.get()
    # 使用单条trajectory计算Advantage
    advantage = compute_advantage(trajectory)

    # 立即更新Policy
    update_policy(
        trajectory,
        advantage
    )

但是这里出现一个新的问题:如果模型在不断更新,那么 Rollout Worker 生成 trajectory 时使用的 Policy 和 Learner 当前正在训练的 Policy 很可能已经不是同一个版本。 例如:

plain-text
t0:
Rollout使用 π0

↓

生成trajectory


t1:

Learner更新

π0 → π1


t2:

Learner继续更新

π1 → π2


t3:

刚才的trajectory才到达Learner

此时:

plain-text
Trajectory来自:π0

当前Policy:π2

所以它已经不是严格 On-policy 数据,而是:

plain-text
Old Policy Data
        ↓
Current Policy Training

这就是异步 RL 中的 Policy Lag / Off-policy Drift 问题,因此 SAO 不能只解决“等待最慢样本”,还必须解决“样本已经过时”的问题。SAO 的解决思路是使用 Rollout Policy 的 log probability 和当前 Policy 的 log probability 做 token-level importance ratio,然后进行严格的双侧 clipping,从而限制旧 trajectory 对当前 Policy 更新的影响。(alphaXiv)

python
# Rollout阶段记录旧Policy的token log probability
trajectory.old_logprobs = rollout_policy.log_prob(trajectory.tokens)


# Learner阶段重新计算当前Policy的log probability
new_logprobs = current_policy.log_prob(trajectory.tokens)


# Importance Sampling Ratio
ratio = torch.exp(new_logprobs - trajectory.old_logprobs)


# 严格限制Policy变化范围
# 防止过旧trajectory对当前模型产生过强更新
ratio = torch.clamp(
    ratio,
    1 - epsilon_low,
    1 + epsilon_high
)

这里 SAO 的 double-side token-level clipping 非常关键,因为 Agent trajectory 很长,一个 trajectory 中可能包含大量 token,而不同 token 的 policy lag 程度并不一样,如果只对整个 sequence 做一个平均 ratio,少数严重偏离的 token 可能仍然影响梯度;SAO 因此直接在 token level 对 importance ratio 进行严格双侧约束,从而降低异步训练中的 off-policy instability。论文摘要明确将这一策略作为 SAO 稳定训练的重要组成部分。(arXiv)

python
# 每一个token都有自己的importance ratio
ratio_t = torch.exp(
    new_logprob_t
    - old_logprob_t
)


# token级别进行双侧clip
clipped_ratio_t = torch.clamp(
    ratio_t,
    1 - epsilon_low,
    1 + epsilon_high
)


# 使用token级别的ratio计算Policy Loss
policy_loss = -torch.mean(
    torch.min(
        ratio_t * advantage_t,
        clipped_ratio_t * advantage_t
    )
)

另一个关键变化是:GRPO原本依靠Group Mean提供baseline,而SAO只有一个trajectory,因此必须解决单样本没有group-relative baseline的问题。 SAO 引入 Value Model,通过 Critic 估计状态价值,然后使用 GAE 等方式计算 token/action-level Advantage,因此:

plain-text
GRPO:

Reward
  ↓
Group Mean
  ↓
Relative Advantage


SAO:

Reward
  ↓
Value Model
  ↓
GAE
  ↓
Advantage

例如:

python
# Critic预测每个状态的Value
values = critic(states)

# 根据reward和value计算GAE
# 用于估计每个action对最终任务的贡献
advantages = compute_gae(rewards,values)

这实际上是 SAO 一个很重要的思想:为了摆脱 GRPO 的 Group Barrier,它牺牲了“无需 Critic”的优势,重新引入 Value Model;换句话说,它用 Critic 的计算成本换取异步 Agent RL 的吞吐和单 rollout 更新能力。 这也是为什么不能简单理解成“SAO = 异步 GRPO”,因为 GRPO 的 Group Relative Advantage 和 SAO 的 Value-based Advantage 在算法上已经发生了变化。公开资料也明确指出,SAO 将 single-rollout sampling 与 value-model training 结合起来,以适应异步 Agentic RL。(Liuzhao09)

python
# GRPO:
# Advantage依赖同一个Prompt下的多个样本

group_advantage = ( reward - group_mean_reward) / ( group_std + 1e-8)


# SAO:
# Advantage来自Critic对当前状态价值的估计
value = critic(state)

advantage = compute_gae(
    reward,
    value
)

从 AI Infra 角度来看,真正的优化逻辑其实是把 同步 Batch Barrier 消掉。传统 GRPO 更像:

plain-text
Rollout Workers → Group Barrier → Reward → Advantage → Learner

SAO 更像:

plain-text
Rollout Workers → Completed Trajectory Queue → Critic/Advantage → Learner

所以 SAO 的收益不只是“训练更快”,而是让 Rollout Generation 和 Policy Optimization 可以真正解耦:短 trajectory 不需要等待长 trajectory,Rollout Worker 完成一个任务后可以立即接下一个任务,Learner 则持续消费已经完成的 trajectory,因此 GPU/CPU/Environment 的利用率可以显著提高;但与此同时,系统必须处理 trajectory 到达时间不同造成的 policy lag,因此需要 rollout_logprob + current_logprob + token-level clipping + value model 这一整套机制。SAO 论文报告其在 Agentic coding/reasoning benchmark 上超过 GRPO 及其变体,并能够稳定训练约 1000 steps,同时被用于 GLM-5.2 的 Agentic RL pipeline。(arXiv)

python
# 一个简化的SAO训练主循环

while training:
    # Rollout Worker异步生成一条trajectory
    trajectory = rollout_worker.generate(task)

    # 记录生成时Policy的token logprob
    # 后面用于处理policy lag
    trajectory.old_logprobs = ( rollout_worker.logprobs  )

    # Critic估计状态价值
    values = critic( trajectory.states)

    # 单trajectory也可以计算Advantage
    # 不再需要等待同Prompt的其他rollout
    advantages = compute_gae(
        trajectory.rewards,
        values
    )

    # 当前Policy重新计算logprob
    new_logprobs = actor.log_prob(trajectory.tokens)

    # 计算importance ratio
    ratio = torch.exp(new_logprobs  - trajectory.old_logprobs)

    # 双侧token-level clipping
    clipped_ratio = torch.clamp(
        ratio,
        1 - epsilon_low,
        1 + epsilon_high
    )

    # SAO Policy Loss
    policy_loss = -torch.mean(
        torch.min(
            ratio * advantages,
            clipped_ratio * advantages
        )
    )

    # 更新Actor
    optimizer.zero_grad()
    policy_loss.backward()
    optimizer.step()

所以你可以把这个问题记成一句话:GRPO 被最慢样本拖住,是因为它的 Group Relative Advantage 要求同一个 group 的 rollout 全部完成后才能更新,而 Agent 轨迹长度和工具执行时间高度不一致,最终形成 straggler + synchronization barrier;SAO 的核心解决办法不是简单“让 GRPO 异步”,而是直接把 Group Sampling 改成 Single-Rollout Sampling,让一条 trajectory 完成就训练,再用 Value Model 提供单样本 Advantage,并用严格的双侧 token-level clipping 抑制异步更新产生的 policy lag 和 off-policy drift。 从系统角度就是:GRPO 优化的是“等齐一组再比较”,SAO 优化的是“谁先完成谁先学”;GRPO 的瓶颈是 Group Barrier,SAO 的核心是 Single Rollout + Async Learner + Value Baseline + Token-level Trust Region。 (arXiv)


AgentRL方法选择PPO还是GRPO

在 Agentic RL(Agent Reinforcement Learning)中,PPO 和 GRPO 的核心区别并不是简单的算法替换,而是两种不同的 Policy Optimization 思路。PPO(Proximal Policy Optimization)继承传统强化学习 Actor-Critic 架构,通过额外训练 Value Model 估计状态价值,再利用 Advantage Function 更新 Agent Policy;GRPO(Group Relative Policy Optimization)则针对大模型 Reasoning 和 Agent 场景重新设计,通过同一个 Prompt 下多个 Rollout 结果之间的相对 Reward 计算优势,不再训练 Critic,从而降低 RL 后训练成本。因此从 Agent Infra 角度看,PPO 更像完整强化学习系统,而 GRPO 更像针对 LLM Agent 特性的高效策略优化方法。两者本质区别可以理解为:PPO需要回答“当前状态下这个动作未来价值是多少”,GRPO只需要回答“同一个任务下这些候选行为哪个更好”。PPO的核心结构是 Actor + Critic + Reward Model,而GRPO结构是 Actor + Group Rollout + Reward Function。

plain-text
PPO:Policy πθ → Rollout → Reward → Value Model → Advantage → Policy Update


GRPO:Policy πθ → Multiple Rollout → Group Reward Ranking → Relative Advantage → Policy Update

在 PPO 中,Agent 首先由 Actor Policy 根据当前状态生成动作,然后通过 Environment 或 Reward Model 获得奖励,同时 Critic Model 预测当前状态价值,最终通过 Advantage 衡量当前动作相比平均水平是否更优。例如 Coding Agent 中,状态可能是当前代码仓库,Action 是修改代码或者执行测试命令,Reward 是测试是否通过。PPO认为,一个动作的好坏不仅取决于当前奖励,还取决于它未来可能带来的收益,因此需要 Value Function 估计长期价值。

PPO基本结构:

python
# Actor模型:
# 负责根据当前状态生成Agent动作
action = actor.generate(state)

# 环境执行Agent动作
# 例如:
# 修改代码、调用terminal、访问网页
result = environment.execute(action)

# Reward模型评价结果
# 例如:
# 测试通过 +10
# 测试失败 0
reward = reward_model(result)

# Critic预测当前状态未来价值
value = critic(state)

# Advantage表示:
# 当前动作比Critic预测值好多少
advantage = reward - value

PPO 更新的核心是 Clipped Policy Objective,它限制新旧 Policy 差异,避免一次更新导致模型能力崩溃。数学上:

python
# 当前Policy概率
new_prob = policy(action)

# 老Policy概率
old_prob = old_policy(action)

# Policy变化比例
ratio = new_prob / old_prob

# PPO限制更新幅度
# 防止模型突然偏离旧策略
clipped_ratio = torch.clamp(
    ratio,
    1-epsilon,
    1+epsilon
)


# PPO Loss
loss = -torch.min(
    ratio * advantage,
    clipped_ratio * advantage
).mean()

PPO最大的优势是能够处理长链路决策问题,因为 Critic 可以学习状态价值。例如 Browser Agent 执行任务:

plain-text
打开网页 → 搜索信息 → 点击页面 → 填写表单 → 提交任务

最终 Reward 可能只在最后一步产生,但是 Critic 可以学习:

plain-text
当前搜索行为虽然没有直接奖励,但是增加最终成功概率,因此Value提高。

因此 PPO 更适合:

plain-text
机器人控制、Browser Agent、复杂游戏Agent、长期规划Agent。

但是 PPO 在大模型 Agent 训练中最大的缺点是成本非常高,因为它需要额外维护 Value Model。一个70B模型进行PPO训练时,通常需要:

python
# Actor:
# 当前正在优化的大模型
actor = LLM_70B()

# Critic:
# 额外训练一个Value模型
critic = Value_70B()

# Reference:
# 防止模型偏离原始模型
reference = LLM_70B()

# Reward:
# 判断结果质量
reward_model = RM_7B()

这意味着一次训练可能同时运行:

plain-text
Actor + Critic + Reference + Reward Model

显存压力、通信成本和训练复杂度都会非常高。尤其是在 Agent 场景中,trajectory 往往很长:

plain-text
用户任务
 ↓
Agent思考
 ↓
调用工具
 ↓
观察结果
 ↓
继续规划
 ↓
最终完成

Value Model 需要预测几十甚至上百步之后的结果,因此 Credit Assignment(奖励归因)非常困难。GRPO 的核心思想就是去掉 Critic。它认为,对于 LLM Agent 来说,很多任务并不需要知道精确价值,只需要比较多个候选答案哪个更好。例如数学推理任务:

plain-text
Prompt:

Solve:
x²+2x+1=0

模型采样多个结果:

python
# 同一个问题生成多个Agent轨迹
responses = [
    answer_1,
    answer_2,
    answer_3,
    answer_4
]

Reward:

python
# 对每个答案进行评分
rewards = [
    0.9,
    0.7,
    0.3,
    0.1
]

GRPO 不训练 Value,而是在 Group 内计算相对优势:

python
import torch

# Reward平均值
mean_reward = torch.mean(rewards)

# Reward标准差
std_reward = torch.std(rewards)

# Group Relative Advantage
advantages = (rewards - mean_reward) / (std_reward + 1e-8)

结果:

plain-text
高Reward答案:
advantage > 0
增加概率


低Reward答案:
advantage < 0
降低概率

因此 GRPO 的核心不是:

plain-text
学习状态价值

而是:

plain-text
学习什么样的输出更容易获得高Reward。

GRPO更新仍然类似PPO:

python
# 当前模型输出概率
new_logprob = policy(response)

# 旧模型输出概率
old_logprob = old_policy( response)

# Policy变化比例
ratio = torch.exp(new_logprob - old_logprob)

# GRPO Clip Loss
loss = -torch.min(
    ratio * advantage,
    torch.clamp(
        ratio,
        1-epsilon,
        1+epsilon
    ) * advantage
).mean()

两者最大区别:

python
# PPO Advantage
advantage = reward - value_prediction

# GRPO Advantage
advantage = ( reward - group_mean_reward) / group_std

PPO需要:

plain-text
Reward → Value估计 → Advantage

GRPO:

plain-text
Reward → Group比较 → Advantage

因此 GRPO 大幅降低训练成本。在 Agentic RL 中,GRPO 为什么越来越流行,主要原因是 LLM Agent 天然适合 Sampling-based Learning。传统强化学习环境中,采样成本非常高,例如机器人一次实验可能需要几分钟甚至几小时;但是 LLM Agent 的 Rollout 可以直接通过 GPU 批量生成。例如一个 Coding Agent:

python
# 一个任务生成多个解决方案
trajectories = actor.generate(
    prompt,
    num_samples=16
)


# 执行测试
for traj in trajectories:
    reward = run_test(traj)


# 根据测试结果更新模型

这非常符合 GRPO:

plain-text
同一个任务
 →
多个Agent尝试
 →
比较结果
 →
强化优秀路径

例如 SWE Agent:

plain-text
Agent A:

修改3个文件

Test Pass


Agent B:

修改1个文件

Test Fail


Agent C:

修改10个文件

Test Pass

只需要:

plain-text
Pass > Fail

不需要训练:

plain-text
这个状态未来价值是多少。

因此 GRPO 特别适合:

plain-text
数学Reasoning Agent

Code Agent

SWE Agent

Deep Research Agent

Tool-use Agent

但是 GRPO 也有缺点,它依赖 Group Sampling。如果采样数量太少:

python
num_samples = 2

可能:

plain-text
无法发现更优策略。

如果 Reward 设计不好:

python
reward = test_pass

模型可能:

plain-text
找到Reward Hack

而不是解决真实问题。

因此 GRPO 对 Reward Engineering 要求更高。PPO 和 GRPO 在 Agentic RL 中的核心区别:

plain-text
PPO:利用Critic学习长期价值

优点:
长期决策能力强
缺点:
训练成本高


GRPO:利用Group Reward学习相对优势

优点:
无需Critic,成本低
缺点:
依赖采样和Reward质量

从工程部署角度:

python
# PPO训练框架
trainer = PPOTrainer(
    actor=model,
    critic=value_model,
    reward_model=rm
)


trajectory = rollout()

advantage = compute_gae(
    trajectory,
    critic
)

trainer.update(advantage)

GRPO:

python
# GRPO训练框架

trainer = GRPOTrainer(
    actor=model,
    reward_model=rm
)


# 一个Prompt生成多个Agent轨迹
outputs = rollout(num_samples=8)


# 环境评分
rewards = evaluate( outputs)


# 计算相对优势
advantages = normalize( rewards)


# 更新模型
trainer.update(
    outputs,
    advantages
)

在 VeRL、TRL 等 Agent RL Framework 中,目前两种路线都存在。对于 Reasoning Model,例如数学推理、代码生成,大量研究采用 GRPO,因为模型可以通过大量 Sampling 获得有效训练信号;对于真实 Environment Agent,例如机器人、Browser Agent、复杂工具调用系统,PPO仍然具有优势,因为长期状态价值估计能够缓解稀疏奖励问题。

因此选择原则可以总结为:如果你的 Agent 任务具有明确最终评价标准,并且可以大量生成多个候选轨迹,例如“代码是否通过”“答案是否正确”“工具调用是否成功”,优先考虑 GRPO;如果任务具有复杂环境交互、长期规划、奖励延迟、状态连续变化,例如机器人控制、Browser 自动化、复杂企业流程 Agent,则 PPO 更合适。未来的大模型 Agent 后训练趋势更可能是 PPO 和 GRPO 的结合,例如先使用 GRPO 快速提升 Reasoning 和 Tool-use 能力,再使用 PPO 优化长期任务执行稳定性。简单理解:PPO学习“这个动作未来值多少”,GRPO学习“这一批答案里面哪个更像专家行为”,而当前大语言模型 Agent 的大量任务天然更容易比较结果,因此 GRPO 正成为 Agentic RL 后训练的重要方向。


OPD中用Forward KL还是Reverse KL

在大模型后训练中的 OPD(On-Policy Distillation)里,KL 散度的方向选择是一个非常关键的问题,因为它决定了 Student Model 如何吸收 Teacher Model 的能力。很多人认为 OPD 就是简单地让小模型模仿大模型,但实际上 OPD 的核心目标不是复制 Teacher 的输出,而是在 Student 自己产生的 on-policy trajectory 分布上,让 Student 的策略分布向 Teacher 靠近。因此 KL 方向直接影响探索能力、模式覆盖、推理能力保留以及模型是否会发生 mode collapse。

OPD 的基本形式:

plain-text
Teacher Policy πT → 提供目标分布

Student Policy πθ → 生成自己的trajectory

优化:

让 πθ 接近 πT

常见目标:

python
loss = KL(
    student_policy,
    teacher_policy

)

但是这里存在两个方向:

plain-text
Forward KL:
KL(Teacher || Student)


Reverse KL:
KL(Student || Teacher)

两者虽然名字相似,但是训练效果完全不同。

Forward KL:Teacher 分布指导 Student 覆盖能力

python
KL( π_teacher || π_student)

展开:

python
import torch

def forward_kl(
    teacher_logits,
    student_logits
):

    # Teacher概率分布
    teacher_prob = torch.softmax(
        teacher_logits,
        dim=-1
    )


    # Student log概率
    student_log_prob = torch.log_softmax(
        student_logits,
        dim=-1
    )


    # KL(Teacher || Student)
    loss = torch.sum(
        teacher_prob * (torch.log(teacher_prob) - student_log_prob ),
        dim=-1
    )


    return loss.mean()

含义:

plain-text
Teacher认为可能的token,Student都应该能够预测。

例如:

Teacher:

python
Teacher distribution:

Paris      0.6
London     0.3
Berlin     0.1

Student:

python
Student:

Paris      0.9
London     0.05
Berlin     0.05

Forward KL 会惩罚:

plain-text
Student没有覆盖Teacher中的London、Berlin概率。

因此:

plain-text
Forward KL 更强调mode covering。

也就是:

plain-text
覆盖Teacher所有合理行为。

在传统 Knowledge Distillation 中:

plain-text
Teacher:

large model

↓

Student:

small model

通常使用 Forward KL。

原因:Teacher 是更强模型,认为

plain-text
这些答案都有一定概率。

Student 不应该只学习最高概率答案。

例如:

数学推理:

Teacher:

python
answer:

A:0.5
B:0.4
C:0.1

如果Student只学习:

plain-text
A

可能损失推理路径。

Forward KL 保留:

plain-text
Teacher uncertainty distribution。

Reverse KL:Student主动靠近Teacher高概率区域

python
KL( π_student || π_teacher)

代码:

python
def reverse_kl(
    teacher_logits,
    student_logits
):

    # Student概率
    student_prob = torch.softmax(
        student_logits,
        dim=-1
    )


    # log Student
    student_log_prob = torch.log_softmax(
        student_logits,
        dim=-1
    )


    # log Teacher
    teacher_log_prob = torch.log_softmax(
        teacher_logits,
        dim=-1
    )


    loss = torch.sum(
        student_prob * ( student_log_prob - teacher_log_prob),
        dim=-1
    )


    return loss.mean()

含义:

plain-text
Student产生什么行为,就要求Teacher也认为这个行为合理。

例如:

Teacher:

python
Paris     0.7
London    0.2
Tokyo     0.1

Student:

python
Paris     0.4
London    0.3
Mars      0.3

Reverse KL:

重点惩罚:

plain-text
Student产生Teacher低概率甚至不存在的token。

因此:

plain-text
Reverse KL 更强调mode seeking。

即:

plain-text
寻找Teacher最优模式。

为什么 OPD 更倾向使用 Reverse KL?在 Agentic RL 场景中,OPD 和普通 KD 最大区别:

普通 KL:

plain-text
Teacher生成数据

↓

Student学习

数据分布:

plain-text
Teacher distribution

而 OPD:

plain-text
Student自己rollout

↓

Teacher评价

↓

Student更新

数据分布:

plain-text
Student on-policy distribution

因此:

plain-text
训练数据来自Student,而不是Teacher。

这改变了 KL 的选择。

OPD:

python
trajectory = student.rollout(task)

teacher_score = teacher.evaluate(

    trajectory

)

然后:

python
loss = KL( student_policy  ||  teacher_policy)

为什么?

因为:

plain-text
Student已经探索了一条路径。

Teacher负责告诉它:

这条路径是否接近专家行为。

此时最重要的是:

plain-text
避免Student探索错误区域。

因此 Reverse KL 更自然。

OPD中的实现(简化好记版):

python
for batch in dataset:


    # Student自己采样

    trajectories = student.generate(  batch  )


    # Teacher计算log probability
    with torch.no_grad():
        teacher_logits = teacher(  trajectories  )


    # Student重新计算概率
    student_logits = student( trajectories )


    # Reverse KL
    loss = reverse_kl(
        teacher_logits,
        student_logits
    )


    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

这里:

plain-text
采样分布:

Student


优化目标:

Student → Teacher

Agentic RL 中为什么不能简单使用 Forward KL

Agent任务:

plain-text
Coding Agent

↓

Tool Calling

↓

Environment Interaction

↓

Long Horizon Planning

Action空间:

python
actions=[

    "call_python",

    "search",

    "edit_file",

    "finish"

]

Teacher:

python
πT(action)

Student:

python
πS(action)

如果使用 Forward KL:

python
KL(πT ||πS)

会要求:

plain-text
Student覆盖Teacher所有可能动作。

问题:

Agent动作空间巨大:

plain-text
百万级tool arguments

无限状态空间

长chain trajectory

导致:

plain-text
Student学习大量低价值探索。

Reverse KL:

python
KL(πS ||  πT)

效果:

plain-text
Student自己的trajectory中:

哪些行为Teacher认为有效?

保留。


哪些行为Teacher认为错误?

降低概率。

更加适合:

plain-text
Agent Policy Improvement。

OPD + RL 通常混合使用 KL

实际大模型训练不会只使用 KL,而是:

python
loss = ( rl_loss +  beta * kl_loss)

例如:

python
def opd_rl_loss(

    policy_loss,

    student_logits,

    teacher_logits,

    beta=0.05

):


    kl = reverse_kl(

        teacher_logits,

        student_logits

    )


    return (

        policy_loss

        +

        beta * kl

    )

其中:

plain-text
RL Loss:
鼓励探索更高reward


KL Loss:
防止偏离Teacher

KL coefficient β 如何影响 OPD

β太小:

python
beta = 0.001

结果:

plain-text
Student过度探索

容易退化

hallucination增加

β太大:

python
beta = 1.0

结果:

plain-text
Student完全复制Teacher

失去RL优化空间

通常:

python
beta:

0.01 ~ 0.1

根据任务调整。

ShortOPD / Dynamic OPD 中 KL 的变化

plain-text
短trajectory rollout

↓

Teacher提供dense signal

↓

Student快速更新

由于trajectory短:

plain-text
distribution gap较小

所以:

plain-text
Reverse KL权重可以提高。

例如:

python
beta = 0.1

长链Agent:

plain-text
几十步tool call

↓

error accumulation

通常:

python
beta = 0.01

避免限制探索。

Forward KL、Reverse KL、Jensen-Shannon KL 对比

KL类型 优化方向 特点 OPD适合度
Forward KL Teacher→Student 保留Teacher所有模式 普通KD高
Reverse KL Student→Teacher 聚焦Teacher高概率行为 Agent OPD高
JS KL 双向 更稳定 混合训练

代码:

python
loss = (

    0.5 * KL(teacher,student)

    +

    0.5 * KL(student, teacher)

)

OPD 中 KL 方向的选择,本质取决于数据分布来源。如果数据来自 Teacher 生成,那么类似传统 Knowledge Distillation,Forward KL 更合理,因为目标是让 Student 拟合 Teacher 的完整概率分布;但是 Agentic OPD 的核心特点是 Student on-policy rollout,Teacher只负责提供监督信号,因此训练目标通常更接近 Reverse KL,即让 Student 当前探索到的行为分布向 Teacher 高质量行为区域靠近。实际的大模型 Agent 后训练,例如 Self-Distilled Agentic RL、On-policy Distillation、Long-horizon Agent Training,通常会将 Reverse KL 作为主要约束,同时结合 RL reward loss,通过 RL objective + β × KL constraint 的形式,在保持探索能力的同时避免 Student 偏离 Teacher 能力边界。换句话说,Forward KL 更像“教学生完整复制老师知识分布”,Reverse KL 更像“让学生自己的行为逐渐变成专家认可的行为”,而 Agentic RL 的目标通常属于后者。


Agentic RL训练框架梳理-含代码

Agentic RL(Agentic Reinforcement Learning)并不是一种新的强化学习算法,而是一套专门用于训练 Agent 能力的大模型后训练框架,它的目标不是让模型回答一个问题,而是让模型能够自主规划任务、调用工具、与环境持续交互、根据反馈调整策略,并最终完成一个真实任务。因此 Agentic RL 的训练对象不再是一轮 Prompt→Response,而是一条完整的 Agent Trajectory(执行轨迹)。传统 SFT 学习的是"给定输入应该输出什么",RLHF 学习的是"什么回答更符合人类偏好",而 Agentic RL 学习的是"在当前环境状态下应该采取什么动作,以及下一步如何继续完成任务"。例如一个代码修复任务,模型需要阅读 Issue、搜索仓库、定位文件、修改代码、运行测试、分析报错、继续修改、再次运行测试,直到所有测试通过为止。整个训练过程中,每一步都会改变环境状态,因此策略需要随着 Observation 不断更新,而不是一次性生成完整答案。

从强化学习角度来看,Agentic RL 仍然遵循 Markov Decision Process(MDP)建模。环境状态表示为sts_t,动作表示为ata_t,状态转移函数表示为P(st+1∣st,at)P(s_{t+1}|s_t,a_t) ,奖励函数表示为 R(st,at)R(s_t,a_t),策略网络表示为 πθ(a∣s)\pi_\theta(a|s),优化目标仍然是最大化累计折扣奖励:

J(θ)=Eτ∼πθ[∑t=0TγtR(st,at)]J(\theta)=\mathbb{E}_{\tau\sim\pi_\theta}\left[\sum_{t=0}^{T}\gamma^tR(s_t,a_t)\right]

其中τ\tau表示完整执行轨迹,而不是最终回答,因此 Agentic RL 实际优化的是整条任务执行路径。训练数据不再是传统问答数据,而是由大量 Trajectory 组成,每条轨迹通常包含 State、Action、Observation、Reward 等信息,例如:

python
trajectory = {
    "states":[s1,s2,s3],
    "actions":[a1,a2,a3],
    "observations":[o1,o2,o3],
    "rewards":[0,0,1]
}

这些数据绝大部分不是人工标注,而是模型自己在线 Rollout 得到,因此 Agentic RL 基本都采用 On-Policy Training。训练开始时,Task Sampler 从任务池中随机抽取一个任务,例如代码修复、浏览网页、数学证明或者 GUI 操作,然后 Actor 根据当前策略生成完整执行轨迹,每执行一步都会调用真实环境,环境返回新的 Observation,直到任务结束,再由 Verifier 或 Environment 自动计算最终 Reward。

python
while training:

    task = sampler.sample()

    trajectory = actor.rollout(task)

    reward = verifier.evaluate(
        trajectory
    )

    trainer.update(
        trajectory,
        reward
    )

Rollout 是整个 Agentic RL 中最重要的组成部分,它真正让模型进入环境执行任务,而不是生成静态文本。例如:

python
def rollout(task):

    state = env.reset(task)

    done = False

    while not done:

        action = policy(state)

        state,reward,done = env.step(action)

    return trajectory

这里的 env.step() 可以对应执行 Python、调用搜索引擎、浏览网页、点击 GUI、修改代码、运行测试或者访问数据库,因此 Agentic RL 的 Environment 已经从传统强化学习中的 Atari 游戏、机器人模拟器演变成真实的软件系统和数字环境。现代 Agent 框架通常都会把 Rollout Worker、Environment Worker、Reward Worker、Trainer 解耦部署,不同 Worker 可以运行在不同 GPU 或不同机器上,提高采样吞吐量。

python
class RolloutWorker:

    def run(self,task):

        return actor.rollout(task)
python
class EnvironmentWorker:

    def step(self,action):

        return environment.execute(action)
python
class RewardWorker:

    def evaluate(self,trajectory):

        return verifier.score(
            trajectory
        )

现代 Agentic RL 已经很少依赖传统 Reward Model,而是越来越多使用真实环境作为奖励来源。例如代码 Agent 可以直接运行单元测试:

python
reward = run_unit_test(
    repository
)

Browser Agent 可以判断网页是否完成:

python
reward = browser.check_finish()

GUI Agent 可以检查任务是否成功:

python
reward = desktop.verify()

数学推理可以直接验证最终答案:

python
reward = math_verifier(
    answer
)

因此 Reward 不再来源于人类偏好,而是来源于真实任务是否完成,这也是近年来 Verifiable RL 越来越重要的原因。

为了稳定优化策略,训练过程中通常不会直接把最终奖励分配给最后一步,而是利用 Credit Assignment 将奖励传播到整个轨迹,再计算每一步 Advantage。传统 PPO 会使用 Value Network:

python
advantage = reward + gamma * value_next - value

随后更新策略:

python
loss = PPO(
    policy,
    advantage
)

近年来越来越多 Agentic RL 使用 GRPO、DAPO、RLOO 等无需 Critic 的算法,例如 GRPO 会让模型针对同一个任务采样多个 Rollout,再根据组内相对表现计算 Advantage。

python
responses = []

for _ in range(8):

    responses.append(
        actor.generate(prompt)
    )

scores = verifier(
    responses
)

advantages = normalize(
    scores
)

然后直接优化策略:

python
loss = grpo_loss(
    responses,
    advantages
)

由于 Agent 会频繁调用工具,因此训练数据中通常包含 Tool Call 与 Tool Return。Tool Call 属于 Agent 决策,应参与训练;Tool Return 属于环境反馈,不属于策略,因此通常采用 Loss Mask。

python
messages = [

{"role":"assistant","tool_call":"search"},

{"role":"tool","content":"..."},

{"role":"assistant","content":"analysis"}
]
python
loss_mask = [

1,

0,

1
]

训练时:

python
loss = (
    token_loss *
    loss_mask
).sum()

现代 Agentic RL 还会加入 Reflection,使模型能够分析失败原因,并把反思结果写入下一轮上下文,提高下一次 Rollout 的成功率。

python
reflection = model.generate(
    "为什么失败?"
)

context.append(
    reflection
)

很多系统还会把成功轨迹保存下来形成 Replay Buffer,用于后续继续训练。

python
replay_buffer.append(
    trajectory
)

下一轮:

python
batch = replay_buffer.sample()

trainer.update(batch)

近年来 OPD(On-Policy Distillation)也逐渐成为 Agentic RL 的重要组成部分。Student 首先执行完整任务,然后 Teacher 对同一任务生成更优轨迹,再利用 KL Divergence 或 Preference Loss 对 Student 进行优化,而不是单纯依赖 Reward。

python
student_rollout = student.run(
    task
)

teacher_rollout = teacher.run(
    task
)

loss = kl_distillation(
    student_rollout,
    teacher_rollout
)

optimizer.step()

如果采用 Multi-Teacher,则不同 Teacher 可以负责不同领域,再通过 Verifier 选择最优轨迹。

python
teacher_outputs = [

teacher1.solve(task),

teacher2.solve(task),

teacher3.solve(task)
]

best = judge(
    teacher_outputs
)

student.learn(
    best
)

为了提升训练效率,大规模 Agentic RL 通常采用异步采样,大量 Rollout Worker 同时执行任务,再统一回传训练数据。

python
trajectories = []

for worker in workers:

    trajectories.extend(
        worker.rollout()
    )

trainer.update(
    trajectories
)

很多训练系统还会结合 Curriculum Learning,根据当前成功率动态调整任务难度,使模型始终训练在能力边界附近,而不是一直学习简单任务。

python
if success_rate > 0.8:

    difficulty += 1

else:

    difficulty -= 1

Agentic RL 已经演变成一个完整的大规模后训练系统。LLM 本身只是其中的策略网络(Policy),真正决定训练效果的是任务生成、Rollout 质量、环境真实性、Verifier 可靠性、工具调用、轨迹过滤、Reflection、Replay Buffer、On-Policy Sampling、Distillation、Curriculum Learning 以及分布式训练系统共同组成的数据闭环。当前 GPT-5、Claude、GLM-5.2、Kimi K3、DeepSeek 等具备 Agent 能力的大模型,其后训练框架基本都围绕这一思想展开,只是在 Rollout 策略、奖励构建、优化算法以及蒸馏方式上存在不同实现。


Agentic RL如何实现

Agentic RL(Agentic Reinforcement Learning)本质上是强化学习在“智能体系统”层面的扩展,它不再只关心单一环境中的动作优化,而是把一个具备规划、记忆、工具调用与多步推理能力的 agent 当作优化对象。传统强化学习通常定义为 state → action → reward 的闭环,比如在 Atari 游戏或机器人控制中,state 是环境观测,action 是离散或连续控制信号,reward 是即时或延迟反馈。但在 Agentic RL 中,这个结构被显著放大:state 不再只是环境变量,而是包含长上下文、历史对话、外部检索结果、工具返回值以及记忆系统中的信息;action 也不再是简单动作,而是“思考 + 规划 + 工具调用 + 子任务生成”的组合;reward 也往往从稠密信号变为稀疏的任务级反馈,例如是否完成用户目标、是否解决问题、甚至来自人类偏好模型或自动评测器的评分。

从系统角度看,一个典型 Agentic RL agent 通常由多个模块组成,这些模块共同构成一个可优化的决策图。首先是 planner,它负责将用户的高层目标拆解为多个子目标,例如用户要求“写一份竞品分析报告”,planner 会将任务拆解为信息收集、竞品分类、特征对比、结论生成等子任务。其次是 actor 或 executor,它负责执行这些子任务,包括调用搜索引擎、数据库查询、代码执行环境或者外部 API。第三是 critic 或 verifier,它用于评估当前轨迹是否合理,例如判断信息是否充分、推理是否一致、工具调用是否正确。最后是 memory 系统,它记录短期上下文(scratchpad)以及长期知识(向量数据库或图结构记忆),使 agent 在多轮任务中具备持续学习能力。一个更完整的系统还会包含 router(决定调用哪个工具或子 agent)以及 scheduler(决定执行顺序)。

如果用一个简化的代码结构来表示 Agentic RL agent,可以类似这样组织:

python
class Agent:
    def __init__(self, planner, actor, critic, memory, tools):
        self.planner = planner
        self.actor = actor
        self.critic = critic
        self.memory = memory
        self.tools = tools

    def step(self, user_goal):
        plan = self.planner.decompose(user_goal, self.memory.retrieve(user_goal))
        trajectory = []

        for sub_goal in plan:
            action = self.actor.act(sub_goal, self.memory, self.tools)
            result = self.tools.execute(action)
            self.memory.write(sub_goal, result)
            score = self.critic.evaluate(sub_goal, result)
            trajectory.append((sub_goal, action, result, score))

        return trajectory

这个结构的关键不在代码本身,而在于它天然形成了一个“可展开轨迹(trajectory)”,这正是 RL 可以优化的对象。在 Agentic RL 中,训练目标不再是单步 action 的概率最大化,而是最大化整条 trajectory 的期望 reward,因此优化对象通常写成:

max⁡πEτ∼π[R(τ)]\max_\pi \mathbb{E}_{\tau \sim \pi} [R(\tau)]

其中 τ 是完整的 agent 行为轨迹,R(τ) 是任务级 reward。

在训练方法上,Agentic RL 主要分为几种范式。第一种是 outcome-based RL,即只看最终结果是否成功。例如在代码生成任务中,只判断最终代码是否通过测试用例,在问答任务中只判断最终答案是否正确。这种方法简单但问题在于 credit assignment 非常困难,因为无法知道哪一步推理导致成功或失败。第二种是 process-based RL,它引入中间步骤的监督或奖励信号,例如对每一步推理是否合理进行评分,这种方式在数学推理和复杂 agent 任务中越来越重要,因为它可以稳定长链路推理。第三种是 trajectory-level RL,也就是把整个 agent rollout 看作一条序列,用 PPO、REINFORCE 或 GRPO 等算法直接优化整条路径的概率分布。

在更现代的系统中,还会引入 self-improving loop,也就是 agent 在执行任务后,通过 critic 或 verifier 进行自我评估,然后把失败轨迹重新加入训练数据,不断迭代策略。例如一个典型流程如下:

python
trajectory = agent.run(task)
reward = evaluator(trajectory)

if reward < threshold:
    memory.store_failure(trajectory)
    retrain_policy(memory.sample())

这种机制在 Voyager(Minecraft 自动探索 agent)中体现得非常明显,它通过不断生成任务、执行、失败、学习技能库,从而实现持续进化。

Agentic RL 的核心难点主要集中在几个方面。首先是 reward 极其稀疏,尤其在复杂任务中,只有最终结果才能判断成功,这导致学习信号非常弱。其次是 credit assignment 问题,即在一个几十步甚至上百步的 reasoning chain 中,很难确定是哪一步决定了最终成败。第三是长 horizon instability,随着步骤增长,误差会逐渐累积,导致 agent drift 出正确轨道。第四是 tool use compounding error,例如一次 API 调用错误可能导致后续所有推理基于错误信息展开。第五是 state explosion,因为 agent state 已经从简单 observation 扩展为“上下文 + memory + tool outputs + reasoning traces”的组合空间。

从前沿发展来看,Agentic RL 正在向几个方向演进。一个重要方向是 process reward model(PRM),即专门训练一个模型来评估每一步推理是否合理,而不是只看最终答案。另一个方向是 LLM-as-policy + RL hybrid,即用大语言模型提供强先验策略,再用 RL 做微调优化,使其更适应具体任务分布。还有一个方向是 graph-based agent RL,把 agent 的行为抽象成图结构,其中节点是 reasoning step 或 tool call,边表示依赖关系,然后在图结构上做优化,例如最短成功路径或最大 reward path。这一方向与你当前的 graph agent 体系其实是高度一致的。

此外还有 multi-agent RL,即多个 agent 分工协作,例如 planner agent 负责规划,coder agent 负责实现,reviewer agent 负责检查,这种结构本质上是一个分布式 policy system。还有 memory-augmented RL,它将 memory retrieval 本身作为可学习策略的一部分,也就是说“记什么”和“如何检索”也进入优化空间,这一点与你正在做的 MIRIX 系统非常接近,本质上 memory 不再只是外部组件,而是 policy 的一部分。

如果把 Agentic RL 放到工程系统层面,它可以被抽象为一个完整的数据闭环系统:首先 agent 在环境中生成 trajectory,然后系统记录 trajectory graph,包括每一步的输入、输出、工具调用与中间状态;其次 reward model 对 trajectory 或 step 进行评分,生成 process reward 或 outcome reward;然后训练模块基于这些数据进行 policy optimization,例如 PPO 或 DPO;最后更新后的 policy 再进入下一轮 rollout。这个循环可以表达为:

plain-text
Agent Rollout → Trajectory Logging → Reward Modeling → Policy Optimization → Deployment → Rollout

从本质上讲,Agentic RL 解决的不是“如何回答问题”,而是“如何构造一个可以持续改进的智能体行为系统”。它的研究趋势正在从单模型能力提升,转向系统级优化,也就是 agent architecture + memory + tool use + RL 的整体联合优化。


Agentic RL之——RLVR有哪些方法

RLVR(Reinforcement Learning with Verifiable Rewards)在当前大模型与agentic RL体系中的本质,是一种用“可验证函数”替代传统reward model的强化学习范式,它试图解决RLHF中最核心的不稳定来源——reward不可验证与偏好主观性问题。按照Sutton & Barto的强化学习定义(https://incompleteideas.net/book/the-book-2nd.html),RL的核心仍然是最大化期望回报J(π)=E[Σ r_t],但RLVR改变的是r_t的生成方式:不再来自人类偏好模型或learned reward model,而是来自一个可以被外部执行或规则系统直接计算的verifier函数f(x,y)。因此从形式上看,RLVR可以写为 y ~ πθ(x),然后 r = f(x,y),再用这个reward进行policy update,但关键变化在于f是确定性或半确定性的,这使得整个优化过程从“learned reward optimization”转向“programmatic reward optimization”。

在agentic RL与LLM reasoning体系中,RLVR之所以变得重要,是因为它提供了一种极其稳定的训练信号来源,尤其适用于long-horizon trajectory任务中那些原本难以建模reward的场景,例如数学推理、代码生成、工具调用agent、web navigation任务等。在这些任务中,trajectory通常很长,包含多个reasoning step + tool action + environment feedback,如果依赖reward model,会引入严重的偏差传播问题,而RLVR通过直接执行验证逻辑(比如unit test、symbolic checker、execution result)来避免这一问题,从而使reward与真实任务成功状态严格对齐。

从方法上看,RLVR并不是单一算法,而是一组围绕“verifiable signal”的强化学习范式集合,当前主流可以分为几类。第一类是rule-based RLVR,这是最早也是最稳定的形式,其reward来自显式规则函数,例如数学题答案匹配、字符串完全一致、逻辑约束检查等,这类方法的特点是reward极其稠密但覆盖范围有限,适用于结构化任务。在这种设定中,RL通常与PPO或GRPO结合使用,例如在每个trajectory结束后,通过verifier返回0/1 reward,然后用trajectory-level advantage进行更新,本质上仍然是PPO在long-horizon MDP上的应用,只是reward来源变得确定。

第二类是execution-based RLVR,这是当前code LLM与agent RL中最重要的形式,其verifier不是静态规则,而是一个可执行环境,例如代码运行器、数据库查询系统或web environment。在这种设定下,模型生成的输出不是纯文本,而是可执行程序或操作序列,reward由执行结果决定,例如unit test是否通过或任务是否完成。这类方法在CodeRL、AlphaCode以及后续LLM coding agent体系中非常常见,其核心优势是reward与真实执行结果强绑定,从而避免“语言正确但任务错误”的情况。这类RLVR通常与trajectory RL结合,因为reward只在trajectory终点或关键状态产生,属于典型sparse terminal reward setting。

第三类是process-based RLVR(过程验证型RLVR),这是更高级的一类方法,其verifier不仅检查最终结果,还检查中间步骤的正确性。例如在数学推理中,不仅检查最终答案,还检查每一步推导是否合法;在agent任务中,不仅检查最终是否完成,还检查中间action是否合理。这种方法实际上试图缓解long-horizon credit assignment问题,因为它提供了dense intermediate reward signal,使得trajectory RL的梯度传播更加稳定。这一方向与process supervision(如OpenAI process reward model相关研究)是高度相关的,但RLVR的区别在于它不依赖learned reward model,而是依赖可验证规则或程序。

第四类是environment-grounded RLVR,这是在agentic RL中最接近真实系统的一类方法,其reward来自环境状态变化,例如web agent是否成功提交表单、robot是否完成操作、API调用是否达到目标状态等。在这种设定中,verifier本身就是environment transition function,因此RLVR退化为“真实环境反馈驱动的强化学习”。这一类方法的特点是最接近现实deployment,但同时cost最高,因为需要真实或高保真模拟环境。

在训练算法层面,RLVR通常不会单独存在,而是与PPO、GRPO或trajectory distillation混合使用。在PPO框架中,RLVR提供的是reward signal,而PPO负责将这个reward传播到trajectory中的每一步action;在GRPO或group-based RL中,RLVR用于对多个candidate trajectories进行排序,从而形成relative advantage;而在distillation-based RL(如OPD)中,RLVR不直接参与gradient更新,而是用于对trajectory进行筛选和加权,再通过supervised learning方式进行拟合。因此RLVR更像一个“reward infrastructure layer”,而不是单一优化算法。

从系统角度来看,一个典型RLVR + agent RL pipeline可以表示为:policy生成trajectory τ,verifier函数f(τ)对trajectory进行评估,输出reward r,然后根据不同算法选择不同路径:如果是PPO,则r进入advantage estimation;如果是GRPO,则用于trajectory ranking;如果是distillation,则用于sample weighting。可以用如下形式表示:

plain-text
τ ~ πθ
r = verifier(τ)

if PPO:
    A = r - V(s)
    update πθ via clipped policy gradient

if distillation:
    w = normalize(r)
    minimize -w * log πθ(τ)

if GRPO:
    rank trajectories by r
    compute relative advantage

RLVR的核心价值在于,它将RL从“学习一个不可验证的reward函数”转变为“优化一个可验证的目标函数”,从而显著降低训练不确定性。但它也有明确边界:首先,它依赖任务可验证性,因此只适用于数学、代码、规则系统和可执行环境,无法直接覆盖开放式生成任务;其次,它会导致reward hacking在规则设计不完备时仍然存在;第三,它对exploration能力要求更高,因为verifier只能判断结果,而不能指导路径。

因此在当前agentic RL体系中,RLVR通常与trajectory RL(PPO类方法)、distillation-based RL(OPD类方法)以及search-based methods共同构成完整训练框架,其中RLVR提供“可执行真实信号”,PPO提供“稳定优化机制”,distillation提供“分布压缩能力”,search提供“路径探索能力”。从更本质的角度来看,RLVR是当前大模型从“语言建模驱动”转向“可执行任务优化驱动”的关键基础设施,它使得long-horizon agent RL第一次具备了稳定、可扩展且可验证的训练信号来源。


Agentic RL 为什么需要 Replay Buffer

Replay Buffer 是 Agentic RL(Agentic Reinforcement Learning)训练框架中用于保存和复用 Agent 交互轨迹(Trajectory)的核心组件,它最早来源于深度强化学习中的经验回放(Experience Replay)机制,最经典的应用是在 DQN 中解决强化学习训练过程中的数据相关性和样本利用率问题。在大语言模型 Agent 训练中,Replay Buffer 的作用进一步扩展,它不仅保存状态、动作、奖励,而是保存完整的 Agent 执行过程,包括用户任务、模型思考过程、工具调用、环境反馈、最终结果以及奖励信号。因此 Agentic RL 中的 Replay Buffer 本质上是一个动态经验数据库,它记录 Agent 曾经经历过哪些任务、采取过什么行为、哪些行为成功、哪些行为失败,并在后续训练阶段不断重新采样这些经验,让模型从过去的执行历史中持续学习。

传统监督学习的数据集通常是固定的:

python
dataset = [

    {
        "input":"question",
        "output":"answer"
    }

]

数据在训练前已经确定,模型只是不断重复学习这些样本。而 Agentic RL 中的数据来自 Agent 自己与环境交互:

python
while True:

    task = sample_task()

    trajectory = agent.run(
        task
    )

    replay_buffer.add(
        trajectory
    )

因此 Replay Buffer 存储的是:

python
trajectory = {

    "task": task,

    "states": [
        s1,
        s2,
        s3
    ],

    "actions":[
        a1,
        a2,
        a3
    ],

    "observations":[
        o1,
        o2,
        o3
    ],

    "reward":1

}

例如一个 Coding Agent 修复 Bug:

python
trajectory = {

"task":
"Fix authentication bug",

"actions":[

"search repository",

"open auth.py",

"modify function",

"run test"

],

"reward":1

}

这个轨迹进入 Replay Buffer:

python
buffer.append(
    trajectory
)

之后训练阶段:

python
batch = buffer.sample(
    batch_size=32
)

trainer.update(
    batch
)

模型不仅学习当前刚刚产生的数据,还可以重新学习过去的重要经验。

Agentic RL 为什么需要 Replay Buffer,核心原因是 Agent 任务通常是长链路(Long Horizon)的,一个任务可能需要几十甚至几百步操作。如果只使用当前一次 Rollout 的数据,训练效率非常低。例如:

plain-text
用户需求:

修改一个大型项目中的权限漏洞

↓

搜索代码

↓

阅读多个文件

↓

定位问题

↓

修改

↓

测试

↓

失败

↓

重新修改

↓

成功

最终成功只产生一个 Reward:

python
reward = 1

但是前面几十步 Action 都决定了最终结果。如果没有 Replay Buffer,这些昂贵的成功轨迹可能只使用一次就丢弃。而通过 Replay Buffer:

python
buffer.add(
    successful_trajectory
)

之后可以反复训练:

python
for epoch in range(10):

    batch = buffer.sample()

    update(batch)

这样可以提高样本利用效率。

在 Agentic RL 中,Replay Buffer 不只是保存成功样本,也会保存失败样本。因为失败轨迹对于 Agent 学习同样重要。例如:

python
trajectory = {

"task":"Deploy service",

"action":

"restart database",

"observation":

"database corrupted",

"reward":

-1

}

这个失败经验可以帮助模型学习:

python
avoid(
    dangerous_action
)

因此 Buffer 中通常包含:

python
buffer = {

successful_cases,

failure_cases,

partial_success_cases,

hard_cases

}

不同类型的数据会采用不同采样权重。

例如:

python
sample_weight = {

"success":1.0,

"failure":0.8,

"hard_example":2.0

}

采样:

python
batch = weighted_sample(
    buffer
)

这就是 Prioritized Replay。

在 Agentic RL 中,最重要的一类 Replay Buffer 是 On-Policy Buffer 和 Off-Policy Buffer 的区别。传统 PPO 通常使用 On-Policy 数据,也就是:

python
current_policy

↓

generate trajectory

↓

update

↓

discard

例如:

python
trajectory = actor.rollout()

trainer.update(
    trajectory
)

delete(
    trajectory
)

这种方式的问题是数据利用率低。

而 Replay Buffer 可以保存历史轨迹:

python
buffer = [

trajectory_001,

trajectory_002,

trajectory_003

]

然后:

python
old_data = buffer.sample()

train(old_data)

这属于 Off-Policy 或 Semi-On-Policy 训练。

但是对于大语言模型 Agent 来说,完全 Off-Policy 会产生 Distribution Shift(分布偏移)问题。因为模型参数不断变化:

python
Policy_v1

↓

Policy_v2

↓

Policy_v3

旧轨迹可能来自:

python
Policy_v1

而现在训练的是:

python
Policy_v3

两者行为分布不同:

πold(a∣s)≠πnew(a∣s)\pi_{old}(a|s)\neq\pi_{new}(a|s)

因此现代 Agentic RL 通常不会无限使用旧数据,而采用有限窗口 Replay Buffer。

例如:

python
buffer.max_size = 100000

超过:

python
if len(buffer)>max_size:

    remove_oldest()

保持数据接近当前 Policy。

很多 Agentic RL 系统采用 Hybrid Replay Buffer,同时保存三类数据:

第一类是 On-Policy Rollout:

python
current_agent.generate()

用于保持当前策略分布。

第二类是 Expert Demonstration:

python
teacher.generate()

用于提供高质量行为。

第三类是 Historical Experience:

python
buffer.sample()

用于提高数据利用率。

例如:

python
batch = concat(

    online_rollout,

    expert_data,

    replay_data

)

然后训练:

python
trainer.update(
    batch
)

这也是目前很多 Agent 后训练系统的趋势。

在 Agentic RL 中,Replay Buffer 存储的信息比传统 RL 更复杂。传统强化学习:

python
{
state,

action,

reward,

next_state
}

Agentic RL:

python
{
task,

messages,

reasoning,

tool_calls,

tool_results,

environment_state,

reward,

success,

failure_reason
}

例如:

json
{
"task":
"Find financial report",

"messages":[

"user request",

"assistant plan",

"tool call",

"search result",

"final answer"

],

"tool_calls":[

{
"name":"browser.search",

"args":"report.pdf"

}

],

"reward":0.95

}

因此 Replay Buffer 在 Agent 系统中实际上也承担 Memory Dataset 的作用。

很多 Agentic RL 系统还会在 Replay Buffer 上增加 Trajectory Filtering。因为不是所有 Rollout 都值得学习。例如:

python
if reward < 0:

    discard()

或者:

python
if trajectory_length > 100:

    keep_if_success()

或者:

python
if improvement > threshold:

    add_to_buffer()

更加先进的方法会计算 Trajectory Advantage:

A(τ)=R(τ)−E[R]A(\tau)=R(\tau)-E[R]

选择高 Advantage 轨迹:

python
score = reward - average_reward

if score > threshold:

    buffer.add(
        trajectory
    )

这样 Buffer 会逐渐集中到高价值经验。

在 Agentic RL 中,Replay Buffer 还经常与 Reflection 结合。Agent 执行失败后,不只是保存失败结果,而是生成失败分析:

python
failure = {

"action":

"wrong_api",

"reason":

"API requires authentication"

}

下一次训练:

python
model.learn(
    failure
)

使模型避免重复错误。

对于长链路 Agent,Replay Buffer 还会进行 Segment Replay。因为完整轨迹可能太长:

python
trajectory = [

plan,

search,

tool,

reflection,

answer

]

不一定全部重要,因此拆成:

python
segments = [

planning_segment,

tool_segment,

reasoning_segment

]

分别训练:

python
buffer.add(
    important_segment
)

这和 ShortOPD、Trajectory Truncation、Credit Assignment 结合,可以提高长任务训练效率。

在实际工程中,一个 Agentic RL Replay Buffer 通常类似:

python
class ReplayBuffer:

    def __init__(self):

        self.storage=[]


    def add(self,trajectory):

        if self.is valuable(
            trajectory
        ):

            self.storage.append(
                trajectory
            )


    def sample(self,batch):

        return random.sample(
            self.storage,
            batch
        )

大规模系统会进一步分布式化:

python
Rollout Worker

        |

        v

Replay Server

        |

        v

Trainer GPU

例如:

python
trajectory = rollout_worker.run()

replay_server.push(
    trajectory
)

trainer.pull(
    batch
)

Agentic RL 中的 Replay Buffer 不只是简单的数据缓存,而是整个 Agent 学习闭环中的经验管理系统。它连接了 Rollout、Environment、Reward、Training 和 Evaluation,使 Agent 能够不断积累过去执行任务的经验,并从成功轨迹、失败轨迹、困难任务和 Teacher 轨迹中持续提升能力。对于现代长链路 Agent 来说,Replay Buffer 的价值甚至超过传统强化学习中的经验回放,因为 Agent 的核心能力不是记住某个答案,而是积累如何完成复杂任务的策略经验。


AgenticRL后训练实践之VeRL框架-含代码

VeRL(Volcano Engine Reinforcement Learning,后来发展为社区广泛使用的 VERL,通常解释为 Easy Reinforcement Learning for LLMs)本质上不是一种新的强化学习算法,而是一套面向大语言模型和 Agent 的分布式 RL 后训练框架,它解决的是 PPO、GRPO、DAPO、RFT、OPD 等算法真正落地时的工程问题,而不是提出新的优化目标。传统 RL 论文通常重点描述 Policy Loss、Advantage、KL 散度等数学公式,但真正训练一个几十亿甚至几百亿参数的大模型,还需要解决 Rollout 数据采集、分布式推理、Reward 计算、Batch 调度、GPU 通信、Checkpoint 管理以及模型同步等大量工程问题。VERL 将这些复杂组件抽象成统一接口,使研究人员能够将主要精力放在算法设计,而不是重复开发训练基础设施。因此,今天很多 Agentic RL 项目都会采用 VERL 作为底层训练框架,而 PPO、GRPO、DAPO、Self-Play、Self-Distillation、On-Policy Distillation 等算法只是运行在 VERL 上层的不同训练策略。

python
from verl.env import TauBenchEnv

env = TauBenchEnv(task)#所有 Environment 都会提供统一接口:

observation = env.reset()

一个完整的 Agentic RL 系统通常包含 Environment、Rollout Worker、Inference Engine、Reward Module、Replay Buffer、Trainer、Checkpoint Manager 等模块。Environment 负责模拟真实环境,例如 τ-bench、Terminal-Bench、BrowserGym、GUI Agent、代码仓库或者 Linux Terminal;Rollout Worker 负责调用当前策略执行任务并采集轨迹;Inference Engine 通常使用 vLLM、SGLang 等高吞吐推理引擎负责批量生成 Token;Reward Module 对完整轨迹进行评分;Replay Buffer 保存训练数据;Trainer 根据 PPO、GRPO 或其他算法更新模型;Checkpoint Manager 则负责参数同步与模型保存。VERL 的价值就在于把这些模块完全解耦,使每一层都可以独立替换,而不会影响整个训练系统。

python
#随后 Actor 根据 Observation 开始生成 Action,Actor 本质就是当前训练策略:
from transformers import AutoModelForCausalLM

actor = AutoModelForCausalLM.from_pretrained(
    model_path
)
python
#每一步生成:
action = actor.generate(
    observation
)
python
#Environment 执行:
next_observation, reward, done, info = env.step(
    action
)

VERL 的训练从 Rollout Generation 开始。Rollout 的目标不是简单生成一句回答,而是让 Agent 在真实环境中执行完整任务,并记录整个执行过程。对于 Agent 来说,每一步都包含 Observation、Reasoning、Action、Tool Call、Tool Return、Reward 等信息,因此最终得到的是完整 Trajectory,而不是传统语言模型中的 Prompt-Response 数据。对于代码 Agent,Observation 可能包括代码仓库、测试结果和历史修改;对于 Browser Agent,Observation 包括网页 DOM、浏览器状态以及用户输入;对于 GUI Agent,则包含屏幕截图、控件坐标以及系统状态。所有环境最终都会抽象成统一接口,因此无论 Agent 操作浏览器、Linux、数据库还是桌面软件,都能够按照同样方式进行 Rollout。

VERL 会自动记录整个 Trajectory:

python
# 每个 Rollout 都是一条完整轨迹(Trajectory)
trajectory = {
    "observations": [],
    "actions": [],
    "tool_calls": [],
    "tool_returns": [],
    "rewards": []
}
python
# 保存这一轮交互
trajectory["observations"].append(
    observation
)

trajectory["actions"].append(
    action
)

trajectory["rewards"].append(
    reward
)

VERL 与传统 RLHF 最大不同之一在于它高度依赖高吞吐推理引擎,因为 RL 训练最大的瓶颈往往不是反向传播,而是 Rollout。假设一次 PPO 更新需要采集 8192 条 Trajectory,每条 Trajectory 平均生成 300 个 Token,那么一次更新就需要生成约 250 万个 Token。如果直接使用 HuggingFace generate,GPU 大量时间都会浪费在 KV Cache 管理和 Batch 调度上,因此 VERL 通常直接接入 vLLM:

python
from vllm import LLM

engine = LLM(
    model=model_path
)

生成 Rollout 后进入 Reward Evaluation 阶段。不同任务对应不同 Reward,例如数学任务比较最终答案是否正确,代码任务运行自动测试,GUI Agent 检查桌面状态是否达到目标,TauBench 比较数据库最终状态是否符合预期。Reward 可以是二值奖励,也可以是连续奖励,还可以同时综合工具使用质量、答案正确率、安全性以及执行效率等多个指标。VERL 并不限制 Reward 的来源,只要求最终能够转换成数值反馈,因此可以非常容易地接入 Rule-Based Verifier、LLM-as-a-Judge 或真实环境反馈。

python
#批量推理
outputs = engine.generate(
    prompts
)
python
#或者接入 SGLang:
from sglang import Engine

engine = Engine(
    model_path
)

这样 Rollout Worker 可以同时生成上百条轨迹,大幅提高 GPU 利用率。

完成 Rollout 后进入 Reward 阶段,不同 Agent 使用不同 Reward。例如数学:

python
reward = run_tests(
    repository
)
python
reward = verifier(
    database_state
)

Agentic RL 与传统文本 RL 最大区别在于 Action 不再只是生成 Token,而是可以调用真实工具,因此 VERL 把 Action 抽象为统一结构,包括 Tool Name、Arguments、Execution Result 等信息。每次 Tool Call 完成以后,环境都会更新 Observation,然后 Agent 基于新的 Observation 继续推理,直到任务结束。整个过程中产生的所有 Observation、Reasoning、Tool Call、Tool Return、Reward 都会被写入同一条 Trajectory,这也是后续 RL 优化的训练样本。

python
# PPO 使用 GAE(Generalized Advantage Estimation)
# 计算每一步 Action 的 Advantage
advantages = compute_gae(
    rewards,
    values,
    gamma=0.99,
    lam=0.95
)

Reward 计算完成以后,VERL 根据所选择的 RL 算法计算 Advantage。如果使用 PPO,则根据 GAE(Generalized Advantage Estimation)计算 Advantage;如果使用 GRPO,则对同一任务采样多个回答,通过组内归一化计算 Relative Advantage;如果使用 DAPO,则使用动态裁剪策略;如果使用 OPD,则还需要增加 Teacher Policy 计算蒸馏损失。对于 VERL 来说,这些算法只是不同的 Trainer,实现 Rollout、Batch、Optimizer 等组件并不需要发生变化,因此同一套框架能够同时支持多种 RL 后训练算法。

python
# rewards:
#     Environment 返回的奖励
#
# values:
#     Critic 网络预测 Value
#
# gamma:
#     奖励折扣因子
#
# lam:
#     GAE 参数
advantages = (
    rewards - rewards.mean()
) / (
    rewards.std() + 1e-6
)

为了充分利用 GPU,VERL 将 Rollout Worker 与 Trainer 完全解耦。Rollout Worker 只负责调用当前策略执行任务并采集 Trajectory,不参与梯度计算;Trainer 则持续从 Replay Buffer 中读取 Batch,完成反向传播并更新模型参数。更新后的模型会重新同步到所有 Rollout Worker,使下一轮采样始终来自最新策略,从而保证整个训练过程属于标准 On-Policy Learning。多个 Rollout Worker 可以同时运行,大规模集群甚至可以同时启动数百个 Worker 并行采集轨迹,而 Trainer 则持续消费这些轨迹进行模型优化,大幅提升 GPU 利用率和训练效率。

python
# PPO Trainer
trainer = PPOTrainer(
    actor,
    critic
)
python
trainer = GRPOTrainer(
    actor
)
python
# 根据采集到的 Rollout 更新模型
trainer.update(
    trajectories,
    advantages
)
python
# 内部通常执行:
# 1. 计算 Policy Loss
# 2. 计算 Value Loss
# 3. 计算 KL Penalty
# 4. Backward
# 5. Optimizer Step
loss = ppo_loss(
    old_log_probs,
    new_log_probs,
    advantages
)
python
teacher = AutoModelForCausalLM.from_pretrained(
    teacher_path
)
python
teacher_output = teacher.generate(
    prompts
)

student_output = actor.generate(
    prompts
)
python
loss = (
    rl_loss +
    beta * kl_div(
        student_logits,
        teacher_logits
    )
)

VERL 不关心 Teacher 是谁,它只负责把对应的 Loss 接入 Trainer。

Agentic RL 与普通文本 RL 最大区别在于 Action 不再只是 Token,而可能是 Tool Call,因此 VERL 将 Action 抽象为统一结构:

python
action = {
    "type": "tool",
    "name": "python",
    "arguments": {
        "code": "print(1)"
    }
}
python
tool_result = executor.run(
    action
)

Observation 更新:

python
observation = {
    "history": messages,
    "tool_result": tool_result
}

继续 Rollout:

python
while not done:

    action = actor.act(
        observation
    )

    observation, reward, done, info = env.step(
        action
    )

VERL 会自动打包 Batch:

python
batch = collator(
    trajectories
)

反向传播:

python
trainer.train_step(
    batch
)

loss.backward()

optimizer.step()

scheduler.step()
python
# 保存最新策略模型
actor.save_pretrained(
    checkpoint_dir
)

对于长链路 Agent,一个完整训练循环就是不断重复"采集 Rollout → 计算 Reward → 保存 Replay Buffer → RL 更新 → 同步模型 → 再次采集 Rollout"这一过程。随着模型不断更新,新策略又会生成新的 On-Policy Trajectory,这些新的轨迹再次进入 Replay Buffer,形成持续自进化闭环。因此,从工程角度来看,VERL 真正提供的并不是一种新的 RL 算法,而是一套标准化的大模型 Agentic RL 基础设施,它统一了 Rollout Generation、Reward Evaluation、Trajectory Management、Distributed Training、Checkpoint Synchronization、Inference Engine 等核心组件,使研究者只需要关注 Environment、Reward Function 和 Optimization Algorithm,就能够快速构建完整的大模型强化学习训练系统。

python
# Rollout Worker 加载最新参数
# 下一轮采样就会使用新策略(On-Policy)
rollout_worker.load(
    latest_checkpoint
)
python
# 创建经验回放池(Replay Buffer)
# 用于缓存 Rollout Worker 采集到的轨迹(Trajectory)
# 后续 Trainer 会从这里采样训练数据
replay_buffer = ReplayBuffer()

# 开始 RL 训练
# 一个 epoch 表示完成一轮 "采样 → 优化 → 更新模型"
for epoch in range(epochs):

    # -------------------------------
    # Step 1:采样一批训练任务
    # -------------------------------
    # Task Sampler 从任务集(如 TauBench、Terminal-Bench)
    # 随机抽取 64 个任务,每个任务都会执行一次完整 Rollout
    tasks = sampler.sample(batch_size=64)

    # -------------------------------
    # Step 2:执行 Rollout
    # -------------------------------
    # 当前 Actor 与环境交互,生成完整 Trajectory
    for task in tasks:

        # Rollout Worker 使用当前策略执行任务
        # 内部通常会不断循环:
        # Observation → LLM → Tool Call
        # → Environment → Observation ...
        # 直到任务结束
        traj = rollout_worker.run(task)

        # 将采集到的轨迹加入 Replay Buffer
        # 一条轨迹通常包含:
        # Observation
        # Action
        # Tool Call
        # Tool Return
        # Reward
        replay_buffer.add(traj)

    # -------------------------------
    # Step 3:采样训练 Batch
    # -------------------------------
    # 从 Replay Buffer 中随机采样一批轨迹
    # PPO、GRPO、DAPO 等算法都会基于这些数据更新模型
    batch = replay_buffer.sample(128)

    # -------------------------------
    # Step 4:强化学习优化
    # -------------------------------
    # Trainer 根据不同算法计算 Loss
    #
    # PPO:
    #     Policy Loss + Value Loss + KL Loss
    #
    # GRPO:
    #     Group Advantage + Policy Loss
    #
    # OPD:
    #     RL Loss + Distillation Loss
    #
    # 最终执行:
    # Forward
    # → Backward
    # → Optimizer.step()
    trainer.update(batch)

    # -------------------------------
    # Step 5:保存最新策略
    # -------------------------------
    # 保存当前训练完成后的 Actor 参数
    # 后续 Rollout 将使用最新策略进行 On-Policy 采样
    actor.save_pretrained(checkpoint_dir)

    # -------------------------------
    # Step 6:同步 Rollout Worker
    # -------------------------------
    # 所有 Rollout Worker 重新加载最新模型
    # 保证下一轮采样的数据来自最新 Policy
    # 避免使用旧策略生成 Off-Policy 数据
    rollout_worker.reload(checkpoint_dir)
python
responses = []

for _ in range(8):

    responses.append(
        actor.generate(prompt)
    )
python
scores = [
    verifier(r)
    for r in responses
]

advantages = (
    scores - np.mean(scores)
) / (
    np.std(scores) + 1e-6
)

trainer.update(
    responses,
    advantages
)

如果采用 Agentic RL,则一个 Response 已经不是普通文本,而是一整条 Tool Trajectory:

python
trajectory = {
    "messages": [...],
    "reasoning": [...],
    "tool_calls": [...],
    "tool_returns": [...],
    "reward": 1
}

VERL 并不会区分这是文本还是 Agent,只要最终能够表示为 Trajectory,就可以进入统一的 Rollout、Reward、Batch、Trainer、Checkpoint 流程。因此从工程角度来看,VERL 的真正价值并不在于提出了新的 PPO、GRPO 或 DAPO,而是在大模型后训练中提供了一套高度模块化、可扩展、支持分布式和高吞吐推理的 Agentic RL 基础设施,使 Rollout Generation、Reward Evaluation、Trajectory Management、Distributed Training、Checkpoint Synchronization、Inference Engine 等组件实现标准化解耦。今天许多面向 Agent 的强化学习系统,包括代码 Agent、GUI Agent、Browser Agent、Terminal Agent 和长链路 Tool-Use Agent,本质上都可以建立在 VERL 提供的训练框架之上,只需要替换 Environment、Reward Function 和 Optimization Algorithm,就能够快速构建完整的 Agentic RL 后训练系统。


如何用SFT训练Agent

在agentic RL与大模型系统中,用SFT(Supervised Fine-Tuning)训练agent,本质上并不是“让模型学会回答问题”,而是把一个长链路决策过程(trajectory)转化为可学习的监督序列学习问题,也就是把强化学习中的“policy optimization”退化成“behavior cloning on trajectories”。从强化学习的理论视角(Sutton & Barto, https://incompleteideas.net/book/the-book-2nd.html)来看,agent本质是在POMDP环境中学习策略π(a|s),而SFT的做法是直接用数据近似这个策略,而不显式建模reward,因此它在数学上等价于最大化log-likelihood:max log πθ(a|s),也就是标准的MLE训练。但在agent场景中,这里的(s,a)不再是单轮QA,而是包含reasoning、tool-use、memory update、environment interaction的完整trajectory拆分。

在LLM agent训练中,SFT的核心难点是如何构造“可学习的轨迹数据”。与传统NLP不同,agent数据不是静态文本,而是结构化执行序列,例如:(state → thought → action → tool call → observation → next state)。因此SFT训练数据通常来自三种来源:人工标注trajectory、teacher model rollout、以及RL或search生成的高质量轨迹。第一类是最早期方法,例如ReAct(https://arxiv.org/abs/2210.03629),通过人工或半人工方式构造“思考+行动”的轨迹,让模型学习如何分解任务;第二类是teacher distillation,例如用更强模型(GPT-4级别或内部oracle模型)生成完整agent行为,然后学生模型做模仿学习;第三类是通过搜索或RL筛选出来的高reward trajectory,例如在code agent或math reasoning中,用verifier筛选出成功路径,再回流做SFT,这一类实际上是“RLVR + SFT”的组合形态。

在agent SFT的实际训练中,一个trajectory通常被展开成token级序列,例如:

plain-text
User: solve this task
Agent: thought: I should first search
Agent: action: search(query="...")
Observation: ...
Agent: thought: now I should call tool
Agent: action: call_api(...)
Agent: final answer: ...

然后训练目标就是最大化每个token的条件概率:

python
loss = 0
for trajectory in dataset:
    for (s, a) in trajectory:
        loss += -log(pi_theta(a | s))
optimize(loss)

但在agent setting中,这个看似简单的SFT目标背后隐藏一个关键问题:trajectory distribution shift。因为agent数据不是独立同分布的文本,而是由policy与环境交互产生的,因此数据分布会随着policy变化而变化,这意味着SFT在agent中实际上是一个“静态近似动态系统”的过程。如果数据来源是teacher model(例如GPT-4生成trajectory),那么SFT本质是behavior cloning;如果数据来源是RL筛选后的trajectory,那么SFT本质是policy compression;如果数据来源是混合数据(human + teacher + RL),那么SFT变成一种trajectory mixture modeling。

从agentic RL的角度来看,SFT训练agent的真正作用并不是提供最优策略,而是提供一个稳定的初始化策略(policy warm-start)。这一点在所有后续RL方法中都非常关键,因为无论是PPO、GRPO还是RLVR,如果没有SFT阶段,policy的初始分布通常过于随机,会导致exploration失败或者reward collapse。因此在现代agent训练pipeline中,SFT通常是第一阶段,用于建立“可执行行为空间”,让模型至少具备基本的tool-use和reasoning pattern。

在更高级的agent系统中,SFT还会被用于trajectory distillation,也就是将RL或search产生的高质量轨迹压缩成模型参数。例如在DeepSeek OPD或类似方法中,on-policy生成的trajectory会先经过reward模型或verifier筛选,然后通过SFT loss进行拟合,这种方式本质上是“用RL筛数据,用SFT学策略”,从而避免直接policy gradient带来的高方差问题。这类方法可以写成:

plain-text
τ ~ π_old
w = reward(τ)
θ = argmax Σ w * log πθ(τ)

从系统结构上看,一个完整agent SFT pipeline通常是:

plain-text
data source (human / teacher / RL / search)
        ↓
trajectory construction (state-action decomposition)
        ↓
token-level serialization
        ↓
supervised learning (MLE loss)
        ↓
policy initialization for RL / RLVR / PPO

但SFT在agent中的局限也非常明显。首先,它无法显式优化长期reward,因此在long-horizon任务中只能学到“平均行为”,而无法学习最优规划策略;其次,它对数据质量极度敏感,如果trajectory包含错误路径,模型会直接模仿错误行为;第三,它无法解决exploration问题,因为SFT本质上是offline learning,不具备主动探索能力。因此在现代agent体系中,SFT通常不作为最终优化手段,而是作为基础层,与RLVR、PPO、trajectory search等方法组合使用。

SFT在agentic RL中将复杂的长链路决策问题投影为一个静态监督学习问题,通过模仿高质量trajectory来构建可执行策略空间,使模型具备基础的工具使用能力与任务分解能力,但它本身并不解决最优策略学习问题,而是为后续RL或search-based optimization提供一个稳定的策略起点与行为先验。因此在现代LLM agent训练体系中,SFT更像是“trajectory representation learning阶段”,而不是“policy optimization阶段”,它的价值在于稳定性与可初始化性,而不是最终性能上限。


长链路agentic RL执行优化:Trajectory RL(PPO变体)

在强化学习的标准理论体系中(Sutton & Barto, Reinforcement Learning: An Introduction, 2018),RL 的优化对象本质上不是单个 action,而是一个完整的 trajectory,也就是从初始状态到终止状态的一整条状态-动作序列:

τ=(s0,a0,r0,s1,a1,...,sT)\tau = (s_0, a_0, r_0, s_1, a_1, ..., s_T)

优化目标是最大化期望回报:

J(π)=Eτ∼π[∑t=0Tγtrt]J(\pi) = \mathbb{E}{\tau \sim \pi} \left[ \sum{t=0}^{T} \gamma^t r_t \right]

这一定义其实已经隐含了一个关键事实:RL 天生就是 trajectory optimization problem,只是在传统 MDP 中 horizon 通常较短,因此可以通过 TD learning 或 step-wise policy gradient 来近似。但当进入 agentic RL(尤其是 LLM agent)之后,这个 horizon 被极大拉长,从几十步扩展到几十甚至上百 tool calls + reasoning steps,因此 trajectory 结构成为主导对象。

在这一背景下,PPO(Proximal Policy Optimization)成为当前 agent RL 中最主流的优化算法。PPO 最初由 Schulman et al. 在 2017 年提出(“Proximal Policy Optimization Algorithms”),其核心思想不是改变 RL 目标函数,而是限制 policy update 的幅度,从而避免在 high-variance trajectory reward 下出现训练崩溃。PPO 的关键优化目标可以写成:

$L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min \left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]$

其中:

  • (r_t(\theta)) 是新旧 policy 的概率比

  • (A_t) 是 advantage(来自 trajectory reward)

在 agent setting 中,这个公式的关键变化是:虽然优化仍然发生在 token/action level,但 reward 已经变成 trajectory-level reward。这意味着 credit assignment 问题被强行“摊平”到每一步 token 上。

当 PPO 被用于 long-horizon agentic RL 时,问题开始变得本质化。根据近年来 agent RL 综述(如 The Landscape of Agentic Reinforcement Learning for LLMs, 2025),LLM agent 已经从传统 MDP 转向 POMDP(部分可观测马尔可夫决策过程),并且 trajectory 长度显著增加,reward 通常是 sparse 或 terminal-based,例如:

  • WebShop 成功率

  • ALFWorld task completion

  • code execution pass/fail

  • tool-use correctness

在这种 setting 下,trajectory RL 的训练流程通常变成如下结构:

首先是 rollout 阶段,agent 在环境中执行完整 trajectory:

python
trajectory = []
state = env.reset(task)

for t in range(T):
    action = policy(state)   # LLM reasoning + tool call
    next_state, reward, done = env.step(action)

    trajectory.append((state, action, reward))
    state = next_state

    if done:
        break

这里的关键点是:policy 不再只是生成 token,而是在生成 action sequence,这个 action 可能是 reasoning step,也可能是 tool invocation,因此 trajectory 实际是一个“混合结构序列”。

接下来是 reward aggregation。在 long-horizon agent RL 中,reward 往往极其稀疏,因此通常用 Monte Carlo return:

R(τ)=∑t=0TγtrtR(\tau) = \sum_{t=0}^{T} \gamma^t r_t

在很多 LLM agent benchmark 中甚至是:

  • 成功 = 1

  • 失败 = 0

这会导致极强的 variance 问题。

然后进入 PPO update 阶段,将 trajectory reward 传播回每一步:

python
for (s, a, A) in trajectory:

    ratio = pi(a|s) / pi_old(a|s)

    loss = min(
        ratio * A,
        clip(ratio, 1 - eps, 1 + eps) * A
    )

    optimize(loss)

这里本质上是在做 trajectory credit assignment 的近似解,但这种近似在长 horizon 下会变得非常不稳定。

这也是为什么近年来 agent RL 研究逐渐暴露出 PPO 在 long-horizon 场景中的系统性瓶颈。根据最新 survey(如 A Technical Survey of Reinforcement Learning Techniques for LLMs, 2025)以及 agent RL 综述(2025–2026),主要问题集中在以下几个方面:

首先是 credit assignment failure。在长链路任务中,reward 通常只在最后一步出现,但 PPO 会将这个 reward 平均传播到所有 token/action 上,这导致模型无法判断到底是哪一步决策影响了最终结果,从而产生 high variance gradient。其次是 trajectory explosion problem,随着 horizon 增长,state-action space 组合呈指数增长,使得 exploration 变得极其低效。第三是 partial observability 问题,在 LLM agent 中 state 并不是完整环境状态,而是 observation + memory context,这使得 MDP 实际退化为 POMDP,进一步增加学习难度。

在工程和研究层面,为了解决 PPO 在 trajectory RL 中的不稳定性,已经出现多个变体方向。

一个方向是 turn-level PPO(Turn-PPO),其核心来自 recent paper Turn-PPO: Turn-Level Advantage Estimation for Multi-Turn RL(2025)。该方法不再在 token level 进行优化,而是在“turn level MDP”上计算 advantage,从而减少 credit assignment 的粒度错误:

其核心改动是:

从 token MDP → turn MDP

另一个方向是 trajectory search-based rollout,例如 TSR(Trajectory-Search Rollouts)(2026)。

该方法并不改变 PPO loss,而是在 rollout 阶段引入 search(beam / best-of-N / shallow MCTS),提高 trajectory quality,从源头减少 RL variance。

还有一个更近期的重要趋势是 graph-based RL(例如 G2PO)。

它将 trajectory 从线性序列扩展为 state-transition graph,通过 aggregation identical states 来降低 variance,本质是在解决“trajectory independence assumption 不成立”的问题。

综合来看,trajectory RL + PPO 在 agentic RL 中的本质已经非常清晰:它是一个将“完整执行过程”作为优化对象的 policy gradient 框架,但由于 long-horizon + sparse reward + partial observability 三个问题叠加,它在实际系统中已经从“标准 RL 方法”演化成一个复杂的系统工程模块,而不是单一算法。

如果用一个统一系统表达 trajectory RL(PPO variant in agent setting),可以写成:

plain-text
Prompt → Policy (LLM)
        → Trajectory rollout (reasoning + tool use + memory)
        → Environment feedback
        → Trajectory reward (verifier / RM)
        → Advantage estimation
        → PPO clipped update
        → Updated policy

在当前的 agentic reinforcement learning(agentic RL)体系中,trajectory RL + PPO 的作用,本质上不是“训练一个更好的语言模型”,而是在一个部分可观测、长时序、工具增强的决策环境中,逼近一个可稳定优化的长链路策略学习框架。这个问题需要从强化学习的原始数学定义开始理解,而不是从 LLM 角度切入。按照 Sutton & Barto《Reinforcement Learning: An Introduction》(https://incompleteideas.net/book/the-book-2nd.html),强化学习的基本对象始终是 trajectory τ = (s0, a0, r0, …, sT),优化目标是最大化期望回报 J(π)=E[Σ γ^t r_t]。也就是说,从理论上讲,RL 从一开始就是 trajectory-level optimization,只是早期任务(如 Atari、控制问题)horizon 很短,因此可以近似为 step-wise learning。但在 agentic RL 中,这个结构发生了根本变化:trajectory 不再是几十步的低维控制序列,而是几十到上百步的语言推理、工具调用、环境交互与记忆更新的混合过程,因此 trajectory 本身成为主要优化对象,而 step 只是 trajectory 内部的局部采样单位。

当我们把 PPO(Schulman et al., 2017 https://arxiv.org/abs/1707.06347)引入这一结构时,它的作用就不再只是“稳定 policy gradient”,而是在工程上承担了三个关键角色。首先是对 trajectory reward 的 credit assignment 近似。在 agentic RL 中,reward 几乎总是 sparse 的,例如任务成功为 1,否则为 0,或者来自 verifier(如代码是否通过测试、是否完成网页任务)。这意味着 reward 只在 trajectory 末端出现,但 PPO 通过 advantage function A_t = R(τ) − V(s_t),将整个 trajectory 的结果分摊到每一个时间步,从而使每个 action 都能获得一个“与最终成功相关的学习信号”。换句话说,PPO 并不是在直接学习“哪一步最重要”,而是在用 value function 对整条轨迹进行统计性归因,这就是所谓的 approximate credit assignment,在 long-horizon setting 中是不可避免的近似解。

其次,PPO 在 agentic RL 中的核心作用是控制 trajectory distribution shift,这一点比“优化奖励”更重要。在长链路 agent 中,一个 policy update 可能导致整个行为轨迹发生结构性变化,例如 tool usage 顺序改变、planning 风格改变、甚至 memory usage pattern 改变。如果没有约束,policy gradient 会导致 trajectory collapse,即模型在下一轮 rollout 中完全偏离原有可用策略空间。PPO 的 clipped objective(TRPO思想见 https://arxiv.org/abs/1502.05477)本质上是在限制新旧 policy ratio 的变化幅度,从而保证 trajectory distribution 的连续性。在 agentic RL 里,这一点尤其重要,因为 trajectory 不再只是语言序列,而是包含工具调用状态、外部环境反馈和长期依赖结构的复合系统,一旦 distribution shift 过大,整个 agent 行为会出现不可控漂移。

第三个作用是将 trajectory-level RL 转换为可训练的 supervised-like optimization problem。在纯 RL 中,policy gradient 的高方差问题在 long-horizon setting 下会急剧放大,但 PPO 通过 clip + advantage weighting,使得优化过程在形式上非常接近 weighted maximum likelihood learning,也就是一种“受约束的行为模仿学习”。在 InstructGPT 的 RLHF pipeline(https://arxiv.org/abs/2203.02155)中,这一点已经体现得非常明显:模型并不是直接最大化 reward,而是通过 PPO 在 KL constraint 下对 reward 加权后的行为分布进行拟合。因此在 agentic RL 中,PPO 实际上承担了一个混合角色,它既是强化学习算法,也是稳定的分布对齐机制。

当我们把这些机制放回 agentic 环境,会发现 trajectory RL + PPO 的真实作用并不是“学习单步动作”,而是学习“在长链路执行过程中保持策略一致性并逐步提升成功概率”。例如在 web agent 或 coding agent 中,一个 trajectory 可能包含 planning、retrieval、tool execution、self-correction 等多个阶段,这些阶段之间存在强依赖关系,而 PPO 通过 trajectory-level advantage 将最终结果反向传播,使模型逐步调整整个行为分布,而不是局部 token。

从工程实现角度,一个典型 agent PPO 训练循环可以表示为:agent 在环境中生成完整 trajectory τ,然后根据终止 reward R(τ) 或 verifier score 计算 advantage,再通过 clipped policy gradient 更新模型参数,这个过程可以写成如下形式:

python
trajectory = rollout(policy, env)

R = compute_terminal_reward(trajectory)

for t, (s, a) in enumerate(trajectory):
    A_t = R - V(s)

    ratio = pi(a|s) / pi_old(a|s)

    loss = min(
        ratio * A_t,
        clip(ratio, 1-eps, 1+eps) * A_t
    )

    optimize(loss)

但这个结构在 agentic RL 中的关键意义不在代码,而在它隐含的系统假设:整个 trajectory 被视为一个不可分割的优化单元,而 PPO 负责在“trajectory distribution space”中做局部梯度更新。换句话说,它优化的不是单个 action,而是 action 序列所构成的概率分布。

不过在长链路 agent 场景中,PPO 也暴露出本质性局限,这一点在 recent agent RL survey 中已有总结(例如 https://arxiv.org/abs/2308.08155)。首先是 credit assignment 的不可避免近似误差,当 trajectory 很长时,A_t = R − V(s_t) 只能提供极其噪声化的学习信号,导致 gradient variance 极高;其次是 exploration inefficiency,在长 horizon 下随机探索几乎无法到达成功 trajectory;第三是 POMDP 问题,在 agent 中 state 并不完整,policy 实际依赖 memory 和 context window,使得 Markov assumption 不成立;第四是 reward hacking,在 verifier-based reward 中,agent 可能学习到 shortcut behavior,而不是真实任务能力。

因此在当前研究前沿,PPO 在 agentic RL 中的定位已经发生变化,它不再是“核心学习算法”,而更像一个 trajectory-level stability controller + approximate credit assignment mechanism,用来在 long-horizon system 中维持可训练性,而真正决定上限能力的因素,已经转向 trajectory sampling strategy、memory system、search mechanism(如 MCTS/beam search)以及数据分布设计。例如 Turn-PPO(https://arxiv.org/abs/2512.17008)尝试将 token-level MDP 提升为 turn-level MDP,降低 horizon 噪声;而 search-based agent RL(如 https://arxiv.org/abs/2503.09516)则引入规划结构,用 search 替代纯 policy gradient 的探索能力,这些都说明一个趋势:PPO 仍然重要,但它正在从“学习算法核心”退化为“稳定优化组件”。

总结 trajectory RL + PPO 在 agentic RL 中的作用:它提供了一种在部分可观测、长时序、稀疏奖励的环境中,将完整执行轨迹作为优化对象的稳定近似方法,通过 clipped policy gradient 控制策略变化幅度,并通过 advantage estimation 将 trajectory-level reward 传播到局部决策层,从而使长链路 agent 的行为优化在工程上可训练、可收敛、可扩展,但它本身并不能解决长 horizon credit assignment 的根本困难,而只是提供一个足够稳定的近似优化框架,使更高层的系统(memory、search、tool-use)能够在其之上构建。


长链路agentic RL执行优化: Distillation-based RL

在长链路 agentic RL 的体系中,distillation-based RL(基于蒸馏的强化学习)本质上是一种介于纯强化学习(trajectory RL / PPO)与纯监督学习(SFT)之间的折中范式,它试图解决 long-horizon agent 训练中最核心的不稳定问题:直接用 policy gradient 优化 trajectory reward 时,梯度方差过大、credit assignment 极其困难、以及 exploration 成本过高。因此,这类方法在工业界(尤其是大模型 + agent 系统)中逐渐演化为一种更稳定的替代路径:不直接“最大化 reward”,而是把 reward 的作用转化为对“数据分布的重加权”,再通过蒸馏方式让模型拟合这个被重塑后的行为分布。这个思想可以从两个经典方向理解:一方面来自 imitation learning / knowledge distillation(Hinton et al., 2015 https://arxiv.org/abs/1503.02531),另一方面来自 RLHF 中 PPO+KL regularization 的弱化版本(例如 InstructGPT https://arxiv.org/abs/2203.02155),再加上后续 DeepSeek 类 OPD(on-policy distillation)体系的工程化扩展,本质上形成了一条“用 supervised learning 近似 RL”的路径。

在 agentic RL 中,distillation-based RL 的关键变化是:优化对象不再是 policy gradient,而是 trajectory distribution matching。也就是说,系统首先通过当前 policy 或外部 teacher / verifier 生成一批 trajectories,这些 trajectories 是 agent 在真实或模拟环境中的完整执行轨迹,包括 reasoning steps、tool calls、memory updates 等复杂结构。然后通过 reward model、verifier 或规则系统对这些 trajectory 进行打分,形成一个隐式的质量分布 p_good(τ),而训练目标不再是最大化 expected return,而是让 student policy 去拟合这个“高质量 trajectory 分布”。换句话说,它将 RL 问题从“优化行为”转化为“学习筛选后的行为分布”。

具体来说,一个典型 distillation-based RL pipeline 会是这样的结构:首先 agent 在环境中采样 trajectory τ ~ π_old(a|s),这些 trajectory 可以是 on-policy(来自当前模型)也可以是 off-policy(来自历史模型或 teacher model)。然后这些 trajectory 会被 reward model 或 verifier 评估,得到一个标量或分段评分 R(τ),这个分数不会直接用于 policy gradient,而是用于构造一个权重函数 w(τ),例如 w(τ)=softmax(R(τ)/T),或者基于 ranking / advantage normalization 的方式。之后,训练过程变成一个加权 maximum likelihood estimation 问题,即让 student 模型去拟合高权重 trajectory 中的 action 序列分布,这个过程可以写成:

python
loss = 0
for trajectory in dataset:
    weight = softmax(reward_model(trajectory))
    for (s, a) in trajectory:
        student_logprob = policy.logprob(a | s)
        loss += - weight * student_logprob
optimize(loss)

从形式上看,这已经不再是 RL,而是 weighted behavior cloning,但它之所以仍然被称为 “distillation-based RL”,是因为 weight 并不是来自人工标注数据,而是来自 reward signal 或 online rollout evaluation,因此它保留了 RL 的“评价机制”,但舍弃了 policy gradient 的高方差优化过程。这一点在 long-horizon agent 场景中非常关键,因为 trajectory 越长,PPO 的 advantage estimation 越不稳定,而 distillation 方法通过直接学习分布,绕开了 temporal difference propagation 的问题。

在 DeepSeek V4 / OPD(on-policy distillation)体系中,这一机制被进一步系统化。模型不再依赖静态 dataset,而是使用当前 policy 生成 on-policy trajectories,然后用 reward model + teacher model 对这些 trajectories 进行联合评估,再进行 KL-based distillation。与 PPO 不同,OPD 不直接优化 reward,而是用 reward 来调制 distillation 强度,也就是说 reward 的作用被“投影”到了 loss weighting 上,而不是 gradient direction 上。这种设计使得训练稳定性显著提高,因为它避免了 RL 中典型的 high variance gradient update,同时仍然保持 reward signal 对 policy 的约束能力。

从 agentic RL 的角度来看,distillation-based RL 的真正作用可以理解为:它在 trajectory space 中做 density re-shaping,而不是 trajectory optimization。也就是说,它不是试图直接寻找最优 trajectory,而是不断调整模型生成 trajectory 的概率分布,使高 reward trajectory 出现的概率逐渐上升。这一点与 PPO 的根本区别在于:PPO是在“policy parameter space”做梯度上升,而 distillation-based RL是在“data distribution space”做监督拟合。

在 long-horizon agent 系统中,这种差异非常关键,因为 trajectory RL 的一个核心问题是 credit assignment failure,而 distillation-based RL 完全绕开这个问题,它不问“哪一步导致成功”,而是问“哪些完整轨迹是好的”,然后直接强化这些轨迹整体。这种方法虽然牺牲了一部分细粒度优化能力,但换来了极高的训练稳定性和工程可扩展性,因此在大模型 agent 系统中非常常见。

从系统角度,一个完整 distillation-based RL agent pipeline 通常是:

plain-text
Prompt → Agent rollout (trajectory generation)
       → Reward model / verifier scoring
       → Trajectory weighting
       → Supervised distillation (sequence-level MLE)
       → Updated policy

这个过程与 PPO 的最大区别在于,PPO 是 step-wise policy gradient + advantage propagation,而 distillation-based RL 是 sequence-level weighted likelihood optimization,因此它更接近 supervised learning pipeline,但数据分布由 RL 信号动态塑形。

不过这种方法也有其结构性限制。首先,它本质上缺乏显式 exploration,因为模型只是在拟合已有 trajectories,而不是主动探索新的 action space;其次,它依赖 reward model 的质量,如果 reward shaping 不准确,会导致 distribution collapse;第三,它对 long-horizon credit assignment 是“回避而不是解决”,因此在需要复杂规划能力的任务中上限可能低于 trajectory RL + search 结合的方法。

因此在当前 agentic RL 体系中,distillation-based RL 通常被定位为“稳定性层”或“中间训练层”,而不是最终优化手段。例如在 InstructGPT 范式中(https://arxiv.org/abs/2203.02155),它表现为 PPO 前后的 KL regularization;在 DeepSeek OPD 范式中,它表现为 reward-weighted distillation;在更广义 agent 系统中,它往往与 search-based RL(MCTS / beam search)结合,用于提升 trajectory quality,同时用 distillation 保持 policy 稳定。

distillation-based RL 在 long-horizon agentic RL 中的作用是:它将强化学习问题从“显式优化序列决策过程”转化为“对高质量轨迹分布进行监督学习拟合”,通过 reward signal 对数据分布进行重加权,从而在避免高方差 policy gradient 的同时,实现对长链路行为策略的稳定逼近,但代价是探索能力和精细 credit assignment 能力的下降,因此它通常作为 PPO / search / RL hybrid 系统中的稳定化组件,而不是独立的最优策略学习方法。


Harbor框架怎么做Rollout Generation 和 RL Optimization-含代码

Harbor 本身不是一个 RL Trainer,而是一个 Agent Harness(运行时框架)。它负责让 Agent 在真实环境(例如 τ-bench、Terminal-Bench)中执行任务,记录完整 Rollout,然后把这些 Rollout 提供给外部 RL 框架(如 VERL、Prime-RL、SkyRL、OpenRLHF)进行优化。因此 Harbor 官方所说的 Rollout Generation 和 RL Optimization,前者主要由 Harbor 完成,后者主要是 Harbor 与外部 RL Trainer 的协同,而不是 Harbor 自己实现 PPO 或 GRPO。

Rollout Generation 的核心思想是把 Agent 放进真实环境中,让 Agent 按照当前策略不断执行任务,并完整记录每一步的 Observation、Reasoning、Tool Call、Tool Return、Environment State、Reward 等信息。对于 Harbor 来说,一个 Rollout 就是一条完整的 Trajectory,而不是最终一个 Success/Fail 标签。例如 Agent 收到 τ-bench 的一个任务:

python
task = {
    "instruction":
    "Cancel the customer's order if it has not been shipped.",
    "environment":
    "customer_support"
}

Harbor 首先初始化 Environment:

python
env = TauBenchEnv( task)

然后初始化 Agent:

python
agent = ClaudeCodeAgent(  model="claude")

开始 Rollout:

python
trajectory = []
observation = env.reset()
done = False
while not done:
    action = agent.act( observation )
    next_observation, reward, done, info = env.step( action  )
    trajectory.append({
        "observation": observation,
        "action": action,
        "reward": reward,
        "next_observation": next_observation,
        "tool_calls": info.get("tool_calls"),
        "tool_returns": info.get("tool_returns")
    })
    observation = next_observation

这里的 Observation 并不仅仅是 Prompt,而可能包含完整上下文:

python
observation = {
    "messages": history,
    "database_state": db_state,
    "available_tools": tool_schema,
    "system_prompt": system_prompt
}

Agent 输出 Action:

python
action = {
    "reasoning":
    "...",
    "tool_call":{
        "name":"cancel_order",
        "arguments":{
            "order_id":"12345"
        }
    }
}

Harbor 会自动调用 Tool:

python
tool_result = tool_executor.execute(action["tool_call"])

返回:

python
tool_result = {
    "status":"success",
    "order_status":"cancelled"
}

Environment 更新状态:

python
next_observation = env.update(tool_result)

整个过程中 Harbor 会不断向 Trajectory 中写入数据:

python
trajectory.append({
    "step": step,
    "messages": messages,
    "reasoning": reasoning,
    "tool_call": tool_call,
    "tool_return": tool_result,
    "reward": reward
})

直到任务结束:

python
reward = verifier.verify( env.final_state())

如果数据库状态满足目标:

python
reward = 1

否则:

python
reward = 0

最终 Harbor 输出:

python
rollout = {
    "task": task,
    "trajectory": trajectory,
    "reward": reward
}

实际上 Harbor 保存的数据通常比这还丰富,例如:

python
rollout = {
    "messages": [...],
    "reasoning_trace":[...],
    "tool_calls":[...],
    "tool_returns":[...],
    "environment_states":[...],
    "reward":1,
    "metadata":{
        "model":"claude",
        "steps":8,
        "latency":2.4
    }
}

这些 Rollout 会保存到磁盘:

python
import json
with open( "rollout.json", "w") as f:
    json.dump(
        rollout,
        f,
        indent=2
    )

或者直接送入 Replay Buffer:

python
replay_buffer.add( rollout)

这就是 Harbor 的 Rollout Generation,本质上就是持续采集 Agent 与真实环境交互的完整轨迹。

RL Optimization 并不是 Harbor 自己实现 PPO,而是 Harbor 把这些 Rollout 提供给 RL 框架。最简单的方式是:

python
batch = replay_buffer.sample( batch_size=64)
trainer.update( batch)

例如使用 VERL:

python
trainer = VERLTrainer(
    actor=model,
    critic=critic
)

训练:

python
trainer.train(  rollouts=batch)

如果是 PPO,首先计算 Return:

python
returns = compute_return(
    rewards,
    gamma=0.99
)

计算 Advantage:

python
advantages = compute_gae(
    rewards,
    values,
    gamma=0.99,
    lam=0.95
)

然后:

python
loss = ppo_loss(
    old_log_probs,
    new_log_probs,
    advantages
)

optimizer.zero_grad()
loss.backward()
optimizer.step()

如果使用 GRPO,则 Harbor 会先生成同一个任务的多个 Rollout:

python
responses = []
for _ in range(8):
    responses.append(harbor.rollout(task) )

得到:

python
responses = [
    rollout1,
    rollout2,
    rollout3,
    ...
]

Verifier 打分:

python
scores = [
    verifier(r)
    for r in responses
]

计算 Group Advantage:

python
import numpy as np
advantages = ( scores -  np.mean(scores)) / ( np.std(scores)+1e-6)

然后:

python
trainer.train( responses,  advantages)

如果采用 OPD(On-Policy Distillation),Harbor 会同时生成 Student 与 Teacher Rollout:

python
student_rollout = harbor.rollout( student, task)

teacher_rollout = harbor.rollout( teacher, task)

生成训练数据:

python
sample = {
    "student": student_rollout,
    "teacher": teacher_rollout,
    "reward": reward
}

训练:

python
loss = ( rl_loss + beta *  kl_loss( student_logits,  teacher_logits ))

如果采用 Self-Distillation,则 Teacher 直接来自上一轮模型:

python
teacher = deepcopy( current_model)

Rollout:

python
trajectory = harbor.rollout( current_model, task)

Reward:

python
reward = verifier(trajectory)

只有通过验证:

python
if reward > 0:
    replay_buffer.add( trajectory )

训练:

python
trainer.train( replay_buffer.sample())

整个 Harbor + RL 的真实训练循环通常可以抽象为:

python
replay_buffer = ReplayBuffer()
for iteration in range(1000):
    tasks = sampler.sample_batch(32)
    rollouts = []
    for task in tasks:
        rollout = harbor.rollout( agent, task )
        replay_buffer.add(  rollout )
        rollouts.append( rollout)

    batch = replay_buffer.sample( batch_size=64 )
    trainer.update( batch )
    agent.save( f"checkpoint_{iteration}.pt" )

如果部署在大规模集群中,Rollout Worker 与 Trainer 会完全解耦。多个 Harbor Worker 并行负责环境交互:

python
class RolloutWorker:
    def collect( self,  policy ):
        task = sampler.sample()
        return harbor.rollout(policy,  task )

训练节点持续拉取数据:

python
while True:
    batch = replay_server.pull(batch_size=128 )
    trainer.update( batch )

更新后的模型再次广播:

python
new_policy = trainer.export()
rollout_worker.load(new_policy)

形成持续在线训练闭环。这也是目前 Agent RL(包括 Harbor、VERL、Prime-RL、SkyRL 等)的典型架构:Harbor 专注于高质量 Rollout Generation,把 Agent 放进真实环境执行任务并记录完整轨迹;RL Optimization 则由外部训练框架利用这些 Rollout 计算 Reward、Advantage、KL、Policy Loss 等目标,对策略模型进行持续更新,更新后的策略再回到 Harbor 继续生成新的 On-Policy Rollout,从而形成数据采集、策略优化、重新采集的闭环。


Self-Evolving OPD(自进化在线策略蒸馏)agentRL后训练方法

Self-Evolving On-Policy Distillation Agentic Learning(自进化在线策略蒸馏 Agent 学习)是一种面向长链路 Agent 的持续自我提升训练范式,它结合了 Self-Evolving(自进化)、On-Policy Distillation(在线策略蒸馏)以及 Agentic Learning(智能体学习)三个核心思想。它的核心目标不是训练一个固定能力的大模型,而是让 Agent 在不断执行任务的过程中产生新的经验,并利用当前策略产生的数据进行自我改进。传统大模型训练通常是一次性的流程,先收集数据,再进行 SFT、RLHF 或 DPO,训练结束后模型能力基本固定。而 Self-Evolving On-Policy Distillation Agentic Learning 认为,真正具备通用能力的 Agent 应该像人一样,在运行过程中不断尝试、犯错、总结经验、吸收优秀行为,并逐渐形成更强的策略。因此它将 Agent 的运行过程本身变成训练数据生成过程。

传统 Knowledge Distillation 的核心思想是让 Student 学习 Teacher:

python
teacher_output = teacher.generate(prompt)

student_loss = KL( student_output,  teacher_output)

其中 Teacher 通常是一个更大的模型,例如:

python
Teacher = GPT-5
Student = 32B Model

Teacher 负责提供高质量答案,Student 负责模仿 Teacher 的行为。但是这种方式存在一个限制,即 Teacher 的能力是固定的。如果任务分布发生变化,例如 Agent 从代码生成扩展到企业自动化、GUI 操作、复杂工作流执行,固定 Teacher 不一定能够提供最优策略。

Self-Evolving On-Policy Distillation 的核心变化是 Teacher 不再来自外部,而是来自 Agent 自己当前版本或者历史版本。也就是说,Agent 先使用当前 Policy 与环境交互,生成自己的 On-Policy Trajectory,然后从这些轨迹中筛选高质量行为,把这些行为作为新的 Teacher 数据,再训练当前 Agent。

整个过程可以表示为:

python
current_policy
        ↓
generate trajectory
        ↓
evaluate trajectory
        ↓
select high quality behavior
        ↓
distill back to policy
        ↓
new stronger policy

其中最关键的是 On-Policy。On-Policy 表示训练数据来自当前正在使用的策略:

python
trajectory = policy_theta.rollout(environment)

而不是来自旧模型或者人工数据:

python
trajectory = old_policy.rollout()

原因是 Agent 的能力提升依赖于当前模型的探索。如果一直使用旧数据训练:

python
old_behavior
        ↓
training
        ↓
old_behavior

模型只能重复已有能力,很难突破。

而 On-Policy:

python
current_behavior
        ↓
exploration
        ↓
better_behavior
        ↓
training
        ↓
improved_behavior

可以形成持续进化。

在数学上,Agent 可以表示为策略:

πθ(a∣s)\pi_\theta(a|s)

其中:

ss 表示当前环境状态,aa 表示 Agent 动作,例如:

python
action = {
"type":"tool_call",
"name":"search",
"arguments":{ "query":"xxx"}
}

Agent 与环境交互产生轨迹:

τ=(s0,a0,r0,s1,a1,r1,...,sT)\tau=(s_0,a_0,r_0,s_1,a_1,r_1,...,s_T)

然后根据轨迹奖励:

R(τ)=∑t=0TγtrtR(\tau)=\sum_{t=0}^{T}\gamma^tr_t

选择高质量轨迹:

python
trajectory = agent.rollout(task)
reward = evaluator( trajectory)

if reward > threshold:
    dataset.append(  trajectory )

这些成功轨迹就成为 Distillation Target。与普通 RL 不同,Self-Evolving On-Policy Distillation 不一定直接优化 Reward,而是增加一个行为模仿目标:

$L = L_{RL} + \lambda L_{distill}$

其中:

$L_{distill} D_{KL} ( \pi_{old} || \pi_{new} )

$$ 表示新的 Agent 学习过去优秀策略的分布。 代码形式:$

python
old_policy = copy( policy)

trajectory = policy.rollout( task)

loss_rl = reinforcement_loss(  trajectory)

loss_distill = KL( old_policy,policy)

loss = (loss_rl  + lambda_value *  loss_distill)

optimizer.step()

这里的 old_policy 就相当于 Teacher。

但是与传统蒸馏不同:

python
Teacher != fixed model

而是:

python
Teacher = previous_self

例如:

第一次训练:

python
Agent_v1

执行任务:

python
trajectory_v1 = Agent_v1.run(task)

筛选优秀轨迹:

python
good_data = filter( trajectory_v1)

训练:

python
Agent_v2.learn( good_data)

之后:

python
Agent_v2

成为新的 Teacher:

python
Agent_v3.learn( Agent_v2.data)

形成:

python
Agent_v1
→
Agent_v2
→
Agent_v3
→
Agent_v4

这种机制类似 AlphaGo Zero 的 Self Play,但 Agentic Learning 中环境更加复杂。AlphaGo 的环境是棋盘:

python
state = board

action = move

reward = win/loss

而 Agent 环境可能是:

python
state = {files,browser,database,memory,tool_result}

action = {search,execute,write,click}

reward = task_success

因此 Self-Evolving Agent 的核心不是简单生成答案,而是在复杂环境中不断提高任务完成能力。

一个典型 Agentic Self-Evolving 训练循环如下:

python
while True:
    task = task_generator.sample()
    trajectory = agent.rollout( task )
    score = verifier.evaluate(  trajectory)

    if score > threshold:
        replay_buffer.add(trajectory )

    batch = replay_buffer.sample()
    trainer.distill( batch)

其中 Task Generator 非常重要,因为它决定 Agent 学什么。如果任务一直简单:

python
task_level = easy

模型不会成长。

因此很多 Self-Evolving Agent 会采用 Curriculum Learning:

python
if success_rate > 0.8:
    task_level += 1

例如:

简单:

plain-text
读取文件

中等:

plain-text
分析多个文件并生成报告

困难:

plain-text
自动修改代码并通过测试

这样 Agent 能不断挑战自己的能力边界。

Self-Evolving On-Policy Distillation 在 Agentic RL 中通常会结合 Verifier,因为 Agent 自己产生的数据不能全部相信。如果没有验证机制,会产生 Self-Training Collapse,也就是模型不断学习自己的错误。

例如:

Agent:

python
modify_code()

错误:

python
test_failed()

但是模型认为成功:

python
reward = 1

然后:

python
wrong_solution
        ↓
distillation
        ↓
stronger_wrong_solution

因此必须加入:

python
external_verifier + environment_feedback + test_execution

例如:

Coding Agent:

python
reward = run_tests( repository)

Browser Agent:

python
reward = check_page_state()

GUI Agent:

python
reward = verify_action()

只有真实完成任务的轨迹才能进入蒸馏。

在长链路 Agent 中,Self-Evolving On-Policy Distillation 还需要解决 Credit Assignment 问题。一个任务可能包含:

python
trajectory = [
plan,
search,
tool_call,
observation,
modify,
test,
final_answer
]

最终 Reward:

python
reward = 1

但是不知道:

python
哪个 action

贡献最大。

因此通常会计算 Advantage:

At=Rt−V(st)A_t=R_t-V(s_t)

或者使用 GRPO:

python
responses = sample_multiple()

scores = verifier( responses)

advantages = normalize( scores)

让模型学习高价值步骤。

Self-Evolving On-Policy Distillation 也经常结合 Replay Buffer,但是与普通 Replay 不同,它保存的不是所有经验,而是 Agent 自己发现的高价值经验。

例如:

python
class ReplayBuffer:
    def add(self,trajectory):
        if trajectory.score > 0.9:
            storage.append(trajectory )

训练:

python
batch = buffer.sample()

agent.update( batch)

这样 Buffer 会逐渐成为 Agent 的经验库。

进一步的发展方向是 Multi-Agent Self-Evolving Distillation。多个 Agent 可以互相产生经验:

python
agents = [coder_agent,research_agent,planner_agent]

执行:

python
trajectory = agent.run(task)

评价:

python
judge.score(trajectory)

优秀行为:

python
shared_memory.add( trajectory)

所有 Agent 学习:

python
agents.learn( shared_memory)

形成群体进化。

因此,Self-Evolving On-Policy Distillation Agentic Learning 的本质是把传统大模型训练从“固定数据集训练”转变为“运行时持续进化”。模型本身既是数据生产者,也是经验筛选者,也是学习者。它通过当前策略不断探索环境,产生新的 On-Policy Trajectory,通过 Verifier 找出成功行为,再利用 Distillation 将这些行为重新注入模型参数,使 Agent 在不断循环中提升能力。未来长链路 Agent 的核心竞争力可能不只是初始模型大小,而是谁能够建立更高效的 Self-Evolving Learning Loop,让 Agent 在真实环境中持续获得经验并自动进化。


Self-Distilled(自蒸馏) Agentic RL后训练方法

Self-Distilled Agentic Reinforcement Learning(自蒸馏 Agentic RL)是一种让 Agent 通过自身生成的数据、自身评估能力和自身反馈机制不断提升能力的后训练方法,它的核心思想是不要完全依赖人工标注轨迹或者更强 Teacher Model,而是让当前 Agent 自己探索环境、产生任务执行轨迹,然后从这些轨迹中筛选高价值经验,再利用这些经验反过来训练自己,使模型形成一个“自己产生数据、自己判断质量、自己学习提升”的闭环。传统的大模型后训练通常依赖外部监督,例如 SFT 使用人工示范数据,RLHF 使用人工偏好数据,Knowledge Distillation 使用 GPT-5、Claude 等更强 Teacher 生成的数据,而 Self-Distilled Agentic RL 认为在 Agent 场景中,模型执行任务本身就会产生大量有价值的行为数据,如果能够设计可靠的验证机制,就可以让 Agent 从自身探索中学习,而不需要无限依赖外部 Teacher。

Self-Distilled Agentic RL 的核心区别在于训练目标发生变化。普通 Agentic RL 更像:

python
trajectory = teacher_or_agent.generate(task)

reward = verifier(trajectory)

update(model)

其中轨迹可能来自人工专家或者更强模型。而 Self-Distillation:

python
trajectory = agent.generate(task)

score = self.evaluate( trajectory)

if score > threshold:
    agent.learn(  trajectory )

模型自己生成 trajectory,然后自己判断哪些行为值得学习。

在数学上,Agent 本身仍然可以表示为策略模型 πθ(a∣s)\pi_\theta(a|s),其中 ss 是当前环境状态,aa 是 Agent 选择的动作。在普通强化学习中,我们希望最大化:

J(θ)=Eτ∼πθ[R(τ)]J(\theta)=E_{\tau\sim\pi_\theta}[R(\tau)]

其中 τ\tau 是完整执行轨迹,R(τ)R(\tau) 是轨迹奖励。而 Self-Distilled Agentic RL 增加了一个自蒸馏目标,希望当前策略不仅最大化奖励,还能够逼近自己发现的高质量行为分布:

$L = L_{RL} + \lambda D_{KL} ( \pi_\theta || \pi_{teacher} )$

但是这里的 Teacher 不一定是真正的外部模型,而可能是历史版本的自己:

python
teacher_policy = copy(current_policy)

teacher_policy.freeze()

当前模型:

python
student_policy = current_policy

训练目标:

python
loss = rl_loss + kl_loss( student_policy, teacher_policy)

因此 Self-Distillation 本质上是一种 Self-Improvement(自我提升)机制。

在 Agent 场景中,一个非常重要的问题是:模型如何知道自己的轨迹是不是好的?因为如果没有可靠 Reward,模型可能会学习自己的错误。因此 Self-Distilled Agentic RL 通常不会简单地相信自己的输出,而是引入 Verifier、Environment Feedback 或 Outcome Evaluation。例如 Coding Agent 可以通过运行测试判断:

python
result = run_test( generated_code)
reward = 1 if result.pass else 0

数学 Agent 可以通过答案验证:

python
reward = math_checker( answer)

Browser Agent 可以通过检查网页状态:

python
reward = browser.verify( task_state)

只有通过验证的轨迹才进入自蒸馏阶段:

python
if reward > 0:
    replay_buffer.add( trajectory)

这一步非常关键,因为 Self-Distillation 最大的问题是模型可能产生“错误自信”。如果模型错误地认为自己的答案正确,那么它会不断强化错误行为,这被称为 Self-Training Collapse。因此实际系统通常需要一个外部或者半外部验证器。

Self-Distilled Agentic RL 通常包含三个核心组件:生成器(Generator)、评估器(Evaluator)、学习器(Learner)。生成器负责让 Agent 在环境中探索:

python
trajectory = agent.rollout( task)

评估器负责判断轨迹质量:

python
score = evaluator( trajectory)

学习器负责吸收高质量经验:

python
trainer.update(trajectory)

其中 Evaluator 可以来自多个来源。例如最简单的是规则验证:

python
def evaluator(result):
    if result.test_pass:
        return 1
    return 0

更复杂的是 LLM Judge:

python
score = judge_model(  trajectory)

或者多个评价器组合:

python
score = (  code_score + reasoning_score  + safety_score)

Self-Distilled Agentic RL 与传统 Knowledge Distillation 最大区别在于 Teacher 来源不同。传统蒸馏:

python
Teacher = GPT-5
Student = Smaller_Model

Teacher 提供:

python
teacher_output = teacher.generate( prompt)

Student 学习:

python
loss = KL( student, teacher)

而 Self-Distillation:

python
Teacher = Previous_Self

例如:

当前模型版本:

python
Agent_v1

经过 Rollout:

python
trajectory = Agent_v1.run(task)

筛选优秀轨迹:

python
good_data = filter( trajectory)

重新训练:

python
Agent_v2.learn(  good_data)

于是:

python
Agent_v1
    ↓
generate experience
    ↓
filter
    ↓
Agent_v2

新的 Agent 比旧 Agent 更强。

这和 AlphaGo Zero 的思想非常接近。AlphaGo Zero 没有大量人工棋谱,而是通过自身对弈产生数据:

python
while True:
    game = self_play()
    winner = evaluate(game)
    train(game)

Self-Distilled Agentic RL 将这种思想迁移到了大语言模型 Agent:

python
while True:
    task = sample_task()
    trajectory = self_play(task)
    reward = verify(  trajectory )
    if reward:
        train(  trajectory)

但是 LLM Agent 的环境比棋盘复杂得多,因此需要更强的任务生成和验证机制。

在长链路 Agent 中,Self-Distillation 特别适合解决数据稀缺问题。例如一个企业 Agent 需要学习操作内部系统:

python
task =
"完成采购审批流程"

人工标注完整轨迹非常昂贵:

plain-text
打开系统
填写表单
上传附件
提交审批
处理异常

但是 Agent 可以自己尝试:

python
trajectory_1 = agent.run(task)

trajectory_2 = agent.run(task)

trajectory_3 = agent.run(task)

然后:

python
scores = [
    evaluator(t1),
    evaluator(t2),
    evaluator(t3)
]

选择最佳:

python
best = max( trajectories,key=score)

加入训练:

python
trainer.update( best)

经过大量循环,Agent 会逐渐掌握这个业务流程。

Self-Distilled Agentic RL 还经常结合 Replay Buffer。因为不是每次 Rollout 都立即训练,而是保存历史高质量经验:

python
class ReplayBuffer:
    def add(self, trajectory):
        if trajectory.reward > 0:
            self.data.append(   trajectory )

训练:

python
batch = replay_buffer.sample()

trainer.update( batch)

这样 Agent 可以不断回忆过去成功经验。

进一步的发展是 Iterative Self-Distillation,也就是循环自蒸馏。模型每隔一段时间生成新的经验,再更新自身:

python
for iteration in range(N):
    trajectories = agent.collect()
    good_cases = filter(   trajectories )
    agent.train( good_cases  )

每一次 iteration:

python
Agent_v1

↓

Agent_v2

↓

Agent_v3

↓

Agent_v4

模型能力逐渐提升。

但是 Self-Distilled Agentic RL 也存在几个关键挑战。第一个问题是 Exploration(探索不足)。如果 Agent 只学习自己已经会的任务,就无法获得新的能力:

python
new_behavior = False

因此需要增加探索:

python
action = sample( policy, temperature=1.0)

第二个问题是 Reward Hacking。Agent 可能找到漏洞:

python
goal:
pass_test

于是:

python
agent:
modify_test()

测试通过,但是任务没有真正完成。因此需要更加可靠的 Verifier。

第三个问题是 Confirmation Bias(自我确认偏差)。如果模型不断训练自己的错误轨迹:

python
wrong_behavior
↓
self_training
↓
stronger_wrong_behavior

最终会导致能力下降。因此需要:

python
quality_filter
+
external_verifier
+
diverse_sampling

目前很多先进 Agent 后训练路线都在探索 Self-Distilled Agentic RL,因为它解决了一个核心问题:未来 Agent 的能力提升不能完全依赖人工标注和超级 Teacher,真正可扩展的方法应该让 Agent 能够在真实环境中不断执行任务、产生经验、验证经验、吸收经验,形成类似人类学习的闭环。它本质上把大模型后训练从一次性的 Dataset Training 转变为持续学习系统(Continual Learning System),其中模型既是执行者,也是数据生产者,也是部分监督者,这也是未来 Agentic RL 发展的重要方向。


ShortOPD:动态长度OPD怎么提升剪枝模型性能

ShortOPD(Short On-Policy Distillation)或者 Dynamic Length OPD(动态长度 On-Policy Distillation)的核心思想是解决传统 OPD 在模型剪枝(Pruning)、蒸馏压缩场景中的一个关键问题:Teacher 和 Student 的能力差距并不是均匀存在于整个生成序列中的,因此没有必要让 Student 学习完整长轨迹,而应该动态决定每一次蒸馏需要学习多少 Token、哪些 Token 和哪些推理片段。对于大模型剪枝后的模型来说,这个问题更加明显,因为剪枝会降低模型容量,使 Student 在某些简单 Token 上仍然保持较强能力,但是在复杂推理、长链路依赖、低频知识和困难决策位置出现明显退化。如果使用传统 Full OPD:

python
teacher_output = teacher.generate( prompt, max_tokens=4096)

student.learn(teacher_output)

Student 会被迫学习完整 4096 Token 的 Teacher 输出,但是其中大量 Token 已经超出了 Student 当前需要提升的范围。例如一个代码 Agent 任务:

plain-text
分析需求
→
读取文件
→
定位函数
→
发现错误
→
修改代码
→
运行测试
→
反思失败原因
→
重新修改
→
最终通过

对于剪枝后的模型来说,可能前面的代码读取和简单分析能力仍然保留,但是在“发现错误→反思→重新规划”阶段能力下降。如果仍然蒸馏全部轨迹,大量计算都会消耗在 Student 已经掌握的区域,而真正困难部分得到的梯度比例反而下降。因此 ShortOPD 的核心不是减少训练数据,而是动态寻找 Teacher 和 Student 差距最大的区域,让蒸馏计算集中在模型能力缺失的位置。

数学上,可以把传统 OPD 看成:

LOPD=∑t=1TDKL(PT(yt∣x,y<t)∣∣PS(yt∣x,y<t))L_{OPD}=\sum_{t=1}^{T}D_{KL}(P_T(y_t|x,y_{<t})||P_S(y_t|x,y_{<t}))

其中 T 是完整生成长度。传统方法默认:

python
所有 token 都同等重要

但是实际上不同位置的信息价值不同,因此 ShortOPD 引入动态权重:

LShortOPD=∑t=1TwtDKL(PT∣∣PS)L_{ShortOPD}=\sum_{t=1}^{T}w_tD_{KL}(P_T||P_S)

其中:

wt=f(DKL,uncertainty,reward,confidence)w_t=f(D_{KL},uncertainty,reward,confidence)

表示第 t 个 Token 是否值得蒸馏。如果 Teacher 和 Student 输出接近:

python
KL(student, teacher) ≈ 0

说明 Student 已经掌握,不需要大量训练;如果:

python
KL(student, teacher) >> 0

说明 Student 和 Teacher 存在能力差距,该位置具有更高蒸馏价值。例如:

python
for token in trajectory:
    kl = calculate_kl( teacher[token], student[token])
    if kl > threshold:
        keep(token)
    else:
        prune(token)

这就是 Dynamic Token Selection。对于剪枝模型,ShortOPD 最大价值在于解决 Capacity Gap(容量差距)问题。剪枝后的模型参数减少,例如:

plain-text
Teacher:
70B parameters


Pruned Student:
7B parameters

虽然 Student 参数减少了 10 倍,但是并不意味着所有能力下降 10 倍。实际上模型压缩通常表现为非均匀损失:

python
能力下降分布:

简单知识:
    -5%

语言理解:
    -10%

复杂推理:
    -40%

长链规划:
    -60%

因此固定长度蒸馏无法精准恢复损失能力,而动态长度 OPD 可以根据 Student 当前状态决定恢复方向。例如:

python
if student_confidence > 0.9:
    rollout_length = 128

elif student_confidence > 0.5:
    rollout_length = 512

else:
    rollout_length = 2048

简单任务使用短 Rollout,困难任务增加 Rollout 长度。这也是 Dynamic Length 的核心:不是所有任务都需要相同思考长度。

对于一个数学问题:

plain-text
1+1=?

Teacher 可能生成:

plain-text
1+1 equals 2

此时 ShortOPD:

python
max_length=32

即可。

但是对于:

plain-text
证明一个复杂数学定理

Teacher 可能生成:

plain-text
分析条件
→
构造公式
→
尝试证明
→
发现错误
→
重新推导
→
完成证明

则需要:

python
max_length=2048

因此动态长度本质是在训练阶段学习:

plain-text
什么时候需要更多 Teacher Guidance

在模型剪枝场景中,ShortOPD 通常会结合 Importance Sampling。因为剪枝后的 Student 对不同 Token 的敏感度不同,需要优先恢复重要区域。例如计算:

python
importance = ( teacher_loss -  student_loss)

如果:

python
importance > threshold

增加蒸馏预算:

python
rollout_length += 512

如果:

python
importance < threshold

减少:

python
rollout_length -= 128

完整逻辑:

python
def dynamic_opd(prompt):
    student_output = student.generate(  prompt )
    difficulty = measure_gap(  student_output, teacher_output )
    if difficulty < 0.2:
        length = 128
    elif difficulty < 0.5:
        length = 512
    else:
        length = 2048
    teacher_output = teacher.generate( prompt, max_tokens=length )
    loss = distill( student, teacher_output )
    return loss

ShortOPD 对剪枝模型还有一个重要作用,就是降低过拟合风险。传统蒸馏经常让 Student 过度模仿 Teacher 的全部输出分布,但是对于小模型来说,完整复制大模型行为并不一定最优,因为 Student 的表达能力有限。如果强行学习大量复杂轨迹,会导致模型训练不稳定。例如:

python
Teacher:"复杂推理链"

Student:"容量不足"

结果可能:

plain-text
训练 Loss 降低

但是实际能力下降

这就是 Distillation Overfitting。

ShortOPD 通过只学习关键片段:

python
Teacher:
A B C D E F G

Student:

       D E F

让有限参数集中表示最重要的决策模式。

在 Agent 场景中,ShortOPD 还可以和 Trajectory Truncation 结合。例如一个 Agent 执行:

python
trajectory = [
plan,
search,
tool_call,
observation,
reflection,
answer
]

计算每个阶段的重要性:

python
importance = {
"plan":0.8,
"search":0.2,
"tool_call":0.9,
"observation":0.1,
"reflection":1.0
}

最终选择:

python
selected_segments = [
"plan",
"tool_call",
"reflection"
]

进行 OPD。

因此 ShortOPD 并不是简单把 Rollout 长度缩短,而是一种 Adaptive Distillation Strategy,它根据 Student 当前能力动态决定 Teacher 提供多少监督信息。对于剪枝模型来说,它解决了三个核心问题:第一,减少无效 Token 蒸馏,让有限训练预算集中在 Student 能力缺失区域;第二,根据模型容量动态调整学习难度,避免小模型被大模型复杂行为压垮;第三,通过恢复高价值推理路径,提高剪枝模型在复杂任务上的性能。

最终,一个面向剪枝模型的 Dynamic Length OPD 流程可以表示为:

python
while training:
    response = student.rollout(  prompt )
    gap = estimate_gap( student, teacher, response )
    length = dynamic_length( gap )
    teacher_response = teacher.rollout( prompt, max_tokens=length )
    loss = KL( student, teacher_response)
    update( student,loss)

因此 ShortOPD 的本质是把传统“固定长度、固定监督强度”的蒸馏方式,变成“根据 Student 能力动态分配 Teacher 计算资源”的蒸馏方式。在大模型压缩、剪枝、量化之后,这种方法能够显著提高小模型对复杂推理能力的恢复效率,因为模型不再学习 Teacher 的全部行为,而是学习那些真正决定能力差距的关键决策区域。


文本OPD(On-Policy Distillation)用verl怎么做

在 verl 中实现文本 OPD(On-Policy Distillation)的核心思想是把 Distillation 过程改造成一个基于当前 Student Policy 在线采样的数据闭环训练过程,而不是传统 SFT 那样直接读取固定 Teacher Dataset。verl 本身是一个面向大模型强化学习训练的框架,它主要抽象了 Rollout、Actor、Reward、Trainer、Distributed Worker 等组件,因此 OPD 并不需要重新设计训练框架,而是把原本 PPO/GRPO 中用于计算 Reward 和 Advantage 的部分替换成 Teacher Model 提供的蒸馏信号。简单理解,普通 RLHF 流程是 Student 根据自己的策略生成回答,然后 Reward Model 判断好坏,最后通过 Policy Optimization 更新模型;而 OPD 流程是 Student 根据自己的当前策略生成回答,然后 Teacher 根据同一个输入提供更优分布或者更优答案,最后通过 KL Divergence 约束 Student 向 Teacher 靠近。因此 OPD 的训练目标不是最大化 reward,而是最小化 Student Policy 和 Teacher Policy 之间的距离:

LOPD=DKL(Pteacher(y∣x)∣∣Pstudent(y∣x))L_{OPD}=D_{KL}(P_{teacher}(y|x)||P_{student}(y|x))

其中PteacherP_{teacher} 表示 Teacher 对下一个 token 的概率分布,PteacherP_{teacher}表示 Student 当前策略的概率分布。verl 中最简单的文本 OPD 实现方式,就是增加一个 Teacher Worker,让 Student Worker 负责在线生成数据,Teacher Worker 负责提供监督分布,然后 Trainer 计算 KL Loss 更新 Student。例如原始 PPO 结构中:

python
rollout = actor.generate(prompt)
reward = reward_model(prompt, rollout)
advantage = compute_advantage( reward)

actor.update( advantage)

修改成 OPD 后:

python
student_output = student.generate(
    prompt,
    temperature=0.7,
    max_tokens=2048
)

teacher_output = teacher.generate( prompt, temperature=0)
loss = distillation_loss(student_output, teacher_output)
student.update( loss)

这里 Student 的数据必须来自当前 Policy,也就是 On-Policy,因为如果直接使用旧 Teacher 数据训练,本质就退化成 Offline Distillation 或 SFT,而不是 OPD。所谓 On-Policy,就是训练过程中不断让当前 Student 产生新的样本:

python
while training:
    prompts = sample_batch()
    responses = student.rollout( prompts )
    teacher_scores = teacher.evaluate(  responses )
    update_student( responses,  teacher_scores )

这样每一轮训练的数据分布都会随着 Student 能力变化而变化。例如初始 Student 只能回答简单问题,那么采样出来的数据主要集中在简单错误区域;经过几轮训练后,Student 能力提高,新的 Rollout 会自然转向更加困难的问题,因此 OPD 实际上形成一个自动难度提升的数据闭环。

在 verl 内部,通常需要增加一个 Teacher Rollout Worker。verl 基于 Ray 架构管理不同 Worker,例如 Actor Worker 负责模型生成,Reference Worker 负责计算 KL,Reward Worker 负责奖励计算。OPD 中可以把 Teacher Model 作为一个额外的 Reference Policy:

python
@ray.remote
class TeacherWorker:
    def __init__(self, model):
        self.model = model
    def generate(self, prompts):
        return self.model.generate(  prompts )

训练过程中:

python
student_rollout = actor_worker.generate( prompts)
teacher_rollout = teacher_worker.generate(prompts)
loss = opd_loss(  student_rollout, teacher_rollout)
actor_worker.update( loss)

对于文本 OPD,最常见的 Loss 是 Token-level KL Distillation。因为语言模型不是直接预测一句话,而是在每一个位置预测下一个 token 的概率,所以需要比较每个 token 位置上的概率分布。Teacher Forward:

python
with torch.no_grad():
    teacher_logits = teacher( input_ids).logits

Student Forward:

python
student_logits = student( input_ids).logits

转换成概率:

python
temperature = 2.0

teacher_prob = torch.softmax(
    teacher_logits / temperature,
    dim=-1
)

student_log_prob = torch.log_softmax(
    student_logits / temperature,
    dim=-1
)

计算 KL:

python
import torch.nn.functional as F

loss = F.kl_div(
    student_log_prob,
    teacher_prob,
    reduction="batchmean"
)

然后反向传播:

python
optimizer.zero_grad()

loss.backward()

optimizer.step()

但是实际大模型 OPD 不会简单地对所有 Token 计算 KL,因为很多 Token 没有学习价值。例如一个回答:

plain-text
北京是中国的首都,它位于华北地区。

其中大量常识 Token Student 已经掌握,如果全部蒸馏,会浪费训练资源。因此实际系统会进行 Token Filtering,只保留 Teacher 和 Student 差距较大的位置。可以通过 KL 或 Entropy 判断:

python
token_kl = calculate_token_kl( teacher_logits, student_logits)

mask = token_kl > threshold

然后:

python
loss = ( token_kl * mask).mean()

这就是 Hard Token Mining。

对于 Agent 场景,verl 中 OPD 会更加复杂,因为数据不是简单文本,而是完整 Message Trajectory。例如:

json
{
    "messages":[
        {
            "role":"user",
            "content":"修复这个代码问题"
        },
        {
            "role":"assistant",
            "content":"分析代码结构"
        },
        {
            "role":"assistant",
            "content":"调用python工具"
        },
        {
            "role":"tool",
            "content":"运行结果"
        },
        {
            "role":"assistant",
            "content":"修改代码"
        }
    ]
}

其中 Tool Return 不应该参与 Loss,因为 Tool Return 是环境信息,不是 Agent 行为:

python
loss_mask = [
    0,  # user
    1,  # assistant reasoning
    1,  # tool call
    0,  # tool return
    1   # assistant action
]

最终:

python
loss = ( kl_loss * loss_mask).sum()

这也是为什么 Agent OPD 通常需要修改 verl 的 DataProto 和 Trainer,因为普通 RL 数据结构只有:

python
{
    prompt,
    response,
    reward
}

而 Agent OPD 需要:

python
{
    messages,
    trajectory,
    tool_calls,
    observations,
    loss_mask,
    teacher_logits
}

如果进一步做 Multi-Teacher OPD,可以在 verl 中加入 Teacher Router,让不同 Teacher 负责不同领域。例如:

python
teachers = {
    "code": code_teacher,
    "math": math_teacher,
    "general": general_teacher
}

根据任务选择:

python
teacher = router(prompt)
teacher_output = teacher.generate( prompt)

或者多个 Teacher 同时生成:

python
outputs = []
for teacher in teachers:
    outputs.append(teacher.generate(prompt))

再由 Evaluator 选择最佳结果:

python
best_output = evaluator( outputs)

student.train( best_output)

因此用 verl 实现 OPD,本质修改点只有三个部分:第一,在 Rollout 阶段增加 Student 当前 Policy 的在线采样;第二,在 Worker 层增加 Teacher Model 或 Multi-Teacher Model 提供蒸馏目标;第三,在 Trainer 层把 PPO 的 Reward/Advantage Loss 替换成 KL Distillation Loss 或 Preference Loss。一个工业级 OPD 系统通常会组合:

python
Student Rollout
        +
Teacher Sampling
        +
Verifier Filtering
        +
Hard Example Mining
        +
KL Distillation
        +
RL Optimization

最终形成一个持续提升的数据闭环:

python
while True:
    data = student.rollout()
    hard_data = filter( data  )
    teacher_data = teacher.label(  hard_data )
    loss = KL( student, teacher_data )
    student.update( loss )

所以 verl 做 OPD 并不是把它当成一个新的训练算法,而是把 OPD 看成 RL Pipeline 中的一种 Policy Improvement 方法,利用 verl 已经成熟的分布式采样、GPU 并行、Checkpoint 管理和训练调度能力,只替换优化目标,让 Student 在自己的数据分布上不断吸收更强 Teacher 的推理能力。


KL散度系数在Agent RL中起到的作用

在 Agent RL 训练中,KL 散度系数(KL Coefficient)主要用于控制策略模型(Policy Model)相对于参考模型(Reference Model)的偏移程度,它是解决强化学习训练中“模型为了获得更高奖励而过度改变自身行为”的核心约束机制。在大语言模型 Agent 强化学习中,模型优化目标并不是单纯最大化 reward,而是在提升任务完成能力的同时,保持模型原有语言能力、通用能力和安全边界。因此实际优化目标是最大化 Reward → 同时约束 Policy 不要偏离 Reference Model 太远。

数学形式通常表示为:

J(θ)=Ex,y∼πθ[R(x,y)]−βKL(πθ∣∣πref)J(\theta)=E_{x,y\sim\pi_\theta}[R(x,y)]-\beta KL(\pi_\theta||\pi_{ref})

其中:

$(\pi_\theta) 表示当前正在训练的 Agent Policy; (\pi_{ref}) 表示冻结的参考模型(Reference Model); (R(x,y)) 表示任务奖励; (\beta) 就是 KL coefficient,也叫 KL penalty coefficient。$

它控制:“模型愿意为了奖励改变多少。”

β→0\beta \rightarrow 0表示几乎没有 KL 约束:

plain-text
Reward ↑
↓
模型自由探索
↓
Policy 大幅变化

模型可能快速学会新的 Agent 行为

  • 工具调用;

  • 长链路规划;

  • Coding workflow。

但是风险很大。

plain-text
Reward Hacking
↓
模型找到漏洞
↓
语言能力下降
↓
输出分布漂移

例如训练 Coding Agent:

原始模型:

plain-text
分析问题
↓
修改代码
↓
运行测试

强化学习后:

plain-text
直接修改测试文件
↓
让测试通过
↓
获得Reward

虽然 reward 增加,但是 Agent 学到了错误策略。

β→∞\beta \rightarrow \infty表示强 KL 约束:

plain-text
Reference Model
↓
Policy几乎不能变化

优点:

  • 稳定;

  • 不容易崩坏;

  • 保留基础能力。

缺点:

Agent 学不到新的行为。

例如:

训练工具调用能力:

原模型:

python
answer = generate_text()

希望变成:

python
tool_call( search())

但是 KL 太大:模型认为“调用工具和原始行为差异太大。”

于是:

plain-text
不调用工具
继续生成文本

导致 Agent 能力提升失败。因此 Agent RL 中 KL 系数本质是在两个目标之间做平衡:

plain-text
探索新能力
        ↕
保持原模型能力

在具体 Agent RL 中,KL 通常作用在 token level,而不是整个 sequence level。

因为 LLM 是逐 token 生成:

P(y∣x)=∏tP(yt∣x,y<t)P(y|x)=\prod_tP(y_t|x,y_{<t})

因此 KL:

KL(πθ∣∣πref)KL(\pi_\theta||\pi_{ref})

可以展开为:

$\sum_t \pi_\theta(y_t|s_t) \log \frac{ \pi_\theta(y_t|s_t) } { \pi_{ref}(y_t|s_t) }$

也就是说每生成一个 token:

plain-text
当前Policy概率 VS Reference概率
↓
计算差异

例如:

Reference:

python
{
"call_tool":0.8,
"answer_directly":0.2
}

训练后的 Policy:

python
{
"call_tool":0.95,
"answer_directly":0.05
}

KL较小, 说明模型增强了工具调用倾向,但没有完全改变。

如果:

Policy:

python
{
"call_tool":1.0,
"answer_directly":0
}

Reference:

python
{
"call_tool":0.1,
"answer_directly":0.9
}

KL非常大,说明模型行为发生巨大漂移。

在 Agent RL 中,KL 系数设置不能像传统文本 RLHF 一样固定,因为 Agent 任务具有明显的长链路特点。

普通聊天模型:

plain-text
Question → Answer

trajectory 很短。

Agent:

plain-text
Goal
↓
Planning
↓
Tool Selection
↓
Execution
↓
Observation
↓
Correction
↓
Final Result

可能几百步。

因此 KL 会累积:$KL_{total} \sum_{t=1}^{T}KL_t$

如果T 很大,即使单步 KL 很小,最终偏移也可能非常大。

例如:

10 token:

plain-text
KL=0.01

Total:

0.1

1000 token:

plain-text
KL=0.01

Total:

10

所以长链路 Agent RL 通常不会简单使用固定 KL。

实际训练中常见方法之一是 Adaptive KL,也就是动态调整 KL coefficient。核心思想:根据当前 Policy 和 Reference 的距离自动调节 β。

流程:

plain-text
Training Step
→
Calculate KL
→
Compare Target KL
→
Increase / Decrease β
→
Next Update

代码:

python
target_kl = 0.1
if current_kl > target_kl:
    beta *= 1.5

elif current_kl < target_kl:
    beta *= 0.5

如果模型偏离太快:

增加 KL:

plain-text
β ↑

↓

限制更新

如果模型变化太慢:

降低 KL:

plain-text
β ↓

↓

允许探索

PPO(Proximal Policy Optimization)中 KL 也是重要控制因素。

PPO 原始目标:

$L^{PPO} E[ min( r_tA_t, clip(r_t,1-\epsilon,1+\epsilon)A_t ) ]$

其中:

$r_t= \frac{\pi_\theta(a_t|s_t)} {\pi_{old}(a_t|s_t)}$

在 LLM RLHF / Agent RL 中通常加入 KL:

$L= L^{PPO} \beta KL(\pi_\theta||\pi_{ref})$

代码:

python
policy_loss = -( ratio * advantage)

kl_loss = (policy_logprob  - reference_logprob)

total_loss = ( policy_loss + beta * kl_loss)

其中:

plain-text
policy_loss:
学习奖励


kl_loss:
限制模型漂移

GRPO(Group Relative Policy Optimization)中同样存在 KL 控制。

DeepSeek-R1 等推理模型训练路线中,GRPO 使用多个采样结果比较优势:

plain-text
Problem
↓
Generate Multiple Solutions
↓
Reward Ranking
↓
Update Policy

例如:

python
answers = [
    model.solve(problem)
    for _ in range(8)
]

scores = reward_model( answers)


update_policy(answers, scores)

但是如果没有 KL,模型可能为了 reward 快速改变输出分布:

例如数学:

原模型:

plain-text
严谨推理

训练后:

plain-text
固定模板投机

因此仍然需要:−βKL-\beta KL

对于 Agent RL,KL 系数设置还和训练阶段有关。

在早期能力迁移阶段:

例如:

让基础模型学会工具调用:

plain-text
LLM
↓
Tool Agent

通常需要较小 KL:

plain-text
β ↓

因为需要允许模型改变行为模式。

例如:

原模型:

plain-text
回答问题

目标:

plain-text
调用搜索
↓
分析结果
↓
回答

如果 β 太大,模型不会改变。

但是在后期优化阶段:

例如:

已经会工具调用,需要提升稳定性:

plain-text
Tool Agent
↓
Reliable Agent

通常提高 KL:

plain-text
β ↑

避免模型为了 reward 学坏习惯。

在长链路 Agent RL 中,还有一个特殊问题:不同 token 的 KL 权重可能不同。

因为 Agent trajectory 中:

plain-text
Reasoning Token

Tool Call Token

Final Answer Token

Tool Return Token

重要程度不同。

通常:

Tool Return:

python
mask = 0

不参与训练。

Tool Call:

可能提高权重:

python
kl_weight = 2.0

因为工具选择很重要。

Final Answer:

正常:

python
kl_weight = 1.0

例如:

python
token_weights = {
"reasoning":1.0,
"tool_call":2.0,
"tool_return":0,
"answer":1.0
}

最终:

$KL= \sum_t w_t KL_t$

这就是 Agent RL 中更细粒度的 KL 控制。实际工程中 KL coefficient 没有一个固定最佳值,而是根据任务类型调整。如果训练目标是:

通用对话增强:

plain-text
KL 较大

保持模型人格和知识

如果训练:

Coding Agent:

plain-text
KL 中等

允许学习工具流程

如果训练:

复杂推理 Agent:

plain-text
KL 较小

允许探索新的推理轨迹

如果训练:

安全对齐:

plain-text
KL 较大

防止策略偏移

因此 Agent RL 中 KL 散度系数的本质不是一个简单超参数,而是控制 Agent 学习速度和稳定性的“缰绳”。它决定模型是在保持原始能力的基础上逐渐适应 Agent 工作流,还是允许模型大幅重塑自己的行为策略。一个成熟的长链路 Agent RL 系统通常采用 adaptive KL、token-level KL weighting、trajectory-level monitoring 等方式动态控制,而不是简单设置一个固定 β。最终目标是达到:

plain-text
Reward提升

+

Agent能力增强

+

模型分布稳定

+

避免Reward Hacking

这也是目前 OpenAI o系列、DeepSeek-R1 类推理模型以及各种 Coding Agent 训练中强化学习稳定性的核心问题之一。


AgenticRL中采样微调RFT有什么用

Agentic RL 中的 **RFT(Rejection Fine-Tuning,也常称为采样微调)**本质上是一种介于监督微调(SFT)与强化学习(RL)之间的后训练方法,它的核心思想不是直接利用 Policy Gradient 根据 Reward 更新模型参数,而是首先利用当前 Policy 在真实 Agent 环境中大量采样生成完整的 Trajectory,然后利用规则评估器(Rule-based Evaluator)、Reward Model、LLM Judge 或真实环境反馈对这些轨迹进行自动评分,只保留其中质量最高、能够成功完成任务的轨迹,再将这些轨迹重新转换为监督学习数据,用普通 Cross Entropy Loss 对模型再次训练,因此整个优化目标依然是最大化高质量轨迹的条件概率,而不是直接最大化 Reward。对于普通聊天模型来说,一条数据通常只有 Instruction 和 Response 两部分,例如用户输入一个问题,模型生成一个回答即可完成训练,而 Agent 模型真正学习的对象已经变成了一整条决策轨迹,包括 Planner、Tool Selection、Tool Call、Observation、Reflection、Retry、Memory Update 以及 Final Answer,因此 RFT 所学习的不再是最终答案,而是整个 Agent 的行为过程,这也是它与传统 SFT 最大的区别。对于一个修复代码 Bug 的任务来说,同一个 Prompt 往往可以产生几十甚至上百条不同的执行轨迹,例如有的轨迹首先阅读 Issue,再搜索代码,随后定位 Bug、修改代码并通过测试;有的轨迹会直接修改错误文件导致测试失败;还有的轨迹虽然第一次失败,但经过 Reflection 后重新定位问题最终完成修复,因此这些轨迹对应的最终 Reward 完全不同,而 RFT 的目标就是自动保留那些真正成功完成任务的轨迹作为新的监督数据。

markdown
NUM_SAMPLES = 64

trajectories = []

for _ in range(NUM_SAMPLES):
    trajectory = agent.rollout(prompt)
    trajectories.append(trajectory)

随后系统会对每一条 Trajectory 进行自动评估,评估方式可以来自多个来源。如果任务具有确定答案,例如数学、代码、单元测试等,则通常直接利用规则进行评分;如果任务属于开放式问答,则可以采用 LLM-as-a-Judge;如果 Agent 工作在 Browser、Terminal 或 Robotics 环境,则直接利用真实环境返回最终 Reward,因此 Evaluator 并不是固定组件,而是整个 Agent RL Pipeline 中负责提供自动监督信号的重要模块。例如代码任务可以直接统计测试通过率作为 Reward。

python
def evaluator(trajectory):
    passed = run_tests(  trajectory.patch )
    return float(passed)

或者采用更加复杂的评分函数。

python
reward = (
    passed_tests * 100
    - failed_tests * 20
    - compile_error * 50
)

如果采用 LLM Judge,则评分过程通常表示为:

python
score = judge_model.generate(
    trajectory
)

所有轨迹评分结束以后,并不会像 PPO、GRPO 一样立即计算 Advantage,而是直接进行筛选,只保留高质量样本,例如只保留 Reward 大于某一阈值或者 Top-K 的轨迹。

python
selected = []

for traj in trajectories:

    score = evaluator(traj)

    if score >= 90:
        selected.append(traj)

或者:

python
selected = sorted(
    trajectories,
    key=lambda x: x.reward,
    reverse=True
)[:8]

随后这些高质量 Trajectory 会重新组织成标准 SFT 数据集,每一个 Token 都重新作为监督学习标签,因此整个训练重新退化成普通 Language Modeling,而不需要任何强化学习算法。

python
dataset = []
for traj in selected:
    dataset.append({ "messages": traj.messages })

然后直接进行 Cross Entropy Training。

python
loss = F.cross_entropy( logits, labels)
loss.backward()
optimizer.step()

因此 RFT 与 PPO 最大的区别就在于梯度来源不同,PPO 更新参数依赖 Reward、Advantage、KL Constraint 等强化学习目标,而 RFT 完全没有 Advantage,也没有 Value Function,更不需要计算 GAE,它始终优化的是普通语言模型训练目标。

python
loss = cross_entropy(
    model_output,
    ground_truth
)

由于 Agent Trajectory 包含大量 Tool Return,因此现代 Agent RFT 通常都会采用 Tool Return Mask,只监督模型自己生成的 Token,而不会监督环境返回的数据,否则模型容易学习复制工具输出,而不是学习何时调用工具。例如一次完整轨迹可能包含 Assistant、Tool Call、Tool Return、Reflection 四种 Token,其中只有 Assistant 和 Tool Call 参与 Loss。

python
loss_mask = [
    1,  # planner
    1,  # tool call
    0,  # tool return
    1,  # reflection
    1   # answer



loss = ( token_loss * loss_mask).sum()

这是因为 Tool Return 属于 Environment Observation,而不是模型 Policy,因此监督 Observation 没有意义,真正需要学习的是模型如何利用 Observation 做出下一步决策。随着训练不断进行,模型能力逐渐提升,采样出来的轨迹质量也越来越高,因此 RFT 往往不是执行一次,而是不断循环进行,每一次都利用最新 Policy 重新采样,再重新筛选,再重新监督学习,使模型持续自举(Self Bootstrapping)。

python
for iteration in range(20):
    trajectories = sample( current_policy )
    selected = filter( trajectories  )
    dataset = build_dataset( selected  )
    trainer.train( dataset )

整个过程实际上构成一个不断闭环的数据飞轮,模型越强,采样质量越高;采样质量越高,新的训练数据越好;新的训练数据越好,下一轮模型能力进一步提升,因此无需人工重新标注即可不断提高 Agent 能力。现代 Agentic RL 通常都会采用"Pretrain → SFT → RFT → RL → Online RL"这样的训练路线,其中 RFT 扮演着连接监督学习与强化学习的重要角色,因为如果模型一开始直接进入 RL,大部分 Rollout 都无法完成任务,Reward 接近于零,Policy Gradient 几乎没有有效学习信号,而经过 RFT 后,模型已经能够学会基本 Planning、Tool Selection、Reflection 和 Retry,此时 RL 才能够真正优化长期决策,因此 RFT 实际上承担了"Warm Start Policy"的作用,使初始策略进入一个能够稳定获得 Reward 的区域,然后再利用 PPO、GRPO、SAO 等强化学习算法继续优化长期策略。对于长链路 Agent 而言,RFT 还有一个极其重要的优势,就是完全绕过了 Credit Assignment 问题,因为强化学习最大的困难在于最终成功以后,很难判断 Reward 应该分配给 Planner、Tool Selection、Reflection 还是 Retry,而 RFT 不需要进行任何 Reward Backpropagation,只要整条轨迹最终成功,就直接把整条轨迹作为正确答案监督模型学习,因此无需计算 Value Function、Advantage 或 Bellman Target,也不存在高方差估计问题,因此训练稳定性远高于直接 RL,同时又能够充分利用自动采样生成的大规模 Agent Trajectory 数据,因此目前越来越多的大模型 Agent 后训练都会首先利用大规模 Sampling + RFT 获得高质量初始策略,再利用在线 RL 进一步优化长期推理能力和工具调用策略,从整个训练体系来看,RFT 本质上就是一种利用自动采样替代人工标注、利用自动筛选替代人工审核、利用监督学习替代强化学习梯度的 Agent 数据闭环优化方法,也是现代 Agentic RL 体系中最重要的数据构建技术之一。


大模型后训练Multi-Teacher On-Policy Distillation(MOPD,多教师在线策略蒸馏)

大模型后训练中的 Multi-Teacher On-Policy Distillation(多教师在线蒸馏)本质上是一种结合知识蒸馏(Knowledge Distillation)、在线采样(On-Policy Sampling)以及强化学习(Reinforcement Learning)思想的后训练方法,它的目标不是简单让 Student 模型模仿某一个 Teacher,而是让 Student 在自己的当前策略(Current Policy)下生成轨迹,再利用多个能力互补的 Teacher 对这些轨迹进行指导、评分、修正甚至重新生成,从而持续提升 Student 的推理能力、Agent 能力以及长链路决策能力。传统 Knowledge Distillation 通常属于 Offline Distillation,训练数据在开始训练之前已经固定,例如直接利用 GPT-4、Claude 或其他大模型离线生成一个数据集,然后 Student 不断拟合这些固定数据,因此整个训练过程中 Teacher 不再参与推理,Student 学到的实际上只是 Teacher 在固定数据分布上的知识,而 Student 一旦偏离这些分布,就容易出现 Error Accumulation(误差累积)或者 Distribution Shift(分布漂移)的问题,因为 Student 在训练阶段始终看到的是 Teacher 的输出,而真正部署以后看到的是自己的输出,两者状态分布并不一致,因此传统蒸馏虽然能够提升知识迁移效率,但对于 Agent、长链路推理以及 Tool Use 等复杂任务效果有限。

python
teacher_dataset = []
for sample in prompts:
    answer = teacher.generate(sample)
    teacher_dataset.append({
        "prompt": sample,
        "answer": answer
    })
trainer.train(teacher_dataset)

On-Policy Distillation 则完全不同,它始终让 Student 使用自己的当前 Policy 生成完整 Trajectory,因此训练数据不是提前准备好的,而是在训练过程中不断重新生成,每完成一次参数更新,下一轮采样的数据分布都会发生变化,因此 Teacher 始终面对 Student 当前真正会犯的错误,而不是离线数据中的错误。例如 Student 首先生成一条 Agent Trajectory。

plain-text
trajectory = student.rollout( prompt)

随后 Teacher 并不会直接给出标准答案,而是根据 Student 当前轨迹进行分析,例如指出哪一步 Planning 出错、哪个 Tool Selection 不合理、哪一步 Reflection 不充分,甚至重新生成更优轨迹。

plain-text
feedback = teacher.review( trajectory)

随后利用 Teacher 的反馈重新构建监督目标。

plain-text
target = merge( trajectory, feedback)

然后继续采用监督学习或者蒸馏 Loss 更新 Student。

python
loss = distillation_loss(  student_output, target)

由于 Student 每一次采样都会改变,因此 Teacher 看到的数据始终来自最新 Policy,这也是 On-Policy 的含义,即 Teacher 永远工作在 Student 当前策略分布上,而不是固定数据分布上,因此能够不断修正 Student 自己真正容易出现的问题。

Multi-Teacher 的引入进一步解决了单一 Teacher 的能力边界问题,因为没有任何一个 Teacher 能够同时在数学、代码、Agent、长上下文、多模态以及工具调用等所有方向都保持最优,因此现代后训练通常会采用多个 Teacher 共同参与。例如一个训练系统中可能同时存在代码 Teacher、数学 Teacher、推理 Teacher、Agent Teacher、语言 Teacher 等多个模型,每个 Teacher 负责自己擅长的领域。

python
teachers = {
    "math": math_teacher,
    "code": code_teacher,
    "agent": agent_teacher,
    "writing": writing_teacher
}

训练时首先判断当前任务类型。

plain-text
task_type = router(prompt)

随后选择对应 Teacher。

plain-text
teacher = teachers[ task_type]

或者多个 Teacher 同时参与。

python
responses = []
for teacher in teachers.values():
    responses.append(
        teacher.generate(prompt)
    )

随后利用 Aggregation 或 Voting 得到最终监督目标。

plain-text
target = aggregate( responses)

Aggregation 并不是简单多数投票,而是通常结合 Rule-based Evaluator、Reward Model、LLM Judge 甚至真实环境反馈共同决定哪一个 Teacher 的回答最优。例如对于代码任务,可以直接运行测试。

python
for answer in responses:
    score = run_tests(answer)
    scores.append(score)
best = responses[
    np.argmax(scores)
]

对于数学题则直接验证最终答案是否正确。

plain-text
score = verifier(  answer)

对于 Agent 则直接执行整个 Trajectory。

plain-text
reward = environment.run( trajectory)

因此真正进入 Student 的监督目标往往已经不是任何一个 Teacher 原始输出,而是经过 Environment Verification 之后得到的最佳结果。现代 Agentic RL 中更进一步的发展是 Trajectory Distillation,即蒸馏对象已经不是最终 Answer,而是完整 Agent Execution。Teacher 会直接输出完整 Planning、Tool Selection、Reflection、Memory Update 等全过程。

plain-text
teacher_traj = teacher.rollout( prompt)

Student 同样生成自己的轨迹。

plain-text
student_traj = student.rollout( prompt)

随后计算整个 Trajectory Loss。

plain-text
loss = trajectory_loss(
    student_traj,
    teacher_traj
)

如果采用 Token-level Distillation,则通常采用 KL Loss。

python
teacher_prob = F.softmax(
    teacher_logits,
    dim=-1
)

student_logprob = F.log_softmax(
    student_logits,
    dim=-1
)

loss = F.kl_div(
    student_logprob,
    teacher_prob,
    reduction="batchmean"
)

也可以与普通 Cross Entropy 同时优化。

plain-text
loss = (ce_loss+alpha * kl_loss)

其中 alpha 控制蒸馏信号权重。On-Policy Distillation 与 RL 结合以后,Teacher 还可以直接参与 Reward Shaping。例如 Student 完成一次 Rollout。

plain-text
trajectory = student.rollout(prompt)

随后多个 Teacher 分别进行评分。

python
scores = []
for teacher in teachers:
    score = teacher.score( trajectory )
    scores.append(score)

最终 Reward 可以表示为多个 Teacher 的加权融合。

python
reward = (
    0.4 * code_score +
    0.3 * reasoning_score +
    0.3 * safety_score
)

或者采用动态权重。

python
weights = router.compute_weight( prompt)
reward = sum(
    w * s
    for w, s in zip(
        weights,
        scores
    )
)

随后 Student 再利用 PPO、GRPO 或 SAO 更新参数。

python
loss = rl_loss(
    reward,
    logprob
)
loss.backward()

因此 Teacher 不仅能够生成 Label,也能够参与 Reward Construction。现代大模型后训练越来越强调 Self-Evolving Distillation,即随着 Student 能力不断提升,Teacher 也不再始终保持固定。例如只有当 Student 无法解决当前问题时才调用 Teacher。

python
answer = student.generate( prompt)
if verifier(answer):
    dataset.append(answer)
else:
    answer = teacher.generate(  prompt )
    dataset.append(answer)

这种方式能够显著减少 Teacher 推理成本,同时避免 Student 永远依赖 Teacher。在更大的 Agent 系统中,多个 Teacher 往往分别负责不同能力,例如代码 Teacher 负责 Program Repair,数学 Teacher 负责 Symbolic Reasoning,Agent Teacher 负责 Planning,搜索 Teacher 负责 Tool Use,最终 Student 将这些不同能力统一蒸馏到同一个模型中,因此 Student 学到的不再是单一 Teacher 的行为,而是整个 Teacher Ensemble 的综合能力。从整个训练流程来看,Multi-Teacher On-Policy Distillation 可以概括为 Student 根据当前 Policy 在线采样真实 Trajectory,然后多个 Teacher 针对当前轨迹分别生成修正、评分或替代方案,再利用 Rule-based Verifier、Reward Model 或真实环境自动筛选高质量监督信号,最终采用 Cross Entropy、KL Distillation、Trajectory Distillation 或与 RL 联合优化持续更新 Student,使 Student 每一轮训练都工作在自己最新策略的数据分布上,并不断吸收多个 Teacher 的优势能力,从而避免传统 Offline Distillation 的分布漂移问题,同时显著提升 Agent 推理、工具调用、长链路规划以及复杂任务执行能力,因此目前越来越多的大模型后训练系统开始采用 Multi-Teacher On-Policy Distillation 作为连接 SFT 与 Agentic RL 的重要训练阶段。


为什么OPD需要短rollout/截断rollout?POPD和TOPD是什么?

OPD(On-Policy Distillation)训练中采用 Short Rollout(短轨迹采样)或者 Truncated Rollout(截断轨迹采样)的核心原因,是因为 Agent 的长链路行为虽然看起来包含更多信息,但从模型优化角度来看,大量完整长轨迹实际上包含大量低价值甚至有害的训练信号。OPD 的目标不是让 Student 简单复制 Teacher 完整执行过程,而是在 Student 当前 Policy 分布下找到最值得学习的行为差异,因此训练重点是 Student 当前能力不足的位置,而不是整个任务执行过程。对于传统语言模型蒸馏来说,一条完整回答通常就是几十到几百个 Token,直接蒸馏问题不大,但是 Agent 系统中的一条 Trajectory 往往包含 Planning、Tool Selection、Tool Call、Observation、Reflection、Retry 和 Final Answer 等多个阶段,一个复杂任务可能产生几千甚至上万个 Token。如果直接使用完整 Rollout 进行 Distillation,训练数据中会混入大量已经掌握的信息、环境返回信息以及错误传播后的无效决策,导致 Teacher 的知识信号被稀释,同时增加训练成本。例如一个代码 Agent 修复 Bug 的任务,完整轨迹可能是:

markdown
trajectory = [
    "read_issue",
    "search_repository",
    "open_file",
    "analyze_function",
    "modify_code",
    "run_test",
    "test_failed",
    "reflection",
    "modify_again",
    "test_pass"
]

如果直接使用完整轨迹训练:

python
loss = cross_entropy(
    student_output,
    teacher_trajectory
)

Student 会同时学习所有步骤,但是其中很多步骤并不是能力提升的关键,例如读取文件、执行测试、工具返回结果等可能已经属于 Student 掌握能力,而真正需要提升的是为什么第一次修改失败之后能够定位原因、为什么选择某个文件、为什么决定重新规划。因此完整 Long Rollout 会产生严重的数据效率问题,同样数量的训练 Token 中,真正具有梯度价值的数据比例非常低。Short Rollout 的思想就是把长任务拆解成多个局部决策窗口,只保留 Student 当前容易出错或者 Teacher 能明显改善的片段,例如:

python
segment = [
    "analyze_error",
    "wrong_decision",
    "teacher_correction"
]

训练目标从学习整个任务:

python
P( entire_trajectory)

转变为学习关键决策:

python
P( better_action | current_state)

这样每一个训练样本都更加接近模型能力边界,可以产生更大的梯度更新。另一个重要原因是长链路 Agent 存在 Error Propagation(错误传播)问题,在一个几十步甚至几百步的任务中,前面一个错误决策可能导致后续全部失败,例如 Agent 第一步选择了错误的数据源,后面即使推理正确,也无法得到正确结果。如果直接把失败长轨迹用于蒸馏:

python
student.learn(failed_trajectory)

模型可能学习到错误的 Action Sequence,因此 OPD 通常会截断到错误发生前后的局部区域,通过 Teacher 提供正确方向:

python
student.learn( state_before_error, teacher_action)

这实际上降低了 Agent RL 中非常困难的 Credit Assignment 问题,因为模型不需要判断整个任务失败到底是哪一步造成的,而只需要学习局部决策优化。Short Rollout 还有一个重要原因是 Tool Use 场景中的 Token 浪费问题,Agent 执行任务时大量 Token 并不是模型产生,而是工具返回,例如搜索结果、网页内容、数据库结果、代码运行日志等,这些内容可能占据整个 Context 的 80% 以上,但是它们并不是 Student 需要学习的行为。如果直接蒸馏完整轨迹:

json
{
    "assistant":"call browser",
    "tool_return":"10000 tokens webpage content",
    "assistant":"analyze result"
}

模型可能花费大量训练资源学习 Tool Return,而不是学习如何调用工具。因此 OPD 通常会进行 Tool Return Mask:

python
loss_mask = [
    1,   # assistant planning
    1,   # tool call
    0,   # tool return
    1,   # reflection
    1    # final answer
]

也就是说训练只优化模型自身产生的决策 Token,而环境产生的信息只作为 Context,不参与 Loss。除了 Short Rollout 之外,OPD 中还发展出了 POPD(Preference-based On-Policy Distillation)和 TOPD(Trajectory-level On-Policy Distillation)两种重要方法。POPD 的核心思想是将 On-Policy Sampling 与 Preference Learning 结合,不要求 Teacher 直接生成唯一正确答案,而是让 Student 根据自己的当前 Policy 生成多个候选轨迹,再由 Teacher 或 Reward Model 对这些轨迹进行排序,形成偏好数据。例如:

python
rollouts = [
    student.generate(prompt),
    student.generate(prompt),
    student.generate(prompt)
]

经过评估得到:

plain-text
Trajectory A reward = 90
Trajectory B reward = 40
Trajectory C reward = 70

然后构造 Preference Pair:

json
{
    "chosen":"Trajectory A",
    "rejected":"Trajectory B"
}

训练目标类似 DPO:

python
loss = -log( sigmoid(  beta *  ( logp(chosen)  - logp(rejected)   )  ))

POPD 学习的不是绝对答案,而是行为偏好,因此特别适合开放式 Agent 任务,因为很多任务不存在唯一正确路径。例如自动写代码、系统设计、任务规划等问题可能存在多个可行方案,此时相比告诉模型“这个答案正确”,告诉模型“A 方案比 B 方案更优”更加符合真实环境。POPD 的核心价值在于利用 Student 自己生成的数据分布进行优化,因此模型学习的是如何避免当前 Policy 中最常见的错误,而不是学习一个固定 Teacher 数据集。

TOPD(Trajectory-level On-Policy Distillation)则更加关注完整 Agent 行为过程,它认为 Agent 的核心能力不是最终输出,而是中间决策链路,因此蒸馏对象从 Answer 扩展到完整 Trajectory。例如普通 SFT:

python
input -> answer

而 TOPD:

python
input -> planning -> tool_choice -> observation -> reflection -> action -> answer

Teacher 输出完整轨迹:

python
teacher_trajectory = {
    "plan":"需要查询数据库",
    "tool":"SQL",
    "observation":"missing data",
    "reflection":"需要重新过滤",
    "action":"execute_second_query",
    "answer":"final result"
}

Student 学习:

python
loss = trajectory_distillation_loss(
    student_trajectory,
    teacher_trajectory
)

TOPD 的优势在于可以直接训练 Agent 的过程能力,例如什么时候应该调用工具、什么时候应该停止搜索、什么时候应该反思、什么时候应该重新规划,这些能力如果只监督最终 Answer 是无法学习的。因此三者实际上解决的是不同问题:Short Rollout 解决的是训练效率和噪声问题,它寻找最有价值的局部决策;POPD 解决的是多路径任务中的行为选择问题,它学习哪个 Trajectory 更优;TOPD 解决的是 Agent 过程能力学习问题,它直接蒸馏完整决策链路。在现代 Agent 后训练系统中,这些方法通常会组合使用,例如首先让 Student 在线生成大量 Rollout,然后通过截断算法找到高价值 Segment,再通过 Multi-Teacher 进行 Preference Ranking 和 Trajectory Evaluation,最后利用 POPD、TOPD 或 RL 方法更新模型:

python
while True:
    trajectories = student.rollout(prompts)
    segments = mine_hard_segments( trajectories)
    preferences = teacher.rank( segments)
    trainer.update(  preferences )

OPD 的核心并不是简单增加 Rollout 长度,而是提高每一个训练 Token 的信息密度。一个经过筛选的 200 Token 高难度 Reflection 片段,可能比一个 10000 Token 的完整成功轨迹更有价值,因为前者直接改变 Student 当前 Policy,而后者可能只是重复模型已经掌握的能力。现代 Agent RL 系统越来越倾向于使用 Adaptive Short Rollout、Trajectory Truncation、Hard Segment Mining 等策略,本质上都是为了让 Teacher 的监督信号集中作用在 Student 最需要提升的位置,从而降低训练成本,提高长链路 Agent 的推理、规划和工具调用能力。


什么样的数据值得做OPD(Online/On-Policy Distillation)

OpenAI、DeepSeek、GLM、Qwen、Kimi 等近两年的后训练路线都逐渐说明了一件事情:并不是所有数据都值得进入 On-Policy Distillation(OPD,Online/On-Policy Distillation)训练,真正有价值的数据并不是"模型答对了"的数据,而是能够改变 Student 当前策略(Current Policy)的数据。如果 Student 已经能够稳定解决某一类任务,那么继续蒸馏这些样本几乎不会带来新的梯度;相反,那些位于 Student 能力边界附近、存在多种推理路径、需要长链路规划、工具调用、反思修正或者 Teacher 能明显优于 Student 的样本,才是真正具有 Distillation Value 的数据。因此 OPD 的数据选择实际上是一个 Information Gain(信息增益)问题,而不是 Data Quantity(数据数量)问题。对于一个已经拥有 90% 数学正确率的模型来说,继续蒸馏大量"2+3=5"这样的简单样本几乎不会改变模型参数,因为 Cross Entropy 已经非常接近于零;真正应该投入计算资源的是那些 Student 会犯错而 Teacher 能够解决的问题,例如复杂代码修复、长链路 Agent、跨工具推理、多跳检索以及需要 Reflection 的任务,因为这些数据才能产生较大的梯度更新,从而推动模型能力继续提升,因此现代 OPD 往往都会先利用 Student 当前 Policy 进行在线采样,再利用 Teacher 或 Evaluator 判断哪些样本真正值得进入下一轮训练,而不是直接把所有 Rollout 全部加入 Dataset。

python
student_answer = student.generate(prompt)
teacher_answer = teacher.generate(prompt)
student_score = evaluator(student_answer)
teacher_score = evaluator(teacher_answer)
if teacher_score > student_score:
    dataset.append({
        "prompt": prompt,
        "answer": teacher_answer
    })

最值得进入 OPD 的第一类数据是 Student 与 Teacher 存在明显能力差距的数据,也就是 Teacher Improvement 较大的样本,因为蒸馏本质上就是知识迁移,如果 Student 与 Teacher 输出完全一致,那么 KL Loss 或 Cross Entropy 几乎没有训练信号,而当 Teacher 展现出更好的 Planning、更合理的 Tool Selection、更完整的 Reflection 或更稳定的推理过程时,这部分差异就会转化为真正的优化方向,因此很多 OPD 系统都会首先计算 Teacher Gain,只保留 Teacher 能明显提升结果的样本。

python
gain = teacher_score - student_score

if gain >= threshold:
    selected.append(sample)

第二类高价值数据是 Student 自己失败而 Teacher 成功的数据,因为这类数据直接对应当前 Policy 的能力边界,也是 On-Policy Distillation 与 Offline Distillation 最大的区别。Offline Distillation 使用的是固定数据集,而 OPD 始终围绕 Student 当前最容易犯错的区域不断更新训练数据,因此 Student 能力越强,采样的数据也会越来越困难,整个数据分布会随着模型不断演化,而不是永远停留在旧数据集。

python
trajectory = student.rollout(prompt)
reward = evaluator(trajectory)
if reward < success_threshold:
    teacher_traj = teacher.rollout(prompt)
    if evaluator(teacher_traj) > reward:
        dataset.append(teacher_traj)

第三类最有价值的数据是存在长链路推理(Long Horizon Reasoning)的数据,因为简单问答往往只有几十个 Token,而真正决定 Agent 能力的是数百甚至数千 Token 的 Decision Trajectory,这些轨迹包含 Planner、Tool Call、Observation、Reflection、Retry、Memory Update 等多个阶段,每一步都会影响后续决策,因此这类数据的信息密度远高于普通 QA。例如一次 Agent Rollout 中真正有价值的并不是最终答案,而是模型如何决定调用搜索、什么时候修改代码、什么时候重新规划任务,因此现代 OPD 更愿意蒸馏完整 Trajectory,而不是只蒸馏 Final Answer。

python
trajectory = {
    "planner": planner,
    "tool_calls": tool_calls,
    "observations": observations,
    "reflection": reflection,
    "answer": answer
}
dataset.append(trajectory)

第四类值得保留的是存在多种合理推理路径的数据,因为真实世界的问题往往没有唯一解,例如同一个编程问题可能存在多种实现方式,同一个规划任务也可能存在不同执行策略。如果 Student 只能学习固定答案,很容易过拟合某一种推理模式,而多个 Teacher 可以分别给出不同推理路径,再通过 Evaluator 自动筛选优秀 Trajectory,这样 Student 学到的是推理空间,而不是固定模板。

python
teacher_outputs = []
for teacher in teachers:
    teacher_outputs.append(
        teacher.rollout(prompt)
    )
best = evaluator.select_best( teacher_outputs)
dataset.append(best)

第五类高价值数据是高不确定性样本(High Uncertainty Samples),即 Student 自己最没有把握的问题,因为模型输出概率分布已经隐含了自己的"信心",如果 Top-1 与 Top-2 Token 概率十分接近,或者不同采样结果差异很大,通常说明模型尚未学会这一类知识,此时蒸馏收益最高,而那些模型已经极其确定的问题即使继续训练,收益也十分有限。

python
prob = model.predict(prompt)
entropy = -torch.sum( prob * torch.log(prob))
if entropy > entropy_threshold:
    dataset.append(prompt)

第六类数据是需要真实工具调用的数据,因为 Agent 的能力很大程度来源于 Tool Use,而不是语言生成本身。如果一条 Trajectory 中包含搜索、浏览器、Python、SQL、代码执行等真实环境交互,那么它比纯文本 QA 更具有训练价值,因为模型真正需要学习的是什么时候调用工具、如何解析 Observation、什么时候结束调用,而不是工具返回了什么,因此很多 OPD 数据都会 Mask 掉 Tool Return,只保留模型自己的决策过程。

python
loss_mask = [
    1,  # planner
    1,  # tool call
    0,  # tool return
    1,  # reflection
    1   # answer
]

第七类数据是经过 Environment Verification 验证的数据,而不是 Teacher 自认为正确的数据,因为 Teacher 自身也会犯错,因此越来越多的 OPD 系统都会增加 Verifier,例如代码执行器、数学验证器、浏览器环境或单元测试,让 Environment 成为最终裁判,而不是完全相信 Teacher。

python
teacher_answer = teacher.generate(prompt)
verified = verifier( teacher_answer)
if verified:
    dataset.append(  teacher_answer )

第八类值得蒸馏的是能够改变 Student 行为模式的数据,而不是简单增加知识的数据。例如 Student 已经知道 Python 语法,但不会在合适的时候调用 Python Tool,那么真正需要蒸馏的是 Tool Selection,而不是 Python 语法知识。同样,对于已经掌握数学公式的模型,更重要的是学习何时进行 Reflection、何时拆分子问题、何时重新规划,而不是继续学习相同公式,因此现代 OPD 越来越关注 Behavior Distillation,而不仅仅是 Output Distillation。

python
target = {
    "planner": planner,
    "tool_choice": tool_choice,
    "reflection": reflection,
    "retry": retry,
    "answer": answer
}

从整个后训练流程来看,真正值得进入 OPD 的数据通常都满足几个共同特点:Student 当前策略容易失败、Teacher 能显著提升结果、任务具有较长决策链路、存在真实工具调用或环境交互、经过 Verifier 自动验证、能够改变 Student 的行为策略而不是重复已有知识,并且这些数据始终来自 Student 当前 Policy 的在线采样,而不是固定离线数据集,因此 OPD 的核心目标不是不断扩大 Dataset,而是不断提高 Dataset 的"学习价值(Learning Value)"。现代大模型后训练越来越倾向于把 OPD 看作一种持续的数据飞轮,即 Student 在线采样发现自己的能力边界,Teacher 和 Evaluator 自动筛选最具信息增益的数据,Verifier 保证数据正确性,再利用这些高价值 Trajectory 继续训练 Student,新的 Student 又会产生新的困难样本,如此循环形成持续演化的数据闭环,这也是为什么 OPD 往往能够用远少于传统 SFT 的数据量,获得更明显的 Agent 能力提升。


大模型工具调用能力如何训练

大模型的工具调用能力,本质上是模型在训练过程中逐步学会了一种“从语言空间映射到动作空间”的能力:也就是在合适的上下文中,把自然语言问题转化为结构化指令(function call / tool call),再通过外部执行系统获得反馈,并在后续生成中利用这些反馈完成任务。这个能力通常不是单一阶段训练出来的,而是由多个训练阶段共同叠加形成的。

第一类是最基础的监督微调(SFT)工具调用数据,这一阶段的核心是让模型“见过正确的工具使用格式”。训练数据通常是人工构造或者半自动生成的三元组对话结构,例如用户提出问题,模型输出一个符合 schema 的 tool call,而不是自然语言回答。例如一个搜索场景的数据可能是这样的结构:

python
{
  "messages": [
    {
      "role": "user",
      "content": "帮我查一下上海今天的天气"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "name": "weather_api",
          "arguments": "{\"city\":\"上海\"}"
        }
      ]
    }
  ]
}

在这个阶段,模型的学习目标非常明确:不是解决问题本身,而是学习“什么时候应该调用工具 + 调用工具的格式如何写对”。这些数据的来源通常分为三种:第一种是人类标注,由标注人员设计任务并写出标准 tool call;第二种是程序生成,例如把自然语言问题映射为 SQL、API 或 DSL,然后自动构造训练样本;第三种是蒸馏数据,即用 GPT-4、Claude 这类强模型在真实任务上生成 tool-use 轨迹,再用于训练较小模型。尤其是第三种在工业界非常重要,因为它可以低成本扩展大量高质量工具调用样本。

第二类是工具轨迹学习(tool-use trajectory learning),这一类数据比单步 tool calling 更复杂,它强调的是“多步决策过程”,也就是 agent 如何在一个任务中连续使用多个工具。比如一个用户让模型“分析某公司财报并给出结论”,模型不再只是调用一次工具,而是会形成一个链式过程:先搜索财报,再下载 PDF,然后解析表格,再做数值计算,最后生成总结。这种数据通常长这样:

plain-text
[
  {"action": "search", "input": "某公司 2024 财报"},
  {"action": "open_url", "input": "pdf_link"},
  {"action": "extract_table", "input": "income statement"},
  {"action": "calculate", "input": "revenue growth"},
  {"action": "final_answer", "input": "summary"}
]

这种训练方式本质上是在教模型“规划能力”,也就是把一个复杂任务拆解为多个可执行子步骤,并在每一步选择合适工具。数据来源通常是通过“环境模拟”产生的,比如在浏览器环境、代码执行环境或数据库环境中,让强模型或人工 agent 完成任务并记录轨迹,再用于训练。此外,也有大量研究用强化学习来生成这种轨迹,使模型在试错中学习更优策略。

第三类是强化学习(RLHF / RLAIF)对工具调用的优化,这一阶段解决的问题是:即使模型会调用工具,也不代表它调用得对。比如可能出现工具选择错误、参数错误、调用顺序不合理等问题,因此需要通过奖励机制来优化整体行为。强化学习的奖励函数不再只是“答案是否正确”,而是包括多个维度,例如工具选择是否合理、调用是否成功、执行结果是否正确、最终答案是否符合人类偏好等。训练流程通常是这样的:模型生成一个工具调用序列,然后在环境中执行,得到结果,再根据结果计算 reward:

plain-text
reward = α * answer_correctness
       + β * tool_validity
       + γ * execution_success
       + δ * efficiency_penalty

然后通过 PPO 或类似算法更新模型,使其更倾向于产生高 reward 的 tool-use 行为。这一阶段的关键点在于“执行反馈”,也就是模型不是在静态数据上学习,而是在一个可交互环境中学习行为策略,因此它更接近强化学习 agent,而不是传统 NLP 模型。

第四类是结构约束与格式对齐训练,这一类通常不会单独作为一个训练阶段,而是贯穿整个训练流程。因为工具调用本质上是结构化输出,如果模型生成的 JSON 不合法,就无法被执行,因此必须在训练或解码层面强约束。常见做法包括在训练时只对 tool-call 部分计算 loss,例如:

python
loss = CrossEntropy(model_output_tool_json, ground_truth_tool_json)

也有方法在解码阶段引入 grammar constraint,让模型只能生成符合 JSON schema 的 token 序列,比如通过 OpenAPI schema 或 JSON grammar parser 限制输出空间。还有一种更工程化的方法是把 tool call token 单独设计成特殊 token,例如:

plain-text
<tool_call>
{"name":"search","arguments":{...}}
</tool_call>

这样模型在生成时会被强制进入“结构模式”,避免出现自然语言污染结构输出的问题。

第五类是大规模模拟环境训练(agent sandbox / environment training),这是目前最接近“真正智能体训练”的方式。在这种方法中,模型不再只是读写静态数据,而是在一个可交互环境中运行,例如浏览器环境、代码执行环境、文件系统环境甚至操作系统环境。模型在环境中执行动作,每一步都会获得反馈,然后继续决策。这种训练通常是闭环的:

plain-text
User Task → LLM → Action (tool call) → Environment execution → Observation → LLM → ...

在这个过程中,模型会逐渐学会更复杂的策略,例如如何减少无效搜索、如何缓存中间结果、如何选择成本更低的工具路径等。很多现代 agent 框架(如 OpenAI tool use、OpenHands、AutoGPT 类系统)本质上都在构造这种训练数据流,并通过日志回放或蒸馏方式反向训练模型。


FIPO 如何实现长链路推理

长链路推理(Long-Horizon Reasoning)是 Agent 系统区别于普通大语言模型的重要能力。传统大语言模型主要解决的是一次性生成问题,即用户输入一个问题,模型经过一次前向计算后输出答案:

plain-text
Input → LLM → Output

这种模式适合知识问答、文本生成、代码补全等任务,但是无法很好处理需要持续执行、动态反馈、多步决策的复杂任务。例如用户要求 Agent “分析一个大型代码仓库中的安全漏洞并提交修复方案”,Agent 不可能一步生成最终结果,它需要先理解项目结构,然后搜索相关代码,分析依赖关系,定位漏洞位置,修改代码,执行测试,根据测试结果继续调整,最后生成报告。整个过程可能包含几十个甚至几百个连续动作,因此真正决定 Agent 能力的不是某一步推理能力,而是在长时间任务过程中是否能够保持正确目标、合理规划动作,并且根据环境反馈不断调整策略。

FIPO(Feedback-Integrated Policy Optimization)的核心思想就是针对这种长链路任务进行优化。它认为 Agent 的训练目标不应该只是学习最终答案,而应该学习完整的任务执行过程。传统 SFT(Supervised Fine-Tuning)训练的数据通常是输入和输出的静态映射,例如:

json
{
  "instruction": "解释什么是数据库索引",
  "response": "数据库索引是一种提高查询效率的数据结构"
}

这种数据训练出来的模型擅长生成知识答案,但是无法学习复杂任务中的动态决策过程。因为 Agent 任务不是:

plain-text
问题 → 答案

而是:

plain-text
任务目标 → 当前状态 → 下一步动作 → 环境反馈 → 更新状态 → 下一步动作 → 最终完成任务

因此 FIPO 将训练对象从单步输出扩展到完整轨迹(Trajectory)。在 Agent 中,一个完整任务可以表示为:

τ=(s0,a0,r0,s1,a1,r1,...,sT)\tau=(s_0,a_0,r_0,s_1,a_1,r_1,...,s_T)

其中 s_t 表示时间 t 时刻的状态,例如用户需求、历史对话、当前文件内容、数据库状态、已经执行过的工具调用;a_t 表示 Agent 在当前状态下选择的动作,例如调用搜索工具、读取文件、修改代码、执行命令;r_t 表示执行动作之后环境给予的反馈,例如工具返回结果、测试是否通过、用户是否满意。因此 Agent 优化目标从传统语言模型的:P(y∣x)P(y|x)变成P(τ∣x)P(\tau|x)

也就是说,模型学习的不只是“最终答案应该是什么”,而是“完成这个任务需要经历怎样的决策过程”。

例如,一个 Coding Agent 修复 Bug 的训练数据,不应该只有:

json
{
"user":"修复登录失败问题",
"assistant":"修改login.py中的token验证逻辑"
}

因为这个数据没有告诉模型为什么修改这个文件,也没有告诉模型如果修改失败应该怎么办。

更适合 Agent 训练的数据应该是:

json
{
"task":"修复用户登录失败问题",

"trajectory":[

{
"state":{
"files":[
"login.py",
"token.py",
"user.py"
]
},

"action":{
"tool":"search_code",
"arguments":{
"query":"token validation"
}
},

"observation":{
"result":"发现token.py存在过期时间判断"
},

"reward":0.3
},

{
"state":{
"file":"token.py"
},

"action":{
"tool":"edit_file",
"arguments":{
"path":"token.py",
"change":"fix timestamp calculation"
}
},

"observation":{
"result":"pytest failed"
},

"reward":-0.2
},

{
"state":{
"test_error":"timestamp mismatch"
},

"action":{
"tool":"analyze_error"
},

"observation":{
"result":"timezone conversion bug"
},

"reward":0.5
},

{
"action":{
"tool":"run_test"
},

"observation":{
"result":"all tests passed"
},

"reward":1
}

]
}

通过这种轨迹数据,模型能够学习的不只是“修改 token.py”,而是完整的工程行为模式:

发现问题 → 搜索相关代码 → 修改 → 测试 → 根据错误调整 → 再验证。

这也是 FIPO 和传统 Chain-of-Thought(CoT)的根本区别。CoT 主要优化模型内部的思维过程,它关注的是:

plain-text
Question → Reasoning → Answer

例如数学问题:

plain-text
计算过程 → 得出结果

但是 Agent 面向真实环境,必须考虑外部反馈:

plain-text
Goal → Planning → Action → Tool → Observation → New Action

因此 FIPO 优化的是 Agent 与环境交互产生的行为轨迹,而不是单纯优化模型内部生成的文字推理。

从强化学习角度看,Agent 可以看作一个 Policy:

πθ(at∣st)\pi_\theta(a_t|s_t)

表示模型在状态 s_t 下选择动作 a_t 的概率。FIPO 的目标就是通过反馈调整模型参数,使 Agent 在长期任务中获得更高累计奖励:

J(θ)=Eτ∼πθ[∑t=0Tγtrt]J(\theta)=E_{\tau\sim\pi_\theta}\left[\sum_{t=0}^{T}\gamma^tr_t\right]

这里的关键是长期奖励(Long-Term Reward)。普通模型可能只关注当前一步是否正确,但是 Agent 需要考虑当前动作是否会影响未来。例如:

用户要求:

“分析这个地下工程设计是否符合规范。”

一个普通 LLM 可能直接根据经验回答:

plain-text
符合规范,需要加强支护。

但是专业 Agent 应该执行:

plain-text
用户问题 → 判断需要规范依据 → 查询知识库 → 找到JGJ120 → 提取稳定性要求 → 获取设计参数 → 计算验算 → 输出结论

如果 Agent 第一步没有选择检索工具,后面所有推理可能建立在错误基础上。因此 FIPO 会给不同动作反馈:

错误轨迹:

json
{
"action":"direct_answer",
"feedback":"没有引用规范依据",
"reward":-1
}

正确轨迹:

json
{
"action":"search_standard",
"feedback":"找到JGJ120相关章节",
"reward":0.5
}

{
"action":"calculate_stability",
"feedback":"验算通过",
"reward":1
}

经过大量训练后,模型会逐渐形成稳定策略:

plain-text
工程问题 → 判断知识需求 → 调用检索 → 获取证据 → 推理分析 → 输出答案

FIPO 解决长链路推理最重要的问题是错误传播(Error Propagation)。在几十步任务中,一个早期错误可能导致后续所有动作偏离。例如:

plain-text
错误理解用户目标
        ↓
选择错误工具
        ↓
获取错误信息
        ↓
基于错误信息继续推理
        ↓
最终任务失败

传统 SFT 只能看到:

json
{
"result":"failed"
}

但是不知道失败原因。

FIPO 通过记录每一步反馈:

json
{
"step":1,
"action":"search_document",
"result":"correct",
"reward":0.5
}

{
"step":2,
"action":"select_wrong_document",
"result":"wrong",
"reward":-1
}

{
"step":3,
"action":"recover",
"result":"correct",
"reward":0.8
}

让模型知道具体哪个决策导致失败,以及正确的恢复方式。

这对于 Tool Calling Agent 尤其重要。一个成熟 Agent 并不是简单知道工具存在,而是需要学习什么时候使用工具、选择哪个工具、工具失败之后怎么办。例如企业知识库 Agent 有:

python
tools = [
    search_document,
    query_database,
    calculate,
    generate_report
]

用户输入:

“判断上海某基坑设计是否满足稳定性要求。”

模型需要学习:

不能直接回答;

应该先调用:

python
search_document(
    "JGJ120 基坑稳定性"
)

然后:

python
extract_requirement(
    "抗倾覆验算"
)

然后:

python
calculate(
    M_resist,
    M_overturn
)

最后:

python
generate_report(
    result
)

整个过程:

plain-text
用户问题 → Tool Selection → Tool Execution → Result Analysis → Next Tool Selection → Final Answer

就是 FIPO 优化的对象。

FIPO 与 RLHF 的区别也在于此。RLHF 主要优化回答质量:P(y1)>P(y2)P(y_1)>P(y_2)

例如:

哪个回答更加符合人类偏好。

但是 Agent 更关心:P(τ1)>P(τ2)P(\tau_1)>P(\tau_2)

例如:

哪个执行路径更加可靠。

两个 Agent 最终都可以生成正确答案:

Agent A:

plain-text
搜索一次 → 找到资料 → 完成任务

Agent B:

plain-text
搜索十次 → 找错资料 → 重复调用 → 最终完成

传统评价可能认为两者一样,但是生产环境中 Agent A 明显更优秀,因为它消耗更少 Token、更少工具调用、更短时间,并且失败概率更低。

因此 FIPO 需要大量真实 Agent Execution Trajectory。这些轨迹通常来自线上 Agent 运行日志:

python
trajectory = {
    "task": user_request,

    "steps":[

        {
            "state":state1,
            "action":"search",
            "observation":"document found",
            "reward":0.5
        },

        {
            "state":state2,
            "action":"calculate",
            "observation":"calculation passed",
            "reward":1
        }
    ]
}

这些数据可以用于不同阶段训练:

SFT 阶段学习基础 Agent 行为,例如如何调用工具、如何生成正确参数。RL 阶段优化长期奖励,例如减少无效工具调用,提高任务成功率。FIPO 阶段进一步利用执行反馈,让模型学习整个长链路策略。FIPO 实际上构建了一个闭环 Agent 学习系统:

plain-text
Foundation Model → Agent Runtime → Tool Execution → Environment Feedback → Trajectory Storage → Policy Optimization → Improved Agent

Foundation Model 提供基础推理能力,Agent Runtime 负责任务执行和状态管理,Tool Execution 连接真实环境,Feedback 提供学习信号,Trajectory Storage 保存经验,Policy Optimization 让模型不断改进。FIPO 的核心价值不是简单提升模型回答能力,而是让模型具备长期自主执行能力。未来 Agent 的竞争不会只依赖更大的参数规模,而会越来越依赖高质量执行轨迹、反馈闭环以及 Harness 系统设计。一个优秀 Agent 不仅需要“会思考”,更需要“会行动、会观察、会纠错、会优化”,而 FIPO 正是在训练层面解决这个问题。


Agent RL中为什么用mask tool return

长链路 Agent 强化学习训练中,mask tool return 的核心原因是区分 Agent 自己产生的行为(Action)和环境返回的信息(Observation)。在传统大语言模型训练中,模型学习的是文本生成,即给定前面的 token 预测后面的 token,但是 Agent 系统已经不再是简单的文本生成任务,而是一个和外部环境持续交互的决策系统。一个完整 Agent 执行过程通常是:User Goal → Agent Reasoning → Tool Call → Environment Execution → Tool Return → Agent Continue Reasoning → Final Answer,其中 Agent 负责产生动作,工具和环境负责产生反馈。因此强化学习真正需要优化的是 Agent 在某个状态下选择什么动作,而不是让模型学习如何生成环境反馈。

从强化学习角度看,一个 Agent 任务可以表示为 trajectory:

python
trajectory = [
    (state_0, action_0, reward_0),
    (state_1, action_1, reward_1),
    (state_2, action_2, reward_2)
]

其中 state 表示当前环境状态,action 表示 Agent 决策,reward 表示任务结果。策略优化目标是:

πθ(a∣s)\pi_\theta(a|s),也就是学习:“在状态 s 下,模型应该选择什么动作 a。”

但是 Tool Return 并不是 action,它属于 environment response。例如一个 Coding Agent 修复代码:

plain-text
User:
修复这个 Python 项目的 bug

Agent:
我要查看 main.py 文件

Tool Call:
read_file("main.py")

Tool Return:
def calculate():
    return x / 0

Agent:
发现除零错误,需要修改代码

这里:

plain-text
Agent 输出:
我要查看 main.py
read_file("main.py")
发现除零错误

属于 Agent 行为,需要训练。

Tool 输出:
def calculate():
    return x / 0

属于环境反馈,不应该训练。

如果训练时不进行 mask,那么语言模型的 loss 会覆盖整个序列:

python
loss = CrossEntropy(
    model_output,
    all_tokens
)

此时模型学习目标变成:

plain-text
User Token → Assistant Token → Tool Return Token → Assistant Token

模型不仅学习:、“什么时候调用工具。”

还会学习:“工具返回应该长什么样。”

这会产生严重问题,因为真实运行环境中 Tool Return 并不是模型生成的。例如搜索工具:

python
result = search_engine(
    "latest AI paper"
)

返回:

plain-text
GPT-5 reasoning architecture paper

这个结果来自搜索系统,而不是 Agent Policy。如果训练模型生成这个结果,相当于让模型学习模拟环境,而不是学习使用环境。因此训练时需要对 Tool Return token 设置 loss mask。典型做法是在 label 中把工具返回部分设置为 -100,让 PyTorch 的 CrossEntropyLoss 忽略这些 token:

python
import torch

labels = input_ids.clone()

for token in tokens:
    if token.role == "tool":
        labels[token.position] = -100

loss_function = torch.nn.CrossEntropyLoss( ignore_index=-100)

loss = loss_function(
    logits.view(-1, vocab_size),
    labels.view(-1)
)

这里:

plain-text
label = token真实目标

-100 = 不参与loss计算

最终训练过程变成:

plain-text
User Input → Agent Reasoning(计算Loss) → Tool Call(计算Loss) → Tool Return(不计算Loss) → Next Reasoning(计算Loss)

也就是说 Tool Return 仍然进入模型上下文,但是不会成为模型学习生成的目标。

这一点非常重要,因为很多人容易误解:mask tool return 并不是“不让模型看工具结果”,而是“不让模型学习生成工具结果”。在 Agent 执行过程中,Tool Return 是必须被模型读取的信息。例如:

python
file_content = read_file(
    "main.py"
)

agent_context.append(
    file_content
)

模型后续:

plain-text
读取代码 → 分析错误 → 修改文件 → 运行测试

完全依赖工具返回。因此正确关系应该是:

plain-text
Tool Return → Context → Reasoning

但是:

Tool Return ≠ Training Target

在长链路 Agent RL 中,这个问题更加明显,因为一次任务可能包含几十甚至几百次工具交互。例如 SWE Agent:

plain-text
Issue → Plan → Search Code → Tool Return → Modify → Test → Tool Return → Debug → Submit

整个 trajectory 可能包含:

plain-text
Agent Action Token:
10000 tokens

Tool Observation Token:
50000 tokens

如果不 mask,训练信号会被大量 Tool Return 淹没。模型看到的大部分训练目标都是:“复制环境信息。”

而不是:“根据环境信息做正确决策。”

错误训练目标:

plain-text
模型学习:

输入:
调用数据库

输出:
数据库结果

正确训练目标:

plain-text
模型学习:

输入:
数据库结果

输出:
下一步应该执行什么动作

因此 mask 后:

python
loss_mask = [
    1, # assistant reasoning
    1, # tool call
    0, # tool return
    1, # next reasoning
]

最终优化的是:

plain-text
Reasoning

↓

Planning

↓

Tool Selection

↓

Error Correction

↓

Task Completion

而不是:

plain-text
Environment Simulation

这和机器人强化学习非常类似。机器人系统:

plain-text
Robot Policy → Motor Command → Physical World → Sensor Observation

训练目标是:“看到状态后如何控制机器人。”

而不是:“如何生成摄像头画面。”

plain-text
Agent Policy → Tool Call → External System → Tool Return

训练目标也是:“看到环境反馈后如何选择下一步动作。”

而不是:“如何生成搜索结果、代码文件内容、数据库返回值。”

在 PPO、GRPO 等 Agent RL 方法中,mask tool return 最终作用在 token-level policy loss 或 advantage 计算阶段。例如:

python
policy_loss = (  token_loss  * loss_mask).sum() / loss_mask.sum()

其中只有 Agent 生成的 token 会影响梯度更新。因此长链路 Agent RL 中的完整训练逻辑可以表示为:

plain-text
Environment State → Agent Policy → Action → Environment → Observation → Agent Policy Update

其中:

plain-text
Action:
需要优化

Observation:
用于条件输入

Reward:
用于评价结果

Mask Tool Return 的目的就是保持 Agent、Environment 和训练目标之间的边界。如果不 mask,模型会逐渐退化成一个“环境模拟器”,擅长生成看似合理的工具结果,却不一定擅长真正调用工具解决问题;而正确 mask 后,模型学习的是长期任务中的规划、工具选择、错误恢复和决策能力。


$$

相关文章

强化学习笔记 →上下文工程 →Mission: 掌握多模态 LoRA 与 QLoRA →