Skip to content

[TRTLLM-14903][fix] Free partially-allocated warmup dummy KV blocks and count spec extra tokens in warmup block estimates - #17162

Open
brnguyen2 wants to merge 4 commits into
NVIDIA:mainfrom
brnguyen2:fix/TRTLLM-14903-estimation-warmup-leak
Open

[TRTLLM-14903][fix] Free partially-allocated warmup dummy KV blocks and count spec extra tokens in warmup block estimates#17162
brnguyen2 wants to merge 4 commits into
NVIDIA:mainfrom
brnguyen2:fix/TRTLLM-14903-estimation-warmup-leak

Conversation

@brnguyen2

@brnguyen2 brnguyen2 commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes TRTLLM-14903: LLM() startup hangs indefinitely during KV cache size estimation for Mamba-hybrid models with speculative decoding enabled.

Two defects combined to produce the hang:

  1. _create_warmup_request under-counted blocks_to_use: it ignored the per-sequence extra tokens (num_extra_kv_tokens, num_extra_decoding_steps, and the draft-token reserve for generation dummies) that add_dummy_requests actually allocates. With spec decoding, block-aligned multi-sequence warmup shapes (e.g. the Mamba hybrid multi-seq warmup added in [None][perf] Close Mamba hybrid warmup gap in autotuner warmup #16177) passed the estimate but overflowed the pool at allocation time.
  2. add_dummy_requests leaked every already-registered sequence when a later add_token raised (e.g. "no free blocks left"). On the minimal KV pool built for cache-size estimation, the leak left too few blocks for the estimation requests themselves, so the executor loop spun forever without scheduling them and LLM() never returned.

The fix makes blocks_to_use mirror the real allocation, and makes add_dummy_requests remove already-registered sequences before re-raising, preserving callers' skip-on-failure semantics.

Note: feat/kimi_k3 currently carries a temporary workaround for this issue (estimation skipped whenever a speculative config is set, in py_executor_creator.py, marked TRTLLM-14903); that workaround should be reverted once this fix merges.

Test Coverage

Verified on a spec-decoding estimation-phase integration run on a Mamba-hybrid model: previously hung unboundedly during estimation warmup; with the fix the run completes and passes logits parity against a non-speculative baseline. The "Mamba hybrid warmup skipped" overflow path is now taken before any allocation, so the pool is no longer poisoned.

PR Checklist

  • PR title and description follow the repo conventions
  • Test coverage noted above

Dev Engineer Review

  • _create_warmup_request now accounts for extra KV tokens, decoding steps, draft-loop reservations, and beam width.
  • add_dummy_requests removes partially registered target and draft sequences after allocation failure.
  • The change prevents leaked KV blocks during KV-cache estimation.
  • Existing skip-on-failure behavior remains unchanged.
  • The V2 KV-cache manager already releases partial dummy allocations.
  • The benchmark harness removes PYTHONSAFEPATH before launching the client.
  • No public API, configuration, or test-list changes were identified.
  • Mamba-hybrid speculative-decoding validation showed matching logits parity.
  • The 16-GPU GSM8K run scored 96.66, matching the reference run.
  • One CI run was disabled for instance maintenance. Reported unrelated CI failures passed targeted reruns or occurred in unrelated runs.

QA Engineer Review

  • The test code in tests/integration/defs/kv_cache/test_prefix_aware_scheduling.py was modified.
  • No test functions were added, modified, or removed.
  • Existing integration coverage remains applicable.
  • Verdict: sufficient.

…nd count spec extra tokens in warmup block estimates

Two defects combined to hang LLM startup indefinitely during KV cache
size estimation for Mamba-hybrid models with speculative decoding:

1. _create_warmup_request under-counted blocks_to_use: it ignored the
   per-sequence extra tokens (num_extra_kv_tokens,
   num_extra_decoding_steps, and the draft-token reserve for generation
   dummies) that add_dummy_requests actually allocates. With spec
   decoding, block-aligned multi-sequence warmup shapes (e.g. the Mamba
   hybrid multi-seq warmup) passed the estimate but overflowed the pool
   at allocation time.

2. add_dummy_requests leaked every already-registered sequence when a
   later add_token raised (e.g. "no free blocks left"). On the minimal
   KV pool built for cache-size estimation the leak left too few blocks
   for the estimation requests themselves, so the executor loop spun
   forever without scheduling them and LLM() never returned.

Fix blocks_to_use to mirror the real allocation, and make
add_dummy_requests remove already-registered sequences before
re-raising, preserving callers' skip-on-failure semantics.

Verified on a spec-decoding estimation-phase integration run on a
Mamba-hybrid model (previously hung unboundedly; now completes with
logits parity against a non-speculative baseline).

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63215 [ run ] triggered by Bot. Commit: e6b4c3d Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63215 [ run ] completed with state DISABLED
Pipeline is freezed and top-1 instance is under maintenance. For urgent request, contact Yiteng Niu

Link to invocation

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

Validated beyond the original truncated-model repro: on a Mamba-hybrid MoE model with suffix-automaton speculative decoding and KV-cache estimation enabled (the previously hanging configuration), a full speculative-decoding logits-parity integration run now completes in ~17 minutes with parity statistics identical to the estimation-skipped and pre-regression baselines (52 prompts, 0 drift), with the estimation phase confirmed active in the logs and no warmup-overflow warnings. A GSM8K accuracy run on the full model at 16-GPU scale with speculative decoding and estimation enabled scores 96.66, matching the reference measured with estimation skipped.

An audit of the V2 KV-cache manager's add_dummy_requests found it already frees partially-allocated dummy requests on capacity failure (success-flag based signaling with an explicit release_resources() on every capacity-failure path), so it does not need the equivalent change.

Marking the PR ready for review. The temporary estimation-skip workaround on feat/kimi_k3 has been reverted on that branch now that the fix is validated there.

@brnguyen2
brnguyen2 marked this pull request as ready for review August 1, 2026 16:38
@brnguyen2
brnguyen2 requested review from a team as code owners August 1, 2026 16:38
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a095f79e-75d1-4a69-9b10-620fea6f7a21

📥 Commits

Reviewing files that changed from the base of the PR and between b57fba9 and 5bdb732.

📒 Files selected for processing (1)
  • tensorrt_llm/_torch/pyexecutor/model_engine.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tensorrt_llm/_torch/pyexecutor/model_engine.py

Walkthrough

The change updates warmup KV-cache capacity estimation, makes dummy-request setup transactional, and removes PYTHONSAFEPATH before launching LMBenchmark.

Changes

KV-cache management

Layer / File(s) Summary
Warmup capacity estimation
tensorrt_llm/_torch/pyexecutor/model_engine.py
Warmup estimation now includes extra KV and decode tokens, draft-loop reservations, block boundaries, and beam width.
Dummy-request rollback
tensorrt_llm/_torch/pyexecutor/resource_manager.py
If dummy target or draft sequence setup fails, partially registered sequences are removed before the original exception is re-raised.
Benchmark subprocess setup
tests/integration/defs/kv_cache/test_prefix_aware_scheduling.py
The LMBenchmark subprocess removes PYTHONSAFEPATH before launch to preserve sibling-module imports.

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

Possibly related PRs

Suggested reviewers: bowenfu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 summarizes both main fixes: warmup block accounting and cleanup of partially allocated dummy KV blocks.
Description check ✅ Passed The description explains the issue, root causes, fixes, and test coverage; the checklist is only partially completed but the required technical content is present.
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.

🧹 Nitpick comments (2)
tensorrt_llm/_torch/pyexecutor/resource_manager.py (2)

976-980: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unbind the unused loop variable.

token_num is not read in this loop. Ruff reports B007.

♻️ Proposed fix
-                for req_id, token_num, _ in batch_request_infos:
+                for req_id, _, _ in batch_request_infos:
🤖 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/resource_manager.py` around lines 976 - 980,
Update the loop over batch_request_infos to bind the unused token_num element to
an underscore, preserving req_id and the existing token-addition loops.

Source: Linters/SAST tools


1039-1046: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the swallowed exception and log it.

The inner handler discards every exception type. A real failure in remove_sequence (for example a binding signature change) then disappears, and the rollback silently stops for that request. The C++ binding raises RuntimeError for an unregistered sequence, so catch that type and log at debug level. Ruff reports S110 and BLE001 here.

♻️ Proposed fix
                 for req in freeing_requests:
                     try:
                         freeing_impl.remove_sequence(req.py_request_id, req,
                                                      False)
-                    except Exception:
+                    except RuntimeError as e:
                         # The sequence may never have been registered (the
                         # batched add itself failed); nothing to clean up.
-                        pass
+                        logger.debug(
+                            "Dummy request rollback skipped for request "
+                            f"{req.py_request_id}: {e}")

As per coding guidelines "Catch the narrowest possible exceptions, keep duck-typing try blocks minimal, prefer isinstance(), use built-in exception types".

🤖 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/resource_manager.py` around lines 1039 - 1046,
Update the exception handler in the freeing_requests rollback loop around
freeing_impl.remove_sequence to catch only RuntimeError, log the caught
exception at debug level, and allow other exception types to propagate. Keep the
try block limited to the remove_sequence call so genuine rollback failures are
not silently swallowed and Ruff S110/BLE001 are resolved.

Sources: Coding guidelines, Linters/SAST tools

🤖 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.

Nitpick comments:
In `@tensorrt_llm/_torch/pyexecutor/resource_manager.py`:
- Around line 976-980: Update the loop over batch_request_infos to bind the
unused token_num element to an underscore, preserving req_id and the existing
token-addition loops.
- Around line 1039-1046: Update the exception handler in the freeing_requests
rollback loop around freeing_impl.remove_sequence to catch only RuntimeError,
log the caught exception at debug level, and allow other exception types to
propagate. Keep the try block limited to the remove_sequence call so genuine
rollback failures are not silently swallowed and Ruff S110/BLE001 are resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 43777f60-714f-419b-8a39-2e0d0fcfe29b

📥 Commits

Reviewing files that changed from the base of the PR and between fdf7bd5 and e6b4c3d.

📒 Files selected for processing (2)
  • tensorrt_llm/_torch/pyexecutor/model_engine.py
  • tensorrt_llm/_torch/pyexecutor/resource_manager.py

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63288 [ run ] triggered by Bot. Commit: e6b4c3d Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63288 [ run ] completed with state FAILURE. Commit: e6b4c3d
/LLM/main/L0_MergeRequest_PR pipeline #51283 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

multi-round-qa.py imports its sibling utils.py through the implicit
script-directory sys.path entry. When the test environment sets
PYTHONSAFEPATH=1, that entry is disabled and the benchmark client exits
immediately with ModuleNotFoundError: No module named 'utils', failing
TestServePrefixAwareScheduling tests with 'Smoke warmup failed with
rc=1' while the server is still healthy. Strip the variable from the
client subprocess environment.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2
brnguyen2 requested a review from yuanjingx87 August 3, 2026 13:01
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

The test_multi_round_qa_shared_prefix_smoke failure in the last pre-merge run is unrelated to this PR: the LMBenchmark client dies at import time with ModuleNotFoundError: No module named 'utils' because the test environment on the affected runners now sets PYTHONSAFEPATH=1, which disables the script-directory sys.path entry that multi-round-qa.py relies on to import its sibling utils.py. The same failure appears on many concurrent PRs' pre-merge runs since 2026-08-02 (e.g. #17163, #17165, #16957, #16609, #16993, #16592).

Folded a harness fix into this PR (dc5a809): strip PYTHONSAFEPATH from the benchmark client's subprocess environment, restoring the standard CPython script-directory import behavior the script depends on.

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63453 [ run ] triggered by Bot. Commit: dc5a809 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63453 [ run ] completed with state FAILURE. Commit: dc5a809
/LLM/main/L0_MergeRequest_PR pipeline #51424 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

Looked into the two failures in the latest CI run; neither reproduces with this PR's change:

  • test_disaggregated_tinyllama_multi_orchestrator[TinyLlama-1.1B-Chat-v1.0]: I applied this PR's diff to model_engine.py/resource_manager.py on top of a recent main build (the public 1.3.0rc23 wheel — both touched code paths are equivalent to this PR's merge-base there) and ran the exact test in the CI container image on a 4-GPU node. Baseline passes, and the patched version passes on repeated runs. A static read of the diff agrees: the success path is unchanged — the new accounting only makes the warmup block estimate more conservative (worst case a clean return None), and the new try/except in add_dummy_requests only executes on an allocation failure that previously raised from the same spot without cleanup. The same test also failed once on an unrelated recent run and passed on retry, so this looks environment-related rather than PR-caused.
  • The GB200 2-node timeout during weight loading appears across many recent unrelated runs, so it is not specific to this PR either.

Rerunning CI.

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63499 [ run ] triggered by Bot. Commit: dc5a809 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63499 [ run ] completed with state FAILURE. Commit: dc5a809
/LLM/main/L0_MergeRequest_PR pipeline #51468 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63510 [ run ] triggered by Bot. Commit: dc5a809 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63510 [ run ] completed with state SUCCESS. Commit: dc5a809
/LLM/main/L0_MergeRequest_PR pipeline #51478 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63521 [ run ] triggered by Bot. Commit: dc5a809 Link to invocation

Comment on lines +2184 to +2189
blocks_to_use = (num_full_seqs * math.ceil(
(max_seq_len + extra_ctx_tokens) / tokens_per_block) + (math.ceil(
(num_left_over_tokens + extra_ctx_tokens) /
tokens_per_block) if num_left_over_tokens > 0 else 0) +
num_gen_requests * self.max_beam_width * math.ceil(
(1 + extra_gen_tokens) / tokens_per_block))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This expression is not very readable. Can we clean it up

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Cleaned up in b57fba9: pulled out a blocks_for_seq helper and split the sum into one statement per term (full context sequences, leftover-token sequence, gen requests).

tokens_per_block = kv_cache_manager.tokens_per_block
extra_ctx_tokens = (getattr(kv_cache_manager, "num_extra_kv_tokens", 0)
or 0) + num_extra_decoding_steps
extra_gen_tokens = extra_ctx_tokens + self.max_draft_loop_tokens

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is going to be 2 * max_draft_len - 1 in most cases, why?

@brnguyen2 brnguyen2 Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It mirrors what add_dummy_requests actually allocates per gen dummy: num_extra_kv_tokens (max_draft_len - 1 in one-engine modes) + num_extra_decoding_steps + max_draft_loop_tokens (max_draft_len for a linear draft loop) add_token calls on top of the single prompt token. The old estimate ignored these, so warmup could pass this check and then fail midway through the actual allocation, which is the leak this PR fixes. Added a comment spelling out the arithmetic in 5bdb732.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63558 [ run ] triggered by Bot. Commit: 5bdb732 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63521 [ run ] completed with state ABORTED. Commit: dc5a809

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63558 [ run ] completed with state FAILURE. Commit: 5bdb732
/LLM/main/L0_MergeRequest_PR pipeline #51524 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

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.

4 participants