生成式奖励模型 (GenRM) 示例
使用 生成式奖励模型(GenRM)——一种 LLM-as-judge 方法——对 rollout 生成的响应进行评分,替代传统的训练式奖励模型。
概述
GenRM(Generative Reward Model,生成式奖励模型)利用预训练的大语言模型(如 Qwen3-VL-30B-A3B-Instruct)来评估模型响应是否与标准答案一致。与训练专用奖励模型不同,GenRM 通过部署为独立 Ray Serve 服务的 SGLang 引擎在推理时进行评估。
核心优势:
- 零训练成本 — 直接使用已有的预训练 LLM,无需额外训练奖励模型
- 泛化能力强 — 利用 LLM 的推理能力,对未见过的任务也能有效评估
- 灵活可控 — 可通过 prompt 模板调整评估标准
本示例中的两个脚本均使用 GRPO 算法在 dapo-math-17k 数据集上训练 Qwen3-4B,通过 GenRM(--rm-type dapo-genrm)进行奖励评分,并使用 AIME-2024 进行评估。
架构
Relax 中 GenRM 有两种顶层部署模式:
- Colocate(
--colocate,推荐)——所有角色(Actor / Rollout / GenRM)共用同一个 placement group。训练阶段 Actor 收回全部 GPU,因此 GenRM GPU 从不空跑。 - Fully Async(
--fully-async)——每个角色独占一份 GPU。Rollout 与训练完全并行;GenRM GPU 训练时空闲。
在 --colocate 下,Rollout 与 GenRM 如何共用 bundle 又分三种子模式——按 GenRM 大小与 rollout 长尾情况选:
| 子模式 | Bundle 分布 | 推理期并发方式 | Inline reward | 触发条件 | 适用场景 |
|---|---|---|---|---|---|
| Split | 不相交 bundle | 并行(各自独占分片) | ✅ per sample | 自动 —— rollout_num_gpus + genrm_num_gpus == actor_total | 小 GenRM;长尾明显(agentic、response 长度方差大) |
| Shared / Co-resident | 同一批 bundle | 并行(按 mem_fraction 切分显存) | ✅ per sample | 自动 —— rollout_num_gpus == genrm_num_gpus == actor_total | 中等大小 GenRM,需要全集群 TP,但显存还能塞下 rollout |
| Shared / Defer-swap | 同一批 bundle | 串行(sleep-wake 编排) | ❌ 延迟到批后 | 显式开启 —— Shared bundles + --rm-type dummy + --defer-reward-to-post-process + --custom-reward-post-process-path | GenRM 显著大于 policy;短 response RLVR / math(rollout 无长尾可藏 GenRM 延迟) |
8-GPU Colocate (Split)
┌──────────── Placement Group (8 GPU) ────────────┐
│ │
│ Inference phase: │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Rollout (4 GPU) │──►│ GenRM (4 GPU) │ │
│ │ bundles 0..3 │◄──│ bundles 4..7 │ │
│ └───────────────────┘ └───────────────────┘ │
│ │
│ Training phase (offload inference weights): │
│ ┌─────────────────────────────────────────┐ │
│ │ Actor (8 GPU) │ │
│ │ Megatron Training │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
8-GPU Colocate (Shared / Co-resident)
┌──────────── Placement Group (8 GPU) ────────────┐
│ │
│ Inference phase (same bundles 0..7): │
│ ┌─────────────────────────────────────────┐ │
│ │ Rollout: mem_fraction_static = 0.6 │ │
│ │ GenRM : mem_fraction_static = 0.3 │ │
│ │ ~0.1 reserved for cuda / activations │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Training phase (offload inference weights): │
│ ┌─────────────────────────────────────────┐ │
│ │ Actor (8 GPU) │ │
│ │ Megatron Training │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
16-GPU Colocate (Shared / Defer-swap)
┌──────────── Placement Group (16 GPU) ───────────┐
│ │
│ Phase A — rollout(独占 16 GPU): │
│ ┌─────────────────────────────────────────┐ │
│ │ Rollout awake (mem_fraction ≈ 0.85) │ │
│ │ GenRM asleep (release_memory_occ.) │ │
│ │ --rm-type dummy → inline reward = 0 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ post_process_genrm_swap.py │
│ offload rollout ─►│─► onload GenRM │
│ ▼ │
│ Phase B — score(独占 16 GPU): │
│ ┌─────────────────────────────────────────┐ │
│ │ Rollout asleep │ │
│ │ GenRM awake (对整批 sample 批量打分) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ offload GenRM │
│ ▼ │
│ Phase C — train(独占 16 GPU): │
│ ┌─────────────────────────────────────────┐ │
│ │ Actor (Megatron Training) │ │
│ │ GenRM 保持 offload,由 │ │
│ │ --defer-reward-to-post-process 守护 │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘三种 colocate 子模式在训练阶段都把全部 GPU 归还给 Actor。Split 与 Shared / Co-resident 走 inline reward:Rollout 每生成一个候选就通过 HTTP 单发给 GenRM。Shared / Defer-swap 把 HTTP 调用改成每轮 rollout 后由 userland custom_reward_post_process 一次性批量发出;split 与 defer-swap 的完整取舍见 examples/generate_reward_model/README.md。
脚本
| 脚本 | Colocate 子模式 | 描述 |
|---|---|---|
run-qwen3-4B-8xgpu-colocated.sh | Split(小 GenRM) | Qwen3-4B policy + 小 GenRM 共 8 GPU;不相交 bundle,inline reward |
run-qwen35-35B-A3B-16xgpu-genrm-397B-split.sh | Split(大 GenRM) | 35B-A3B policy + 397B FP8 GenRM 共 16 GPU;8+8 不相交分片,inline reward |
run-qwen35-35B-A3B-16xgpu-genrm-397B-defer.sh | Shared / Defer-swap | 35B-A3B policy + 397B FP8 GenRM 共 16 GPU;共享 bundle,两阶段 sleep-wake 切换,批量 reward,实现见 post_process_genrm_swap.py |
run-qwen3-4B-8xgpu-async.sh | (Fully Async) | 每个角色独占 GPU 池;rollout 与训练完全并行 |
资源分配
--colocate 下三种子模式训练阶段都归还所有 GPU 给 Actor;差异仅在推理阶段 Rollout / GenRM 如何共处:
Split(不相交 bundle):
Actor: 8 GPU(全部)
Rollout: 4 GPU(bundles 0..3,mem_fraction 默认)
GenRM: 4 GPU(bundles 4..7,mem_fraction 默认)Shared / Co-resident(同一批 bundle,两者常驻,按 mem_fraction 切分显存):
Actor: 8 GPU(全部)
Rollout: 8 GPU(bundles 0..7,mem_fraction_static = 0.6)
GenRM: 8 GPU(bundles 0..7,mem_fraction_static = 0.3)
└── 总和 ≤ 0.9;余量留给 cuda / activationsShared / Defer-swap(同一批 bundle,串行切换):
Actor: 16 GPU(全部)
Rollout: 16 GPU(bundles 0..15,mem_fraction_static ≈ 0.85;打分时休眠)
GenRM: 16 GPU(bundles 0..15,mem_fraction_static ≈ 0.85;rollout / 训练时休眠)Async 模式(--fully-async,独占池):
Actor(训练): 2 GPU(专用)
Rollout: 3 GPU(专用)
Reference: 1 GPU
Actor Forward: 1 GPU
GenRM: 1 GPU(专用)快速开始
前置条件
模型权重 — 下载 Qwen3-4B(策略模型)和 Qwen3-VL-30B-A3B-Instruct(GenRM 评估模型):
bash# 放置在 exps/ 目录下(或设置 EXP_DIR / MODEL_DIR) exps/Qwen3-4B/ exps/Qwen3-VL-30B-A3B-Instruct/数据集 — 准备
dapo-math-17k用于训练,aime-2024用于评估:bashexps/dapo-math-17k/dapo-math-17k.jsonl exps/aime-2024/aime-2024.jsonlRay 集群 — 一个可访问的 Ray 集群,地址为
http://127.0.0.1:8265。
启动训练
# Colocate 模式(推荐,至少 8 GPU)
bash examples/generate_reward_model/run-qwen3-4B-8xgpu-colocated.sh
# Fully async 模式(至少 8 GPU)
bash examples/generate_reward_model/run-qwen3-4B-8xgpu-async.sh验证服务健康状态
训练任务启动后,检查 GenRM 服务是否正常运行:
curl http://localhost:8000/genrm/health预期响应:
{
"status": "healthy",
"service": "genrm"
}配置
GenRM 专用命令行参数
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
--genrm-model-path | str | None | GenRM 模型路径,设置后启用 GenRM |
--genrm-num-gpus | int | 1 | GenRM 使用的 GPU 总数 |
--genrm-num-gpus-per-engine | int | 1 | 每个 GenRM 引擎使用的 GPU 数量 |
--genrm-engine-config | JSON | None | 引擎初始化 JSON 配置(如 max_context_len、dp_size、pp_size) |
--genrm-sampling-config | JSON | None | 采样参数 JSON 配置 |
引擎配置键
| 键 | 类型 | 默认值 | 描述 |
|---|---|---|---|
max_context_len | int | 8192 | 最大上下文长度 |
dp_size | int | 1 | 数据并行大小 |
pp_size | int | 1 | 流水线并行大小 |
ep_size | int | 1 | 专家并行大小 |
mem_fraction_static | float | SGLang 默认值 | 单引擎 SGLang 静态显存比例。Shared 模式下必须设置(见下文配置示例)。 |
采样配置键
| 键 | 类型 | 默认值 | 描述 |
|---|---|---|---|
temperature | float | 0.1 | 采样温度 |
top_p | float | 1.0 | 核采样概率 |
top_k | int | -1 | Top-k 采样(-1 表示禁用) |
max_response_len | int | 4096 | 最大响应长度 |
资源分配
GenRM 在 --resource JSON 中作为 "genrm" 角色配置,格式为 [num_groups, num_gpus_per_group]。
Colocated / Split(小 GenRM,默认):
python3 relax/entrypoints/train.py \
--genrm-model-path /path/to/genrm/model \
--genrm-num-gpus-per-engine 4 \
--genrm-engine-config '{"max_context_len": 10240}' \
--genrm-sampling-config '{"temperature": 0.1, "top_p": 1.0, "top_k": -1, "max_response_len": 1024}' \
--resource '{"actor": [1, 8], "rollout": [1, 4], "genrm": [1, 4]}' \
--colocate \
--rm-type dapo-genrmColocated / Shared(大 GenRM,新增):把 rollout 和 genrm 都设为 actor 的全部 GPU;框架自动识别为 shared 模式,让两个引擎通过 mem_fraction_static 切分每张 GPU 的显存:
python3 relax/entrypoints/train.py \
--genrm-model-path /path/to/genrm/model \
--genrm-num-gpus-per-engine 8 \
--genrm-engine-config '{"max_context_len": 10240, "mem_fraction_static": 0.3}' \
--genrm-sampling-config '{"temperature": 0.1, "top_p": 1.0, "top_k": -1, "max_response_len": 1024}' \
--rollout-num-gpus-per-engine 1 \
--sglang-mem-fraction-static 0.6 \
--resource '{"actor": [1, 8], "rollout": [1, 8], "genrm": [1, 8]}' \
--colocate \
--rm-type dapo-genrm自动识别 colocate 子模式
启用 --colocate 且配置了 GenRM 时,GPU 分配决定 Split vs Shared:
| 分配 | 子模式 |
|---|---|
rollout_num_gpus + genrm_num_gpus == actor_total | Split(不相交 bundle) |
rollout_num_gpus == genrm_num_gpus == actor_total | Shared(同一批 bundle) |
| 其他 | 启动时报错拒绝 |
Shared 内部默认是 Co-resident(两个引擎按 mem_fraction_static 同时驻留)。再加上 --rm-type dummy + --defer-reward-to-post-process + --custom-reward-post-process-path 就切成 Defer-swap——sleep-wake 串行,每次只有一个引擎占显存。何时优先 defer-swap 见 示例 README。
Shared / Co-resident 必须设置 mem_fraction_static
Shared / Co-resident 模式下两个 SGLang 引擎同时驻留在同一组 GPU,必须设置各自的 mem_fraction_static,使单卡之和 < 1.0(建议 ≤ 0.9,剩余给 cuda graph + activations)。Rollout 通过 --sglang-mem-fraction-static(或 --sglang-config YAML overrides)配置;GenRM 通过 --genrm-engine-config 中的 mem_fraction_static 配置。Shared / Defer-swap 无需切分——两者永不共存,各自可取 ≈ 0.85。
Fully-Async 模式:
python3 relax/entrypoints/train.py \
--genrm-model-path /path/to/genrm/model \
--genrm-num-gpus-per-engine 1 \
--genrm-engine-config '{"max_context_len": 10240}' \
--genrm-sampling-config '{"temperature": 0.1, "top_p": 1.0, "top_k": -1, "max_response_len": 1024}' \
--resource '{"actor": [1, 2], "rollout": [1, 3], "reference": [1, 1], "actor_fwd": [1, 1], "advantages": [1, 0], "genrm": [1, 1]}' \
--fully-async \
--rm-type dapo-genrm多实例 GenRM(一个服务托管多个评判模型)
以上内容都假设只部署一个评判模型。--genrm-instances 让一个 GenRM Serve 部署同时托管多个独立的评判模型——不同大小、不同 checkpoint、不同评分标准——每个由调用方在请求中传入的 route_key 字符串来选择。Serve 部署本身、HTTP 路由(/genrm)以及健康检查 / metrics 接口都不变,只是请求体多了一个字段。
这种能力天然适配:
- 多目标奖励 —— 例如一个实例判答案正确性,另一个判安全性/无害性,你的 reward 函数把两者合并成一个训练信号。
- Agentic 流水线 —— agent 轨迹中不同模块(planner、tool-caller、最终答案判断等)可以各自由适合该模块的评判模型打分,而不需要为每个模块单独起一套 GenRM 部署(以及单独切一份 GPU)。
--genrm-instances 命令行参数
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
--genrm-instances | JSON | None | {route_key: instance_spec} 的 JSON 字典。设置后优先于 --genrm-model-path(若两者都设置,旧的单实例参数会被忽略并打 warning)。 |
每个 instance_spec 是一个字典,支持以下键:
| 键 | 类型 | 是否必填 | 描述 |
|---|---|---|---|
model_path | str | ✅ 必填 | 该实例评判模型的路径 |
num_gpus | int | ✅ 必填 | 该实例的 GPU 预算。不会在多个实例间自动均分——每个实例都必须显式声明自己的预算。 |
num_gpus_per_engine | int | 否 | 该实例每个 SGLang 引擎占用的 GPU 数,默认取全局 --genrm-num-gpus-per-engine。 |
engine_config | dict | 否 | 该实例专属的引擎配置(如 max_context_len、mem_fraction_static),默认取全局 --genrm-engine-config。 |
sampling_config | dict | 否 | 该实例专属的采样参数,默认取全局 --genrm-sampling-config。 |
旧的 --genrm-model-path 配置方式完全不受影响——内部会自动归一化为一个使用保留 key "__default__" 的单实例 --genrm-instances 配置,因此没有携带 route_key 的请求(或从未切换到 --genrm-instances 的脚本)行为和以前完全一致。
示例:两个评判模型,拆分 bundle
以下配置对应 run-qwen3-4B-8xgpu-dual-genrm-split.sh:一个 8 卡 Split 布局,rollout 占 4 卡,两个 GenRM 实例共用剩下 4 卡(各占 2 卡):
python3 relax/entrypoints/train.py \
--genrm-instances '{
"quality": {"model_path": "/path/to/quality-judge", "num_gpus": 2, "num_gpus_per_engine": 2,
"sampling_config": {"temperature": 0.1, "max_response_len": 64}},
"safety": {"model_path": "/path/to/safety-judge", "num_gpus": 2, "num_gpus_per_engine": 2,
"sampling_config": {"temperature": 0.1, "max_response_len": 32,
"chat_template_kwargs": {"enable_thinking": false}}}
}' \
--rollout-num-gpus 4 \
--resource '{"actor": [1, 8], "rollout": [1, 4], "genrm": [1, 4]}' \
--colocate \
--custom-rm-path examples.generate_reward_model.reward_dual_genrm_quality_safety.reward_func \
--reward-key score--resource 中的 "genrm" 需要等于所有实例 num_gpus 之和(此处 2 + 2 = 4);除此之外,配置 一节里 Split / Shared 的 bundle 判定公式对这个总量同样适用,无需另外理解。
需要简洁作答的评判模型,记得关闭「思考」模式
如果评判模型默认会先输出一段较长的思考过程再给出结论(推理增强模型的常见习惯),reward 函数里宽松的解析逻辑可能找不到干净的 1/0,导致悄悄地把所有样本打成 0 分。可以在该实例的 sampling_config 里加上 "chat_template_kwargs": {"enable_thinking": false} 强制它直接给结论,如上面 safety 实例的写法。
调用指定实例:route_key
在 reward 函数(或任何持有 GenRMClient 的代码)里,传入 route_key 即可选择由哪个实例来响应这次调用:
from relax.utils.genrm_client import get_genrm_client
genrm_client = get_genrm_client()
quality_response = await genrm_client.generate(
messages=[{"role": "user", "content": "Judge correctness..."}],
route_key="quality",
)
safety_response = await genrm_client.generate(
messages=[{"role": "user", "content": "Judge safety..."}],
route_key="safety",
)在 HTTP 层,route_key 只是 /generate 请求体里多出的一个字段:
curl -X POST http://localhost:8000/genrm/generate \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Judge safety..."}],
"route_key": "safety"
}'不传 route_key 会路由到唯一的 "__default__" 实例——这正是服务用旧的 --genrm-model-path 启动时的行为。如果传的 route_key 从未在 --genrm-instances 中声明过,请求会直接失败,报出清晰的「未注册的 GenRM 实例」错误,而不是静默路由错。
启用 --genrm-instances 之后,/health 与 /metrics 接口会返回每个实例各自的明细:
curl http://localhost:8000/genrm/health{
"status": "healthy",
"service": "genrm",
"instances": {
"quality": {"status": "healthy"},
"safety": {"status": "healthy"}
}
}Agentic 场景示例:按模块路由
在 agentic rollout 中,轨迹的不同步骤通常已经在 metadata 里携带了「这一步是哪个模块产生的」这类信息。自定义 reward 函数可以直接读取这个字段并映射成 route_key,而不必像 reward_dual_genrm_quality_safety.py 那样硬编码两个固定的调用:
# --genrm-instances '{"planner_judge": {...}, "tool_call_judge": {...}, "final_answer_judge": {...}}'
MODULE_TO_ROUTE_KEY = {
"plan": "planner_judge",
"tool_call": "tool_call_judge",
"final_answer": "final_answer_judge",
}
async def agentic_reward_func(args, sample, **kwargs) -> dict:
genrm_client = get_genrm_client()
per_step_scores = []
for step in sample.metadata["trajectory_steps"]:
route_key = MODULE_TO_ROUTE_KEY[step["module"]]
judge_response = await genrm_client.generate(
messages=_format_step_messages(step),
route_key=route_key,
)
per_step_scores.append(_parse_judgement(judge_response))
# 如何聚合完全由你决定:加权平均、取 min() 做「木桶效应」惩罚,
# 或者只给最后一步打分、把前面的步骤当作过程奖励——
# Relax 本身不预设任何聚合策略。
return {"score": sum(per_step_scores) / len(per_step_scores)}按流水线中各模块的角色来分配 GPU 预算(高频的 tool-call 检查用小而快的模型,每条轨迹只跑一次的最终答案判断用更大的模型),并确保所有实例 num_gpus 之和仍满足 配置 一节中的 Split/Shared bundle 等式。
脚本详解
两个脚本共享相同的结构,以下是关键配置组的详细说明:
奖励配置
启用 GenRM 的关键设置是 --rm-type dapo-genrm,它将奖励计算路由到 relax/engine/rewards/dapo_genrm.py 中的 async_compute_score_genrm() 函数。核心实现如下:
DAPO_GENRM_PROMPT_TEMPLATE = """Below are two answers to a question. ...
[Question]: {question}
[Standard Answer]: {ground_truth}
[Model_answer] : {predict_str}
Judgement:"""
def _format_messages(question, ground_truth, predict_str):
# 提取 "Answer:" 之后的部分,若无则截取末尾 300 字符
if "Answer:" in predict_str:
predict_str = predict_str.split("Answer:")[-1]
else:
predict_str = predict_str[-300:]
prompt = DAPO_GENRM_PROMPT_TEMPLATE.format(
question=question, ground_truth=ground_truth, predict_str=predict_str,
)
return [{"role": "user", "content": prompt}]
async def async_compute_score_genrm(args, sample) -> dict:
genrm_client = get_genrm_client() # 单例 HTTP 客户端
question = sample.metadata.get("question", "")
ground_truth = sample.metadata.get("label", "")
messages = _format_messages(question, ground_truth, sample.response)
response = await genrm_client.generate(messages) # 调用 GenRM 服务
prediction = response.strip()
# 严格相等:只有精确的 "1" 才产生正分
score = 1.0 if prediction == "1" else 0.0
return {"score": score, "acc": int(score), "pred": prediction}ROLLOUT_ARGS=(
--rm-type dapo-genrm # 使用 GenRM 进行奖励评分
--reward-key score # 输出字典中的奖励键
--n-samples-per-prompt 8 # 每个 prompt 生成 8 个响应
--rollout-max-response-len 8192
--rollout-temperature 1
)训练配置
两个脚本均使用 GRPO 算法,超参数如下:
GRPO_ARGS=(
--advantage-estimator grpo
--use-kl-loss
--kl-loss-coef 0.00
--kl-loss-type low_var_kl
--eps-clip 0.2
--eps-clip-high 0.28
--use-tis # 截断重要性采样
)
OPTIMIZER_ARGS=(
--optimizer adam
--lr 1e-6
--lr-decay-style constant
--weight-decay 0.1
)GenRM 服务配置
GenRM 模型和引擎在 ray job submit 级别进行配置:
--genrm-model-path ${MODEL_DIR}/Qwen3-VL-30B-A3B-Instruct/ \
--genrm-num-gpus-per-engine 1 \
--genrm-engine-config '{"max_context_len": 10240}' \
--genrm-sampling-config '{"temperature": 0.1, "top_p": 1.0, "top_k": -1, "max_response_len": 1024}'提示
建议为 GenRM 使用较低的温度(如 0.1),以产生确定性的评估结果。较高的温度会引入评估方差。
使用示例
直接调用 GenRM API
curl -X POST http://localhost:8000/genrm/generate \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "Evaluate the answer consistency..."}
]
}'响应:
{
"response": "1"
}在 Python 中使用 GenRMClient
from relax.utils.genrm_client import get_genrm_client
# 获取单例客户端(避免每次请求创建新客户端的开销)
client = get_genrm_client()
# 异步生成
response = await client.generate(
messages=[{"role": "user", "content": "Evaluate..."}],
sampling_params={"temperature": 0.2},
)
print(response) # "1" 或 "0"最佳实践
- 优先使用 colocate 模式:在 colocate 模式下,GenRM 的 GPU 在不进行评估时会卸载回训练,全部 GPU 都参与梯度计算。比 async 模式的 GPU 利用率更高(async 模式下 GenRM 的 GPU 在训练阶段处于闲置)。
- 选对 colocate 子模式:
- GenRM 较小、能放在部分 GPU 上时(如 4B reward model 用 4 GPU),用 Split。
- GenRM 较大、需要全集群 TP 时(如 30B MoE 用 TP=8),用 Shared。Shared 模式还能避免「GenRM 太大装不下 4 GPU、Rollout 又被挤压」的两难。
- Shared 模式下谨慎设置
mem_fraction_static:单卡上各引擎之和 ≤ 0.9。常用起点:rollout 0.6、genrm 0.3。 - 设置合适的上下文长度:引擎配置中的
max_context_len应能容纳最长的 prompt + 响应组合。 - 使用低采样温度:温度 0.1 可产生确定性的评估结果;仅在需要评估多样性时提高。
- 监控健康状态:定期检查
/health端点,确保 GenRM 引擎正常运行。 - 按模型大小分配 GPU:大型 GenRM 模型(如 30B)建议 shared 模式 +
--genrm-num-gpus-per-engine设为整个集群规模。 --genrm-instances中每个实例都要显式写num_gpus:不存在跨实例的自动均分,实例大小配错了是配置问题,不是框架默认行为——按各自要跑的评判模型来定量。- 需要简洁作答的评判模型记得关闭「思考」模式:推理增强模型默认往往会先输出一段较长的思考过程再给结论;如果你的解析逻辑期望拿到干净的
1/0,在该实例的sampling_config里设置"chat_template_kwargs": {"enable_thinking": false}。
故障排除
GenRM 未启用
确保设置了 --genrm-model-path(或 --genrm-instances)参数。只有配置了至少一个实例时,GenRM 才会被激活。
Colocated 模式下资源分配错误
在启用 GenRM 的 colocated 模式下,GPU 分配必须恰好满足以下两种之一:
- Split:
rollout_num_gpus + genrm_num_gpus == actor_total_gpus - Shared:
rollout_num_gpus == genrm_num_gpus == actor_total_gpus
其它组合(例如 rollout + genrm < actor_total,或 rollout < actor_total < rollout + genrm)会在启动阶段被拒绝。请把 --resource 中的 rollout / genrm 调整到这两种合法布局之一。
Shared 模式 OOM 或引擎初始化失败
如果 shared 模式启动时 OOM 或 cuda graph capture 失败,降低一个或两个引擎的 mem_fraction_static,让单卡之和 ≤ 0.9。对大 MoE GenRM,可能还需要禁用 cuda graph 或减小 max_context_len。
引擎初始化超时
如果 GenRM 引擎初始化失败:
- 检查模型路径是否在所有节点上都可以访问
- 确认有足够的 GPU 显存可用
- 查看 Ray 日志中 SGLang 引擎的启动错误信息
GenRM 始终返回 0
DAPO-GenRM 奖励函数使用严格相等来解析响应 — 只有精确的 "1" 字符串才会产生正分。如果 GenRM 模型输出了其他内容(如 "1."、"Yes" 或多行文本),分数将为 0。请验证 GenRM 模型和 prompt 模板能产生干净的 "1" / "0" 输出。如果评判模型是推理增强模型,检查它是否在给结论前先输出了 <think> 思考轨迹——见 多实例 GenRM 一节里的「思考模式」提示。
--genrm-instances:报错 "missing required key 'num_gpus'"
--genrm-instances 中的每个实例都必须显式声明自己的 num_gpus——不存在跨实例的隐式均分(与 Relax 里其他一些多教师调度逻辑不同)。给对应实例的配置补上 "num_gpus": <n> 即可。
报错 "No GenRM instance registered for route_key=..."
reward 函数(或直接发 HTTP 请求的调用方)传入的 route_key 没有匹配到 --genrm-instances 里的任何一个 key。检查是否写错了字符串,或者确认服务确实是用 --genrm-instances 启动的,而不是旧的 --genrm-model-path(后者只暴露 "__default__" 这一个 key——对单实例部署传任何其他 route_key 都会以同样的方式报错)。
文件结构
examples/generate_reward_model/
├── README.md # 示例概述 + split-vs-defer 选择指南
├── post_process_genrm_swap.py # defer 脚本使用的 custom hook(sleep-wake swap + 批量打分)
├── run-qwen3-4B-8xgpu-colocated.sh # 4B colocate 模式
├── run-qwen3-4B-8xgpu-async.sh # 4B fully async 模式
├── run-qwen35-35B-A3B-16xgpu-genrm-397B-split.sh # 35B + 397B,split-bundle inline reward
├── run-qwen35-35B-A3B-16xgpu-genrm-397B-defer.sh # 35B + 397B,shared-bundle 两阶段 swap
├── run-qwen3-4B-8xgpu-dual-genrm-split.sh # 4B policy + 两个 GenRM 实例(--genrm-instances),split-bundle
└── reward_dual_genrm_quality_safety.py # 通过 route_key 路由到两个 GenRM 实例的自定义 reward