From bb8e1d39047aba354fe856aae0bc7e65642e1005 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Mon, 20 Jul 2026 18:18:28 -0700 Subject: [PATCH 01/10] Add single-node Qwen3 DAPO math example --- pyproject.toml | 3 + torchtitan/experiments/__init__.py | 1 + .../rl/examples/dapo_math/README.md | 73 +++++++++ .../rl/examples/dapo_math/__init__.py | 27 ++++ .../rl/examples/dapo_math/config_registry.py | 150 ++++++++++++++++++ .../experiments/rl/examples/dapo_math/data.py | 130 +++++++++++++++ .../experiments/rl/examples/dapo_math/env.py | 41 +++++ .../rl/examples/dapo_math/rollouter.py | 51 ++++++ .../rl/examples/dapo_math/rubric.py | 67 ++++++++ .../rl/examples/dapo_math/run_train.sh | 47 ++++++ .../experiments/rl/tests/test_dapo_math.py | 107 +++++++++++++ 11 files changed, 697 insertions(+) create mode 100644 torchtitan/experiments/rl/examples/dapo_math/README.md create mode 100644 torchtitan/experiments/rl/examples/dapo_math/__init__.py create mode 100644 torchtitan/experiments/rl/examples/dapo_math/config_registry.py create mode 100644 torchtitan/experiments/rl/examples/dapo_math/data.py create mode 100644 torchtitan/experiments/rl/examples/dapo_math/env.py create mode 100644 torchtitan/experiments/rl/examples/dapo_math/rollouter.py create mode 100644 torchtitan/experiments/rl/examples/dapo_math/rubric.py create mode 100755 torchtitan/experiments/rl/examples/dapo_math/run_train.sh create mode 100644 torchtitan/experiments/rl/tests/test_dapo_math.py diff --git a/pyproject.toml b/pyproject.toml index c45098ef2c6..341016b703f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,9 @@ dependencies = [ "tokenizers>=0.15.0", "safetensors", + # Rule-based verification for math RL examples + "math-verify==0.9.0", + # Miscellaneous "tyro", "tensorboard", diff --git a/torchtitan/experiments/__init__.py b/torchtitan/experiments/__init__.py index 2e9918c7f1d..c430ba208f4 100644 --- a/torchtitan/experiments/__init__.py +++ b/torchtitan/experiments/__init__.py @@ -17,6 +17,7 @@ # RL examples own a per-example config_registry under rl/examples/; # listed here so `--module ` resolves (see ConfigManager). "alphabet_sort", + "dapo_math", "search_r1", ] ) diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md new file mode 100644 index 00000000000..94ba885a7f8 --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -0,0 +1,73 @@ +# Qwen3-4B-Base on DAPO-Math + +This example trains Qwen3-4B-Base on verifiable math problems using DAPO loss. It fits one eight-GPU node by assigning two GPUs to the trainer and one GPU to each of six independent generators. + +## Configuration + +```text +model: Qwen3-4B-Base +trainer: TP=2, fp32 language-model head +generators: 6 replicas, TP=1 +rollouts: 8 prompts x 16 completions per optimizer step +prompt budget: 2,048 tokens +response budget: 8,192 tokens +packing length: 10,240 tokens +off-policy limit: 4 trainer versions +learning rate: 1e-6, constant +``` + +Training uses the 12,643-row [filtered DAPO-Math dataset](https://huggingface.co/datasets/hamishivi/DAPO-Math-17k-Processed_filtered). Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). Math-Verify assigns a binary reward from the final `Answer:` expression. + +The default configuration runs 1,000 optimizer steps, or 128,000 sampled completions. `max_offpolicy_steps=4` bounds policy lag and permits 40 prompt groups in the active rollout buffer. + +## Setup + +Follow the [RL environment setup](../../README.md), then download the base checkpoint: + +```bash +python scripts/download_hf_assets.py \ + --repo_id Qwen/Qwen3-4B-Base \ + --local_dir torchtitan/experiments/rl/example_checkpoint \ + --all +``` + +## Run + +The launcher uses the TP=2 trainer configuration and defaults to 150 steps: + +```bash +./torchtitan/experiments/rl/examples/dapo_math/run_train.sh +``` + +Override the duration or output directory with environment variables: + +```bash +TRAINING_STEPS=10 \ +DUMP_FOLDER=outputs/rl/qwen3_4b_dapo_math_smoke \ +./torchtitan/experiments/rl/examples/dapo_math/run_train.sh +``` + +The replicated DP=2 trainer configuration is also available: + +```bash +python -m torchtitan.experiments.rl.train \ + --module dapo_math \ + --config rl_dapo_qwen3_4b_math_8k +``` + +## 150-step result + +The single-node TP=2 trial completed optimizer steps 0 through 149 in 1 hour 46 minutes. This trial used the earlier 7,168-token response and 9,216-token packing limits; the runnable configuration above restores the full 8,192-token response budget. + +```text +metric first 10 steps last 10 steps +rollout reward, mean 0.067 0.401 +rollout total length, mean 988 tokens 3,284 tokens +rollout truncation rate, mean 1.75% 14.55% +``` + +The final recorded step had mean reward `0.302`, mean total length `3,999`, and truncation rate `18.75%`. The maximum observed mean reward was `0.632`; the maximum per-step truncation rate was `30.73%`. Initial AIME validation scored `0/30` before training. + + + + diff --git a/torchtitan/experiments/rl/examples/dapo_math/__init__.py b/torchtitan/experiments/rl/examples/dapo_math/__init__.py new file mode 100644 index 00000000000..b337a213f9e --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from torchtitan.experiments.rl.examples.dapo_math.data import ( + AIME2025Dataset, + DapoMathDataset, + DapoMathSample, +) +from torchtitan.experiments.rl.examples.dapo_math.env import DapoMathEnv +from torchtitan.experiments.rl.examples.dapo_math.rollouter import DapoMathRollouter +from torchtitan.experiments.rl.examples.dapo_math.rubric import ( + RewardMathVerify, + score_math_response, +) + +__all__ = [ + "AIME2025Dataset", + "DapoMathDataset", + "DapoMathEnv", + "DapoMathRollouter", + "DapoMathSample", + "RewardMathVerify", + "score_math_response", +] diff --git a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py new file mode 100644 index 00000000000..910aa7ce5a4 --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py @@ -0,0 +1,150 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +"""Single-node Qwen3-4B-Base DAPO-Math recipes.""" + +from __future__ import annotations + +from torchtitan.components.checkpoint import CheckpointManager +from torchtitan.components.loss import ChunkedLossWrapper +from torchtitan.components.lr_scheduler import LRSchedulersContainer +from torchtitan.components.optimizer import default_adamw +from torchtitan.config import CompileConfig, ParallelismConfig, TrainingConfig +from torchtitan.experiments.rl.actors.generator import ( + SamplingConfig, + VLLMCudagraphConfig, + VLLMGenerator, +) +from torchtitan.experiments.rl.actors.trainer import PolicyTrainer +from torchtitan.experiments.rl.components.batcher import BatchConfig, Batcher +from torchtitan.experiments.rl.controller import ( + AsyncLoopConfig, + Controller, + ValidationConfig, +) +from torchtitan.experiments.rl.examples.dapo_math.rollouter import DapoMathRollouter +from torchtitan.experiments.rl.losses import DAPOLoss +from torchtitan.experiments.rl.models.cast_linear import LMHeadCastConverter +from torchtitan.experiments.rl.models.vllm_registry import InferenceParallelismConfig +from torchtitan.experiments.rl.observability.metrics import MetricsProcessor +from torchtitan.experiments.rl.renderer import RendererConfig +from torchtitan.experiments.rl.routing.inter_generator_router import ( + InterGeneratorRouter, +) +from torchtitan.experiments.rl.routing.strategies import LeastLoadedRoutingStrategy +from torchtitan.models.qwen3 import model_registry + + +_NUM_TRAINING_STEPS = 1000 + + +def _qwen3_4b_dapo_math_8k_config( + *, trainer_parallelism: ParallelismConfig +) -> Controller.Config: + """Build the 8K DAPO-Math recipe for one trainer parallelism layout.""" + return Controller.Config( + model_spec=model_registry( + "4B", + attn_backend="varlen", + # Compute vocabulary logits in fp32; the rest of the forward uses bf16. + converters=[LMHeadCastConverter.Config()], + ), + hf_assets_path="torchtitan/experiments/rl/example_checkpoint/Qwen3-4B-Base", + dump_folder="outputs/rl/qwen3_4b_dapo_math_8k", + async_loop=AsyncLoopConfig( + num_training_steps=_NUM_TRAINING_STEPS, + num_groups_per_train_step=8, + group_size=16, + # Four versions of policy lag plus the current version allow 40 active groups. + max_offpolicy_steps=4, + validation=ValidationConfig(num_samples=30), + batcher=Batcher.Config( + # The dataset is filtered to 2K prompts; generation contributes up to 8K. + batch=BatchConfig(local_batch_size=1, seq_len=10240), + ), + ), + compile=CompileConfig(enable=True, backend="aot_eager"), + rollouter=DapoMathRollouter.Config(), + renderer=RendererConfig(name="qwen3", enable_thinking=True), + num_generators=6, + generator_router=InterGeneratorRouter.Config( + strategy=LeastLoadedRoutingStrategy.Config() + ), + metrics=MetricsProcessor.Config( + enable_wandb=True, + console_log_keys_validation=[ + "validation_reward/_mean", + "validation_reward/_max", + "validation/response_length/mean", + "timing/validate", + ], + ), + trainer=PolicyTrainer.Config( + optimizer=default_adamw( + lr=1e-6, + betas=(0.9, 0.98), + weight_decay=0.1, + ), + lr_scheduler=LRSchedulersContainer.Config( + warmup_steps=0, + total_steps=_NUM_TRAINING_STEPS, + decay_type="linear", + min_lr_factor=1.0, + ), + training=TrainingConfig(), + parallelism=trainer_parallelism, + checkpoint=CheckpointManager.Config( + enable=True, + initial_load_in_hf=True, + interval=100, + last_save_model_only=False, + keep_latest_k=3, + ), + loss=ChunkedLossWrapper.Config( + num_chunks=8, + loss_fn=DAPOLoss.Config( + ratio_clip_low=0.2, + ratio_clip_high=0.28, + ), + ), + ), + generator=VLLMGenerator.Config( + model_dtype="bfloat16", + parallelism=InferenceParallelismConfig( + data_parallel_degree=1, + tensor_parallel_degree=1, + ), + cudagraph=VLLMCudagraphConfig(enable=True), + checkpoint=CheckpointManager.Config(enable=False), + sampling=SamplingConfig( + temperature=1.0, + top_p=1.0, + max_tokens=8192, + ), + ), + ) + + +def rl_dapo_qwen3_4b_math_8k() -> Controller.Config: + """Use two replicated trainer ranks and six single-GPU generators.""" + return _qwen3_4b_dapo_math_8k_config( + trainer_parallelism=ParallelismConfig( + data_parallel_replicate_degree=2, + data_parallel_shard_degree=1, + tensor_parallel_degree=1, + ) + ) + + +def rl_dapo_qwen3_4b_math_8k_tp2() -> Controller.Config: + """Use one TP=2 trainer mesh and six single-GPU generators.""" + return _qwen3_4b_dapo_math_8k_config( + trainer_parallelism=ParallelismConfig( + data_parallel_replicate_degree=1, + data_parallel_shard_degree=1, + tensor_parallel_degree=2, + ) + ) diff --git a/torchtitan/experiments/rl/examples/dapo_math/data.py b/torchtitan/experiments/rl/examples/dapo_math/data.py new file mode 100644 index 00000000000..5fd9d7656c8 --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/data.py @@ -0,0 +1,130 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +import random +from collections.abc import Iterator +from dataclasses import dataclass + +from datasets import load_dataset + +from torchtitan.config import Configurable + + +_AIME_PROMPT_TEMPLATE = ( + "Solve the following math problem step by step. The last line of your response " + "should be of the form Answer: $Answer (without quotes) where $Answer is the " + "answer to the problem.\n\n" + "{problem}\n\n" + 'Remember to put your answer on its own line after "Answer:".' +) + + +@dataclass(frozen=True, kw_only=True, slots=True) +class DapoMathSample: + """A math prompt paired with its expected final answer.""" + + prompt: str + ground_truth: str + + +class _ShuffledMathDataset(Configurable): + """Provides math problems in shuffled row order.""" + + def __init__( + self, + samples: list[DapoMathSample], + *, + seed: int, + shuffle: bool, + ) -> None: + if not samples: + raise ValueError("math dataset must contain at least one sample") + self._samples = samples + self._rng = random.Random(seed) + self._shuffle = shuffle + self._order = list(range(len(samples))) + if shuffle: + self._rng.shuffle(self._order) + self._position = 0 + + def __iter__(self) -> Iterator[DapoMathSample]: + return self + + def __next__(self) -> DapoMathSample: + if self._position == len(self._order): + if self._shuffle: + self._rng.shuffle(self._order) + self._position = 0 + sample_index = self._order[self._position] + self._position += 1 + return self._samples[sample_index] + + def state_dict(self) -> dict: + """Snapshot row order and position so resume continues the same stream.""" + return { + "rng_state": self._rng.getstate(), + "order": list(self._order), + "position": self._position, + } + + def load_state_dict(self, state_dict: dict) -> None: + """Restore state returned by `state_dict`.""" + self._rng.setstate(state_dict["rng_state"]) + self._order = list(state_dict["order"]) + self._position = state_dict["position"] + + +class DapoMathDataset(_ShuffledMathDataset): + """Provides filtered DAPO-Math problems in the original `Answer:` format.""" + + @dataclass(kw_only=True, slots=True) + class Config(Configurable.Config): + repo_id: str = "hamishivi/DAPO-Math-17k-Processed_filtered" + split: str = "train" + seed: int = 42 + shuffle: bool = True + + def __init__(self, config: Config) -> None: + dataset = load_dataset(config.repo_id, split=config.split) + samples: list[DapoMathSample] = [] + for row in dataset: + prompt_messages = row["source_prompt"] + if len(prompt_messages) != 1 or prompt_messages[0]["role"] != "user": + raise ValueError("DAPO-Math rows must contain exactly one user prompt") + samples.append( + DapoMathSample( + prompt=prompt_messages[0]["content"], + ground_truth=str(row["ground_truth"]), + ) + ) + super().__init__(samples, seed=config.seed, shuffle=config.shuffle) + + +class AIME2025Dataset(_ShuffledMathDataset): + """Provides AIME 2025 I+II problems using the DAPO answer format.""" + + @dataclass(kw_only=True, slots=True) + class Config(Configurable.Config): + repo_id: str = "opencompass/AIME2025" + subsets: tuple[str, ...] = ("AIME2025-I", "AIME2025-II") + split: str = "test" + seed: int = 99 + shuffle: bool = False + + def __init__(self, config: Config) -> None: + samples: list[DapoMathSample] = [] + for subset in config.subsets: + dataset = load_dataset(config.repo_id, subset, split=config.split) + samples.extend( + DapoMathSample( + prompt=_AIME_PROMPT_TEMPLATE.format(problem=row["question"]), + ground_truth=str(row["answer"]), + ) + for row in dataset + ) + super().__init__(samples, seed=config.seed, shuffle=config.shuffle) diff --git a/torchtitan/experiments/rl/examples/dapo_math/env.py b/torchtitan/experiments/rl/examples/dapo_math/env.py new file mode 100644 index 00000000000..ec2ea858778 --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/env.py @@ -0,0 +1,41 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +from dataclasses import dataclass + +from renderers import Message + +from torchtitan.experiments.rl.environment import ( + MessageEnv, + MessageEnvInitOutput, + MessageEnvStepOutput, +) +from torchtitan.experiments.rl.examples.dapo_math.data import DapoMathSample + + +class DapoMathEnv(MessageEnv): + """Single-turn message environment for a verifiable math problem.""" + + @dataclass(kw_only=True, slots=True) + class Config(MessageEnv.Config): + pass + + def __init__(self, config: Config, *, env_input: DapoMathSample) -> None: + del config + self._prompt = env_input.prompt + + async def init(self) -> MessageEnvInitOutput: + """Return the problem as one user message.""" + return MessageEnvInitOutput( + init_prompt_messages=[{"role": "user", "content": self._prompt}] + ) + + async def step(self, completion_message: Message) -> MessageEnvStepOutput: + """End after the model's first response; the rubric scores its final answer.""" + del completion_message + return MessageEnvStepOutput(done=True) diff --git a/torchtitan/experiments/rl/examples/dapo_math/rollouter.py b/torchtitan/experiments/rl/examples/dapo_math/rollouter.py new file mode 100644 index 00000000000..97961095242 --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/rollouter.py @@ -0,0 +1,51 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +from dataclasses import dataclass, field + +from torchtitan.experiments.rl.environment import TokenEnv +from torchtitan.experiments.rl.examples.dapo_math.data import ( + AIME2025Dataset, + DapoMathDataset, +) +from torchtitan.experiments.rl.examples.dapo_math.env import DapoMathEnv +from torchtitan.experiments.rl.examples.dapo_math.rubric import RewardMathVerify +from torchtitan.experiments.rl.rollout.advantage import AdvantageEstimator +from torchtitan.experiments.rl.rollout.rollouter import Rollouter +from torchtitan.experiments.rl.rubrics import Rubric + + +class DapoMathRollouter(Rollouter): + """Provides DAPO-Math training with AIME 2025 validation and math rewards.""" + + @dataclass(kw_only=True, slots=True) + class Config(Rollouter.Config): + train_dataset: DapoMathDataset.Config = field( + default_factory=DapoMathDataset.Config + ) + validation_dataset: AIME2025Dataset.Config = field( + default_factory=AIME2025Dataset.Config + ) + rubric: Rubric.Config = field( + default_factory=lambda: Rubric.Config( + reward_fns=[RewardMathVerify.Config(weight=1.0)], + error_reward=0.0, + ) + ) + message_env: DapoMathEnv.Config = field(default_factory=DapoMathEnv.Config) + token_env: TokenEnv.Config = field( + default_factory=lambda: TokenEnv.Config( + max_rollout_tokens=10240, + max_num_turns=1, + ) + ) + advantage: AdvantageEstimator.Config = field( + default_factory=lambda: AdvantageEstimator.Config( + should_std_normalize=False + ) + ) diff --git a/torchtitan/experiments/rl/examples/dapo_math/rubric.py b/torchtitan/experiments/rl/examples/dapo_math/rubric.py new file mode 100644 index 00000000000..75d857907cb --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/rubric.py @@ -0,0 +1,67 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +from dataclasses import dataclass + +from math_verify import LatexExtractionConfig, LatexNormalizationConfig, parse, verify +from math_verify.errors import TimeoutException + +from torchtitan.experiments.rl.examples.dapo_math.data import DapoMathSample +from torchtitan.experiments.rl.rollout import Rollout +from torchtitan.experiments.rl.rubrics import RewardFn + + +_FINAL_ANSWER_EXTRACTION = [ + LatexExtractionConfig( + normalization_config=LatexNormalizationConfig(units=True), + boxed_match_priority=0, + try_extract_without_anchor=False, + ) +] + + +def score_math_response(response: str, ground_truth: str) -> float: + """Score an `Answer:` or `\\boxed{}` expression with Math-Verify. + + Args: + response: Model response containing a marked final answer. + ground_truth: Expected answer from the dataset. + + Example: + score_math_response("work\nAnswer: $34$", "34") # 1.0 + """ + try: + gold = parse(ground_truth) + prediction = parse( + response, + extraction_config=_FINAL_ANSWER_EXTRACTION, + extraction_mode="first_match", + ) + return float(bool(gold) and verify(gold, prediction)) + except (Exception, TimeoutException): + # Model output is untrusted; malformed LaTeX is an incorrect answer, not a + # training-loop failure. Math-Verify raises `TimeoutException` from BaseException. + return 0.0 + + +class RewardMathVerify(RewardFn): + """Binary reward for a mathematically equivalent final answer.""" + + @dataclass(kw_only=True, slots=True) + class Config(RewardFn.Config): + pass + + async def __call__(self, rollout: Rollout, env_input: DapoMathSample) -> float: + """Return 1 when Math-Verify equates the response and ground truth.""" + if not rollout.turns: + return 0.0 + completion_message = rollout.turns[-1].completion_message + response = ( + (completion_message.get("content") or "") if completion_message else "" + ) + return score_math_response(response, env_input.ground_truth) diff --git a/torchtitan/experiments/rl/examples/dapo_math/run_train.sh b/torchtitan/experiments/rl/examples/dapo_math/run_train.sh new file mode 100755 index 00000000000..637e0a4cd1d --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/run_train.sh @@ -0,0 +1,47 @@ +#!/usr/bin/bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +set -exo pipefail + +# Environment variables override launcher defaults, for example: +# TRAINING_STEPS=10 DUMP_FOLDER=outputs/rl/dapo_smoke ./torchtitan/experiments/rl/examples/dapo_math/run_train.sh +MODULE=${MODULE:-"dapo_math"} +CONFIG=${CONFIG:-"rl_dapo_qwen3_4b_math_8k_tp2"} +TRAINING_STEPS=${TRAINING_STEPS:-150} +DUMP_FOLDER=${DUMP_FOLDER:-"outputs/rl/qwen3_4b_dapo_math_8k_tp2_150"} + +# Resolve the worktree that contains this script, regardless of the caller's cwd. +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +REPO_ROOT=$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel) +cd "${REPO_ROOT}" + +# Monarch-spawned trainer and generator processes import this checkout. +export PYTHONPATH="${PWD}${PYTHONPATH:+:${PYTHONPATH}}" + +# Limit CPU thread pools because every actor process inherits these settings. +export OMP_NUM_THREADS=${OMP_NUM_THREADS:-1} +export MKL_NUM_THREADS=${MKL_NUM_THREADS:-1} +# Same-host weight sync resolves to shared memory before the cross-host transport. +export USE_TORCHCOMMS=${USE_TORCHCOMMS:-0} + +# Same-host GET returns the shared-memory tensor directly instead of cloning each shard. +export TORCHSTORE_MUTABLE_SHM=${TORCHSTORE_MUTABLE_SHM:-1} + +# This single-node recipe uses shared memory and does not require InfiniBand. +export MONARCH_RDMA_DISABLE_IBVERBS=${MONARCH_RDMA_DISABLE_IBVERBS:-1} +export MONARCH_RDMA_ALLOW_TCP_FALLBACK=${MONARCH_RDMA_ALLOW_TCP_FALLBACK:-1} + +mkdir -p "${DUMP_FOLDER}" + +python3 -m torchtitan.experiments.rl.train \ + --module "${MODULE}" \ + --config "${CONFIG}" \ + --async-loop.num-training-steps "${TRAINING_STEPS}" \ + --trainer.lr-scheduler.total-steps "${TRAINING_STEPS}" \ + --dump-folder "${DUMP_FOLDER}" \ + "$@" \ + 2>&1 | tee "${DUMP_FOLDER}/train.log" diff --git a/torchtitan/experiments/rl/tests/test_dapo_math.py b/torchtitan/experiments/rl/tests/test_dapo_math.py new file mode 100644 index 00000000000..293e6811675 --- /dev/null +++ b/torchtitan/experiments/rl/tests/test_dapo_math.py @@ -0,0 +1,107 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +"""CPU tests for the DAPO-Math dataset, environment, and rubric.""" + +from __future__ import annotations + +import asyncio + +from torchtitan.experiments.rl.examples.dapo_math import ( + AIME2025Dataset, + DapoMathDataset, + DapoMathEnv, + DapoMathSample, + data as math_data, + RewardMathVerify, + score_math_response, +) +from torchtitan.experiments.rl.rollout import Rollout, RolloutStatus, RolloutTurn +from torchtitan.experiments.rl.types import RolloutTurnID + + +def _dapo_rows() -> list[dict]: + return [ + { + "source_prompt": [{"role": "user", "content": "problem 1"}], + "ground_truth": "34", + }, + { + "source_prompt": [{"role": "user", "content": "problem 2"}], + "ground_truth": "113", + }, + { + "source_prompt": [{"role": "user", "content": "problem 3"}], + "ground_truth": "7", + }, + ] + + +def test_dapo_dataset_is_deterministic_and_resumable(monkeypatch) -> None: + monkeypatch.setattr(math_data, "load_dataset", lambda *args, **kwargs: _dapo_rows()) + config = DapoMathDataset.Config(seed=7) + first = config.build() + second = config.build() + assert [next(first) for _ in range(3)] == [next(second) for _ in range(3)] + + checkpoint = first.state_dict() + expected = [next(first) for _ in range(3)] + resumed = config.build() + resumed.load_state_dict(checkpoint) + assert [next(resumed) for _ in range(3)] == expected + + +def test_aime_dataset_combines_both_subsets(monkeypatch) -> None: + def load_dataset(repo_id, subset, *, split): + del repo_id, split + answer = r"42^\circ" if subset == "AIME2025-I" else r"\boxed{42}" + return [{"question": f"{subset} question", "answer": answer}] + + monkeypatch.setattr(math_data, "load_dataset", load_dataset) + dataset = AIME2025Dataset.Config().build() + samples = [next(dataset), next(dataset)] + assert [sample.ground_truth for sample in samples] == [r"42^\circ", r"\boxed{42}"] + assert "AIME2025-I question" in samples[0].prompt + assert "AIME2025-II question" in samples[1].prompt + + +def test_env_is_single_turn() -> None: + env = DapoMathEnv.Config().build( + env_input=DapoMathSample(prompt="solve me", ground_truth="3"), + ) + initial = asyncio.run(env.init()) + assert initial.init_prompt_messages == [{"role": "user", "content": "solve me"}] + assert asyncio.run(env.step({"role": "assistant", "content": "Answer: 3"})).done + + +def _rollout(response: str) -> Rollout: + return Rollout( + group_id=0, + rollout_id=0, + status=RolloutStatus.COMPLETED, + turns=[ + RolloutTurn( + rollout_id=RolloutTurnID(group_id=0, rollout_id=0, turn_id=0), + prompt_token_ids=[1], + completion_token_ids=[2], + completion_logprobs=[-0.1], + completion_message={"role": "assistant", "content": response}, + ) + ], + ) + + +def test_math_verifier_requires_an_answer_marker() -> None: + assert score_math_response("work\nAnswer: $34$", "34") == 1.0 + assert score_math_response(r"work\n\boxed{34}", "34") == 1.0 + assert score_math_response("work mentions 34", "34") == 0.0 + + +def test_reward_handles_equivalent_latex_and_units() -> None: + reward = RewardMathVerify.Config().build() + sample = DapoMathSample(prompt="problem", ground_truth=r"336^\circ") + assert asyncio.run(reward(_rollout("work\nAnswer: $336$"), sample)) == 1.0 + assert asyncio.run(reward(_rollout("work\nAnswer: $335$"), sample)) == 0.0 From 53eb7145f72c561279d247cdc056417a0a89d55f Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 10:45:27 -0700 Subject: [PATCH 02/10] Refine DAPO math recipes and documentation --- .../rl/examples/dapo_math/README.md | 92 ++++++++++++------- .../rl/examples/dapo_math/config_registry.py | 65 ++++++------- .../experiments/rl/examples/dapo_math/data.py | 12 ++- .../rl/examples/dapo_math/rollouter.py | 6 +- .../rl/examples/dapo_math/rubric.py | 1 + .../rl/examples/dapo_math/run_train.sh | 47 ---------- 6 files changed, 108 insertions(+), 115 deletions(-) delete mode 100755 torchtitan/experiments/rl/examples/dapo_math/run_train.sh diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md index 94ba885a7f8..2c47fe8fdf2 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/README.md +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -1,24 +1,63 @@ -# Qwen3-4B-Base on DAPO-Math +# DAPO Math -This example trains Qwen3-4B-Base on verifiable math problems using DAPO loss. It fits one eight-GPU node by assigning two GPUs to the trainer and one GPU to each of six independent generators. +[DAPO-Math-17k](https://huggingface.co/datasets/BytedTsinghua-SIA/DAPO-Math-17k) is the verifiable math dataset released with [DAPO](https://arxiv.org/abs/2503.14476). This environment trains Qwen3-4B-Base with DAPO loss on a filtered version of that dataset. -## Configuration +## Environment + +Each episode is single-turn: + +```text +user math problem -> one assistant solution -> binary Math-Verify reward +``` + +The prompt asks for step-by-step reasoning followed by a final `Answer:` expression. [Math-Verify](https://github.com/huggingface/Math-Verify) parses that expression and assigns a reward of one when it is mathematically equivalent to the reference answer, or zero otherwise. + +An abridged episode from the reference run looks like this: + +```text +Question: +Let r_1, r_2, ..., r_47 be the roots of x^47 - 1 = 0. Compute +the sum of r_i^2020 over all 47 roots. + +Response: +The roots are the 47th roots of unity. Since 2020 is congruent to -1 +modulo 47, raising every root to the 2020th power permutes the roots. +Their sum is therefore zero. + +Answer: \boxed{0} + +Reward: 1 +``` + +## Datasets + +Training uses the 12,643-row [filtered DAPO-Math dataset](https://huggingface.co/datasets/hamishivi/DAPO-Math-17k-Processed_filtered). Each row contains one user prompt and its verifiable final answer. + +Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). The same single-turn environment and Math-Verify reward are used for training and validation. + +## Reference configurations + +Both configurations run 150 optimizer steps on one eight-GPU node. One TP=2 trainer uses two GPUs, and six independent TP=1 generators use the remaining GPUs. Each optimizer step consumes 8 prompt groups with 16 completions per group. `max_offpolicy_steps=4` bounds policy lag. + +The 8K configuration is the default reference recipe: ```text -model: Qwen3-4B-Base -trainer: TP=2, fp32 language-model head -generators: 6 replicas, TP=1 -rollouts: 8 prompts x 16 completions per optimizer step -prompt budget: 2,048 tokens -response budget: 8,192 tokens -packing length: 10,240 tokens -off-policy limit: 4 trainer versions -learning rate: 1e-6, constant +config: rl_dapo_qwen3_4b_math_8k +prompt budget: 2,048 tokens +response budget: 8,192 tokens +packing length: 10,240 tokens ``` -Training uses the 12,643-row [filtered DAPO-Math dataset](https://huggingface.co/datasets/hamishivi/DAPO-Math-17k-Processed_filtered). Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). Math-Verify assigns a binary reward from the final `Answer:` expression. +The 32K configuration extends the response and packing budgets while keeping the same model, optimizer, and GPU topology: + +```text +config: rl_dapo_qwen3_4b_math_32k +prompt budget: 2,048 tokens +response budget: 32,768 tokens +packing length: 34,816 tokens +``` -The default configuration runs 1,000 optimizer steps, or 128,000 sampled completions. `max_offpolicy_steps=4` bounds policy lag and permits 40 prompt groups in the active rollout buffer. +Both configurations use a constant learning rate of `1e-6`, DAPO clipping of `[0.2, 0.28]`, and an fp32 language-model head with a bf16 model forward. The 32K configuration has not been benchmarked. ## Setup @@ -33,31 +72,20 @@ python scripts/download_hf_assets.py \ ## Run -The launcher uses the TP=2 trainer configuration and defaults to 150 steps: - -```bash -./torchtitan/experiments/rl/examples/dapo_math/run_train.sh -``` - -Override the duration or output directory with environment variables: - -```bash -TRAINING_STEPS=10 \ -DUMP_FOLDER=outputs/rl/qwen3_4b_dapo_math_smoke \ -./torchtitan/experiments/rl/examples/dapo_math/run_train.sh -``` - -The replicated DP=2 trainer configuration is also available: +Run the 150-step 8K reference configuration from the repository root. CLI arguments override fields from the config registry; this example selects an explicit output directory: ```bash python -m torchtitan.experiments.rl.train \ --module dapo_math \ - --config rl_dapo_qwen3_4b_math_8k + --config rl_dapo_qwen3_4b_math_8k \ + --dump-folder outputs/rl/qwen3_4b_dapo_math_8k_150 ``` -## 150-step result +Use `rl_dapo_qwen3_4b_math_32k` as the config name to run the 32K variant. + +## 150-step reference result -The single-node TP=2 trial completed optimizer steps 0 through 149 in 1 hour 46 minutes. This trial used the earlier 7,168-token response and 9,216-token packing limits; the runnable configuration above restores the full 8,192-token response budget. +The single-node trial completed optimizer steps 0 through 149 in 1 hour 46 minutes. This trial used an earlier 7,168-token response limit and 9,216-token packing length; the 8K configuration above uses the intended 8,192-token response budget. ```text metric first 10 steps last 10 steps diff --git a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py index 910aa7ce5a4..d817f71f8d4 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py +++ b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py @@ -25,6 +25,7 @@ Controller, ValidationConfig, ) +from torchtitan.experiments.rl.environment import TokenEnv from torchtitan.experiments.rl.examples.dapo_math.rollouter import DapoMathRollouter from torchtitan.experiments.rl.losses import DAPOLoss from torchtitan.experiments.rl.models.cast_linear import LMHeadCastConverter @@ -38,13 +39,13 @@ from torchtitan.models.qwen3 import model_registry -_NUM_TRAINING_STEPS = 1000 - - -def _qwen3_4b_dapo_math_8k_config( - *, trainer_parallelism: ParallelismConfig +def _qwen3_4b_dapo_math_config( + *, + max_response_tokens: int, + max_total_tokens: int, + dump_folder: str, ) -> Controller.Config: - """Build the 8K DAPO-Math recipe for one trainer parallelism layout.""" + """Build a single-node DAPO-Math recipe for one response budget.""" return Controller.Config( model_spec=model_registry( "4B", @@ -53,21 +54,24 @@ def _qwen3_4b_dapo_math_8k_config( converters=[LMHeadCastConverter.Config()], ), hf_assets_path="torchtitan/experiments/rl/example_checkpoint/Qwen3-4B-Base", - dump_folder="outputs/rl/qwen3_4b_dapo_math_8k", + dump_folder=dump_folder, async_loop=AsyncLoopConfig( - num_training_steps=_NUM_TRAINING_STEPS, + num_training_steps=150, num_groups_per_train_step=8, group_size=16, - # Four versions of policy lag plus the current version allow 40 active groups. max_offpolicy_steps=4, validation=ValidationConfig(num_samples=30), batcher=Batcher.Config( - # The dataset is filtered to 2K prompts; generation contributes up to 8K. - batch=BatchConfig(local_batch_size=1, seq_len=10240), + batch=BatchConfig(local_batch_size=1, seq_len=max_total_tokens), ), ), compile=CompileConfig(enable=True, backend="aot_eager"), - rollouter=DapoMathRollouter.Config(), + rollouter=DapoMathRollouter.Config( + token_env=TokenEnv.Config( + max_rollout_tokens=max_total_tokens, + max_num_turns=1, + ) + ), renderer=RendererConfig(name="qwen3", enable_thinking=True), num_generators=6, generator_router=InterGeneratorRouter.Config( @@ -88,14 +92,17 @@ def _qwen3_4b_dapo_math_8k_config( betas=(0.9, 0.98), weight_decay=0.1, ), + # A minimum factor of 1 keeps the learning rate constant. lr_scheduler=LRSchedulersContainer.Config( warmup_steps=0, - total_steps=_NUM_TRAINING_STEPS, - decay_type="linear", min_lr_factor=1.0, ), training=TrainingConfig(), - parallelism=trainer_parallelism, + parallelism=ParallelismConfig( + data_parallel_replicate_degree=1, + data_parallel_shard_degree=1, + tensor_parallel_degree=2, + ), checkpoint=CheckpointManager.Config( enable=True, initial_load_in_hf=True, @@ -122,29 +129,25 @@ def _qwen3_4b_dapo_math_8k_config( sampling=SamplingConfig( temperature=1.0, top_p=1.0, - max_tokens=8192, + max_tokens=max_response_tokens, ), ), ) def rl_dapo_qwen3_4b_math_8k() -> Controller.Config: - """Use two replicated trainer ranks and six single-GPU generators.""" - return _qwen3_4b_dapo_math_8k_config( - trainer_parallelism=ParallelismConfig( - data_parallel_replicate_degree=2, - data_parallel_shard_degree=1, - tensor_parallel_degree=1, - ) + """Run 8K responses on one node: one TP=2 trainer and six TP=1 generators.""" + return _qwen3_4b_dapo_math_config( + max_response_tokens=8192, + max_total_tokens=10240, + dump_folder="outputs/rl/qwen3_4b_dapo_math_8k", ) -def rl_dapo_qwen3_4b_math_8k_tp2() -> Controller.Config: - """Use one TP=2 trainer mesh and six single-GPU generators.""" - return _qwen3_4b_dapo_math_8k_config( - trainer_parallelism=ParallelismConfig( - data_parallel_replicate_degree=1, - data_parallel_shard_degree=1, - tensor_parallel_degree=2, - ) +def rl_dapo_qwen3_4b_math_32k() -> Controller.Config: + """Run 32K responses on one node: one TP=2 trainer and six TP=1 generators.""" + return _qwen3_4b_dapo_math_config( + max_response_tokens=32768, + max_total_tokens=34816, + dump_folder="outputs/rl/qwen3_4b_dapo_math_32k", ) diff --git a/torchtitan/experiments/rl/examples/dapo_math/data.py b/torchtitan/experiments/rl/examples/dapo_math/data.py index 5fd9d7656c8..b66bfbea190 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/data.py +++ b/torchtitan/experiments/rl/examples/dapo_math/data.py @@ -32,8 +32,10 @@ class DapoMathSample: ground_truth: str -class _ShuffledMathDataset(Configurable): - """Provides math problems in shuffled row order.""" +# TODO: Share this cycling iterator with other RL datasets instead of keeping +# per-environment implementations. +class _CyclingDataset(Configurable): + """Provides an endless, resumable stream over a finite sample list.""" def __init__( self, @@ -57,6 +59,8 @@ def __iter__(self) -> Iterator[DapoMathSample]: def __next__(self) -> DapoMathSample: if self._position == len(self._order): + # Rollout production consumes an endless stream; crossing the dataset + # boundary starts a new epoch. Training reshuffles; validation does not. if self._shuffle: self._rng.shuffle(self._order) self._position = 0 @@ -79,7 +83,7 @@ def load_state_dict(self, state_dict: dict) -> None: self._position = state_dict["position"] -class DapoMathDataset(_ShuffledMathDataset): +class DapoMathDataset(_CyclingDataset): """Provides filtered DAPO-Math problems in the original `Answer:` format.""" @dataclass(kw_only=True, slots=True) @@ -105,7 +109,7 @@ def __init__(self, config: Config) -> None: super().__init__(samples, seed=config.seed, shuffle=config.shuffle) -class AIME2025Dataset(_ShuffledMathDataset): +class AIME2025Dataset(_CyclingDataset): """Provides AIME 2025 I+II problems using the DAPO answer format.""" @dataclass(kw_only=True, slots=True) diff --git a/torchtitan/experiments/rl/examples/dapo_math/rollouter.py b/torchtitan/experiments/rl/examples/dapo_math/rollouter.py index 97961095242..bea6963fc7a 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/rollouter.py +++ b/torchtitan/experiments/rl/examples/dapo_math/rollouter.py @@ -21,7 +21,11 @@ class DapoMathRollouter(Rollouter): - """Provides DAPO-Math training with AIME 2025 validation and math rewards.""" + """Single-turn math reasoning with a binary Math-Verify reward. + + Each sample produces one assistant solution; training uses DAPO-Math and + validation uses AIME 2025. + """ @dataclass(kw_only=True, slots=True) class Config(Rollouter.Config): diff --git a/torchtitan/experiments/rl/examples/dapo_math/rubric.py b/torchtitan/experiments/rl/examples/dapo_math/rubric.py index 75d857907cb..27824b9eeb5 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/rubric.py +++ b/torchtitan/experiments/rl/examples/dapo_math/rubric.py @@ -16,6 +16,7 @@ from torchtitan.experiments.rl.rubrics import RewardFn +# Require an `Answer:` or `\boxed{}` marker so intermediate math is ignored. _FINAL_ANSWER_EXTRACTION = [ LatexExtractionConfig( normalization_config=LatexNormalizationConfig(units=True), diff --git a/torchtitan/experiments/rl/examples/dapo_math/run_train.sh b/torchtitan/experiments/rl/examples/dapo_math/run_train.sh deleted file mode 100755 index 637e0a4cd1d..00000000000 --- a/torchtitan/experiments/rl/examples/dapo_math/run_train.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/bash -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the BSD-style license found in the -# LICENSE file in the root directory of this source tree. - -set -exo pipefail - -# Environment variables override launcher defaults, for example: -# TRAINING_STEPS=10 DUMP_FOLDER=outputs/rl/dapo_smoke ./torchtitan/experiments/rl/examples/dapo_math/run_train.sh -MODULE=${MODULE:-"dapo_math"} -CONFIG=${CONFIG:-"rl_dapo_qwen3_4b_math_8k_tp2"} -TRAINING_STEPS=${TRAINING_STEPS:-150} -DUMP_FOLDER=${DUMP_FOLDER:-"outputs/rl/qwen3_4b_dapo_math_8k_tp2_150"} - -# Resolve the worktree that contains this script, regardless of the caller's cwd. -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -REPO_ROOT=$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel) -cd "${REPO_ROOT}" - -# Monarch-spawned trainer and generator processes import this checkout. -export PYTHONPATH="${PWD}${PYTHONPATH:+:${PYTHONPATH}}" - -# Limit CPU thread pools because every actor process inherits these settings. -export OMP_NUM_THREADS=${OMP_NUM_THREADS:-1} -export MKL_NUM_THREADS=${MKL_NUM_THREADS:-1} -# Same-host weight sync resolves to shared memory before the cross-host transport. -export USE_TORCHCOMMS=${USE_TORCHCOMMS:-0} - -# Same-host GET returns the shared-memory tensor directly instead of cloning each shard. -export TORCHSTORE_MUTABLE_SHM=${TORCHSTORE_MUTABLE_SHM:-1} - -# This single-node recipe uses shared memory and does not require InfiniBand. -export MONARCH_RDMA_DISABLE_IBVERBS=${MONARCH_RDMA_DISABLE_IBVERBS:-1} -export MONARCH_RDMA_ALLOW_TCP_FALLBACK=${MONARCH_RDMA_ALLOW_TCP_FALLBACK:-1} - -mkdir -p "${DUMP_FOLDER}" - -python3 -m torchtitan.experiments.rl.train \ - --module "${MODULE}" \ - --config "${CONFIG}" \ - --async-loop.num-training-steps "${TRAINING_STEPS}" \ - --trainer.lr-scheduler.total-steps "${TRAINING_STEPS}" \ - --dump-folder "${DUMP_FOLDER}" \ - "$@" \ - 2>&1 | tee "${DUMP_FOLDER}/train.log" From 8b91f370bac24d1572dd52fbcff7230f9798e561 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 10:48:10 -0700 Subject: [PATCH 03/10] Add DAPO training curves --- .../experiments/rl/examples/dapo_math/README.md | 4 ++-- .../assets/qwen3_4b_7k_response_length.png | Bin 0 -> 57576 bytes .../dapo_math/assets/qwen3_4b_7k_reward.png | Bin 0 -> 68672 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_response_length.png create mode 100644 torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_reward.png diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md index 2c47fe8fdf2..95eefbade63 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/README.md +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -96,6 +96,6 @@ rollout truncation rate, mean 1.75% 14.55% The final recorded step had mean reward `0.302`, mean total length `3,999`, and truncation rate `18.75%`. The maximum observed mean reward was `0.632`; the maximum per-step truncation rate was `30.73%`. Initial AIME validation scored `0/30` before training. - +![Qwen3-4B DAPO Math-Verify reward](./assets/qwen3_4b_7k_reward.png) - +![Qwen3-4B DAPO mean response length](./assets/qwen3_4b_7k_response_length.png) diff --git a/torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_response_length.png b/torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_response_length.png new file mode 100644 index 0000000000000000000000000000000000000000..423acf125429b118a4c1628734d054202e5fa5c4 GIT binary patch literal 57576 zcmeFZXH-*Nw=hZ*L8K@u4;@7TL5ftV0TB>sVnb?Bx>Tvs5)cI&MG=r1P^y&BLyy?# zgdQM3Kso_}Bq5NHqdgTTK12dI@f$0GoE4}26Q04^&21a8Kef{gk`ubwm{e4|Myj&OsjzFq?_vVqJ~(mDzj4?*XtN70wZdV72$QqGvSljPHHL4D>`#vmMb0 zD$z%NGU-yq+_AC5DejAau)PXL@dif=9_Z!H_?7Md$f|FvDtVg2e(annPoIK!${-=@ z?N$l;Ly*a*;mq^CV^|tk+)f%*xRU8SbSsrPx}madSt^nHiO~h?Yr6yUFAzl{e35M{ zBV`LUpOK%xxQY)c%XwTp zL-_tu0nd`=)?N7u+QVw27w#!BhjZj*O|T=na9&OxXbYG|7Gmlxa)GW6bu)~}CwAb& zwmunj+@`wRHFh;KW00obvoRcEJkG#Ozhk8TGcXD;9Qt+7z;KOG@W1YFFiQPXhlzn9 z&VzyFpE@@5?}NW2`iBnv&o}d%Scb#&r{na`lU$~M)n=yVGXLwI=>h#6gWgSjV`KXF zO(%aB7oPxk-$0=b0d;x-yWdso00xFr=MFxM#y8Gx(FtS9ndyiy&0>uAriEO<92|HzrL@x zw~J4p&c89P^{c17BbS^3=D*glZ=d1q_)cW6`suwT) z4*A=g-yjd>pmD<`z}GA20Ef4IJOXv-9sWvWo7w`|A75o{wIv}{{`mv z@;_lL{5|Msymv4>oqzT5d)Ytrwd4=h|2KO0o7jGp(y2}7h?e|+QLfIBi#3=Fyq z##i)iJz`v+KKyR#>%^y@$W}hDsb)^qhD=wnCo`8#q0s}LVuvnE>WUp|)4eK&=VFu$ zWIOj{rpn{{U#1V$ROOaq!`09&0V1JyV9hXt=Avmb0+%#;?Ze=TWO)F#*Zr0og+u_b zu`{yp=ra6%>AsQcW1VwS@+kuon^@HEmpf6PAcBm)*QJ9-E1nYDlAnLsQ*!!0Dzk~{ z&LXt`D2meM=iu2*`}u8gIf=Ym#~)#ihrp^J7Y> z^F~w9(@RHP&iv8%^)V*SHDua%GXWEvt670d q?habuiuznH&1MUoip3p3FXti1D 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 _m1-Me@+Xhy*M@nse@qZ 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
mEdsQ{Z@dKV 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 KzUcmQ|NjFUTJnPc literal 0 HcmV?d00001 diff --git a/torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_reward.png b/torchtitan/experiments/rl/examples/dapo_math/assets/qwen3_4b_7k_reward.png new file mode 100644 index 0000000000000000000000000000000000000000..f79371fc2f68b9f7e51cadb9b771f231a6074cda GIT binary patch literal 68672 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

k**vYg638;vIN_m-i3K_wI)h4_m^Jt} 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 Date: Tue, 21 Jul 2026 11:01:13 -0700 Subject: [PATCH 04/10] Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: --- .../experiments/rl/examples/dapo_math/README.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md index 95eefbade63..d3242a6dd34 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/README.md +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -85,16 +85,7 @@ Use `rl_dapo_qwen3_4b_math_32k` as the config name to run the 32K variant. ## 150-step reference result -The single-node trial completed optimizer steps 0 through 149 in 1 hour 46 minutes. This trial used an earlier 7,168-token response limit and 9,216-token packing length; the 8K configuration above uses the intended 8,192-token response budget. - -```text -metric first 10 steps last 10 steps -rollout reward, mean 0.067 0.401 -rollout total length, mean 988 tokens 3,284 tokens -rollout truncation rate, mean 1.75% 14.55% -``` - -The final recorded step had mean reward `0.302`, mean total length `3,999`, and truncation rate `18.75%`. The maximum observed mean reward was `0.632`; the maximum per-step truncation rate was `30.73%`. Initial AIME validation scored `0/30` before training. +TODO: add eval results. Add 32k variant. ![Qwen3-4B DAPO Math-Verify reward](./assets/qwen3_4b_7k_reward.png) From 07ffd3243de4d7a7886523aaddc14a69d54e5666 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 11:07:22 -0700 Subject: [PATCH 05/10] Clarify DAPO validation and prompt --- .../experiments/rl/examples/dapo_math/README.md | 16 +++++++++++----- .../rl/examples/dapo_math/config_registry.py | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md index d3242a6dd34..a2142a059dd 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/README.md +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -12,12 +12,18 @@ user math problem -> one assistant solution -> binary Math-Verify reward The prompt asks for step-by-step reasoning followed by a final `Answer:` expression. [Math-Verify](https://github.com/huggingface/Math-Verify) parses that expression and assigns a reward of one when it is mathematically equivalent to the reference answer, or zero otherwise. -An abridged episode from the reference run looks like this: +An episode from the reference run is shown below. The prompt is reproduced in full; the response is abridged. ```text -Question: -Let r_1, r_2, ..., r_47 be the roots of x^47 - 1 = 0. Compute -the sum of r_i^2020 over all 47 roots. +Prompt: +Solve the following math problem step by step. The last line of your response +should be of the form Answer: $Answer (without quotes) where $Answer is the +answer to the problem. + +Let $r_1, r_2, \ldots, r_{47}$ be the roots of $x^{47} - 1 = 0$. Compute +\( \sum_{i=1}^{47} r_i^{2020} \). + +Remember to put your answer on its own line after "Answer:". Response: The roots are the 47th roots of unity. Since 2020 is congruent to -1 @@ -33,7 +39,7 @@ Reward: 1 Training uses the 12,643-row [filtered DAPO-Math dataset](https://huggingface.co/datasets/hamishivi/DAPO-Math-17k-Processed_filtered). Each row contains one user prompt and its verifiable final answer. -Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). The same single-turn environment and Math-Verify reward are used for training and validation. +Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). Validation does not shuffle, and each pass consumes exactly 30 rows, so the pre- and post-training evaluations use the same problems in the same order. The same single-turn environment and Math-Verify reward are used for training and validation. ## Reference configurations diff --git a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py index d817f71f8d4..adb27e39a33 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py +++ b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py @@ -45,7 +45,7 @@ def _qwen3_4b_dapo_math_config( max_total_tokens: int, dump_folder: str, ) -> Controller.Config: - """Build a single-node DAPO-Math recipe for one response budget.""" + """Build the shared Qwen3-4B DAPO-Math configuration.""" return Controller.Config( model_spec=model_registry( "4B", @@ -60,6 +60,7 @@ def _qwen3_4b_dapo_math_config( num_groups_per_train_step=8, group_size=16, max_offpolicy_steps=4, + # AIME 2025 has 30 rows, so both validation passes cover the full set. validation=ValidationConfig(num_samples=30), batcher=Batcher.Config( batch=BatchConfig(local_batch_size=1, seq_len=max_total_tokens), From 31b56e1b5b2e94a6a22b9bea264c5dcc08c5e354 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 11:23:48 -0700 Subject: [PATCH 06/10] Clarify AIME validation size --- torchtitan/experiments/rl/examples/dapo_math/config_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py index adb27e39a33..f5b8bc4d0ed 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py +++ b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py @@ -60,7 +60,7 @@ def _qwen3_4b_dapo_math_config( num_groups_per_train_step=8, group_size=16, max_offpolicy_steps=4, - # AIME 2025 has 30 rows, so both validation passes cover the full set. + # Evaluate all 15 AIME I and 15 AIME II problems. validation=ValidationConfig(num_samples=30), batcher=Batcher.Config( batch=BatchConfig(local_batch_size=1, seq_len=max_total_tokens), From c2c64154f021467a7a6a89046a54946a02a7d012 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 11:32:38 -0700 Subject: [PATCH 07/10] Reuse validation samples across passes --- torchtitan/experiments/rl/controller.py | 8 +++- .../rl/tests/test_async_controller.py | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/torchtitan/experiments/rl/controller.py b/torchtitan/experiments/rl/controller.py index 322f1ddbff8..645cc6d2f0e 100644 --- a/torchtitan/experiments/rl/controller.py +++ b/torchtitan/experiments/rl/controller.py @@ -374,6 +374,7 @@ def __init__(self, config: Config): # (https://github.com/PrimeIntellect-ai/renderers/pull/70). # Until then, reach into the renderer's tokenizer for the pad id (eos doubles as pad). self._rollouter: Rollouter = config.rollouter.build() + self._validation_samples: list[object] | None = None self.rollout_recorder = config.rollout_recorder.build( dump_dir=config.dump_folder ) @@ -606,8 +607,13 @@ async def _collect_validation_rollouts( """Sample held-out prompts, run each greedily (n=1) concurrently, and emit validation metrics.""" # TODO: group_size=1 (best-of-1) only. Support best-of-N. generate = self._make_generate_fn(metrics_prefix="validation_generator") + if self._validation_samples is None: + # Reuse the same held-out subset for every validation pass. + self._validation_samples = [ + self._rollouter.get_validation_sample() for _ in range(num_groups) + ] # TODO(naming): reserve "sample" for TrainingSample; rename the rollouter's raw-prompt "sample" -> "prompt"/"data_input". - samples = [self._rollouter.get_validation_sample() for _ in range(num_groups)] + samples = self._validation_samples group_results = await asyncio.gather( *( self._rollouter.run_group_rollouts( diff --git a/torchtitan/experiments/rl/tests/test_async_controller.py b/torchtitan/experiments/rl/tests/test_async_controller.py index 50599358f25..27cf731ecad 100644 --- a/torchtitan/experiments/rl/tests/test_async_controller.py +++ b/torchtitan/experiments/rl/tests/test_async_controller.py @@ -16,6 +16,7 @@ RolloutGroupWork, RolloutGroupWorkBuffer, ) +from torchtitan.experiments.rl.controller import Controller from torchtitan.experiments.rl.controller_metrics import ( compute_perf_ratio_metrics, compute_policy_age_metrics, @@ -157,6 +158,42 @@ def test_rollout_id_to_string_is_callable_and_uses_int_group_id() -> None: assert rollout_id.to_string(include_turn=False) == "group=5/rollout=2" +def test_validation_reuses_the_initial_held_out_samples() -> None: + class ValidationRollouter: + def __init__(self) -> None: + self.next_sample = 0 + self.seen_samples: list[int] = [] + + def get_validation_sample(self) -> int: + sample = self.next_sample + self.next_sample += 1 + return sample + + async def run_group_rollouts(self, *, sample: int, group_id: int, **kwargs): + self.seen_samples.append(sample) + return RolloutGroup(group_id=group_id, rollouts=[]) + + async def run() -> None: + rollouter = ValidationRollouter() + controller = Controller.__new__(Controller) + controller._rollouter = rollouter + controller._validation_samples = None + controller.renderer = object() + controller._make_generate_fn = lambda metrics_prefix: object() + + for step in range(2): + await controller._collect_validation_rollouts( + num_groups=2, + sampling=object(), + step=step, + ) + + assert rollouter.next_sample == 2 + assert rollouter.seen_samples == [0, 1, 0, 1] + + asyncio.run(run()) + + def test_take_finalized_does_not_release_active_slot() -> None: async def run() -> None: buffer = RolloutGroupWorkBuffer.Config().build(max_active_rollout_groups=1) From 905fe711345ff6aab8ceb8487530b12eac534904 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 11:52:16 -0700 Subject: [PATCH 08/10] Cycle validation data at the configured size --- torchtitan/experiments/rl/controller.py | 8 +--- .../rl/examples/dapo_math/config_registry.py | 13 +++++-- .../experiments/rl/examples/dapo_math/data.py | 18 +++++++-- .../rl/tests/test_async_controller.py | 37 ------------------- .../experiments/rl/tests/test_dapo_math.py | 13 ++++++- 5 files changed, 37 insertions(+), 52 deletions(-) diff --git a/torchtitan/experiments/rl/controller.py b/torchtitan/experiments/rl/controller.py index 645cc6d2f0e..322f1ddbff8 100644 --- a/torchtitan/experiments/rl/controller.py +++ b/torchtitan/experiments/rl/controller.py @@ -374,7 +374,6 @@ def __init__(self, config: Config): # (https://github.com/PrimeIntellect-ai/renderers/pull/70). # Until then, reach into the renderer's tokenizer for the pad id (eos doubles as pad). self._rollouter: Rollouter = config.rollouter.build() - self._validation_samples: list[object] | None = None self.rollout_recorder = config.rollout_recorder.build( dump_dir=config.dump_folder ) @@ -607,13 +606,8 @@ async def _collect_validation_rollouts( """Sample held-out prompts, run each greedily (n=1) concurrently, and emit validation metrics.""" # TODO: group_size=1 (best-of-1) only. Support best-of-N. generate = self._make_generate_fn(metrics_prefix="validation_generator") - if self._validation_samples is None: - # Reuse the same held-out subset for every validation pass. - self._validation_samples = [ - self._rollouter.get_validation_sample() for _ in range(num_groups) - ] # TODO(naming): reserve "sample" for TrainingSample; rename the rollouter's raw-prompt "sample" -> "prompt"/"data_input". - samples = self._validation_samples + samples = [self._rollouter.get_validation_sample() for _ in range(num_groups)] group_results = await asyncio.gather( *( self._rollouter.run_group_rollouts( diff --git a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py index f5b8bc4d0ed..0199adf5a1a 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/config_registry.py +++ b/torchtitan/experiments/rl/examples/dapo_math/config_registry.py @@ -26,6 +26,7 @@ ValidationConfig, ) from torchtitan.experiments.rl.environment import TokenEnv +from torchtitan.experiments.rl.examples.dapo_math.data import AIME2025Dataset from torchtitan.experiments.rl.examples.dapo_math.rollouter import DapoMathRollouter from torchtitan.experiments.rl.losses import DAPOLoss from torchtitan.experiments.rl.models.cast_linear import LMHeadCastConverter @@ -46,6 +47,10 @@ def _qwen3_4b_dapo_math_config( dump_folder: str, ) -> Controller.Config: """Build the shared Qwen3-4B DAPO-Math configuration.""" + num_validation_samples = 30 + validation_dataset = AIME2025Dataset.Config( + num_samples=num_validation_samples, + ) return Controller.Config( model_spec=model_registry( "4B", @@ -60,18 +65,20 @@ def _qwen3_4b_dapo_math_config( num_groups_per_train_step=8, group_size=16, max_offpolicy_steps=4, - # Evaluate all 15 AIME I and 15 AIME II problems. - validation=ValidationConfig(num_samples=30), + validation=ValidationConfig( + num_samples=num_validation_samples, + ), batcher=Batcher.Config( batch=BatchConfig(local_batch_size=1, seq_len=max_total_tokens), ), ), compile=CompileConfig(enable=True, backend="aot_eager"), rollouter=DapoMathRollouter.Config( + validation_dataset=validation_dataset, token_env=TokenEnv.Config( max_rollout_tokens=max_total_tokens, max_num_turns=1, - ) + ), ), renderer=RendererConfig(name="qwen3", enable_thinking=True), num_generators=6, diff --git a/torchtitan/experiments/rl/examples/dapo_math/data.py b/torchtitan/experiments/rl/examples/dapo_math/data.py index b66bfbea190..dbcf31d6df3 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/data.py +++ b/torchtitan/experiments/rl/examples/dapo_math/data.py @@ -43,13 +43,17 @@ def __init__( *, seed: int, shuffle: bool, + num_samples: int | None = None, ) -> None: if not samples: raise ValueError("math dataset must contain at least one sample") + if num_samples is not None and not 0 < num_samples <= len(samples): + raise ValueError("num_samples must be between 1 and the dataset size") self._samples = samples self._rng = random.Random(seed) self._shuffle = shuffle self._order = list(range(len(samples))) + self._num_samples = num_samples if num_samples is not None else len(samples) if shuffle: self._rng.shuffle(self._order) self._position = 0 @@ -58,9 +62,9 @@ def __iter__(self) -> Iterator[DapoMathSample]: return self def __next__(self) -> DapoMathSample: - if self._position == len(self._order): - # Rollout production consumes an endless stream; crossing the dataset - # boundary starts a new epoch. Training reshuffles; validation does not. + if self._position == self._num_samples: + # Rollout production consumes an endless stream; crossing the configured + # cycle boundary starts a new epoch. Training reshuffles; validation does not. if self._shuffle: self._rng.shuffle(self._order) self._position = 0 @@ -119,6 +123,7 @@ class Config(Configurable.Config): split: str = "test" seed: int = 99 shuffle: bool = False + num_samples: int = 30 def __init__(self, config: Config) -> None: samples: list[DapoMathSample] = [] @@ -131,4 +136,9 @@ def __init__(self, config: Config) -> None: ) for row in dataset ) - super().__init__(samples, seed=config.seed, shuffle=config.shuffle) + super().__init__( + samples, + seed=config.seed, + shuffle=config.shuffle, + num_samples=config.num_samples, + ) diff --git a/torchtitan/experiments/rl/tests/test_async_controller.py b/torchtitan/experiments/rl/tests/test_async_controller.py index 27cf731ecad..50599358f25 100644 --- a/torchtitan/experiments/rl/tests/test_async_controller.py +++ b/torchtitan/experiments/rl/tests/test_async_controller.py @@ -16,7 +16,6 @@ RolloutGroupWork, RolloutGroupWorkBuffer, ) -from torchtitan.experiments.rl.controller import Controller from torchtitan.experiments.rl.controller_metrics import ( compute_perf_ratio_metrics, compute_policy_age_metrics, @@ -158,42 +157,6 @@ def test_rollout_id_to_string_is_callable_and_uses_int_group_id() -> None: assert rollout_id.to_string(include_turn=False) == "group=5/rollout=2" -def test_validation_reuses_the_initial_held_out_samples() -> None: - class ValidationRollouter: - def __init__(self) -> None: - self.next_sample = 0 - self.seen_samples: list[int] = [] - - def get_validation_sample(self) -> int: - sample = self.next_sample - self.next_sample += 1 - return sample - - async def run_group_rollouts(self, *, sample: int, group_id: int, **kwargs): - self.seen_samples.append(sample) - return RolloutGroup(group_id=group_id, rollouts=[]) - - async def run() -> None: - rollouter = ValidationRollouter() - controller = Controller.__new__(Controller) - controller._rollouter = rollouter - controller._validation_samples = None - controller.renderer = object() - controller._make_generate_fn = lambda metrics_prefix: object() - - for step in range(2): - await controller._collect_validation_rollouts( - num_groups=2, - sampling=object(), - step=step, - ) - - assert rollouter.next_sample == 2 - assert rollouter.seen_samples == [0, 1, 0, 1] - - asyncio.run(run()) - - def test_take_finalized_does_not_release_active_slot() -> None: async def run() -> None: buffer = RolloutGroupWorkBuffer.Config().build(max_active_rollout_groups=1) diff --git a/torchtitan/experiments/rl/tests/test_dapo_math.py b/torchtitan/experiments/rl/tests/test_dapo_math.py index 293e6811675..61a9f982620 100644 --- a/torchtitan/experiments/rl/tests/test_dapo_math.py +++ b/torchtitan/experiments/rl/tests/test_dapo_math.py @@ -61,13 +61,24 @@ def load_dataset(repo_id, subset, *, split): return [{"question": f"{subset} question", "answer": answer}] monkeypatch.setattr(math_data, "load_dataset", load_dataset) - dataset = AIME2025Dataset.Config().build() + dataset = AIME2025Dataset.Config(num_samples=2).build() samples = [next(dataset), next(dataset)] assert [sample.ground_truth for sample in samples] == [r"42^\circ", r"\boxed{42}"] assert "AIME2025-I question" in samples[0].prompt assert "AIME2025-II question" in samples[1].prompt +def test_aime_dataset_restarts_after_configured_num_samples(monkeypatch) -> None: + def load_dataset(repo_id, subset, *, split): + del repo_id, split + return [{"question": f"{subset} question", "answer": "42"}] + + monkeypatch.setattr(math_data, "load_dataset", load_dataset) + dataset = AIME2025Dataset.Config(num_samples=1).build() + first = next(dataset) + assert next(dataset) == first + + def test_env_is_single_turn() -> None: env = DapoMathEnv.Config().build( env_input=DapoMathSample(prompt="solve me", ground_truth="3"), From d16fe13c3ecc8c6939ae00b4f6c999671e83491b Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 12:09:02 -0700 Subject: [PATCH 09/10] Select the AIME validation subset at load time --- .../experiments/rl/examples/dapo_math/data.py | 40 ++++++++----------- .../experiments/rl/tests/test_dapo_math.py | 6 ++- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/torchtitan/experiments/rl/examples/dapo_math/data.py b/torchtitan/experiments/rl/examples/dapo_math/data.py index dbcf31d6df3..5146e908aa3 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/data.py +++ b/torchtitan/experiments/rl/examples/dapo_math/data.py @@ -10,7 +10,7 @@ from collections.abc import Iterator from dataclasses import dataclass -from datasets import load_dataset +from datasets import concatenate_datasets, load_dataset from torchtitan.config import Configurable @@ -43,17 +43,13 @@ def __init__( *, seed: int, shuffle: bool, - num_samples: int | None = None, ) -> None: if not samples: raise ValueError("math dataset must contain at least one sample") - if num_samples is not None and not 0 < num_samples <= len(samples): - raise ValueError("num_samples must be between 1 and the dataset size") self._samples = samples self._rng = random.Random(seed) self._shuffle = shuffle self._order = list(range(len(samples))) - self._num_samples = num_samples if num_samples is not None else len(samples) if shuffle: self._rng.shuffle(self._order) self._position = 0 @@ -62,9 +58,9 @@ def __iter__(self) -> Iterator[DapoMathSample]: return self def __next__(self) -> DapoMathSample: - if self._position == self._num_samples: - # Rollout production consumes an endless stream; crossing the configured - # cycle boundary starts a new epoch. Training reshuffles; validation does not. + if self._position == len(self._order): + # Rollout production consumes an endless stream; crossing the dataset + # boundary starts a new epoch. Training reshuffles; validation does not. if self._shuffle: self._rng.shuffle(self._order) self._position = 0 @@ -126,19 +122,17 @@ class Config(Configurable.Config): num_samples: int = 30 def __init__(self, config: Config) -> None: - samples: list[DapoMathSample] = [] - for subset in config.subsets: - dataset = load_dataset(config.repo_id, subset, split=config.split) - samples.extend( - DapoMathSample( - prompt=_AIME_PROMPT_TEMPLATE.format(problem=row["question"]), - ground_truth=str(row["answer"]), - ) - for row in dataset + dataset = concatenate_datasets( + [ + load_dataset(config.repo_id, subset, split=config.split) + for subset in config.subsets + ] + ).select(range(config.num_samples)) + samples = [ + DapoMathSample( + prompt=_AIME_PROMPT_TEMPLATE.format(problem=row["question"]), + ground_truth=str(row["answer"]), ) - super().__init__( - samples, - seed=config.seed, - shuffle=config.shuffle, - num_samples=config.num_samples, - ) + for row in dataset + ] + super().__init__(samples, seed=config.seed, shuffle=config.shuffle) diff --git a/torchtitan/experiments/rl/tests/test_dapo_math.py b/torchtitan/experiments/rl/tests/test_dapo_math.py index 61a9f982620..1bedb64f9f2 100644 --- a/torchtitan/experiments/rl/tests/test_dapo_math.py +++ b/torchtitan/experiments/rl/tests/test_dapo_math.py @@ -10,6 +10,8 @@ import asyncio +from datasets import Dataset + from torchtitan.experiments.rl.examples.dapo_math import ( AIME2025Dataset, DapoMathDataset, @@ -58,7 +60,7 @@ def test_aime_dataset_combines_both_subsets(monkeypatch) -> None: def load_dataset(repo_id, subset, *, split): del repo_id, split answer = r"42^\circ" if subset == "AIME2025-I" else r"\boxed{42}" - return [{"question": f"{subset} question", "answer": answer}] + return Dataset.from_list([{"question": f"{subset} question", "answer": answer}]) monkeypatch.setattr(math_data, "load_dataset", load_dataset) dataset = AIME2025Dataset.Config(num_samples=2).build() @@ -71,7 +73,7 @@ def load_dataset(repo_id, subset, *, split): def test_aime_dataset_restarts_after_configured_num_samples(monkeypatch) -> None: def load_dataset(repo_id, subset, *, split): del repo_id, split - return [{"question": f"{subset} question", "answer": "42"}] + return Dataset.from_list([{"question": f"{subset} question", "answer": "42"}]) monkeypatch.setattr(math_data, "load_dataset", load_dataset) dataset = AIME2025Dataset.Config(num_samples=1).build() From b7bc0a4c8cfcd5b8d8ed852a4baeca35839d9762 Mon Sep 17 00:00:00 2001 From: Felipe Mello Date: Tue, 21 Jul 2026 12:23:52 -0700 Subject: [PATCH 10/10] Scope Math-Verify to the DAPO recipe --- pyproject.toml | 3 --- torchtitan/experiments/rl/examples/dapo_math/README.md | 8 ++++++-- .../experiments/rl/examples/dapo_math/requirements.txt | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 torchtitan/experiments/rl/examples/dapo_math/requirements.txt diff --git a/pyproject.toml b/pyproject.toml index 341016b703f..c45098ef2c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,9 +18,6 @@ dependencies = [ "tokenizers>=0.15.0", "safetensors", - # Rule-based verification for math RL examples - "math-verify==0.9.0", - # Miscellaneous "tyro", "tensorboard", diff --git a/torchtitan/experiments/rl/examples/dapo_math/README.md b/torchtitan/experiments/rl/examples/dapo_math/README.md index a2142a059dd..891243e7663 100644 --- a/torchtitan/experiments/rl/examples/dapo_math/README.md +++ b/torchtitan/experiments/rl/examples/dapo_math/README.md @@ -39,7 +39,7 @@ Reward: 1 Training uses the 12,643-row [filtered DAPO-Math dataset](https://huggingface.co/datasets/hamishivi/DAPO-Math-17k-Processed_filtered). Each row contains one user prompt and its verifiable final answer. -Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). Validation does not shuffle, and each pass consumes exactly 30 rows, so the pre- and post-training evaluations use the same problems in the same order. The same single-turn environment and Math-Verify reward are used for training and validation. +Validation uses all 30 problems from [AIME 2025](https://huggingface.co/datasets/opencompass/AIME2025). The same single-turn environment and Math-Verify reward are used for training and validation. ## Reference configurations @@ -67,9 +67,11 @@ Both configurations use a constant learning rate of `1e-6`, DAPO clipping of `[0 ## Setup -Follow the [RL environment setup](../../README.md), then download the base checkpoint: +Follow the [RL environment setup](../../README.md), install this recipe's verifier, and download the base checkpoint: ```bash +pip install -r torchtitan/experiments/rl/examples/dapo_math/requirements.txt + python scripts/download_hf_assets.py \ --repo_id Qwen/Qwen3-4B-Base \ --local_dir torchtitan/experiments/rl/example_checkpoint \ @@ -93,6 +95,8 @@ Use `rl_dapo_qwen3_4b_math_32k` as the config name to run the 32K variant. TODO: add eval results. Add 32k variant. +The plots below were generated on July 20, 2026 from commit [`fec3e196`](https://github.com/felipemello1/torchtitan/commit/fec3e196a4ceb87bfc87fb4f1a36a538d7e98ee4). + ![Qwen3-4B DAPO Math-Verify reward](./assets/qwen3_4b_7k_reward.png) ![Qwen3-4B DAPO mean response length](./assets/qwen3_4b_7k_response_length.png) diff --git a/torchtitan/experiments/rl/examples/dapo_math/requirements.txt b/torchtitan/experiments/rl/examples/dapo_math/requirements.txt new file mode 100644 index 00000000000..4ca03dae36f --- /dev/null +++ b/torchtitan/experiments/rl/examples/dapo_math/requirements.txt @@ -0,0 +1 @@ +math-verify==0.9.0