Chapter 10

自定义 Reward 函数与数据

📌 commit 6c6056daa522 规则 reward / Agent 函数 / 多轮 VLM

RLHF 训练的"灵魂"是 reward。OpenRLHF 把 reward 抽象得很灵活——可以是 RM 模型、可以是 Python 函数、 可以是远端 HTTP 服务、甚至可以是一个"Agent function"和环境交互。本章教你怎么写自定义 reward + 数据格式, 以及 v0.10 新加的"agent / VLM"功能。

10.1四种 reward 来源

来源flag用途
RM 模型 --reward.model_name_or_path <path> 对话偏好对齐
本地 Python 函数 --reward.remote_url /path/to/file.py 规则 reward(数学、代码)
Remote HTTP --reward.remote_url http://server:8000/score 外部 judge(GPT-4 评分等)
Agent 函数 --train.agent_func_path /path/to/agent.py 带环境交互的复杂任务

10.2写一个本地规则 reward

仓库自带 examples/python/reward_func.py 是最小例子:

import torch

def reward_func(queries, prompts, labels, **kwargs):
    """
    Args:
        queries (List[str]):  prompt + response 的完整文本
        prompts (List[str]):  仅 prompt
        labels (List[str]):   ground truth(数据里 label_key 字段)
    Returns:
        dict: 包含 rewards / scores / extra_logs 三个 key
    """
    reward = torch.randint(0, 2, (len(queries),)).float()

    return {
        "rewards": reward,                       # 用于 advantage 计算
        "scores": reward,                        # 用于 dynamic filtering(0-1 之间)
        "extra_logs": {"dummy_scores": reward},  # 写到 wandb
    }

三个 key 的含义:

key含义
rewards 真正参与 advantage 计算的 reward;可以是任意 float
scores 用于 dynamic filtering(决定哪些组要丢);通常 = rewards,但归一到 [0, 1]
extra_logs 不参与训练,只是写日志方便监控

10.3数学题 reward 函数(实战范本)

examples/python/math_reward_func.py

from openrlhf.utils import extract_boxed_answer, grade_answer

def reward_func(queries, prompts, labels, **kwargs) -> dict:
    rewards = []

    for query, prompt, label in zip(queries, prompts, labels):
        # 抠出 response 部分
        if isinstance(prompt, str) and prompt in query:
            response = query[len(prompt):]
        else:
            response = query

        # 抠 \boxed{} 内的答案
        pred_answer = extract_boxed_answer(response)
        # 用 math_verify 库判断对错
        is_correct = grade_answer(pred_answer, label)
        rewards.append(1.0 if is_correct else 0.0)

    rewards_tensor = torch.tensor(rewards, dtype=torch.float)
    accuracy = rewards_tensor.mean()

    return {
        "rewards": rewards_tensor,
        "scores": rewards_tensor,
        "extra_logs": {"math_accuracy": accuracy},
    }

这种"答案对就 +1,错就 0"是 R1 风格训练的标准模板。

10.4多 reward 加权组合

常见诉求:

$r = w_1 \cdot r_\text{correct} + w_2 \cdot r_\text{format} + w_3 \cdot r_\text{length}$

def reward_func(queries, prompts, labels, **kwargs):
    rewards = []
    correct_log, format_log, length_log = [], [], []

    for query, prompt, label in zip(queries, prompts, labels):
        response = query[len(prompt):]
        pred = extract_boxed_answer(response)

        r_correct = 1.0 if grade_answer(pred, label) else 0.0
        r_format = 0.1 if has_proper_format(response) else -0.1
        r_length = -0.001 * max(0, len(response) - 2048)

        rewards.append(r_correct + r_format + r_length)
        correct_log.append(r_correct)
        format_log.append(r_format)
        length_log.append(r_length)

    return {
        "rewards": torch.tensor(rewards),
        "scores": torch.tensor(correct_log),       # filtering 只看正确性
        "extra_logs": {
            "r_correct": torch.tensor(correct_log).mean(),
            "r_format":  torch.tensor(format_log).mean(),
            "r_length":  torch.tensor(length_log).mean(),
        },
    }

