Skip to content

refactor(rollout): seat colocated actor pools from the manager, not module state - #233

Closed
Rockdu wants to merge 1 commit into
mainfrom
refactor/reward-pool-placement
Closed

refactor(rollout): seat colocated actor pools from the manager, not module state#233
Rockdu wants to merge 1 commit into
mainfrom
refactor/reward-pool-placement

Conversation

@Rockdu

@Rockdu Rockdu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

What

  • Remove the module-level placement-group state in miles/rollout/rm_hub/core.py (set_manager_placement_group / get_manager_placement_group and the set/get_reward_placement_group aliases) and the _encode_actors global in sft_rollout.py.
  • RolloutManager now seats the colocated reward pool itself in __init__ (rm_hub.create_reward_pool(args, pg), keyed on --rm-type); pickscore_rm keeps its AsyncPickScorePool(args) lookup, which hits that singleton.
  • AsyncRewardActorPool takes placement_group= explicitly. A colocated pool constructed without one raises immediately — this is also the fail-fast for mixed GPU reward types under --colocate-reward (previously the second pool's 0.05-GPU actor pended in Ray forever).
  • call_rollout_fn passes placement_group= to rollout functions whose signature declares it (same pattern as evaluation= on custom_generate); sft_rollout.generate_rollout uses it to build a SftEncodePool singleton. Rollout functions without the kwarg are untouched.

Why

The reward pool is built lazily inside the rollout function, which has no handle on the manager, so the placement group was smuggled through a module global. The pool outlives every rollout call — its owner is the RolloutManager, whose __init__ already decides where the engines sit (init_rollout_engines), so it is the one object that knows which bundles carry a rollout claim. #207 was about to add a second global (_reward_slots) on top of this one; seating pools from the manager gives that PR a place to hang its bundle accounting without any module state.

GenerateState was deliberately not used as the owner: its lifecycle is one rollout call (reset() per call), while the pool is process-lifetime.

Validation

  • pytest tests/fast/rollout/test_rollout_placement_group.py — 2 passed (kwarg is passed only to signatures that take it; an unseated colocated pool raises before creating actors).
  • pre-commit run clean on the touched files.
  • Not executed locally: the colocated pickscore path end-to-end. test_h3_t2va_grpo_2xGPU (H3 recipe, --colocate-reward --rm-type pickscore) covers it in this PR's CI; the three --colocate-reward recipes (H3, Cosmos3, Wan2.2 17-GPU) all use --rm-type pickscore, so create_reward_pool has a pool for each.

Files

  • miles/rollout/rm_hub/core.py — drop the globals and aliases; placement_group= parameter; raise when colocated without seats.
  • miles/rollout/rm_hub/__init__.pycreate_reward_pool(args, placement_group).
  • miles/rollout/rm_hub/pickscore.py — pass placement_group through.
  • miles/ray/rollout.py — seat the reward pool in __init__; pass placement_group=self.pg to call_rollout_fn.
  • miles/rollout/base_types.pycall_rollout_fn forwards placement_group when the signature takes it.
  • miles/rollout/sft_rollout.pySftEncodePool singleton replaces the _encode_actors global; generate_rollout declares placement_group=.
  • docs/user-guide/customization.md — document the optional kwarg and who seats the reward pool.
  • tests/fast/rollout/test_rollout_placement_group.py — new.

Follow-ups not in scope

  • sft_rollout._scheduler_grid is still a module-level cache (a derived constant, not placement state).
  • feat(reward): support HPSv2 and refine reward scheduling #207 should rebase onto this and drop _dispersal_order / ColocatedRewardSlots; with the pool seated by the manager, the remaining need is one actor per bundle, which the existing stride already gives.

🤖 Generated with Claude Code

https://claude.ai/code/session_019FxEmRysrrXzWUiQxxDR9k

…odule state

rm_hub/core.py published the rollout placement group through a module
global (set/get_manager_placement_group plus two aliases) because the
reward pool is built lazily inside the rollout function, which has no
handle on the manager. sft_rollout leaned on the same global and kept
its encode actors in another.

The pool outlives every rollout call, so RolloutManager now builds it
in __init__ via rm_hub.create_reward_pool(args, pg); pickscore_rm's
singleton lookup finds that seated instance. A colocated pool without
seats raises at construction instead of pending in Ray forever, which
is also the fail-fast for mixed GPU reward types. The SFT encode pool
becomes a SingletonMeta class fed by a new optional placement_group
kwarg that call_rollout_fn passes only to signatures declaring it, so
custom rollout functions keep working unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FxEmRysrrXzWUiQxxDR9k
@Rockdu

Rockdu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: this belongs on top of #207's branch, not as a standalone PR. Re-basing there.

@Rockdu Rockdu closed this Sep 4, 2026
@Rockdu
Rockdu deleted the refactor/reward-pool-placement branch September 4, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant