[https://nvbugs/6539941][fix] Preserve primary warmup error when batch cleanup also fails - #17152
[https://nvbugs/6539941][fix] Preserve primary warmup error when batch cleanup also fails#17152trtllm-agent wants to merge 1 commit into
Conversation
…error ``_release_batch_context`` freed the dummy warmup batch in a bare ``finally:``. Freeing issues GPU work -- the V2 KV cache manager records a CUDA event per pool via ``cuEventRecord`` -- so it raises again whenever the failure being unwound already left the CUDA context in a sticky error state. A raise inside ``finally`` *replaces* the in-flight exception, demoting the real one to ``__context__`` where no traceback prints it. That is what this bug reports: every frame in its traceback is cleanup (``free_resources`` -> ``_kv_cache.py::close`` -> ``CachedCudaEvent`` -> ``cuEventRecord`` -> ``CuError: an illegal memory access was encountered``), so it was categorized against the V2 KV cache manager. The first-order error, recoverable only from the CI stdout, was a ``CUBLAS_STATUS_EXECUTION_FAILED`` in the MoE forward on another rank. Free the batch on both the normal and the exceptional path, but on the exceptional path contain a cleanup failure to a warning so the original error keeps propagating. Resources are still released in every case; only the error attribution changes. All seven warmup cleanup sites route through this one helper. Also pin ``_force_non_greedy_for_capture=False`` on the ``spec_metadata`` mock in ``test_promoted_context_precedes_speculative_overlap_generation``. A bare ``Mock()`` auto-vivifies any attribute as a truthy child ``Mock``, so the ``False`` default in the production ``getattr(spec_metadata, '_force_non_greedy_for_capture', False)`` was never reached and the capture-only-override assertion fired on that non-warmup path. The test fails this way on unmodified main, independently of the change above; pinning the attribute follows the convention the same file already uses for other explicitly-declared mock attributes, and leaves the production assertion intact because it guards a real serving leak. The target test passes on this GPU either way (GSM8K 90.11 against a threshold of 84.80), and the run does exercise the suspected trigger -- the autotuner logs the ``fp8_block_scale_moe_runner`` fallback tactic at the reported shapes, where ``tile_tokens_dim`` clamps to 8 -- so the tileN=8 theory carried over from bugs 6525059/6432948 is not confirmed here. This change makes a recurrence report its actual cause instead of the reporter frame. Signed-off-by: trtllm-agent <296075020+trtllm-agent@users.noreply.github.com>
WalkthroughThe batch context release path now preserves the original exception when cleanup also fails. Normal cleanup errors still propagate. The speculative-overlap test fixture explicitly sets ChangesBatch cleanup behavior
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/unittest/_torch/executor/test_pytorch_model_engine.py (1)
1060-1063: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd direct coverage for
_release_batch_contextexception handling.Add cases that preserve a primary forward exception when
free_resources()fails, and propagate a cleanup exception after successful context execution.Test coverage summary
- Added: none.
- Modified:
PyTorchModelEngineTestCase.test_promoted_context_precedes_speculative_overlap_generation.- Removed: none.
- Test-list membership: covered through
unittest/_torch/executorentries inl0_dgx_b300.yml,l0_b300.yml,l0_gb300_multi_gpus.yml, andl0_h100.yml.- Coverage verdict: insufficient.
🤖 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 `@tests/unittest/_torch/executor/test_pytorch_model_engine.py` around lines 1060 - 1063, Add direct test cases for _release_batch_context covering both exception paths: preserve the original forward/context exception when free_resources() also fails, and propagate the cleanup exception when context execution succeeds. Extend or split PyTorchModelEngineTestCase.test_promoted_context_precedes_speculative_overlap_generation as appropriate, asserting the resulting exception and ensuring cleanup is attempted.Source: Path instructions
🤖 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/model_engine.py`:
- Around line 2150-2156: Update the exception-unwinding cleanup around
free_batch_resources so cleanup continues independently for every manager and
request even when one operation fails. Catch broad exceptions for each cleanup
operation, log secondary failures, and preserve and re-raise the original
primary exception after all cleanup attempts complete.
---
Nitpick comments:
In `@tests/unittest/_torch/executor/test_pytorch_model_engine.py`:
- Around line 1060-1063: Add direct test cases for _release_batch_context
covering both exception paths: preserve the original forward/context exception
when free_resources() also fails, and propagate the cleanup exception when
context execution succeeds. Extend or split
PyTorchModelEngineTestCase.test_promoted_context_precedes_speculative_overlap_generation
as appropriate, asserting the resulting exception and ensuring cleanup is
attempted.
🪄 Autofix (Beta)
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: 44ab9b63-6e73-4a51-b09d-3c9a508ed635
📒 Files selected for processing (2)
tensorrt_llm/_torch/pyexecutor/model_engine.pytests/unittest/_torch/executor/test_pytorch_model_engine.py
| try: | ||
| free_batch_resources() | ||
| except Exception as e: # noqa: BLE001 | ||
| logger.warning( | ||
| f"Failed to free warmup batch resources while unwinding: {e}" | ||
| ) | ||
| raise |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 -P '^\s*(async\s+)?def\s+free_resources\s*\(' --glob '*.py' .
rg -n -C 4 '\.free_resources\(' --glob '*.py' .Repository: NVIDIA/TensorRT-LLM
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- cleanup helper and enclosing exception path ---'
rg -n -C 18 'free_batch_resources|warmup batch resources|unwinding' tensorrt_llm/_torch/pyexecutor/model_engine.py
printf '%s\n' '--- helper definition and direct calls ---'
rg -n -C 12 'def free_batch_resources|free_batch_resources\(' tensorrt_llm/_torch/pyexecutor/model_engine.py
printf '%s\n' '--- resource-manager cleanup implementation ---'
sed -n '2580,2632p' tensorrt_llm/_torch/pyexecutor/resource_manager.pyRepository: NVIDIA/TensorRT-LLM
Length of output: 8737
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- all definitions and calls of the cleanup helper ---'
rg -n -C 20 'free_batch_resources|warmup batch resources|unwinding' tensorrt_llm/_torch/pyexecutor/model_engine.py
printf '%s\n' '--- cleanup-related exception classes and CUDA error handling ---'
rg -n -C 5 'CUDA|cuda|torch\.cuda|OutOfMemoryError|RuntimeError|except .*Error' \
tensorrt_llm/_torch/pyexecutor/model_engine.py \
tensorrt_llm/_torch/pyexecutor/resource_manager.pyRepository: NVIDIA/TensorRT-LLM
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import ast
from pathlib import Path
root = Path("tensorrt_llm/_torch")
for path in sorted(root.rglob("*.py")):
try:
tree = ast.parse(path.read_text())
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "free_resources":
raises = [
ast.unparse(child)
for child in ast.walk(node)
if isinstance(child, ast.Raise) and child.exc is not None
]
calls = [
ast.unparse(child)
for child in ast.walk(node)
if isinstance(child, ast.Call)
]
print(f"{path}:{node.lineno}")
print(" raises:", raises or ["<implicit>"])
print(" calls:", calls[:12])
PY
printf '%s\n' '--- manager selection and warmup request cleanup ---'
rg -n -C 8 '_get_draft_kv_cache_manager|kv_cache_manager_key|SPEC_RESOURCE_MANAGER|CROSS_KV_CACHE_MANAGER' \
tensorrt_llm/_torch/pyexecutor/model_engine.pyRepository: NVIDIA/TensorRT-LLM
Length of output: 26989
Continue cleanup after a secondary failure.
free_batch_resources() stops at the first failing manager or request, so later resources remain allocated. Run each cleanup operation independently, log secondary failures, and re-raise the primary exception. The manager methods have no documented single cleanup exception type to catch.
🤖 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/model_engine.py` around lines 2150 - 2156,
Update the exception-unwinding cleanup around free_batch_resources so cleanup
continues independently for every manager and request even when one operation
fails. Catch broad exceptions for each cleanup operation, log secondary
failures, and preserve and re-raise the original primary exception after all
cleanup attempts complete.
Source: Coding guidelines
Summary
PyTorchModelEngine's warmup batch context manager released KV-cache, cross-KV, and speculative-decoding resources from a barefinallyblock. Because freeing those resources issues its own GPU work, any failure in the model forward that leaves the CUDA context in a sticky error state causes the cleanup to raise a second exception while unwinding — and an exception escapingfinallyreplaces the one being propagated. The result is that a CUBLAS/illegal-memory fault originating in the attention-warmup forward (here, right after the FP8 block-scale MoE kernel runs at a small-token shape) was reported as a cache-manager error, hiding the first-order failure from the CI log and from triage.free_batch_resources()helper and the context manager now uses explicitexcept BaseException/elsearms: on the success path it frees normally, and on the failure path it frees inside a nestedtry, downgrades any secondary cleanup exception to alogger.warning, and re-raises the original error unchanged. This is diagnostic-only — it changes no resource-management semantics and does not attempt to fix the underlying kernel fault (tracked separately as an open bug and not reproducible in this environment) — so the true first-order error now surfaces in the traceback. A companion one-line test fix replaces a bareMock()withMock(_force_non_greedy_for_capture=False), since attribute auto-vivification yielded a truthy child mock that defeated the productiongetattrdefault and tripped a capture-only assertion on the non-warmup path.Test plan
Links
Dev Engineer Review
free_batch_resources().Mockattribute auto-vivification from triggering unrelated assertions.QA Engineer Review
tests/unittest/_torch/executor/test_pytorch_model_engine.py.spec_metadata._force_non_greedy_for_capture=False.