10.5数据格式

OpenRLHF 用 HuggingFace datasets / parquet / jsonl 都行。关键是 --data.input_key--data.label_key 指向哪个字段。

偏好对齐(PPO + RM)的 prompt 数据

// 一条样本
{
  "context_messages": [
    {"role": "user", "content": "请解释什么是相对论"}
  ]
}
# 启动时
--data.input_key context_messages \
--data.apply_chat_template               # 自动拼模板

数学题数据(GRPO + 规则 reward)

// 一条样本
{
  "prompt": "求方程 x² + 2x - 3 = 0 的解",
  "label": "x=1 或 x=-3"
}
# 启动时
--data.input_key prompt \
--data.label_key label \
--data.apply_chat_template

偏好对(RM 训练用)

// 一条样本
{
  "prompt": "...",
  "chosen": "...",     // 被偏好的回复
  "rejected": "..."    // 被拒绝的回复
}
# 用 train_rm 训练 RM
python -m openrlhf.cli.train_rm \
  --pretrain <SFT 模型> \
  --dataset <偏好数据> \
  --apply_chat_template \
  --max_len 4096 \
  --save_path ./rm_output

10.6Remote URL reward

把 reward 计算放外部服务,训练时调 HTTP:

启动远端 RM 服务

python -m openrlhf.cli.serve_rm \
  --reward_pretrain <RM 模型> \
  --port 8000 \
  --max_len 4096

训练时连过去

--reward.remote_url http://reward-server:8000/score

用途:

10.7Agent 函数:环境交互式 RL

v0.10 引入。每次"调 reward" 都让模型与某个环境进行多步交互,最后给一个 reward。 仓库自带 examples/python/agent_func.py 是范本。

典型 agent_func 签名

async def step(observation, action, **kwargs):
    """
    每一步交互。
    Args:
        observation: 当前环境状态(字符串 / 图像)
        action:      模型刚生成的 action(string)
    Returns:
        next_observation: 下一步环境给模型看的内容
        reward:           当前步 reward(可以是 0,最后一步给总分)
        done:             bool
        info:             extra logs
    """
    # 例如:在数学题环境里
    if is_final_answer(action):
        is_correct = grade(action, ground_truth)
        return None, 1.0 if is_correct else 0.0, True, {}
    else:
        # 多轮思考链
        return f"<refine>{action}</refine>", 0.0, False, {}

启用方式

--train.agent_func_path examples/python/agent_func.py \
--rollout.max_new_tokens 32000 \
--algo.advantage_estimator group_norm \
--rollout.n_samples_per_prompt 8

OpenRLHF 把 agent_func 装到 vLLM 周围,每个 prompt 对应一个 agent rollout,中间多轮交互、最后一步反馈 reward。 适合:

10.8多轮 VLM Agent

v0.10 进一步扩展到 VLM(视觉语言)。examples/python/vlm_multiturn_agent.py:每次交互 observation 可以是 新的图像(如截图、相机帧),action 是模型生成的文本指令。

使用脚本:

bash examples/scripts/train_vlm_math_hybrid_engine.sh

这是当前最前沿的 RLHF 形态——把 LLM Agent 引入"图像 → 推理 → 行动"完整闭环。

10.9dataset 配置常用 flag

flag含义
--data.prompt_dataset HF dataset 名或本地路径
--data.input_key prompt 字段名
--data.label_key ground truth 字段名
--data.apply_chat_template 用 tokenizer 的 chat template 拼
--data.max_len 样本最大 token 数
--data.max_samples 取前 N 条(debug 用)
--data.prompt_split HF dataset 的 split (默认 train)
--data.eval_dataset 验证集
--data.tokenizer_chat_template 覆盖默认 chat template

10.10常用公开数据集

