Skip to content

[GG] Fix MRV2 profiled CUDA graph pool lifecycle - #180

Merged
lukealonso merged 1 commit into
local-inference-lab:dev/gilded-gnosisfrom
voipmonitor:fix/gg-mrv2-profile-pool-lifecycle-20260726
Jul 27, 2026
Merged

[GG] Fix MRV2 profiled CUDA graph pool lifecycle#180
lukealonso merged 1 commit into
local-inference-lab:dev/gilded-gnosisfrom
voipmonitor:fix/gg-mrv2-profile-pool-lifecycle-20260726

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Jul 26, 2026

Copy link
Copy Markdown

Summary

Preserve the CUDA graph private-pool lifecycle across MRV2 graph-memory
profiling and production capture.

The previous implementation profiled into a graph pool, reset every graph,
kept allocations from that pool alive, and then attempted to capture new
graphs into the same pool handle. PyTorch's caching allocator rejects that
state: once the last graph reference is gone, a private pool with live
allocations has use_count == 0 and cannot be reopened.

This change:

  • profiles into the production graph pool so retained pages are reusable;
  • captures one allocation-free anchor graph before profiling graphs are reset;
  • keeps the anchor alive until production graphs replace it;
  • releases the anchor on success, skipped capture, and capture failure;
  • reserves the complete graph high-water mark because retained pool pages are
    PyTorch-reserved and the outer profiler intentionally restores the pre-graph
    Torch peak.

There is no tail padding, allocator over-read allowance, disabled graph mode,
or backend-specific workaround in this PR.

Why a new PR

This supersedes #168. That PR is hosted on an external fork that the current
maintainer account cannot update, and its one-line pool reuse is incomplete:
it reproduces PyTorch's use_count > 0 allocator assertion when retained
allocations outlive the last profiling graph. This PR retains the valid pool
reuse idea and adds the missing lifecycle and accounting contract.

Upstream PR vllm-project#47925 was also checked. It pre-sizes model output
staging buffers and does not address private-pool teardown/reopen semantics, so
this is not duplicate work.

Root-cause proof

A minimal CUDA repro holds an output allocated by a private graph pool, resets
the last graph, empties the cache, and recaptures into the same pool handle.
Without a live graph it hits the same allocator assertion. A one-kernel anchor
kept alive across teardown makes recapture succeed without adding pool storage.

The unit tests cover pool identity, anchor ordering, full high-water accounting,
and anchor release when production capture throws.

Validation

Exact PR-only image:

vLLM base: dev/gilded-gnosis@89b4a98d1ffebb2dda1e1ac5e55238e3a9cfbd58
SparkInfer base: master@c39b8062ba450c030e669d898a026d10980c9470
image: local/vllm:gilded-gnosis-issue34-mrv2-pool-lifecycle-vllm89b4a98-sic39b806-20260726
  • git diff --check
  • Python bytecode compilation of both changed files
  • tests/v1/cudagraph/test_breakable_cudagraph.py and
    tests/v1/worker/test_gpu_worker.py: 27 passed
  • minimal private-pool reset/recapture GPU repro: passed with a live anchor

The complete
rtx6kpro#34
stack was then tested in an isolated image containing only current GG, #172,
this runtime change, and SparkInfer #76:

local/vllm:gilded-gnosis-v20-issue34-pool-anchor-poc-vllm2d3b70c-si0c98d6b-fi801d57a-cu132-20260726
Gate Result
TP8/DCP1/MTP0, Luke NVFP4 A16 87.716 aggregate tok/s; prior candidate 87.744; no measurable regression
TP4/DCP4/MTP3 NF3, GMU 0.98, graph 64 booted; 563,712 KV tokens; 300,017-token prefill plus 16 decode completed; server remained healthy
TP8/DCP2/MTP3 A4 online MXFP8, ring DMA 300,066-token prefill plus 512 decode completed in 65.924 s; server remained healthy

The DCP2 profile measured 1.00 GiB total graph high-water with 0.72 GiB already
retained in the reusable pool and produced 1,102,720 KV tokens. The older
gross - retained calculation reported more KV because it omitted those live
PyTorch-reserved pages; that was overcommit, not free capacity. Operators can
recover equivalent KV capacity by raising GMU by the logged graph-memory delta
(0.91 to approximately 0.9205 in this test).

No new GPU fault or server error appeared in either long-context gate.

AI assistance disclosure

Root-cause analysis, implementation, tests, hardware validation, and this
write-up were prepared with OpenAI Codex assistance and reviewed by the human
submitter.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@voipmonitor, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8943bb91-d27c-471b-a600-ca1bb33a427f

📥 Commits

Reviewing files that changed from the base of the PR and between 7538b65 and 0b25fdb.

📒 Files selected for processing (2)
  • tests/v1/cudagraph/test_breakable_cudagraph.py
  • vllm/v1/worker/gpu/model_runner.py
📝 Walkthrough

Walkthrough

Changes

The CUDA graph memory profiling path now reuses the production graph pool through a temporary pool anchor. Production capture releases the anchor on skipped, successful, and failed capture paths, with tests covering pool bindings, lifecycle events, and exception cleanup.

CUDA graph pool anchor lifecycle

