当前位置:首页 > 编程开发 > 正文

工具调用的奖励怎么设计:二值奖励会饿死模型

约 4161 字 · 预计阅读 11 分钟
一句话总结:单步正确率 20% 的模型,做五步串联任务,全对的概率是 0.03%——用"全对才给分"的二值奖励,平均要 3125 次尝试才见到一次正样本,梯度信号等于没有。这就是 BotPsy 那类端侧 agent 模型为什么要给部分分。

工具调用用二值奖励,在弱模型上几乎失效

算一遍就明白。任务要连续调对五步工具,每步独立正确率 p:

单步正确率 五步全对概率 二值奖励平均要试多少次
44%1.65%61 次
30%0.24%412 次
20%0.03%3125 次
10%0.00001%100000 次

工具调用的奖励怎么设计:二值奖励会饿死模型

图 1:横轴为对数刻度,五步串联任务下二值奖励的期望尝试次数

这个表的意思是:模型越弱,越学不动。而强化学习恰恰是用来提升弱模型的。逻辑上自相矛盾。

强化学习的梯度来自"这次比平均好多少"。如果 3125 次里 3124 次都是 0 分,那"平均分"就是 0,没有任何一次能提供"比平均好"的信号。整个训练停在这里。

用十行代码验证这个数字

上面的表是按 p⁵ 算出来的解析值。可以再用蒙特卡洛跑一遍,确认量级对得上:

import random

def first_success(p, steps=5, cap=200000):
    """返回第一次五步全对的尝试次数;超过 cap 次算放弃"""
    for i in range(1, cap + 1):
        if all(random.random() < p for _ in range(steps)):
            return i
    return None

random.seed(20261111)
for p in (0.44, 0.30, 0.20):
    runs = [first_success(p) for _ in range(200)]
    ok = [r for r in runs if r]
    avg = sum(ok) / len(ok)
    print(f"p={p}: 200 次试验里 {len(ok)} 次在 20 万步内成功,平均 {avg:.0f} 次")

实际跑出来:p=0.44 平均 56 次,p=0.30 平均 413 次,p=0.20 平均 2803 次。和解析值 61 / 412 / 3125 对得上,差异来自随机波动(每组只跑 200 次试验)。

p=0.20 那组跑起来明显变慢,因为平均要近三千次才等到一次全对。

值得注意的一点:二值奖励的期望尝试次数是指数级增长的。单步正确率从 44% 降到 20%,期望尝试次数从 61 涨到 3125,涨了 51 倍。而正确率只降了 2.2 倍。

这就是为什么弱模型用二值奖励训不动——不是慢一点,是慢两个数量级。

奖励设计:部分给分怎么给

BotPsy 的做法是拆开给:工具名对、参数名对、参数值对,各拿一部分奖励。

一个五步任务,如果每步都能拿到部分分,那每一步都有信号。哪怕最终任务失败,模型也知道"第二步的工具选对了,第三步参数写错了"。

这跟二值奖励的根本区别在于:部分给分把"任务成功"这个稀疏信号,转换成了"每一步做对了多少"这个稠密信号。

代价是奖励和真实目标的偏离。模型可能学会"凑部分分"——把工具名和参数名写对,参数值瞎填也能拿 2/3 的分。所以部分给分的设计要保证"部分分高"和"任务成功"强相关。

三条可以复用的经验

第一,先算清楚奖励的稀疏度。 用上表的方法:单步正确率多少、要串几步、全对概率是多少。如果低于 1%,二值奖励基本不能用。

第二,部分分的粒度要跟失败原因对齐。 如果模型最常错在参数值,那参数值那部分权重要高;如果最常错在工具选择,就反过来。拍脑袋定权重,模型会往容易拿分的地方跑。

第三,留一条兜底信号。 有些任务实在没法拆部分分(比如"这个回答好不好"),可以加一个过程奖励:用另一个模型给中间步骤打分。这个做法成本高,但比训不动强。

三层奖励的权重怎么定