数据集用途HF ID
prompt-collection 偏好对齐 prompts OpenRLHF/prompt-collection-v0.1
preference-700k RM 训练数据 OpenRLHF/preference_dataset_mixture2_and_safe_pku
RM-700k-pretrained 已训好的 RM OpenRLHF/Llama-3-8b-rm-700k
SFT-mixture 已 SFT 好的模型 OpenRLHF/Llama-3-8b-sft-mixture
DAPO-Math-17K 数学题(GRPO 用) zhuzilin/dapo-math-17k
OpenR1-Math R1 风格数学题 open-r1/OpenR1-Math-220k

10.11reward 调试的 3 个技巧

  1. 用 max_samples=8 dry run:先确认 reward_func 能正常返回,shape 对,类型对。
  2. 把 extra_logs 打满:所有中间 reward 信号都写到 wandb,方便排查 reward hacking。
  3. 跑一次 SFT 模型的 baseline:用 SFT 模型直接采样、过 reward_func,看分数分布。如果都 0 或都 1 说明数据 / 函数有问题。

10.12reward hacking 的早期信号

现象多半原因
reward 飙升但 sample 质量降 reward function 有 bug 被钻漏洞
response 越来越长 没有 length penalty
response 越来越短 format reward 过强
response 中出现重复 pattern reward 对某种结构有 spurious 偏好
训完模型不会答简单问题 reward 数据 distribution 偏窄

遇到这些现象,第一时间降低 lr + 升 KL coef,把 policy 拉回锚定。

10.13Agent 接口源码锚(openrlhf/utils/agent.py

ch06.14 提到了 AgentExecutor。"我要写一个多轮 agent" 时实际改的是这两个基类:

基类方法用途
AgentInstanceBase reset(prompt, **kw) → observation 每条 prompt 开始时初始化 instance 状态
step(action) → (observation, reward, done, info)每步生成的 action 输入环境,返回反馈
AgentExecutorBase execute(prompt) → trajectory 编排"生成→step→生成"循环(一般用现成的 MultiTurnAgentExecutor 就够)

典型 instance 实现(参考 examples/python/agent_func.py):

class MathAgentInstance(AgentInstanceBase):
    def reset(self, prompt, **kw):
        self.problem = prompt
        return {"prompt": prompt}

    def step(self, action):
        # action 是模型刚生成的 token / 字符串
        # 返回工具调用结果作为下一轮 observation
        if "<tool_call>" in action:
            code = extract_code(action)
            result = sandbox.run(code)
            return {"prompt": result}, 0.0, False, {}
        else:
            answer_correct = check_answer(self.problem, action)
            return {}, float(answer_correct), True, {"correct": answer_correct}

启用:--reward.remote_url examples/python/agent_func.py --train.agent_async_enable。OpenRLHF 自动给每条 prompt 起一个 instance,跑完整条 trajectory 后才进 RL。

10.14VLM 多轮 agent(2026-04 新)

2026-04 之后 OpenRLHF 把 agent 框架扩到了 VLM。看 vlm_multiturn_agent.py 与默认文本 agent 的差异:

能力实现
step 返回的 observation 含图像 {"prompt": "...", "images": [PIL.Image]}
多轮图像累积 accumulate_mm_inputs() 合并历次图像张量
screenshot 反馈 用户 step 内调浏览器自动化 / GUI 截图作为下一轮 obs
VLM forward 路径 Actor 自动检测 images 字段走 VLM forward

用法:--vlm_enable --reward.remote_url examples/python/vlm_multiturn_agent.py。典型场景:网页 agent / GUI agent / 视觉推理。

10.15Remote HTTP reward server 模式

"reward 在另一台机器上跑"这个需求很常见(外部沙箱 / 大型 RM)。OpenRLHF 通过 --reward.remote_url http://... 支持:

特性说明
协议 HTTP POST JSON,body 含 queries / prompts / labels
响应 JSON {"rewards": [...], "extra_logs": {...}}
批量 OpenRLHF 一次发 batch_size 个样本,server 一次性返回
超时 --reward.remote_timeout 600(秒)
典型 server openrlhf serve_rm 起的 vLLM-based RM server

"为什么不直接 RPC?" → HTTP 跨语言、跨集群最方便。reward server 可以是 Python / Rust / Go,OpenRLHF 端只看 JSON。

10.16这章你需要带走的