Skip to content

fix(router): make the per-class decode wait limit correct for disaggregated serving - #108

Draft
ishandhanani wants to merge 225 commits into
idhanani/base-4cb0a16e4cfrom
idhanani/dyn-16543-disagg-decode-gate
Draft

ishandhanani wants to merge 225 commits into
idhanani/base-4cb0a16e4cfrom
idhanani/dyn-16543-disagg-decode-gate

Conversation

@ishandhanani

Copy link
Copy Markdown
Owner

Summary

PR ai-dynamo#16618 added two separate things, and this PR is about the second:

This PR implements (b) so it is correct in both topologies:

  • Aggregated: unchanged. The router's one queue checks both class fractions on the same workers.
  • Disaggregated with KV routing: the prefill router's queue is the only admission gate. It checks the class prefill fraction on the prefill workers and the class decode fraction on the decode workers. For the decode check, it reads the decode router's booked load directly, with no message to the decode actor. When the decode router frees KV, it fires a separate broadcast signal (RoomChanged) that wakes the prefill queue. That signal does not share the decode actor's notify_one CapacityUpdates. The decode router books each decode leg that follows a remote prefill (RequestPhase::Decode with staged KV) straight away: no class wait and no deadline expiry, because the leg's prefill is already running. Router-wide reject thresholds still apply when the decode worker is selected.

Why this changes behavior: disaggregated KV routing has two policy-class queues in series, prefill then decode, and both get the same policy profile. As merged, the class decode limit did nothing at the prefill router, which never tracks held blocks. At the decode router it could hold a decode leg after its prefill was already dispatched. That held prefill KV in place, and broke SGLang bootstrap, where prefill and decode must run at the same time.

Design:

  • lib/kv-router: a new PairedPoolRoom hook that the host installs on a scheduler (set_paired_pool, like set_selection_trace). In each gate pass the queue checks it together with the existing prefill check, in both the drain path and the never-queue direct path. Only classes with a decode_busy_threshold_frac use it, and without a hook nothing changes. For requests without a LoRA, one answer is cached per class per gate pass. LocalScheduler::any_rank_has_room reads booked load from the slot registry with no actor message, and subscribe_room_changed exposes the broadcast hint. The hint fires on local releases, the actor's cleanup drain, peer-router state changes, and worker config changes. ScheduleRequest / SelectionOperation carry an in-process admitted_upstream flag, so the queue books those requests directly. Wire DTOs (PreprocessedRequest, RouterConfigOverride) are unchanged.
  • lib/llm: the frontend now installs the decode RoutingHost on the PrefillRouter whenever routing is KV, not only under conditional disagg. Every prefill binding's KV router gets a lazily resolved DecodePoolRoom (it holds a Weak<KvRouter>). Until the decode host exists, the pool reports room, so nothing changes. RoutingHost maps a decode leg with staged_kv_cleanup (frontend-only, #[serde(skip)]) to AdmittedUpstream. A conditional-disagg bypass also has RequestPhase::Decode but has nothing staged, so the decode queue stays its gate and its behavior is unchanged.

Decode-room eligibility (what the prefill hop applies):

  • Applied: decode worker reachability and the request's LoRA. Both come from reusing the decode router's existing availability filter. That filter narrows LoRA candidates by the request's pin and allowed workers, which at the prefill hop name prefill workers. So a LoRA request that also carries a prefill pin or allowed set is checked on reachability alone.
  • Not applied: a decode worker pin (decode_worker_id), allowed-worker hints, routing constraints, and the KV-transfer topology domain of the chosen prefill worker.
  • Decode selection still enforces exact decode eligibility (RoutingEligibility, lib/kv-router/src/scheduling/filter.rs). So when the coarse gate admits a request, it is routed at decode exactly as before feat(router): add a decode busy threshold to policy-class queues ai-dynamo/dynamo#16618.

Known limits and follow-ups:

  • Between the decode-room check at the prefill hop and the decode booking (after prefill dispatch), a burst of prefill admissions can overshoot the decode fraction. Each admission sees decode room before the earlier legs have booked. A pending-decode reservation count would close this gap.
  • If either hop is not KV-routed, nothing checks the class decode limit.
  • Request classifiers run only on the decode/aggregated router, so a class a classifier assigns there is not known at the prefill hop. This is unchanged by this PR.

Part of ai-dynamo#16543

Validation

