# Hy3 Reinforcement Learning Training English | [简体中文](./README_CN.md) This document describes how to run reinforcement learning training for Hy3 with [verl](https://github.com/volcengine/verl). Training runs on [Megatron-LM](https://github.com/NVIDIA/Megatron-LM); NVIDIA [Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge) (`HYV3Bridge`) converts the HF checkpoint into a Megatron model on the fly at training startup — no offline conversion needed. Rollout runs on [vLLM](https://github.com/vllm-project/vllm). Training script: [`examples/grpo_trainer/run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh) in the verl repository. ## Quick Start ### Environment Recommended image: `verlai/verl:vllm023.dev1` Verified version combination: | Component | Version | | --- | --- | | verl | [220e903](https://github.com/verl-project/verl/commit/220e9039902c6db56860e2afd659803dc34ec005) | | Megatron-Bridge | [df0852c](https://github.com/NVIDIA-NeMo/Megatron-Bridge/commit/df0852cf94c674de07f9f7ef933c484de8aca505) | | transformers | 5.6.0 | | nvidia-modelopt | 0.44.0rc5 | ### Prepare dependencies Clone verl at the verified version, then clone the dependencies into `third_party/` under the repository root: ```bash git clone https://github.com/verl-project/verl.git cd verl git checkout 220e903 mkdir third_party git clone https://github.com/NVIDIA-NeMo/Megatron-Bridge.git third_party/Megatron-Bridge git -C third_party/Megatron-Bridge checkout df0852c git clone https://github.com/Ascend/TransferQueue.git third_party/TransferQueue ``` Create a `runtime_env.yaml` so the Ray runtime env ships the working directory (including `third_party/`) to every worker node and adds the dependencies to `PYTHONPATH`: ```yaml # runtime_env.yaml working_dir: ./ excludes: [ "/.git/", "/third_party/Megatron-Bridge/.git/", "/third_party/TransferQueue/.git/", "**/__pycache__/", ] env_vars: PYTHONPATH: third_party/Megatron-Bridge/src:third_party/TransferQueue ``` ### Prepare the dataset The script defaults to [DAPO-Math-17k](https://huggingface.co/datasets/BytedTsinghua-SIA/DAPO-Math-17k) (train) and [AIME-2024](https://huggingface.co/datasets/BytedTsinghua-SIA/AIME-2024) (validation). Both are already in the format verl expects on HuggingFace — just download and dump to parquet: ```python # prepare_data.py import datasets dapo = datasets.load_dataset("BytedTsinghua-SIA/DAPO-Math-17k", "default")["train"] dapo.to_parquet("DAPO-Math-17k/dapo-math-17k.parquet") aime = datasets.load_dataset("BytedTsinghua-SIA/AIME-2024", "default")["train"] aime.to_parquet("AIME-2024/aime-2024.parquet") ``` ```bash python prepare_data.py # output layout matches the script's default DATA_DIR ``` Place the data under the verl repository root (`DATA_DIR` defaults to `$PWD`), or point elsewhere with `DATA_DIR=/path/to/data`. ### Submit the job Use the HuggingFace checkpoint directly; training data is in parquet format. Point `RAY_ADDRESS` at the cluster head node, then submit with `ray job submit`: ```bash export RAY_ADDRESS=http://: ray job submit --no-wait --runtime-env=runtime_env.yaml -- \ bash examples/grpo_trainer/run_hy_v3_megatron.sh ``` > **Note**: on H20, rollout (vLLM inference) requires at least TP=16 (the script defaults to `ROLLOUT_TP=16`) — the weights of a single instance only fit when sharded across 16 GPUs. ### Key parameters **Hy3 settings** | Parameter | Value | Reason | | --- | --- | --- | | `moe_router_enable_expert_bias` | True | Hy3 routes with a per-expert bias (aux-loss-free) | | `moe_router_bias_update_rate` | 0 | 0 freezes the bias (it still participates in scoring but is never updated) | | `moe_router_load_balancing_type` | none | no auxiliary load-balancing loss | **Training setup** | Parameter | What it does | | --- | --- | | `data.train_batch_size` | prompts sampled per step | | `rollout.n` | responses generated per prompt, i.e. the GRPO group size (the in-group advantage baseline is computed over them), typically 8–16 | | `actor.ppo_mini_batch_size` | samples per actor parameter update | | `actor.optim.lr` | actor learning rate, typically the 1e-6 scale | | `data.max_response_length` | maximum generation length, task-dependent | **Algorithm behavior** | Parameter | What it does | How to set it | | --- | --- | --- | | `algorithm.norm_adv_by_std_in_grpo` | whether advantages are divided by the in-group std | True is original GRPO; False is the [Dr.GRPO](https://arxiv.org/abs/2503.20783) fix, preventing too-easy/too-hard prompts with tiny variance from being amplified | | `actor.clip_ratio_low` / `clip_ratio_high` | PPO trust-region bounds | a slightly relaxed upper bound (clip-higher, e.g. 0.2/0.28) gives low-probability tokens more room to rise, mitigating entropy collapse | | `actor.clip_ratio_c` | [dual-clip](https://arxiv.org/abs/1912.09729) lower-bound constant | caps the penalty on negative-advantage tokens to keep a single step from blowing up the policy | | `actor.kl_loss_coef` | KL-regularization strength toward the reference policy | 0 means KL-free (rely on clipping); add a small value (e.g. 1e-3) if training is unstable | | `algorithm.rollout_correction.rollout_is` / `rollout_is_threshold` | correction for the rollout/training log-prob mismatch (IcePop) | `token` plus lower/upper bounds (e.g. `0.5_4.0`; token weights outside the range are zeroed); recommended whenever the numeric gap between the two engines is non-negligible | | `rollout.temperature` / `top_p` | rollout sampling exploration strength | typically 0.9–1.0; too low a temperature reduces in-group diversity and degrades the GRPO baseline | **Memory and sequence length** Parameters that must move together when extending the sequence length (changing `data.max_response_length` alone is not enough): | Parameter | Coupling | | --- | --- | | `data.max_response_length` | target response length | | `rollout.max_model_len` | vLLM context length, must be ≥ prompt + response | | `actor.ppo_max_token_len_per_gpu` | training-side per-GPU token budget; sequences are split across CP ranks, so each GPU actually holds `(prompt+response)/CP` per sequence — the requirement is budget × CP ≥ prompt + response | | `actor.megatron.context_parallel_size` | scale up proportionally for much longer sequences (activation memory grows linearly with sequence length and is sharded by CP) | For training-side OOM (the error occurs during actor update / log_prob), you can try: 1. Lower `actor.ppo_max_token_len_per_gpu` — dynamic batching packs micro batches against it, so it directly bounds the activation peak; 2. Set `actor.ppo_micro_batch_size_per_gpu` to 1; 3. Lower `ref.log_prob_max_token_len_per_gpu` / `rollout.log_prob_max_token_len_per_gpu`; 4. Raise `actor.megatron.context_parallel_size` (activations are sharded by CP) or `pipeline_model_parallel_size` (fewer layers per stage). Offload and recompute: | Config | Script default | What it does | | --- | --- | --- | | `actor.megatron.param_offload` | True | offloads parameters to CPU while training is idle, freeing GPU memory for rollout | | `actor.megatron.optimizer_offload` | True | offloads optimizer state (fp32 master weights + momenta — the biggest memory consumer) to CPU | | `actor.megatron.grad_offload` | True | offloads gradient buffers to CPU | | `override_transformer_config.recompute_granularity` | full | full activation recomputation: forward stores no activations, backward recomputes them — trades ~30% extra compute for most of the activation memory | | `override_transformer_config.recompute_method` / `recompute_num_layers` | uniform / 1 | recompute uniformly at 1-layer granularity — the finest granularity, lowest peak | Both substantially relieve OOM: offload keeps parameters/optimizer state/gradients out of GPU memory at the cost of per-step CPU↔GPU transfers, and recompute keeps activations out of GPU memory at the cost of one extra forward pass during backward. ## Customizing Your Training ### Using your own dataset verl reads parquet data where each row contains 5 fields (see the verl docs: [Prepare Data](https://verl.readthedocs.io/en/latest/preparation/prepare_data.html)): ```python { "data_source": "my_dataset", # dataset name; the RewardManager uses it to index the scoring function "prompt": [ # HuggingFace chat template format; the tokenizer renders and tokenizes it {"role": "user", "content": "1+1=?"} ], "ability": "math", # task category "reward_model": { "style": "rule", "ground_truth": "2" # reference answer; the reward function's scoring logic must align with its format }, "extra_info": {"split": "train", "index": 0}, # metadata } ``` Write a preprocessing script that converts your data into this format (verl's [`examples/data_preprocess/`](https://github.com/verl-project/verl/tree/main/examples/data_preprocess) ships a dozen ready-to-adapt templates — GSM8K, MATH, etc.), save as parquet, then point the script at your files via environment variables: ```bash TRAIN_FILES=/path/to/my_train.parquet \ VAL_FILES=/path/to/my_val.parquet \ bash examples/grpo_trainer/run_hy_v3_megatron.sh ``` **Reward function**: math-style tasks with rule-verifiable answers can reuse the default DAPO reward as-is (it routes to a built-in scoring function by `data_source`); other tasks need a custom reward function, specified via `custom_reward_function.path` (see the verl docs: [Implement Reward Function](https://verl.readthedocs.io/en/latest/preparation/reward_function.html)). ### Switching the RL algorithm The script defaults to GRPO, but the algorithm layer is decoupled from the model layer, so switching algorithms requires no changes to the Hy3-specific configuration. **Switch between built-in algorithms (one config key)**: verl ships a dozen advantage estimators, selected via `algorithm.adv_estimator` — options include `gae` (PPO), `grpo`, `rloo`, `remax`, `reinforce_plus_plus`, `opo`, `gpg`, etc. (full list in `AdvantageEstimator` in [`core_algos.py`](https://github.com/verl-project/verl/blob/main/verl/trainer/ppo/core_algos.py)); the policy loss is selected via `actor_rollout_ref.actor.policy_loss.loss_mode` (`vanilla`, `gspo`, `cispo`, `clip_cov`, etc.). See the verl docs for each algorithm's theory and configuration: [PPO](https://verl.readthedocs.io/en/latest/algo/ppo.html) / [GRPO](https://verl.readthedocs.io/en/latest/algo/grpo.html) / [DAPO](https://verl.readthedocs.io/en/latest/algo/dapo.html). **Training Hy3 with a recipe**: full-pipeline algorithms (e.g. DAPO with dynamic sampling) live as standalone implementations under verl's [`recipe/`](https://github.com/verl-project/verl/tree/main/recipe) directory, each with its own launch script. When pointing a recipe at Hy3, carry over the following required Hy3 settings into the recipe's launch script: ```bash # Required Hy3 settings (apply to any recipe) actor_rollout_ref.model.path=/path/to/Hy3 actor_rollout_ref.model.trust_remote_code=True data.trust_remote_code=True actor_rollout_ref.actor.megatron.use_mbridge=True actor_rollout_ref.actor.megatron.vanilla_mbridge=False +actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_enable_expert_bias=True +actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_bias_update_rate=0 +actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_load_balancing_type=none +actor_rollout_ref.actor.megatron.override_transformer_config.moe_grouped_gemm=True ``` For implementing entirely new algorithms, see the verl docs: [Extend to other RL algorithms](https://verl.readthedocs.io/en/latest/advance/dpo_extension.html). ## Results We launched Hy3 GRPO training on 128 H20 GPUs (16 nodes × 8) with [`run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh): math reasoning on the DAPO dataset at 8192 max response length, with PP/CP/EP parallelism plus full offload, and BF16 rollout + BF16 training. Exact values used in this run: batch 128 prompts × 16 samples (2048 trajectories/step), `ppo_mini_batch_size=128` (one update per step), lr 1e-6, clip 0.2/0.28 (dual-clip c=10.0), KL-free, DAPO overlong buffer (len 4096 / penalty 1.0), IcePop threshold `0.5_4.0`, sampling temperature 0.9 / top_p 1.0. The training dynamics are stable: the rollout/training log-prob drift (`rollout_probs_diff`) stays below 0.015 throughout, and both reward and the AIME validation score grow steadily over the run. ![Hy3 RL training curves](../assets/rl-training.png) ## Acknowledgements We thank the Tencent Hunyuan team for their support on model training and engineering infrastructure, as well as the [verl](https://github.com/volcengine/verl), [Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge), [Megatron-LM](https://github.com/NVIDIA/Megatron-LM), and [vLLM](https://github.com/vllm-project/vllm) communities for their help.