Layer / File(s) Summary
Anchor creation and release
vllm/v1/worker/gpu/model_runner.py
Creates and stores a CUDA graph pool anchor, then resets the graph and removes its token during release.
Production pool profiling
vllm/v1/worker/gpu/model_runner.py, tests/v1/cudagraph/test_breakable_cudagraph.py
Profiles against the global production pool, preserves manager and wrapper pool bindings, retains the pool anchor, destroys profiling graphs, and updates memory accounting.
Capture cleanup guarantees
vllm/v1/worker/gpu/model_runner.py, tests/v1/cudagraph/test_breakable_cudagraph.py
Releases the anchor when capture is skipped or completes, including when model capture raises an exception.

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

Sequence Diagram(s)

sequenceDiagram
  participant GPUModelRunner
  participant GraphManagers
  participant CUDA
  GPUModelRunner->>CUDA: obtain global production pool
  GPUModelRunner->>GraphManagers: profile and capture using production pool
  GPUModelRunner->>CUDA: create pool anchor
  GPUModelRunner->>GraphManagers: destroy profiling graphs
  GPUModelRunner->>GraphManagers: run production capture
  GraphManagers-->>GPUModelRunner: success or failure
  GPUModelRunner->>CUDA: release pool anchor
Loading

Possibly related PRs

Suggested reviewers: woosukkwon, benchislett, njhill

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: fixing the profiled CUDA graph pool lifecycle.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
vllm/v1/worker/gpu/model_runner.py (1)

154-158: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a Google-style docstring.

Add Args: and Returns: sections for pool, device, and the retained graph/token pair.

Proposed fix
-    """Keep a graph-private pool live between profiling and real capture.
+    """Keep a graph-private pool live between profiling and real capture.
 
     PyTorch cannot reopen a pool whose last graph was reset while allocations
     remain. This tiny graph holds the pool reference until production capture.
+
+    Args:
+        pool: CUDA graph pool to retain.
+        device: CUDA device on which to create the anchor.
+
+    Returns:
+        The anchor CUDA graph and its retained token tensor.
     """

As per coding guidelines, Python code must use Google-style docstrings with Args:/Returns:/Raises: sections.

🤖 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 `@vllm/v1/worker/gpu/model_runner.py` around lines 154 - 158, Update the
docstring for the helper that retains the graph-private pool to Google style,
adding an Args: section documenting pool and device and a Returns: section
documenting the retained graph/token pair; preserve the existing lifecycle
explanation.

Source: Coding guidelines

tests/v1/cudagraph/test_breakable_cudagraph.py (1)

220-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the skipped-capture cleanup path.

The suite covers failed capture but not needs_capture() == False, so the new release at Line 1014 is unverified. Add a fake manager returning False and assert the anchor is reset and cleared.

🤖 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/v1/cudagraph/test_breakable_cudagraph.py` around lines 220 - 263, Add a
test for the capture-skipped path in GPUModelRunner.capture_model using a fake
cudagraph manager whose needs_capture() returns False; initialize
_cudagraph_pool_anchor with a reset tracker, invoke capture_model(), and assert
the anchor is reset, cleared, and no capture event occurs.
🤖 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 `@vllm/v1/worker/gpu/model_runner.py`:
- Around line 966-969: Update shutdown() to release and clear
_cudagraph_pool_anchor after shutdown synchronization completes, including the
path where startup exits before capture_model(). Preserve the existing assertion
and anchor creation flow in the initialization code.

---

Nitpick comments:
In `@tests/v1/cudagraph/test_breakable_cudagraph.py`:
- Around line 220-263: Add a test for the capture-skipped path in
GPUModelRunner.capture_model using a fake cudagraph manager whose
needs_capture() returns False; initialize _cudagraph_pool_anchor with a reset
tracker, invoke capture_model(), and assert the anchor is reset, cleared, and no
capture event occurs.

In `@vllm/v1/worker/gpu/model_runner.py`:
- Around line 154-158: Update the docstring for the helper that retains the
graph-private pool to Google style, adding an Args: section documenting pool and
device and a Returns: section documenting the retained graph/token pair;
preserve the existing lifecycle explanation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 804e2e9d-5ca5-4115-a51c-452357555469

📥 Commits

Reviewing files that changed from the base of the PR and between 89b4a98 and 7538b65.

📒 Files selected for processing (2)
  • tests/v1/cudagraph/test_breakable_cudagraph.py
  • vllm/v1/worker/gpu/model_runner.py

Comment thread vllm/v1/worker/gpu/model_runner.py
@voipmonitor
voipmonitor force-pushed the fix/gg-mrv2-profile-pool-lifecycle-20260726 branch from 7538b65 to 7fce7ed Compare July 26, 2026 10:47
@voipmonitor

voipmonitor commented Jul 26, 2026

Copy link
Copy Markdown
Author

Review follow-up finalized in 0b25fdb376: added Google-style Args/Returns documentation, covered skipped capture and early shutdown, and retained the failed-capture cleanup test. Exact-image focused suites: 27 passed; formatting, lint, and all non-mypy pre-commit hooks pass. The only remaining mypy finding (defer_copy_event: bool | None) reproduces unchanged on the exact GG base 89b4a98d1f; this PR introduces no new mypy findings. No runtime-path arithmetic or graph policy changed in the follow-up.

Reuse the production pool during graph-memory profiling, keep it alive through production recapture, and account the full graph high-water mark.

Assisted-by: OpenAI Codex
Signed-off-by: Martin Vit <martin@voipmonitor.org>
@voipmonitor
voipmonitor force-pushed the fix/gg-mrv2-profile-pool-lifecycle-20260726 branch from 7fce7ed to 0b25fdb Compare July 26, 2026 11:00
@lukealonso
lukealonso merged commit 1391c9c into local-inference-lab:dev/gilded-gnosis Jul 27, 2026
1 check passed
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