All commands use a per-worktree CARGO_TARGET_DIR.

  • cargo fmt --all --check: clean.
  • cargo clippy -p dynamo-kv-router -p dynamo-llm --all-targets -- -D warnings: pass, no warnings.
  • cargo test -p dynamo-kv-router --lib: 1134 passed, 0 failed, 3 ignored.
  • cargo test -p dynamo-kv-router --lib --features standalone-selection, which also runs the selection-core tests: 1393 passed, 0 failed, 3 ignored.
  • cargo test -p dynamo-llm --lib kv_router::: 467 passed, 0 failed. The new end-to-end test also passed 5 of 5 repeated runs.
  • cargo bench -p dynamo-kv-router --no-run: builds (4 bench targets). With --features standalone-selection,bench: builds (6 targets).
  • cd lib/bindings/python && cargo check: pass.
  • pre-commit run --files <changed files>, run through uvx: 11 hooks passed, the rest skipped (no matching files).
  • python3 docs/fern/scripts/docs_lint.py <changed page>: 0 errors. The 5 NAV warnings are on other, pre-existing pages.

New tests:

  • scheduling::queue::tests::paired_pool_holds_until_its_room_signal_releases_the_request: the request stays held while the paired pool has no room. Room alone does not release it; the pool's room signal does, with no other capacity change.
  • scheduling::queue::tests::paired_pool_rejects_a_never_queue_class_like_its_own_workers: the never-queue direct path and the queue agree.
  • scheduling::queue::tests::paired_pool_is_ignored_by_classes_without_a_decode_limit: a class without a decode limit never consults the hook, and its prefill limit behaves as before.
  • scheduling::queue::tests::admitted_upstream_skips_class_limits_but_not_reject_thresholds: the request is booked past a full class limit and ahead of the backlog, an already-passed deadline does not expire it, and reject thresholds still reject it.
  • scheduling::local::tests::pool_room_reads_booked_decode_blocks_and_signals_releases: the direct read honors the worker filter, and the room signal fires on release even with queueing disabled.
  • kv_router::routing_host::tests::disagg_decode_wait_limit_gates_the_prefill_hop_and_never_the_decode_hop: two real KV hosts share one policy file. Staged decode legs book past the class decode limit. A conditional-bypass-shaped decode request still waits at decode. A new prefill request waits at the prefill hop while decode is full, and freed decode KV releases it.
  • kv_router::prefill_router::tests::kv_decode_host_resolves_the_decode_pool_without_conditional_disagg.

Queue-gate benchmark for the aggregated path (selection_core no_hints_queue_gate, base 4cb0a16 vs this branch, shared host with load average 4 to 7):

workers base this branch
64 39.6 us 36.1 us
512 90.5 us 76.7 us
4096 614.6 us 608.0 us

No regression within noise. The aggregated gate only adds one per-class array reset and one early-returning check per predicate.

andrewpaprotsky and others added 2 commits October 29, 2026 10:46
Signed-off-by: Andrew Paprotsky <apaprotskyi@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added fix documentation Improvements or additions to documentation router labels Oct 29, 2026
indrajit96 and others added 24 commits October 29, 2026 07:41
Automated refresh from the public Dynamo Google Calendar.

Generated by .github/workflows/community-events-refresh.yml.

Signed-off-by: dynamo-ops <170655669+dynamo-ops@users.noreply.github.com>
…dynamo#17014)

Signed-off-by: Piotr Tarasiewicz <ptarasiewicz@nvidia.com>
Signed-off-by: Zhan Xue <zhan.xue@intel.com>
…i-dynamo#17595)

Signed-off-by: vanshil shah <49670736+vanshilshah97@users.noreply.github.com>
…l selection (ai-dynamo#17524)

Signed-off-by: Sachal Malick <s@chal.ai>
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
Co-authored-by: Sachal Malick <s@chal.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…7594)