工具调用的奖励通常分三层:格式(能不能解析)、参数(名和值对不对)、结果(任务成不成)。三层怎么配权重,直接决定模型往哪个方向长。

用一个简单的期望回报表看:

模型行为 格式分 参数分 结果分 加权总分(1:1:1)
全对,任务成功1113.0
格式对、参数错、任务失败10.301.3
格式错、参数对、任务失败0.40.801.2
格式对、参数全错、任务失败1001.0

在等权配置下,"格式对但参数错"能拿 1.3 分,比"格式错但参数对"的 1.2 分更高。

这意味着模型会优先学格式。格式好学——它只取决于输出结构,和任务内容无关;参数对错取决于理解,难度高得多。等权配置下,模型自然往容易的方向走。

BotPsy 的 IFEval 90.8 和 BFCL 44.0,就是这种倾向的极端表现:格式能力被训到了很高,工具调用的准确率还停在一半。

如果想反过来,得把结果层的权重调高:

权重配置(格式:参数:结果) 模型会更倾向
1 : 1 : 1先保证格式能解析
1 : 2 : 4优先把参数填对
1 : 1 : 8一切为了任务成功,格式容忍度高

没有普适的最优配置。端侧场景要格式严格(渲染、解析都靠它),就可以给格式高权重;需要模型真正把事办成,就得把结果层压上去。

关键是意识到你在用权重投票。 三层等权不是"中立",是明确地偏向最容易的那层。

强化学习为什么用 DAPO 不用 PPO

BotPsy 用的是 DAPO,不是传统的 PPO。这个选择和小模型的处境有关。

PPO 需要一个和策略模型同规模的价值网络(critic)来估计基线。对 0.8B 的模型来说,加一个 0.8B 的 critic,训练成本直接翻倍。

DAPO 走的是组内归一化:同一个问题采样一组回答,用这组的平均奖励当基线。不需要 critic,也就省掉一半显存。

代价是基线估计得更粗——组内有几个样本,基线就由这几个样本决定。采样方差大的时候,梯度信号会跟着抖。

对小模型来说这笔买卖划算:显存本来就紧,能用更少的资源跑 RL,比用更精细的估计但跑不动更重要。

工具调用的奖励为什么特别难设计

因为工具调用有"看起来对但实际错"的情况。

参数传了个不存在的值——语法对,语义错。工具选对了但顺序错了——每步都对,串联起来失败。调用了不该调的工具——用户没说要做这个。

这几种错,二值奖励都只能给 0,分不出差别。而它们对应的修正方式完全不同。

所以现在工具调用类模型的训练,普遍用三层奖励:格式奖励(能不能解析出调用)、参数奖励(名和值对不对)、结果奖励(执行完任务成不成)。三层各自的权重,决定了模型最终偏向哪种能力。多教师蒸馏那篇讲的是怎么把不同能力的梯度分开传,这里讲的是怎么让信号不稀疏——两个问题经常同时出现。

端侧模型那篇里 BotPsy 的 BFCL 只有 44 分,也可以从这个角度看——它把大量训练信号投在了格式和参数上(所以 IFEval 能到 90.8),结果奖励那层相对弱,所以端到端调用准确率还没跟上。

参考资料

  1. BotPsy 0.8B 的训练配方(含部分给分奖励设计)
目录导航
  • 工具调用用二值奖励,在弱模型上几乎失效
  • 用十行代码验证这个数字
  • 奖励设计:部分给分怎么给
  • 三条可以复用的经验
  • 三层奖励的权重怎么定
  • 强化学习为什么用 DAPO 不用 PPO
  • 工具调用的奖励为什么特别难设计
    • 参考资料
  • 相关文章:

  • 0.8B 端侧 agent 模型:工具调用只到 44 分
  • 换个用户模拟器,agent 均分从 26.10 涨到 29.54
  • 写一个最小的 Agent 循环:80 行代码,和三个必须处理的边界
  • 给本地模型接工具:llama.cpp + Function Calling 实战
  • Function Calling 实战:schema 军规、三道闸与并发,200 次注入实测全绿之下还有 16.5% 错调
  • 发表评论

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。