Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cicd-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ jobs:
for file in $(uv run --group dev pyrefly check $(git ls-files 'nemo_rl/**/*.py' 'examples/**/*.py' 'docs/*.py' 'tools/**/*.py') --output-format json | jq -r --slurpfile all_files <(git ls-files 'nemo_rl/**/*.py' 'examples/**/*.py' 'docs/*.py' 'tools/**/*.py' | jq -R -s 'split("\n")[:-1]') --arg pwd "$(pwd)/" '(.errors | group_by(.path) | map({(.[0].path | sub($pwd; "")): length}) | add // {}) as $error_counts | $all_files[0][] | . as $file | if ($error_counts[$file] // 0) == 0 then $file else empty end'); do
if ! fgrep -q "$file" pyrefly.toml; then
echo "File $file has zero errors but is not in pyrefly.toml in the 'project-includes' list. Please add it to this whitelist."
((missing_count++))
missing_count=$((missing_count + 1))
fi
done

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,8 @@ Refer to `examples/configs/evals/eval.yaml` for a full list of parameters that c

For detailed instructions on how to set up and launch NeMo RL on Slurm or Kubernetes clusters, please refer to the dedicated [Set Up Clusters](docs/cluster.md) documentation.

On CSCS Clariden (GH200), start instead at the [Apertus quickstart](docs/apertus-quickstart.md) — clone-and-run against the shared prebuilt images, with Slurm submission details in `infra/slurm/cscs/README.md`.

## Tips and Tricks
- If you forget to initialize the NeMo and Megatron submodules when cloning the NeMo-RL repository, you may run into an error like this:

Expand Down
3 changes: 2 additions & 1 deletion docs/apertus-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
How to reproduce online GRPO post-training for Apertus 1.5 8B on a CSCS GH200 node from a clean checkout.
This branch runs on the **stock `nvcr.io/nvidia/nemo-rl:v0.7.0` image** via `uv run --locked` — no custom image build.
For the architecture gotchas behind the gates here, see [apertus-traps-and-invariants.md](apertus-traps-and-invariants.md); for Slurm submission details, see `infra/slurm/cscs/README.md` in the repo.
The faster vLLM 0.25.1 stack is also clone-and-run: a certified prebuilt image is shared under `MLLM/containers/` and the checkout ships its EDF (`docker/nemo_rl_vllm0251.toml`) — see the "Custom vLLM 0.25.1 GH200 image" section of the Slurm README.

## Prerequisites

Expand Down Expand Up @@ -34,7 +35,7 @@ sbatch infra/slurm/cscs/probe_grpo_fixgate.slurm
> Subsequent submissions start in minutes and may run concurrently.
> The async variant is `sbatch infra/slurm/cscs/probe_grpo_async.slurm` (same KL gate, 2+2 GPU split).

This runs 3 steps of colocated online GRPO on one node (4 GPUs, TP2/PP1) against `examples/configs/recipes/llm/probe-grpo-apertus1p5-8b-1n4g-megatron.yaml`.
This runs 3 steps of colocated online GRPO on one node (4 GPUs, TP2/PP1) against `examples/configs/recipes/llm/grpo-apertus1p5-8b-1n4g-megatron-probe.yaml`.