Signed-off-by: Dr. Stefan Schimanski <sschimanski@nvidia.com>
…RPC (ai-dynamo#17032)

Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
Signed-off-by: GLAMR <svc-glamr@nvidia.com>
…i-dynamo#17523)

Signed-off-by: hongkuanz <hongkuanz@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…vLLM v0.32.0 (ai-dynamo#17402)

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Signed-off-by: Pranjul Gupta <pranjulgupta0203@gmail.com>
Signed-off-by: Dr. Stefan Schimanski <sschimanski@nvidia.com>
Signed-off-by: devivasudevan <49675305+devivasudevan@users.noreply.github.com>
Co-authored-by: Pranjul Gupta <pranjulgupta0203@gmail.com>
Co-authored-by: devivasudevan <49675305+devivasudevan@users.noreply.github.com>
…eck (ai-dynamo#17526)

Signed-off-by: Dmitry Tokarev <dtokarev@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ynamo#17350)

Signed-off-by: Guan Luo <gluo@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… to 0.17.3 (ai-dynamo#17536)

Signed-off-by: Dmitry Tokarev <dtokarev@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
)

Signed-off-by: Yao, Qing <qing.yao@intel.com>
Signed-off-by: pallavi jaini <pallavi.jaini@intel.com>
Signed-off-by: Pallavi Jaini <pallavi.jaini@intel.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: pallavi jaini <pallavi.jaini@intel.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Dmitry Tokarev <dtokarev@nvidia.com>
Signed-off-by: jain-ria <riajain@NVIDIA.com>
…te a PR (ai-dynamo#17471)

Signed-off-by: Alec Flowers <aflowers@nvidia.com>
Signed-off-by: Alec <35311602+alec-flowers@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Alec Flowers <aflowers@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: tanmayv25 <tanmay2592@gmail.com>
glamr-agent and others added 29 commits November 4, 2026 10:40
…mo#18000)

Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
…ages (ai-dynamo#17925)

Signed-off-by: Dmitry Tokarev <dtokarev@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…i-dynamo#17963)

Signed-off-by: Ege Cem Kirci <40044930+egecemkirci@users.noreply.github.com>
…rom suite (ai-dynamo#16798)

Signed-off-by: Nate Mailhot <nmailhot@nvidia.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…i-dynamo#16570)

Signed-off-by: Krishnan Prashanth <kprashanth@nvidia.com>
…ge refresh (ai-dynamo#18064)

Signed-off-by: Dan Gil <dagil@nvidia.com>
Co-authored-by: Dmitry Tokarev <dtokarev@nvidia.com>
… builds (ai-dynamo#18021)

Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
Signed-off-by: GLAMR <svc-glamr@nvidia.com>
…emas (ai-dynamo#17518)

Signed-off-by: Dr. Stefan Schimanski <sschimanski@nvidia.com>
…o#17064)

Signed-off-by: neilkg <33635204+neilkg@users.noreply.github.com>
… traces (ai-dynamo#17516)

Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
Co-authored-by: Yuewei Na <nv-yna@users.noreply.github.com>
Signed-off-by: Yuewei Na <nv-yna@users.noreply.github.com>
Co-authored-by: Yuewei Na <nv-yna@users.noreply.github.com>
…#17887)

Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
Signed-off-by: GLAMR <svc-glamr@nvidia.com>
…6474)

Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
Signed-off-by: GLAMR <svc-glamr@nvidia.com>
Co-authored-by: Tushar Sharma <tusharma@nvidia.com>
…ai-dynamo#18017)

Signed-off-by: furionw <qiwa@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s the TRT-LLM mock does (ai-dynamo#18102)

Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jacky <18255193+kthui@users.noreply.github.com>
Signed-off-by: Jacky <18255193+kthui@users.noreply.github.com>
Signed-off-by: Jacky <18255193+kthui@users.noreply.github.com>
)

Signed-off-by: Dr. Stefan Schimanski <sschimanski@nvidia.com>
Signed-off-by: hongkuanz <hongkuanz@nvidia.com>
…ted (ai-dynamo#18141)

Signed-off-by: Sungsoo Ha <sungsooh@nvidia.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Yogendra Pandey <20666257+ynpandey-nv@users.noreply.github.com>
…dynamo#17707)

Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ze (ai-dynamo#18084)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-dynamo#17625)

Signed-off-by: neilkg <33635204+neilkg@users.noreply.github.com>
Signed-off-by: doxu <doxu@nvidia.com>
Co-authored-by: Neil Gollapudi <33635204+neilkg@users.noreply.github.com>
…sagg-decode-gate

# Conflicts:
#	lib/kv-router/src/scheduling/queue.rs
#	lib/kv-router/src/scheduling/types.rs

This branch had an error being deployed

1 failed deployment
external_collaborator — 5b1fa3c4 Deployed Nov 5, 2026 by ishandhanani via ok-to-test #546
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.