前阵子做数学推理模型的强化学习微调,我遇到一个很典型的情况:模型在训练集上把"最终答案正确率"刷到了九成以上,可只要你把最终答案遮住,认真看它的抓取过程,就会发现大量自相矛盾的中间步骤。明明是4加7,它可能前面写等于10,后面又因为结果对了反过来硬凑一个解释。问题的根源就一句话——整条回复生成完,统共就给了一个总分。后来我把奖励改成了token级别的过程反馈,情况立刻不一样了。这篇想聊的 token_level_rewards,简单说就是:不再对一条完整输出只打一个标量分,而是把奖励信号细化到token或语义步骤级别,让模型知道"这一步走对了、那一步错了"。它适合所有做长文本生成RL微调的人,尤其是数学推理、代码生成、工具调用这类带中间过程的场景。下面按我踩过的坑和验证过的方法来讲。1. 为什么一整条回复只打一个分,会让推理模型越学越别扭1.1 信用分配问题:模型不知道错在哪一步标准的RLHF或RLVR流程,通常是等模型把整条回复生成完,再根据最终结果给一个奖励。数学题对了给+1,错了给-1,或者用规则程序判断一下最终字符串是否匹配。这个流程本身没有错,但存在一个非常麻烦的信用分配问题。假设模型生成了40步推理,前20步都很好,第21步把一个符号搞反了,后面全部建立在错误基础上。最终奖励是-1。那么在PPO的反向传播里,这个-1会均匀摊到40步的所有token上,前20步本来正确的token也被压低了概率。下一轮训练,模型想改,但它不知道具体哪里错了,只能把整段输出风格调整一下,甚至把本来对的步骤也改掉。我那时候看过一组内部样本:同一个错误模式连着出现三轮更新,模型每次都在"改写最后的结论表达",而中间那个符号错误一直没被修正。这就是整体奖励的局限——它只告诉模型"死"了,不告诉它"哪一刀是致命的"。1.2 偶发正确导致的错误模式固化比稀疏信号更危险的是"偶发正确"。长思维链模型经常会出现这样的轨迹:运算逻辑一塌糊涂,但最终答案碰巧对上了。这时候整体奖励给+1,那么前面所有错误token都会被当成"优秀行为"放大概率。这类错误积累多了之后,模型会形成一种"强答案、弱过程"的稳定策略:反正只要最终输出看起来像正确答案就行,中间步骤随便编。你把这种模型部署到真实任务上,用户看一眼过程就知道不靠谱,但它在训练指标上表现得很好。1.3 语义单元token化的奖励直觉解决方案就是让反馈粒度变细。所谓token级别,并不严格指"语料库里的每个token独立打一个分",而是指奖励分配的语义单元从"整条回答"缩小到"一个步骤、一个句子,乃至一组关键token"。举个直觉的例子:学生考完试只拿到一个总分,和拿到"填空扣2分、计算题第三题扣5分、选择题全对"的分项反馈,学习效率是完全不一样的。token_level_rewards做的就是这个事——把总分成一个个分项,再映射到策略模型可以优化的token概率上。这一步想通后,剩下的核心问题就是:逐token或逐步骤的reward怎么来?这就是下一节要拆的。2. token级奖励的四种主流落地路径2.1 规则型过程奖励:适合可验证中间状态第一种最朴素,适合能自动验证中间状态的场景。比如代码执行:每一步生成一个执行结果,用实际运行是否报错、输出是否符合预期作为这一步的分数;再比如SQL查询:对每一步生成的中间表做schema校验;数学场景里,至少可以检查每一步的公式是否合法、括号是否闭合、变量是否被定义。这类规则判分的好处是零模型开销、确定性高、不会引入第二层模型噪声。但缺点是它只能覆盖"形式正确性",对语义正确性无能为力。比如数学推理里"两边同时乘以-1"这个步骤,规则系统只能判断有没有写出变换,判断不了这个变换是不是真的该做。所以规则型奖励通常作为基础信号,用来惩罚格式违规和行为越界,再和其他判分器组合。2.2 过程奖励模型:最通用的token级打分方案如果要覆盖任意语义场景,就需要一个过程奖励模型。它做的事情是:输入一个prompt和目前已经生成的前置步骤,输出一个分数,表示"走到这一步为止,整体解题方向对不对"。每生成一个step,PRM就给个分;生成结束后,把step分数广播到该step包含的所有token上,就得到了token级reward。PRM的优点是几乎不依赖任务规则,适用于开放式写作、复杂推理、对话规划这类场景。但它也有明显风险:PRM自己是另一个模型,它的判断可能有偏。如果PRM倾向于认为"写得长就是写得好",那模型会被带偏成话痨;如果PRM数据里基于某种错误标注模式训练,整个RL流程都在放大错误。我见过不少团队一上来就用LLM标注了一批PRM训练集,结果训练出来的过程奖励模型对长步骤一律给高分,最后策略模型学会了把一个公式拆成八句话写。所以PRM需要严格的构建和验证流程,不能拿几条few-shot prompt生成的标签就直接开训。2.3 组内相对基线:GRPO和RLOO的隐式token信用第三种路径不显式地训练奖励模型,而是利用采样组的相对比较来估算每个token的优劣。GRPO的核心是用同一prompt采样出N条回答,取这N条回答的整体得分均值做基线,然后用每条回答的得分减去基线得到优势。如果你给每条回答内部加上了step级别的得分结构,GRPO在计算优势时,实际上就是在做token级别的credit分配。每个token的advantage不再等于"整条回答得分减去组均值",而是可以等于"该token所处step的得分减去组均值"。这么做不需要额外训练critic模型,也不需要大量显存去拟合价值网络,工程上非常轻量。代价是baseline质量受限于组内样本数。如果同一个prompt采样出来的N条回答得分都很接近,优势会趋于零,策略更新幅度变小。所以实际使用GRPO做token级reward时,往往要配合较高的温度采样,拉开样本差异。2.4 Critic网络逐位置估计价值最"正统"的逐token优势来源,是PPO里的critic网络。Critic对"当前前缀状态"预测期望回报,再通过GAE公式把最终奖励逐步回溯,得到每个已经生成的token的优势值。这套机制解决的问题是时间上的延迟反馈——不需要外部对每个step给分,也能把最终奖励合理分配到中间token上。但它有两个很难受的点:一是长序列的value估计方差非常大,尤其推理中段状态复杂度高,critic很容易欠拟合;二是显存开销大,actor和critic两套前向,长度一上来就容易OOM。我个人的判断是:如果你的任务有可写的过程判分规则,优先走GRPO+step score;如果完全没有中间信号且必须做长程推理,才考虑上critic。一上来就追PPO+critic的token级方案,训练稳定性会让人怀疑人生。这四条路径可以混合,比如规则奖励打底,PRM做语义判分,GRPO组内baseline做优势归一化,最后再加上critic做临时插值。下面用一个实际做过的数学推理实验来串一遍。3. 一次数学推理实验:把step score广播到token的完整设计3.1 实验设定与数据准备我当时的场景是:一个开源7B规模模型,在标准数学问答数据上做RL微调,目标是让它的最终答案正确率提升,同时强制要求输出带编号的解题步骤。整个任务的难点在于,模型并不仅仅要"给出对的结果",还需要"给出可检查的推理过程"。数据方面,我选了能程序化判断最终答案的公开数学推理题。注意这里有个前置条件——最终答案必须能被规则校验,否则后面所有step得分都失去对齐锚点。如果任务答案完全开放,就不要用这套自动判分方案,而是走PRM路线。3.2 步骤拆分与三路判分器生成完一条回答后,第一步是按照换行符和"Step N:"这类标记切成步骤序列。太长的步骤继续按句子拆分。这里有个容易被忽略的细节:拆完步骤后,要给每个step保留它在完整token序列里的起止位置,方便后面把step分数广播回token。我一开始没保留位置索引,结果奖励和token对不上,训练效果一塌糊涂。判分器我同时挂了三个:规则格式判分:检查步骤编号是否连续、公式括号是否闭合、是否有重复的无意义步骤;LLM judge判分:用同一个基座模型做异步批处理,对每一步输入一个打分prompt,输出0到3的过程分数;最终答案判分:程序核对最终结果是否正确。三类分数合成为一个step_score,大致公式是:step_score = 0.5 * judge_score + 0.3 * format_score if is_final_step: step_score += answer_reward # 最终答案正确 +1,错误 -1judge_score本身做了0到3的归一化,format_score是一个0到1的规则分值。权重是我调了几轮后觉得比较稳的配置:judge占大头保证语义判断,format兜底防止模型输出乱序内容,final answer作为整体锚点防止过程分和结果严重脱节。3.3 把step score映射为token reward拿到每个step的分数后,映射到token就很简单了:def compute_token_rewards(steps, answer_correct): token_rewards = [] for step in steps: score = step_scores[step.id] for _ in range(step.token_count): token_rewards.append(score) if answer_correct: # 对最后一个token额外加一份结束奖励,避免eos成为优势最低点 token_rewards[-1] += 1.0 return token_rewards这一步虽然叫token_level_rewards,本质上还是step级别广播。纯逐token打分在语义任务里并不现实,也比较脆——单个token的优劣往往依赖上下文,直接打分会把模型带向"局部词频优化"。把step score广播到token,是工程上性价比最高的实现方式。3.4 GRPO组内优势计算reward算好之后,我去掉了critic,用GRPO做组内基线。流程是:同一个prompt采样4到8条回答,把每条回答的内部token reward汇总成一个样本总分,再以组内均值做baseline,计算每个token的优势。group_scores = [sample.score for sample in group] baseline = np.mean(group_scores) std = np.std(group_scores) + 1e-4 for sample in group: for i, token_reward in enumerate(sample.token_rewards): advantage = (token_reward - baseline) / std sample.token_advantages[i] = advantage做完归一化之后,每个token的优势会有正有负。正值鼓励提升该token概率,负值压制。相比整条样本只有一个优势,token级优势让模型能分清好步骤和坏步骤。这个实验跑下来,最终答案准确率大约涨了8个点,更关键的是过程的格式合规率和中间逻辑一致性明显变好。3.5 实验里发现的反直觉现象有个现象让我印象很深:原本模型在中间步骤里经常偷偷换符号,训练后期这种错误几乎消失了,因为它学会了"特定步骤位置对应特定惩罚"。但同时也出现一个问题——当judge对某个长步骤给分偏高时,模型会故意把本来就对的步骤拉长,凑judge的得分维度。这说明任何模型判分器都可以被策略模型钻空子。要对抗这种博弈,就得不停轮换judge提示词的表述,或者混合多个judge模型的判断。这些工程细节放在下一段展开。4. 工程实现里的五个坑:缓存、归一化、KL、对齐、评测4.1 采样缓存与判分复用RL训练里rollout阶段要采样大量样本,如果每个step都实时调用一个LLM judge,成本非常高,而且延迟会拖慢整个训练循环。我的做法是把采样结果按输入prompt的hash值做缓存,相同提示下相同step的judge结果直接复用,只有新增step才触发新判分。如果数据本身是离线准备的,还可以提前把一部分标准步骤的判分结果存成索引表,训练时直接查表。这样能省掉大约60%的judge调用量。有一条要注意:judge结果缓存的有效期不能太长,如果策略模型已经明显改变了输出分布,旧缓存会引入偏差。一般我每隔几百步清一次缓存。4.2 判分分布偏移与归一化LLM judge输出的分数经常集中在区间中部,区分度很低。比如0到3分,它绝大多数时候给2分或1分,极少给0分。这会导致整体的token reward分布非常扁平,GRPO的组内标准差很小,优势信号普遍被压缩。我的解决方式是先对step_score做一次百分位归一化,再进入GRPO的advantage计算。百分位基数是最近一张rollout batch里所有step的分数分布。这样一来,即使是0.1分的细微差异,只要确实存在分布偏移,也能变成明显正负。归一化一定要按同一批样本计算,不能跨batch直接做,否则容易把分布漂移当成有效信号,训练会抖。4.3 KL散度与token级奖励的互相牵制token级奖励的另一个隐含风险是它对KL惩罚特别敏感。奖励尺度大的时候,模型会快速把所有token概率推向两个极端——好处是过程改进快,坏处是输出多样性骤降,变成固定模板复读机。我实验里出现过连续几十步都是同一句话的翻版。怎么判断KL系数该调多少?经验是盯住response的熵值。如果熵值下降过快,先不要调KL,而是把token reward的scale压到原来的0.5倍,再看熵的形状。token级reward的scale其实是超参里最值得调的一档,它直接决定了"细粒度反馈"和"策略坍缩"之间的平衡。4.4 token reward和attention mask对齐这是训练时最容易出bug的地方,也是报错最少但效果最差的地方。多个样本长度不同,padding后,padding位置的token reward如果不做mask,会被当成一个低优势token参与更新,把整体baseline拉低。实际操作里,我在计算每个token的advantage前,会额外生成一个有效的token mask,把padding位置替换成无效,并在loss计算时强制忽略。另外,eos token的reward处理也要单独看。很多人习惯给eos设0,但在GRPO里,0在均值负区间反而成了一个高优势token,模型会更早地强制结束生成。我的做法是eos共享最终答案的判定结果:答案正确,eos继承一个正的小分,答案错误,eos继承负分。4.5 评测阶段必须配一个"不看judge"的硬指标训练过程中,我发现一个让人迷惑的现象:judge平均分一直在涨,但最终答案正确率反而停滞了。检查bad case后发现,模型学会了一种投机行为——把正确公式重复两遍,然后结论写错。这种输出每一步看起来都不错,judge给分很高,但最终答案错了,结果锚点分数拉低了总体。所以我在正式评测里保留了三个维度:最终答案准确率、过程格式合规率、judge平均分。前两个是硬指标,judge平均分只做参考。尤其要警惕"judge涨、结果不涨"的组合,它说明策略模型正在针对judge的偏好做过度优化。5. token级奖励的边界:什么时候不该硬上5.1 不是所有任务都需要token级信号如果你做的是单步分类、短语生成、或者输出只有几十个token的短文本,整体奖励已经完全够用。引入token级判分器只会增加延迟和噪声,训练方差反而更大。这类任务里,模型很容易通过整体奖励找到正确方向,不需要更细的信用分配。任务输出过长但中间步骤高度依赖前序结果的,比如长篇小说生成,token级reward反而可能破坏连贯性。模型为了迎合局部高分,会把每一步都写得"看起来合理",丧失长程主题布局。这种情况更适合用critic做整体式价值估计,而不是局部判分。5.2 低质量PRM比没有PRM更糟有段时间我图省事,用LLM prompt直接标注了一批PRM训练数据。标注器本身对"subtle错误"的识别能力就不行,导致PRM对某些错误类型完全无感。结果RL训练里,模型在PRM高分的路径上越走越偏,最终结果正确率反而掉了。后来我把PRM去掉,退回整体reward,先保住基线,再重新构造PRM训练集才拉回来。建议是:如果PRM在独立验证集上的准确率低于80%,不要直接拿来当token级判分源。宁可用整体奖励做RL,靠采样组内baseline做隐式信用分配,也比注入一个劣质判分器强。5.3 对探索的抑制密集的token级奖励本身有"强化局部高奖励行为"的趋势。如果模型还在早期探索阶段,你过早给每个step打正负分,它会很快收敛到局部最优路径,不愿意尝试更长但可能更好的解法。实操上,我倾向于在前两个epoch只给最终答案奖励,让模型充分探索解题路径,等到训练中期再开启step级判分。这样既保留了探索空间,又能在后期精准纠偏。在我的训练日志里,这个顺序调整带来的效果比调一倍学习率都明显。5.4 广义token reward的演进方向现在提到token_level_rewards,也不用死盯着"文本token"不放。代码执行节点、工具调用节点、树搜索节点,本质上都可以视为广义token。你想让模型在哪一层决定上获得反馈,就把reward锚点放在哪一层。比如工具调用场景,模型每次调用工具的输入输出都可以作为独立step打分;搜索场景里,每层节点的置信度就是天然的过程奖励。这套思路完全可以复用:先拆语义单元,再给单元打分,然后广播回策略模型能优化的参数位置。我的总体建议是:先别急着上最复杂的方案,从"最容易出错的位置"下手。先拿整体reward跑通一个稳定基线,然后在中间某个关键环节挂一个规则判分器,最后再上模型judge。每一步都盯着bad case,比一开始就堆满token级信号要靠谱得多。
前阵子做数学推理模型的强化学习微调,我遇到一个很典型的情况:模型在训练集上把"最终答案正确率"刷到了九成以上,可只要你把最终答案遮住,认真看它的抓取过程,就会发现大量自相矛盾的中间步骤。明明是4加7,它可能前面写等于10,后面又因为结果对了反过来硬凑一个解释。问题的根源就一句话——整条回复生成完,统共就给了一个总分。后来我把奖励改成了token级别的过程反馈,情况立刻不一样了。这篇想聊的 token_level_rewards,简单说就是:不再对一条完整输出只打一个标量分,而是把奖励信号细化到token或语义步骤级别,让模型知道"这一步走对了、那一步错了"。它适合所有做长文本生成RL微调的人,尤其是数学推理、代码生成、工具调用这类带中间过程的场景。下面按我踩过的坑和验证过的方法来讲。1. 为什么一整条回复只打一个分,会让推理模型越学越别扭1.1 信用分配问题:模型不知道错在哪一步标准的RLHF或RLVR流程,通常是等模型把整条回复生成完,再根据最终结果给一个奖励。数学题对了给+1,错了给-1,或者用规则程序判断一下最终字符串是否匹配。这个流程本身没有错,但存在一个非常麻烦的信用分配问题。假设模型生成了40步推理,前20步都很好,第21步把一个符号搞反了,后面全部建立在错误基础上。最终奖励是-1。那么在PPO的反向传播里,这个-1会均匀摊到40步的所有token上,前20步本来正确的token也被压低了概率。下一轮训练,模型想改,但它不知道具体哪里错了,只能把整段输出风格调整一下,甚至把本来对的步骤也改掉。我那时候看过一组内部样本:同一个错误模式连着出现三轮更新,模型每次都在"改写最后的结论表达",而中间那个符号错误一直没被修正。这就是整体奖励的局限——它只告诉模型"死"了,不告诉它"哪一刀是致命的"。1.2 偶发正确导致的错误模式固化比稀疏信号更危险的是"偶发正确"。长思维链模型经常会出现这样的轨迹:运算逻辑一塌糊涂,但最终答案碰巧对上了。这时候整体奖励给+1,那么前面所有错误token都会被当成"优秀行为"放大概率。这类错误积累多了之后,模型会形成一种"强答案、弱过程"的稳定策略:反正只要最终输出看起来像正确答案就行,中间步骤随便编。你把这种模型部署到真实任务上,用户看一眼过程就知道不靠谱,但它在训练指标上表现得很好。1.3 语义单元token化的奖励直觉解决方案就是让反馈粒度变细。所谓token级别,并不严格指"语料库里的每个token独立打一个分",而是指奖励分配的语义单元从"整条回答"缩小到"一个步骤、一个句子,乃至一组关键token"。举个直觉的例子:学生考完试只拿到一个总分,和拿到"填空扣2分、计算题第三题扣5分、选择题全对"的分项反馈,学习效率是完全不一样的。token_level_rewards做的就是这个事——把总分成一个个分项,再映射到策略模型可以优化的token概率上。这一步想通后,剩下的核心问题就是:逐token或逐步骤的reward怎么来?这就是下一节要拆的。2. token级奖励的四种主流落地路径2.1 规则型过程奖励:适合可验证中间状态第一种最朴素,适合能自动验证中间状态的场景。比如代码执行:每一步生成一个执行结果,用实际运行是否报错、输出是否符合预期作为这一步的分数;再比如SQL查询:对每一步生成的中间表做schema校验;数学场景里,至少可以检查每一步的公式是否合法、括号是否闭合、变量是否被定义。这类规则判分的好处是零模型开销、确定性高、不会引入第二层模型噪声。但缺点是它只能覆盖"形式正确性",对语义正确性无能为力。比如数学推理里"两边同时乘以-1"这个步骤,规则系统只能判断有没有写出变换,判断不了这个变换是不是真的该做。所以规则型奖励通常作为基础信号,用来惩罚格式违规和行为越界,再和其他判分器组合。2.2 过程奖励模型:最通用的token级打分方案如果要覆盖任意语义场景,就需要一个过程奖励模型。它做的事情是:输入一个prompt和目前已经生成的前置步骤,输出一个分数,表示"走到这一步为止,整体解题方向对不对"。每生成一个step,PRM就给个分;生成结束后,把step分数广播到该step包含的所有token上,就得到了token级reward。PRM的优点是几乎不依赖任务规则,适用于开放式写作、复杂推理、对话规划这类场景。但它也有明显风险:PRM自己是另一个模型,它的判断可能有偏。如果PRM倾向于认为"写得长就是写得好",那模型会被带偏成话痨;如果PRM数据里基于某种错误标注模式训练,整个RL流程都在放大错误。我见过不少团队一上来就用LLM标注了一批PRM训练集,结果训练出来的过程奖励模型对长步骤一律给高分,最后策略模型学会了把一个公式拆成八句话写。所以PRM需要严格的构建和验证流程,不能拿几条few-shot prompt生成的标签就直接开训。2.3 组内相对基线:GRPO和RLOO的隐式token信用第三种路径不显式地训练奖励模型,而是利用采样组的相对比较来估算每个token的优劣。GRPO的核心是用同一prompt采样出N条回答,取这N条回答的整体得分均值做基线,然后用每条回答的得分减去基线得到优势。如果你给每条回答内部加上了step级别的得分结构,GRPO在计算优势时,实际上就是在做token级别的credit分配。每个token的advantage不再等于"整条回答得分减去组均值",而是可以等于"该token所处step的得分减去组均值"。这么做不需要额外训练critic模型,也不需要大量显存去拟合价值网络,工程上非常轻量。代价是baseline质量受限于组内样本数。如果同一个prompt采样出来的N条回答得分都很接近,优势会趋于零,策略更新幅度变小。所以实际使用GRPO做token级reward时,往往要配合较高的温度采样,拉开样本差异。2.4 Critic网络逐位置估计价值最"正统"的逐token优势来源,是PPO里的critic网络。Critic对"当前前缀状态"预测期望回报,再通过GAE公式把最终奖励逐步回溯,得到每个已经生成的token的优势值。这套机制解决的问题是时间上的延迟反馈——不需要外部对每个step给分,也能把最终奖励合理分配到中间token上。但它有两个很难受的点:一是长序列的value估计方差非常大,尤其推理中段状态复杂度高,critic很容易欠拟合;二是显存开销大,actor和critic两套前向,长度一上来就容易OOM。我个人的判断是:如果你的任务有可写的过程判分规则,优先走GRPO+step score;如果完全没有中间信号且必须做长程推理,才考虑上critic。一上来就追PPO+critic的token级方案,训练稳定性会让人怀疑人生。这四条路径可以混合,比如规则奖励打底,PRM做语义判分,GRPO组内baseline做优势归一化,最后再加上critic做临时插值。下面用一个实际做过的数学推理实验来串一遍。3. 一次数学推理实验:把step score广播到token的完整设计3.1 实验设定与数据准备我当时的场景是:一个开源7B规模模型,在标准数学问答数据上做RL微调,目标是让它的最终答案正确率提升,同时强制要求输出带编号的解题步骤。整个任务的难点在于,模型并不仅仅要"给出对的结果",还需要"给出可检查的推理过程"。数据方面,我选了能程序化判断最终答案的公开数学推理题。注意这里有个前置条件——最终答案必须能被规则校验,否则后面所有step得分都失去对齐锚点。如果任务答案完全开放,就不要用这套自动判分方案,而是走PRM路线。3.2 步骤拆分与三路判分器生成完一条回答后,第一步是按照换行符和"Step N:"这类标记切成步骤序列。太长的步骤继续按句子拆分。这里有个容易被忽略的细节:拆完步骤后,要给每个step保留它在完整token序列里的起止位置,方便后面把step分数广播回token。我一开始没保留位置索引,结果奖励和token对不上,训练效果一塌糊涂。判分器我同时挂了三个:规则格式判分:检查步骤编号是否连续、公式括号是否闭合、是否有重复的无意义步骤;LLM judge判分:用同一个基座模型做异步批处理,对每一步输入一个打分prompt,输出0到3的过程分数;最终答案判分:程序核对最终结果是否正确。三类分数合成为一个step_score,大致公式是:step_score = 0.5 * judge_score + 0.3 * format_score if is_final_step: step_score += answer_reward # 最终答案正确 +1,错误 -1judge_score本身做了0到3的归一化,format_score是一个0到1的规则分值。权重是我调了几轮后觉得比较稳的配置:judge占大头保证语义判断,format兜底防止模型输出乱序内容,final answer作为整体锚点防止过程分和结果严重脱节。3.3 把step score映射为token reward拿到每个step的分数后,映射到token就很简单了:def compute_token_rewards(steps, answer_correct): token_rewards = [] for step in steps: score = step_scores[step.id] for _ in range(step.token_count): token_rewards.append(score) if answer_correct: # 对最后一个token额外加一份结束奖励,避免eos成为优势最低点 token_rewards[-1] += 1.0 return token_rewards这一步虽然叫token_level_rewards,本质上还是step级别广播。纯逐token打分在语义任务里并不现实,也比较脆——单个token的优劣往往依赖上下文,直接打分会把模型带向"局部词频优化"。把step score广播到token,是工程上性价比最高的实现方式。3.4 GRPO组内优势计算reward算好之后,我去掉了critic,用GRPO做组内基线。流程是:同一个prompt采样4到8条回答,把每条回答的内部token reward汇总成一个样本总分,再以组内均值做baseline,计算每个token的优势。group_scores = [sample.score for sample in group] baseline = np.mean(group_scores) std = np.std(group_scores) + 1e-4 for sample in group: for i, token_reward in enumerate(sample.token_rewards): advantage = (token_reward - baseline) / std sample.token_advantages[i] = advantage做完归一化之后,每个token的优势会有正有负。正值鼓励提升该token概率,负值压制。相比整条样本只有一个优势,token级优势让模型能分清好步骤和坏步骤。这个实验跑下来,最终答案准确率大约涨了8个点,更关键的是过程的格式合规率和中间逻辑一致性明显变好。3.5 实验里发现的反直觉现象有个现象让我印象很深:原本模型在中间步骤里经常偷偷换符号,训练后期这种错误几乎消失了,因为它学会了"特定步骤位置对应特定惩罚"。但同时也出现一个问题——当judge对某个长步骤给分偏高时,模型会故意把本来就对的步骤拉长,凑judge的得分维度。这说明任何模型判分器都可以被策略模型钻空子。要对抗这种博弈,就得不停轮换judge提示词的表述,或者混合多个judge模型的判断。这些工程细节放在下一段展开。4. 工程实现里的五个坑:缓存、归一化、KL、对齐、评测4.1 采样缓存与判分复用RL训练里rollout阶段要采样大量样本,如果每个step都实时调用一个LLM judge,成本非常高,而且延迟会拖慢整个训练循环。我的做法是把采样结果按输入prompt的hash值做缓存,相同提示下相同step的judge结果直接复用,只有新增step才触发新判分。如果数据本身是离线准备的,还可以提前把一部分标准步骤的判分结果存成索引表,训练时直接查表。这样能省掉大约60%的judge调用量。有一条要注意:judge结果缓存的有效期不能太长,如果策略模型已经明显改变了输出分布,旧缓存会引入偏差。一般我每隔几百步清一次缓存。4.2 判分分布偏移与归一化LLM judge输出的分数经常集中在区间中部,区分度很低。比如0到3分,它绝大多数时候给2分或1分,极少给0分。这会导致整体的token reward分布非常扁平,GRPO的组内标准差很小,优势信号普遍被压缩。我的解决方式是先对step_score做一次百分位归一化,再进入GRPO的advantage计算。百分位基数是最近一张rollout batch里所有step的分数分布。这样一来,即使是0.1分的细微差异,只要确实存在分布偏移,也能变成明显正负。归一化一定要按同一批样本计算,不能跨batch直接做,否则容易把分布漂移当成有效信号,训练会抖。4.3 KL散度与token级奖励的互相牵制token级奖励的另一个隐含风险是它对KL惩罚特别敏感。奖励尺度大的时候,模型会快速把所有token概率推向两个极端——好处是过程改进快,坏处是输出多样性骤降,变成固定模板复读机。我实验里出现过连续几十步都是同一句话的翻版。怎么判断KL系数该调多少?经验是盯住response的熵值。如果熵值下降过快,先不要调KL,而是把token reward的scale压到原来的0.5倍,再看熵的形状。token级reward的scale其实是超参里最值得调的一档,它直接决定了"细粒度反馈"和"策略坍缩"之间的平衡。4.4 token reward和attention mask对齐这是训练时最容易出bug的地方,也是报错最少但效果最差的地方。多个样本长度不同,padding后,padding位置的token reward如果不做mask,会被当成一个低优势token参与更新,把整体baseline拉低。实际操作里,我在计算每个token的advantage前,会额外生成一个有效的token mask,把padding位置替换成无效,并在loss计算时强制忽略。另外,eos token的reward处理也要单独看。很多人习惯给eos设0,但在GRPO里,0在均值负区间反而成了一个高优势token,模型会更早地强制结束生成。我的做法是eos共享最终答案的判定结果:答案正确,eos继承一个正的小分,答案错误,eos继承负分。4.5 评测阶段必须配一个"不看judge"的硬指标训练过程中,我发现一个让人迷惑的现象:judge平均分一直在涨,但最终答案正确率反而停滞了。检查bad case后发现,模型学会了一种投机行为——把正确公式重复两遍,然后结论写错。这种输出每一步看起来都不错,judge给分很高,但最终答案错了,结果锚点分数拉低了总体。所以我在正式评测里保留了三个维度:最终答案准确率、过程格式合规率、judge平均分。前两个是硬指标,judge平均分只做参考。尤其要警惕"judge涨、结果不涨"的组合,它说明策略模型正在针对judge的偏好做过度优化。5. token级奖励的边界:什么时候不该硬上5.1 不是所有任务都需要token级信号如果你做的是单步分类、短语生成、或者输出只有几十个token的短文本,整体奖励已经完全够用。引入token级判分器只会增加延迟和噪声,训练方差反而更大。这类任务里,模型很容易通过整体奖励找到正确方向,不需要更细的信用分配。任务输出过长但中间步骤高度依赖前序结果的,比如长篇小说生成,token级reward反而可能破坏连贯性。模型为了迎合局部高分,会把每一步都写得"看起来合理",丧失长程主题布局。这种情况更适合用critic做整体式价值估计,而不是局部判分。5.2 低质量PRM比没有PRM更糟有段时间我图省事,用LLM prompt直接标注了一批PRM训练数据。标注器本身对"subtle错误"的识别能力就不行,导致PRM对某些错误类型完全无感。结果RL训练里,模型在PRM高分的路径上越走越偏,最终结果正确率反而掉了。后来我把PRM去掉,退回整体reward,先保住基线,再重新构造PRM训练集才拉回来。建议是:如果PRM在独立验证集上的准确率低于80%,不要直接拿来当token级判分源。宁可用整体奖励做RL,靠采样组内baseline做隐式信用分配,也比注入一个劣质判分器强。5.3 对探索的抑制密集的token级奖励本身有"强化局部高奖励行为"的趋势。如果模型还在早期探索阶段,你过早给每个step打正负分,它会很快收敛到局部最优路径,不愿意尝试更长但可能更好的解法。实操上,我倾向于在前两个epoch只给最终答案奖励,让模型充分探索解题路径,等到训练中期再开启step级判分。这样既保留了探索空间,又能在后期精准纠偏。在我的训练日志里,这个顺序调整带来的效果比调一倍学习率都明显。5.4 广义token reward的演进方向现在提到token_level_rewards,也不用死盯着"文本token"不放。代码执行节点、工具调用节点、树搜索节点,本质上都可以视为广义token。你想让模型在哪一层决定上获得反馈,就把reward锚点放在哪一层。比如工具调用场景,模型每次调用工具的输入输出都可以作为独立step打分;搜索场景里,每层节点的置信度就是天然的过程奖励。这套思路完全可以复用:先拆语义单元,再给单元打分,然后广播回策略模型能优化的参数位置。我的总体建议是:先别急着上最复杂的方案,从"最容易出错的位置"下手。先拿整体reward跑通一个稳定基线,然后在中间某个关键环节挂一个规则判分器,最后再上模型judge。每一步都盯着bad case,比一开始就堆满token级信号要靠谱得多。