**Expected:** every step prints `Generation KL Error: 0.0003` — the train↔generate logprob-agreement gate — and the run completes all 3 steps with no OOM.
A KL above ~0.002 means the generation path regressed; start from the traps page.
Expand Down
2 changes: 1 addition & 1 deletion docs/apertus-traps-and-invariants.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Short resume tests: keep the config byte-identical and kill externally.
```bash
# 1. step-0 refit self-diff: NRL_DEBUG_REFIT_SELFDIFF_DIR=... run_grpo (debug recipe)
# → expect 0 tensors changed
# 2. GRPO probe (probe-grpo-apertus1p5-8b-1n4g-megatron.yaml, vllm util 0.40)
# 2. GRPO probe (grpo-apertus1p5-8b-1n4g-megatron-probe.yaml, vllm util 0.40)
# → expect Generation KL Error < 0.002
# 3. online-DPO smoke (infra/slurm/cscs/probe_nemo_rl_dpo_megatron_apertus.slurm)
# → expect step-1 preference_loss ≈ 0.6931
Expand Down
2 changes: 1 addition & 1 deletion docs/apertus-xielu.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ snapshot lines across runs; use the per-step `generation:` phase timers.
visible in production (traps page, item 1).
- **Re-enabling** (should a future measured regime justify it): the kernel-free
default lives as `policy.generation.vllm_cfg.env_vars.PYTHONPATH: ""` in the recipe
family root (`probe-grpo-apertus1p5-8b-1n4g-megatron.yaml`), so it holds on every
family root (`grpo-apertus1p5-8b-1n4g-megatron-probe.yaml`), so it holds on every
launcher; override it per-run by exporting `VLLM_XIELU_SITE=<site>` to the fixgate
(what `bench/xielu_ab.slurm`'s kernel arm does), purge compile caches, and re-run
the paired same-node A/B before believing any number.
2 changes: 1 addition & 1 deletion docs/design-docs/apertus-omni-grpo-rl.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ gold, so the normalized/symbolic comparator carries it.)
`{prompt_token_ids, ground_truth, answer_variants, task_name}`.
2. `nemo_rl/data/processors.py` — add `mmididx_grpo_data_processor` + register in `PROCESSOR_REGISTRY`.
3. `nemo_rl/environments/single_turn_verifier_environment.py` — the verifier env (§3).
4. `examples/configs/recipes/llm/probe-grpo-apertus-omni-reasoning-alp.yaml` — inherits the
4. `examples/configs/recipes/llm/grpo-apertus-omni-reasoning-alp-smoke.yaml` — inherits the
reasoning-ALP base; binds the new dataset + `single_turn_verifier` env + omni ckpt/tokenizer +
`stop_token_ids` + ALP.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
# Apertus-1.5 8B DPO on MaxMin_Tr_3600-Filtered-Decontaminated (full set).
# Hyperparameters IDENTICAL to swiss-ai/posttraining reference (generate_submit.py
# + trainers/preference.py + train-preference.yaml defaults, apertus-1.5-alignment):
# loss: dpo, beta 25 length-normalized (== preference_average_log_probs), lr 1e-6,
# adamw (0.9/0.999/1e-8, wd 0), warmup_ratio 0.1, linear decay to 0, clip 20,
# gbs 128, 1 epoch, seed 5315, bf16, full train set, no holdout.
# Known non-identical (cross-framework, unavoidable): data order (different
# samplers), numerics (ZeRO-2+FA2 vs Megatron TP+TE). Monitoring val (1,024-row
# subset of train) is additive observation; reference ran eval_strategy=no.
defaults: ../../dpo.yaml
dpo:
max_num_epochs: 1
max_num_steps: 100000
seed: 5315
val_period: 200
val_batches: 8
val_global_batch_size: 128
val_at_start: false
sft_loss_weight: 0.0
preference_loss_weight: 1.0
reference_policy_kl_penalty: 25.0
preference_average_log_probs: true
checkpointing:
enabled: true
checkpoint_dir: results/dpo-apertus1p5-8b-maxmin
save_period: 150
keep_top_k: 5
Expand All @@ -31,19 +17,13 @@ data:
train:
dataset_name: BinaryPreferenceDataset
data_path: /capstor/store/cscs/swissai/infra01/datasets/alignment/preference_datasets/MaxMin_Tr_3600-FD-nemo-rl-binpref-full/train.parquet
split: train
validation:
dataset_name: BinaryPreferenceDataset
# DatasetDict root (save_to_disk): the inherited `split: validation` selects
# the split; pointing at the validation/ subdir loads a splitless Dataset
# and the split lookup degenerates into a column lookup and fails.
data_path: /capstor/store/cscs/swissai/infra01/datasets/alignment/preference_datasets/MaxMin_Tr_3600-FD-nemo-rl-binpref
split: validation
policy:
model_name: /capstor/store/cscs/swissai/infra01/apertus_1p5/hf_checkpoints/ap1p5-8b-sft-256k-adam-lr6e-5-constant-128n_4200 # pragma: allowlist secret
tokenizer:
name: /capstor/store/cscs/swissai/infra01/MLLM/tokenizer/Apertus-v1.5-8B-official
train_global_batch_size: 128
train_micro_batch_size: 1
max_total_sequence_length: 4096
max_grad_norm: 20.0
Expand All @@ -52,27 +32,17 @@ policy:
dtensor_cfg:
enabled: false
megatron_cfg:
# Fused final-linear + CE/logprob: never materializes the 266,752-wide
# logits (the largest activation at this vocab; ~0.5 MB/token in bf16).
use_linear_ce_fusion_loss: true
linear_ce_fusion_chunk_size: 256
# Upstream #2833 (f2ab62c20): the fused forward reads output_layer.weight
# without calling output_layer.forward(), which leaves a stale
# param_gather_handle in the distributed-optimizer prefetch chain and can
# assert on a later iteration; disable the overlap while fusion is on.
distributed_data_parallel_config:
overlap_param_gather: false
enabled: true
tensor_model_parallel_size: 2
sequence_parallel: false
bias_activation_fusion: false
optimizer:
lr: 1.0e-6
lr: 1.0e-06
min_lr: 0.0
weight_decay: 0.0
adam_beta1: 0.9
adam_beta2: 0.999
adam_eps: 1.0e-8
scheduler:
lr_warmup_iters: 192
lr_decay_iters: 1921
Expand All @@ -86,4 +56,3 @@ logger:
name: dpo-apertus1p5-8b-maxmin-beta25-lr1e-6
cluster:
gpus_per_node: 4
num_nodes: 1
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
# Online GRPO + Adaptive Length Penalty on Apertus 1.5 8B (omni) — NATIVE env path (NOT NeMo-Gym).
# Train: DeepVision rl_prompt MMIDIDX store (prompt-only, image tokens inline), prompts capped at 2048
# -> ~6k output budget in an 8k context. Reward = gold-set match (single_turn_verifier).
# Val: MATH-500 (text) -> cross-modal reasoning-transfer probe (graded by the math env).
# Base: the NON-GYM megatron probe (inherits grpo_math_8B_megatron; Apertus ckpt + _tools_fixed tokenizer
# + TP=2 + sleep_level=2). No gym, no HTTP rollout server.
defaults: probe-grpo-apertus1p5-8b-1n4g-megatron.yaml

defaults: grpo-apertus1p5-8b-1n4g-megatron-probe.yaml
grpo:
num_prompts_per_step: 64
num_generations_per_prompt: 8 # GRPO group size (K)
max_num_epochs: 1
max_num_steps: 2000 # 1 epoch over the <=2048 subset is ~1488 steps at npp=64
num_generations_per_prompt: 8
max_num_steps: 2000
val_period: 50
val_at_start: true # MATH-500 baseline before any omni RL
val_at_start: true
max_val_samples: 500
reward_shaping:
enabled: true
alp_coef: 1.0
max_response_length: 6000 # must be <= generation.max_new_tokens

max_response_length: 6000
policy:
train_global_batch_size: 512 # = num_prompts_per_step * num_generations_per_prompt
train_global_batch_size: 512
max_total_sequence_length: 8192
tokenizer:
chat_template_kwargs:
enable_thinking: true # for the MATH-500 val render; train omni prompts already bake it in
enable_thinking: true
generation:
max_new_tokens: 6000
stop_token_ids: [2, 68, 72] # Apertus EOS set: </s>, <|assistant_end|>, <|tools_suffix|>
stop_token_ids:
- 2
- 68
- 72
vllm_cfg:
max_model_len: 8192

data:
_override_: true # replace the base math data, don't merge -> must supply all keys grpo reads
_override_: true
max_input_seq_length: ${policy.max_total_sequence_length}
shuffle: true
num_workers: 1
Expand All @@ -51,29 +44,20 @@ data:
task_name: math
env_name: math
default:
prompt_file: examples/prompts/cot.txt # only reaches the text val (omni train is pre-tokenized)
prompt_file: examples/prompts/cot.txt
system_prompt_file: null

env:
single_turn_verifier:
num_workers: 8
math:
num_workers: 8
math_verify_impl: hf_math_verify

cluster:
gpus_per_node: 4
num_nodes: 8 # 8 proven safe; 16 tested ceiling; 32 deadlocks

num_nodes: 8
checkpointing:
enabled: true
checkpoint_dir: results/grpo-apertus-omni-reasoning-alp-8n
save_period: 50
keep_top_k: 5

logger:
wandb_enabled: true
tensorboard_enabled: false
log_dir: logs/grpo-apertus-omni-reasoning-alp-8n
wandb:
project: grpo-apertus
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
# 1-node SMOKE of the omni GRPO+ALP pipeline — shake out integration on real data cheaply
# before the 8-node run: loader reads the DeepVision .bin, gen produces boxed answers, the
# single_turn_verifier grades vs the gold set, ALP shapes, a step completes. Tiny + fast.
defaults: grpo-apertus-omni-reasoning-alp-8n.yaml

grpo:
num_prompts_per_step: 4
num_generations_per_prompt: 4
max_num_steps: 3
val_period: 0
val_at_start: false

policy:
train_global_batch_size: 16 # = num_prompts_per_step * num_generations_per_prompt
train_global_batch_size: 16
generation:
max_new_tokens: 2048 # shorter for a fast smoke (still > typical)

max_new_tokens: 2048
cluster:
gpus_per_node: 4
num_nodes: 1

checkpointing:
enabled: false
logger:
wandb_enabled: false
log_dir: logs/grpo-apertus-omni-smoke
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defaults: grpo-apertus1p5-8b-1n4g-megatron-probe.yaml
loss_fn:
use_importance_sampling_correction: true
grpo:
async_grpo:
enabled: true
policy:
generation:
colocated:
enabled: false
resources:
num_nodes: 1
gpus_per_node: 2
vllm_cfg:
async_engine: true
tensor_parallel_size: 2
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# (SequencePackingFusionLossWrapper, upstream-tested) — the fusion-class
# memory win available to packed GRPO, since linear-CE fusion is asserted
# incompatible with packing. Same gate as the base probe: gen-KL ~0.0003.
defaults: probe-grpo-apertus1p5-8b-1n4g-megatron.yaml
defaults: grpo-apertus1p5-8b-1n4g-megatron-probe.yaml

policy:
sequence_packing:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GRPO + Adaptive Length Penalty (Xiang et al. 2025) — difficulty-aware length penalty:
# reward -= alp_coef * pass_rate * (response_len / max_response_length). Harder prompts penalized less.
defaults: probe-grpo-apertus1p5-8b-1n4g-megatron-gym-reasoning.yaml
defaults: grpo-apertus1p5-8b-1n4g-megatron-probe-gym-reasoning.yaml

grpo:
reward_shaping:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# enable_thinking=true prompts CoT; uses_reasoning_parser stays false (Apertus has no vLLM
# reasoning parser, and math_with_judge's math-verify extracts \boxed{} from the full text).
# Longer sequence length to fit the chain-of-thought.
defaults: probe-grpo-apertus1p5-8b-1n4g-megatron-gym.yaml
defaults: grpo-apertus1p5-8b-1n4g-megatron-probe-gym.yaml

policy:
max_total_sequence_length: 8192
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
# Single-turn NeMo-Gym GRPO smoke for Apertus 1.5 8B (math_with_judge, no judge).
# Inherits the validated colocated probe (Apertus model/tokenizer/megatron/sleep_level=2)
# and overlays the gym wiring: HTTP-served async vLLM + math_with_judge env + simple_agent.
defaults: probe-grpo-apertus1p5-8b-1n4g-megatron.yaml

defaults: grpo-apertus1p5-8b-1n4g-megatron-probe.yaml
grpo:
num_prompts_per_step: 4
num_generations_per_prompt: 4
max_rollout_turns: 1
max_num_steps: 3
val_period: 0
val_at_start: false
max_val_samples: null
normalize_rewards: true
use_leave_one_out_baseline: true
skip_reference_policy_logprobs_calculation: true

loss_fn:
reference_policy_kl_penalty: 0

policy:
train_global_batch_size: 16
generation:
vllm_cfg:
async_engine: true
expose_http_server: true
http_server_serving_chat_kwargs:
chat_template: /capstor/store/cscs/swissai/infra01/apertus_1p5/hf_checkpoints/ap1p5-8b-sft-256k-adam-lr6e-5-constant-128n_4200/chat_template.jinja # pragma: allowlist secret

chat_template: /capstor/store/cscs/swissai/infra01/apertus_1p5/hf_checkpoints/ap1p5-8b-sft-256k-adam-lr6e-5-constant-128n_4200/chat_template.jinja
data:
shuffle: false
num_workers: 0
train:
dataset_name: NemoGymDataset
data_path: examples/nemo_gym/apertus/math_with_judge_example_agent.jsonl # committed smoke fixture; each row carries its own agent_ref
data_path: examples/nemo_gym/apertus/math_with_judge_example_agent.jsonl
repeat: 8
split_validation_size: null
validation:
Expand All @@ -41,14 +27,10 @@ data:
dataset_name: NemoGymDataset
env_name: nemo_gym
prompt_file: null
system_prompt_file: null
processor: nemo_gym_data_processor

env:
should_use_nemo_gym: true
nemo_gym:
# gym binds its servers on random ports in [low, high]; keep this disjoint from the
# Ray worker port range (10002-11000 in the multinode launcher) to avoid bind collisions.
port_range_low: 11001
port_range_high: 20000
config_paths:
Expand All @@ -65,6 +47,5 @@ env:
extra_body:
chat_template_kwargs:
enable_thinking: false

logger:
log_dir: logs/grpo-apertus-gym
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defaults: ../../grpo_math_8B_megatron.yaml
grpo:
num_prompts_per_step: 8
num_generations_per_prompt: 4
max_num_steps: 3
val_period: 0
cot_think_token_ids:
- 32
- 33
data:
train:
dataset_name: DeepScaler
policy:
model_name: /capstor/store/cscs/swissai/infra01/apertus_1p5/hf_checkpoints/ap1p5-8b-sft-256k-adam-lr6e-5-constant-128n_4200 # pragma: allowlist secret
tokenizer:
name: /capstor/store/cscs/swissai/infra01/MLLM/tokenizer/Apertus-v1.5-8B-official
train_global_batch_size: 32
logprob_batch_size: 2
max_total_sequence_length: 2048
megatron_cfg:
tensor_model_parallel_size: 2
pipeline_model_parallel_size: 1
bias_activation_fusion: false
generation:
max_new_tokens: 512
vllm_cfg:
env_vars:
PYTHONPATH: ''
gpu_memory_utilization: 0.55
max_model_len: 2048
sleep_level: 2
logger:
log_dir: logs/grpo-apertus-probe
cluster:
gpus_per_node: 4
Loading
Loading