[cfg, recipe, tests] feat: add MMK12 DAPO recipe on V1 omni loop (#446 Phase 4) - #541
[cfg, recipe, tests] feat: add MMK12 DAPO recipe on V1 omni loop (#446 Phase 4)#541OnePunchMonk wants to merge 11 commits into
Conversation
Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Assisted-by: AI tooling Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
Assisted-by: AI tooling Signed-off-by: Wenzhe Wang <w_wenzhe@163.com>
…i loop (verl-project#446 Phase 2) Phase 1 (verl-project#456) left the overlong reward buffer disabled everywhere; verl's registered DAPORewardManager already reads reward.reward_kwargs.overlong_buffer_cfg and reward.reward_kwargs.max_resp_len off the config, but verl_omni's reward.yaml never declared reward_kwargs, so any override there was silently dropped for the autogenerated trainer configs. - Add reward.reward_kwargs (overlong_buffer_cfg + max_resp_len) to reward.yaml and regenerate the four _generated_*.yaml trainer configs. - Enable overlong shaping in the DAPO tiny-random smoke test (reward.reward_manager.name=dapo path only; the naive-manager AVQA example is unaffected and documented as such). - Add a CPU test that drives DAPORewardManager directly and asserts the overlong penalty changes the reward on a truncated dummy response — the Phase 2 exit criterion from verl-project#446. - Split the shared launcher-contract test helper so the "no overlong" check only applies to the naive-manager example, and add overlong-specific assertions for the dapo-manager smoke test. This does not touch dynamic sampling (Phase 3) or the reward-loop metrics plumbing beyond what DAPORewardManager already logs in reward_extra_info. Draft: opened for early feedback while GPU LoRA validation on real Qwen3-Omni-30B-A3B-Instruct + gsm8k is still pending (see PR description). Co-authored-by: Claude Code
Co-authored-by: Claude Code
…op (verl-project#446 Phase 3) Confirms the upstream V1 PPOTrainer replay buffer already implements group filtering, so Phase 3 is config-only: adds a dynamic-sampling AVQA launcher with algorithm.filter_groups.enable=true/metric=acc on the streaming DAPO reward path, plus a CPU launcher-contract test.
…l-project#446 Phase 4) Applies the Phase 1-3 DAPO contract (token-level clip-higher, dynamic sampling, overlong shaping) to a second modality, MMK12 math reasoning, reusing the existing mmk12_reward.py scorer and data pipeline. Adds a CPU launcher-contract test mirroring the AVQA one.
There was a problem hiding this comment.
🟡 Changes recommended
The docs toctree references a missing examples/dapo_trainer.md page and a new CPU test uses AutoTokenizer.from_pretrained("gpt2"), which can break CI by requiring network access.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the Qwen3-Omni Thinker DAPO “config + recipe” stack to a second dataset/modality (MMK12), while also wiring the shared DAPO config contract (overlong reward kwargs + dynamic sampling knobs) into the omni trainer config and adding CPU contract tests.
Changes:
- Add DAPO launchers for AVQA (phase-1 baseline), AVQA + dynamic sampling (phase-3), and MMK12 + dynamic sampling + overlong shaping (phase-4).
- Plumb
reward.reward_kwargs(overlong buffer config +max_resp_len) into the omni reward config and regenerate trainer YAMLs. - Add CPU tests to validate the launcher/config “DAPO contract” and (directly) the DAPO overlong penalty behavior.
File summaries
| File | Description |
|---|---|
| verl_omni/trainer/config/reward/reward.yaml | Adds reward.reward_kwargs so DAPO reward manager can receive overlong-buffer settings. |
| verl_omni/trainer/config/algorithm.py | Updates OmniAlgoConfig to inherit upstream AlgoConfig so online/DAPO fields survive structured config conversion. |
| verl_omni/trainer/config/_generated_omni_trainer.yaml | Regenerated to include reward.reward_kwargs defaults. |
| verl_omni/trainer/config/_generated_omni_megatron_trainer.yaml | Regenerated to include reward.reward_kwargs defaults. |
| verl_omni/trainer/config/_generated_diffusion_veomni_trainer.yaml | Regenerated to include reward.reward_kwargs defaults. |
| verl_omni/trainer/config/_generated_diffusion_trainer.yaml | Regenerated to include reward.reward_kwargs defaults. |
| tests/workers/config/test_omni_config_on_cpu.py | Adds assertions that OmniAlgoConfig is an AlgoConfig and that inherited online fields can be set via Hydra. |
| tests/utils/test_qwen3_omni_dapo_launcher_on_cpu.py | Adds “launcher contract” tests for phase 1/3/4 DAPO scripts and the tiny-random smoke script. |
| tests/utils/test_dapo_overlong_reward_on_cpu.py | Adds a CPU unit test asserting overlong penalty changes reward with DAPORewardManager. |
| tests/special_e2e/run_dapo_qwen3_omni_thinker_lora_v1_smoke.sh | Adds a tiny-random GPU smoke script for DAPO + overlong shaping (no dynamic sampling). |
| examples/dapo_trainer/README.md | Documents the phased DAPO recipes and how they map to AVQA/MMK12. |
| examples/dapo_trainer/qwen3_omni/run_qwen3_omni_thinker_dapo_lora_v1.sh | Adds phase-1 AVQA DAPO launcher (no dynamic sampling, naive reward manager). |
| examples/dapo_trainer/qwen3_omni/run_qwen3_omni_thinker_dapo_dynamic_sampling_lora_v1.sh | Adds phase-3 AVQA launcher with filter_groups enabled and streaming reward path via DAPO manager. |
| examples/dapo_trainer/qwen3_omni/run_qwen3_omni_thinker_dapo_lora_mmk12_v1.sh | Adds phase-4 MMK12 launcher reusing mmk12 reward + overlong shaping + dynamic sampling. |
| docs/index.md | Adds a Sphinx toctree entry for a DAPO example docs page. |
Review details
Suppressed comments (1)
tests/utils/test_dapo_overlong_reward_on_cpu.py:51
- Avoid
from_pretrained("gpt2")here (Hub download). Build a minimal local tokenizer and pass that intoDAPORewardManagerso the test is fully offline/reproducible.
}
)
tokenizer = AutoTokenizer.from_pretrained("gpt2")
return DAPORewardManager(config, tokenizer, _compute_score)
- Files reviewed: 16/16 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| examples/flowgrpo_trainer.md | ||
| examples/flowdppo_trainer.md | ||
| examples/dpo_trainer.md | ||
| examples/dapo_trainer.md |
| import numpy as np | ||
| import torch | ||
| from omegaconf import OmegaConf | ||
| from transformers import AutoTokenizer |
| overrides=[ | ||
| "algorithm.filter_groups.enable=true", | ||
| "algorithm.filter_groups.metric=acc", | ||
| "algorithm.filter_groups.max_num_gen_batches=7", | ||
| "algorithm.use_kl_in_reward=true", | ||
| "algorithm.kl_penalty=low_var_kl", | ||
| "algorithm.kl_ctrl.type=adaptive", | ||
| "algorithm.kl_ctrl.kl_coef=0.02", | ||
| "algorithm.kl_ctrl.target_kl=0.03", | ||
| "algorithm.rollout_correction.rollout_is=token", | ||
| "algorithm.rollout_correction.rollout_is_threshold=3.0", | ||
| ], | ||
| ) | ||
|
|
||
| algorithm_cfg: OmniAlgoConfig = omega_conf_to_dataclass(cfg.algorithm) | ||
|
|
||
| assert isinstance(algorithm_cfg, OmniAlgoConfig) | ||
| assert isinstance(algorithm_cfg, AlgoConfig) | ||
| assert isinstance(algorithm_cfg.filter_groups, FilterGroupsConfig) | ||
| assert algorithm_cfg.filter_groups.enable is True | ||
| assert algorithm_cfg.filter_groups.metric == "acc" | ||
| assert algorithm_cfg.filter_groups.max_num_gen_batches == 7 | ||
| assert algorithm_cfg.use_kl_in_reward is True |
Summary
Implements Phase 4 of #446 (Qwen3-Omni Thinker DAPO): applies the Phase 1-3
DAPO contract to a second modality/dataset, MMK12 math reasoning.
Stacked on #540 (Phase 3), which is stacked on #502 (Phase 2), which is
stacked on #456 (Phase 1) — none merged yet. This branch is built on top of
dapo-p3-dynamic-sampling, so the diff againstmainincludes all threePRs' commits. Please review only the commit on top
(
feat: add MMK12 DAPO recipe on V1 omni loop (#446 Phase 4)) until theearlier phases land; I'll rebase and shrink the diff as each merges.
What's actually wired
Per #446's non-goals, this is explicitly not a new adapter or reward path —
it reuses the existing
mmk12_reward.pyscorer andexamples/gspo_trainer/data_process/mmk12.pydata pipeline (already used bythe GSPO MMK12 recipe) and combines them with the DAPO policy/reward
contract from Phases 1-3:
examples/dapo_trainer/qwen3_omni/run_qwen3_omni_thinker_dapo_lora_mmk12_v1.sh:same vanilla clip-higher / token-mean / GRPO / no-KL policy settings as the
AVQA dynamic-sampling launcher,
algorithm.filter_groups.enable=true, andthe overlong buffer sized for MMK12's longer 12288-token max response
(
overlong_buffer_cfg.len=1024vs AVQA's 128, scaled to the same ~8% ofmax_response_length).test_dapo_mmk12_example_launcher_has_phase_four_contractmirroringthe existing AVQA contract tests.
examples/dapo_trainer/README.md.Test plan
CPU (all green locally):
Not yet run: any GPU e2e on real MMK12 data or a tiny-random smoke. I
don't have further Modal/GPU access or the MMK12 dataset in this pass, so the
overlong_buffer_cfg.len=1024sizing is a scaled estimate from the AVQAvalue, not empirically tuned — flagging this explicitly. This PR stays in
draft until a GPU run validates it and until the earlier phases land
underneath it.
AI usage disclosure
Claude Code was used to derive this recipe from the existing AVQA DAPO
launchers and the GSPO MMK12 recipe, and to write the test above. A human
submitter (onepunchmonk) will review every changed line and run a GPU
validation before taking this out of draft.