Skip to content

perf(router): probe worker availability with an Fx set - #18008

Merged
jthomson04 merged 3 commits into
mainfrom
jthomson04/router-fx-availability
Nov 5, 2026
Merged

jthomson04 merged 3 commits into
mainfrom
jthomson04/router-fx-availability

Conversation

@jthomson04

@jthomson04 jthomson04 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Selection checks the frontend's available-worker set once per worker on every admission. That happens in candidate preparation, best_cached_tokens, the queue gate, and the scheduler actor's candidate collection when ranks are full. The set comes from Client::available_instance_ids() as a std HashSet, so with 2,048 workers each admission pays for thousands of SipHash probes. This PR converts the provider's set to an FxHashSet once per distinct set and probes that instead. The runtime hands out the same Arc until membership changes, so the conversion runs only when membership changes.

selection_replay_bench (AgentX workload, 2,048 workers) Base This PR Change
Busy thresholds on, saturation (booked req/s) 6,832 / 7,037 8,238 / 8,207 +18.6%
Busy thresholds on, admission p50 at saturation 74 ms 63 ms −15%
Busy thresholds on, scheduler actor (kcycles per admission) 451 377 −16%
Baseline, run_selection callers (kcycles per admission) 521 471 −10%
Baseline saturation / admission p50 at 4.6k req/s 12.1k / 0.60 ms 12.1k / 0.58 ms flat

Setup: Tyche Grace node. Both arms ran at once, one per socket, swapping sockets between the two rounds, replaying the same corpus (equal corpus_digest). The cycle counts come from window-bracketed perf profiles. Busy thresholds: --active-decode-blocks-threshold 0.25 --active-prefill-tokens-threshold-frac 1.0. Bench: #17959.

Key Changes

  • AvailableWorkerIds (scheduling/types.rs) wraps the WorkerAvailabilityProvider and caches the last converted set against its source Arc. Holding the source keeps its address from being reused, so a pointer match means the same set. A per-request set, such as a LoRA filter's, is converted per request, at about the cost of building it.
  • SchedulerQueue wraps the provider once and shares it between the handle and the actor. RoutingEligibility and PreparedAdmission take the Fx set (WorkerIdSet).
  • any_eligible_worker_rank still walks the smaller of the allowed and available sets when one is sparse. Each set type now has its own branch.
  • The runtime client (RoutingInstances) now keeps its routable-ID set's Arc across rebuilds that leave membership unchanged (overload updates, expiry, reconciliation), as it already did for the overloaded set. Without this, each overload update forced a new conversion and a full compare in place of a pointer compare.

Validation

  • cargo test -p dynamo-kv-router --lib (1,164 passed, including a new test for the conversion cache).
  • cargo test -p dynamo-router-policies --test default_policy, cargo test -p dynamo-runtime --lib component::client (including a new test that overload updates keep the availability snapshot), and the lib/llm availability test.
  • cargo clippy --all-targets -p dynamo-kv-router -p dynamo-router-policies -p dynamo-worker-selection-policy-catalog -p dynamo-llm -p dynamo-runtime -- -D warnings.

Issues and Ongoing Work

🤖 Generated with Claude Code

@jthomson04
jthomson04 requested a review from a team as a code owner November 4, 2026 04:36
@github-actions github-actions Bot added the perf label Nov 4, 2026
@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

The scheduler now represents available workers with WorkerIdSet and a shared, cached AvailableWorkerIds snapshot. Eligibility filtering and queue processing use these types to obtain and evaluate available workers.

Changes

Worker availability scheduling

Layer / File(s) Summary
Worker availability snapshot types
lib/kv-router/src/scheduling/types.rs
Adds WorkerIdSet and AvailableWorkerIds. Repeated retrievals reuse a conversion when the provider returns the same source allocation.
Worker eligibility filtering
lib/kv-router/src/scheduling/filter.rs, lib/llm/src/kv_router.rs
Updates eligibility APIs to accept WorkerIdSet. Sparse rank enumeration compares allowed and available set capacities and scans the smaller set when it is below the threshold.
Queue availability snapshot integration
lib/kv-router/src/scheduling/queue.rs
Stores a shared availability snapshot and uses it when processing requests, checking eligibility, and selecting workers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: Low · up to eb426

