Skip to content

[https://nvbugs/6480621][fix] Preserve KV ownership in disaggregated precheck - #17223

Open
chienchunhung wants to merge 4 commits into
NVIDIA:mainfrom
chienchunhung:codex/nvbug-6480621-precheck-ownership
Open

[https://nvbugs/6480621][fix] Preserve KV ownership in disaggregated precheck#17223
chienchunhung wants to merge 4 commits into
NVIDIA:mainfrom
chienchunhung:codex/nvbug-6480621-precheck-ownership

Conversation

@chienchunhung

@chienchunhung chienchunhung commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix the disaggregated cache-transceiver precheck so synthetic KV pages remain owned until Python/NIXL transfers are proven complete. The change also aligns the precheck's model/runtime selection and verified payload boundaries with the target test.

Issue and impact

kv_transfer_sender_future_timeout_ms (1 second by default) is a polling interval, not the request-level kv_transfer_timeout_ms deadline. Previously, the Python sender's blockAll path could return after one interval while a send remained queued or in flight. The precheck treated that return as completion and released every KV page in the wave. A later wave could reuse and overwrite those pages before the earlier send finished consuming them, providing a concrete mechanism consistent with the observed timing-dependent payload mismatches.

The ownership violation is serious, but its demonstrated in-tree impact is confined to the synthetic CI precheck. That gate transfers deterministic patterns in separate, short-lived MPI processes and fails before the real model servers and benchmark start; its KV pool is destroyed before serving begins. The demonstrated impact is precheck/CI failure before benchmark execution; there is no evidence of corrupted user output from this path.

Normal PyExecutor uses finite 0/1 status polling and does not combine sender block-all with unconditional release; the C++ block-all path already waits for completion. An in-tree call-site audit found no production caller using the affected sequence. An out-of-tree caller of this internal Python API that releases storage based only on block-all return could have the same ownership hazard. The real request deadline and production finite-poll behavior are unchanged.

Changes

  • Retry Python sender block-all wait slices until successful sends complete, while detecting failure and cancellation.
  • Release precheck pages only after completion is proven; retain failed, cancelled, missing, nonterminal, and setup-error allocations until process teardown.
  • Propagate LLM_MODELS_ROOT and fail unresolved automatic KV-manager selection so the target resolves to DeepSeek V4 Pro with V2/Python, matching serving.
  • Verify only prompt KV pages transferred on the wire, excluding speculative-decoding reserve pages at exact block boundaries.
  • Add focused ownership, failure-path, configuration, launch, and MTP-boundary regressions.

Validation

This validates the 3-CTX, concurrency-180 CI proxy. The original 8-CTX, concurrency-1760 E2E workload remains a separate validation requirement.

Related PRs

  • #16674 introduced the perf-sanity cache-transceiver precheck and its original block-all/release assumption.
  • #17137 is the stacked child that restores the 60-second KV-transfer timeout and provides the targeted validation above.

Dev Engineer Review

  • Precheck KV ownership now remains retained until transfer success is confirmed.
  • Blocking sender waits retry in bounded slices and detect failure, cancellation, and auxiliary-transfer errors.
  • Prompt KV verification excludes speculative pages.
  • Automatic KV-manager resolution now fails on missing models or resolver errors.
  • Serving and precheck configuration now share model selection and LLM_MODELS_ROOT.
  • Shell environment export handling quotes model-root values safely.
  • Changes preserve non-blocking wait behavior and add targeted failure handling.
  • Review should confirm compatibility with existing transfer APIs and the separate 8-CTX, concurrency-1760 workload.
  • No test-list files were modified.

QA Engineer Review

Test-code changes include:

  • Added test_precheck_passes_mtp_exact_block_boundary.
  • Added automatic model-resolution failure tests:
    • test_resolve_model_prefs_auto_requires_registered_model
    • test_resolve_model_prefs_auto_propagates_model_default_failure
    • test_resolve_model_prefs_auto_propagates_resolver_failure
    • test_resolve_model_prefs_explicit_v1_does_not_require_model
  • Added test_precheck_commands_export_model_root_safely.
  • Added request block-view, model-resolution, and transfer ownership/error-path tests.
  • Updated bounded-polling tests for repeated waits, cancellation, failures, auxiliary transfers, and fallback timeouts.
  • Updated existing precheck E2E configuration support for mtp_draft_len.
  • Updated performance-submit tests for environment extraction and malformed exports.

No tests/integration/test_lists/ coverage entries are identified for these test functions. Verdict: needs follow-up.

@chienchunhung

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63581 [ run ] triggered by Bot. Commit: dc597f9 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63581 [ run ] completed with state FAILURE. Commit: dc597f9
/LLM/main/L0_MergeRequest_PR pipeline #51545 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

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast --stage-list "DGX_H100-PyTorch-4,A30-PyTorch-2,GB300-12_GPUs-3_Nodes-PyTorch-Disagg-PerfSanity-FUNCTIONAL-ONLY-CTX1-NODE1-GPU4-GEN1-NODE2-GPU8-1"

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63819 [ run ] triggered by Bot. Commit: 79ec91f Link to invocation

@chienchunhung
chienchunhung requested a review from nv-xtf August 4, 2026 18:34
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63819 [ run ] completed with state SUCCESS. Commit: 79ec91f
/LLM/main/L0_MergeRequest_PR pipeline #51760 (Partly Tested) completed with status: 'SUCCESS'

CI Report

Link to invocation

Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
@chienchunhung
chienchunhung force-pushed the codex/nvbug-6480621-precheck-ownership branch from c42d28b to f9ca117 Compare August 4, 2026 23:44

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63865 [ run ] triggered by Bot. Commit: f9ca117 Link to invocation

@chienchunhung
chienchunhung marked this pull request as ready for review August 4, 2026 23:57
@chienchunhung
chienchunhung requested review from a team as code owners August 4, 2026 23:57
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change propagates LLM_MODELS_ROOT, adds bounded transfer polling, tightens KV-cache manager resolution, limits verification to requested prompt blocks, and delays page release until transfers complete safely. Tests cover configuration, transfer failures, ownership, cancellation, and MTP boundaries.

Changes

Cache transceiver precheck

Layer / File(s) Summary
Model-root configuration wiring
jenkins/scripts/perf/..., tests/scripts/perf-sanity/cache_transceiver_precheck/*, tests/unittest/scripts/test_perf_submit.py, tests/unittest/others/test_cache_transceiver_precheck_config.py
The submit scripts extract and forward LLM_MODELS_ROOT. The precheck exports it with shell quoting. Tests cover parsing and shell-safe propagation.
Bounded transfer completion polling
tensorrt_llm/_torch/disaggregation/native/transfer.py, tensorrt_llm/_torch/disaggregation/transceiver.py, tests/unittest/disaggregated/test_transceiver_bounded_polling.py
TxSession.wait_complete repeats bounded waits, checks cancellation and failures, and reports terminal errors without treating them as timeouts.
Precheck validation and resource ownership
tests/scripts/perf-sanity/cache_transceiver_precheck/run_precheck.py, tests/scripts/perf-sanity/cache_transceiver_precheck/precheck_config.py, tests/scripts/perf-sanity/cache_transceiver_precheck/README.md
Automatic manager resolution now fails explicitly when unresolved. Block checks use the requested prompt length. Context and generation pages remain allocated until transfer completion and synchronization are confirmed.
Precheck and transfer regression coverage
tests/unittest/others/test_cache_transceiver_precheck_run.py, tests/unittest/disaggregated/test_cache_transceiver_precheck_e2e.py
Tests cover speculative-page exclusion, resolution behavior, transfer outcomes, ownership failures, empty waves, and an MTP exact-block-boundary scenario.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: bowenfu, chuangz0

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.99% 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 uses the required NVBugs and fix format and clearly identifies the main change: preserving KV ownership in the disaggregated precheck.
Description check ✅ Passed The description clearly explains the problem, implementation, impact, related changes, and validation results, although it does not use the template's exact section headings or 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.

🧹 Nitpick comments (2)
tests/unittest/disaggregated/test_transceiver_bounded_polling.py (1)

441-448: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case that fails a sibling between wait slices.

test_tx_session_blocking_wait_detects_failed_sibling_behind_pending_task never reaches the loop body. wait_complete calls has_failed() first, and the ERROR sibling is already present, so the method returns FAILED before any task.wait() call. The assertion pending_task.wait_calls == [] confirms this. The test therefore duplicates test_tx_session_blocking_wait_treats_task_failure_as_terminal instead of covering the in-loop has_failed() check at tensorrt_llm/_torch/disaggregation/native/transfer.py lines 1372-1375.

To cover that check, start with no failed task and flip a sibling to ERROR from inside the first wait() call.

♻️ Proposed test that exercises the in-loop sibling check
 def test_tx_session_blocking_wait_detects_failed_sibling_behind_pending_task() -> None:
     pending_task = _FakeTask(TaskStatus.TRANSFERRING, wait_result=False)
-    failed_task = _FakeTask(TaskStatus.ERROR)
+    sibling = _FakeTask(TaskStatus.TRANSFERRING, wait_result=False)
-    session = _make_tx_session([pending_task, failed_task])
+    session = _make_tx_session([pending_task, sibling])
+    wait = pending_task.wait
+
+    def fail_sibling_during_wait(timeout: Optional[float] = None) -> bool:
+        result = wait(timeout)
+        sibling.status = TaskStatus.ERROR
+        return result
+
+    pending_task.wait = fail_sibling_during_wait
 
     assert session.wait_complete(blocking=True) == WaitResult.FAILED
-    assert pending_task.wait_calls == []
-    assert failed_task.wait_calls == []
+    assert pending_task.wait_calls == [0.25]
+    assert sibling.wait_calls == []
🤖 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/disaggregated/test_transceiver_bounded_polling.py` around
lines 441 - 448, Update
test_tx_session_blocking_wait_detects_failed_sibling_behind_pending_task so no
task is initially in ERROR; make the first pending task’s wait() transition the
sibling task to ERROR, then assert wait_complete(blocking=True) returns
WaitResult.FAILED and verifies the expected wait calls. This must exercise the
in-loop has_failed() check rather than the initial pre-loop failure check.
tensorrt_llm/_torch/disaggregation/native/transfer.py (1)

1359-1394: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Remove stale WaitResult.TIMEOUT handling. The executor passes atLeastNum with a default of 0; no caller under tensorrt_llm passes None. TxSession and RxSession return only COMPLETED, FAILED, or None, so remove the unreachable TIMEOUT, timed_out, and related consensus plumbing.

🤖 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/disaggregation/native/transfer.py` around lines 1359 -
1394, Remove the obsolete WaitResult.TIMEOUT and timed_out consensus handling
from the blockAll execution path, including any related plumbing in TxSession
and RxSession. Preserve the existing COMPLETED, FAILED, and None outcomes, and
keep the atLeastNum default behavior without adding None handling.
🤖 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/disaggregation/native/transfer.py`:
- Around line 1359-1394: Remove the obsolete WaitResult.TIMEOUT and timed_out
consensus handling from the blockAll execution path, including any related
plumbing in TxSession and RxSession. Preserve the existing COMPLETED, FAILED,
and None outcomes, and keep the atLeastNum default behavior without adding None
handling.

In `@tests/unittest/disaggregated/test_transceiver_bounded_polling.py`:
- Around line 441-448: Update
test_tx_session_blocking_wait_detects_failed_sibling_behind_pending_task so no
task is initially in ERROR; make the first pending task’s wait() transition the
sibling task to ERROR, then assert wait_complete(blocking=True) returns
WaitResult.FAILED and verifies the expected wait calls. This must exercise the
in-loop has_failed() check rather than the initial pre-loop failure check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d9c1baee-87b6-4c9b-aee8-8c23bd2307af

📥 Commits

Reviewing files that changed from the base of the PR and between e409c14 and f9ca117.

📒 Files selected for processing (12)
  • jenkins/scripts/perf/local/submit.py
  • jenkins/scripts/perf/submit.py
  • tensorrt_llm/_torch/disaggregation/native/transfer.py
  • tensorrt_llm/_torch/disaggregation/transceiver.py
  • tests/scripts/perf-sanity/cache_transceiver_precheck/README.md
  • tests/scripts/perf-sanity/cache_transceiver_precheck/precheck_config.py
  • tests/scripts/perf-sanity/cache_transceiver_precheck/run_precheck.py
  • tests/unittest/disaggregated/test_cache_transceiver_precheck_e2e.py
  • tests/unittest/disaggregated/test_transceiver_bounded_polling.py
  • tests/unittest/others/test_cache_transceiver_precheck_config.py
  • tests/unittest/others/test_cache_transceiver_precheck_run.py
  • tests/unittest/scripts/test_perf_submit.py

@BowenFu

BowenFu commented Aug 5, 2026

Copy link
Copy Markdown

Could you keep the blocking path bounded and consistent before merge?

  • Use kv_transfer_timeout_ms as the overall deadline while retaining _timeout_s as the polling interval.
  • Apply the same fix to run_cache_transceiver_test.py, which also waits for all transfers and then frees the sequence.
  • When auxiliary transfer is required but aux_task is absent, do not return from the blocking path as if waiting completed.
  • Remove WaitResult.TIMEOUT and the associated timed_out plumbing if no path can return that value after this change.

Without these changes, a stalled peer can wait forever or pages can still be released before transfer completion.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63865 [ run ] completed with state SUCCESS. Commit: f9ca117
/LLM/main/L0_MergeRequest_PR pipeline #51805 completed with status: 'FAILURE'

CI Report

⚠️ Multi-GPU Label Required:
Multi-GPU tests require the ci: full pre-merge approved label on this PR. Ask a member of NVIDIA/trt-llm-ci-approvers to add the label, then re-trigger CI with the same bot command (no rebase needed).

⚠️ 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

@Shixiaowei02

Copy link
Copy Markdown
Collaborator

The blocking wait now has no deadline left, while the receive side still bounds itself. Serving never takes that path, but the harnesses and precheck do, a stalled peer hangs to the stage limit instead of timing out.

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