-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[https://nvbugs/6550276][fix] Clamp residency to what the quota affords (allreduce-MIN across ranks) behind… #17261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,6 +176,11 @@ def __init__( | |
| scheduler_policy = CapacitySchedulerPolicy.MAX_UTILIZATION | ||
| self.policy = scheduler_policy | ||
| self.peft_cache_manager = peft_cache_manager | ||
| # Non-droppable per-sequence state (Mamba recurrent state) yields no | ||
| # evictable pages, so MAX_UTILIZATION's suspend/resume cannot recover | ||
| # from over-admission: once every resident sequence is suspended the | ||
| # pool can never drain. Bound admission instead. | ||
| self.max_resident_sequences = kv_cache_manager.max_resident_sequences() | ||
|
|
||
| # Chunking config. | ||
| self.chunking_enabled = False | ||
|
|
@@ -193,7 +198,8 @@ def __init__( | |
| f"KVCacheV2Scheduler: tokens_per_block={self.tokens_per_block}, " | ||
| f"max_num_tokens={max_num_tokens}, max_batch_size={max_batch_size}, " | ||
| f"draft_mgr={draft_mgr_name}, cross_mgr={cross_mgr_name}, " | ||
| f"enable_prefix_aware_scheduling={enable_prefix_aware_scheduling}" | ||
| f"enable_prefix_aware_scheduling={enable_prefix_aware_scheduling}, " | ||
| f"max_resident_sequences={self.max_resident_sequences}" | ||
| ) | ||
| if ctx_chunk_config is not None: | ||
| self.chunking_enabled = True | ||
|
|
@@ -309,6 +315,16 @@ def _schedule_loop(self, active_requests, inflight_request_ids): | |
| if req.state_value == self._gen_to_complete_state_value: | ||
| budget.pre_claim_peft(req) | ||
|
|
||
| # Sequences already holding a non-droppable state slot. Counted over all | ||
| # active requests (not just the ones scheduled this iteration) because a | ||
| # suspended sequence keeps its slot. | ||
| max_resident = self.max_resident_sequences | ||
| num_resident = ( | ||
| sum(1 for req in requests_list if self._is_started_request(req)) | ||
| if max_resident is not None | ||
| else 0 | ||
| ) | ||
|
Comment on lines
+318
to
+326
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Apply the residency cap to At Line 321, the scheduler counts only started context and generation requests. The only new admission gate starts at Line 425, after the Phase 1 Gate disaggregated initialization before Also applies to: 422-426 🤖 Prompt for AI Agents |
||
|
|
||
| # --- Phase 1: generation / disagg only --- | ||
| while req_it < req_it_end: | ||
| req = requests_list[req_it] | ||
|
|
@@ -403,6 +419,11 @@ def _schedule_loop(self, active_requests, inflight_request_ids): | |
| for req in pending_ctx: | ||
| if budget.requests_full: | ||
| break | ||
| # A first context chunk starts a new sequence and therefore claims a | ||
| # state slot for the rest of its lifetime. | ||
| starts_new_sequence = max_resident is not None and req.is_first_context_chunk | ||
| if starts_new_sequence and num_resident >= max_resident: | ||
| break | ||
|
Comment on lines
+422
to
+426
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 6 --glob '*.py' \
'active_requests|RequestList|schedule_request\(' \
tensorrt_llm/_torch/pyexecutorRepository: NVIDIA/TensorRT-LLM Length of output: 50376 🏁 Script executed: # First, let's look at the specific file and lines mentioned
cd /tmp && find . -name "scheduler_v2.py" -type f 2>/dev/null | head -1 | xargs wc -lRepository: NVIDIA/TensorRT-LLM Length of output: 159 🏁 Script executed: # Read the scheduler_v2.py file around lines 422-426
sed -n '400,450p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 2377 🏁 Script executed: # Get a broader view of the function containing this code
sed -n '350,500p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 6700 🏁 Script executed: # Search for context about how active_requests is populated and used in scheduler_v2
ast-grep outline tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py --view expandedRepository: NVIDIA/TensorRT-LLM Length of output: 2072 🏁 Script executed: # Look at the full _schedule_loop function signature and Phase 1 logic
sed -n '258,350p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 4545 🏁 Script executed: # Check how pending_ctx is used and whether chunks are separate request objects
rg -n "pending_ctx|is_first_context_chunk|is_last_context_chunk" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py | head -40Repository: NVIDIA/TensorRT-LLM Length of output: 761 🏁 Script executed: # Look for request progression through context chunks to understand the lifecycle
rg -n "context_current_position|chunk" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py | head -30Repository: NVIDIA/TensorRT-LLM Length of output: 1945 🏁 Script executed: # Check the request list iteration to see if a single request can have multiple "entries"
sed -n '230,260p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 1367 🏁 Script executed: # Now let me understand how requests progress through chunks across iterations
# Check if is_first_context_chunk changes during a request's lifetime
rg -n "context_current_position|is_first_context_chunk" tensorrt_llm/_torch/pyexecutor -A 2 -B 2 | head -60Repository: NVIDIA/TensorRT-LLM Length of output: 6145 🏁 Script executed: # Let me look at how requests move between scheduling phases
# Understanding whether a request stays in active_requests or gets removed/re-added
sed -n '419,450p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 1712 🏁 Script executed: # Check what happens at the end of Phase 2 and how requests transition
sed -n '450,500p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 2427 🏁 Script executed: # Look for how context position advances to understand the chunk progression
rg -n "context_current_position\s*=" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py -A 2 -B 2Repository: NVIDIA/TensorRT-LLM Length of output: 157 🏁 Script executed: # Let me understand if requests can appear multiple times in pending_ctx
# and how chunks advance across iterations
sed -n '530,650p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 5632 🏁 Script executed: # Look at the chunked context scheduling to see how chunks progress
sed -n '591,700p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 5018 🏁 Script executed: # Search for how context chunks are handled and if a request appears multiple times
rg -n "is_context_finished|context_remaining_length" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 654 🏁 Script executed: # Check if there are any comments or code about request ordering in pending_ctx
sed -n '300,320p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 1357 🏁 Script executed: # Look for any sorting or ordering logic applied to pending_ctx or active_requests
rg -n "sort|pending_ctx\|active_requests" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py | grep -E "(sort|pending_ctx)"Repository: NVIDIA/TensorRT-LLM Length of output: 788 🏁 Script executed: # Verify: after a request is scheduled in Phase 2, does its state change to advance chunks?
# Look for what happens to context_current_position or chunk progression
sed -n '440,445p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 434 🏁 Script executed: # Critical: verify whether the same request can appear multiple times in a single pending_ctx loop
# and whether is_first_context_chunk can change within the same iteration
rg -n "is_first_context_chunk\|context_current_position" tensorrt_llm/_torch/pyexecutor/llm_request.py | head -20Repository: NVIDIA/TensorRT-LLM Length of output: 157 🏁 Script executed: # Check how requests are ordered in active_requests and if first chunks are guaranteed to come first
# Look at the py_executor to see if active_requests maintains any ordering
rg -n "active_requests\s*=" tensorrt_llm/_torch/pyexecutor/py_executor.py | head -20Repository: NVIDIA/TensorRT-LLM Length of output: 1270 🏁 Script executed: # Look for evidence of how requests progress through chunks
# Check if context_current_position is modified during scheduling
rg -n "context_current_position\s*=" tensorrt_llm/_torch/pyexecutor -B 2 -A 2 | head -50Repository: NVIDIA/TensorRT-LLM Length of output: 3019 🏁 Script executed: # Specifically check the update_requests function that advances context position
rg -n "update_requests|_update_request" tensorrt_llm/_torch/pyexecutor/py_executor.py | head -20Repository: NVIDIA/TensorRT-LLM Length of output: 1671 🏁 Script executed: # Critical: trace whether the same request can appear in pending_ctx across iterations
# Check the flow: is the request removed from active_requests after scheduling a chunk?
rg -n "remove\|pop\|del.*active_requests" tensorrt_llm/_torch/pyexecutor/py_executor.py | head -20Repository: NVIDIA/TensorRT-LLM Length of output: 157 🏁 Script executed: # Verify the exact flow: if a request has chunks, does it appear in pending_ctx with
# is_first_context_chunk changing between iterations?
# Check how a request transitions from first chunk to non-first chunk
sed -n '1083,1100p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 1002 🏁 Script executed: # Look at _sort_requests to see how context_requests are ordered
sed -n '1083,1100p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 1002 🏁 Script executed: # Let me trace the actual problem: when is_first_context_chunk FALSE for a request?
rg -n "is_first_context_chunk.*=" tensorrt_llm/_torch/pyexecutor -B 3 -A 1 | head -60Repository: NVIDIA/TensorRT-LLM Length of output: 157 🏁 Script executed: # Confirm: does pending_ctx maintain FIFO order of active_requests?
# And can a chunked request have its first chunk in one iteration and subsequent in the next?
sed -n '385,395p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 567 🏁 Script executed: # Understand: in a single active_requests list, can we have req1 (first chunk) then req2 (non-first chunk)?
# This would happen if req1 just arrived and req2 has already been partially scheduled
# Check if requests stay in active_requests during chunk progression
rg -n "is_context_finished\|context_remaining_length\|is_last_context_chunk" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py -B 1 -A 1Repository: NVIDIA/TensorRT-LLM Length of output: 157 🏁 Script executed: # Final verification: trace through a multi-chunk scenario
# If request A schedules first chunk in iteration 1, does it stay in active_requests for iteration 2?
# and can its second chunk then encounter the resident cap?
sed -n '419,428p' tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 655 🏁 Script executed: # Check if there's any guarantee about ordering: are first chunks always at the head?
# Or can they be interleaved?
rg -n "\.append\(req\)" tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.pyRepository: NVIDIA/TensorRT-LLM Length of output: 429 Skip capped first-chunk requests with 🤖 Prompt for AI Agents |
||
| peft_pages = budget.peft_pages_needed(req) | ||
| if peft_pages is None: | ||
| continue | ||
|
|
@@ -421,6 +442,8 @@ def _schedule_loop(self, active_requests, inflight_request_ids): | |
| has_chunking = has_chunking or chunking_flag | ||
| scheduled_ctx.append(req) | ||
| budget.commit(req, tokens, peft_pages) | ||
| if starts_new_sequence: | ||
| num_resident += 1 | ||
|
|
||
| # Deadlock detection: if generation requests exist but none were | ||
| # scheduled and none were evicted, no forward pass will run and no | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Keep the allreduced cap on attention-only pipeline ranks.
Lines 2934-2941 calculate and store the reduced cap on every rank. Line 2760 returns
Nonebefore reading that cap when a hybrid pipeline stage has no local Mamba layers. The Mamba stage then limits admission while the attention-only stage admits an uncapped batch. This can desynchronize pipeline execution.Return
_resident_sequence_capbefore the local-Mamba check. ReturnNoneonly when the attention-only rank did not inherit a distributed cap. Add a mixed PP regression with one Mamba rank and one attention-only rank.Proposed fix
def max_resident_sequences(self) -> Optional[int]: """Number of sequences whose recurrent state can be resident at once.""" + if self._resident_sequence_cap is not None: + return self._resident_sequence_cap if self.local_num_mamba_layers == 0: return None return self._max_resident_sequences()Also applies to: 2934-2941
🤖 Prompt for AI Agents