Mixed LoRA and ordinary traffic may lose some of the intended admission-performance benefit, but worker selection remains correct. This is mergeable with awareness of that performance limitation.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title "perf(router): probe worker availability with an Fx set" matches the main change. The changeset replaces the std HashSet availability checks with an FxHashSet-based WorkerIdSet, cached throu…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description covers the Overview, Key Changes, and Validation sections, and it gives detailed benchmark data and a clear rationale for the change. It has no Issues and Ongoing Work list entry beyon…
Full details: Title check

Explanation

The title "perf(router): probe worker availability with an Fx set" matches the main change. The changeset replaces the std HashSet availability checks with an FxHashSet-based WorkerIdSet, cached through AvailableWorkerIds, and the PR objectives describe the same change. The title is specific and follows a conventional commit style.

Full details: Description check

Explanation

The description covers the Overview, Key Changes, and Validation sections, and it gives detailed benchmark data and a clear rationale for the change. It has no Issues and Ongoing Work list entry beyond a related reference (#17959), and it does not mention closing an issue. The template's "Closes #XXXX" line is absent. The Pull Request Checklist item ("I used Dynamo's /pr-checklist skill") is also not shown as checked. These gaps are non-critical, so the description is mostly complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 12 functions across 4 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between de49ef3 and eb426f4.

Files selected for processing: 4
  • lib/kv-router/src/scheduling/filter.rs
  • lib/kv-router/src/scheduling/queue.rs
  • lib/kv-router/src/scheduling/types.rs
  • lib/llm/src/kv_router.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

@jthomson04
jthomson04 requested a review from a team as a code owner November 4, 2026 04:53
@devin-ai-integration

Copy link
Copy Markdown
Contributor

❌ Dynamo PR CI failed — run 41775881272 (attempt 1) on 147fddc30e

Gate checks: ❌ backend-status-check · ✅ deploy-status-check · ❌ dynamo-status-check

Other Jobs
dynamo-runtime ❌ 2 ✅ 5
planner ❌ 2 ✅ 3

Failure details

4 jobs failed, all with the same 2 planner unit tests in test_live_retune_frontend_config.py: the frontend/worker argparser rejects --router-mode power-of-two (valid choices: round-robin, random, kv, direct, least-loaded, device-aware-weighted). Both architectures fail identically, so this is not an infrastructure problem.

❌ planner / CPU Test cpu, amd64: invalid choice 'power-of-two' for --router-mode

Job: planner / CPU Test cpu, amd64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126400381798 -R ai-dynamo/dynamo --log-failed

_______________ test_effective_argv[command7-args7-power_of_two] _______________
E   AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (choose from 'round-robin', 'random', 'kv', 'direct', 'least-loaded', 'device-aware-weighted')
E   assert False
______ test_worker_router_mode_matches_the_real_worker_parser[args3-env3] ______
E   argparse.ArgumentError: argument --router-mode: invalid choice: 'power-of-two' (choose from 'round-robin', 'random', 'kv', 'direct', 'least-loaded', 'device-aware-weighted')
E   SystemExit: 2
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]
============ 2 failed, 4170 passed, 30 skipped in 435.37s (0:07:15) ============

Two tests parametrized with router mode power_of_two fail because the real frontend and worker parsers no longer accept power-of-two as a --router-mode choice (failed on 3 auto-retries each). The test cases and the parser's allowed router modes disagree.

❌ planner / CPU Test cpu, arm64: same 2 tests, invalid choice 'power-of-two'

Job: planner / CPU Test cpu, arm64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126400381759 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
============ 2 failed, 4170 passed, 30 skipped in 546.18s (0:09:06) ============

Identical to the amd64 job: same 2 tests, same --router-mode parse error.

❌ dynamo-runtime / test / sequential cuda13.0, amd64: same 2 planner tests

Job: dynamo-runtime / test / sequential cuda13.0, amd64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126401606778 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
== 2 failed, 7778 passed, 170 skipped, 2854 deselected in 1588.81s (0:26:28) ===
##[error]A pytest stage did not record a passing result. See this step's log for the stage.

The runtime suite picks up the same 2 planner tests and fails on the same --router-mode power-of-two parse error; nothing else failed. The "custom container implementation failed" lines after it just report the non-zero pytest exit.

❌ dynamo-runtime / test / sequential cuda13.0, arm64: same 2 planner tests

Job: dynamo-runtime / test / sequential cuda13.0, arm64 · Failed step: Run CPU-only tests (parallelized) · Logs: gh run view --job 126401606663 -R ai-dynamo/dynamo --log-failed

FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two] - AssertionError: P4: cannot parse the frontend args: argument --router-mode: invalid choice: 'power-of-two' (...)
FAILED components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3] - SystemExit: 2
== 2 failed, 7778 passed, 170 skipped, 2854 deselected in 1954.89s (0:32:34) ===

Same 2 tests and error as the amd64 runtime job.

For agents
{"pr": 18008, "run_id": 41775881272, "run_attempt": 1, "head_sha": "147fddc30e1e91c2866a69d9f6f57281691f1330",
 "failures": [
  {"job": "planner / CPU Test cpu, amd64", "job_id": 126400381798, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126400381798 -R ai-dynamo/dynamo --log-failed"},
  {"job": "planner / CPU Test cpu, arm64", "job_id": 126400381759, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126400381759 -R ai-dynamo/dynamo --log-failed"},
  {"job": "dynamo-runtime / test / sequential cuda13.0, amd64", "job_id": 126401606778, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126401606778 -R ai-dynamo/dynamo --log-failed"},
  {"job": "dynamo-runtime / test / sequential cuda13.0, arm64", "job_id": 126401606663, "failed_step": "Run CPU-only tests (parallelized)", "signature": "argument --router-mode: invalid choice: 'power-of-two'", "tests": ["components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_effective_argv[command7-args7-power_of_two]", "components/src/dynamo/planner/tests/unit/test_live_retune_frontend_config.py::test_worker_router_mode_matches_the_real_worker_parser[args3-env3]"], "log_cmd": "gh run view --job 126401606663 -R ai-dynamo/dynamo --log-failed"}
 ]}

Posted automatically by Devin for run 41775881272. Updated on every full-CI run of this PR.

@jthomson04
jthomson04 force-pushed the jthomson04/router-fx-availability branch from 147fddc to 10bfe10 Compare November 4, 2026 17:36
@copy-pr-bot

copy-pr-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@jthomson04

Copy link
Copy Markdown
Contributor Author

/ok to test 10bfe10

jthomson04 and others added 2 commits November 4, 2026 13:27
Selection checks the provider's available-worker set once per worker on every
admission: candidate preparation, best cached tokens, the queue gate, and the
actor's candidate collection. The set is a std HashSet, so each check paid for
SipHash. Convert the provider's set to an FxHashSet once per distinct set (the
frontend's provider returns the same Arc until membership changes) and probe
that instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
…ate policy tests

RoutingInstances rebuilt the routable-ID set on every overload update,
expiry and reconciliation, even with membership unchanged, so each one made
the scheduler convert the available set again and fall back from a pointer
compare to a full compare of prepared rows. Share the set across rebuilds
that leave the routable IDs unchanged, as the overloaded set already is.

Build the builtin policy tests' availability sets as WorkerIdSet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
@jthomson04
jthomson04 force-pushed the jthomson04/router-fx-availability branch from 10bfe10 to eee5e68 Compare November 4, 2026 21:28
@jthomson04
jthomson04 enabled auto-merge (squash) November 4, 2026 21:51
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
@jthomson04
jthomson04 merged commit 83f28d1 into main Nov 5, 2026
124 of 125 checks passed
@jthomson04
jthomson04 deleted the jthomson04/router-fx-availability branch November 5, 2026 01:48
ishandhanani added a commit to ishandhanani/dynamo that referenced this pull request Nov 5, 2026
Third upstream merge for the split v2 PR: the five commits past
53015f9 that landed while the second merge was validated. Four
conflicts, each placed in the owner crate, upstream behavior whole, the
stack's moves intact, no shims.

- ai-dynamo#18008 probe worker availability with an Fx set: WorkerIdSet and the
  per-set AvailableWorkerIds cache in the kernel's types.rs, the listed-
  worker walk in the kernel's filter.rs, the actor's cached availability in
  queue.rs (auto-merged); WorkerIdSet exported to hosts through
  dynamo_scheduler::host (lib/llm's LoRA availability filter) and to
  plugins through dynamo_kv_router::plugins::prelude (the builtin
  default_policy test stays prelude-only).
- ai-dynamo#18084 CRTC stale-leaf sweep trigger scaled with tree size:
  lib/router/kv-indexer concurrent_radix_tree_compressed (auto-merged).
- ai-dynamo#17900 shared fixed-lane bench primitive: lib/bench (auto-merged).
- ai-dynamo#17947 planner priority pools, ai-dynamo#17625 beam tool-call finalization:
  outside the router, auto-merged.

Signed-off-by: Ishan Dhanani <ishandhanani@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants