Repository navigation
Conversation
Port of ai-dynamo#1540 (head 0ae0014). A duration-bounded agentic run could finish every request at the --benchmark-duration cutoff and then idle for the whole --benchmark-grace-period. Delayed SPAWN children register their join, descendant and tree bookkeeping at spawn time; PhaseRunner's cancel_all_pending() at the sending cutoff closes their timer coroutines without running them, so the rollback never ran and has_pending_branch_work() stayed True until the grace period expired. Track delayed children until they dispatch and roll back the ones that never did in expire_replay_deadlines(), via the same _rollback_failed_first_turn + _finalize_failed_dispatches path a post-cutoff dispatch refusal takes. They count as children_truncated and the joins they gated drain. Signed-off-by: Francesco Di Natale <3429989+FrankD412@users.noreply.github.com>
Try out this PRQuick install: pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@8e14c26dfc7c4b56685c29c26b1c1252de30f856Recommended with virtual environment (using uv): uv venv --python 3.12 && source .venv/bin/activate
uv pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@8e14c26dfc7c4b56685c29c26b1c1252de30f856Last updated for commit: |
…g cutoff The delayed-turn-0 rollback did not cover every child the sending cutoff can strand. Runs with that fix still waited out the whole grace period with one leaked child: `leaked state at cleanup: 0 active_joins, 0 future_joins, 1 tracked children, 1 parents with descendants`. Three more call sites put a DAG child's next turn on the shared LoopScheduler: - agentic_replay._dispatch_next_turn: a child continuation with think-time delay_ms - agentic_replay._dispatch_snapshot_for_profiling: a live child seeded from the trajectory snapshot at profiling start - request_rate.handle_credit_return: a child continuation with delay_ms PhaseRunner's cancel_all_pending() closes those timers unrun, so the child never returns another credit or reaches on_child_stopped, and has_pending_branch_work() stays True. BranchOrchestrator now records these children with park_child_turn(). The timer body claims its turn with unpark_child_turn() before dispatching. expire_replay_deadlines() stops every child still parked at the cutoff via on_child_stopped, so it counts in children_truncated and its parent and tree drain. Parking only happens in phases where PhaseRunner calls expire_replay_deadlines() (it is skipped only for the accelerated-warmup handoff, which never parks). Signed-off-by: Frank Di Natale <3429989+FrankD412@users.noreply.github.com> Signed-off-by: Francesco Di Natale <3429989+FrankD412@users.noreply.github.com>
Without a shared scheduler, _abandon_delayed_children cancelled every task in _delayed_dispatch_tasks whenever any delayed child was still pending. A task that had already popped its child from _pending_delayed_children and was awaiting dispatch_first_turn was cancelled too, so neither its own refusal rollback nor the abandon rollback covered that child, and its bookkeeping held the phase until the grace period expired. Key the fallback tasks by child x_correlation_id and cancel only the tasks of children being rolled back. A task already dispatching finishes and settles its own result. Production always passes the shared scheduler, so this only affects isolated callers. Also drop review-flagged docstrings that restated test and helper names. Signed-off-by: Francesco Di Natale <3429989+FrankD412@users.noreply.github.com>
At the start of profiling, _dispatch_snapshot_for_profiling restores each live child stream from the warmup snapshot and sent its next turn through issue_credit, ignoring the result. issue_credit's False covers both "refused" and "issued, was the final credit", so a child refused after leaving the parked set (for example by --request-count or the duration cutoff) was never reported to the orchestrator: its parent's join stayed pending and the phase waited out the grace period. Route both the delayed and the immediate snapshot child paths through _issue_child_continuation_or_drain, the dispatcher normal child continuations already use. It surfaces REJECTED explicitly and calls on_child_stopped (counted in children_truncated, join released), keeps DEFERRED admissions, and registers the replay barrier's refusal cleanup. Root dispatch is unchanged, and request and conversation counting are unchanged: the counter ignores counts_toward_phase_target for child turns. Signed-off-by: Francesco Di Natale <3429989+FrankD412@users.noreply.github.com>
FrankD412
marked this pull request as ready for review
October 10, 2026 03:41
…or what they do unpark_child_turn returns False when the cutoff already stopped the child, so it is a conditional claim: rename it try_claim_parked_child_turn. _abandon_delayed_children also stops children parked between turns, not only the turn-0 offsets children_delayed counts: rename it _abandon_cancelled_child_dispatches. No behaviour change. Signed-off-by: Frank Di Natale <3429989+FrankD412@users.noreply.github.com>
Collaborator
Author
|
Hardware results (AgentX scenario, conc 1, All 21 profiling runs from the
The Slurm |
Port of the test half of ai-dynamo#1540 f6c42b2. A delayed-dispatch timer that already fired is a running task, which cancel_all_pending() does not close, so it can resume after the cutoff has rolled its child back. The test resumes such a timer after expire_replay_deadlines() and asserts nothing is dispatched; it fails without the guard in _dispatch_first_turn_after_offset. The upstream commit's other half (dropping a redundant check in _sleep_think_ms) does not apply: this fork has no join think-time. Signed-off-by: Frank Di Natale <3429989+FrankD412@users.noreply.github.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.
Port of ai-dynamo#1540 (head
f6c42b2fa), except the changes listed under "Not included from ai-dynamo#1540". The upstream PR is still open, so if it changes in review this port will be updated to match.Why
A duration-bounded agentic run (AgentX scenario,
semianalysis_cc_traces_weka_062126_256k,--benchmark-duration 3600 --benchmark-grace-period 7200) finishes every request at the duration cutoff, then idles for the whole--benchmark-grace-periodbefore the phase completes:Per-request metrics such as TTFT and ITL are unaffected, but the stall distorts every throughput metric. Request, output-token and total-token throughput are divided by the phase's request window, and that window stays open until the grace period expires. A stalled run therefore reports throughput understated by a factor of (duration + grace) / duration, 3× with a 3600 s duration and a 7200 s grace period, even though it completed the same requests as a run that did not stall.
benchmark_durationstill reports the configured duration, so nothing in the export flags the distortion. The run also holds its allocation idle for the whole grace period.Cause. A SPAWN child whose first request comes after its branch start is dispatched later, by
BranchOrchestrator._start_delayed_first_turnthrough the sharedLoopScheduler. Its join, descendant and tree bookkeeping is registered when the child spawns. At the sending cutoff,PhaseRunnercallscancel_all_pending(), which closes the timer coroutine without running it. The child's rollback in_dispatch_first_turn_after_offsettherefore never runs. Sohas_pending_branch_work()stays True and the runner waits out the full grace period. Cleanup then logsBranchOrchestrator leaked state at cleanup.What
src/aiperf/timing/branch_orchestrator.py: track delayed children in_pending_delayed_childrenfrom scheduling until they dispatch.PhaseRunneralready callsexpire_replay_deadlines()right aftercancel_all_pending(). That method now first runs_abandon_delayed_children(), which rolls back every child that never dispatched. It uses the same_rollback_failed_first_turn+_finalize_failed_dispatchespath a refused dispatch after the cutoff takes. These children count aschildren_truncated, and the joins waiting on them complete. Children already in flight are unchanged, and so is the accelerated-warmup -> profiling handoff (preserve_branch_handoffskipsexpire_replay_deadlines()).tests/unit/timing/test_branch_orchestrator_dispatch_offset.py: 5 tests using the realLoopScheduler, cancelling and then expiring in the same order asPhaseRunner.tests/integration/test_weka_flat_split_e2e.py:test_child_delayed_past_duration_cutoff_does_not_hold_phase_for_grace.docs/benchmark-modes/dag.md: the branch-stats paragraph now covers delayed children cut off at the duration limit.The upstream diff applies cleanly here. This repo's
branch_orchestrator.pyhas diverged from aiperf main, but every method the fix calls exists and behaves the same way here.Not included from ai-dynamo#1540
These ai-dynamo#1540 changes touch join think-time in the orchestrator spine, which this fork does not have. This repo branched from aiperf
mainat2eb04aa2f(2026-07-29), before ai-dynamo#1218 added the spine'srounds/think_time_msandBranchOrchestrator._sleep_think_ms(2026-07-31). There is nothing here for them to apply to:862254672fix(timing): interrupt join think-time when the phase stops sendingdcc963886fix(timing): keep think-time for joins still sendable at the cutoff (includes theCreditIssuer.can_dispatch_join/can_dispatch_child_turnchecks, used only by that code)_sleep_think_mshalf off6c42b2fa(dropping a redundantremaining <= 0check). Its other half, the test for the abandoned-child guard, is ported as8e14c26df.Child continuation think-time (
delay_mson a child's next turn) is covered by the second commit below. If this fork later syncs past ai-dynamo#1218, it picks up these fixes along with the feature.Second commit: children whose next turn is cancelled at the cutoff
The first commit was not enough. On the SemiAnalysis cluster (Qwen3.8-Flash-Next-NVFP4, AgentX scenario, conc 1, duration 3600 s, grace 7200 s), 2 of 3 runs with it still waited out the full grace period. Its rollback did fire (
children_truncated=17, the same 17 delayed children), but one child still leaked:leaked state at cleanup: 0 active_joins, 0 future_joins, 1 tracked children, 1 parents with descendants. Without the fix, all 3 runs stalled with 17-19 leaked children.Cause. Three more call sites put a DAG child's next turn on the shared
LoopScheduler, and none of them tell the orchestrator if the cutoff cancels it:agentic_replay._dispatch_next_turndelay_msagentic_replay._dispatch_snapshot_for_profilingrequest_rate.handle_credit_returndelay_mscancel_all_pending()closes those timers unrun, so the child never returns another credit or reacheson_child_stopped, andhas_pending_branch_work()stays True.Fix.
BranchOrchestrator.park_child_turn()records a child whose next turn waits on a timer.unpark_child_turn()before dispatching.expire_replay_deadlines()stops every child still parked at the cutoff throughon_child_stopped, so it counts inchildren_truncatedand its parent and tree drain.PhaseRunnercallsexpire_replay_deadlines(). The accelerated-warmup handoff, where that call is skipped, never parks.Tests.
test_weka_flat_split_e2e.py:test_child_continuation_past_duration_cutoff_does_not_hold_phase_for_grace: a worker whose turn 1 is recorded about 4000 s after turn 0.test_snapshot_seeded_child_turn_past_duration_cutoff_does_not_hold_phase_for_grace: an AgentX trajectory sampled at t* ≈ 0.75 s, so profiling seeds the worker's turn 1 about 4000 s out.elapsed=25.00s | grace_period_timeout=True,leaked state at cleanup: ... 4 tracked childrenelapsed=25.00s | grace_period_timeout=True,leaked state at cleanup: ... 1 tracked children, 1 parents with descendants(the cluster signature)elapsed=5.00s, no warningtests/unit/{timing,credit,common,records}test_records_manager.py::test_completed_excludes_analyzer_metrics,UnknownScenarioError) also fails on agentx-harnessmaster89b2186test_weka_flat_split_e2e.py(-m integration)Not yet verified: a rerun of the cluster study with this commit.
Verification of the first commit (macOS)
branch_orchestrator.pychangechildren_truncated=0tests/unit/timing/with the changetests/integration/test_weka_flat_split_e2e.py(-m integration)ruff check/ruff format --checkon changed filesNot run locally: the full
tests/unit/suite, or a real GPU rerun.Note
Medium Risk
Changes phase drain and DAG bookkeeping at the sending cutoff; incorrect rollback could truncate children early or leave joins stuck, but behavior is heavily tested and scoped to timer-cancel paths.
Overview
Fixes duration-bounded DAG runs stalling through the full grace period when cancelled scheduler timers left phantom branch work (
has_pending_branch_work()stayed true).BranchOrchestratornow tracks delayed turn-0 SPAWN children in_pending_delayed_childrenand rolls them back in_abandon_cancelled_child_dispatches()(invoked fromexpire_replay_deadlines()aftercancel_all_pending()), counting them aschildren_truncatedand releasing parent joins. Delayed-dispatch fallback tasks are keyed by child correlation id so cutoff can cancel only pending sleeps.park_child_turn/try_claim_parked_child_turncover children waiting on think-time or snapshot-seeded timers; cutoff stops still-parked children viaon_child_stopped.agentic_replayandrequest_ratepark delayed child continuations and dispatch through claim helpers so stale timer bodies do not issue after cutoff.Docs (
dag.md) explain cutoff behavior for delayed and mid-tree children. Tests add orchestrator, strategy, and Weka integration coverage for past-cutoff delayed dispatch, continuations, and snapshot-seeded children.Reviewed by Cursor Bugbot for commit 9fcb1e0. Bugbot is set up for automated code reviews on this repo. Configure here.