Skip to content

[https://nvbugs/6550276][fix] Clamp residency to what the quota affords (allreduce-MIN across ranks) behind… - #17261

Open
trtllm-agent wants to merge 1 commit into
NVIDIA:mainfrom
tensorrt-cicd:repair-bot-bug6550276
Open

[https://nvbugs/6550276][fix] Clamp residency to what the quota affords (allreduce-MIN across ranks) behind…#17261
trtllm-agent wants to merge 1 commit into
NVIDIA:mainfrom
tensorrt-cicd:repair-bot-bug6550276

Conversation

@trtllm-agent

@trtllm-agent trtllm-agent commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Root cause: _max_resident_sequences() returned raw max_batch_size * pp_size (512), turning a batch-size ceiling into a hard 37.48 GiB floor of non-droppable recurrent-state slots that no free_gpu_memory_fraction can satisfy against the 24.86 GiB quota.
  • Fix: Clamp residency to what the quota affords (allreduce-MIN across ranks) behind a new max_resident_sequences() hook the V2 scheduler gates first-context-chunk admission on, and truncate the inherited warmup constraints to that clamp so all three sizing paths agree.
  • Original test: pytest tests/integration/defs/perf/test_perf.py --perf --test-list=.repair-bot/perf_test_list.txt --output-dir=build/perf_output -v
  • Automated fix generated by repair-bot

Test plan

  • Verify fix on the same GPU type as the original failure
  • Check for regressions in related tests

Links

Dev Engineer Review

  • Mamba residency sizing now clamps resident sequences to the minimum KV cache quota across ranks.
  • KVCacheManagerV2.max_resident_sequences() provides an optional scheduler cap.
  • The V2 scheduler limits first context chunks when the cap is set.
  • Warmup constraints are truncated to the clamped residency.
  • Attention-only managers remain unbounded.
  • The changes preserve dummy-slot requirements and use shared attention-page sizing.
  • Error handling covers quotas that cannot hold one live recurrent state.
  • No configuration files or test-list files changed.

QA Engineer Review

  • Added scheduler coverage for:
    • New sequence admission limits.
    • Already-started sequence limits.
    • Non-first context chunks without repeated residency charging.
    • Unbounded admission when no cap is configured.
  • Added Mamba cache coverage for:
    • Failure when the quota cannot hold one recurrent state.
    • Residency clamping below max_batch_size.
    • Truncation of inherited constraints.
    • Full residency when the quota is sufficient.
    • Unbounded residency for attention-only managers.
  • The modified test functions are not listed in the provided context, and no tests/integration/test_lists/ coverage data is available.
  • Verdict: needs follow-up.

…quota

When max_batch_size asks for more fixed-size recurrent state than the GPU
cache quota can hold, the V2 Mamba state pool is unbuildable at any
free_gpu_memory_fraction (513 slots need 37.5 GiB of the 24.9 GiB an 80 GiB
H100 has left after qwen3.5_27b weights). Bound the resident set to what the
quota affords and have the scheduler queue the excess via a new
max_resident_sequences() hook, which reports None (unbounded) for plain
attention models and whenever the quota affords every requested sequence.

The inherited warmup constraints are truncated to the clamped residency too:
every constraint entry costs one SSM slot, so a constraint built from the raw
max_batch_size would re-impose the very floor the clamp removes.

Signed-off-by: trtllm-agent <296075020+trtllm-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change adds an optional resident-sequence cap to KV cache managers, computes quota-based residency for Mamba caches across ranks, enforces admission limits in the scheduler, and adds scheduler and cache-manager regression tests.

Changes

KV cache residency control

Layer / File(s) Summary
Residency contract and quota budgeting
tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py, tensorrt_llm/_torch/pyexecutor/mamba_cache_manager.py
KVCacheManagerV2 exposes an optional residency cap. Mamba cache configuration calculates quota-affordable residency, reduces it across ranks, and applies the clamped value to cache sizing and constraints.
Scheduler residency admission
tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py
KVCacheV2Scheduler tracks resident requests and stops first context-chunk admission when the configured capacity is reached.
Residency regression coverage
tests/unittest/_torch/executor/test_kv_cache_v2_scheduler.py, tests/unittest/_torch/executor/test_mamba_cache_manager.py
Tests cover capped and uncapped scheduling, quota-limited Mamba residency, distributed reduction, constraint truncation, and attention-only managers.

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

Sequence Diagram(s)

sequenceDiagram
  participant KVCacheV2Scheduler
  participant KVCacheManagerV2
  participant MambaCacheManager
  participant Distributed
  KVCacheV2Scheduler->>KVCacheManagerV2: read max_resident_sequences()
  MambaCacheManager->>Distributed: reduce quota-affordable capacity
  Distributed-->>MambaCacheManager: return clamped capacity
  KVCacheV2Scheduler->>KVCacheV2Scheduler: count resident started requests
  KVCacheV2Scheduler->>KVCacheManagerV2: admit first context chunk when capacity remains
Loading

Possibly related PRs

Suggested labels: api-compatible

Suggested reviewers: juney-nvidia, liji-nv, bowenfu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the bug fix and the main change: clamping residency to the available quota across ranks.
Description check ✅ Passed The description explains the root cause, fix, test plan, and bug link, but it omits the repository checklist.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_torch/pyexecutor/mamba_cache_manager.py`:
- Around line 2758-2762: Update max_resident_sequences to return the allreduced
_resident_sequence_cap before checking local_num_mamba_layers, returning None
only when no distributed cap was inherited; preserve the local
_max_resident_sequences calculation for Mamba ranks without a cap. Add a mixed
pipeline regression covering one Mamba rank and one attention-only rank to
verify both ranks enforce the same admission cap.

In `@tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py`:
- Around line 318-326: Extend the residency accounting in the scheduler around
_is_started_request and the Phase 1 DISAGG_GENERATION_INIT path so an
in-progress disaggregated initialization consumes a resident slot before
prepare_disagg_gen_init() runs. Apply max_resident_sequences to admission at
that path, and retain the slot until the request releases its cache. Add a
regression test with cap=1 that submits two disaggregated generation
initializations and verifies the second is not admitted concurrently.
- Around line 422-426: The resident-limit guard in the context scheduling loop
must skip only the capped first-chunk request; replace the `break` under
`starts_new_sequence` with `continue` so later non-first chunks in `pending_ctx`
remain schedulable. Add a regression test covering enforced resident limits
where a capped first chunk precedes another request’s non-first chunk, verifying
the latter is scheduled and the scheduler does not deadlock.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 01870fef-8a4a-49a7-a8d8-88cc5d99353f

📥 Commits

Reviewing files that changed from the base of the PR and between 78639d2 and 94d2555.

📒 Files selected for processing (5)
  • tensorrt_llm/_torch/pyexecutor/kv_cache_manager_v2.py
  • tensorrt_llm/_torch/pyexecutor/mamba_cache_manager.py
  • tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py
  • tests/unittest/_torch/executor/test_kv_cache_v2_scheduler.py
  • tests/unittest/_torch/executor/test_mamba_cache_manager.py

Comment on lines +2758 to +2762
def max_resident_sequences(self) -> Optional[int]:
"""Number of sequences whose recurrent state can be resident at once."""
if self.local_num_mamba_layers == 0:
return None
return self._max_resident_sequences()

Copy link
Copy Markdown
Contributor

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 None before 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_cap before the local-Mamba check. Return None only 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/pyexecutor/mamba_cache_manager.py` around lines 2758 -
2762, Update max_resident_sequences to return the allreduced
_resident_sequence_cap before checking local_num_mamba_layers, returning None
only when no distributed cap was inherited; preserve the local
_max_resident_sequences calculation for Mamba ranks without a cap. Add a mixed
pipeline regression covering one Mamba rank and one attention-only rank to
verify both ranks enforce the same admission cap.

Comment on lines +318 to +326
# 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
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Apply the residency cap to DISAGG_GENERATION_INIT.

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 DISAGG_GENERATION_INIT path. That path calls prepare_disagg_gen_init(), which creates and resizes the primary cache. For Mamba, this consumes non-droppable recurrent-state slots.

Gate disaggregated initialization before prepare_disagg_gen_init(). Count its slot until the request releases its cache. Add a cap=1 regression that submits two disaggregated generation initializations.

Also applies to: 422-426

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py` around lines 318 -
326, Extend the residency accounting in the scheduler around _is_started_request
and the Phase 1 DISAGG_GENERATION_INIT path so an in-progress disaggregated
initialization consumes a resident slot before prepare_disagg_gen_init() runs.
Apply max_resident_sequences to admission at that path, and retain the slot
until the request releases its cache. Add a regression test with cap=1 that
submits two disaggregated generation initializations and verifies the second is
not admitted concurrently.

Comment on lines +422 to +426
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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/pyexecutor

Repository: 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 -l

Repository: 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.py

Repository: 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.py

Repository: 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 expanded

Repository: 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.py

Repository: 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 -40

Repository: 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 -30

Repository: 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.py

Repository: 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 -60

Repository: 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.py

Repository: 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.py

Repository: 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 2

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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 -20

Repository: 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 -20

Repository: 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 -50

Repository: 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 -20

Repository: 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 -20

Repository: 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.py

Repository: 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.py

Repository: 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 -60

Repository: 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.py

Repository: 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 1

Repository: 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.py

Repository: 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.py

Repository: NVIDIA/TensorRT-LLM

Length of output: 429


Skip capped first-chunk requests with continue, not break. A first context chunk that hits the resident-sequence limit should be skipped in that iteration, not exit the context phase entirely. If a non-first chunk of another request follows the capped first chunk in pending_ctx, the break statement prevents it from being scheduled. This can deadlock the scheduler: the first chunk's request holds a resident slot without being scheduled, while the non-first chunk cannot advance to free or reuse cache. Change line 426 from break to continue and add a regression test that schedules two requests with resident-sequence limits enforced, where an earlier request's non-first chunk is iterated after a later request's capped first chunk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/pyexecutor/scheduler/scheduler_v2.py` around lines 422 -
426, The resident-limit guard in the context scheduling loop must skip only the
capped first-chunk request; replace the `break` under `starts_new_sequence` with
`continue` so later non-first chunks in `pending_ctx` remain schedulable. Add a
regression test covering enforced resident limits where a capped first chunk
precedes another request’s non-first chunk, verifying the latter is scheduled
and the scheduler does not deadlock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants