> ## Documentation Index
> Fetch the complete documentation index at: https://starforge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 用 verl 训练

> 必填的 --model / --train-data、Hydra 绑定、方法与 estimator、可选的实验 main.py

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf new my-verl --method verl/grpo --framework-version 0.9.0
sf submit my-verl --profile h200:8 \
  --model Qwen/Qwen2.5-7B --train-data data/train.parquet
```

字节 verl 作为一等运行时，catalog 版本 **0.9.0**。方法有：`verl/grpo`、`dapo`、`ppo`、
`rloo`、`reinforce-baseline`、`remax`、`distillation`、`sft`。

和 NeMo-RL 不同，verl 提交时需要 `--model` 和 `--train-data`。可观测性是 `platform`。

<Info>
  verl 镜像是部署侧的产物（`FORGE_IMAGE_VERL` 或 `FORGE_RUNTIME_REGISTRY_FILE`）。
  管理员先发布它，别人才提交得了；没有它时提交失败，错误会点名缺的是运行时，而不是方法。
</Info>

## 创建时写明版本

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf new my-verl --method verl/grpo --framework-version 0.9.0
```

0.9 用统一的 `verl.trainer.main_ppo`（sync V1）；0.8 的 `main_ppo_sync` 已被上游移除，catalog 不再保留旧运行时。

如果存在 `experiments/my-verl/main.py`，就用这个文件当入口（verl-recipe 风格：子类化 `TaskRunner` / `RayPPOTrainer`）。只改奖励可以继续用配置里的 `custom_reward_function.path`。

## 模型和数据在提交时必填

和 NeMo-RL 不同，verl 绑定要显式路径。recipe 映射：

| CLI / JobSpec       | Hydra 键                        |
| ------------------- | ------------------------------ |
| `--model`           | `actor_rollout_ref.model.path` |
| `--train-data`      | `data.train_files`             |
| `--validation-data` | `data.val_files`               |

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf submit my-verl --profile h200:8 \
  --model Qwen/Qwen3.5-9B \
  --train-data data/train.parquet \
  --validation-data data/val.parquet
```

平台数据集：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf submit my-verl --profile h200:8 \
  --model Qwen/Qwen3.5-9B \
  --train-dataset alice/gsm8k-zh@v2 \
  --train-data train.parquet
```

优先写在配置的 `data.train.dataset`；CLI 只是覆盖。[数据集](/zh-Hans/guides/datasets)。

缺了必填的模型/数据声明，`sf validate` 会在本地失败。

## RL 方法怎么选

这里的 RL 方法跑的都是同一个 `verl.trainer.main_ppo`；recipe 只钉死一件事——`algorithm.adv_estimator`，而它就是方法本身。

| 方法                        | estimator                      | 基线来自               | 备注                                                           |
| ------------------------- | ------------------------------ | ------------------ | ------------------------------------------------------------ |
| `verl/grpo`               | `grpo`                         | 组内均值 ÷ 组内标准差       | 需要 `rollout.n ≥ 2`                                           |
| `verl/rloo`               | `rloo`                         | **同题其他样本**的均值      | 无偏；不除标准差。`rollout.n ≥ 2`                                     |
| `verl/reinforce-baseline` | `reinforce_plus_plus_baseline` | 组内均值 + 全局 batch 白化 | 与 `openrlhf/reinforce-baseline` 同一 estimator。`rollout.n ≥ 2` |
| `verl/remax`              | `remax`                        | 模型自己的贪心解           | `rollout.n = 1`，每步多一遍贪心生成                                    |
| `verl/ppo`                | `gae`                          | 训练出来的 critic       | 多一份同尺寸模型；盯 `train/value_loss`                                |
| `verl/dapo`               | `grpo`                         | 过滤后组内均值            | DAPO 四件套，默认全开                                                |

在 `config.yaml` 里写 `algorithm.adv_estimator` 会在提交时被拒：它是方法的身份，不是旋钮。要换 estimator 就换方法。

loss 变体（GSPO、CISPO、GMPO、clip-cov / kl-cov、GPG）才是旋钮——在上面任一 recipe 上设 `policy_loss_mode`。

## DAPO

`verl/dapo` 是配置，不是自带的 trainer：0.9 的主干 `main_ppo` 已经包含论文里的四件套，所以 recipe 只钉死
`algorithm.filter_groups.enable` 与 `dapo` reward manager，论文取值作为参数默认值下发（裁剪 0.2/0.28、
`token-mean`、4096 token 超长软区、惩罚系数 1.0、每题 16 条、两条路径的 KL 全关）。

提交前有两件事要知道：

* 动态采样是在 **rollout 阶段**按 `filter_metric` 判定一组的，所以那时奖励必须已经算出来：要么用规则奖励函数，
  要么让奖励模型独占一个资源池。共卡的奖励模型是在采样之后才打分的，上游会直接 assert。
* 每步耗时天然是波动的——合格的组不够就继续补采。想要「DAPO 去掉动态采样」那一档，用 `verl/grpo` 配
  `clip_ratio_high=0.28` 和 `loss_agg_mode=token-mean`。

## rollout 校正（TIS / IcePop）

即使权重完全一致，rollout 引擎（vLLM，bf16）和训练器（FSDP，fp32）也不是同一个策略；rollout 变旧或被补采
会把这个差距进一步拉大。每个 verl RL 方法都暴露了校正开关：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf submit my-verl --profile h200:8 \
  --set rollout_calculate_log_probs=true \
  --set rollout_is=sequence \
  --set rollout_is_threshold=2.0     # 或 0.5_5.0 走 IcePop（区间外置零，而不是截断）
```

`rollout_calculate_log_probs` 不是可选项：没有 rollout 侧自己的 log-prob，就没有可比对的行为策略；平台会在编译期
直接拒绝这种组合，而不是让它悄悄不生效。`rollout_is` 不设时只出 mismatch 指标、不做校正——新环境上先这么跑一轮
是合理的第一步。

## On-policy 蒸馏要两个资源池

`verl/distillation` 的教师推理服务跑在自己的 Ray placement group 里，是**叠加**在训练池之上申请的。
所以作业要声明两个资源池，并把 `teacher` 角色映射到第二个：

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# spec.resources
pools:
  - {name: train, series: h100, nodes: 1, gpus_per_node: 8}
  - {name: teach, series: h100, nodes: 1, gpus_per_node: 8}
roles: {actor: train, rollout: train, teacher: teach}
```

平台用学生池填 `trainer.nnodes`，用教师池填 `distillation.nnodes`。只给一个池的提交会在编译期被拒，
而不是挂在等资源上。多池作业只支持 kuberay 与 slurm 后端。

## 拓扑

adapter 用 `FORGE_CLUSTER_*` 写 `trainer.nnodes` / `trainer.n_gpus_per_node`（有教师池时用主池的形状）。

## 导出 / 评测

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf export my-verl --checkpoint <path> --checkpoint-format verl-fsdp
# 还有：verl-megatron
```

SFT 的 `sf eval` 可以把验证样本挂回训练 run（带 `--run-id`）。见[流水线](/zh-Hans/guides/pipelines)。
