perf(async): eval-priority rollout scheduling on the shared pool#737
Draft
jeffreysijuntan wants to merge 1 commit into
Draft
perf(async): eval-priority rollout scheduling on the shared pool#737jeffreysijuntan wants to merge 1 commit into
jeffreysijuntan wants to merge 1 commit into
Conversation
In fully-async training, periodic validation previously paused dispatch and waited for ALL in-flight training rollouts to drain before any eval rollout could start (_validate_async_with_pause -> pause_generation + wait_for_drain). With long-horizon SWE tasks that drain can take many minutes of idle pool. Instead, run eval on the same shared rollout pool and let eval rollouts preempt training rollouts for concurrency slots: - Add PrioritySemaphore (rllm/utils/priority_semaphore.py): an asyncio counting semaphore that grants freed permits to the highest-priority waiter (FIFO within a priority). Exposes `available`/`waiting` for diagnostics and a `slot(priority)` async context manager. - AgentFlowEngine uses it in place of asyncio.Semaphore; process_task_with_retry acquires at EVAL_PRIORITY when is_validation else TRAIN_PRIORITY. - Drop the pause/drain barrier in the fully-async validation path; call _validate_async directly. Eval saturates the pool as training rollouts free slots; training fills whatever eval leaves. Weights stay frozen for the eval duration because the await blocks the training loop (no weight sync runs). Scoped to the gateway/agentflow path (fully-async). Sync paths already called _validate_async directly. Coordinator pause/drain methods remain in use by weight sync and the generation loop. Adds tests/engine/test_priority_semaphore.py (priority order, FIFO ties, context-manager release on exception, cancel-after-grant permit hand-off). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In fully-async training, periodic validation currently stops the world:
_validate_async_with_pausecallscoordinator.pause_generation()thenawait coordinator.wait_for_drain()— it waits for all in-flight trainingrollouts to finish before any eval rollout starts. With long-horizon SWE tasks,
that drain can leave the rollout pool idle for many minutes every
test_freqsteps.
Change
Run eval on the same shared rollout pool and let eval rollouts preempt
training rollouts for concurrency slots — no separate pool, no drain barrier.
PrioritySemaphore(rllm/utils/priority_semaphore.py): an asynciocounting semaphore that hands a freed permit to the highest-priority
waiter (FIFO within a priority). Exposes
available/waitingfor theexisting
inflight/pendingdiagnostics, plus aslot(priority)asynccontext manager. Handles the cancel-after-grant case (returns the permit to
the pool before re-granting) the same way
asyncio.Semaphoredoes.AgentFlowEngineuses it in place ofasyncio.Semaphore.process_task_with_retryacquires atEVAL_PRIORITYwhenis_validationelse
TRAIN_PRIORITY(the flag already threads down to this call).unified_trainer: the fully-async validation path calls_validate_asyncdirectly; the
pause_generation+wait_for_drain+resume_generationwrapper is removed.
Behavior
freed slots go to eval first, so eval saturates the pool as fast as
training naturally drains (no waiting for a full drain first).
fills whatever eval leaves free, exactly as requested.
eval tasks ≥ pool→ eval takes the whole pool, training only resumes in theeval tail.
eval tasks < pool→ training keeps the leftover capacity busy.Consistency
Eval still runs on frozen weights:
await self._validate_async(...)blocks thetraining loop, and weight sync (
_perform_weight_sync) only runs from that sameloop, so no sync happens for the eval duration. No explicit weight pinning
needed. The generation loop keeps producing training groups during eval; the
coordinator's staleness/concurrency caps bound the pile-up (blocked training
tasks still count toward
max_concurrent_rollouts).Scope
AgentFlowEngine._semaphoregoverns rollout concurrency). Sync training paths already call
_validate_asyncdirectly and are unaffected.pause_generation/wait_for_drain/resume_generationremainin use by weight sync and the generation loop's end-of-run drain.
Tests
tests/engine/test_priority_semaphore.py— priority ordering, FIFO within apriority, context-manager release on exception, and cancel-after-grant permit
hand-off. Existing
tests/engine/test_agentflow_engine.pystill passes (7/7).🤖 Generated with Claude Code