[None][feat] ADP conversation router: configurable least-queued placement for new conversations#16294
Conversation
9ea88bd to
ae5515b
Compare
…ment for new conversations kv_cache_routing_conversation_affinity pins conversations to ranks and round-robins first turns, equalizing per-rank conversation counts. But conversation weight (turn rate, agentic fan-out, prefill length) is not uniform: on an agentic multi-turn workload at concurrency 1000 we measured near-uniform per-rank conversation counts (max/min 1.14x) yet 1.8-3.4x per-rank queue-depth skew, with the coldest ranks dummy-padded in up to 15.7% of iterations while hot ranks queued 24 deep. Add attention_dp_config.kv_cache_routing_new_conv_placement: - round_robin (default): existing behavior, unchanged. - least_queued: place unpinned requests (first turns, conversation-less requests, sticky overflow) on the rank with the fewest live requests, reusing the existing _least_loaded helper. Heavy pinned conversations keep their rank's count high, so new conversations steer away from hot ranks; the in-batch count accumulator spreads bursts valley-first. Sticky returns, soft/hard caps, and the padding invariant are unchanged; routing stays a deterministic pure function of allgathered state, as required by the replicated no-broadcast protocol. Measured on a 4x(TP8, attention-DP) prefill disagg deployment at concurrency 1000 (agentic multi-turn replay, 95% KV block reuse): per-rank queue skew 3.4x -> 2.0x, TTFT p90 -15.5%, p99 -17.2%, KV reuse unchanged, throughput neutral. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
ae5515b to
01da19e
Compare
|
/bot run --disable-fail-fast |
|
Caution Review failedFailed to post review comments. GitHub was unavailable or timed out while CodeRabbit was posting the review. Please request a new review later if the pull request still needs one. This happened while posting 1 inline comment. Use ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (4)
🧰 Additional context used📓 Path-based instructions (6)**/*📄 CodeRabbit inference engine (AGENTS.md)
Files:
**⚙️ CodeRabbit configuration file
Files:
**/*.py📄 CodeRabbit inference engine (CODING_GUIDELINES.md)
Files:
**/*.{cpp,h,cc,cuh,cu,hpp,py}📄 CodeRabbit inference engine (CODING_GUIDELINES.md)
Files:
**/*.{py,cpp,h,hpp,cc,cxx,cu,cuh}📄 CodeRabbit inference engine (AGENTS.md)
Files:
tests/**⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (14)📚 Learning: 2026-02-13T10:15:37.120ZApplied to files:
📚 Learning: 2026-04-24T23:54:27.755ZApplied to files:
📚 Learning: 2026-05-29T08:18:25.109ZApplied to files:
📚 Learning: 2026-05-29T08:18:27.934ZApplied to files:
📚 Learning: 2026-06-25T13:04:55.284ZApplied to files:
📚 Learning: 2026-03-06T11:45:02.068ZApplied to files:
📚 Learning: 2026-03-09T12:34:56.631ZApplied to files:
📚 Learning: 2026-04-16T00:07:18.998ZApplied to files:
📚 Learning: 2026-04-24T03:28:03.123ZApplied to files:
📚 Learning: 2026-05-16T01:43:01.298ZApplied to files:
📚 Learning: 2025-12-12T03:27:08.565ZApplied to files:
📚 Learning: 2026-02-25T01:48:05.078ZApplied to files:
📚 Learning: 2026-05-29T08:18:31.547ZApplied to files:
📚 Learning: 2026-02-27T21:32:25.857ZApplied to files:
📝 WalkthroughWalkthroughAdds a configurable policy for assigning new conversations to tensor-parallel ranks. The default remains round-robin; ChangesConversation placement policy
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ADPRouter
participant ConversationAwareADPRouter
participant RankSelection
ADPRouter->>ConversationAwareADPRouter: Pass new_conv_placement
ConversationAwareADPRouter->>RankSelection: Select least-loaded rank when configured
ConversationAwareADPRouter->>RankSelection: Select round-robin rank otherwise
RankSelection-->>ConversationAwareADPRouter: Return target rank
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
PR_Github #58882 [ run ] triggered by Bot. Commit: |
|
PR_Github #58882 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
|
PR_Github #58979 [ run ] triggered by Bot. Commit: |
|
PR_Github #58979 [ run ] completed with state |
…scheduler queues Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add three cheap census logs that expose routing decisions and per-rank queueing directly: - py_executor iteration log (rides the existing print_iter_log line): num_active / num_queued / num_fetched per rank per iteration. num_queued counts CONTEXT_INIT requests with no prefill progress -- requests that have never entered a batch -- sampled at the iteration boundary (after this iteration's completions, before the next fetch). num_fetched is the number of new requests the ADP router assigned to this rank during the iteration (delta of the cumulative fetch counter). - ConversationAwareADPRouter: at most once per CENSUS_INTERVAL_SEC (60s), rank 0 logs conv_rank_census (live conversation->rank pins per rank) plus the post-assignment per-rank load snapshot. - ConversationRouter (disagg orchestrator): at most once per CENSUS_INTERVAL_SEC (60s), logs conv_server_census (live conversation->server pins per instance). No collectives and no new sync points; the censuses are log-only and never feed routing. These logs were used to diagnose a 3.4x per-rank queue skew under near-uniform per-rank conversation counts (see PR Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> NVIDIA#16294).
…scheduler queues Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add per-rank queueing and routing fields to the existing print_iter_log iteration line: num_active = 14, num_queued = 12, num_fetched = 3, num_convs = 438 - num_queued: CONTEXT_INIT requests with no prefill progress -- requests that have never entered a batch -- sampled at the iteration boundary (after this iteration's completions, before the next fetch). Counted directly instead of derived from active - scheduled, which mixes epochs (completions leave active_requests before the line prints; dummy padding is scheduled but never active). - num_fetched: new requests the ADP router assigned to this rank during this iteration (delta of the cumulative fetch counter). - num_convs: live conversations pinned to this rank by ConversationAwareADPRouter, maintained as O(1) incremental counters on bind/rebind/LRU-eviction and exposed via ADPRouter.num_pinned_conversations() (None for routers without pinning). Reading per-rank conversation counts next to queue depth distinguishes count imbalance from conversation-weight imbalance. The disagg orchestrator's ConversationRouter additionally logs a conversation->server census at most once per 60s. No collectives and no new sync points; all fields are log-only. These signals were used to diagnose a 3.4x per-rank queue skew under near-uniform per-rank conversation counts (see PR NVIDIA#16294). Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…scheduler queues Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add per-rank queueing and routing fields to the existing print_iter_log iteration line: num_active = 14, num_queued = 12, num_fetched = 3, num_convs = 438 - num_queued: CONTEXT_INIT requests with no prefill progress -- requests that have never entered a batch -- sampled at the iteration boundary (after this iteration's completions, before the next fetch). Counted directly instead of derived from active - scheduled, which mixes epochs (completions leave active_requests before the line prints; dummy padding is scheduled but never active). - num_fetched: new requests the ADP router assigned to this rank during this iteration (delta of the cumulative fetch counter). - num_convs: live conversations pinned to this rank by ConversationAwareADPRouter, maintained as O(1) incremental counters on bind/rebind/LRU-eviction and exposed via ADPRouter.num_pinned_conversations() (None for routers without pinning). Reading per-rank conversation counts next to queue depth distinguishes count imbalance from conversation-weight imbalance. No collectives and no new sync points; all fields are log-only. These signals were used to diagnose a 3.4x per-rank queue skew under near-uniform per-rank conversation counts (see PR NVIDIA#16294). Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…scheduler queues Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add per-rank routing and scheduling fields to the existing print_iter_log iteration line: num_active = 14, num_fetched = 3, num_dummy = 1, num_unscheduled = 5, num_convs = 438 - num_fetched: new requests the ADP router assigned to this rank during this iteration (delta of the cumulative fetch counter). - num_dummy: attention-DP pad dummies in this iteration's scheduled batch (0/1 per rank, padded in when the rank would otherwise run an empty batch). num_scheduled_requests counts the pad, so subtract num_dummy to recover the real scheduled count. - num_unscheduled: active requests not placed in this iteration's batch (len(active_requests) - scheduled batch_size). Sampled at schedule time, so the pad cancels in both terms and it never mixes epochs the way active - num_scheduled would (num_scheduled printed on this line is the previous iteration's). - num_convs: live conversations pinned to this rank by ConversationAwareADPRouter, maintained as O(1) incremental counters on bind/rebind/LRU-eviction and exposed via ADPRouter.num_pinned_conversations() (None for routers without pinning). Reading per-rank conversation counts next to queue depth distinguishes count imbalance from conversation-weight imbalance. No collectives and no new sync points; all fields are log-only. These signals were used to diagnose a 3.4x per-rank queue skew under near-uniform per-rank conversation counts (see PR NVIDIA#16294). Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
QiJune
left a comment
There was a problem hiding this comment.
LGTM. The deterministic tie-break can create some cross-batch affinity skew for sparse or bursty traffic, as noted in the discussion, but this is non-blocking for the high-concurrency workload targeted by this change.
…ration log Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add per-rank routing and scheduling fields to the existing print_iter_log iteration line: num_active = 14, num_fetched = 3, num_dummy = 1, num_unscheduled = 5 - num_fetched: new requests the ADP router assigned to this rank during this iteration (delta of the cumulative fetch counter). - num_dummy: attention-DP pad dummies in this iteration's scheduled batch (0/1 per rank, padded in when the rank would otherwise run an empty batch). num_scheduled_requests counts the pad, so subtract num_dummy to recover the real scheduled count. - num_unscheduled: active requests not placed in this iteration's batch (len(active_requests) - scheduled batch_size). Sampled at schedule time, so the pad cancels in both terms and it never mixes epochs the way active - num_scheduled would (the num_scheduled printed on this line is the previous iteration's). num_dummy and num_unscheduled are captured at schedule time and so lag one iteration behind num_active/num_fetched, consistent with num_scheduled_requests. No collectives and no new sync points; all fields are log-only. These signals surfaced a 1.8-3.4x per-rank queue-depth skew on an agentic multi-turn workload, motivating the least_queued placement option in NVIDIA#16294. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…ration log Diagnosing attention-DP load imbalance today requires reconstructing state from per-request metrics. Add per-rank routing and scheduling fields to the existing print_iter_log iteration line: num_active = 14, num_fetched = 3, num_dummy = 1, num_unscheduled = 5 - num_fetched: new requests the ADP router assigned to this rank during this iteration (delta of the cumulative fetch counter). - num_dummy: attention-DP pad dummies in this iteration's scheduled batch (0/1 per rank, padded in when the rank would otherwise run an empty batch). num_scheduled_requests counts the pad, so subtract num_dummy to recover the real scheduled count. - num_unscheduled: active requests not placed in this iteration's batch (len(active_requests) - scheduled batch_size). Sampled at schedule time, so the pad cancels in both terms and it never mixes epochs the way active - num_scheduled would (the num_scheduled printed on this line is the previous iteration's). num_dummy and num_unscheduled are captured at schedule time and so lag one iteration behind num_active/num_fetched, consistent with num_scheduled_requests. No collectives and no new sync points; all fields are log-only. These signals surfaced a 1.8-3.4x per-rank queue-depth skew on an agentic multi-turn workload, motivating the least_queued placement option in NVIDIA#16294. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…nnahz/dep-1083-port-flashinfer-stable-va-lifecycle-for-native-all-reduce * 'main' of https://github.com/NVIDIA/TensorRT-LLM: (83 commits) [None][feat] Bind SourceIdentity to checkpoint artifacts (NVIDIA#16159) [None][infra] Waive 4 failed cases for main in pre-merge 49550 (NVIDIA#16798) [None][fix] Make FlashInfer sampling op wrappers opaque to Dynamo (NVIDIA#16732) [None][feat] top-k: route decode to CuTe DSL GVR top-k in e2e (NVIDIA#16420) [None][feat] Default GLM-5 to the Python KV-cache transceiver (NVIDIA#16524) [None][chore] Add NVTX ranges to per-iteration ADP sync points in PyExecutor (NVIDIA#16422) [https://nvbugs/6426850][test] Unwaive Qwen3.5 397B NVFP4 ADP4 TRTLLM test (NVIDIA#16348) [https://nvbugs/6445456][fix] Restore inplace ops for functionalization v2 (NVIDIA#16410) [None][infra] Waive 1 failed cases for main in pre-merge 49229 (NVIDIA#16786) [None][fix] Load DeepSeek V4 mixed-precision NVFP4 checkpoints (NVIDIA#16433) [None][feat] ADP conversation router: configurable least-queued placement for new conversations (NVIDIA#16294) [None][infra] Waive 1 failed cases for main in pre-merge 49424 (NVIDIA#16780) [None][infra] Waive 1 failed cases for main in pre-merge 49424 (NVIDIA#16781) [TRTLLMINF-188][infra] Require approval for PerfSanity wildcard runs (NVIDIA#16777) [TRTLLM-13969][feat] Support MiniMax M3 for Disaggregated Serving (NVIDIA#16017) [NVIDIA#11932][fix] Filter CUTLASS MoE GEMM tile configs by device shared memory on SM121 (NVIDIA#12704) [None][chore] Remove attention backend test waivers (NVIDIA#16723) [TRTLLM-14540][perf] Skip fp32 state round-trip in FlashInfer GDN pre… (NVIDIA#16703) [TRTLLM-13694][feat] Add IBDB recipe provenance and refresh configs (NVIDIA#16254) [None][infra] Preview/bump/main (NVIDIA#16758) ... Signed-off-by: Hannah Zhang <hannahz@nvidia.com>
…ment for new conversations (NVIDIA#16294) Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
Summary by CodeRabbit
New Features
Tests
Description
kv_cache_routing_conversation_affinitypins each conversation to one attention-DP rank and round-robins first turns across ranks. Round-robin equalizes per-rank conversation counts, but per-conversation load (turn rate, agentic fan-out, prefill length) is far from uniform. On an agentic multi-turn workload (4×(TP8, attention-DP) prefill workers in a disaggregated deployment, concurrency 1000, ~95% KV block reuse) we measured:This PR adds
attention_dp_config.kv_cache_routing_new_conv_placement:"round_robin"(default) — existing behavior, unchanged."least_queued"— place requests that have no pinned rank yet (first turn of a conversation, conversation-less requests, sticky overflow) on the rank with the fewest live requests, reusing the existing_least_loadedhelper (previously only reachable as an RR fallback). Heavy pinned conversations keep their rank's count high, so new conversations steer away from hot ranks — emergent weight-awareness without predicting per-conversation weight. The shared in-batch count accumulator spreads bursts valley-first.Sticky returns, soft/hard caps, and the padding invariant are untouched; routing remains a deterministic pure function of allgathered state (required by the replicated no-broadcast protocol — least-loaded is stateless, one less cross-iteration state than the RR cursor).
A/B on the workload above (only this knob changed):
Remaining skew comes from already-pinned heavy conversations (not migrated, preserving prefix-cache residency by design).