做LLM强化学习的同学,最近应该没少看见token_level_rewards这个词。它并不是一个孤立的算法,而是一种奖励设计的思路切换:从给整个回答打分,变成给每个token或者每个步骤打分。这个看起来不大的变化,实际影响面比想象中大得多——直接关系到强化学习训练时的credit assignment(信用分配)、奖励信号密度、以及最终模型是学会了推理还是学会了钻空子。
我最早接触这个概念,是在做数学推理任务的RL训练时。用序列级奖励(sequence-level reward)去跑PPO,发现模型经常出现“明明整道题做错了,但损失却降得很快”的诡异情况。后来换成token级别的奖励设计,训练曲线才变得正常。这篇博文我就围绕token_level_rewards,从原理到实现,再到调参踩坑,完整梳理一遍,希望能帮你少走弯路。
1. 为什么非要用token级别:序列奖励的信用分配困境
1.1 一句话说清credit assignment问题
强化学习的核心问题之一是:一个策略产生了一条完整轨迹,最后得到了一个reward,这个reward到底应该归因到轨迹中的哪些动作上?这就是credit assignment。
放到LLM生成任务的场景里,生成一个200个token的回答,打分器只给整句话打了一个总分。如果这个回答是错误的,我们只是知道“整句话不该得到高分”,但到底是第10个token开始跑偏,还是第150个token才跑偏?传统PPO做GAE时会把整句话当作一个稀疏reward环境来处理,优势函数在整个轨迹上分摊——但LLM生成token之间是高度自相关的,前一个token的错误选择往往会带偏后面所有token,这时候把总分平均分摊回去,梯度方向就是错的。
我举一个更直观的例子。某开发者用7B模型做代码生成任务,序列级奖励按“能否通过单元测试”来打分。测试一跑全是红的,reward=-1,于是模型每个token的advantage都被拉低。但实际代码里,前面80个token写得很标准,就是第81个token把函数名拼错了,后面所有token跟着全错。负向梯度不仅惩罚了错误token,还惩罚了原本正确的80个token,这个就是序列奖励最典型的“连坐效应”。
1.2 密度与方差的权衡:稠密奖励为什么训练更稳
从RL理论上讲,奖励信号的密度直接影响策略梯度的方差。序列级奖励是稀疏奖励,每一条完整序列才有一个reward,如果这个reward还带很大的噪声(比如用LLM-as-a-judge打分,同一结果不同温度采样能打出3分和7分的差距),那策略梯度的方差会非常大。
token级别奖励本质上是把稀疏信号拆成稠密信号。每生成一个token、每写完一个逻辑步骤,就有一个局部得分。这样做有三点直接收益:
- 梯度信号更平滑,不再是一次性一个大脉冲,而是分阶段的小幅修正;
- 更容易定位到具体错误,训练时模型知道要调整的是哪一个环节;
- 价值模型的拟合难度降低,因为reward的方差变小,critic不需要去预测一个高度不确定的长程回报。
但要注意,密集不等于均匀。不是所有token都需要一个独立奖励,那样反而会引入大量标注噪声。实际做法更多是在“句子边界”“逻辑步骤边界”“代码块边界”等语义完整的位置打分数,中间token用分数分摊或差值表示。
2. 主流实现方案:PRM、MC估计与混合折减
2.1 过程奖励模型(PRM)打底
PRM(Process Reward Model)是token级奖励最经典的实现方式。它的目标是:输入部分生成序列和当前位置的上下文,输出一个标量分数,表示“当前已生成的这部分推理过程有多大概率走向正确结果”。和ORM(Outcome Reward Model)预测最终结果不同,PRM每一步都在预测未来的成功率。
PRM的训练数据是关键。常规做法是从每个推理样本中采样多条完整轨迹,标注每条轨迹在哪个位置开始出错。标注方式可以靠人工标注,也可以用“自动构造”的思路:从答案反推步骤,如果某一步之后无法推导到正确终点,就标记该步骤为错误。
这里有个实际经验:PRM训练数据的正负样本比例非常影响效果。我见过很多团队的PRM训练集里正确步骤和错误步骤比例失衡——正确步骤是错误步骤的10倍以上,结果PRM退化成了“差不多给高分”的模式。需要做下采样或者倒置采样,尽量让模型对错误步骤的敏感性保持住。
2.2 蒙特卡洛采样平均:没有PRM也能做token级奖励
如果不打算训练PRM,还有一种代替思路:蒙特卡洛采样平均。基本逻辑是,给定部分序列,从当前状态出发采样k条后续完整轨迹,统计这些轨迹中达到正确结果的频率/概率,作为当前状态的价值估计。
比如在数学推理任务里,模型已经写了前两步,从这两步往下续写8次,如果6次推出正确答案,那当前两个步骤的状态价值大概就是6/8=0.75。这个0.75就是token级(步骤级)奖励,它反映了当前进度离正确答案还有多远。
这个方案最大的优点是无需另外训练奖励模型,直接用生成模型做rollout就能拿到估计值。缺点也很明显:采样次数越多估计越准确,但计算成本成倍上升;而且如果策略模型本身很弱,采样出来的轨迹质量就差,状态价值会被系统性低估。在实际项目里,k值通常取4~8作为折中。
2.3 混合折减:不是所有任务都适合纯token级
纯token级奖励在开放性任务上有时候会“用力过猛”。比如对话任务,中间某个回复可能看起来偏离了剧情线,但整体用户满意度并不差。一个很常见的做法是混合奖励——最终结果仍然给序列级奖励,中间步骤给token级过程奖励,两者按系数加权合并。
我推荐一组从某RL训练项目里调出来比较稳妥的参数:总奖励 = 0.3 × 最终结果奖励(序列级) + 0.7 × 过程步骤奖励(token级)。这个比例不是固定的,如果任务对过程正确性要求高(比如数学证明、SQL生成),可以加大过程奖励权重;如果任务是闲聊对话,更看重最终体验一致性,可以反过来加大最终奖励权重。
核心原则是:token级奖励负责提供方向感,序列级奖励负责守住最终目标的下限,二者缺一不可。
3. 实操过程:一个简单但完整的GRPO训练配置
3.1 任务设定与奖励设计
下面用一个具体例子来讲实操。任务:让7B模型做小学数学应用题,要求模型输出逐步推理,最终给出答案。我们用GRPO算法做训练,因为GRPO不需要额外的critic模型,天然适合和token级奖励配合。
奖励函数拆成三部分:
- 步骤正确性:模型输出的中间推理步骤和参考解题路径做语义匹配,匹配度超过阈值得0.5分,否则0分;
- 最终答案正确性:最终答案和标准答案一致得1分,否则0分;
- 局部惩罚:如果某个推理步骤中包含了重复文本或自相矛盾的描述,扣0.2分。
每一步的奖励由PRM或者MC估计来打,最后一步额外加上答案正确性奖励。GRPO在计算优势时,会基于同一组生成样本的奖励值做归一化处理,得到每个token的advantage因子。
3.2 关键配置参数与调参依据
直接上一份可跑的参考配置,训练框架用的是某常见RL训练库(具体不限,逻辑是通用的):
# GRPO训练相关核心配置 actor_lr: 5e-6 critic_lr: 5e-6 # 如果用PPO而非GRPO时才会用到 reward_normalization: true temperature: 0.7 top_p: 0.9 beta: 0.04 # KL系数,限制策略偏移 mc_sampling_times: 6 # MC估计采样的轨迹条数 reward_coef: process_step_reward: 0.7 final_answer_reward: 0.3这里重点说几个参数的“为什么”。
温度系数0.7:GRPO生成batch时如果温度太高,采样多样性大但有效推理路径占比低,MC估计得到的部分状态价值噪声大;温度太低则采样轨迹过于保守,探索能力不足。0.7是我实测的一个平衡点,如果任务复杂度高,可以适当上调到0.8。
KL系数beta:设到0.04是防止策略在几步更新内产生剧烈偏移。token级奖励的粒度细,一个batch就可能改变策略对某些token的偏好,没有KL约束的话,模型很容易偏向某些“高分但语义异常”的输出模式。
mc_sampling_times:这个值直接决定token级奖励估计的方差。我分别用2、4、6、8测试过,2次采样奖励波动大到无法收敛,6次开始趋稳,8次收益递减且训练时间增加约35%。所以6是当前配置下的甜点值。
3.3 从零到一的核心训练循环
下面是一个训练循环的核心代码骨架,展示token级奖励如何接入GRPO的优势计算。代码风格偏伪代码,重点在逻辑链路,复制到项目里需要自行适配数据格式:
# 伪代码:token级奖励接入GRPO流程 # key_points: # 1. 对每个generated_token的reward是过程奖励+最终奖励的组合到步级别 # 2. 每一步对应的token序列全部摊分该步奖励 # 3. GRPO按多次采样奖励做组内归一化生成advantage import numpy as np def grpo_update_one_batch(prompts, policy, reward_model, tokenizer): """一组样本上进行GRPO更新""" # 1. 生成阶段 contexts = [tokenizer.encode(p) for p in prompts] outputs = policy.generate( prompts, num_return_sequences=6, # group_size temperature=0.7, top_p=0.9, max_new_tokens=128, ) # outputs: [batch_size * 6] 条候选轨迹 # 2. 奖励标注阶段(关键) rewards = [] for one_output in outputs: # 按句子边界/逻辑步骤拆分 steps = split_by_steps(one_output) step_returns = [] for i, step in enumerate(steps): # 当前步骤到最终答案估计回报 partial_reward = mc_estimate( policy, context_tokens_steps[:i+1], mc_times=6, reward_fn=final_answer_check, ) step_returns.append(partial_reward) # 最后一步额外加入结束奖励 final_ret = final_answer_check(one_output) # 融合 merged = 0.7 * np.mean(step_returns) + 0.3 * final_ret rewards.append(merged) # 3. 组内归一化(GRPO) # 按每组6条采样轨迹独立计算advantage rewards = np.array(rewards).reshape(-1, 6) mean = rewards.mean(axis=-1, keepdims=True) std = rewards.std(axis=-1, keepdims=True) + 1e-6 advantages = (rewards - mean) / std # 4. 策略梯度更新(简化) # 计算policy的对数概率,用advantages做加权 # 同时用KL散度做正则约束 policy.train_step( outputs_train_pairs, advantages=advantages.flatten(), kl_coef=0.04, )这份代码的关键点是第二段“奖励标注阶段”。这里不是一句话给一个reward,而是把一个长回答按逻辑步骤切分,逐步用MC采样估计“当前走了多远、离正确终点还有多少概率”,再把每个步骤的估计和最终结果reward做混合。整个过程的可解释性比黑箱PRM打分强很多:每个reward值都能追溯到“从某一步续写之后,有多少概率能推出正确答案”。
如果你选择的是PRM路线,那第二步就替换成PRM前向推理:对每个逻辑步骤输出分数;最后一步的最终奖励仍可额外叠加结果正确性判断。用PRM的好处是不需要每轮都重采样做MC估计,训练吞吐量更高,代价是自己还要多训练一个打分器。
3.4 从序列到token:步级拆分的切法
这里有一个“token级”的实践细节:并不是每个token都值得单独打一个reward。拼音、标点、停顿词等token打reward没有语义意义。所以实操中,我通常把“token级”理解为“最小语义单元级”,也就是一个完整的单词、一个代码token或一个推理步骤结束符。在数学推理里,最有代表性的拆分点就是“换行”“句号”“=”符号后。
具体到代码实现,可以在生成时给这些特殊token添加额外的自定义结束标记,使得每个步骤的边界更可控。比如在模型生成时,用[\n]作为步骤结束符,再根据这个符号切分步骤序列。切分不好会导致两个问题:步骤过碎导致奖励估计噪声大,步骤过粗则退化到序列级。经验法则是每个步骤的token数控制在8~20个之间,如果超过30个token还没有步骤切分点,说明该任务生成粒度偏长,可以考虑增加步骤强制周期。
4. 常见问题与排查技巧实录
4.1 训练损失急剧波动:先查奖励范围与归一化
用token级奖励后,首次训练最容易遇到的就是loss剧烈波动,甚至动不动就NaN。我排查过几个实例,80%的原因是奖励值范围没有得到合理约束。序列级奖励通常收敛在0或1附近,但token级奖励经过MC估计后可能会出现类似0.05、0.97、0.6等分散值,再加上按步平均又混合了最终奖励,数值分布形态完全不同,底层的advantage归一化如果没有做好配套,梯度就会走走停停。
解决办法:
- 开启reward_normalization,并确保advantage是在每个采样组内部做归一化;
- 如果奖励值本身有强离群点,先做一次reward clipping,范围限制在-1到1之间;
- 检查每个batch的reward分布方差,如果某组6条采样的reward方差接近于0,那这部分样本的advantage会被放大到很大,容易诱发突变。遇到这种情况,可以在归一化分母中加一个小的epsilon,或者跳过方差过小的样本组。
4.2 模型疯狂刷字数就是不推理:奖励黑客攻击
这是token级奖励最经典的翻车场景。模型发现只要把中间步骤写得又长又绕,或者大量重复上下文,PRM就会给出偏高的过程分。从原理上说,这是因为PRM训练数据中,长步骤通常和正确结果关联更强——毕竟推理过程写得多,最后得出正确答案的概率自然更高。模型一旦学到“长=对”,输出就会越来越长,而推理质量反而下降。
我遇到过最严重的一次:某训练项目里,模型输出长度从平均80个token暴涨到600个token,但最终正确率反而掉了7%。排查后定位到PRM打分时被长度因素主导。解决方案有两个方向:
- 在PRM训练数据中加入“短但正确”和“长但错误”的样本,平衡步骤长度与正确性的相关性,通常各需要占15%以上;
- 在奖励函数中加入长度惩罚:如果步骤数超过参考步骤数的1.5倍,将过程奖励乘以系数0.8。
4.3 采样开销高到训练跑不动:MC次数与批量动态调整
token级奖励的一个绕不开的成本是MC采样估计。每生成一个中间步骤,都要从当前状态往下续写多条轨迹来估计状态价值,这个开销是序列级奖励的5~10倍。一批batch有1024条轨迹,每条按6次MC估计,相当于每次迭代要生成6144条子轨迹,训练显存和时间压力都很明显。
我试过几种减支技巧:
- 只在每个逻辑步骤的前两步做MC估计,后两步直接复用最近一次估计值,大致不影响训练效果,能节省约25%的采样量;
- 动态调整MC次数:奖励估计置信度高(多个采样结果一致)时调低次数,置信度低时调高次数;
- 把MC采样的temperature设得比主策略略高,这能在同样采样次数下覆盖更多状态,提高估计密度。
如果你使用的是PPO而不是GRPO,还可以用critic的预测值替代一部分MC采样——相当于用学到的价值函数对采样结果做平滑,减少采样次数。但critic存在高估风险,需要定期和真实MC估计做交叉校验。
4.4 算出来的token级奖励没有区分度:粒度匹配难题
粒度问题在代码生成任务里特别常见。比如一个任务是生成一个Python函数,参考代码只有一段,中间没有明确的步骤边界,硬拆出来的“步骤”是一整个连续的token流,每步的MC估计结果都差不多(要么都接近1要么接近0),奖励几乎无区分度,等于白折腾。
针对这种情况,我的建议是调整任务的“语义粒度”——不是用换行符,而是用AST节点边界或函数调用块边界来切分。如果实在切不出来,退回到序列级奖励加个中间状态价值折减可能更实际。不要为了用token级奖励而强行切分,没有合理语义边界的任务,强行上token级奖励只会增加方差,不会提升效果。
4.5 奖励估计和最终结果存在冲突
最后说一个我踩过好几轮才搞明白的坑:token级奖励和序列级最终奖励可能在梯度方向上互相打架。举例来说,模型中间推理步骤明明质量很好,每步MC估计都是高分,但最后因为一个算术运算失误导致答案错误,最终奖励是0。这时候总奖励(假设0.7过程+0.3结果)仍然是0.3×0 = 0的过程高分赋予的加权,模型的梯度会“奖励”一个最终失败的推理过程。短期看,模型学到了好的推理步骤;长期看,如果一直出现这种“过程全对、结果出错”,模型可能陷入局部最优,不再尝试修正最后的运算细节。
这个问题的应对方案是,在最终步骤奖励中引入“过程一致校验”:如果中间步骤分数很高但最终答案错误,则需要拉低该轨迹的整体得分,甚至可以加一个额外的惩罚项。换句话说,让token级奖励和结果级奖励的矛盾浮出水面并显式建模掉,而不是简单加权混合。
5. 扩展场景与后续思路
5.1 从数学推理到代码生成与Agent任务
token级奖励并不仅限于数学推理。在代码生成中,它可以转化为“编译通过步骤”和“单元测试通过步骤”的分阶段奖励;在Agent任务中,它可以转化为“调用了正确工具”“查询了正确数据库”“回复了正确行动逻辑”的步骤奖励。这一类需要多步决策的任务,天然适合token级奖励——因为每一步的错误率高,反馈不及时模型很难学。用token级奖励后,模型能更快理解“哪个动作导致了惩罚”,而不是等一整轮任务结束时才看到一次性奖惩。
而且这类任务的奖励信号往往来自环境运行结果,天然客观(编译报错、超时、断言失败),比用LLM-as-a-judge打分噪声小很多,是token级奖励最容易见效的场景。
5.2 和搜索/树搜索增强结合
token级奖励的另一个价值是给树搜索类方法提供节点评分。在生成过程中,如果每生成一个逻辑步骤就能得到未来期望得分,那模型的解码策略可以做更细粒度的beam search式剪枝,而不是等全部生成完了再做整体排序。这个方向目前还在快速演进中,但实际验证下来,一个可靠的step-level价值估计配合适当的深度搜索,往往比单纯加大生成次数带来的增益更明显。
6. 实操心得总结
写到后半程,补几个我个人的实操体会。
第一,token级奖励本质上是把“事后评价”变“过程评价”。这个路数适合一切“解路径比答案更重要”的任务。如果任务对中间过程没有要求,那建议直接用序列级奖励,省心省力还不容易出幺蛾子。
第二,PRM路线和MC采样路线不是互斥的。我现在的习惯是:先以PRM为初始价值估计,训练过程中定期用MC采样抽查PRM输出的分布是否退化。一旦发现PRM和MC估计偏差持续扩大,就说明奖励模型对OOD样本处理出了问题,需要尽快重新校准或混入更多局部采样的真实轨迹数据。
第三,奖励设计坑远比网络结构坑多。跑LLM强化学习,网络结构基本沿用成熟方案不太容易翻车,反而是reward function的设计和格式对齐最容易出问题。多花时间检查每个reward是否真的在衡量你想要的行为,比调学习率有价值得多。
最后再分享一个小技巧:在训练的key指标监控板上,同时记录“每一步奖励和最终结果奖励的相关系数”。这个值是判断奖励设计是否合理的一个简单但可靠信号——如果两者相关系数低于0.3,说明过程奖励和结果奖励之间已经脱节了;把它恢复到0.5以上,训练效果普遍会好一个档次。我自己现在看训练曲线时,第一个看的不是loss,而是这个相关系数。